diff --git a/src/main/java/de/zendric/app/xpensely_server/model/Expense.java b/src/main/java/de/zendric/app/xpensely_server/model/Expense.java index 9e97e24..34bc45f 100644 --- a/src/main/java/de/zendric/app/xpensely_server/model/Expense.java +++ b/src/main/java/de/zendric/app/xpensely_server/model/Expense.java @@ -1,6 +1,7 @@ package de.zendric.app.xpensely_server.model; import java.time.LocalDate; +import java.time.LocalDateTime; import com.fasterxml.jackson.annotation.JsonBackReference; @@ -36,6 +37,7 @@ public class Expense { private Double otherPersonAmount; private String category; private LocalDate date; + private LocalDateTime lastModified; @ManyToOne @JoinColumn(name = "expense_list_id", nullable = false) diff --git a/src/main/java/de/zendric/app/xpensely_server/model/ExpenseHistoryEntry.java b/src/main/java/de/zendric/app/xpensely_server/model/ExpenseHistoryEntry.java new file mode 100644 index 0000000..5e74989 --- /dev/null +++ b/src/main/java/de/zendric/app/xpensely_server/model/ExpenseHistoryEntry.java @@ -0,0 +1,52 @@ +package de.zendric.app.xpensely_server.model; + +import java.time.LocalDateTime; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@Entity +@Table(name = "expense_history_entry", indexes = @Index(name = "idx_history_list_timestamp", columnList = "expense_list_id, timestamp DESC")) +public class ExpenseHistoryEntry { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "expense_list_id", nullable = false) + private ExpenseList expenseList; + + /** Plain column on purpose — no FK, so history survives expense deletion. */ + private Long expenseId; + + /** Denormalized so entries for deleted expenses still render. */ + private String expenseTitle; + + @ManyToOne + private AppUser actor; + + @Enumerated(EnumType.STRING) + private HistoryEntryType type; + + private LocalDateTime timestamp; + + /** JSON array of FieldChange; null for CREATED/DELETED. */ + @Column(columnDefinition = "TEXT") + private String changes; +} diff --git a/src/main/java/de/zendric/app/xpensely_server/model/FieldChange.java b/src/main/java/de/zendric/app/xpensely_server/model/FieldChange.java new file mode 100644 index 0000000..8976242 --- /dev/null +++ b/src/main/java/de/zendric/app/xpensely_server/model/FieldChange.java @@ -0,0 +1,9 @@ +package de.zendric.app.xpensely_server.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record FieldChange( + String field, + @JsonProperty("old") String oldValue, + @JsonProperty("new") String newValue) { +} diff --git a/src/main/java/de/zendric/app/xpensely_server/model/HistoryEntryType.java b/src/main/java/de/zendric/app/xpensely_server/model/HistoryEntryType.java new file mode 100644 index 0000000..a47f993 --- /dev/null +++ b/src/main/java/de/zendric/app/xpensely_server/model/HistoryEntryType.java @@ -0,0 +1,5 @@ +package de.zendric.app.xpensely_server.model; + +public enum HistoryEntryType { + CREATED, UPDATED, DELETED +} diff --git a/src/main/java/de/zendric/app/xpensely_server/repo/ExpenseHistoryRepository.java b/src/main/java/de/zendric/app/xpensely_server/repo/ExpenseHistoryRepository.java new file mode 100644 index 0000000..0009cca --- /dev/null +++ b/src/main/java/de/zendric/app/xpensely_server/repo/ExpenseHistoryRepository.java @@ -0,0 +1,18 @@ +package de.zendric.app.xpensely_server.repo; + +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import de.zendric.app.xpensely_server.model.ExpenseHistoryEntry; + +public interface ExpenseHistoryRepository extends JpaRepository { + + Page findByExpenseListIdOrderByTimestampDescIdDesc(Long expenseListId, Pageable pageable); + + @Query("select h.expenseId from ExpenseHistoryEntry h") + List findAllExpenseIds(); +} diff --git a/src/main/java/de/zendric/app/xpensely_server/repo/ExpenseRepository.java b/src/main/java/de/zendric/app/xpensely_server/repo/ExpenseRepository.java index fe7a610..659d240 100644 --- a/src/main/java/de/zendric/app/xpensely_server/repo/ExpenseRepository.java +++ b/src/main/java/de/zendric/app/xpensely_server/repo/ExpenseRepository.java @@ -10,4 +10,6 @@ import de.zendric.app.xpensely_server.model.Expense; @Repository public interface ExpenseRepository extends JpaRepository { List findAllByOrderByDateAsc(); + + List findByLastModifiedIsNull(); }