Files
2026-10-03 23:45:04 +02:00

122 lines
8.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# BRETT
An imageboard-style upload and discussion platform built with **Nuxt 4** and **shadcn-vue**. Users can upload files or URLs, comment in threads (including file attachments and voice messages), respond with Discord-style reactions, subscribe to threads, and pick from over 20 themes.
![Screenshot of the upload grid](screenshot.png)
## Features
- **Uploads:** Drag & drop (page dropzone + upload popover), file or URL upload, max. 200 MB. Types: **images** (generated thumbnails), **videos** (embedded Plyr player), **audio** (MP3/FLAC with embedded cover art and music metadata via `music-metadata`, dedicated player dialog), **YouTube** and **Twitch embeds**, plus **link preview cards**
- **Threads & comments:** Every upload is a thread with comments. Comments support **file attachments** and **voice messages** recorded in the browser (converted to MP3 via `ffmpeg-static`). Discord-style **reactions** on both comments *and* uploads (toggle, counters, tooltips, optimistic updates, burst animation), **@mentions** with autocomplete suggestions and profile hover cards — all with **live WebSocket updates**
- **Thread subscriptions & notifications:** Creating, commenting, favoriting, or reacting to a thread automatically subscribes you (`thread_subscriptions` table). Can be toggled per thread via a bell button (a disabled subscription stays disabled even on later participation and also suppresses mentions in that thread). Notifications are **grouped per thread** (no spam from multiple comments), there are **no self-notifications**, and the bell shows live events plus toasts
- **Announcement (MOTD):** Admin-editable slim bar below the header (`MotdBanner.vue`), publicly readable and updated live via WebSocket
- **Tags & filters:** Uploads can be tagged; filter popover by **type, uploader, time range, and tag** (URL query–driven)
- **Command palette:** `Ctrl/Cmd + K` opens a global search across uploads/users/actions
- **Favorites:** Favorite uploads, visible on the profile page
- **Auth & roles:** Session-cookie login (30 days); the first registered user automatically becomes admin. **Admin panel** with user list, role management, and **per-role configurable permissions** (`admin.access`, `roles.manage`, `users.manage`, `site.manage`, `comment.delete`, `upload.delete`, `tag.delete`) — the `admin` role is an implicit wildcard and can do everything; `moderator` is a system role with moderation rights
- **Profiles & personalization:** Profile pages with own uploads, comments, and stats; user overview page. In settings: **avatar** (max. 5 MB), **name color**, **card effects** (WebGL shaders via `ogl` — Aurora, Particles, Plasma, Silk, Dither, Scanner and more as hover-card backgrounds), **border effects**, and vignette
- **Themes:** 21 themes (Standard, Discord, Cyberpunk, Spring, Terminal, Infoseer, Homebrew, Florense, Kawi Green, Clo.nz, Clawmorphism, Realmorphism, Amber, Sunny Sprout, Spotify, Nordic Theme, SS, Amethyst Haze, Deep Wine, myClaude, Neo), switched via `data-theme` on `<html>`, persisted via cookie — SSR-safe without flash. The imported TweakCN themes are included as dark variants only
- **Layouts:** Switchable Bento/uniform grid for the upload overview
## Tech stack
| Area | Technology |
|---|---|
| Framework | Nuxt 4 / Nitro, Vue 3 (`<script setup>` + TS) |
| UI | shadcn-vue (`shadcn-nuxt`), Reka UI, Tailwind CSS v4, Phosphor/Simple Icons via `@nuxt/icon` (local SVG bundle mode), vue-sonner |
| Database | MariaDB 11 + Drizzle ORM (`mysql2`) |
| Realtime | Nitro WebSocket (`crossws`) |
| Media | `@nuxt/image`, Plyr, `music-metadata`, `ffmpeg-static`, `ogl` (shader effects) |
| Data/Utils | `@tanstack/vue-table`, Zod, Unovis charts, dnd-kit-vue |
| State | Nuxt `useState` composables, VueUse |
## Project structure
```
app/
assets/css/tailwind.css # Tailwind v4 entry + all theme tokens ([data-theme="…"])
components/ # App components (header, dropzone, tiles, comments,
# MotdBanner, CommandPalette, ThreadSubscriptionButton, …)
effects/ # WebGL card effects + vignette/border
ui/ # shadcn-vue base components
composables/ # useAuth, useTheme, useCommandPalette, useCommentReactions,
# useNotifications, useCardShader, …
layouts/ middleware/ plugins/ pages/
index.vue # Upload grid with filters / homepage
thread/[id].vue # Thread detail page with comments, attachments, voice recording
admin.vue # Admin panel (users, roles, permissions, MOTD)
settings.vue # Profile settings (avatar, color, card effects)
users.vue user/[id].vue login.vue register.vue
server/
api/ # Nitro endpoints (auth, uploads, comments, notifications,
# users, tags, link-preview, motd, admin/…)
db/schema.ts # Drizzle schema (users, sessions, uploads, comments,
# comment_attachments, comment_reactions,
# upload_reactions, favorites, thread_subscriptions,
# link_previews, notifications, roles, user_roles,
# permissions, role_permissions, tags, upload_tags,
# settings)
db/migrations/ # Generated SQL migrations
plugins/migrate.ts # Runs migrations on server start (dev)
routes/_ws.ts # WebSocket route for live events (broadcastToAll)
utils/ # auth (requirePermission), db, roles, uploads,
# notifications, threadSubscriptions, linkPreview
scripts/migrate.ts # Manual migration runner (npm run db:migrate)
shared/utils/ # Shared utilities — incl. permissions.ts (catalog + seeding)
public/uploads/ # Upload target, incl. uploads/avatars/ (gitignored)
docker-compose.yml # MariaDB container
```
## Setup
**Requirements:** Node.js ≥ 20, Docker (for the bundled MariaDB) or your own MySQL/MariaDB instance.
```bash
# 1. Start the database
docker compose up -d
# 2. Configure environment
cp .env.example .env # contains DATABASE_URL, matches the Docker setup
# 3. Install dependencies
npm install
# 4. Fetch the ffmpeg binary (needed for voice messages → MP3;
# the download runs in ffmpeg-static's postinstall script, which may be
# blocked depending on your npm configuration)
node node_modules/ffmpeg-static/install.js
# 5. Start the dev server (migrations + permission seeding run automatically)
npm run dev
```
The app runs on <http://localhost:3000>. The **first registered account automatically gets the admin role** and can then create additional roles, assign permissions, and set the announcement in the admin panel.
### Without Docker
Provide your own MariaDB/MySQL instance and adjust `DATABASE_URL` in `.env`:
```
DATABASE_URL=mysql://user:pass@host:3306/database
```
## NPM scripts
| Command | Purpose |
|---|---|
| `npm run dev` | Dev server with HMR, auto-migrations |
| `npm run build` | Production build |
| `npm run preview` | Run the built app locally |
| `npm run db:generate` | Generate a new Drizzle migration from a schema change |
| `npm run db:migrate` | Apply migrations manually |
| `npm run db:studio` | Drizzle Studio (DB GUI) |
## Development
- **Change the schema:** Edit `server/db/schema.ts` → `npm run db:generate` → commit the migration. In dev mode, `server/plugins/migrate.ts` applies it automatically on start.
- **Add a new permission:** Add an entry to `PERMISSIONS` in `shared/utils/permissions.ts` → seeded on next start via `seedPermissions()`; check server-side with `requirePermission(event, key)`, client-side with `can(key)` from `useAuth`.
- **Live events:** The server broadcasts via `broadcastToAll` in `server/routes/_ws.ts`; the client handles events in `app/plugins/notifications.client.ts`.
- **Add a new theme:** Create a `[data-theme="…"]` block with the CSS variables in `app/assets/css/tailwind.css`, add the name to `THEME_NAMES` in `app/composables/useTheme.ts`, and add an entry in `app/components/ThemeSelector.vue`. Themes can also override the `shadow-*` tokens (glow, offset, or flat shadows) and `font-sans`/`font-heading`/`font-mono`; `--font-logo` intentionally stays theme-independent.
- **Conventions:** Semantic tokens instead of hardcoded colors (`primary`, `accent`, `muted`, …); `rounded-md`/`rounded-sm` scale via `--radius` with the theme (`neo` is square: `--radius: 0`). More details in `AGENTS.md`.
- **Health check:** `GET /api/health` reports the DB connection status.