Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ Built with a serverless architecture on Cloudflare Pages, it's secure, infinitel
- **Duplicate Prevention**: Intelligently prevents users from adding a song that is already in the queue or currently playing.
- **Consent-Driven Analytics**: Integrates with **Google Analytics** and asks for user consent via a two-step welcome modal, ensuring privacy compliance.
- **Robust Search & Previews**: Instantly search Spotify's entire library and listen to 30-second audio previews before queueing.
- **Full Admin Control**:
- **Maintenance Mode**: Easily take the site offline with a single environment variable, redirecting all traffic to a maintenance page.
- **Protected Rate-Limit Reset**: A dedicated `/reset.html` page, secured by **hCaptcha**, allows users to reset their local anti-spam limits.
- **Maintenance Mode**: Easily take the site offline with a single environment variable, redirecting all traffic to a maintenance page.
- **Event Customization**: Centrally manage your event's name and branding through environment variables.
- **Modern UI/UX**: A polished dark-mode interface with smooth animations, toast notifications, and confetti effects for a premium user experience.

Expand Down
23 changes: 23 additions & 0 deletions functions/_middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export async function onRequest(context) {
const { request, env, next } = context;
const url = new URL(request.url);
const pathname = url.pathname;

if (env.MAINTENANCE !== 'TRUE') {
return await next();
}

if (pathname.startsWith('/maintenance.html')) {
return await next();
}

const maintenanceAsset = await env.ASSETS.fetch(new URL('/maintenance.html', request.url));

return new Response(maintenanceAsset.body, {
status: 503,
statusText: 'Service Unavailable',
headers: {
'Content-Type': 'text/html; charset=utf-8',
},
});
}
7 changes: 2 additions & 5 deletions functions/api/config.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
// functions/api/config.js

export async function onRequestGet(context) {
// Legge la variabile d'ambiente EVENT_NAME. Se non è definita, usa un valore di default.
const eventName = context.env.EVENT_NAME || "Il Nostro Fantastico Evento";
const googleAnalyticsId = context.env.GOOGLE_ANALYTICS_ID || null;

// Prepara i dati di configurazione da inviare al frontend
const config = {
eventName: eventName,
googleAnalyticsId: googleAnalyticsId,
};

// Restituisce la configurazione in formato JSON
return new Response(JSON.stringify(config), {
status: 200,
headers: { 'Content-Type': 'application/json' },
Expand Down
60 changes: 60 additions & 0 deletions functions/api/queue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
async function getAccessToken(context) {
const { SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, SPOTIFY_REFRESH_TOKEN } = context.env;
if (!SPOTIFY_CLIENT_ID || !SPOTIFY_CLIENT_SECRET || !SPOTIFY_REFRESH_TOKEN) {
throw new Error("Variabili d'ambiente del server non configurate.");
}

const basicAuth = btoa(`${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`);
const response = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST',
headers: { 'Authorization': `Basic ${basicAuth}`, 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: SPOTIFY_REFRESH_TOKEN }),
});

if (!response.ok) throw new Error("Impossibile rinfrescare il token di Spotify.");
const data = await response.json();
return data.access_token;
}

export async function onRequestGet(context) {
try {
const accessToken = await getAccessToken(context);
const queueEndpoint = 'https://api.spotify.com/v1/me/player/queue';

const queueResponse = await fetch(queueEndpoint, {
headers: { 'Authorization': `Bearer ${accessToken}` },
});

if (queueResponse.status === 204 || queueResponse.status > 400) {
return new Response(JSON.stringify({ nowPlaying: null, queue: [] }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}

const data = await queueResponse.json();

const nowPlaying = data.currently_playing ? {
uri: data.currently_playing.uri,
} : null;

const queue = (data.queue || []).map(item => ({
track: item.name || 'Traccia Sconosciuta',
artists: (item.artists || []).map(a => a.name).join(', '),
albumCover: item.album?.images?.[0]?.url || '',
uri: item.uri || '',
}));

return new Response(JSON.stringify({ nowPlaying, queue }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});

} catch (error) {
console.error("Errore nella funzione queue:", error.message);
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}
83 changes: 83 additions & 0 deletions maintenance.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#121212" />
<title>Temporanemente non disponibile</title>
<style>
*, *::before, *::after {
box-sizing: border-box;
}

html, body {
height: 100%;
}

body {
margin: 0;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;

background:
radial-gradient(1200px 600px at 20% -10%, rgba(29,185,84,.18), rgba(29,185,84,0) 60%),
radial-gradient(800px 400px at 110% 0%, rgba(30,215,96,.12), rgba(30,215,96,0) 60%),
linear-gradient(180deg, #0e0e0e 0%, #121212 35%, #0f1f15 100%);

color: #fafafa;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial;
}

.message-card {
background: #181818;
border: 1px solid rgba(255, 255, 255, .06);
border-radius: 14px;
padding: 40px;
max-width: 500px;
width: 100%;
text-align: center;
box-shadow: 0 10px 30px rgba(0, 0, 0, .35);
}

.logo {
height: 60px;
width: 60px;
margin: 0 auto 25px auto;
display: block;
}

h1 {
font-size: 24px;
font-weight: 800;
color: #fafafa;
margin-top: 0;
margin-bottom: 12px;
}

p {
color: #b3b3b3;
font-size: 16px;
margin: 0;
}
</style>
</head>
<body>

<main>
<div class="message-card">

<h1>Servizio momentaneamente non disponibile</h1>

<p>
Ci scusiamo per il disagio.<br>L'applicazione è in fase di manutenzione o l'evento potrebbe non essere ancora iniziato!<br>Riprova tra qualche minuto.
</p>
</div>
</main>

</body>
</html>
185 changes: 0 additions & 185 deletions reset.html

This file was deleted.

Loading