Implementation plan
This commit is contained in:
parent
eb4ea30404
commit
5cdafc7a4d
1 changed files with 409 additions and 0 deletions
409
IMPLEMENTATION_PLAN.md
Normal file
409
IMPLEMENTATION_PLAN.md
Normal file
|
|
@ -0,0 +1,409 @@
|
||||||
|
# Kaboot: Account-Based Quiz Persistence with Authentik + SQLite
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Add user accounts via Authentik (OIDC) and persist quizzes to SQLite database. Users can save custom and AI-generated quizzes to their account and load them later.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Docker Compose Stack │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │
|
||||||
|
│ │ Authentik │ │ PostgreSQL │ │ Redis │ │ Kaboot │ │
|
||||||
|
│ │ (IdP) │◀───│ (Authentik │ │ (Authentik │ │ Backend │ │
|
||||||
|
│ │ :9000 │ │ DB) │ │ Cache) │ │ :3001 │ │
|
||||||
|
│ └──────┬───────┘ └──────────────┘ └──────────────┘ └─────┬─────┘ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ OIDC/OAuth2 SQLite │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ ┌──────▼───────────────────────────────────────────────▼─────────▼─────┐ │
|
||||||
|
│ │ Kaboot Frontend (Vite) │ │
|
||||||
|
│ │ :5173 │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Infrastructure Setup
|
||||||
|
|
||||||
|
### 1.1 Docker Compose Configuration
|
||||||
|
- [ ] Create `docker-compose.yml` with all services:
|
||||||
|
- [ ] PostgreSQL (Authentik database)
|
||||||
|
- [ ] Redis (Authentik cache)
|
||||||
|
- [ ] Authentik server
|
||||||
|
- [ ] Authentik worker
|
||||||
|
- [ ] Kaboot backend service
|
||||||
|
- [ ] Create `.env.example` with required variables:
|
||||||
|
- [ ] `PG_PASS` - PostgreSQL password
|
||||||
|
- [ ] `AUTHENTIK_SECRET_KEY` - Authentik secret
|
||||||
|
- [ ] `PG_USER`, `PG_DB` - Optional customization
|
||||||
|
- [ ] Create setup script to generate secrets (`scripts/setup.sh`)
|
||||||
|
- [ ] Add `authentik/` directory structure for volumes:
|
||||||
|
- [ ] `authentik/media/`
|
||||||
|
- [ ] `authentik/certs/`
|
||||||
|
- [ ] `authentik/custom-templates/`
|
||||||
|
- [ ] Update `.gitignore` for new files:
|
||||||
|
- [ ] `.env`
|
||||||
|
- [ ] `authentik/media/*`
|
||||||
|
- [ ] `authentik/certs/*`
|
||||||
|
- [ ] `server/data/`
|
||||||
|
|
||||||
|
### 1.2 Authentik Configuration Documentation
|
||||||
|
- [ ] Document initial setup steps in `docs/AUTHENTIK_SETUP.md`:
|
||||||
|
- [ ] Navigate to `http://localhost:9000/if/flow/initial-setup/`
|
||||||
|
- [ ] Create admin account
|
||||||
|
- [ ] Create OAuth2/OIDC Application + Provider:
|
||||||
|
- [ ] Application name: `Kaboot`
|
||||||
|
- [ ] Application slug: `kaboot`
|
||||||
|
- [ ] Provider type: `OAuth2/OIDC`
|
||||||
|
- [ ] Client type: `Public` (SPA with PKCE)
|
||||||
|
- [ ] Client ID: `kaboot-spa`
|
||||||
|
- [ ] Redirect URIs: `http://localhost:5173/callback`, `http://localhost:5173/silent-renew.html`
|
||||||
|
- [ ] Scopes: `openid`, `profile`, `email`, `offline_access`
|
||||||
|
- [ ] Note down OIDC endpoints (issuer, JWKS URI, etc.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Backend API Development
|
||||||
|
|
||||||
|
### 2.1 Project Setup
|
||||||
|
- [ ] Create `server/` directory structure:
|
||||||
|
```
|
||||||
|
server/
|
||||||
|
├── Dockerfile
|
||||||
|
├── package.json
|
||||||
|
├── tsconfig.json
|
||||||
|
└── src/
|
||||||
|
├── index.ts
|
||||||
|
├── db/
|
||||||
|
├── middleware/
|
||||||
|
├── routes/
|
||||||
|
└── services/
|
||||||
|
```
|
||||||
|
- [ ] Initialize `package.json` with dependencies:
|
||||||
|
- [ ] `express` - Web framework
|
||||||
|
- [ ] `better-sqlite3` - SQLite driver
|
||||||
|
- [ ] `jsonwebtoken` - JWT verification
|
||||||
|
- [ ] `jwks-rsa` - JWKS client for Authentik
|
||||||
|
- [ ] `uuid` - ID generation
|
||||||
|
- [ ] `cors` - CORS middleware
|
||||||
|
- [ ] Dev deps: `typescript`, `@types/*`, `tsx`
|
||||||
|
- [ ] Create `tsconfig.json` for Node.js
|
||||||
|
- [ ] Create `Dockerfile` for backend container
|
||||||
|
|
||||||
|
### 2.2 Database Layer
|
||||||
|
- [ ] Create `server/src/db/schema.sql`:
|
||||||
|
- [ ] `users` table (synced from OIDC claims)
|
||||||
|
- [ ] `quizzes` table (with `user_id` foreign key)
|
||||||
|
- [ ] `questions` table (with `quiz_id` foreign key)
|
||||||
|
- [ ] `answer_options` table (with `question_id` foreign key)
|
||||||
|
- [ ] Indexes for foreign keys
|
||||||
|
- [ ] Create `server/src/db/connection.ts`:
|
||||||
|
- [ ] Initialize better-sqlite3 connection
|
||||||
|
- [ ] Run schema on startup
|
||||||
|
- [ ] Export db instance
|
||||||
|
|
||||||
|
### 2.3 Authentication Middleware
|
||||||
|
- [ ] Create `server/src/middleware/auth.ts`:
|
||||||
|
- [ ] JWKS client setup pointing to Authentik
|
||||||
|
- [ ] `requireAuth` middleware function:
|
||||||
|
- [ ] Extract Bearer token from Authorization header
|
||||||
|
- [ ] Verify JWT signature against JWKS
|
||||||
|
- [ ] Validate issuer matches Authentik
|
||||||
|
- [ ] Attach decoded user to request
|
||||||
|
- [ ] Define `AuthenticatedRequest` interface
|
||||||
|
|
||||||
|
### 2.4 API Routes
|
||||||
|
- [ ] Create `server/src/routes/quizzes.ts`:
|
||||||
|
- [ ] `GET /api/quizzes` - List user's quizzes (with question count)
|
||||||
|
- [ ] `GET /api/quizzes/:id` - Get full quiz with questions and options
|
||||||
|
- [ ] `POST /api/quizzes` - Save new quiz (upsert user from token)
|
||||||
|
- [ ] `PUT /api/quizzes/:id` - Update existing quiz
|
||||||
|
- [ ] `DELETE /api/quizzes/:id` - Delete quiz (verify ownership)
|
||||||
|
- [ ] Create `server/src/routes/users.ts`:
|
||||||
|
- [ ] `GET /api/users/me` - Get current user profile
|
||||||
|
- [ ] Create `server/src/index.ts`:
|
||||||
|
- [ ] Express app setup
|
||||||
|
- [ ] CORS configuration (allow frontend origin)
|
||||||
|
- [ ] JSON body parser
|
||||||
|
- [ ] Mount routes
|
||||||
|
- [ ] Error handling middleware
|
||||||
|
- [ ] Start server on port 3001
|
||||||
|
|
||||||
|
### 2.5 Backend Testing
|
||||||
|
- [ ] Test API manually with curl/Postman:
|
||||||
|
- [ ] Verify 401 without token
|
||||||
|
- [ ] Verify endpoints work with valid Authentik token
|
||||||
|
- [ ] Verify quiz CRUD operations
|
||||||
|
- [ ] Verify user sync from token claims
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Frontend Authentication
|
||||||
|
|
||||||
|
### 3.1 Dependencies
|
||||||
|
- [ ] Add to `package.json`:
|
||||||
|
- [ ] `react-oidc-context` - React OIDC hooks
|
||||||
|
- [ ] `oidc-client-ts` - Underlying OIDC client
|
||||||
|
- [ ] Run `npm install`
|
||||||
|
|
||||||
|
### 3.2 OIDC Configuration
|
||||||
|
- [ ] Create `src/config/oidc.ts`:
|
||||||
|
- [ ] Define `oidcConfig` object:
|
||||||
|
- [ ] `authority` - Authentik issuer URL
|
||||||
|
- [ ] `client_id` - `kaboot-spa`
|
||||||
|
- [ ] `redirect_uri` - `${origin}/callback`
|
||||||
|
- [ ] `post_logout_redirect_uri` - `${origin}`
|
||||||
|
- [ ] `response_type` - `code` (PKCE)
|
||||||
|
- [ ] `scope` - `openid profile email offline_access`
|
||||||
|
- [ ] `automaticSilentRenew` - `true`
|
||||||
|
- [ ] `userStore` - `WebStorageStateStore` with localStorage
|
||||||
|
- [ ] `onSigninCallback` - Clean URL after redirect
|
||||||
|
|
||||||
|
### 3.3 Auth Provider Setup
|
||||||
|
- [ ] Modify `src/main.tsx`:
|
||||||
|
- [ ] Import `AuthProvider` from `react-oidc-context`
|
||||||
|
- [ ] Import `oidcConfig`
|
||||||
|
- [ ] Wrap `<App />` with `<AuthProvider {...oidcConfig}>`
|
||||||
|
|
||||||
|
### 3.4 Auth UI Components
|
||||||
|
- [ ] Create `src/components/AuthButton.tsx`:
|
||||||
|
- [ ] Use `useAuth()` hook
|
||||||
|
- [ ] Show loading state while auth initializing
|
||||||
|
- [ ] Show "Sign In" button when unauthenticated
|
||||||
|
- [ ] Show username + "Sign Out" button when authenticated
|
||||||
|
- [ ] Modify `src/components/Landing.tsx`:
|
||||||
|
- [ ] Add `<AuthButton />` to top-right corner
|
||||||
|
- [ ] Style consistently with existing design
|
||||||
|
|
||||||
|
### 3.5 Authenticated Fetch Hook
|
||||||
|
- [ ] Create `src/hooks/useAuthenticatedFetch.ts`:
|
||||||
|
- [ ] Use `useAuth()` to get access token
|
||||||
|
- [ ] Create `authFetch` wrapper that:
|
||||||
|
- [ ] Adds `Authorization: Bearer <token>` header
|
||||||
|
- [ ] Adds `Content-Type: application/json`
|
||||||
|
- [ ] Handles 401 by triggering silent renew
|
||||||
|
- [ ] Export `{ authFetch, isAuthenticated }`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Quiz Library Feature
|
||||||
|
|
||||||
|
### 4.1 Quiz Library Hook
|
||||||
|
- [ ] Create `src/hooks/useQuizLibrary.ts`:
|
||||||
|
- [ ] State: `quizzes`, `loading`, `error`
|
||||||
|
- [ ] `fetchQuizzes()` - GET /api/quizzes
|
||||||
|
- [ ] `loadQuiz(id)` - GET /api/quizzes/:id, return Quiz
|
||||||
|
- [ ] `saveQuiz(quiz, source, aiTopic?)` - POST /api/quizzes
|
||||||
|
- [ ] `deleteQuiz(id)` - DELETE /api/quizzes/:id
|
||||||
|
- [ ] Handle loading and error states
|
||||||
|
|
||||||
|
### 4.2 Quiz Library UI
|
||||||
|
- [ ] Create `src/components/QuizLibrary.tsx`:
|
||||||
|
- [ ] Modal overlay design (consistent with app style)
|
||||||
|
- [ ] Header: "My Quizzes" with close button
|
||||||
|
- [ ] Search/filter input (optional, future enhancement)
|
||||||
|
- [ ] Quiz list:
|
||||||
|
- [ ] Show title, question count, source badge (AI/Manual), date
|
||||||
|
- [ ] Click to select
|
||||||
|
- [ ] Delete button with confirmation
|
||||||
|
- [ ] Footer: "Load Selected" and "Cancel" buttons
|
||||||
|
- [ ] Empty state: "No saved quizzes yet"
|
||||||
|
- [ ] Loading state: Skeleton/spinner
|
||||||
|
|
||||||
|
### 4.3 Landing Page Integration
|
||||||
|
- [ ] Modify `src/components/Landing.tsx`:
|
||||||
|
- [ ] Add "My Quizzes" button (only visible when authenticated)
|
||||||
|
- [ ] Add state for quiz library modal visibility
|
||||||
|
- [ ] Render `<QuizLibrary />` modal when open
|
||||||
|
- [ ] Handle quiz load: call `onLoadQuiz` prop with loaded quiz
|
||||||
|
|
||||||
|
### 4.4 Types Update
|
||||||
|
- [ ] Modify `src/types.ts`:
|
||||||
|
- [ ] Add `SavedQuiz` interface (extends `Quiz` with id, source, dates)
|
||||||
|
- [ ] Add `QuizListItem` interface (for list view)
|
||||||
|
- [ ] Add `QuizSource` type: `'manual' | 'ai_generated'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: Save Integration
|
||||||
|
|
||||||
|
### 5.1 Save After AI Generation
|
||||||
|
- [ ] Modify `src/hooks/useGame.ts`:
|
||||||
|
- [ ] Add `pendingQuizToSave` state
|
||||||
|
- [ ] After successful AI generation, set `pendingQuizToSave`
|
||||||
|
- [ ] Add `savePendingQuiz()` and `dismissSavePrompt()` functions
|
||||||
|
- [ ] Export these for UI to consume
|
||||||
|
- [ ] Create `src/components/SaveQuizPrompt.tsx`:
|
||||||
|
- [ ] Modal asking "Save this quiz to your library?"
|
||||||
|
- [ ] Show quiz title
|
||||||
|
- [ ] "Save" and "Skip" buttons
|
||||||
|
- [ ] Only show when authenticated
|
||||||
|
|
||||||
|
### 5.2 Save in Quiz Creator
|
||||||
|
- [ ] Modify `src/components/QuizCreator.tsx`:
|
||||||
|
- [ ] Add checkbox or toggle: "Save to my library"
|
||||||
|
- [ ] Pass `shouldSave` flag to `onFinalize`
|
||||||
|
- [ ] Modify `src/hooks/useGame.ts`:
|
||||||
|
- [ ] Update `finalizeManualQuiz` to accept save preference
|
||||||
|
- [ ] If save requested + authenticated, call `saveQuiz`
|
||||||
|
|
||||||
|
### 5.3 Load Quiz Flow
|
||||||
|
- [ ] Modify `src/hooks/useGame.ts`:
|
||||||
|
- [ ] Add `loadSavedQuiz(quiz: Quiz)` function
|
||||||
|
- [ ] Initialize game state with loaded quiz
|
||||||
|
- [ ] Transition to LOBBY state
|
||||||
|
- [ ] Wire up from Landing → QuizLibrary → useGame
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: Polish & Error Handling
|
||||||
|
|
||||||
|
### 6.1 Loading States
|
||||||
|
- [ ] Add loading indicators:
|
||||||
|
- [ ] Quiz library list loading
|
||||||
|
- [ ] Quiz loading when selected
|
||||||
|
- [ ] Save operation in progress
|
||||||
|
- [ ] Disable buttons during async operations
|
||||||
|
|
||||||
|
### 6.2 Error Handling
|
||||||
|
- [ ] Display user-friendly error messages:
|
||||||
|
- [ ] Failed to load quiz library
|
||||||
|
- [ ] Failed to save quiz
|
||||||
|
- [ ] Failed to delete quiz
|
||||||
|
- [ ] Network/auth errors
|
||||||
|
- [ ] Add retry mechanisms where appropriate
|
||||||
|
|
||||||
|
### 6.3 Toast Notifications (Optional)
|
||||||
|
- [ ] Add `react-hot-toast` or similar
|
||||||
|
- [ ] Show success toasts:
|
||||||
|
- [ ] "Quiz saved successfully"
|
||||||
|
- [ ] "Quiz deleted"
|
||||||
|
- [ ] Show error toasts for failures
|
||||||
|
|
||||||
|
### 6.4 Edge Cases
|
||||||
|
- [ ] Handle auth token expiry gracefully
|
||||||
|
- [ ] Handle offline state
|
||||||
|
- [ ] Handle concurrent save attempts
|
||||||
|
- [ ] Validate quiz data before save
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: Documentation & Deployment
|
||||||
|
|
||||||
|
### 7.1 Documentation
|
||||||
|
- [ ] Update main `README.md`:
|
||||||
|
- [ ] Add Docker Compose setup instructions
|
||||||
|
- [ ] Document environment variables
|
||||||
|
- [ ] Add Authentik configuration steps
|
||||||
|
- [ ] Create `docs/AUTHENTIK_SETUP.md` (detailed IdP setup)
|
||||||
|
- [ ] Create `docs/API.md` (backend API documentation)
|
||||||
|
|
||||||
|
### 7.2 Production Considerations
|
||||||
|
- [ ] Document production deployment:
|
||||||
|
- [ ] HTTPS setup (reverse proxy)
|
||||||
|
- [ ] Update redirect URIs for production domain
|
||||||
|
- [ ] Database backup strategy
|
||||||
|
- [ ] Authentik email configuration
|
||||||
|
- [ ] Add health check endpoints
|
||||||
|
- [ ] Add logging configuration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure Reference
|
||||||
|
|
||||||
|
```
|
||||||
|
kaboot/
|
||||||
|
├── docker-compose.yml # NEW
|
||||||
|
├── .env.example # NEW
|
||||||
|
├── .env # NEW (gitignored)
|
||||||
|
├── scripts/
|
||||||
|
│ └── setup.sh # NEW
|
||||||
|
├── docs/
|
||||||
|
│ ├── AUTHENTIK_SETUP.md # NEW
|
||||||
|
│ └── API.md # NEW
|
||||||
|
├── server/ # NEW
|
||||||
|
│ ├── Dockerfile
|
||||||
|
│ ├── package.json
|
||||||
|
│ ├── tsconfig.json
|
||||||
|
│ └── src/
|
||||||
|
│ ├── index.ts
|
||||||
|
│ ├── db/
|
||||||
|
│ │ ├── connection.ts
|
||||||
|
│ │ └── schema.sql
|
||||||
|
│ ├── middleware/
|
||||||
|
│ │ └── auth.ts
|
||||||
|
│ ├── routes/
|
||||||
|
│ │ ├── quizzes.ts
|
||||||
|
│ │ └── users.ts
|
||||||
|
│ └── services/
|
||||||
|
│ └── quiz.service.ts
|
||||||
|
├── authentik/ # NEW (gitignored contents)
|
||||||
|
│ ├── media/
|
||||||
|
│ ├── certs/
|
||||||
|
│ └── custom-templates/
|
||||||
|
├── src/
|
||||||
|
│ ├── main.tsx # MODIFY
|
||||||
|
│ ├── App.tsx # MODIFY
|
||||||
|
│ ├── types.ts # MODIFY
|
||||||
|
│ ├── config/
|
||||||
|
│ │ └── oidc.ts # NEW
|
||||||
|
│ ├── hooks/
|
||||||
|
│ │ ├── useGame.ts # MODIFY
|
||||||
|
│ │ ├── useAuthenticatedFetch.ts # NEW
|
||||||
|
│ │ └── useQuizLibrary.ts # NEW
|
||||||
|
│ └── components/
|
||||||
|
│ ├── Landing.tsx # MODIFY
|
||||||
|
│ ├── QuizCreator.tsx # MODIFY
|
||||||
|
│ ├── AuthButton.tsx # NEW
|
||||||
|
│ ├── QuizLibrary.tsx # NEW
|
||||||
|
│ └── SaveQuizPrompt.tsx # NEW
|
||||||
|
└── package.json # MODIFY
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Technical Decisions
|
||||||
|
|
||||||
|
| Decision | Choice | Rationale |
|
||||||
|
|----------|--------|-----------|
|
||||||
|
| Auth Provider | Authentik | Self-hosted, full-featured OIDC, Docker-friendly |
|
||||||
|
| OIDC Library | react-oidc-context | Lightweight, hooks-based, well-maintained |
|
||||||
|
| Backend Framework | Express | Simple, widely known, sufficient for this use case |
|
||||||
|
| Database | SQLite (better-sqlite3) | Zero config, file-based, perfect for single-server |
|
||||||
|
| Token Storage | localStorage | Required for SPA silent renew; acceptable for this app |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estimated Effort
|
||||||
|
|
||||||
|
| Phase | Description | Estimate |
|
||||||
|
|-------|-------------|----------|
|
||||||
|
| 1 | Infrastructure Setup | 1 day |
|
||||||
|
| 2 | Backend API Development | 1-2 days |
|
||||||
|
| 3 | Frontend Authentication | 1 day |
|
||||||
|
| 4 | Quiz Library Feature | 1 day |
|
||||||
|
| 5 | Save Integration | 0.5 day |
|
||||||
|
| 6 | Polish & Error Handling | 0.5 day |
|
||||||
|
| 7 | Documentation & Deployment | 0.5 day |
|
||||||
|
| **Total** | | **5-6 days** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Progress Tracking
|
||||||
|
|
||||||
|
**Last Updated**: 2026-01-13
|
||||||
|
|
||||||
|
| Phase | Status | Notes |
|
||||||
|
|-------|--------|-------|
|
||||||
|
| Phase 1 | Not Started | |
|
||||||
|
| Phase 2 | Not Started | |
|
||||||
|
| Phase 3 | Not Started | |
|
||||||
|
| Phase 4 | Not Started | |
|
||||||
|
| Phase 5 | Not Started | |
|
||||||
|
| Phase 6 | Not Started | |
|
||||||
|
| Phase 7 | Not Started | |
|
||||||
Loading…
Add table
Add a link
Reference in a new issue