docs(skill): split erika project overview references

This commit is contained in:
2026-07-28 20:04:18 +07:00 Unverified
parent 2ac971bb6e
commit acebeb1f10
6 changed files with 214 additions and 200 deletions
+28 -200
View File
@@ -90,21 +90,37 @@ Strong success criteria let you loop independently. Weak criteria ("make it work
- Do not pass code strings directly to execution tools (e.g., `bun -e 'console.log("hello world")'`).
- Always write scripts to dedicated files before executing them.
## 9. Database
## 9. Commit Messages
**Use Conventional Commits with a scope.**
When committing changes, use the format:
```
type(scope): short imperative summary
```
Examples:
- `feat(form): add submission link webhook placeholder`
- `fix(auth): reject missing discord ids`
- `test(webhook): cover update template preview`
Prefer common types such as `feat`, `fix`, `test`, `docs`, `refactor`, `chore`, and `style`. Use a concise scope that names the affected area, such as `form`, `auth`, `webhook`, `leaderboard`, or `ui`.
## 10. Database
**Use standard tools for external databases.**
- Assume the use of an external database for persistent storage.
- Use Drizzle ORM exclusively for database interactions.
## 10. Explicit Approval for Plans
## 11. Explicit Approval for Plans
**Achieve alignment before execution.**
- Ensure the implementation plan is completely clear and mutually understood.
- Do not begin executing the plan until explicit approval (e.g., "let's do it") is given by the user.
## 11. Communication Style
## 12. Communication Style
**Be concise and professional. No fluff.**
@@ -114,204 +130,16 @@ Strong success criteria let you loop independently. Weak criteria ("make it work
---
## Project Overview
## Reference Files
Erika is a Next.js website for the content creator "Erika!". It has two main features:
Load only the reference files relevant to the task:
1. **Linktree** (`/`) — A landing page showing profile info, social links, and live follower counts from YouTube, Roblox, Discord, and TikTok.
2. **Custom Form System** (`/form`, `/admin/form`) — A full form builder where admins create forms with various question types, and users submit responses via Discord OAuth login.
### Tech Stack
- **Runtime**: Bun
- **Framework**: Next.js (App Router)
- **Database**: PostgreSQL via Drizzle ORM
- **Cache**: Redis (Bun's built-in `RedisClient`)
- **Auth**: NextAuth.js with Discord OAuth provider
- **UI**: ShadCN components + Tailwind CSS
- **Fonts**: Anuphan (Thai), Geist Sans (Latin)
- **Deployment**: Docker Compose + Nginx reverse proxy
---
## Environment Variables (`.env`)
| Variable | Purpose |
| Task Area | Reference |
|---|---|
| `DATABASE_URL` | PostgreSQL connection string |
| `NEXTAUTH_SECRET` | Secret for signing NextAuth sessions |
| `NEXTAUTH_URL` | Canonical URL of the site (e.g. `https://erika.sudloh.com`) |
| `REDIS_URL` | Redis connection string, used for caching follower counts and tracking unique visitors |
| `BASE_URL` | Public base URL, used in metadata/SEO |
| `DISCORD_CLIENT_ID` / `DISCORD_CLIENT_SECRET` | Discord OAuth app credentials for user login |
| `ADMIN_DISCORD_IDS` | **Comma-separated list of Discord user IDs that have admin access.** These users can access `/admin/form` to create, edit, and delete forms. Any Discord user NOT in this list is treated as a regular user. |
| `CRON_SECRET` | Secret key to authorize the automated `/api/cron/tiktok` polling endpoint |
| `DISCORD_BOT_TOKEN` | Bot token used to fetch Discord server member count |
| `DISCORD_GUILD_ID` | Discord server ID to fetch member count from |
| `YOUTUBE_API_KEY` / `YOUTUBE_CHANNEL_ID` | YouTube Data API credentials to fetch subscriber count |
| `ROBLOX_USER_ID` | Roblox user ID to fetch follower count |
| `TIKTOK_CLIENT_KEY` / `TIKTOK_CLIENT_SECRET` | TikTok Official API credentials |
| `TIKTOK_ACCESS_TOKEN` / `TIKTOK_REFRESH_TOKEN` / `TIKTOK_OPEN_ID` | TikTok OAuth tokens (obtained via one-time flow at `/api/auth/tiktok`) |
| Project purpose, stack, and environment variables | `references/project.md` |
| Shared libraries, follower fetchers, and database schema files | `references/libraries-and-data.md` |
| App Router routes, server actions, and API endpoints | `references/routes-and-actions.md` |
| UI components, config, Docker, and infrastructure files | `references/components-and-infra.md` |
| Auth model, access control, and performance-sensitive behavior | `references/behavior.md` |
---
## File & Directory Reference
### `lib/` — Shared Libraries
| File | Purpose |
|---|---|
| `lib/config.ts` | **Linktree configuration for the root page (`/`).** Defines the profile (name, description, avatar) and the list of social links (Roblox, Discord, TikTok, YouTube) displayed on the landing page. Also defines `config_p2` which adds the Form link. This does NOT configure the form system. |
| `lib/icons.tsx` | SVG icon components for social platforms (Roblox, Discord, TikTok, YouTube, Form) |
| `lib/redis.ts` | Redis client singleton using Bun's built-in `RedisClient`. Used for caching follower counts (1hr TTL) and tracking unique visitors. |
| `lib/auth.ts` | Shared session, admin, and form role-access authorization helpers |
| `lib/discord.ts` | Shared Discord API utilities for guild roles/member profiles with in-memory TTL caching |
| `lib/sanitize-html.ts` | Sanitizes admin-authored rich text before storage/rendering |
| `lib/utils.ts` | Utility functions (e.g. `cn()` for className merging) |
### `lib/followers/` — Follower Count Fetchers
| File | Purpose |
|---|---|
| `lib/followers/index.ts` | Aggregates all platform follower counts with Redis caching (1hr TTL). Exports `getFollowerCounts()` used by the root page. |
| `lib/followers/discord.ts` | Fetches Discord server member count via bot API |
| `lib/followers/youtube.ts` | Fetches YouTube subscriber count via YouTube Data API |
| `lib/followers/roblox.ts` | Fetches Roblox follower count via public API |
| `lib/followers/tiktok.ts` | Fetches TikTok follower count and profile via TikTok Official API. Auto-refreshes expired tokens. |
### `db/` — Database Layer
| File | Purpose |
|---|---|
| `db/index.ts` | Drizzle ORM client initialization, exports `db` |
| `db/schema.ts` | Barrel file re-exporting all schemas |
| `db/schema/account.ts` | NextAuth account tables (`user`, `account`, `session`, `verificationToken`) in the `account` PostgreSQL schema. Users have a `discordId` field. |
| `db/schema/form.ts` | Form system tables in the `form` PostgreSQL schema: `forms` (form metadata, webhook config, Discord role access control), `questions` (question definitions with types: text/textarea/radio/checkbox), `submissions` (user submissions with edit history), `answers` (individual answers) |
### `app/actions/` — Server Actions
| File | Purpose |
|---|---|
| `app/actions/discord.ts` | Admin-protected server action for loading Discord server roles in the form settings UI |
### `app/` — Routes
#### Root & Global
| File | Purpose |
|---|---|
| `app/layout.tsx` | Root layout. Sets up fonts (Anuphan + Geist), theme provider, tooltip provider, toaster, page progress bar, and SEO metadata. |
| `app/page.tsx` | **Linktree landing page (`/`).** Displays profile avatar (from TikTok API or fallback), social links with live follower counts, unique visitor counter, and a video background. |
| `app/globals.css` | Global CSS styles |
#### `/form` — Public Form System (user-facing)
| File | Purpose |
|---|---|
| `app/form/page.tsx` | Lists all **open** forms for users to fill out |
| `app/form/layout.tsx` | Form section layout with header, login state, and theme toggle. Shows a login dialog if the user is not authenticated. |
| `app/form/client.tsx` | Client-side form components (user avatar dropdown, sign out, etc.) |
| `app/form/[id]/page.tsx` | Individual form submission page where users answer questions. Enforces Discord role access control (redirects to not-found if unauthorized). |
| `app/form/[id]/layout.tsx` | Layout for individual form pages |
#### `/admin` — Admin Panel (admin-only)
| File | Purpose |
|---|---|
| `app/admin/layout.tsx` | **Centralized admin auth guard.** Checks `getServerSession` against `ADMIN_DISCORD_IDS` env var for ALL `/admin/*` routes. Redirects non-admins to `/`. |
| `app/admin/page.tsx` | **Admin dashboard homepage (`/admin`).** Overview cards linking to Form and Backend sections. Wrapped with `AdminShell` sidebar. |
#### `/admin/form` — Admin Form Management
| File | Purpose |
|---|---|
| `app/admin/form/page.tsx` | **Admin form list.** Shows all forms (open and closed) with create/edit/delete actions. Wrapped with `AdminShell` sidebar. |
| `app/admin/form/actions.ts` | Server actions for form CRUD (create, update settings, delete). All protected by admin auth check. |
| `app/admin/form/[id]/layout.tsx` | **Admin form editor layout.** Has its own form-specific sidebar (Editor/Responses/Extra). The admin-level sidebar is NOT shown here. |
| `app/admin/form/[id]/page.tsx` | Admin form editor page |
| `app/admin/form/[id]/client.tsx` | Client-side admin form editor components (question builder, drag-and-drop, settings) |
| `app/admin/form/[id]/result/` | Admin view of form submission results |
| `app/admin/form/[id]/extra/` | Additional admin form settings (Discord webhook templates, Role-based Access Control via Shadcn DropdownMenu) |
#### `/admin/backend` — Backend Settings
| File | Purpose |
|---|---|
| `app/admin/backend/page.tsx` | **Backend settings page (`/admin/backend`).** Auto-discovers platform files from `lib/followers/` using `fs.readdir`. Reads cached counts from Redis. Wrapped with `AdminShell` sidebar. |
| `app/admin/backend/client.tsx` | Client component rendering platform cards with cached counts and individual refetch buttons. |
| `app/admin/backend/actions.ts` | Server action `refetchPlatform()` — dynamically imports the platform module, calls the fetcher, and updates Redis cache per-platform. Adding a new `.ts` file to `lib/followers/` auto-registers it here. |
#### `/discord` — Discord Redirect
| File | Purpose |
|---|---|
| `app/discord/page.tsx` | Simple redirect to the Discord server invite link |
#### `/api` — API Routes
| File | Purpose |
|---|---|
| `app/api/auth/[...nextauth]/route.ts` | NextAuth.js configuration with Discord OAuth provider. Exports `authOptions`. |
| `app/api/auth/tiktok/route.ts` | Initiates TikTok OAuth flow (one-time setup to get tokens) |
| `app/api/auth/tiktok/callback/route.ts` | TikTok OAuth callback handler |
| `app/api/cron/tiktok/route.ts` | TikTok polling endpoint. Checks TikWM for new organic videos and notifies Discord. Triggered every 5 mins by the `erika-cron` Docker container. |
| `app/api/upload/route.ts` | File upload endpoint (admin-only, checks `ADMIN_DISCORD_IDS`) |
#### `app/actions/` — Server Actions
| File | Purpose |
|---|---|
| `app/actions/questions.ts` | Server actions for question CRUD within a form. Admin-protected. |
| `app/actions/submissions.ts` | Server actions for form submissions (submit, edit, view results). |
### `components/` — UI Components
| File | Purpose |
|---|---|
| `components/admin-shell.tsx` | **Shared admin sidebar layout.** Client component with `SidebarProvider` + `Sidebar` (Dashboard, Form, Backend links). Used by `/admin`, `/admin/form`, `/admin/backend`. NOT used by `/admin/form/[id]` which has its own form-specific sidebar. |
| `components/LinkCard.tsx` | Social link card component with follower count badges |
| `components/ShaderBackground.tsx` | WebGL shader background effect |
| `components/delete-buttons.tsx` | Delete confirmation dialogs for forms, submissions, and bulk delete. Uses AlertDialog/Dialog from ShadCN. |
| `components/login-dialog.tsx` | Discord OAuth login dialog |
| `components/rich-text-editor.tsx` | Rich text editor for form descriptions |
| `components/page-progress.tsx` | Page navigation progress bar |
| `components/theme-toggle.tsx` | Light/dark theme toggle |
| `components/theme-provider.tsx` | next-themes provider wrapper |
| `components/ui/` | ShadCN component library (button, card, table, badge, sidebar, etc.) |
### Config & Infrastructure
| File | Purpose |
|---|---|
| `drizzle.config.ts` | Drizzle Kit config — points to schema, outputs to `drizzle/`, uses schemas: `public`, `account`, `form` |
| `docker-compose.yml` | Runs the app, Nginx reverse proxy, and a self-pinging `erika-cron` container via Docker Compose with `network_mode: host` |
| `proxy.ts` | Replaces `middleware.ts` in modern Next.js. Currently a pass-through for `/form/:path*` routes — auth is handled via dialog in the layout, not here. |
| `nginx.conf` | Nginx config for reverse proxying to the Next.js app |
| `next.config.ts` | Next.js configuration |
| `Dockerfile` | Container build config |
---
## Auth Model
1. **User login**: Discord OAuth via NextAuth.js. Users log in at `/form` to submit forms.
2. **Admin check**: Server-side only. Compares the logged-in user's `discordId` (from session) against the comma-separated `ADMIN_DISCORD_IDS` env var. The primary guard is **centralized** in `app/admin/layout.tsx` which protects all `/admin/*` routes. Additional per-action checks exist in:
- `app/admin/form/actions.ts` (form CRUD actions)
- `app/actions/questions.ts` (question CRUD actions)
- `app/actions/submissions.ts` (admin submission deletion actions)
- `app/api/upload/route.ts` (file uploads)
3. **Form role access check**: `lib/auth.ts` contains the shared role-access check. Public form pages use it before rendering restricted forms, and `submitForm()` repeats the same check before mutating data.
4. **No middleware.ts**: Modern Next.js replaces `middleware.ts` with `proxy.ts`. The project has a `proxy.ts` that matches `/form/:path*` but currently just passes through. Auth checks are done per-page/per-action in server components and server actions.
---
## Optimizations & Performance
The following optimizations have been implemented to keep CPU and RAM usage extremely low while maintaining speed. **Do not remove or bypass these without good reason:**
1. **DB Connection Pooling**: `db/index.ts` uses a `globalThis` singleton to prevent connection leaks during dev hot-reloads, with a strict max pool size (`max: 5`) to reduce idle RAM.
2. **Session Caching**: The NextAuth session callback uses an in-memory `Map` with a TTL to cache the `discordId` lookup. This prevents a DB hit on *every single request* that checks the session.
3. **Discord API Caching**: `lib/discord.ts` aggressively caches Discord API responses (roles, profiles) in memory with a 60s TTL. This avoids massive delays (30s-50s) on admin pages caused by redundant external API requests.
4. **Redis Batching**: The unique visitor tracking (`app/page.tsx`) only runs the `scard` (count) command if `sadd` (add IP) actually added a new IP.
5. **Single Docker Replica**: The project runs on a **single replica** (`replicas: 1` in docker-compose.yml). Given Bun's performance, 1 replica is sufficient and drastically reduces the total RAM footprint compared to running 3 replicas.
6. **Nginx Streaming**: Nginx must have `proxy_buffering off;` (or specifically disabled for the Next.js upstream) so that Next.js App Router streaming (React Server Components and Server Actions) works without hanging or causing 504 timeouts.
Keep detailed project maps in these references rather than expanding `SKILL.md`.
+21
View File
@@ -0,0 +1,21 @@
# Behavior Notes
## Auth Model
1. **User login**: Discord OAuth via NextAuth.js. Users log in at `/form` to submit forms.
2. **Admin check**: Server-side only. The logged-in user's `discordId` is compared against `ADMIN_DISCORD_IDS`.
3. **Centralized admin guard**: `app/admin/layout.tsx` protects all `/admin/*` routes.
4. **Action-level admin checks**: Important mutations also check authorization in `app/admin/form/actions.ts`, `app/actions/questions.ts`, `app/actions/submissions.ts`, and `app/api/upload/route.ts`.
5. **Form role access**: `lib/auth.ts` handles role access. Public form pages check before rendering restricted forms, and `submitForm()` checks again before mutation.
6. **No `middleware.ts`**: Next.js uses `proxy.ts`; auth checks live in server components and server actions.
## Performance-Sensitive Behavior
Do not remove or bypass these without good reason:
1. `db/index.ts` uses a `globalThis` singleton and small pool size to prevent dev hot-reload connection leaks and reduce idle RAM.
2. The NextAuth session callback caches `discordId` lookups in memory with a TTL.
3. `lib/discord.ts` caches Discord API roles/profiles in memory for 60 seconds to avoid slow repeated external calls.
4. `app/page.tsx` batches Redis unique visitor tracking and only counts when `sadd` adds a new IP.
5. Docker Compose runs a single app replica; this is intentional for low RAM usage.
6. Nginx must disable proxy buffering for the Next.js upstream so App Router streaming and Server Actions do not hang.
@@ -0,0 +1,27 @@
# Components And Infrastructure
## Components
| File | Purpose |
|---|---|
| `components/admin-shell.tsx` | Shared admin sidebar layout for `/admin`, `/admin/form`, and `/admin/backend`; not used by `/admin/form/[id]` |
| `components/LinkCard.tsx` | Social link card with follower count badges |
| `components/space-background.tsx` | Space/video-style visual background |
| `components/delete-buttons.tsx` | Delete confirmation dialogs for forms, submissions, and bulk deletion |
| `components/login-dialog.tsx` | Discord OAuth login dialog |
| `components/rich-text-editor.tsx` | Rich text editor for form descriptions |
| `components/page-progress.tsx` | Page navigation progress bar |
| `components/theme-toggle.tsx` | Light/dark theme toggle |
| `components/theme-provider.tsx` | `next-themes` provider wrapper |
| `components/ui/` | ShadCN component library |
## Config And Infrastructure
| File | Purpose |
|---|---|
| `drizzle.config.ts` | Drizzle Kit config for schemas and migration output |
| `docker-compose.yml` | App, Nginx reverse proxy, and self-pinging cron service |
| `proxy.ts` | Modern Next.js replacement for `middleware.ts`; currently passes through `/form/:path*` |
| `nginx.conf` | Nginx reverse proxy config |
| `next.config.ts` | Next.js configuration |
| `Dockerfile` | Container build config |
@@ -0,0 +1,32 @@
# Libraries And Data
## `lib/` Shared Libraries
| File | Purpose |
|---|---|
| `lib/config.ts` | Linktree config for `/`: profile, social links, and `config_p2` with Form link. This does not configure the form system. |
| `lib/icons.tsx` | SVG icon components for social platforms and Form |
| `lib/redis.ts` | Redis singleton using Bun `RedisClient`; used for follower counts and unique visitors |
| `lib/auth.ts` | Shared session, admin, and form role-access authorization helpers |
| `lib/discord.ts` | Discord API utilities for guild roles/member profiles with in-memory TTL caching |
| `lib/sanitize-html.ts` | Sanitizes admin-authored rich text before storage/rendering |
| `lib/utils.ts` | Utility helpers such as `cn()` |
## `lib/followers/` Fetchers
| File | Purpose |
|---|---|
| `lib/followers/index.ts` | Aggregates all platform follower counts with Redis caching; exports `getFollowerCounts()` |
| `lib/followers/discord.ts` | Fetches Discord server member count via bot API |
| `lib/followers/youtube.ts` | Fetches YouTube subscriber count via YouTube Data API |
| `lib/followers/roblox.ts` | Fetches Roblox follower count via public API |
| `lib/followers/tiktok.ts` | Fetches TikTok follower count/profile and refreshes expired tokens |
## `db/` Database Layer
| File | Purpose |
|---|---|
| `db/index.ts` | Drizzle ORM client initialization; exports `db` |
| `db/schema.ts` | Barrel file re-exporting all schemas |
| `db/schema/account.ts` | NextAuth account tables in the `account` PostgreSQL schema; users have `discordId` |
| `db/schema/form.ts` | Form schema: forms, questions, submissions, and answers in the `form` PostgreSQL schema |
+36
View File
@@ -0,0 +1,36 @@
# Erika Project
Erika is a Next.js website for the content creator "Erika!". It has two main features:
1. **Linktree** (`/`) - A landing page showing profile info, social links, and live follower counts from YouTube, Roblox, Discord, and TikTok.
2. **Custom Form System** (`/form`, `/admin/form`) - A full form builder where admins create forms with various question types, and users submit responses via Discord OAuth login.
## Tech Stack
- **Runtime**: Bun
- **Framework**: Next.js App Router
- **Database**: PostgreSQL via Drizzle ORM
- **Cache**: Redis using Bun's built-in `RedisClient`
- **Auth**: NextAuth.js with Discord OAuth provider
- **UI**: ShadCN components + Tailwind CSS
- **Fonts**: Anuphan for Thai, Geist Sans for Latin
- **Deployment**: Docker Compose + Nginx reverse proxy
## Environment Variables
| Variable | Purpose |
|---|---|
| `DATABASE_URL` | PostgreSQL connection string |
| `NEXTAUTH_SECRET` | Secret for signing NextAuth sessions |
| `NEXTAUTH_URL` | Canonical URL of the site, such as `https://erika.sudloh.com` |
| `REDIS_URL` | Redis connection string, used for follower-count caching and unique visitors |
| `BASE_URL` | Public base URL, used in metadata/SEO and generated links |
| `DISCORD_CLIENT_ID` / `DISCORD_CLIENT_SECRET` | Discord OAuth app credentials for user login |
| `ADMIN_DISCORD_IDS` | Comma-separated Discord user IDs with admin access |
| `CRON_SECRET` | Secret for the automated `/api/cron/tiktok` polling endpoint |
| `DISCORD_BOT_TOKEN` | Bot token used to fetch Discord server member count |
| `DISCORD_GUILD_ID` | Discord server ID to fetch member count from |
| `YOUTUBE_API_KEY` / `YOUTUBE_CHANNEL_ID` | YouTube Data API credentials for subscriber count |
| `ROBLOX_USER_ID` | Roblox user ID to fetch follower count |
| `TIKTOK_CLIENT_KEY` / `TIKTOK_CLIENT_SECRET` | TikTok Official API credentials |
| `TIKTOK_ACCESS_TOKEN` / `TIKTOK_REFRESH_TOKEN` / `TIKTOK_OPEN_ID` | TikTok OAuth tokens from one-time setup at `/api/auth/tiktok` |
@@ -0,0 +1,70 @@
# Routes And Actions
## Root And Global
| File | Purpose |
|---|---|
| `app/layout.tsx` | Root layout with fonts, theme provider, tooltip provider, toaster, page progress, and SEO metadata |
| `app/page.tsx` | Linktree landing page with avatar, social links, follower counts, unique visitor count, and video background |
| `app/globals.css` | Global CSS styles |
## `/form` User-Facing Forms
| File | Purpose |
|---|---|
| `app/form/page.tsx` | Lists open forms for users |
| `app/form/layout.tsx` | Form layout with header, login state, theme toggle, and login dialog |
| `app/form/client.tsx` | Client-side form components and submission UX |
| `app/form/[id]/page.tsx` | Individual submission page; enforces Discord role access |
| `app/form/[id]/layout.tsx` | Layout for individual form pages |
## `/admin` Admin Panel
| File | Purpose |
|---|---|
| `app/admin/layout.tsx` | Centralized admin guard for all `/admin/*` routes |
| `app/admin/page.tsx` | Admin dashboard homepage with Form and Backend cards |
## `/admin/form` Form Management
| File | Purpose |
|---|---|
| `app/admin/form/page.tsx` | Admin form list with create/edit/delete actions |
| `app/admin/form/actions.ts` | Admin-protected form CRUD and settings actions |
| `app/admin/form/[id]/layout.tsx` | Form editor layout with form-specific sidebar |
| `app/admin/form/[id]/page.tsx` | Admin form editor page |
| `app/admin/form/[id]/client.tsx` | Client-side question builder, drag-and-drop, and settings |
| `app/admin/form/[id]/result/` | Admin form submission results |
| `app/admin/form/[id]/extra/` | Discord webhook templates and role-based access control settings |
## `/admin/backend` Backend Settings
| File | Purpose |
|---|---|
| `app/admin/backend/page.tsx` | Backend settings page; auto-discovers `lib/followers/` platform files and reads cached counts |
| `app/admin/backend/client.tsx` | Platform cards with cached counts and refetch buttons |
| `app/admin/backend/actions.ts` | `refetchPlatform()` dynamically imports fetchers and updates Redis cache |
## `/discord`
| File | Purpose |
|---|---|
| `app/discord/page.tsx` | Redirects to the Discord server invite |
## `/api`
| File | Purpose |
|---|---|
| `app/api/auth/[...nextauth]/route.ts` | NextAuth.js route with Discord OAuth provider |
| `app/api/auth/tiktok/route.ts` | Starts TikTok OAuth setup |
| `app/api/auth/tiktok/callback/route.ts` | TikTok OAuth callback handler |
| `app/api/cron/tiktok/route.ts` | TikTok polling endpoint; checks TikWM and notifies Discord |
| `app/api/upload/route.ts` | Admin-only file upload endpoint |
## `app/actions/` Server Actions
| File | Purpose |
|---|---|
| `app/actions/discord.ts` | Admin-protected Discord role loading |
| `app/actions/questions.ts` | Admin-protected question CRUD within a form |
| `app/actions/submissions.ts` | Form submission, update, deletion, and webhook notification actions |