Skip to content

Commit 8c46236

Browse files
authored
Merge pull request #15 from dcsil/sweet-swartz
Migrate to PostgreSQL and fix critical bugs for production deployment
2 parents 03e74c9 + 2502806 commit 8c46236

19 files changed

Lines changed: 7477 additions & 406 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Deploy to Production
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*' # Trigger on version tags like v1.0.0
7+
8+
jobs:
9+
deploy:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v3
15+
16+
- name: Deploy to Vercel
17+
uses: amondnet/vercel-action@v25
18+
with:
19+
vercel-token: ${{ secrets.VERCEL_TOKEN }}
20+
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
21+
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
22+
vercel-args: '--prod'

CLAUDE.MD

Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
# CLAUDE.MD - AI Development Guide
2+
3+
This file provides context for AI assistants working on the Classroom Presentation Randomizer project.
4+
5+
## Project Overview
6+
7+
A Next.js application for university instructors to manage randomized student presentations with timing, grading, and crash recovery features. Built with TypeScript, React, SQLite, and Tailwind CSS.
8+
9+
## Architecture
10+
11+
### Tech Stack
12+
- **Framework**: Next.js 15 (App Router)
13+
- **Language**: TypeScript
14+
- **Database**: PostgreSQL with @vercel/postgres (migrated from SQLite)
15+
- **Auth**: JWT with httpOnly cookies (jose library)
16+
- **Styling**: Tailwind CSS
17+
- **Password Hashing**: bcryptjs
18+
- **Deployment**: Vercel (production) with GitHub Actions CI/CD
19+
20+
### Key Components
21+
22+
#### Frontend Components (`/components`)
23+
- **Dashboard.tsx**: Main container, orchestrates phases (Setup → Presentation → History)
24+
- **SetupPhase.tsx**: Session creation, roster import (CSV/manual), rubric builder
25+
- **PresentationPhase.tsx**: Team randomization, smart timer with crash recovery, grading UI
26+
- **HistoryView.tsx**: Completed presentations, grade editing, CSV export
27+
28+
#### API Routes (`/app/api`)
29+
- **auth/**: Login, signup, logout (JWT management)
30+
- **session/**: Create sessions, get session data
31+
- **teams/**: Add teams (bulk/individual), fetch team lists
32+
- **rubric/**: Create criteria, save/load templates, lock rubric
33+
- **presentations/**: Start presentation, update timer state, recovery
34+
- **grades/**: Submit grades, edit grades (with audit trail)
35+
- **export/**: Generate CSV exports
36+
37+
#### Database (`/lib/db.ts`)
38+
- PostgreSQL connection via @vercel/postgres
39+
- Schema initialized via `scripts/init-postgres.sql`
40+
- Foreign key enforcement enabled
41+
- Tables: users, sessions, teams, rubric_templates, rubric_criteria, presentations, grades, feedback, grade_audit, session_state
42+
- All database operations are asynchronous (await required)
43+
44+
#### Types (`/types/index.ts`)
45+
- Centralized TypeScript definitions for all data models
46+
47+
## Key Features & Implementation Details
48+
49+
### 1. Smart Timer System
50+
- **Manual Start Only**: Timers never auto-start (instructor control)
51+
- **Visual Warnings**: Green → Yellow (2min warning) → Red (overtime)
52+
- **Overtime Tracking**: Counts into negative with clear indication
53+
- **Crash Recovery**: Timer state saved to `session_state` table, auto-resumes on reload
54+
- **Implementation**: PresentationPhase.tsx maintains timer state, syncs with backend every 5 seconds
55+
56+
### 2. Rubric Locking
57+
- Rubric criteria can be edited during Setup Phase
58+
- **Locks automatically** when first presentation starts (ensures fairness)
59+
- Prevents mid-session rubric changes that would create inconsistent grading
60+
61+
### 3. Team Randomization
62+
- Fair random selection from eligible pool (teams not yet presented)
63+
- Skip/Defer returns teams to pool for later selection
64+
- Algorithm: Fisher-Yates shuffle on eligible team IDs
65+
66+
### 4. Grade Audit Trail
67+
- All grade edits logged to `grade_audit` table with timestamps
68+
- Preserves academic integrity and accountability
69+
70+
### 5. CSV Import/Export
71+
- **Import**: Flexible parsing (`Team Name, Member1, Member2, ...`)
72+
- **Export**: Includes team info, scores per criterion, total, feedback
73+
74+
## Database Schema Notes
75+
76+
### Critical Relationships
77+
- `sessions``teams` (1:many)
78+
- `sessions``rubric_criteria` (1:many)
79+
- `sessions``presentations` (1:many)
80+
- `presentations``teams` (1:1)
81+
- `presentations``grades` (1:many, one per criterion)
82+
- `presentations``feedback` (1:1)
83+
84+
### Important Fields
85+
- **presentations.timer_state**: JSON object with `{ presentationTimeLeft, qaTimeLeft, isInQA, isPaused }`
86+
- **presentations.status**: 'in_progress' | 'completed'
87+
- **rubric_criteria.is_locked**: Boolean, set when first presentation starts
88+
- **grade_audit.edited_at**: Timestamp for grade modifications
89+
90+
## Development Guidelines
91+
92+
### Code Style
93+
- Use TypeScript strict mode
94+
- Functional React components with hooks
95+
- API routes return `NextResponse` with appropriate status codes
96+
- Database queries use async/await with @vercel/postgres
97+
- Error handling: try/catch with meaningful error messages
98+
- Transactions for multi-step operations (teams, grades)
99+
100+
### State Management
101+
- Local component state (useState) for UI interactions
102+
- Database as source of truth for persistent data
103+
- Polling for timer recovery (checks session_state on mount)
104+
105+
### Security Considerations
106+
- JWT stored in httpOnly cookies (prevents XSS)
107+
- Passwords hashed with bcryptjs (never stored plain)
108+
- API routes verify JWT before processing
109+
- Input validation on all endpoints (team names, scores, etc.)
110+
- SQL injection prevented by parameterized queries (@vercel/postgres tagged templates)
111+
- Secrets stored in Vercel environment variables (never in source code)
112+
113+
### Common Patterns
114+
115+
#### API Route Structure (Postgres)
116+
```typescript
117+
export async function POST(request: Request) {
118+
try {
119+
// 1. Verify JWT from cookie
120+
const session = await getSession();
121+
if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
122+
123+
// 2. Parse request body
124+
const data = await request.json();
125+
126+
// 3. Validate input
127+
if (!data.requiredField) return NextResponse.json({ error: 'Missing field' }, { status: 400 });
128+
129+
// 4. Database operation (async, must await)
130+
const result = await sql`INSERT INTO table (field) VALUES (${data.field}) RETURNING *`;
131+
const record = result.rows[0];
132+
133+
// 5. Return response
134+
return NextResponse.json({ success: true, data: record });
135+
} catch (error) {
136+
return NextResponse.json({ error: 'Internal error' }, { status: 500 });
137+
}
138+
}
139+
```
140+
141+
#### Transaction Pattern (for multi-step operations)
142+
```typescript
143+
await sql.query('BEGIN');
144+
try {
145+
await sql`INSERT INTO teams ...`;
146+
await sql`INSERT INTO presentations ...`;
147+
await sql.query('COMMIT');
148+
} catch (error) {
149+
await sql.query('ROLLBACK');
150+
throw error;
151+
}
152+
```
153+
154+
#### Component Data Fetching
155+
```typescript
156+
useEffect(() => {
157+
async function fetchData() {
158+
const res = await fetch('/api/endpoint');
159+
const data = await res.json();
160+
setState(data);
161+
}
162+
fetchData();
163+
}, [dependency]);
164+
```
165+
166+
## File Locations
167+
168+
### Adding New Features
169+
- **New API endpoint**: Create file in `/app/api/feature-name/route.ts`
170+
- **New component**: Add to `/components/FeatureName.tsx`
171+
- **New types**: Add to `/types/index.ts`
172+
- **Database changes**: Modify schema in `/lib/db.ts` (handle migrations manually)
173+
174+
### Configuration Files
175+
- **Environment**: `.env.local` (JWT_SECRET)
176+
- **TypeScript**: `tsconfig.json`
177+
- **Tailwind**: `tailwind.config.ts`
178+
- **Next.js**: `next.config.ts`
179+
180+
## Testing Scenarios
181+
182+
### Manual Testing Checklist
183+
1. **Auth Flow**: Signup → Login → Dashboard access
184+
2. **Session Creation**: Create session with valid timers
185+
3. **Team Import**: CSV with various team sizes, manual add
186+
4. **Rubric Creation**: Multiple criteria with weights
187+
5. **Presentation Flow**:
188+
- Random team selection
189+
- Timer start/pause/resume
190+
- Switch to Q&A
191+
- Stop & Grade
192+
- Emergency Stop with defer
193+
6. **Grading**: Valid/invalid scores, feedback submission
194+
7. **Grade Editing**: Modify scores, verify audit trail
195+
8. **CSV Export**: Verify all data present
196+
9. **Crash Recovery**: Refresh during active timer
197+
10. **Rubric Lock**: Verify lock after first presentation starts
198+
199+
## Common Issues & Solutions
200+
201+
### Database Connection Errors (Postgres)
202+
- Ensure `POSTGRES_URL` environment variable is set correctly
203+
- Check Vercel Postgres dashboard for connection issues
204+
- Verify database is in same region as deployment
205+
206+
### Timer Drift
207+
- Timer state synced to backend every 5 seconds
208+
- On recovery, use server timestamp as source of truth
209+
- Client-side countdown for smooth UX
210+
211+
### Rubric Lock Edge Cases
212+
- Lock check: `SELECT rubric_locked FROM sessions WHERE session_id = ?`
213+
- Prevent edits if rubric is locked
214+
- UI disables rubric form when locked
215+
216+
## Deployment
217+
218+
### Local Development Setup
219+
220+
1. **Install Dependencies**:
221+
```bash
222+
npm install
223+
```
224+
225+
2. **Setup Local Postgres** (for testing migration):
226+
```bash
227+
# Using Docker (recommended)
228+
docker run --name classroom-postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres
229+
230+
# Create database
231+
psql -U postgres -c "CREATE DATABASE classroom_dev;"
232+
```
233+
234+
3. **Run Migration**:
235+
```bash
236+
psql -U postgres -d classroom_dev -f scripts/init-postgres.sql
237+
```
238+
239+
4. **Configure Environment Variables** (`.env.local`):
240+
```
241+
POSTGRES_URL=postgresql://postgres:postgres@localhost:5432/classroom_dev
242+
JWT_SECRET=your-jwt-secret-here
243+
```
244+
245+
5. **Start Dev Server**:
246+
```bash
247+
npm run dev
248+
```
249+
250+
### Production Deployment (Vercel)
251+
252+
1. **Create Vercel Account**: Sign up at https://vercel.com with GitHub
253+
254+
2. **Import Repository**: Connect your GitHub repository to Vercel
255+
256+
3. **Create Postgres Database**:
257+
- Go to Storage tab in Vercel dashboard
258+
- Create new Postgres database
259+
- Connect to project
260+
- Run migration script in SQL editor
261+
262+
4. **Set Environment Variables**:
263+
- `JWT_SECRET`: Generate with `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
264+
- `POSTGRES_URL`, `POSTGRES_PRISMA_URL`, `POSTGRES_URL_NON_POOLING`: Auto-provided by Vercel
265+
266+
5. **Deploy**: Push to main branch, Vercel auto-deploys
267+
268+
### GitHub Actions CI/CD
269+
270+
**Workflow File**: `.github/workflows/deploy.yml`
271+
272+
**Triggers on**: Git tags (e.g., `v1.0.0`)
273+
274+
**Required GitHub Secrets**:
275+
- `VERCEL_TOKEN`: From Vercel Settings → Tokens
276+
- `VERCEL_ORG_ID`: From `.vercel/project.json` after first deploy
277+
- `VERCEL_PROJECT_ID`: From `.vercel/project.json` after first deploy
278+
279+
**To Deploy**:
280+
```bash
281+
git tag v1.0.0
282+
git push origin v1.0.0
283+
```
284+
285+
### Production Checklist
286+
- [ ] Generate secure `JWT_SECRET` (32+ bytes)
287+
- [ ] Set all environment variables in Vercel dashboard
288+
- [ ] Set `NODE_ENV=production`
289+
- [ ] Configure database backup strategy
290+
- [ ] Consider SQLite file location (persistent volume)
291+
- [ ] Test crash recovery in production environment
292+
293+
### Environment Variables
294+
```
295+
JWT_SECRET=<secure-random-string-256-bits>
296+
NODE_ENV=production
297+
```
298+
299+
## Future Enhancement Areas
300+
301+
### Not in MVP (Don't implement unless requested)
302+
- Multi-instructor support (requires user_id isolation)
303+
- Multiple concurrent sessions (currently single-session design)
304+
- Real-time sync for projection display
305+
- Student-facing grade portal
306+
- Email notifications
307+
- Advanced analytics/charts
308+
- Mobile responsive overhaul
309+
310+
## Questions to Ask Before Making Changes
311+
312+
1. **Does this change affect grading fairness?** (e.g., rubric modifications mid-session)
313+
2. **Will this persist across browser crashes?** (timer state, presentation status)
314+
3. **Does this require authentication?** (all API routes except auth)
315+
4. **Is there audit trail needed?** (grade edits, rubric changes)
316+
5. **Could this be a security risk?** (input validation, SQL injection, XSS)
317+
318+
## Useful Commands
319+
320+
```bash
321+
# Development
322+
pnpm dev # Start dev server (http://localhost:3000)
323+
pnpm build # Production build
324+
pnpm start # Start production server
325+
pnpm lint # ESLint check
326+
327+
# Database
328+
# Database file: ./classroom.db
329+
# Inspect with: sqlite3 classroom.db
330+
# Reset DB: Delete classroom.db, restart server
331+
```
332+
333+
## Contact & Maintenance
334+
335+
This is an MVP project for CSC491. Prioritize simplicity and reliability over feature creep. When in doubt, ask the user before adding complexity.

0 commit comments

Comments
 (0)