Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .claude/agents/code-reviewer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
name: code-reviewer
description: Expert code reviewer for quality, performance, and maintainability. Use PROACTIVELY after writing or modifying code to ensure standards are met before committing.
tools: Read, Grep, Glob, Bash
model: sonnet
---

Senior code reviewer for programming.in.th (Next.js 15, React 19, TypeScript, Prisma).

**Process**: `git diff --name-only` → `git diff` → read files → review

**Review for**:
- **Performance**: Server Components (avoid unnecessary `'use client'`), selective Prisma fields, no N+1, pagination, caching
- **Types**: No `any`, Zod validation for APIs, proper error handling
- **Patterns**: Follows codebase conventions, focused functions, clear naming

**Key patterns**:
- Prisma: Always `select`, import from `@/lib/prisma`
- Auth: `getServerUser()` from `@/lib/session`, check `user.admin`
- APIs: Zod schemas, consistent errors (400/401/403/404/500)
- Components: Tailwind, `dark:` variants, accessibility

**Output**: Issues by severity (Critical/Warning/Suggestion) with `file:line` and fixes. Verdict: **APPROVED** / **CHANGES REQUESTED**
33 changes: 33 additions & 0 deletions .claude/agents/security-reviewer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
name: security-reviewer
description: Security specialist for identifying vulnerabilities, auth issues, and data exposure risks. Use PROACTIVELY when reviewing API routes, auth logic, file handling, or user input processing.
tools: Read, Grep, Glob, Bash
model: sonnet
---

Security specialist for programming.in.th (auth, code submissions, file storage).

**Process**: `git diff` for changes OR grep for security patterns → analyze → remediate

**Check for**:
- **Auth**: `getServerUser()` on protected routes, `user.admin` for admin routes
- **Validation**: Zod `safeParse()` for all input, no internal details in errors
- **Injection**: Prisma parameterized queries, no user input in commands/paths
- **Data exposure**: Selective fields only, no secrets in responses/logs
- **Files**: Presigned S3 URLs, validate types/sizes, sanitize paths

**Search for secrets**:
```bash
grep -rE "(password|secret|key|token)\s*[:=]" --include="*.ts"
```

**Required patterns**:
```typescript
const user = await getServerUser()
if (!user) return Response.json({ error: 'Unauthorized' }, { status: 401 })

const result = Schema.safeParse(input)
if (!result.success) return Response.json({ error: 'Invalid' }, { status: 400 })
```

**Output**: Findings by severity (Critical/High/Medium/Low) with risk, evidence, fix. Verdict: **SECURE** / **ISSUES FOUND** / **CRITICAL**
12 changes: 12 additions & 0 deletions .claude/commands/perf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
description: Analyze a component or page for performance issues
allowed-tools: Read, Glob, Grep, Bash
argument-hint: "<file-path>"
---

Analyze `$1` for:
- **React**: Unnecessary `'use client'`? Missing memoization? Re-render issues?
- **Data**: Selective fields? N+1 queries? Pagination? Caching (ISR/SWR)?
- **Bundle**: Optimized imports? Next.js `<Image>`?

Report issues by severity (Critical/Warning) with specific fixes.
13 changes: 13 additions & 0 deletions .claude/commands/review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
description: Review code changes for quality, performance, and maintainability
allowed-tools: Bash, Read, Glob, Grep
argument-hint: "[file-path or --staged]"
---

1. Get changes: `git diff` (or `git diff --cached` for staged, or read specific file)
2. Review for:
- **Performance**: Server Components, selective queries, no N+1, caching
- **Types**: No `any`, proper Zod validation
- **Security**: Auth checks, input validation, no secrets
- **Patterns**: Follows codebase conventions
3. Report issues and verdict: **APPROVED** / **CHANGES REQUESTED**
11 changes: 11 additions & 0 deletions .claude/commands/verify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
description: Run all verification checks (types, lint, tests) - the "No Regression" check
allowed-tools: Bash, Read, Glob, Grep
---

Run in sequence, stop on failure:
1. `pnpm check-types`
2. `pnpm lint`
3. `pnpm test`

Report **PASS/FAIL** with error details if any.
43 changes: 43 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Bash(pnpm:*)",
"Bash(npm:*)",
"Bash(npx:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(git log:*)",
"Bash(git branch:*)",
"Bash(git show:*)",
"Read",
"Glob",
"Grep",
"Skill"
]
},
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "bash -c 'FILE=\"$CLAUDE_TOOL_ARG_file_path\"; if [[ \"$FILE\" == *.ts || \"$FILE\" == *.tsx ]]; then echo \"[Hook] TypeScript file modified: $FILE\"; fi'"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash -c 'cd \"$(git rev-parse --show-toplevel)\" && if git diff --name-only HEAD 2>/dev/null | grep -qE \"\\.(ts|tsx)$\"; then echo \"[Reminder] TypeScript files changed. Consider running: pnpm check-types && pnpm lint && pnpm test\"; fi'"
}
]
}
]
}
}
50 changes: 50 additions & 0 deletions .claude/skills/api-development/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
name: api-development
description: Use when creating or modifying Elysia API routes. Ensures proper validation with t schema, auth guards, error handling, and performance patterns.
allowed-tools: Read, Edit, Write, Glob, Grep, Bash
---

API routes use [Elysia](https://elysiajs.com) with TypeBox validation:

```typescript
import { Elysia, t } from 'elysia'
import { prisma } from '@/lib/prisma'

const app = new Elysia()
.get('/tasks/:id', async ({ params, query, status }) => {
const limit = query.limit ?? 10
const task = await prisma.task.findUnique({
where: { id: params.id },
select: { id: true, title: true }
})
if (!task) return status(404, { error: 'Not found' })
return task
}, {
params: t.Object({ id: t.String() }),
query: t.Object({ limit: t.Optional(t.Numeric()) })
})
.post('/tasks', async ({ body, status }) => {
const task = await prisma.task.create({ data: body })
return status(201, task)
}, {
body: t.Object({
title: t.String({ minLength: 1 }),
fullScore: t.Number({ minimum: 0 })
})
})
```
Comment on lines +7 to +35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if Elysia is used in the codebase
echo "=== Checking for Elysia usage ==="
rg -l "from 'elysia'" --type=ts --type=tsx 2>/dev/null | head -20

echo -e "\n=== Checking for Zod usage ==="
rg -l "from 'zod'" --type=ts --type=tsx 2>/dev/null | head -20

echo -e "\n=== Checking API route files ==="
fd "route\.(ts|tsx)$" src/app 2>/dev/null | head -20

echo -e "\n=== Checking package.json for dependencies ==="
cat package.json | jq '.dependencies, .devDependencies' 2>/dev/null | grep -E '"(elysia|zod)"' -A0

echo -e "\n=== Checking for Elysia in package.json ==="
grep -i "elysia" package.json || echo "No Elysia found in package.json"

echo -e "\n=== Checking for Zod in package.json ==="
grep -i "zod" package.json || echo "No Zod found in package.json"

Repository: programming-in-th/programming.in.th

Length of output: 1097


🏁 Script executed:

#!/bin/bash
# Check a sample API route to see if it uses Zod validation
echo "=== Sample API route: src/app/api/tasks/route.ts ==="
cat -n src/app/api/tasks/route.ts | head -50

echo -e "\n=== Check for Zod usage in API routes ==="
rg "from 'zod'" src/app/api --type=ts -A2 -B2 | head -40

Repository: programming-in-th/programming.in.th

Length of output: 2171


Replace Elysia/TypeBox examples with Next.js and Zod.

This documentation demonstrates Elysia with TypeBox validation, but the codebase uses Next.js App Router with Zod. The API routes (e.g., src/app/api/tasks/route.ts) parse input using Zod schemas (e.g., TaskSchema.parse(await req.json())) and return errors via a badRequest() utility. Update the examples to match the actual tech stack, showing how to validate route inputs with Zod and return a 400 status code for invalid input.

🤖 Prompt for AI Agents
In @.claude/skills/api-development/SKILL.md around lines 7 - 35, Update the docs
example to use Next.js App Router handlers and Zod validation: replace the
Elysia route snippets with a Next.js route handler (e.g.,
src/app/api/tasks/route.ts) that calls TaskSchema.parse(await req.json()) for
POST bodies and parses route/query values as needed, and on validation failure
return the existing badRequest() utility with a 400 response; mention using
TaskSchema.parse in the handler and show using badRequest() for errors so the
example matches the codebase conventions.


**Auth guard pattern**:
```typescript
.derive(async ({ headers, status }) => {
const user = await getUser(headers.authorization)
if (!user) return status(401, { error: 'Unauthorized' })
return { user }
})
.get('/admin', ({ user, status }) => {
if (!user.admin) return status(403, { error: 'Forbidden' })
return 'admin only'
})
```

**Checklist**: `t.Object` validation, auth derive/guard, selective Prisma fields, pagination, `status()` for errors.
30 changes: 30 additions & 0 deletions .claude/skills/component-development/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
name: component-development
description: Use when creating or modifying React components. Ensures proper Server/Client component patterns, performance optimization, and accessibility.
allowed-tools: Read, Edit, Write, Glob, Grep
---

Components in `src/components/`. Default to Server Components.

```tsx
// Server Component (default) - no directive needed
export function TaskCard({ task }: { task: Task }) {
return <div className="p-4 dark:bg-gray-800">{task.title}</div>
}

// Client Component - only for interactivity
'use client'
import { useState } from 'react'
export function Toggle() {
const [on, setOn] = useState(false)
return <button onClick={() => setOn(!on)}>{on ? 'On' : 'Off'}</button>
}
```

**When to use `'use client'`**: onClick/onSubmit, useState/useEffect, browser APIs.

**Performance**: Push `'use client'` to smallest component, use `memo()` for expensive renders, Next.js `<Image>`.

**Accessibility**: Labels for inputs, ARIA attributes, keyboard nav for custom elements.

**Styling**: Tailwind only, `dark:` variants, custom colors: `prog-primary-500`.
35 changes: 35 additions & 0 deletions .claude/skills/database-changes/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
name: database-changes
description: Use when modifying Prisma schema or database queries. Ensures proper migrations, type safety, and query performance.
allowed-tools: Read, Edit, Write, Glob, Grep, Bash
---

Schema at `prisma/schema.prisma`. Always import from `@/lib/prisma`.

**Schema changes**:
```bash
# Edit schema, then:
pnpm prisma migrate dev --name descriptive_name
pnpm check-types
```

**Query patterns**:
```typescript
// Always select specific fields
const tasks = await prisma.task.findMany({
where: { private: false },
select: { id: true, title: true },
take: 10, skip: 0 // Always paginate
})

// Avoid N+1 - use include or batch queries
const tasks = await prisma.task.findMany({ include: { tags: true } })
// OR
const submissions = await prisma.submission.findMany({
where: { taskId: { in: taskIds } }
})
```

**Indexes**: Add `@@index([field])` for WHERE/ORDER BY columns.

**Models**: User, Task, Submission, Assessment, Category, Tag, Bookmark.
9 changes: 6 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ GITHUB_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

# Auth.js v5 Configuration
# Generate AUTH_SECRET with: openssl rand -base64 32
AUTH_SECRET=
# Base URL for authentication (replaces NEXTAUTH_URL in v5)
AUTH_URL=http://localhost:3000

# Bucket for files such as statement and attachments
BUCKET_NAME=
BUCKET_KEY_ID=
Expand All @@ -15,8 +21,5 @@ BUCKET_REGION=us-east-1
DATABASE_URL=
# DIRECT_URL=
# SHADOW_DATABASE_URL=

# Public URL
NEXTAUTH_URL=http://localhost:3000
NEXT_PUBLIC_REALTIME_URL=https://rtss.crackncode.org
NEXT_PUBLIC_AWS_URL=https://prginth01.sgp1.cdn.digitaloceanspaces.com
45 changes: 45 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# programming.in.th

Next.js 15 + React 19 + TypeScript + Prisma competitive programming platform.

## Core Principles

1. **Performance**: Server Components by default, selective Prisma fields, ISR/SWR caching
2. **No Regression**: Run `pnpm check-types && pnpm lint && pnpm test` before commits
3. **Maintainability**: Follow existing patterns, strict TypeScript, Zod validation

## Key Patterns

```tsx
// Server Component (default) - direct Prisma
const tasks = await prisma.task.findMany({
where: { private: false },
select: { id: true, title: true } // Always select specific fields
})

// Client Component - only when needed
'use client' // forms, useState, useEffect, browser APIs

// API Routes - always validate with Zod
const result = Schema.safeParse(input)
if (!result.success) return Response.json({ error: 'Invalid' }, { status: 400 })

// Auth - use getServerUser() from @/lib/session
// Prisma - import from @/lib/prisma (singleton)
```

## Commands

```bash
pnpm dev # Dev server (Turbopack)
pnpm check-types # TypeScript check
pnpm lint # ESLint
pnpm test # Vitest
```

## Gotchas

- Prisma: Always `@/lib/prisma`, always use `select`
- Auth: `getServerUser()` for server-side, check `user.admin` for admin routes
- Files: Presigned S3 URLs only, sanitize paths
- Dark mode: `dark:` Tailwind variants
Loading