|
|
|
@@ -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`.
|
|
|
|
|