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-reactand@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
svixpackage - Create webhook endpoint
/api/webhooks/clerk - Implement signature verification
- Handle
user.createdevent (insert into database) - Handle
user.updatedevent - Handle
user.deletedevent - 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):trueorfalsetag(optional): Tag ID to filter bydueBefore(optional): Unix timestampdueAfter(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.