Initialer commit

This commit is contained in:
2026-05-03 21:51:45 +02:00
commit 7dd108a58e
117 changed files with 9145 additions and 0 deletions

34
bin/main/application.yml Normal file
View File

@@ -0,0 +1,34 @@
server:
port: 8091
spring:
thymeleaf:
cache: false
# ── Spotify (Client Credentials no user login required for public playlists) ──
# Set SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET as environment variables.
# Create credentials at https://developer.spotify.com/dashboard
spotify:
client-id: ${SPOTIFY_CLIENT_ID:}
client-secret: ${SPOTIFY_CLIENT_SECRET:}
redirect-uri: ${SPOTIFY_REDIRECT_URI:http://localhost:8091/spotify/callback}
# ── Tidal OAuth2 PKCE ──
# Set TIDAL_CLIENT_ID as an environment variable.
# Register an app at https://developer.tidal.com
tidal:
client-id: ${TIDAL_CLIENT_ID:}
client-secret: ${TIDAL_CLIENT_SECRET:}
country-code: ${TIDAL_COUNTRY_CODE:DE}
# ── Google / YouTube Data API ──
# Set GOOGLE_API_KEY as an environment variable.
# Create an API key at https://console.cloud.google.com and enable YouTube Data API v3.
google:
api-key: ${GOOGLE_API_KEY:}
# ── Deezer OAuth commented out until app registration is available ──
# deezer:
# app-id: ${DEEZER_APP_ID}
# app-secret: ${DEEZER_APP_SECRET}
# redirect-uri: ${DEEZER_REDIRECT_URI:http://localhost:8091/auth/callback}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="de" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Playlists libredeck</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
<div class="container">
<header>
<h1 class="logo"><a th:href="@{/}">libre<span>deck</span></a></h1>
<div class="header-actions">
<span th:text="${provider}" class="provider-badge"></span>
<a th:href="@{/auth/logout}" class="btn btn-sm">Abmelden</a>
</div>
</header>
<main>
<h2>Deine Playlists</h2>
<p class="hint">Wähle eine Playlist aus, um ein druckfertiges PDF zu generieren.</p>
<div class="playlist-grid">
<div th:each="pl : ${playlists}" class="playlist-card">
<img th:if="${pl.coverUrl != null and !pl.coverUrl.isEmpty()}"
th:src="${pl.coverUrl}" alt="Cover" class="playlist-cover">
<div th:unless="${pl.coverUrl != null and !pl.coverUrl.isEmpty()}"
class="playlist-cover placeholder"></div>
<div class="playlist-info">
<h3 th:text="${pl.title}">Playlist Name</h3>
<p th:text="${pl.trackCount} + ' Titel'" class="track-count"></p>
</div>
<a th:href="@{/generate(playlistId=${pl.id}, playlistTitle=${pl.title})}"
class="btn btn-generate"
th:title="'PDF für ' + ${pl.title} + ' generieren'">
PDF generieren
</a>
</div>
</div>
<div th:if="${#lists.isEmpty(playlists)}" class="empty-state">
<p>Keine Playlists gefunden.</p>
</div>
</main>
</div>
<div id="loading-overlay" style="display:none">
<div class="spinner"></div>
<p>PDF wird generiert…</p>
</div>
<script>
document.querySelectorAll('.btn-generate').forEach(btn => {
btn.addEventListener('click', () => {
document.getElementById('loading-overlay').style.display = 'flex';
});
});
</script>
</body>
</html>