This commit is contained in:
Daniel Graf
2025-11-02 07:27:08 +01:00
committed by GitHub
parent d29d65195b
commit fc0058c97a
11 changed files with 78 additions and 64 deletions

View File

@@ -2,14 +2,11 @@ package com.dedicatedcode.reitti.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
@@ -32,25 +29,15 @@ public class WebConfig implements WebMvcConfigurer {
resolver.setCookiePath("/");
return resolver;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("lang");
return interceptor;
}
@Bean
public ReloadableResourceBundleMessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setCacheSeconds(3600);
return messageSource;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
//
//
// @Bean
// public ReloadableResourceBundleMessageSource messageSource() {
// ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
// messageSource.setBasename("classpath:messages");
// messageSource.setDefaultEncoding("UTF-8");
// messageSource.setCacheSeconds(3600);
// return messageSource;
// }
}

View File

@@ -13,12 +13,15 @@ import com.dedicatedcode.reitti.model.security.TokenUser;
import com.dedicatedcode.reitti.model.security.User;
import com.dedicatedcode.reitti.repository.ProcessedVisitJdbcService;
import com.dedicatedcode.reitti.repository.TripJdbcService;
import com.dedicatedcode.reitti.service.I18nService;
import com.dedicatedcode.reitti.service.MagicLinkTokenService;
import com.dedicatedcode.reitti.service.MemoryService;
import com.dedicatedcode.reitti.service.RequestHelper;
import com.dedicatedcode.reitti.service.integration.ImmichIntegrationService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@@ -36,23 +39,27 @@ import static com.dedicatedcode.reitti.model.Role.USER;
@Controller
@RequestMapping("/memories")
public class MemoryController {
private static final Logger log = LoggerFactory.getLogger(MemoryController.class);
private final MemoryService memoryService;
private final TripJdbcService tripJdbcService;
private final ProcessedVisitJdbcService processedVisitJdbcService;
private final ImmichIntegrationService immichIntegrationService;
private final MagicLinkTokenService magicLinkTokenService;
private final I18nService i18n;
public MemoryController(MemoryService memoryService,
TripJdbcService tripJdbcService,
ProcessedVisitJdbcService processedVisitJdbcService,
ImmichIntegrationService immichIntegrationService,
MagicLinkTokenService magicLinkTokenService) {
MagicLinkTokenService magicLinkTokenService,
I18nService i18n) {
this.memoryService = memoryService;
this.tripJdbcService = tripJdbcService;
this.processedVisitJdbcService = processedVisitJdbcService;
this.immichIntegrationService = immichIntegrationService;
this.magicLinkTokenService = magicLinkTokenService;
this.i18n = i18n;
}
@GetMapping
@@ -156,12 +163,13 @@ public class MemoryController {
@RequestParam LocalDate startDate,
@RequestParam LocalDate endDate,
@RequestParam(required = false) String headerImageUrl,
@RequestParam(required = false) String year,
@RequestParam(required = false, defaultValue = "UTC") ZoneId timezone,
Model model,
HttpServletResponse response) {
if (title == null || title.trim().isEmpty()) {
model.addAttribute("error", "memory.validation.title.required");
model.addAttribute("error", i18n.translate("memory.validation.title.required"));
model.addAttribute("title", title);
model.addAttribute("description", description);
model.addAttribute("startDate", startDate);
@@ -177,23 +185,27 @@ public class MemoryController {
// Validate dates are not in the future
if (start.isAfter(today) || end.isAfter(today)) {
model.addAttribute("error", "memory.validation.date.future");
model.addAttribute("error", i18n.translate("memory.validation.date.future"));
model.addAttribute("title", title);
model.addAttribute("description", description);
model.addAttribute("startDate", startDate);
model.addAttribute("endDate", endDate);
model.addAttribute("headerImageUrl", headerImageUrl);
model.addAttribute("year", year);
return "memories/new :: new-memory";
}
// Validate end date is not before start date
if (end.isBefore(start)) {
model.addAttribute("error", "memory.validation.end.date.before.start");
model.addAttribute("error", i18n.translate("memory.validation.end.date.before.start"));
model.addAttribute("title", title);
model.addAttribute("description", description);
model.addAttribute("startDate", startDate);
model.addAttribute("endDate", endDate);
model.addAttribute("headerImageUrl", headerImageUrl);
model.addAttribute("year", year);
return "memories/new :: new-memory";
}
@@ -212,12 +224,15 @@ public class MemoryController {
return "memories/fragments :: empty";
} catch (Exception e) {
model.addAttribute("error", "memory.validation.start.date.required");
log.error("Error creating memory", e);
model.addAttribute("error", i18n.translate("memory.creation.error", e.getMessage()));
model.addAttribute("title", title);
model.addAttribute("description", description);
model.addAttribute("startDate", startDate);
model.addAttribute("endDate", endDate);
model.addAttribute("headerImageUrl", headerImageUrl);
model.addAttribute("year", year);
return "memories/new :: new-memory";
}
}
@@ -261,7 +276,7 @@ public class MemoryController {
// Add validation similar to create method
if (title == null || title.trim().isEmpty()) {
model.addAttribute("error", "memory.validation.title.required");
model.addAttribute("error", i18n.translate("memory.validation.title.required"));
model.addAttribute("memory", memory);
model.addAttribute("cancelEndpoint", "/memories/" + id);
@@ -276,7 +291,7 @@ public class MemoryController {
Instant today = Instant.now();
if (start.isAfter(today) || end.isAfter(today)) {
model.addAttribute("error", "memory.validation.date.future");
model.addAttribute("error", i18n.translate("memory.validation.date.future"));
model.addAttribute("memory", memory);
model.addAttribute("cancelEndpoint", "/memories/" + id);
@@ -286,7 +301,7 @@ public class MemoryController {
}
if (end.isBefore(start)) {
model.addAttribute("error", "memory.validation.end.date.before.start");
model.addAttribute("error", i18n.translate("memory.validation.end.date.before.start"));
model.addAttribute("memory", memory);
model.addAttribute("cancelEndpoint", "/memories/" + id);
@@ -310,7 +325,7 @@ public class MemoryController {
return "memories/view :: memory-header";
} catch (Exception e) {
model.addAttribute("error", "memory.validation.start.date.required");
model.addAttribute("error", i18n.translate("memory.validation.start.date.required"));
model.addAttribute("memory", memory);
model.addAttribute("cancelEndpoint", "/memories/" + id);

View File

@@ -246,7 +246,7 @@ public class MemoryBlockGenerationService {
}
log.info("Generated {} memory block parts", blockParts.size());
return blockParts;
}

View File

@@ -14,6 +14,7 @@ logging.level.com.dedicatedcode.reitti = INFO
spring.messages.basename=messages
spring.messages.encoding=UTF-8
spring.messages.cache-duration=3600
spring.messages.fallback-to-system-locale=false
# PostgreSQL configuration
spring.datasource.url=jdbc:postgresql://localhost:5432/reittidb

View File

@@ -738,6 +738,7 @@ language.english=English
language.finnish=Finnish
language.german=German
language.french=French
language.russian=Russian
language.brazilian_portuguese=Portuguese (Brazil)
# Login page
@@ -826,6 +827,7 @@ error.action.back=Go Back
error.action.retry=Try Again
# Memory validation
memory.creation.error=Error creating your memory: {0}
memory.validation.start.date.required=Start date is required
memory.validation.end.date.required=End date is required
memory.validation.end.date.before.start=End date cannot be before start date

View File

@@ -24,14 +24,22 @@
object-fit: cover;
border-radius: 50%;
font-size: 1.6rem;
background: var(--color-highlight);
font-family: var(--serif-font);
}
.avatar-marker-fallback {
font-size: 14px;
font-weight: bold;
color: #666;
text-align: center;
line-height: 1;
.avatar-marker-img:after {
content: attr(alt);
top: 0;
position: absolute;
background: white;
left: 0;
width: 100%;
height: 100%;
padding-top: 2px;
padding-left: 1px;
font-family: var(--serif-font);
}
@keyframes avatar-pulse {

View File

@@ -1548,6 +1548,7 @@ button:disabled {
transition: border-color 0.2s, box-shadow 0.2s;
display: inline-block;
position: relative;
overflow: hidden;
}
.avatar.small {
@@ -1560,14 +1561,16 @@ button:disabled {
}
.avatar::after {
content: attr(alt);
font-size: 36px;
font-family: var(--serif-font);
font-weight: lighter;
color: white;
font-size: 2.4rem;
top: 0;
position: absolute;
top: 8px;
width: 64px;
height: 64px;
background: var(--color-background-dark);
left: 0;
width: 100%;
height: 100%;
padding-top: 10px;
font-family: var(--serif-font);
}
.avatar:not(:has(img))::after {
@@ -1855,6 +1858,10 @@ button:disabled {
.coordinate-input {
min-width: unset;
}
.auto-update-overlay {
width: 80%;
}
}
.memories-overview {
@@ -2345,14 +2352,6 @@ button:disabled {
margin-bottom: 10px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--color-highlight), #ffd700);
width: 20%;
transition: width 2s ease;
border-radius: 3px;
}
.progress-text {
color: #ccc;
font-size: 0.9rem;
@@ -2371,4 +2370,5 @@ button:disabled {
transform: translateX(300%);
width: 30%;
}
}
}

View File

@@ -223,6 +223,11 @@
<span class="language-flag">🇫🇷</span>
<span th:text="#{language.french}" class="language-name">Français</span>
</label>
<label class="btn language-btn">
<input type="radio" name="preferred_language" value="ru" th:checked="${selectedLanguage == 'ru'}" style="display: none;">
<span class="language-flag">🇷🇺</span>
<span th:text="#{language.russian}" class="language-name">Russian</span>
</label>
</div>
</div>
</div>

View File

@@ -29,7 +29,7 @@
<div id="edit-fragment" class="memory-header" th:fragment="edit-memory">
<div th:if="${error}" class="alert alert-error">
<i class="lni lni-warning"></i>
<span th:text="#{${error}}">Error message</span>
<span th:text="${error}">Error message</span>
</div>
<form th:attr="hx-post=@{/memories/{id}(id=${memory.id})}" hx-vals="js:{timezone: getUserTimezone()}" method="post" class="memory-form" hx-swap="outerHTML" hx-target=".memory-header">

View File

@@ -33,13 +33,14 @@
<div th:fragment="new-memory" class="settings-content-area">
<div th:if="${error}" class="alert alert-error">
<i class="lni lni-warning"></i>
<span th:text="#{${error}}">Error message</span>
<span th:text="${error}">Error message</span>
</div>
<form th:attr="hx-post=@{/memories}" class="memory-form" hx-indicator="#memory-processing-overlay" hx-vals='js:{"timezone": getUserTimezone()}' onsubmit="return validateDates()">
<form th:attr="hx-post=@{/memories}" class="memory-form" hx-indicator="#memory-processing-overlay" hx-swap="outerHTML" hx-target="closest .settings-content-area" hx-vals='js:{"timezone": getUserTimezone()}' onsubmit="return validateDates()">
<div class="form-group">
<label for="title" th:text="#{memory.form.title.label}">Title *</label>
<input type="text" id="title" name="title" required class="form-control" th:placeholder="#{memory.form.title.placeholder}" th:value="${title}">
<input type="hidden" name="year" th:value="${year}">
</div>
<div class="form-group">

View File

@@ -592,12 +592,7 @@
rawLocationLoader.init(userConfigs);
// Load location data for the memory date range without bounds filtering
rawLocationLoader.loadForDateRange(
memory.startDate,
memory.endDate,
false, // autoUpdateMode
false // withBounds - don't filter by current map bounds
);
rawLocationLoader.loadForDateRange(false, false);
}
}
}