feat: add social features, user uploads, AI music generation, and PWA enhancements
- Add social features: like/favorite songs and playlists, notifications, and public profile pages - Enable user music: logged-in users can upload songs and create playlists with visibility controls and NSFW flag - Implement AI music generation via ComfyUI with per-user rate limiting, workflow configuration, and optional cover art generation - Expand admin panel for user management and AI settings - Enhance PWA support: lock-screen controls, background playback, and visualizer - Add email verification via SMTP and avatar cropping - Update package version to 1.0.0 and bump service worker cache
This commit is contained in:
parent
13a2ad52e5
commit
8170cfb7a2
141
README.md
141
README.md
|
|
@ -1,36 +1,56 @@
|
|||
# Tunes
|
||||
|
||||
A self-contained music website: browse and listen to songs and playlists,
|
||||
with user accounts and an admin upload UI. Everything runs from a single
|
||||
Node.js process with an embedded SQLite database and local media storage —
|
||||
no external services required.
|
||||
A self-hosted music website: browse and listen to songs and playlists, with
|
||||
user accounts, social features, and AI-assisted music and cover art generation.
|
||||
Everything runs from a single Node.js process with an embedded SQLite database
|
||||
and local media storage — no external services required beyond an optional
|
||||
ComfyUI instance for AI generation.
|
||||
|
||||
## Features (v1)
|
||||
## Features
|
||||
|
||||
- **Public browse & listen**
|
||||
- Home page with featured playlists and recently added songs
|
||||
- Paginated, searchable song list (title / artist / album)
|
||||
- Playlist grid and detail view with "Play all"
|
||||
- Persistent bottom-of-page audio player with queue support
|
||||
- HTTP Range streaming, so seeking/scrubbing works correctly
|
||||
- Persistent bottom-of-page audio player with queue support, repeat, and visualizer
|
||||
- HTTP Range streaming so seeking/scrubbing works correctly
|
||||
- PWA-ready with Media Session API: lock-screen controls and background playback on mobile
|
||||
- Lightbox for cover art
|
||||
|
||||
- **User accounts**
|
||||
- Email + password registration (argon2id hashing)
|
||||
- Optional email verification via SMTP
|
||||
- Login / logout with SQLite-backed sessions (survive restarts)
|
||||
- Account page: update display name, change password
|
||||
- Account page: update display name, avatar, and password
|
||||
- Public profile pages with song/playlist activity
|
||||
- CSRF protection on all state-changing routes
|
||||
- **Admin**
|
||||
- Upload songs with cover art; title/artist/album/genre/year metadata
|
||||
|
||||
- **Social**
|
||||
- Like and favorite songs and playlists
|
||||
- In-app notifications
|
||||
- Profile pages showing liked and favorited content
|
||||
|
||||
- **User music**
|
||||
- Logged-in users can upload their own songs and create playlists
|
||||
- Visibility controls: `public`, `logged_in`, `vip`, or `private`
|
||||
- NSFW flag for explicit content
|
||||
|
||||
- **AI music generation** (requires ComfyUI)
|
||||
- Generate songs from style prompts, lyrics, BPM, key, duration, and creativity settings
|
||||
- Per-user rate limiting (cooldown + hourly cap)
|
||||
- Generated songs can be previewed, published, or deleted
|
||||
- Optionally generate album cover art after publishing
|
||||
|
||||
- **Admin panel** (`/admin`)
|
||||
- User management: disable, enable, verify, grant/revoke VIP, delete
|
||||
- ComfyUI integration settings: base URL, music workflow JSON, image workflow JSON, node ID mappings
|
||||
- Toggle music generation and image generation on/off site-wide
|
||||
|
||||
- **My Music** (`/mymusic`) — for any logged-in user
|
||||
- Upload songs with cover art; title/artist/album/genre/year/BPM metadata
|
||||
- Duration auto-extracted from the audio file
|
||||
- Create and edit playlists, add/remove tracks
|
||||
- Toggle songs and playlists between public and private
|
||||
- First user who registers with `ADMIN_BOOTSTRAP_EMAIL` is auto-promoted
|
||||
to admin (only while no admin exists yet)
|
||||
|
||||
## Roadmap (not in v1)
|
||||
|
||||
- Generate new music via an external API from within the app
|
||||
- Let logged-in users publish their own songs and create playlists
|
||||
- Tune into streaming radio stations
|
||||
- Create and edit playlists, add/remove/reorder tracks
|
||||
- Bulk visibility changes
|
||||
|
||||
## Tech stack
|
||||
|
||||
|
|
@ -39,10 +59,13 @@ no external services required.
|
|||
- **EJS** + `express-ejs-layouts` — server-rendered views
|
||||
- **argon2** — password hashing
|
||||
- **express-session** + `better-sqlite3-session-store` — persistent sessions
|
||||
- **multer** — file uploads (audio + cover art)
|
||||
- **multer** — file uploads (audio, cover art, avatars)
|
||||
- **sharp** — server-side image resizing and conversion
|
||||
- **music-metadata** — extract duration from uploaded audio
|
||||
- **nodemailer** — email verification
|
||||
- **csrf-sync**, **zod**, **pino** — CSRF, validation, logging
|
||||
- **htmx** (vendored) — small sprinklings of interactivity, no build step
|
||||
- **htmx** (vendored) — progressive interactivity, no build step
|
||||
- **CropperJS** (vendored) — client-side avatar cropping
|
||||
|
||||
## Getting started
|
||||
|
||||
|
|
@ -50,6 +73,7 @@ no external services required.
|
|||
|
||||
- Node.js 18+ (tested on 18.19)
|
||||
- npm
|
||||
- *(optional)* A running [ComfyUI](https://github.com/comfyanonymous/ComfyUI) instance for AI generation
|
||||
|
||||
### Install and run
|
||||
|
||||
|
|
@ -58,13 +82,11 @@ git clone <your-repo-url> tunes
|
|||
cd tunes
|
||||
npm install
|
||||
cp .env.example .env
|
||||
# Edit .env:
|
||||
# SESSION_SECRET=<a long random string>
|
||||
# ADMIN_BOOTSTRAP_EMAIL=<your email>
|
||||
# Edit .env — at minimum set SESSION_SECRET and ADMIN_BOOTSTRAP_EMAIL
|
||||
node server.js
|
||||
```
|
||||
|
||||
The server boots at http://localhost:3000 and runs database migrations
|
||||
The server starts at http://localhost:3000 and runs database migrations
|
||||
automatically against `tunes.db` in the project root.
|
||||
|
||||
For auto-reload during development:
|
||||
|
|
@ -78,35 +100,51 @@ npm run dev
|
|||
1. Open http://localhost:3000/register
|
||||
2. Register with the email you set as `ADMIN_BOOTSTRAP_EMAIL` in `.env`.
|
||||
That first matching registration is automatically given the `admin` role.
|
||||
3. Once logged in, visit `/admin/songs` to upload audio and `/admin/playlists`
|
||||
to create playlists.
|
||||
3. Visit `/admin` to manage users and configure generation settings.
|
||||
4. Visit `/mymusic/songs` to upload audio and `/mymusic/playlists` to create playlists.
|
||||
|
||||
Any subsequent registrations are normal users.
|
||||
|
||||
### Adding music
|
||||
|
||||
1. Go to `/admin/songs/new`
|
||||
1. Go to `/mymusic/songs/new`
|
||||
2. Fill in title, artist, and (optionally) album/genre/year
|
||||
3. Choose an audio file (mp3, m4a, ogg, flac, wav, opus, webm — up to 100 MB)
|
||||
4. Optionally add a cover image
|
||||
5. Check "Public" so visitors can see and play it, then click **Upload**
|
||||
5. Set visibility to **Public** so visitors can see and play it, then click **Upload**
|
||||
|
||||
Uploaded files live under `media/audio/` and `media/covers/`. The database
|
||||
stores relative paths, so you can move the whole project folder freely as
|
||||
long as `media/` and `tunes.db` stay together.
|
||||
stores relative paths, so you can move the whole project folder freely as long
|
||||
as `media/` and `tunes.db` stay together.
|
||||
|
||||
### AI generation setup
|
||||
|
||||
1. Stand up a ComfyUI instance accessible from the server.
|
||||
2. In `/admin`, set the **ComfyUI Base URL** (e.g. `http://localhost:8188`).
|
||||
3. Upload a **Music Workflow JSON** and fill in the node ID mappings for your workflow.
|
||||
4. Optionally upload an **Image Workflow JSON** and set the prompt node ID for cover art.
|
||||
5. Check **Enable Song Generation** (and **Enable Album Cover Generation** if desired).
|
||||
6. VIP users (and admins) can now visit `/generate` to produce songs.
|
||||
|
||||
## Configuration
|
||||
|
||||
All settings come from environment variables (loaded from `.env` via
|
||||
`dotenv`):
|
||||
All settings come from environment variables (loaded from `.env` via `dotenv`):
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `PORT` | `3000` | HTTP port |
|
||||
| `SESSION_SECRET` | *(insecure default)* | Session cookie signing secret — **set this in production** |
|
||||
| `ADMIN_BOOTSTRAP_EMAIL` | *(unset)* | Email that auto-becomes admin on first registration |
|
||||
| `DB_PATH` | `./tunes.db` | SQLite database file |
|
||||
| `MEDIA_DIR` | `./media` | Root directory for audio and cover uploads |
|
||||
| `SESSION_SECRET` | *(insecure default)* | Session cookie signing key — **set this in production** |
|
||||
| `ADMIN_BOOTSTRAP_EMAIL` | *(unset)* | Email auto-promoted to admin on first registration |
|
||||
| `DB_PATH` | `./tunes.db` | SQLite database file path |
|
||||
| `MEDIA_DIR` | `./media` | Root directory for all uploaded and generated files |
|
||||
| `SITE_NAME` | `Bri-Tunes` | Site name shown in the header and page titles |
|
||||
| `APP_BASE_URL` | `http://localhost:3000` | Full URL used in email verification links |
|
||||
| `SMTP_HOST` | *(unset)* | SMTP server hostname (leave blank to print verify URLs to console) |
|
||||
| `SMTP_PORT` | `587` | SMTP port |
|
||||
| `SMTP_SECURE` | `false` | Use TLS (`true`) or STARTTLS (`false`) |
|
||||
| `SMTP_USER` | *(unset)* | SMTP username |
|
||||
| `SMTP_PASS` | *(unset)* | SMTP password |
|
||||
| `SMTP_FROM` | *(unset)* | `From` address for outgoing email |
|
||||
|
||||
## Project layout
|
||||
|
||||
|
|
@ -115,26 +153,31 @@ tunes/
|
|||
├── server.js # entry point: loads env, runs migrations, starts app
|
||||
├── db/
|
||||
│ ├── index.js # SQLite connection + migration runner
|
||||
│ └── migrations/*.sql # schema migrations
|
||||
│ └── migrations/*.sql # numbered schema migrations
|
||||
├── src/
|
||||
│ ├── app.js # Express wiring (sessions, CSRF, views, routes)
|
||||
│ ├── middleware/auth.js # requireUser / requireAdmin
|
||||
│ ├── middleware/auth.js # requireUser / requireAdmin / requireVip
|
||||
│ ├── routes/
|
||||
│ │ ├── public.js # /, /songs, /playlists, /stream/:id
|
||||
│ │ ├── auth.js # /register, /login, /logout
|
||||
│ │ ├── account.js # /account
|
||||
│ │ └── admin.js # /admin/songs, /admin/playlists
|
||||
│ ├── services/ # users, songs, playlists (DB access)
|
||||
│ │ ├── public.js # /, /songs, /playlists, /profiles, /stream/:id
|
||||
│ │ ├── auth.js # /register, /login, /logout, /verify-email
|
||||
│ │ ├── account.js # /account — profile and password settings
|
||||
│ │ ├── admin.js # /mymusic — user song and playlist management
|
||||
│ │ ├── admin-panel.js # /admin — site administration
|
||||
│ │ ├── generate.js # /generate — AI music + cover art generation
|
||||
│ │ └── social.js # /api — likes, favorites
|
||||
│ ├── services/ # users, songs, playlists, social, generation, settings
|
||||
│ └── views/ # EJS templates
|
||||
├── public/ # static CSS, JS, vendored htmx
|
||||
└── media/ # runtime audio + cover uploads (git-ignored)
|
||||
├── public/
|
||||
│ ├── css/app.css # main stylesheet
|
||||
│ └── js/ # player, visualizer, social, lightbox, etc.
|
||||
└── media/ # runtime uploads and generated files (git-ignored)
|
||||
```
|
||||
|
||||
## Data and backups
|
||||
|
||||
Everything you care about lives in two places:
|
||||
|
||||
- `tunes.db` — users, songs, playlists, sessions
|
||||
- `media/` — audio files and cover art
|
||||
- `tunes.db` — users, songs, playlists, sessions, notifications, generation records
|
||||
- `media/` — audio files, cover art, avatars, and AI-generated files
|
||||
|
||||
Back up both together to preserve the full state of the site.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "tunes",
|
||||
"version": "0.1.0",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Self-contained music website with browse/listen + user accounts",
|
||||
"main": "server.js",
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@
|
|||
const cur = state.queue[state.index];
|
||||
if (!cur) return;
|
||||
window.briTunesNowPlaying = cur;
|
||||
transitioning = true;
|
||||
audio.src = `/stream/${cur.id}`;
|
||||
audio.play()
|
||||
.then(() => window.dispatchEvent(new CustomEvent('briTunes:play', { detail: cur })))
|
||||
|
|
@ -128,9 +129,30 @@
|
|||
if (state.index > 0) { state.index -= 1; playCurrent(); }
|
||||
}
|
||||
|
||||
// Track whether we're in the middle of a track transition so the 'pause'
|
||||
// event fired by src reassignment doesn't mislead iOS into thinking playback stopped.
|
||||
let transitioning = false;
|
||||
|
||||
audio.addEventListener('ended', next);
|
||||
audio.addEventListener('play', () => { if ('mediaSession' in navigator) navigator.mediaSession.playbackState = 'playing'; });
|
||||
audio.addEventListener('pause', () => { if ('mediaSession' in navigator) navigator.mediaSession.playbackState = 'paused'; });
|
||||
audio.addEventListener('play', () => {
|
||||
transitioning = false;
|
||||
if ('mediaSession' in navigator) navigator.mediaSession.playbackState = 'playing';
|
||||
});
|
||||
audio.addEventListener('pause', () => {
|
||||
if (transitioning) return;
|
||||
if ('mediaSession' in navigator) navigator.mediaSession.playbackState = 'paused';
|
||||
});
|
||||
// setPositionState keeps iOS from throttling the JS thread during background playback.
|
||||
audio.addEventListener('timeupdate', () => {
|
||||
if (!('mediaSession' in navigator) || !audio.duration || !isFinite(audio.duration)) return;
|
||||
try {
|
||||
navigator.mediaSession.setPositionState({
|
||||
duration: audio.duration,
|
||||
playbackRate: audio.playbackRate,
|
||||
position: audio.currentTime,
|
||||
});
|
||||
} catch (_) {}
|
||||
});
|
||||
nextBtn.addEventListener('click', next);
|
||||
|
||||
if ('mediaSession' in navigator) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const CACHE = 'bri-tunes-v2';
|
||||
const CACHE = 'bri-tunes-v3';
|
||||
const STATIC_PRECACHE = [
|
||||
'/',
|
||||
'/static/css/app.css',
|
||||
|
|
|
|||
Loading…
Reference in New Issue