Files
chocoadmin/AGENTS.md
T

9.9 KiB
Raw Blame History

This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ before writing any code. Heed deprecation notices.


Project: chocoadmin

Admin panel for the chocomae service (mouse-typing), extracted into a standalone Next.js 16 app. It shares the existing MariaDB database — do not modify the DB schema.

Critical rules

  • Never alter the DB schema. All tables are shared with the live chocomae service.
  • Race-condition guard for approve/cancel/reject: use a conditional updateMany({ where: { id, Status: REQUEST_STATUSES.REQUESTED }, data: { Status: <target> } }) as the first step inside a transaction. If count === 0, check existence to return 404 vs 409. Never use read-then-write (findUnique → check Status → update) — it allows double-processing under concurrent requests.
  • Wrap every mutation in a transaction: maestro, maestro_extension/maestro_upgrade, and maestro_log must be updated atomically.
  • Send email only after a successful commit, never inside the transaction.
  • Never use include: { maestro: true } when querying maestro_extension or maestro_upgrade. Always use select with only the needed columns to avoid logging the maestro Password field.
  • Admin password verification uses MariaDB PASSWORD() function — the existing admin table stores hashed passwords this way. Do not change this without a migration plan.
  • Extension approval date calculation happens server-side: if current expiry is in the past, use today +1 year; otherwise use current expiry +1 year.
  • Upgrade approval sets AvailableActivateDateTime = NOW() + 1 year. Do not change PlayerCount on upgrade.
  • When approving an extension or upgrade, close all other Status = 1 requests for the same MaestroID by setting them to Status = 2, then set the approved request to Status = 3.
  • Docker containers must set TZ: Asia/Seoul so that date calculations (setHours(0,0,0,0), formatDate) match the KST-based existing data in the shared MariaDB.
  • Extension requests (POST /api/maestros/[id]/extension-requests) require ActivateStatus = 2 (ACTIVE). Trial (1) and cancelled (100) accounts are rejected with 400. Approval (PATCH /api/extension-requests/[id]) re-checks ActivateStatus and throws 409 if not ACTIVE (transaction rolls back the atomic claim).
  • Upgrade requests: TRIAL accounts (ActivateStatus = 1) may request the same tier (trial-to-paid conversion, requestedAccountType >= AccountType). ACTIVE accounts must use a strictly higher tier (>). At approval time, this direction is re-verified against the maestro's current AccountType — if it became invalid (e.g., admin manually raised the tier), the approval throws 409 and rolls back.
  • Admin-initiated extension/upgrade registrations do not send an email (unlike self-registration flows which send payment-instruction emails). This is intentional.

Authentication

  • NextAuth.js v5 (next-auth@5.0.0-beta.31) with Credentials Provider.
  • Config lives in auth.ts (project root), not inside app/.
  • Unauthenticated requests are caught by proxy.ts (middleware) and redirected to /login.
  • API routes must call auth() and return 401 if no session.
  • Session maxAge is 8 hours — do not lengthen without review.
  • auth.ts tracks failed login attempts per admin name in an in-memory Map. After 5 consecutive failures the account is locked out for 15 minutes. Counter clears on success.

Key files

File Purpose
auth.ts NextAuth config (Credentials Provider, MariaDB PASSWORD verify)
proxy.ts Middleware — redirects unauthenticated users to /login
lib/db.ts Prisma Client singleton with SQL pretty-logging and result table logging
lib/logger.ts Structured logger — debug/info/warn/error, level controlled by LOG_LEVEL / APP_ENV
lib/errors.ts ApiError — unified error class with HTTP status code
lib/api-handler.ts withApiHandler — wraps route handlers with auth, timing, and error logging
lib/constants.ts Status code constants (ACTIVATE_STATUS, ACCOUNT_TYPE, REQUEST_STATUS); PAID_ACCOUNT_TYPE_VALUES (유료 계정 유형 배열 — 중복 정의 방지용 공용 상수)
lib/maestros.ts Maestro DB queries
lib/extension-requests.ts Extension request queries and approval logic
lib/upgrade-requests.ts Upgrade request queries and approval logic
prisma/schema.prisma Generated via prisma db pull — do not hand-edit model fields
docs/business-rules.md Full reference for existing chocomae business logic
docs/phase/phase9.md Log policy: levels, format, masking rules, Docker log operations

maestro_log conventions

Always record a log entry after a successful DB change. Use these Type values:

Type When
extension_maestro Extension approved
cancel_extension_maestro Extension cancelled (new type, not in original)
upgrade_maestro Upgrade approved
reject_upgrade_maestro Upgrade rejected (new type, not in original)
update_maestro_name Maestro name changed
update_maestro_email Maestro email changed
update_maestro_status ActivateStatus changed (admin, new)
update_maestro_account_type AccountType changed (admin, new)
update_maestro_available_date AvailableActivateDateTime changed (admin, new)
update_maestro_allow_enter_code AllowEditEnterCode changed (admin, new)
request_extension_maestro Admin-initiated extension request created
request_upgrade_maestro Admin-initiated upgrade request created

Remark column is char(100) — keep values under 100 characters.

Implemented phases (do not re-implement)

  • Phase 08: login, maestro list/detail, extension requests, upgrade requests, Docker setup, transaction safety, auth guards.
  • Phase 9 (substantially complete): structured logging (lib/logger.ts), withApiHandler wrapper (lib/api-handler.ts), ApiError class (lib/errors.ts), Prisma SQL pretty-logging and result table logging (lib/db.ts). Remaining: correlation IDs, stage/production log verification.
  • Phase 10: Maestro info edit form (PATCH /api/maestros/[id]) + student list with server-side pagination (GET /api/maestros/[id]/students).
  • Phase 11: Email auto-send via Nodemailer after extension/upgrade approval (lib/mail.ts). Upgrade email edge-case verification (trial→paid, paid→paid) still pending.
  • Phase 12: Admin-initiated extension/upgrade request registration from maestro detail page.
    • POST /api/maestros/[id]/extension-requests — inserts into maestro_extension using maestro's current AccountType read server-side; rejects trial account types.
    • POST /api/maestros/[id]/upgrade-requests — inserts into maestro_upgrade inside a transaction; validates requestedAccountType > maestro.AccountType.
    • ExtensionRequestsSection.tsx, UpgradeRequestsSection.tsx — client components with inline action UI; call router.refresh() on success.
    • PAID_ACCOUNT_TYPE_VALUES consolidated in lib/constants.ts — shared by lib/maestros.ts, lib/extension-requests.ts, lib/upgrade-requests.ts, and both new API routes.
  • Phase 13 (security/correctness fixes):
    • P0-1: getActionTarget in lib/extension-requests.ts replaced with inline select (no include: { maestro: true }) — prevents Password from appearing in debug result logs.
    • P0-2: Approve/cancel/reject flows in both lib/extension-requests.ts and lib/upgrade-requests.ts now use an atomic updateMany({ Status: REQUESTED → target }) guard; count === 0 triggers 404/409 — eliminates concurrent double-processing.
    • P0-3: docker-compose.yml and docker-compose.stage.yml now set TZ: Asia/Seoul; lib/maestros.ts date-change log remark uses formatDate() instead of toISOString().slice(0,10) for KST consistency.
    • P1-1/P1-2: createExtensionRequest and approveExtensionRequest require ActivateStatus === ACTIVE; UpgradeRequestsSection and createUpgradeRequest allow TRIAL same-tier upgrade.
    • P1-3: approveUpgradeRequest re-verifies upgrade direction against maestro's current AccountType at approval time.
    • P1-4/P1-5: createExtensionRequest wrapped in db.$transaction; both create functions write request_extension_maestro / request_upgrade_maestro log entries.
    • P1-6: Extension/upgrade approval routes now await email send and include emailSent: boolean in response; table UI shows inline amber warning when emailSent === false.
    • P2-2: updateMaestro now returns 400 instead of silently skipping when availableActivateDateTime cannot be parsed.
    • P2-5: withApiHandler catch block returns { message: "Internal server error" } JSON 500 instead of re-throwing (which produced HTML responses).
    • P2-6: NextAuth session maxAge set to 8 hours (auth.ts).
    • P2-7: In-memory brute-force protection added to auth.ts — 5 failures → 15-minute lockout per admin name.
    • P2-8: Default status filter for extension/upgrade request lists changed from undefined (all) to REQUEST_STATUSES.REQUESTED; reset links updated to ?status=1.

Planned phases (not yet implemented)

이후 작업 후보는 docs/plan/project-plan.md Section 10 "MVP 이후 확장 후보" 참조.

Packages to know

  • mariadb + @prisma/adapter-mariadb — Prisma uses the mariadb driver directly (not mysql2)
  • @base-ui/react — used instead of Radix UI primitives for shadcn components
  • zod v4 — Zod v4 API differs from v3; check docs before writing schemas
  • next-auth v5 beta — API surface differs significantly from v4