141 lines
4.9 KiB
Markdown
141 lines
4.9 KiB
Markdown
# 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.
|
|
|
|
## Features (v1)
|
|
|
|
- **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
|
|
- **User accounts**
|
|
- Email + password registration (argon2id hashing)
|
|
- Login / logout with SQLite-backed sessions (survive restarts)
|
|
- Account page: update display name, change password
|
|
- CSRF protection on all state-changing routes
|
|
- **Admin**
|
|
- Upload songs with cover art; title/artist/album/genre/year 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
|
|
|
|
## Tech stack
|
|
|
|
- **Node.js + Express 4** — server and routing
|
|
- **SQLite** via `better-sqlite3` — embedded database, WAL mode
|
|
- **EJS** + `express-ejs-layouts` — server-rendered views
|
|
- **argon2** — password hashing
|
|
- **express-session** + `better-sqlite3-session-store` — persistent sessions
|
|
- **multer** — file uploads (audio + cover art)
|
|
- **music-metadata** — extract duration from uploaded audio
|
|
- **csrf-sync**, **zod**, **pino** — CSRF, validation, logging
|
|
- **htmx** (vendored) — small sprinklings of interactivity, no build step
|
|
|
|
## Getting started
|
|
|
|
### Prerequisites
|
|
|
|
- Node.js 18+ (tested on 18.19)
|
|
- npm
|
|
|
|
### Install and run
|
|
|
|
```bash
|
|
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>
|
|
node server.js
|
|
```
|
|
|
|
The server boots at http://localhost:3000 and runs database migrations
|
|
automatically against `tunes.db` in the project root.
|
|
|
|
For auto-reload during development:
|
|
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
### Creating the first admin
|
|
|
|
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.
|
|
|
|
Any subsequent registrations are normal users.
|
|
|
|
### Adding music
|
|
|
|
1. Go to `/admin/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**
|
|
|
|
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.
|
|
|
|
## Configuration
|
|
|
|
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 |
|
|
|
|
## Project layout
|
|
|
|
```
|
|
tunes/
|
|
├── server.js # entry point: loads env, runs migrations, starts app
|
|
├── db/
|
|
│ ├── index.js # SQLite connection + migration runner
|
|
│ └── migrations/*.sql # schema migrations
|
|
├── src/
|
|
│ ├── app.js # Express wiring (sessions, CSRF, views, routes)
|
|
│ ├── middleware/auth.js # requireUser / requireAdmin
|
|
│ ├── 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)
|
|
│ └── views/ # EJS templates
|
|
├── public/ # static CSS, JS, vendored htmx
|
|
└── media/ # runtime audio + cover uploads (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
|
|
|
|
Back up both together to preserve the full state of the site.
|