Skip to content
Merged
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
20 changes: 11 additions & 9 deletions .claude/agents/code-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ Senior code reviewer for programming.in.th (Next.js 15, React 19, TypeScript, Pr
**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
- **Performance**: Server Components preferred, selective Prisma fields, no N+1, pagination
- **Types**: No `any`, validation on APIs, proper error handling
- **Patterns**: Follows codebase conventions

**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
**Pattern references**:
- [React patterns](../docs/react-patterns.md)
- [API patterns](../docs/api-patterns.md)
- [Database patterns](../docs/database-patterns.md)
- [Auth patterns](../docs/auth-patterns.md)

**Output**: Issues by severity (Critical/Warning/Suggestion) with `file:line` and fixes. Verdict: **APPROVED** / **CHANGES REQUESTED**
**Output**: Issues by severity (Critical/Warning/Suggestion) with `file:line` and fixes.

**Verdict**: **APPROVED** / **CHANGES REQUESTED**
19 changes: 6 additions & 13 deletions .claude/agents/security-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,17 @@ 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
- **Auth**: See [auth-patterns.md](../docs/auth-patterns.md)
- **Validation**: Input validation on all endpoints (see [api-patterns.md](../docs/api-patterns.md))
- **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
- **Data exposure**: Selective Prisma fields (see [database-patterns.md](../docs/database-patterns.md))
- **Files**: Presigned S3 URLs only, 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 })
**Output**: Findings by severity (Critical/High/Medium/Low) with risk, evidence, fix.

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**
**Verdict**: **SECURE** / **ISSUES FOUND** / **CRITICAL**
67 changes: 67 additions & 0 deletions .claude/docs/api-patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# API & Validation Patterns

## Validation Strategy

**Current**: Zod for validation
**Migration in progress**: Moving to Elysia with TypeBox (`t.*` schemas)

## Elysia Routes (Preferred)

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

const app = new Elysia()
.get('/tasks/:id', async ({ params, query, status }) => {
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 })
})
})
```

## Legacy Next.js Routes (Zod)

```typescript
import { z } from 'zod'

const Schema = z.object({ title: z.string().min(1) })

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

## Error Responses

Use consistent HTTP status codes:
- `400` - Invalid input
- `401` - Unauthorized (not logged in)
- `403` - Forbidden (logged in but not allowed)
- `404` - Not found
- `500` - Server error

**Never expose internal details** in error messages.

## Checklist

- [ ] Validation on all inputs (`t.Object` or Zod schema)
- [ ] Auth checks (see [auth-patterns.md](./auth-patterns.md))
- [ ] Selective Prisma fields (see [database-patterns.md](./database-patterns.md))
- [ ] Pagination for list endpoints
- [ ] Proper `status()` codes for errors
52 changes: 52 additions & 0 deletions .claude/docs/auth-patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Authentication Patterns

## Server-Side Auth

```typescript
import { getServerUser } from '@/lib/session'

// In Server Components or API routes
const user = await getServerUser()

if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
```

## Admin Routes

```typescript
const user = await getServerUser()

if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}

if (!user.admin) {
return Response.json({ error: 'Forbidden' }, { status: 403 })
}
```

## Elysia Auth Guard

```typescript
import { Elysia } from 'elysia'

const app = new Elysia()
.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

- [ ] `getServerUser()` on all protected routes
- [ ] Check `user.admin` for admin-only routes
- [ ] Return 401 for unauthenticated, 403 for unauthorized
- [ ] Never expose user data without ownership check
70 changes: 70 additions & 0 deletions .claude/docs/database-patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Database & Prisma Patterns

## Setup

- **Schema**: `prisma/schema.prisma`
- **Import**: Always `import { prisma } from '@/lib/prisma'`

## Schema Changes

```bash
# 1. Edit prisma/schema.prisma
# 2. Create migration
pnpm prisma migrate dev --name descriptive_name
# 3. Verify types
pnpm check-types
```

## Query Patterns

### Always Select Specific Fields

```typescript
// Good
const tasks = await prisma.task.findMany({
where: { private: false },
select: { id: true, title: true, fullScore: true }
})

// Bad - fetches all columns
const tasks = await prisma.task.findMany({
where: { private: false }
})
```

### Always Paginate

```typescript
const tasks = await prisma.task.findMany({
where: { private: false },
select: { id: true, title: true },
take: 10,
skip: page * 10
})
```

### Avoid N+1 Queries

```typescript
// Option 1: Include related data
const tasks = await prisma.task.findMany({
select: { id: true, title: true, tags: true }
})

// Option 2: Batch query
const taskIds = tasks.map(t => t.id)
const submissions = await prisma.submission.findMany({
where: { taskId: { in: taskIds } }
})
```

## Indexes

Add `@@index([field])` for columns used in:
- `WHERE` clauses
- `ORDER BY` clauses
- Foreign key lookups

## Models Reference

User, Task, Submission, Assessment, Category, Tag, Bookmark
45 changes: 45 additions & 0 deletions .claude/docs/react-patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# React & Component Patterns

## Server vs Client Components

**Default to Server Components** - no directive needed.

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

**Use `'use client'` only when required**:
- Event handlers (`onClick`, `onSubmit`)
- React hooks (`useState`, `useEffect`)
- Browser APIs (`localStorage`, `window`)

```tsx
'use client'
import { useState } from 'react'

export function Toggle() {
const [on, setOn] = useState(false)
return <button onClick={() => setOn(!on)}>{on ? 'On' : 'Off'}</button>
}
```

## Performance

- Push `'use client'` to the smallest possible component
- Use `memo()` for expensive renders
- Use Next.js `<Image>` for images

## Styling

- **Tailwind only** - no CSS files
- **Dark mode**: Use `dark:` variants (e.g., `dark:bg-gray-800`)
- **Custom colors**: `prog-primary-500`

## Accessibility

- Labels for all form inputs
- ARIA attributes where semantic HTML isn't sufficient
- Keyboard navigation for custom interactive elements
35 changes: 6 additions & 29 deletions .claude/skills/api-development/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,24 @@ description: Use when creating or modifying Elysia API routes. Ensures proper va
allowed-tools: Read, Edit, Write, Glob, Grep, Bash
---

API routes use [Elysia](https://elysiajs.com) with TypeBox validation:
See [API patterns](../../docs/api-patterns.md) for full reference.

**Quick example**:
```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
new Elysia()
.get('/tasks/:id', async ({ params, status }) => {
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()) })
params: t.Object({ id: t.String() })
})
.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 })
})
})
```

**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.
**Checklist**: `t.Object` validation, auth guards, selective Prisma fields, pagination, proper status codes.
27 changes: 10 additions & 17 deletions .claude/skills/component-development/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,20 @@ description: Use when creating or modifying React components. Ensures proper Ser
allowed-tools: Read, Edit, Write, Glob, Grep
---

Components in `src/components/`. Default to Server Components.
See [React patterns](../../docs/react-patterns.md) for full reference.

**Quick rules**:
- Default to Server Components (no directive)
- Use `'use client'` only for: `onClick`, `useState`, `useEffect`, browser APIs
- Push client boundary to smallest component
- Tailwind only, use `dark:` variants

**Example**:
```tsx
// Server Component (default) - no directive needed
// Server Component (default)
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`.
**Checklist**: Minimal client components, `dark:` variants, accessible inputs.
Loading