DO NOT PUSH ANYTHING TO GITHUB WITHOUT EXPLICIT USER INSTRUCTIONS. THE APP IS SERVED IN PRODUCTION.
Photography Lounge (PL) Discord bot with curated discussion prompts and an admin dashboard. Single-server deployment — exclusively serves the Photography Lounge Discord server.
Pnpm monorepo with three apps and one shared package:
apps/bot Discord.js bot (commands, permissions middleware, auto-leave guard)
apps/dashboard Next.js 16 admin dashboard (NextAuth + Discord OAuth, feature toggles, audit logs)
apps/voting Next.js 16 community prompt voting site (Discord OAuth, upvote/downvote, admin view)
packages/db Prisma client + schema
Prisma models: FeatureConfig, ConfigAuditLog, DiscussionSchedule, DiscussionPromptLog, Prompt, PromptTag, PromptVote, PromptDuplicateFlag (plus NextAuth models: Account, Session, User, VerificationToken).
pnpm init:local # Interactive first-time setup (prompts for creds, generates .env)
pnpm dev:local # One-command local dev: Docker + migrations + builds + apps
pnpm dev # Start apps only (assumes Docker and packages already built)
pnpm build # Build all packages and apps
pnpm test # Run all tests (vitest) across workspaces
pnpm test:integration # Integration tests (requires DATABASE_URL)
pnpm status # Health check: shows state of all services and config
pnpm db <cmd> # Database ops: push, reset, studio, seed, migrate
pnpm dev:down # Stop Docker services
pnpm cleanup # Stop containers + remove build artifacts
pnpm cleanup --full # Nuclear option: also removes node_modules + Docker volumes
pnpm seed:prompts # Seed discussion prompts into DB (requires DATABASE_URL)- Bot: discord.js 14, dotenv
- Dashboard: Next.js 16 (App Router), NextAuth 4 (Discord OAuth with
guildsscope), Tailwind CSS - Voting: Next.js 16 (App Router), NextAuth 4 (Discord OAuth with
identify emailscope), Tailwind CSS - Database: Prisma 7 on Supabase Postgres (pooled connections via PgBouncer port 6543)
- Testing: Vitest across all workspaces (
vitest.workspace.tsat root,vitest.integration.workspace.tsfor integration tests)
- Single-server only — The bot exclusively serves Photography Lounge (
PL_GUILD_IDenv var). It auto-leaves any other server onguildCreateand ignores commands from non-PL guilds. - NextAuth + Discord OAuth is required — the dashboard needs the Discord access token to call the Discord guilds API for authorization. Do not replace with Clerk/Auth0.
@/path alias maps tosrc/in each app (apps/dashboard/src/,apps/voting/src/) — configured in each app'stsconfig.jsonandvitest.config.ts.(dashboard)route group — Settings and Audit pages live underapp/(dashboard)/with a shared layout that handles auth viaisPlAdmin().lib/discord.ts— Authorization utilities:getAdminGuilds()fetches admin guilds,isPlAdmin()checks if user is a PL admin.lib/actions.ts— Server actions for feature toggles with re-verification and audit logging.- Discussion prompts — 400 curated prompts in
bot/src/data/discussion-prompts.mdacross 2 categories (Creative Process, Inspiration). To add/edit prompts, edit the markdown file directly — each line is- Prompt text here?. The parser (bot/src/data/parse-prompts.ts) loads them at startup. The bot build step copies the.mdtodist/. - Discussion scheduler — interval-based scheduler in
bot/src/services/scheduler.tsposts every 6 hours per channel, waiting for a natural conversation pause before posting. - Hierarchical permissions —
canUseFeature()inbot/src/middleware/permissions.tsimplements Channel > Role > Server specificity with "Allow Wins" conflict resolution. - Permissions default to allow — when no
FeatureConfigexists for a feature,canUseFeature()returnstrue. All features are enabled until admins explicitly disable them. - Voting site —
apps/votingis a Next.js 16 community tool for upvoting/downvoting discussion prompts (NextAuth + Discord OAuth,identify emailscope). Runs on port 3200 in local dev. Deployed to Vercel atdiscussion-prompts.pl.iser.io. Admin access is controlled byVOTING_ADMIN_USER_IDS(comma-separated Discord user IDs) and optionallyVOTING_ADMIN_ROLE_IDS+DISCORD_TOKENfor role-based access via guild member lookup. API routes:/api/vote(voting),/api/prompt(POST/PATCH/DELETE for prompt management),/api/flag(duplicate flagging toggle),/api/admin/voters(admin voter list),/api/admin/export(JSON export). Rate limiting: 20 votes/min and 20 prompt submissions/min per user. Privacy:submittedBy/submittedByUsernamefields are stripped from non-admin responses. Prompts are seeded fromdiscussion-prompts.mdintoPrompt/PromptTagtables with 17 granular tags. Each app's.envis a symlink to the root.env. - Dashboard deployment —
apps/dashboardis deployed to Vercel atbot-dashboard.pl.iser.io. Uses the same Supabase database. RequiresguildsOAuth scope for Discord admin guild lookup. Build usesscripts/build.shwith Prisma NFT trace patching (same pattern as voting app). - Vercel monorepo deployment — Both voting and dashboard apps use
outputFileTracingRootpointing to the monorepo root, a customscripts/build.shthat builds the Prisma client then Next.js then patches.nft.jsontrace files to include the Prisma engine binary. Thevercel.jsonin each app setsinstallCommand: "cd ../.. && pnpm install"andbuildCommand: "bash scripts/build.sh". - Tests use
vi.clearAllMocks()inbeforeEach— mock return values must be re-established after clearing, not just invi.mock().
See .env.example at root for all required variables. Key ones:
DISCORD_TOKEN,DISCORD_CLIENT_ID,DISCORD_CLIENT_SECRETPL_GUILD_ID— Photography Lounge server ID (used by bot and all apps)DATABASE_URLNEXTAUTH_SECRET,NEXTAUTH_URLVOTING_NEXTAUTH_SECRET,VOTING_ADMIN_USER_IDS,VOTING_ADMIN_ROLE_IDS
pnpm init:local # First time: interactive setup wizard
pnpm dev:local # Every time: starts everything with one commandRequires Docker for local Postgres. See docs/LOCAL_SETUP.md for the full manual setup walkthrough.
- Dev logs are written to
/tmp/photobot-dev.log— check this file to diagnose startup or runtime issues. - Docker container conflicts —
dev.shrunsdocker compose downand force-removes named containers before starting. If you still hit conflicts, rundocker rm -f photobot-db photobot-ollamamanually. - Integration test failures — these require
DATABASE_URLpointing to a running Postgres instance. Unit tests (pnpm test) work without a database.