runtimeConfig values are inlined at build time; only NUXT_* env vars override them at runtime, so DATABASE_URL never reached the app and every request failed with a 500. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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), react with emojis and stickers, chat via direct and group messages, collect uploads in halls, and spend points in the shop - in German or English, with over 30 themes.
Features
- Uploads: Drag & drop (page dropzone + upload popover), paste media or URLs straight from the clipboard, file or URL upload. 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 (recursive quotes, lightbox). Comments support file attachments and voice messages recorded in the browser (converted to MP3 via
ffmpeg-static, optional noise suppression). Discord-style reactions on comments and uploads - emojis and stickers - with toggle, counters, tooltips, optimistic updates and burst animation, plus @mentions with autocomplete and profile hover cards. Bot commands in the composer (magic 8-ball, coin flip, choose, reminders, …). All with live WebSocket updates and smooth view transitions - Direct & group messages: Floating, draggable/resizable chat windows plus a dedicated messages page; attachments, voice messages, reactions and read receipts. Group chats support invitations that must be accepted or declined, leaving groups, and creator-only member removal - synced live via
pushToUser - Follows & blocking: Follow users to get notified about their uploads and to use the followed-only grid filter. Blocking hides a user's uploads and comments, blocks DMs, and removes them from mention suggestions
- Reports & mod panel: Report uploads, comments, or users (reason + details, self- and duplicate-protected). The mod panel (
mod.access,report.moderate) provides an enriched queue with resolve/dismiss actions and direct links to targets - Thread moderation: Per-thread locks for comments, reactions, stickers, links, and videos (
thread.moderate) - Thread subscriptions & notifications: Creating, commenting, favoriting, or reacting to a thread automatically subscribes you (
thread_subscriptionstable). 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, tag, calendar date range, and followed users. Admins can attach icons (emoji, sticker, Iconify, or uploaded image) and animation effects to tags (
tag.manage) - Command palette:
Ctrl/Cmd + Kopens a global search across uploads/users/actions, including a random-thread action - Favorites: Favorite uploads, visible on the profile page
- Halls: Curated upload collections - user halls and admin-managed standard halls (
hall.standard), with visibility controls, stacked cover thumbnails, and a uniform thumbnail grid - Stickers & memes: Standard stickers (admin-managed,
sticker.standard) plus personal sticker slots from the shop;:name:shortcodes render inline. Meme generator with admin-managed standard templates (meme.standard) - Points & shop: Point balance spent on public display names, flair, animated name gradients, golden upload frames (animated pulse/beam variants), timed upload effects, a mystery box, and extra sticker slots. Purchased effects can be deactivated without a refund. Prices are admin-configurable, and admins can gift points to users
- Bot & YouTube monitoring: Composer bot commands (8-ball, coin, choose, reminders, …) and monitored YouTube channels polled on a configurable interval - new videos are uploaded automatically as bot posts
- Statistics: Stats page with Unovis charts
- 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,thread.moderate,upload.delete,tag.delete,tag.manage,sticker.standard,meme.standard,hall.standard,mod.access,report.moderate) - theadminrole is an implicit wildcard and can do everything;moderatoris a system role with moderation rights. Configurable site limits (upload sizes, attachments, …) and optional guest access via site settings - Profiles & personalization: Profile pages with own uploads, comments, stats, bio and role badges. Rich hover cards with media banners and 3D holographic effects (Galaxy, Cosmos holofoil). 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: 33 themes (Standard, Discord Ash/Dark/Onyx, 4f, Cyberpunk, Spring, Terminal, Infoseer, Homebrew, Florense, Kawi Green, Clo.nz, Clawmorphism, Realmorphism, Amber, Sunny Sprout, Spotify, Nordic Theme, SS, Amethyst Haze, Deep Wine, myClaude, Focused, Claude +, 2077, Astra, Apotheosis Mint Midnight, Emerald, Vulkan, Sakura, Minimal Neutral, Neo), switched via
data-themeon<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; Bento tiles grow with activity
- i18n: German (default) and English UI via
@nuxtjs/i18n, persisted in a locale cookie
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, emoji-picker-element |
| i18n | @nuxtjs/i18n (de + en) |
| Database | MariaDB 11 + Drizzle ORM (mysql2) |
| Realtime | Nitro WebSocket (crossws) - broadcastToAll + per-user pushToUser |
| 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, DmSidebar/DmFloatingWindow,
# StickerPicker, ReportDialog, UploadFilters, halls, …)
effects/ # WebGL card effects + vignette/border
ui/ # shadcn-vue base components
composables/ # useAuth, useTheme, useCommandPalette, useCommentReactions,
# useNotifications, useDirectMessages, useStickers,
# useSiteSettings, useCardShader, …
layouts/ middleware/ plugins/ pages/
index.vue # Upload grid with filters / homepage
t/[id].vue # Thread detail page with comments, attachments, voice recording
messages/[[id]].vue # Direct & group messages
halls/ # Hall overview + detail page
memes.vue shop.vue stats.vue channels.vue
admin.vue # Admin panel (users, roles, permissions, limits, prices,
# MOTD, tags, site settings)
mod.vue # Moderator panel (report queue)
settings.vue # Profile settings (avatar, color, card effects, bio)
users.vue user/[id].vue login.vue register.vue
server/
api/ # Nitro endpoints (auth, uploads, comments, notifications,
# users, tags, halls, stickers, memes, messages, reports,
# mod, shop, stats, youtube, admin/, link-preview, motd,
# site, limits, icons, health)
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, direct_messages(+_reactions), dm_groups(+_members),
# user_follows, user_blocks, reports, halls(+_items),
# stickers, meme_templates, point_purchases, bot_reminders,
# youtube_channels(+_subs), site_stats)
db/migrations/ # Generated SQL migrations
plugins/migrate.ts # Runs migrations + permission seeding on server start (dev only)
routes/_ws.ts # WebSocket route for live events (broadcastToAll / pushToUser)
routes/uploads/ # Uploaded-file serving with guest-access check
utils/ # auth (requirePermission), db, roles, uploads, blocks,
# notifications, threadSubscriptions, linkPreview,
# bot, dm, halls, youtubeFeed, site, limits, shop, quotes
scripts/migrate.ts # Manual migration runner (npm run db:migrate)
shared/utils/ # Shared utilities - incl. permissions.ts (catalog + seeding),
# reports.ts, site.ts, messages.ts, stickers.ts, bot.ts
i18n/locales/ # de.json + en.json (same key structure)
uploads/ # Upload target (gitignored): media, avatars, stickers,
# tag-icons, hall covers, thumbnails, …
Dockerfile # Two-stage build → Nitro .output (production)
docker-compose.yml # MariaDB container (local development)
docker-compose.prod.yml # Production stack (app + MariaDB) for Coolify
Setup
Requirements: Node.js ≥ 20, Docker (for the bundled MariaDB) or your own MySQL/MariaDB instance.
# 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
Production / self-hosting
docker-compose.prod.yml defines the production stack (Nuxt app + MariaDB with healthcheck, persistent uploads and db_data volumes) and is meant for orchestrators like Coolify. Notes:
DB_PASSWORD/DB_ROOT_PASSWORDcome from the environment (set them in Coolify, never commit them)- The
Dockerfilebuilds the Nitro output onnode:24-slim(Debian - required for theffmpeg-staticbinary); migrations + permission seeding run automatically on container start - Uploads live in
uploads/relative to the app working directory - keep it on a persistent volume - The app serves everything (HTTP + WebSocket) on port 3000 behind the reverse proxy
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.tsapplies it automatically on start. - Add a new permission: Add an entry to
PERMISSIONSinshared/utils/permissions.ts→ seeded on next start viaseedPermissions(); check server-side withrequirePermission(event, key), client-side withcan(key)fromuseAuth. - Live events: The server broadcasts via
broadcastToAll/pushToUserinserver/routes/_ws.ts; the client handles events inapp/plugins/notifications.client.tsand registered handlers (e.g. DM handlers inuseDirectMessages). - UI strings: All strings go through
$t()/t()- keepi18n/locales/de.jsonanden.jsonkey-identical. - Add a new theme: Create a
[data-theme="…"]block with the CSS variables inapp/assets/css/tailwind.css, add the name toTHEME_NAMESandTHEMESinapp/composables/useTheme.ts. Themes can also override theshadow-*tokens (glow, offset, or flat shadows) andfont-sans/font-heading/font-mono;--font-logointentionally stays theme-independent. - Conventions: Semantic tokens instead of hardcoded colors (
primary,accent,muted, …);rounded-md/rounded-smscale via--radiuswith the theme (neois square:--radius: 0). More details inAGENTS.md. - Health check:
GET /api/healthreports the DB connection status.
