Future releases filter settings for feed and RSS
Build & Deploy / build-and-deploy (push) Successful in 7s
Build & Deploy / build-and-deploy (push) Successful in 7s
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
target/
|
target/
|
||||||
*.jar
|
*.jar
|
||||||
*.war
|
*.war
|
||||||
|
mvnw
|
||||||
|
mvnw.cmd
|
||||||
|
|
||||||
# IDE
|
# IDE
|
||||||
.idea/
|
.idea/
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ A background job refreshes release groups on a configurable schedule (6 / 12 / 2
|
|||||||
### 📰 RSS feed
|
### 📰 RSS feed
|
||||||
A global RSS 2.0 feed with Media RSS cover art — subscribe from your reader of choice. Discoverable via `<link rel="alternate">` from the app root.
|
A global RSS 2.0 feed with Media RSS cover art — subscribe from your reader of choice. Discoverable via `<link rel="alternate">` from the app root.
|
||||||
|
|
||||||
|
### 📅 Future release filter
|
||||||
|
Hide unreleased releases from the feed, the RSS feed, or both — independently configurable from the settings panel. Releases without a date are always shown.
|
||||||
|
|
||||||
### 🌗 Theme switching
|
### 🌗 Theme switching
|
||||||
Built-in light/dark theme toggle, persisted in `localStorage`.
|
Built-in light/dark theme toggle, persisted in `localStorage`.
|
||||||
|
|
||||||
@@ -77,6 +80,8 @@ All settings are in `application.properties`. Key values can be overridden via e
|
|||||||
| `discdrop.rss.item-count` | — | `50` | RSS item count |
|
| `discdrop.rss.item-count` | — | `50` | RSS item count |
|
||||||
| `discdrop.sync.default-schedule-hours` | — | `24` | Default sync interval |
|
| `discdrop.sync.default-schedule-hours` | — | `24` | Default sync interval |
|
||||||
| `discdrop.sync.default-primary-types` | — | `album` | Default types for new artists |
|
| `discdrop.sync.default-primary-types` | — | `album` | Default types for new artists |
|
||||||
|
| `hideFutureFeed` | — | `false` | Hide unreleased from web feed |
|
||||||
|
| `hideFutureRss` | — | `false` | Hide unreleased from RSS feed |
|
||||||
|
|
||||||
The H2 file database lives in `./data/discdrop.mv.db`
|
The H2 file database lives in `./data/discdrop.mv.db`
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import io.discdrop.persistence.FollowedArtist;
|
|||||||
import io.discdrop.persistence.ReleaseGroupEntity;
|
import io.discdrop.persistence.ReleaseGroupEntity;
|
||||||
import io.discdrop.service.ArtistService;
|
import io.discdrop.service.ArtistService;
|
||||||
import io.discdrop.service.FeedService;
|
import io.discdrop.service.FeedService;
|
||||||
|
import io.discdrop.service.SettingsService;
|
||||||
import io.discdrop.service.SyncService;
|
import io.discdrop.service.SyncService;
|
||||||
import io.quarkus.qute.Location;
|
import io.quarkus.qute.Location;
|
||||||
import io.quarkus.qute.Template;
|
import io.quarkus.qute.Template;
|
||||||
@@ -34,6 +35,9 @@ public class ArtistResource {
|
|||||||
@Inject
|
@Inject
|
||||||
FeedService feedService;
|
FeedService feedService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SettingsService settingsService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@Location("fragments/feed-list.html")
|
@Location("fragments/feed-list.html")
|
||||||
Template fragments_feed_list;
|
Template fragments_feed_list;
|
||||||
@@ -113,7 +117,8 @@ public class ArtistResource {
|
|||||||
if (offset < 0) {
|
if (offset < 0) {
|
||||||
offset = 0;
|
offset = 0;
|
||||||
}
|
}
|
||||||
List<ReleaseGroupEntity> rows = feedService.feedPage(offset);
|
boolean hideFuture = settingsService.isHideFutureFeed();
|
||||||
|
List<ReleaseGroupEntity> rows = feedService.feedPage(offset, hideFuture);
|
||||||
int nextOffset = offset + rows.size();
|
int nextOffset = offset + rows.size();
|
||||||
boolean hasMore = rows.size() == feedService.pageSize();
|
boolean hasMore = rows.size() == feedService.pageSize();
|
||||||
return fragments_feed_list.data("rows", rows)
|
return fragments_feed_list.data("rows", rows)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package io.discdrop.resource;
|
|||||||
import io.discdrop.persistence.FollowedArtist;
|
import io.discdrop.persistence.FollowedArtist;
|
||||||
import io.discdrop.persistence.ReleaseGroupEntity;
|
import io.discdrop.persistence.ReleaseGroupEntity;
|
||||||
import io.discdrop.service.FeedService;
|
import io.discdrop.service.FeedService;
|
||||||
|
import io.discdrop.service.SettingsService;
|
||||||
import io.discdrop.service.SyncService;
|
import io.discdrop.service.SyncService;
|
||||||
import io.quarkus.qute.Location;
|
import io.quarkus.qute.Location;
|
||||||
import io.quarkus.qute.Template;
|
import io.quarkus.qute.Template;
|
||||||
@@ -24,6 +25,9 @@ public class PageResource {
|
|||||||
@Inject
|
@Inject
|
||||||
SyncService syncService;
|
SyncService syncService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SettingsService settingsService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Template index;
|
Template index;
|
||||||
|
|
||||||
@@ -38,7 +42,8 @@ public class PageResource {
|
|||||||
if (offset < 0) {
|
if (offset < 0) {
|
||||||
offset = 0;
|
offset = 0;
|
||||||
}
|
}
|
||||||
List<ReleaseGroupEntity> rows = feedService.feedPage(offset);
|
boolean hideFuture = settingsService.isHideFutureFeed();
|
||||||
|
List<ReleaseGroupEntity> rows = feedService.feedPage(offset, hideFuture);
|
||||||
int nextOffset = offset + rows.size();
|
int nextOffset = offset + rows.size();
|
||||||
boolean hasMore = rows.size() == feedService.pageSize();
|
boolean hasMore = rows.size() == feedService.pageSize();
|
||||||
return fragments_feed_list.data("rows", rows)
|
return fragments_feed_list.data("rows", rows)
|
||||||
@@ -52,7 +57,8 @@ public class PageResource {
|
|||||||
@GET
|
@GET
|
||||||
@Transactional
|
@Transactional
|
||||||
public TemplateInstance index() {
|
public TemplateInstance index() {
|
||||||
List<ReleaseGroupEntity> rows = feedService.feedPage(0);
|
boolean hideFuture = settingsService.isHideFutureFeed();
|
||||||
|
List<ReleaseGroupEntity> rows = feedService.feedPage(0, hideFuture);
|
||||||
boolean hasMore = rows.size() == feedService.pageSize();
|
boolean hasMore = rows.size() == feedService.pageSize();
|
||||||
return index.data("rows", rows)
|
return index.data("rows", rows)
|
||||||
.data("nextOffset", rows.size())
|
.data("nextOffset", rows.size())
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package io.discdrop.resource;
|
|||||||
|
|
||||||
import io.discdrop.persistence.ReleaseGroupEntity;
|
import io.discdrop.persistence.ReleaseGroupEntity;
|
||||||
import io.discdrop.service.FeedService;
|
import io.discdrop.service.FeedService;
|
||||||
|
import io.discdrop.service.SettingsService;
|
||||||
import io.quarkus.qute.Location;
|
import io.quarkus.qute.Location;
|
||||||
import io.quarkus.qute.Template;
|
import io.quarkus.qute.Template;
|
||||||
import io.quarkus.qute.TemplateInstance;
|
import io.quarkus.qute.TemplateInstance;
|
||||||
@@ -27,6 +28,9 @@ public class RssResource {
|
|||||||
@Inject
|
@Inject
|
||||||
FeedService feedService;
|
FeedService feedService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SettingsService settingsService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@Location("rss.xml")
|
@Location("rss.xml")
|
||||||
Template rss;
|
Template rss;
|
||||||
@@ -40,7 +44,8 @@ public class RssResource {
|
|||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.APPLICATION_XML)
|
@Produces(MediaType.APPLICATION_XML)
|
||||||
public TemplateInstance feed() {
|
public TemplateInstance feed() {
|
||||||
List<ReleaseGroupEntity> entities = feedService.latest(itemCount);
|
boolean hideFuture = settingsService.isHideFutureRss();
|
||||||
|
List<ReleaseGroupEntity> entities = feedService.latest(itemCount, hideFuture);
|
||||||
List<RssItem> items = new ArrayList<>(entities.size());
|
List<RssItem> items = new ArrayList<>(entities.size());
|
||||||
for (ReleaseGroupEntity e : entities) {
|
for (ReleaseGroupEntity e : entities) {
|
||||||
String pubDate = null;
|
String pubDate = null;
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ public class SettingsResource {
|
|||||||
|
|
||||||
@POST
|
@POST
|
||||||
public TemplateInstance save(@FormParam("defaultPrimaryTypes") Set<String> defaultPrimaryTypes,
|
public TemplateInstance save(@FormParam("defaultPrimaryTypes") Set<String> defaultPrimaryTypes,
|
||||||
@FormParam("syncScheduleHours") int syncScheduleHours) {
|
@FormParam("syncScheduleHours") int syncScheduleHours,
|
||||||
|
@FormParam("hideFutureFeed") boolean hideFutureFeed,
|
||||||
|
@FormParam("hideFutureRss") boolean hideFutureRss) {
|
||||||
if (defaultPrimaryTypes == null) {
|
if (defaultPrimaryTypes == null) {
|
||||||
defaultPrimaryTypes = Set.of();
|
defaultPrimaryTypes = Set.of();
|
||||||
}
|
}
|
||||||
@@ -47,6 +49,8 @@ public class SettingsResource {
|
|||||||
if (previous != syncScheduleHours) {
|
if (previous != syncScheduleHours) {
|
||||||
syncService.reschedule();
|
syncService.reschedule();
|
||||||
}
|
}
|
||||||
|
settingsService.setHideFutureFeed(hideFutureFeed);
|
||||||
|
settingsService.setHideFutureRss(hideFutureRss);
|
||||||
return buildPanel("saved");
|
return buildPanel("saved");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +58,8 @@ public class SettingsResource {
|
|||||||
return fragments_settings_panel
|
return fragments_settings_panel
|
||||||
.data("defaultPrimaryTypes", settingsService.getDefaultPrimaryTypes())
|
.data("defaultPrimaryTypes", settingsService.getDefaultPrimaryTypes())
|
||||||
.data("syncScheduleHours", settingsService.getSyncScheduleHours())
|
.data("syncScheduleHours", settingsService.getSyncScheduleHours())
|
||||||
|
.data("hideFutureFeed", settingsService.isHideFutureFeed())
|
||||||
|
.data("hideFutureRss", settingsService.isHideFutureRss())
|
||||||
.data("allPrimaryTypes", SettingsService.ALL_PRIMARY_TYPES)
|
.data("allPrimaryTypes", SettingsService.ALL_PRIMARY_TYPES)
|
||||||
.data("scheduleOptions", SCHEDULE_OPTIONS)
|
.data("scheduleOptions", SCHEDULE_OPTIONS)
|
||||||
.data("flash", flash);
|
.data("flash", flash);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import jakarta.enterprise.context.ApplicationScoped;
|
|||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
@@ -17,21 +18,27 @@ public class FeedService {
|
|||||||
private static final String ORDER_HQL =
|
private static final String ORDER_HQL =
|
||||||
"ORDER BY firstReleaseDate DESC NULLS LAST, discoveredAt DESC";
|
"ORDER BY firstReleaseDate DESC NULLS LAST, discoveredAt DESC";
|
||||||
|
|
||||||
private PanacheQuery<ReleaseGroupEntity> orderedQuery() {
|
private static final String WHERE_ORDER_HQL =
|
||||||
|
"WHERE firstReleaseDate IS NULL OR firstReleaseDate <= ?1 " + ORDER_HQL;
|
||||||
|
|
||||||
|
private PanacheQuery<ReleaseGroupEntity> orderedQuery(boolean hideFuture) {
|
||||||
|
if (hideFuture) {
|
||||||
|
return ReleaseGroupEntity.find(WHERE_ORDER_HQL, LocalDate.now());
|
||||||
|
}
|
||||||
return ReleaseGroupEntity.find(ORDER_HQL);
|
return ReleaseGroupEntity.find(ORDER_HQL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public List<ReleaseGroupEntity> feedPage(int offset) {
|
public List<ReleaseGroupEntity> feedPage(int offset, boolean hideFuture) {
|
||||||
if (offset < 0) {
|
if (offset < 0) {
|
||||||
offset = 0;
|
offset = 0;
|
||||||
}
|
}
|
||||||
return orderedQuery().range(offset, offset + pageSize - 1).list();
|
return orderedQuery(hideFuture).range(offset, offset + pageSize - 1).list();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public List<ReleaseGroupEntity> latest(int count) {
|
public List<ReleaseGroupEntity> latest(int count, boolean hideFuture) {
|
||||||
return orderedQuery().range(0, Math.max(0, count - 1)).list();
|
return orderedQuery(hideFuture).range(0, Math.max(0, count - 1)).list();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int pageSize() {
|
public int pageSize() {
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ public class SettingsService {
|
|||||||
|
|
||||||
public static final String KEY_DEFAULT_PRIMARY_TYPES = "defaultPrimaryTypes";
|
public static final String KEY_DEFAULT_PRIMARY_TYPES = "defaultPrimaryTypes";
|
||||||
public static final String KEY_SYNC_SCHEDULE_HOURS = "syncScheduleHours";
|
public static final String KEY_SYNC_SCHEDULE_HOURS = "syncScheduleHours";
|
||||||
|
public static final String KEY_HIDE_FUTURE_FEED = "hideFutureFeed";
|
||||||
|
public static final String KEY_HIDE_FUTURE_RSS = "hideFutureRss";
|
||||||
|
|
||||||
public static final List<String> ALL_PRIMARY_TYPES = List.of("album", "single", "ep", "broadcast", "other");
|
public static final List<String> ALL_PRIMARY_TYPES = List.of("album", "single", "ep", "broadcast", "other");
|
||||||
|
|
||||||
@@ -42,6 +44,24 @@ public class SettingsService {
|
|||||||
AppSetting.set(KEY_SYNC_SCHEDULE_HOURS, String.valueOf(hours));
|
AppSetting.set(KEY_SYNC_SCHEDULE_HOURS, String.valueOf(hours));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isHideFutureFeed() {
|
||||||
|
return Boolean.parseBoolean(AppSetting.get(KEY_HIDE_FUTURE_FEED, "false"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void setHideFutureFeed(boolean hide) {
|
||||||
|
AppSetting.set(KEY_HIDE_FUTURE_FEED, String.valueOf(hide));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isHideFutureRss() {
|
||||||
|
return Boolean.parseBoolean(AppSetting.get(KEY_HIDE_FUTURE_RSS, "false"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void setHideFutureRss(boolean hide) {
|
||||||
|
AppSetting.set(KEY_HIDE_FUTURE_RSS, String.valueOf(hide));
|
||||||
|
}
|
||||||
|
|
||||||
private Set<String> parseTypes(String raw) {
|
private Set<String> parseTypes(String raw) {
|
||||||
if (raw == null || raw.isBlank()) {
|
if (raw == null || raw.isBlank()) {
|
||||||
return new LinkedHashSet<>();
|
return new LinkedHashSet<>();
|
||||||
|
|||||||
@@ -34,6 +34,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="divider my-1"></div>
|
||||||
|
<div class="text-sm font-semibold mb-1">Future releases</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="label cursor-pointer justify-start gap-3">
|
||||||
|
<input type="checkbox" name="hideFutureFeed" value="true"
|
||||||
|
class="checkbox checkbox-sm"
|
||||||
|
{#if hideFutureFeed}checked{/if}/>
|
||||||
|
<span class="text-xs">Hide unreleased in feed</span>
|
||||||
|
</label>
|
||||||
|
<label class="label cursor-pointer justify-start gap-3">
|
||||||
|
<input type="checkbox" name="hideFutureRss" value="true"
|
||||||
|
class="checkbox checkbox-sm"
|
||||||
|
{#if hideFutureRss}checked{/if}/>
|
||||||
|
<span class="text-xs">Hide unreleased in RSS</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-primary btn-sm w-full">Save</button>
|
<button class="btn btn-primary btn-sm w-full">Save</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user