Compare commits
4 Commits
15c153c56a
...
beea9a0dcf
| Author | SHA1 | Date | |
|---|---|---|---|
| beea9a0dcf | |||
| 2deb335953 | |||
| e7f9b6838e | |||
| dcdd04df8c |
@@ -1,30 +1,25 @@
|
|||||||
package com.discdrop.client.dto;
|
package com.discdrop.client.dto;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public record ReleaseGroupBrowseResponse(
|
public record ReleaseGroupBrowseResponse(
|
||||||
List<ReleaseGroup> releaseGroups,
|
@JsonProperty("release-groups") List<ReleaseGroup> releaseGroups,
|
||||||
int releaseGroupCount
|
@JsonProperty("release-group-count") int releaseGroupCount
|
||||||
) {
|
) {
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public record ReleaseGroup(
|
public record ReleaseGroup(
|
||||||
String id,
|
String id,
|
||||||
String title,
|
String title,
|
||||||
String firstReleaseDate,
|
@JsonProperty("first-release-date") String firstReleaseDate,
|
||||||
PrimaryType primaryType,
|
@JsonProperty("primary-type") String primaryType,
|
||||||
List<SecondaryType> secondaryTypes,
|
@JsonProperty("secondary-types") List<String> secondaryTypes,
|
||||||
List<ArtistCredit> artistCredit
|
@JsonProperty("artist-credit") List<ArtistCredit> artistCredit
|
||||||
) {
|
) {
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public record PrimaryType(String id, String name) {}
|
public record ArtistCredit(@JsonProperty("artist") Artist artist) {
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
||||||
public record SecondaryType(String id, String name) {}
|
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
||||||
public record ArtistCredit(Artist artist) {
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public record Artist(String id, String name) {}
|
public record Artist(String id, String name) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ public class DatabaseService {
|
|||||||
var typePlaceholders = String.join(",", primaryTypes.stream().map(t -> "?").toList());
|
var typePlaceholders = String.join(",", primaryTypes.stream().map(t -> "?").toList());
|
||||||
sql += " AND primary_type IN (" + typePlaceholders + ")";
|
sql += " AND primary_type IN (" + typePlaceholders + ")";
|
||||||
}
|
}
|
||||||
sql += " ORDER BY first_release_date ASC";
|
sql += " ORDER BY first_release_date DESC";
|
||||||
|
|
||||||
try (Connection conn = getConnection();
|
try (Connection conn = getConnection();
|
||||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||||
|
|||||||
@@ -43,15 +43,14 @@ public class ReleaseGroupService {
|
|||||||
total = Math.min(page.releaseGroupCount(), MAX_RELEASE_GROUPS);
|
total = Math.min(page.releaseGroupCount(), MAX_RELEASE_GROUPS);
|
||||||
|
|
||||||
for (var rg : page.releaseGroups()) {
|
for (var rg : page.releaseGroups()) {
|
||||||
String primaryType = rg.primaryType() != null ? rg.primaryType().name() : "Other";
|
String primaryType = rg.primaryType() != null ? rg.primaryType() : "Other";
|
||||||
|
|
||||||
if (!monitoredTypes.contains(primaryType)) continue;
|
if (!monitoredTypes.contains(primaryType)) continue;
|
||||||
|
|
||||||
String secondaryTypes = "[]";
|
String secondaryTypes = "[]";
|
||||||
if (rg.secondaryTypes() != null && !rg.secondaryTypes().isEmpty()) {
|
if (rg.secondaryTypes() != null && !rg.secondaryTypes().isEmpty()) {
|
||||||
try {
|
try {
|
||||||
secondaryTypes = json.writeValueAsString(
|
secondaryTypes = json.writeValueAsString(rg.secondaryTypes());
|
||||||
rg.secondaryTypes().stream().map(s -> s.name()).toList());
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
secondaryTypes = "[]";
|
secondaryTypes = "[]";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,8 +79,6 @@ public class RSSFeedBuilder {
|
|||||||
|
|
||||||
private String buildDescription(ReleaseGroup rg) {
|
private String buildDescription(ReleaseGroup rg) {
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.append("<img src=\"https://coverartarchive.org/release-group/")
|
|
||||||
.append(rg.mbid()).append("/front\" alt=\"Cover\" onerror=\"this.style.display='none'\">");
|
|
||||||
sb.append("<p><b>").append(escHtml(rg.artistName())).append("</b>")
|
sb.append("<p><b>").append(escHtml(rg.artistName())).append("</b>")
|
||||||
.append(" <i>").append(escHtml(rg.title())).append("</i><br>");
|
.append(" <i>").append(escHtml(rg.title())).append("</i><br>");
|
||||||
sb.append("Type: ").append(escHtml(rg.primaryType()));
|
sb.append("Type: ").append(escHtml(rg.primaryType()));
|
||||||
@@ -88,6 +86,8 @@ public class RSSFeedBuilder {
|
|||||||
sb.append(" | Secondary: ").append(escHtml(rg.secondaryTypes()));
|
sb.append(" | Secondary: ").append(escHtml(rg.secondaryTypes()));
|
||||||
}
|
}
|
||||||
sb.append("<br>First Release Date: ").append(rg.firstReleaseDate());
|
sb.append("<br>First Release Date: ").append(rg.firstReleaseDate());
|
||||||
|
sb.append("<br><a href=\"https://coverartarchive.org/release-group/")
|
||||||
|
.append(rg.mbid()).append("/front\">Cover Art</a>");
|
||||||
sb.append("</p>");
|
sb.append("</p>");
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import io.quarkus.qute.TemplateInstance;
|
|||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.ws.rs.*;
|
import jakarta.ws.rs.*;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -55,12 +56,14 @@ public class ArtistController {
|
|||||||
@POST
|
@POST
|
||||||
@Path("/{mbid}/track")
|
@Path("/{mbid}/track")
|
||||||
@Produces(MediaType.TEXT_HTML)
|
@Produces(MediaType.TEXT_HTML)
|
||||||
public TemplateInstance trackArtist(@PathParam("mbid") String mbid) {
|
public Response trackArtist(@PathParam("mbid") String mbid) {
|
||||||
var artist = artistService.addArtist(mbid);
|
var artist = artistService.addArtist(mbid);
|
||||||
var types = getMonitoredTypes(artist);
|
var types = getMonitoredTypes(artist);
|
||||||
releaseGroupService.fetchAndCache(artist.mbid(), artist.name(), types);
|
releaseGroupService.fetchAndCache(artist.mbid(), artist.name(), types);
|
||||||
var allArtists = artistService.findAll();
|
var allArtists = artistService.findAll();
|
||||||
return artistListTemplate.data("artists", allArtists);
|
var listHtml = artistListTemplate.data("artists", allArtists).render();
|
||||||
|
var emptySearch = searchResultsTemplate.data("artists", List.of()).render();
|
||||||
|
return Response.ok(emptySearch + listHtml).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@DELETE
|
@DELETE
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{#include layouts/base}
|
{#include layouts/base}
|
||||||
{#page-content}
|
{#page-content}
|
||||||
<div class="container mx-auto p-4">
|
<div class="w-full max-w-4xl mx-auto">
|
||||||
<h2 class="text-2xl font-bold mb-4">🎤 Artists</h2>
|
<h2 class="text-2xl font-bold mb-4">🎤 Artists</h2>
|
||||||
<div id="artist-list" hx-get="/api/artists/list" hx-trigger="load">
|
<div id="artist-list" hx-get="/api/artists/list" hx-trigger="load">
|
||||||
<span class="loading loading-spinner loading-lg"></span>
|
<span class="loading loading-spinner loading-lg block mx-auto"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/page-content}
|
{/page-content}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<div id="artist-list" hx-swap-oob="true">
|
||||||
{#if artists.isEmpty()}
|
{#if artists.isEmpty()}
|
||||||
<div class="card bg-base-100 shadow-xl p-8 text-center">
|
<div class="card bg-base-100 shadow-xl p-8 text-center">
|
||||||
<p class="text-lg text-base-content/60">No artists tracked yet</p>
|
<p class="text-lg text-base-content/60">No artists tracked yet</p>
|
||||||
@@ -30,35 +31,40 @@
|
|||||||
<input type="checkbox" class="checkbox checkbox-xs checkbox-primary"
|
<input type="checkbox" class="checkbox checkbox-xs checkbox-primary"
|
||||||
{#if artist.albumMonitored}checked{/if}
|
{#if artist.albumMonitored}checked{/if}
|
||||||
hx-patch="/api/artists/{artist.mbid}/monitor?type=album"
|
hx-patch="/api/artists/{artist.mbid}/monitor?type=album"
|
||||||
hx-target="#artist-list" hx-swap="innerHTML">
|
hx-target="#artist-list"
|
||||||
|
hx-swap="innerHTML">
|
||||||
<span class="label-text text-xs">Album</span>
|
<span class="label-text text-xs">Album</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="label cursor-pointer gap-1">
|
<label class="label cursor-pointer gap-1">
|
||||||
<input type="checkbox" class="checkbox checkbox-xs"
|
<input type="checkbox" class="checkbox checkbox-xs"
|
||||||
{#if artist.singleMonitored}checked{/if}
|
{#if artist.singleMonitored}checked{/if}
|
||||||
hx-patch="/api/artists/{artist.mbid}/monitor?type=single"
|
hx-patch="/api/artists/{artist.mbid}/monitor?type=single"
|
||||||
hx-target="#artist-list" hx-swap="innerHTML">
|
hx-target="#artist-list"
|
||||||
|
hx-swap="innerHTML">
|
||||||
<span class="label-text text-xs">Single</span>
|
<span class="label-text text-xs">Single</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="label cursor-pointer gap-1">
|
<label class="label cursor-pointer gap-1">
|
||||||
<input type="checkbox" class="checkbox checkbox-xs"
|
<input type="checkbox" class="checkbox checkbox-xs"
|
||||||
{#if artist.epMonitored}checked{/if}
|
{#if artist.epMonitored}checked{/if}
|
||||||
hx-patch="/api/artists/{artist.mbid}/monitor?type=ep"
|
hx-patch="/api/artists/{artist.mbid}/monitor?type=ep"
|
||||||
hx-target="#artist-list" hx-swap="innerHTML">
|
hx-target="#artist-list"
|
||||||
|
hx-swap="innerHTML">
|
||||||
<span class="label-text text-xs">EP</span>
|
<span class="label-text text-xs">EP</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="label cursor-pointer gap-1">
|
<label class="label cursor-pointer gap-1">
|
||||||
<input type="checkbox" class="checkbox checkbox-xs"
|
<input type="checkbox" class="checkbox checkbox-xs"
|
||||||
{#if artist.broadcastMonitored}checked{/if}
|
{#if artist.broadcastMonitored}checked{/if}
|
||||||
hx-patch="/api/artists/{artist.mbid}/monitor?type=broadcast"
|
hx-patch="/api/artists/{artist.mbid}/monitor?type=broadcast"
|
||||||
hx-target="#artist-list" hx-swap="innerHTML">
|
hx-target="#artist-list"
|
||||||
|
hx-swap="innerHTML">
|
||||||
<span class="label-text text-xs">Broadcast</span>
|
<span class="label-text text-xs">Broadcast</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="label cursor-pointer gap-1">
|
<label class="label cursor-pointer gap-1">
|
||||||
<input type="checkbox" class="checkbox checkbox-xs"
|
<input type="checkbox" class="checkbox checkbox-xs"
|
||||||
{#if artist.otherMonitored}checked{/if}
|
{#if artist.otherMonitored}checked{/if}
|
||||||
hx-patch="/api/artists/{artist.mbid}/monitor?type=other"
|
hx-patch="/api/artists/{artist.mbid}/monitor?type=other"
|
||||||
hx-target="#artist-list" hx-swap="innerHTML">
|
hx-target="#artist-list"
|
||||||
|
hx-swap="innerHTML">
|
||||||
<span class="label-text text-xs">Other</span>
|
<span class="label-text text-xs">Other</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,3 +84,4 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="card bg-base-100 shadow-xl mt-1 max-h-64 overflow-y-auto">
|
<div class="bg-base-100 shadow-xl border border-base-300 rounded-box mt-1 max-h-64 overflow-y-auto">
|
||||||
{#if artists.isEmpty()}
|
{#if artists.isEmpty()}
|
||||||
<div class="p-3 text-sm text-base-content/60">No artists found</div>
|
<div class="p-3 text-sm text-base-content/60">No artists found</div>
|
||||||
{#else}
|
{#else}
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
{#else}
|
{#else}
|
||||||
<button class="btn btn-primary btn-xs"
|
<button class="btn btn-primary btn-xs"
|
||||||
hx-post="/api/artists/{artist.mbid}/track"
|
hx-post="/api/artists/{artist.mbid}/track"
|
||||||
hx-target="#artist-list"
|
hx-target="#search-results"
|
||||||
hx-swap="innerHTML"
|
hx-swap="innerHTML"
|
||||||
onclick="this.closest('#search-results').innerHTML=''; document.querySelector('[name=q]').value=''">
|
onclick="document.getElementById('search-input').value=''">
|
||||||
+ Add
|
+ Add
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -7,40 +7,63 @@
|
|||||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@4.12.23/dist/full.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/daisyui@4.12.23/dist/full.min.css" rel="stylesheet">
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
.search-dropdown {
|
#search-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#search-results {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
var saved = localStorage.getItem('daisyui-theme');
|
||||||
|
if (saved) document.documentElement.setAttribute('data-theme', saved);
|
||||||
|
});
|
||||||
|
function setTheme(sel) {
|
||||||
|
var theme = sel.value;
|
||||||
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
|
localStorage.setItem('daisyui-theme', theme);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-screen bg-base-200">
|
<body class="min-h-screen bg-base-200">
|
||||||
<div class="navbar bg-base-100 shadow-lg px-4">
|
<div class="navbar bg-base-100 shadow-lg px-4">
|
||||||
<div class="flex-1 gap-2">
|
<div class="navbar-start">
|
||||||
<a href="/" class="text-xl font-bold">🎵 DiscDrop</a>
|
<div class="flex items-center gap-2">
|
||||||
<div class="tabs tabs-box ml-4 hidden sm:flex">
|
<a href="/" class="text-xl font-bold">🎵 DiscDrop</a>
|
||||||
<a href="/" class="tab {#if activeTab == 'releases'}tab-active{/if}">Releases</a>
|
<div class="tabs tabs-box ml-2 hidden sm:flex">
|
||||||
<a href="/artists" class="tab {#if activeTab == 'artists'}tab-active{/if}">Artists</a>
|
<a href="/" class="tab {#if activeTab == 'releases'}tab-active{/if}">Releases</a>
|
||||||
|
<a href="/artists" class="tab {#if activeTab == 'artists'}tab-active{/if}">Artists</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-none gap-2 relative">
|
<div class="navbar-center">
|
||||||
<div class="form-control">
|
<div id="search-wrapper" class="w-48 md:w-64">
|
||||||
<input type="text" placeholder="Search artists..."
|
<form hx-get="/api/artists/search"
|
||||||
class="input input-bordered input-sm w-48 md:w-64"
|
hx-target="#search-results"
|
||||||
name="q"
|
hx-swap="innerHTML"
|
||||||
hx-get="/api/artists/search"
|
hx-trigger="keyup changed delay:300ms from:#search-input">
|
||||||
hx-trigger="keyup changed delay:300ms"
|
<input id="search-input"
|
||||||
hx-target="#search-results"
|
type="text"
|
||||||
hx-swap="innerHTML"
|
placeholder="Search artists..."
|
||||||
autocomplete="off">
|
class="input input-bordered input-sm w-full"
|
||||||
|
name="q"
|
||||||
|
autocomplete="off">
|
||||||
|
</form>
|
||||||
|
<div id="search-results"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="search-results" class="search-dropdown"></div>
|
</div>
|
||||||
|
|
||||||
<select class="select select-bordered select-sm w-full max-w-[8rem]" data-choose-theme>
|
<div class="navbar-end gap-2">
|
||||||
<option value="dark">Dark</option>
|
<select class="select select-bordered select-sm w-full max-w-[8rem]"
|
||||||
|
onchange="setTheme(this)">
|
||||||
|
<option value="dark" selected>Dark</option>
|
||||||
<option value="light">Light</option>
|
<option value="light">Light</option>
|
||||||
<option value="synthwave">Synthwave</option>
|
<option value="synthwave">Synthwave</option>
|
||||||
<option value="retro">Retro</option>
|
<option value="retro">Retro</option>
|
||||||
@@ -60,7 +83,7 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<dialog id="settingsModal" class="modal">
|
<dialog id="settingsModal" class="modal">
|
||||||
<div class="modal-box" id="settings-modal-content"
|
<div class="modal-box max-w-md" id="settings-modal-content"
|
||||||
hx-get="/api/settings/form"
|
hx-get="/api/settings/form"
|
||||||
hx-trigger="load"
|
hx-trigger="load"
|
||||||
hx-swap="innerHTML">
|
hx-swap="innerHTML">
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{#include layouts/base}
|
{#include layouts/base}
|
||||||
{#page-content}
|
{#page-content}
|
||||||
<div class="container mx-auto p-4">
|
<div class="w-full max-w-4xl mx-auto">
|
||||||
<h2 class="text-2xl font-bold mb-4">📡 Releases</h2>
|
<h2 class="text-2xl font-bold mb-4 text-center">📡 Releases</h2>
|
||||||
<div id="feed-entries" hx-get="/api/artists/feed-table" hx-trigger="load">
|
<div id="feed-entries" hx-get="/api/artists/feed-table" hx-trigger="load">
|
||||||
<span class="loading loading-spinner loading-lg"></span>
|
<span class="loading loading-spinner loading-lg block mx-auto"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex gap-4">
|
<div class="mt-6 flex justify-center gap-4">
|
||||||
<a href="/feed/rss.xml" class="btn btn-primary">📥 RSS Feed</a>
|
<a href="/feed/rss.xml" class="btn btn-primary">📥 RSS Feed</a>
|
||||||
<a href="/feed/opml" class="btn btn-ghost">📋 OPML</a>
|
<a href="/feed/opml" class="btn btn-ghost">📋 OPML</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user