Add report templates and unify template storage
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
package de.svencarstensen.muh.domain;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Document("reportTemplates")
|
||||
public record ReportTemplate(
|
||||
@Id String userId,
|
||||
List<InvoiceTemplateElement> elements,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package de.svencarstensen.muh.domain;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Document("templates")
|
||||
@CompoundIndex(name = "user_template_type_unique", def = "{'userId': 1, 'type': 1}", unique = true)
|
||||
public record Template(
|
||||
@Id String id,
|
||||
String userId,
|
||||
TemplateType type,
|
||||
List<InvoiceTemplateElement> elements,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package de.svencarstensen.muh.domain;
|
||||
|
||||
public enum TemplateType {
|
||||
INVOICE,
|
||||
REPORT
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.svencarstensen.muh.repository;
|
||||
|
||||
import de.svencarstensen.muh.domain.ReportTemplate;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
|
||||
public interface ReportTemplateRepository extends MongoRepository<ReportTemplate, String> {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.svencarstensen.muh.repository;
|
||||
|
||||
import de.svencarstensen.muh.domain.Template;
|
||||
import de.svencarstensen.muh.domain.TemplateType;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface TemplateRepository extends MongoRepository<Template, String> {
|
||||
|
||||
Optional<Template> findByUserIdAndType(String userId, TemplateType type);
|
||||
}
|
||||
@@ -3,8 +3,11 @@ package de.svencarstensen.muh.service;
|
||||
import de.svencarstensen.muh.domain.AppUser;
|
||||
import de.svencarstensen.muh.domain.InvoiceTemplate;
|
||||
import de.svencarstensen.muh.domain.InvoiceTemplateElement;
|
||||
import de.svencarstensen.muh.domain.Template;
|
||||
import de.svencarstensen.muh.domain.TemplateType;
|
||||
import de.svencarstensen.muh.repository.AppUserRepository;
|
||||
import de.svencarstensen.muh.repository.InvoiceTemplateRepository;
|
||||
import de.svencarstensen.muh.repository.TemplateRepository;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -12,40 +15,52 @@ import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
public class InvoiceTemplateService {
|
||||
|
||||
private static final TemplateType TEMPLATE_TYPE = TemplateType.INVOICE;
|
||||
|
||||
private final AppUserRepository appUserRepository;
|
||||
private final TemplateRepository templateRepository;
|
||||
private final InvoiceTemplateRepository invoiceTemplateRepository;
|
||||
|
||||
public InvoiceTemplateService(
|
||||
AppUserRepository appUserRepository,
|
||||
TemplateRepository templateRepository,
|
||||
InvoiceTemplateRepository invoiceTemplateRepository
|
||||
) {
|
||||
this.appUserRepository = appUserRepository;
|
||||
this.templateRepository = templateRepository;
|
||||
this.invoiceTemplateRepository = invoiceTemplateRepository;
|
||||
}
|
||||
|
||||
public InvoiceTemplateResponse currentTemplate(String actorId) {
|
||||
String userId = requireActorId(actorId);
|
||||
requireActiveUser(userId);
|
||||
return invoiceTemplateRepository.findById(userId)
|
||||
return templateRepository.findByUserIdAndType(userId, TEMPLATE_TYPE)
|
||||
.map(this::toResponse)
|
||||
.or(() -> invoiceTemplateRepository.findById(userId).map(this::toLegacyResponse))
|
||||
.orElseGet(() -> new InvoiceTemplateResponse(false, List.of(), null));
|
||||
}
|
||||
|
||||
public InvoiceTemplateResponse saveTemplate(String actorId, List<InvoiceTemplateElementPayload> payloadElements) {
|
||||
String userId = requireActorId(actorId);
|
||||
requireActiveUser(userId);
|
||||
InvoiceTemplate existing = invoiceTemplateRepository.findById(userId).orElse(null);
|
||||
Template existing = templateRepository.findByUserIdAndType(userId, TEMPLATE_TYPE).orElse(null);
|
||||
InvoiceTemplate legacyTemplate = existing == null
|
||||
? invoiceTemplateRepository.findById(userId).orElse(null)
|
||||
: null;
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
InvoiceTemplate saved = invoiceTemplateRepository.save(new InvoiceTemplate(
|
||||
Template saved = templateRepository.save(new Template(
|
||||
existing != null ? existing.id() : templateId(userId),
|
||||
userId,
|
||||
TEMPLATE_TYPE,
|
||||
sanitizeElements(payloadElements),
|
||||
existing != null ? existing.createdAt() : now,
|
||||
existing != null ? existing.createdAt() : legacyTemplate != null ? legacyTemplate.createdAt() : now,
|
||||
now
|
||||
));
|
||||
return toResponse(saved);
|
||||
@@ -108,11 +123,12 @@ public class InvoiceTemplateService {
|
||||
);
|
||||
}
|
||||
|
||||
private InvoiceTemplateResponse toResponse(InvoiceTemplate template) {
|
||||
List<InvoiceTemplateElementPayload> elements = template.elements() == null
|
||||
? List.of()
|
||||
: template.elements().stream().map(this::toPayload).toList();
|
||||
return new InvoiceTemplateResponse(true, elements, template.updatedAt());
|
||||
private InvoiceTemplateResponse toResponse(Template template) {
|
||||
return toResponse(template.elements(), template.updatedAt());
|
||||
}
|
||||
|
||||
private InvoiceTemplateResponse toLegacyResponse(InvoiceTemplate template) {
|
||||
return toResponse(template.elements(), template.updatedAt());
|
||||
}
|
||||
|
||||
private InvoiceTemplateElementPayload toPayload(InvoiceTemplateElement element) {
|
||||
@@ -152,6 +168,17 @@ public class InvoiceTemplateService {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
private InvoiceTemplateResponse toResponse(List<InvoiceTemplateElement> elements, LocalDateTime updatedAt) {
|
||||
List<InvoiceTemplateElementPayload> payloads = elements == null
|
||||
? List.of()
|
||||
: elements.stream().map(this::toPayload).toList();
|
||||
return new InvoiceTemplateResponse(true, payloads, updatedAt);
|
||||
}
|
||||
|
||||
private String templateId(String userId) {
|
||||
return userId + ":" + TEMPLATE_TYPE.name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
public record InvoiceTemplateElementPayload(
|
||||
String id,
|
||||
String paletteId,
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
package de.svencarstensen.muh.service;
|
||||
|
||||
import de.svencarstensen.muh.domain.AppUser;
|
||||
import de.svencarstensen.muh.domain.InvoiceTemplateElement;
|
||||
import de.svencarstensen.muh.domain.ReportTemplate;
|
||||
import de.svencarstensen.muh.domain.Template;
|
||||
import de.svencarstensen.muh.domain.TemplateType;
|
||||
import de.svencarstensen.muh.repository.AppUserRepository;
|
||||
import de.svencarstensen.muh.repository.ReportTemplateRepository;
|
||||
import de.svencarstensen.muh.repository.TemplateRepository;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
public class ReportTemplateService {
|
||||
|
||||
private static final TemplateType TEMPLATE_TYPE = TemplateType.REPORT;
|
||||
|
||||
private final AppUserRepository appUserRepository;
|
||||
private final TemplateRepository templateRepository;
|
||||
private final ReportTemplateRepository reportTemplateRepository;
|
||||
|
||||
public ReportTemplateService(
|
||||
AppUserRepository appUserRepository,
|
||||
TemplateRepository templateRepository,
|
||||
ReportTemplateRepository reportTemplateRepository
|
||||
) {
|
||||
this.appUserRepository = appUserRepository;
|
||||
this.templateRepository = templateRepository;
|
||||
this.reportTemplateRepository = reportTemplateRepository;
|
||||
}
|
||||
|
||||
public InvoiceTemplateService.InvoiceTemplateResponse currentTemplate(String actorId) {
|
||||
String userId = requireActorId(actorId);
|
||||
requireActiveUser(userId);
|
||||
return templateRepository.findByUserIdAndType(userId, TEMPLATE_TYPE)
|
||||
.map(this::toResponse)
|
||||
.or(() -> reportTemplateRepository.findById(userId).map(this::toLegacyResponse))
|
||||
.orElseGet(() -> new InvoiceTemplateService.InvoiceTemplateResponse(false, List.of(), null));
|
||||
}
|
||||
|
||||
public InvoiceTemplateService.InvoiceTemplateResponse saveTemplate(
|
||||
String actorId,
|
||||
List<InvoiceTemplateService.InvoiceTemplateElementPayload> payloadElements
|
||||
) {
|
||||
String userId = requireActorId(actorId);
|
||||
requireActiveUser(userId);
|
||||
Template existing = templateRepository.findByUserIdAndType(userId, TEMPLATE_TYPE).orElse(null);
|
||||
ReportTemplate legacyTemplate = existing == null
|
||||
? reportTemplateRepository.findById(userId).orElse(null)
|
||||
: null;
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
Template saved = templateRepository.save(new Template(
|
||||
existing != null ? existing.id() : templateId(userId),
|
||||
userId,
|
||||
TEMPLATE_TYPE,
|
||||
sanitizeElements(payloadElements),
|
||||
existing != null ? existing.createdAt() : legacyTemplate != null ? legacyTemplate.createdAt() : now,
|
||||
now
|
||||
));
|
||||
return toResponse(saved);
|
||||
}
|
||||
|
||||
private AppUser requireActiveUser(@NonNull String actorId) {
|
||||
return appUserRepository.findById(actorId)
|
||||
.filter(AppUser::active)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "Nicht berechtigt"));
|
||||
}
|
||||
|
||||
private @NonNull String requireActorId(String actorId) {
|
||||
if (isBlank(actorId)) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Nicht berechtigt");
|
||||
}
|
||||
String sanitized = Objects.requireNonNull(actorId).trim();
|
||||
return Objects.requireNonNull(sanitized);
|
||||
}
|
||||
|
||||
private List<InvoiceTemplateElement> sanitizeElements(
|
||||
List<InvoiceTemplateService.InvoiceTemplateElementPayload> payloadElements
|
||||
) {
|
||||
if (payloadElements == null || payloadElements.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return payloadElements.stream()
|
||||
.map(this::sanitizeElement)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private InvoiceTemplateElement sanitizeElement(InvoiceTemplateService.InvoiceTemplateElementPayload payload) {
|
||||
if (payload == null
|
||||
|| isBlank(payload.id())
|
||||
|| isBlank(payload.paletteId())
|
||||
|| isBlank(payload.kind())
|
||||
|| payload.x() == null
|
||||
|| payload.y() == null
|
||||
|| payload.width() == null
|
||||
|| payload.fontSize() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new InvoiceTemplateElement(
|
||||
payload.id().trim(),
|
||||
payload.paletteId().trim(),
|
||||
payload.kind().trim(),
|
||||
trimOrEmpty(payload.label()),
|
||||
nullToEmpty(payload.content()),
|
||||
payload.x(),
|
||||
payload.y(),
|
||||
payload.width(),
|
||||
payload.height(),
|
||||
payload.fontSize(),
|
||||
payload.fontWeight(),
|
||||
blankToNull(payload.textAlign()),
|
||||
blankToNull(payload.lineOrientation()),
|
||||
blankToNull(payload.imageSrc()),
|
||||
payload.imageNaturalWidth(),
|
||||
payload.imageNaturalHeight()
|
||||
);
|
||||
}
|
||||
|
||||
private InvoiceTemplateService.InvoiceTemplateResponse toResponse(Template template) {
|
||||
return toResponse(template.elements(), template.updatedAt());
|
||||
}
|
||||
|
||||
private InvoiceTemplateService.InvoiceTemplateResponse toLegacyResponse(ReportTemplate template) {
|
||||
return toResponse(template.elements(), template.updatedAt());
|
||||
}
|
||||
|
||||
private InvoiceTemplateService.InvoiceTemplateElementPayload toPayload(InvoiceTemplateElement element) {
|
||||
return new InvoiceTemplateService.InvoiceTemplateElementPayload(
|
||||
element.id(),
|
||||
element.paletteId(),
|
||||
element.kind(),
|
||||
element.label(),
|
||||
element.content(),
|
||||
element.x(),
|
||||
element.y(),
|
||||
element.width(),
|
||||
element.height(),
|
||||
element.fontSize(),
|
||||
element.fontWeight(),
|
||||
element.textAlign(),
|
||||
element.lineOrientation(),
|
||||
element.imageSrc(),
|
||||
element.imageNaturalWidth(),
|
||||
element.imageNaturalHeight()
|
||||
);
|
||||
}
|
||||
|
||||
private boolean isBlank(String value) {
|
||||
return value == null || value.isBlank();
|
||||
}
|
||||
|
||||
private String blankToNull(String value) {
|
||||
return isBlank(value) ? null : value.trim();
|
||||
}
|
||||
|
||||
private String trimOrEmpty(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
private String nullToEmpty(String value) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
private InvoiceTemplateService.InvoiceTemplateResponse toResponse(
|
||||
List<InvoiceTemplateElement> elements,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
List<InvoiceTemplateService.InvoiceTemplateElementPayload> payloads = elements == null
|
||||
? List.of()
|
||||
: elements.stream().map(this::toPayload).toList();
|
||||
return new InvoiceTemplateService.InvoiceTemplateResponse(true, payloads, updatedAt);
|
||||
}
|
||||
|
||||
private String templateId(String userId) {
|
||||
return userId + ":" + TEMPLATE_TYPE.name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package de.svencarstensen.muh.web;
|
||||
|
||||
import de.svencarstensen.muh.service.CatalogService;
|
||||
import de.svencarstensen.muh.service.InvoiceTemplateService;
|
||||
import de.svencarstensen.muh.service.ReportTemplateService;
|
||||
import de.svencarstensen.muh.security.SecuritySupport;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -19,15 +20,18 @@ public class SessionController {
|
||||
|
||||
private final CatalogService catalogService;
|
||||
private final InvoiceTemplateService invoiceTemplateService;
|
||||
private final ReportTemplateService reportTemplateService;
|
||||
private final SecuritySupport securitySupport;
|
||||
|
||||
public SessionController(
|
||||
CatalogService catalogService,
|
||||
InvoiceTemplateService invoiceTemplateService,
|
||||
ReportTemplateService reportTemplateService,
|
||||
SecuritySupport securitySupport
|
||||
) {
|
||||
this.catalogService = catalogService;
|
||||
this.invoiceTemplateService = invoiceTemplateService;
|
||||
this.reportTemplateService = reportTemplateService;
|
||||
this.securitySupport = securitySupport;
|
||||
}
|
||||
|
||||
@@ -41,6 +45,11 @@ public class SessionController {
|
||||
return invoiceTemplateService.currentTemplate(securitySupport.currentUser().id());
|
||||
}
|
||||
|
||||
@GetMapping("/report-template")
|
||||
public InvoiceTemplateService.InvoiceTemplateResponse currentReportTemplate() {
|
||||
return reportTemplateService.currentTemplate(securitySupport.currentUser().id());
|
||||
}
|
||||
|
||||
@PostMapping("/password-login")
|
||||
public CatalogService.SessionResponse passwordLogin(@RequestBody PasswordLoginRequest request) {
|
||||
return catalogService.loginWithPassword(request.email(), request.password());
|
||||
@@ -62,7 +71,7 @@ public class SessionController {
|
||||
|
||||
@PutMapping("/invoice-template")
|
||||
public InvoiceTemplateService.InvoiceTemplateResponse saveInvoiceTemplate(
|
||||
@RequestBody InvoiceTemplateRequest request
|
||||
@RequestBody TemplateRequest request
|
||||
) {
|
||||
return invoiceTemplateService.saveTemplate(
|
||||
securitySupport.currentUser().id(),
|
||||
@@ -70,6 +79,16 @@ public class SessionController {
|
||||
);
|
||||
}
|
||||
|
||||
@PutMapping("/report-template")
|
||||
public InvoiceTemplateService.InvoiceTemplateResponse saveReportTemplate(
|
||||
@RequestBody TemplateRequest request
|
||||
) {
|
||||
return reportTemplateService.saveTemplate(
|
||||
securitySupport.currentUser().id(),
|
||||
request.elements()
|
||||
);
|
||||
}
|
||||
|
||||
public record PasswordLoginRequest(@NotBlank String email, @NotBlank String password) {
|
||||
}
|
||||
|
||||
@@ -85,7 +104,7 @@ public class SessionController {
|
||||
) {
|
||||
}
|
||||
|
||||
public record InvoiceTemplateRequest(
|
||||
public record TemplateRequest(
|
||||
List<InvoiceTemplateService.InvoiceTemplateElementPayload> elements
|
||||
) {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user