diff --git a/src/main/java/dev/kruhlmann/imgfloat/model/Asset.java b/src/main/java/dev/kruhlmann/imgfloat/model/Asset.java index 1c479ae..1a50a6b 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/model/Asset.java +++ b/src/main/java/dev/kruhlmann/imgfloat/model/Asset.java @@ -29,9 +29,12 @@ public class Asset { @Column(columnDefinition = "TEXT", nullable = false) private String preview; - @Column(nullable = false) + @Column(name = "created_at", nullable = false, updatable = false) private Instant createdAt; + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + private double x; private double y; private double width; @@ -66,17 +69,20 @@ public class Asset { this.zIndex = 1; this.hidden = true; this.createdAt = Instant.now(); + this.updatedAt = this.createdAt; } @PrePersist @PreUpdate public void prepare() { + Instant now = Instant.now(); if (this.id == null) { this.id = UUID.randomUUID().toString(); } if (this.createdAt == null) { - this.createdAt = Instant.now(); + this.createdAt = now; } + this.updatedAt = now; this.broadcaster = normalize(broadcaster); if (this.name == null || this.name.isBlank()) { this.name = this.id; @@ -235,6 +241,14 @@ public class Asset { this.createdAt = createdAt; } + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + public Integer getZIndex() { return zIndex == null ? 1 : Math.max(1, zIndex); } diff --git a/src/main/java/dev/kruhlmann/imgfloat/model/AssetView.java b/src/main/java/dev/kruhlmann/imgfloat/model/AssetView.java index 31af79e..c54c2c4 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/model/AssetView.java +++ b/src/main/java/dev/kruhlmann/imgfloat/model/AssetView.java @@ -25,7 +25,8 @@ public record AssetView( Double audioVolume, boolean hidden, boolean hasPreview, - Instant createdAt + Instant createdAt, + Instant updatedAt ) { public static AssetView from(String broadcaster, Asset asset) { return new AssetView( @@ -53,7 +54,8 @@ public record AssetView( asset.getAudioVolume(), asset.isHidden(), asset.getPreview() != null && !asset.getPreview().isBlank(), - asset.getCreatedAt() + asset.getCreatedAt(), + asset.getUpdatedAt() ); } } diff --git a/src/main/java/dev/kruhlmann/imgfloat/model/Channel.java b/src/main/java/dev/kruhlmann/imgfloat/model/Channel.java index 9c198a7..a93b668 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/model/Channel.java +++ b/src/main/java/dev/kruhlmann/imgfloat/model/Channel.java @@ -12,6 +12,7 @@ import jakarta.persistence.PreUpdate; import jakarta.persistence.Table; import java.util.Collections; import java.util.HashSet; +import java.time.Instant; import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; @@ -32,6 +33,12 @@ public class Channel { private double canvasHeight = 1080; + @Column(name = "created_at", nullable = false, updatable = false) + private Instant createdAt; + + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + public Channel() {} public Channel(String broadcaster) { @@ -75,6 +82,11 @@ public class Channel { @PrePersist @PreUpdate public void normalizeFields() { + Instant now = Instant.now(); + if (createdAt == null) { + createdAt = now; + } + updatedAt = now; this.broadcaster = normalize(broadcaster); this.admins = admins.stream().map(Channel::normalize).collect(Collectors.toSet()); if (canvasWidth <= 0) { @@ -85,6 +97,14 @@ public class Channel { } } + public Instant getCreatedAt() { + return createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + private static String normalize(String value) { return value == null ? null : value.toLowerCase(Locale.ROOT); } diff --git a/src/main/java/dev/kruhlmann/imgfloat/model/Settings.java b/src/main/java/dev/kruhlmann/imgfloat/model/Settings.java index e2c177e..b50bd58 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/model/Settings.java +++ b/src/main/java/dev/kruhlmann/imgfloat/model/Settings.java @@ -3,7 +3,10 @@ package dev.kruhlmann.imgfloat.model; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreUpdate; import jakarta.persistence.Table; +import java.time.Instant; @Entity @Table(name = "settings") @@ -37,6 +40,12 @@ public class Settings { @Column(nullable = false) private int canvasFramesPerSecond; + @Column(name = "created_at", nullable = false, updatable = false) + private Instant createdAt; + + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + protected Settings() {} public static Settings defaults() { @@ -123,4 +132,26 @@ public class Settings { public void setCanvasFramesPerSecond(int canvasFramesPerSecond) { this.canvasFramesPerSecond = canvasFramesPerSecond; } + + @PrePersist + public void initializeTimestamps() { + Instant now = Instant.now(); + if (createdAt == null) { + createdAt = now; + } + updatedAt = now; + } + + @PreUpdate + public void updateTimestamp() { + updatedAt = Instant.now(); + } + + public Instant getCreatedAt() { + return createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } } diff --git a/src/main/java/dev/kruhlmann/imgfloat/model/SystemAdministrator.java b/src/main/java/dev/kruhlmann/imgfloat/model/SystemAdministrator.java index 2d3b2d2..d5869ad 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/model/SystemAdministrator.java +++ b/src/main/java/dev/kruhlmann/imgfloat/model/SystemAdministrator.java @@ -7,6 +7,7 @@ import jakarta.persistence.PrePersist; import jakarta.persistence.PreUpdate; import jakarta.persistence.Table; import jakarta.persistence.UniqueConstraint; +import java.time.Instant; import java.util.Locale; import java.util.UUID; @@ -20,6 +21,12 @@ public class SystemAdministrator { @Column(name = "twitch_username", nullable = false) private String twitchUsername; + @Column(name = "created_at", nullable = false, updatable = false) + private Instant createdAt; + + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + public SystemAdministrator() {} public SystemAdministrator(String twitchUsername) { @@ -29,9 +36,14 @@ public class SystemAdministrator { @PrePersist @PreUpdate public void prepare() { + Instant now = Instant.now(); if (this.id == null) { this.id = UUID.randomUUID().toString(); } + if (this.createdAt == null) { + this.createdAt = now; + } + this.updatedAt = now; twitchUsername = twitchUsername.toLowerCase(Locale.ROOT); } @@ -46,4 +58,12 @@ public class SystemAdministrator { public void setTwitchUsername(String twitchUsername) { this.twitchUsername = twitchUsername; } + + public Instant getCreatedAt() { + return createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } } diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/ChannelDirectoryService.java b/src/main/java/dev/kruhlmann/imgfloat/service/ChannelDirectoryService.java index 39aea9f..8528dcd 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/ChannelDirectoryService.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/ChannelDirectoryService.java @@ -85,7 +85,7 @@ public class ChannelDirectoryService { Channel channel = getOrCreateChannel(broadcaster); boolean added = channel.addAdmin(username); if (added) { - channelRepository.save(channel); + channelRepository.saveAndFlush(channel); messagingTemplate.convertAndSend(topicFor(broadcaster), "Admin added: " + username); } return added; @@ -95,7 +95,7 @@ public class ChannelDirectoryService { Channel channel = getOrCreateChannel(broadcaster); boolean removed = channel.removeAdmin(username); if (removed) { - channelRepository.save(channel); + channelRepository.saveAndFlush(channel); messagingTemplate.convertAndSend(topicFor(broadcaster), "Admin removed: " + username); } return removed; diff --git a/src/main/resources/static/js/dashboard.js b/src/main/resources/static/js/dashboard.js index bf559ff..3cd99b4 100644 --- a/src/main/resources/static/js/dashboard.js +++ b/src/main/resources/static/js/dashboard.js @@ -85,7 +85,7 @@ function renderSuggestedAdmins(list) { const addBtn = document.createElement("button"); addBtn.type = "button"; addBtn.className = "ghost"; - addBtn.textContent = "Add as admin"; + addBtn.textContent = "Add channel admin"; addBtn.addEventListener("click", () => addAdmin(admin.login)); actions.appendChild(addBtn);