FliesentischKingandDevin 82e679e316 Fix production database URL injection via NUXT_DATABASE_URL
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>
2026-10-25 14:23:04 +01:00
2026-10-03 20:07:49 +02:00
2026-10-22 21:24:43 +02:00
2026-10-03 20:07:49 +02:00
2026-10-03 20:07:49 +02:00
2026-10-03 20:07:49 +02:00
2026-10-25 14:04:00 +01:00
2026-10-03 20:07:49 +02:00

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.

Screenshot of the upload grid

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_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, 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 + K opens 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) - the admin role is an implicit wildcard and can do everything; moderator is 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-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; 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_PASSWORD come from the environment (set them in Coolify, never commit them)
  • The Dockerfile builds the Nitro output on node:24-slim (Debian - required for the ffmpeg-static binary); 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.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/pushToUser in server/routes/_ws.ts; the client handles events in app/plugins/notifications.client.ts and registered handlers (e.g. DM handlers in useDirectMessages).
  • UI strings: All strings go through $t()/t() - keep i18n/locales/de.json and en.json key-identical.
  • 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 and THEMES in app/composables/useTheme.ts. 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.
S
Description
Ultimate Imageboard Software
Readme
7.4 MiB
Languages
Vue 61.5%
TypeScript 32.8%
CSS 5.6%