Files
2025-11-30 08:25:20 +08:00

13 KiB

Example: Personal Task Manager with Authentication

This example shows planning docs for a task manager with user authentication using Clerk.

User Request: "I want to build a task manager where users can sign up, log in, and manage their own tasks privately. Users should be able to organize tasks with tags and due dates."


IMPLEMENTATION_PHASES.md (Excerpt)

Implementation Phases: Personal Task Manager

Project Type: Authenticated Web App (Multi-user CRUD) Stack: Cloudflare Workers + Vite + React + Tailwind v4 + shadcn/ui + D1 + Clerk Estimated Total: 20 hours (~20 minutes human time) Created: 2025-10-25


Phase 1: Project Setup

Type: Infrastructure Estimated: 2-3 hours Files: package.json, wrangler.jsonc, vite.config.ts, src/index.ts

[Same structure as simple example]


Phase 2: Database Schema

Type: Database Estimated: 3-4 hours Files: migrations/0001_initial.sql, src/lib/db-types.ts

Tasks

  • Create D1 database
  • Design schema for users, tasks, tags, task_tags tables
  • Write migration SQL
  • Create TypeScript types
  • Apply migration locally

Verification Criteria

  • All tables created successfully
  • Foreign keys work (task references user)
  • Unique constraints work (user email, tag name per user)
  • Can query with joins (tasks with their tags)

Exit Criteria

Complete database schema with relationships working.


Phase 3: Clerk Authentication Setup

Type: Integration Estimated: 3 hours Files: src/main.tsx, src/middleware/auth.ts, src/lib/clerk-types.ts

Tasks

  • Create Clerk account and application
  • Install @clerk/clerk-react and @clerk/backend
  • Configure Clerk in frontend (ClerkProvider)
  • Create custom JWT template in Clerk dashboard
  • Implement auth middleware for Worker
  • Test JWT verification
  • Create protected route wrapper component

Verification Criteria

  • Can sign up new user (Clerk modal)
  • Can sign in existing user
  • JWT is included in API requests
  • Worker middleware verifies JWT correctly
  • Invalid JWT returns 401
  • Protected routes redirect if not authenticated

Exit Criteria

Authentication flow working end-to-end. Users can sign up, log in, and access protected routes.


Phase 4: User Sync (Webhook)

Type: Integration Estimated: 2 hours Files: src/routes/webhooks.ts, src/lib/webhook-verify.ts

Tasks

  • Install svix package
  • Create webhook endpoint /api/webhooks/clerk
  • Implement signature verification
  • Handle user.created event (insert into database)
  • Handle user.updated event
  • Handle user.deleted event
  • Configure webhook in Clerk dashboard

Verification Criteria

  • New user signup triggers webhook
  • User record created in D1 database
  • Invalid webhook signature rejected
  • User updates sync to database
  • User deletions remove tasks (cascade)

Exit Criteria

User data synced between Clerk and D1 database.


Phase 5: Tasks API

Type: API Estimated: 5 hours Files: src/routes/tasks.ts, src/lib/schemas.ts

Tasks

  • Define Zod schemas for task validation
  • GET /api/tasks (user's tasks only)
  • POST /api/tasks (create for current user)
  • PATCH /api/tasks/:id (update if user owns)
  • DELETE /api/tasks/:id (delete if user owns)
  • Add authorization checks (verify ownership)
  • Filter by completion, tag, due date

Verification Criteria

  • GET /api/tasks returns only current user's tasks
  • Cannot access other users' tasks
  • Cannot update/delete other users' tasks (403)
  • Filters work (completed, tag, due date)
  • All CRUD operations tested

Exit Criteria

Tasks API complete with proper authorization.


Phase 6: Tags API

Type: API Estimated: 2 hours Files: src/routes/tags.ts

Tasks

  • GET /api/tags (user's tags)
  • POST /api/tags (create tag)
  • DELETE /api/tags/:id (delete tag)
  • POST /api/tasks/:id/tags (add tag to task)
  • DELETE /api/tasks/:id/tags/:tagId (remove tag from task)

Verification Criteria

  • Can create tags
  • Can attach tags to tasks
  • Can filter tasks by tag
  • Deleting tag doesn't delete tasks

Exit Criteria

Tag management working with task associations.


Phase 7: Dashboard UI

Type: UI Estimated: 6 hours Files: src/pages/Dashboard.tsx, src/components/layout/Sidebar.tsx, etc.

Tasks

  • Build dashboard layout with sidebar
  • Add user menu with sign out
  • Create task list view
  • Add task filtering (completed, tags, due date)
  • Build task creation dialog
  • Build task editing dialog
  • Add task deletion with confirmation

Verification Criteria

  • Dashboard shows user's tasks
  • Filters work correctly
  • Can create tasks via dialog
  • Can edit existing tasks
  • Can delete tasks
  • UI updates optimistically

Exit Criteria

Complete dashboard with full task management.


Phase 8: Tags UI

Type: UI Estimated: 3 hours Files: src/components/TagManager.tsx, src/components/TagBadge.tsx

Tasks

  • Build tag creation form
  • Build tag list component
  • Add tag selection to task form (multi-select)
  • Display tags as badges on tasks
  • Allow removing tags from tasks

Verification Criteria

  • Can create new tags
  • Can select tags when creating/editing task
  • Tags display on task cards
  • Can filter by tag
  • Can remove tags from tasks

Exit Criteria

Full tag management integrated with tasks.


DATABASE_SCHEMA.md (Excerpt)

Database Schema: Personal Task Manager


Tables

users

Column Type Constraints Notes
id INTEGER PRIMARY KEY Auto-increment
clerk_id TEXT UNIQUE, NOT NULL Clerk user ID
email TEXT UNIQUE, NOT NULL User email
display_name TEXT NULL Display name
created_at INTEGER NOT NULL Unix timestamp
updated_at INTEGER NOT NULL Unix timestamp

Indexes: idx_users_clerk_id, idx_users_email


tasks

Column Type Constraints Notes
id INTEGER PRIMARY KEY Auto-increment
user_id INTEGER FOREIGN KEY, NOT NULL References users(id)
title TEXT NOT NULL Task title
description TEXT NULL Task description
completed INTEGER NOT NULL 0 or 1
due_date INTEGER NULL Unix timestamp
created_at INTEGER NOT NULL Unix timestamp
updated_at INTEGER NOT NULL Unix timestamp

Indexes: idx_tasks_user_id, idx_tasks_due_date, idx_tasks_completed

Relationships: Many-to-one with users

Cascade: ON DELETE CASCADE (deleting user deletes their tasks)


tags

Column Type Constraints Notes
id INTEGER PRIMARY KEY Auto-increment
user_id INTEGER FOREIGN KEY, NOT NULL References users(id)
name TEXT NOT NULL Tag name
color TEXT NOT NULL Hex color code
created_at INTEGER NOT NULL Unix timestamp

Indexes: idx_tags_user_id, idx_tags_name_user_id UNIQUE

Unique Constraint: (user_id, name) - users can't have duplicate tag names


task_tags

Column Type Constraints Notes
id INTEGER PRIMARY KEY Auto-increment
task_id INTEGER FOREIGN KEY, NOT NULL References tasks(id)
tag_id INTEGER FOREIGN KEY, NOT NULL References tags(id)
created_at INTEGER NOT NULL Unix timestamp

Indexes: idx_task_tags_task_id, idx_task_tags_tag_id, idx_task_tags_composite UNIQUE

Unique Constraint: (task_id, tag_id) - can't add same tag to task twice

Cascade: ON DELETE CASCADE (deleting task or tag removes association)


Relationships Diagram

┌─────────────┐
│   users     │
└──────┬──────┘
       │ 1
       │
       ├─────────────────────┬
       │ N                   │ N
┌──────┴──────────┐   ┌──────┴──────────┐
│     tasks       │   │      tags       │
└──────┬──────────┘   └──────┬──────────┘
       │ N                   │ N
       │                     │
       └──────────┬──────────┘
                  │
           ┌──────┴──────────┐
           │   task_tags     │
           │   (junction)    │
           └─────────────────┘

API_ENDPOINTS.md (Excerpt)

API Endpoints: Personal Task Manager

Auth: Required on all /api/* routes (except webhooks)


Authentication

GET /api/auth/me

Purpose: Get current user profile

Response 200:

{
  "data": {
    "id": 1,
    "email": "user@example.com",
    "displayName": "John Doe",
    "createdAt": 1234567890
  }
}

Tasks

GET /api/tasks

Purpose: Get current user's tasks

Query Parameters:

  • completed (optional): true or false
  • tag (optional): Tag ID to filter by
  • dueBefore (optional): Unix timestamp
  • dueAfter (optional): Unix timestamp

Response 200:

{
  "data": [
    {
      "id": 1,
      "title": "Review PR",
      "description": "Check the new feature",
      "completed": false,
      "dueDate": 1234567890,
      "tags": [
        { "id": 1, "name": "work", "color": "#3b82f6" }
      ],
      "createdAt": 1234567890,
      "updatedAt": 1234567890
    }
  ]
}

POST /api/tasks

Purpose: Create task for current user

Request Body:

{
  "title": "New Task",
  "description": "Optional",
  "dueDate": 1234567890,
  "tagIds": [1, 2]
}

Validation:

z.object({
  title: z.string().min(1).max(100),
  description: z.string().optional(),
  dueDate: z.number().optional(),
  tagIds: z.array(z.number()).optional()
})

Response 201: Created task with tags


Tags

GET /api/tags

Purpose: Get current user's tags

Response 200:

{
  "data": [
    { "id": 1, "name": "work", "color": "#3b82f6" },
    { "id": 2, "name": "personal", "color": "#10b981" }
  ]
}

POST /api/tags

Purpose: Create tag for current user

Request Body:

{
  "name": "urgent",
  "color": "#ef4444"
}

Response 201: Created tag

Response 400: Duplicate tag name (user already has tag with this name)


INTEGRATION.md (Excerpt)

Third-Party Integrations: Personal Task Manager


Clerk (Authentication)

Purpose: User authentication and management

Environment Variables:

VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
CLERK_WEBHOOK_SECRET=whsec_...

Custom JWT Template (Clerk dashboard):

{
  "email": "{{user.primary_email_address}}",
  "userId": "{{user.id}}",
  "firstName": "{{user.first_name}}",
  "lastName": "{{user.last_name}}"
}

Frontend:

import { ClerkProvider, SignedIn, SignedOut, UserButton } from '@clerk/clerk-react'

<ClerkProvider publishableKey={...}>
  <SignedIn>
    <Dashboard />
    <UserButton />
  </SignedIn>
  <SignedOut>
    <LandingPage />
  </SignedOut>
</ClerkProvider>

Backend Middleware:

import { verifyToken } from '@clerk/backend'

export async function authMiddleware(c: Context, next: Next) {
  const token = c.req.header('Authorization')?.replace('Bearer ', '')
  const verified = await verifyToken(token, {
    secretKey: c.env.CLERK_SECRET_KEY
  })
  c.set('clerkUserId', verified.userId)
  c.set('email', verified.email)

  // Get local user ID from database
  const user = await c.env.DB.prepare(
    'SELECT id FROM users WHERE clerk_id = ?'
  ).bind(verified.userId).first()

  c.set('userId', user.id)
  await next()
}

Webhook (User Sync):

app.post('/api/webhooks/clerk', async (c) => {
  const payload = await c.req.text()
  const verified = await verifyClerkWebhook(payload, c.req.raw.headers, c.env.CLERK_WEBHOOK_SECRET)

  const event = JSON.parse(payload)

  if (event.type === 'user.created') {
    await c.env.DB.prepare(`
      INSERT INTO users (clerk_id, email, display_name, created_at, updated_at)
      VALUES (?, ?, ?, ?, ?)
    `).bind(
      event.data.id,
      event.data.email_addresses[0].email_address,
      `${event.data.first_name} ${event.data.last_name}`,
      Date.now(),
      Date.now()
    ).run()
  }

  return c.json({ received: true })
})

Note: This example shows the complete structure for an authenticated multi-user app. For AI-powered features, see ai-web-app.md.