Markdown Converter
Agent skill for markdown-converter
You are working on the **REdI Quiz Platform**, a comprehensive web-based assessment system for the Resuscitation EDucation Initiative at Metro North Health. This platform enables healthcare staff to complete knowledge assessments with administrative capabilities for content management, user manageme
Sign in to like and favorite skills
You are working on the REdI Quiz Platform, a comprehensive web-based assessment system for the Resuscitation EDucation Initiative at Metro North Health. This platform enables healthcare staff to complete knowledge assessments with administrative capabilities for content management, user management, and completion tracking.
Single-Container Deployment: The application uses a unified backend container that serves both the API and frontend static files:
npm run dev)quiz/ ├── backend/ # Node.js/Express API (serves frontend in production) │ ├── src/ # Source code │ │ ├── config/ # Configuration modules │ │ ├── middleware/ │ │ ├── routes/ # API routes │ │ ├── services/ # Business logic │ │ ├── validators/ │ │ └── types/ │ ├── prisma/ # Database schema & migrations │ └── Dockerfile # Multi-stage build (backend + frontend) ├── frontend/ # React SPA │ └── src/ │ ├── components/ │ ├── pages/ │ ├── hooks/ │ ├── context/ │ └── services/ └── nginx/ # Legacy configs (not used in current architecture)
TypeScript
any types - use proper typingI prefix (e.g., IQuestionBank)Naming Conventions
Error Handling
Security
Starting Development
# Start backend and database in development mode docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d # Start frontend separately (in a new terminal) cd frontend && npm run dev # Backend API: http://localhost:9471 # Frontend: http://localhost:5173
Production Deployment
# Build and start (backend serves frontend) docker-compose up -d # Backend serves both API and frontend on port 3000 # Configure host nginx to proxy to backend:3000
Database Changes
# Create migration cd backend npx prisma migrate dev --name description_of_change # Apply migrations npx prisma migrate deploy
Before Committing
# Backend cd backend npm run lint npm run typecheck npm test # Frontend cd frontend npm run lint npm run typecheck npm test
Schema Changes
npx prisma migrate dev --name change_descriptionQueries
Creating New Endpoints
backend/src/routes/backend/src/services/backend/src/validators/API Response Format
// Success { success: true, data: { ... }, meta?: { page, pageSize, totalCount, totalPages } } // Error { success: false, error: { code: "ERROR_CODE", message: "Human-readable message", details?: { ... } } }
Component Structure
Styling
Forms
Backend Tests
Frontend Tests
# Development npm run dev # Start dev server npm run build # Build for production npm run lint # Run linter npm run lint:fix # Fix lint issues npm run typecheck # Check TypeScript types npm test # Run tests npm run test:coverage # Test with coverage # Database npx prisma generate # Generate Prisma Client npx prisma migrate dev # Create and apply migration npx prisma studio # Open Prisma Studio GUI npx prisma db seed # Seed database # Docker docker-compose up -d # Start production docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d # Start dev docker-compose down # Stop all services docker-compose logs -f backend # View backend logs docker-compose exec backend sh # Shell into backend
The platform supports 6 question types with specific JSON structures:
See the specification document for detailed schemas and scoring algorithms.
This section documents hard-won lessons from API payload mismatch bugs. Future agents MUST follow these rules when modifying any API endpoint or frontend service.
ALL backend API responses MUST follow this exact shape:
// Success with single item { success: true, data: T } // Success with paginated list { success: true, data: T[], meta: { page, pageSize, totalCount, totalPages } } // Error { success: false, error: { code, message, details? } }
The frontend Axios interceptor (
frontend/src/services/api.ts) automatically strips the outer response.data wrapper. So when frontend code does const body = await api.get(...), body is already the { success, data, meta? } object.
Backend service interfaces that describe API response shapes MUST use
string (not Date) for all date fields. Prisma returns Date objects, but JSON serialization converts them to ISO 8601 strings. Always explicitly call .toISOString() when mapping Prisma results to response interfaces:
// CORRECT interface IMyRow { createdAt: string; } const row: IMyRow = { createdAt: prismaResult.createdAt.toISOString() }; // WRONG - type lies about runtime value interface IMyRow { createdAt: Date; } // Will be string after JSON.stringify const row: IMyRow = { createdAt: prismaResult.createdAt }; // Implicit conversion
Every frontend API service function MUST:
as unknown as IApiResponse<ExpectedType>body.data (not body itself){ data: body.data, meta: body.meta! } or a named wrapper// Single item export async function getItem(id: string): Promise<IItem> { const body = (await api.get(`/items/${id}`)) as unknown as IApiResponse<IItem>; return body.data; } // Paginated list export async function listItems(): Promise<{ data: IItem[]; meta: IPaginationMeta }> { const body = (await api.get('/items')) as unknown as IApiResponse<IItem[]>; return { data: body.data, meta: body.meta! }; }
When adding or modifying an API endpoint, verify ALL of these:
{ success: true, data: <result> }string for dates (not Date)| Pitfall | Example | Fix |
|---|---|---|
| Backend returns array, frontend expects wrapper | vs | Align frontend to match backend |
| Backend includes relation fields, frontend type missing them | , | Add optional fields to frontend type |
| Validator missing fields that service accepts | No in update validator | Add |
| Date type mismatch | Backend , frontend | Use + |
| Test mock wrong shape | Mock returns nested | Mock the post-interceptor shape |
corrupts arrays | becomes | Always check before |
| correctAnswer shape mismatch | Editor saves , scoring expects | Use / in QuestionEditor |
The scoring engine (
backend/src/services/scoringService.ts) defines the authoritative format for correctAnswer and user response objects. Both MUST use structured objects, not bare primitives:
| Question Type | correctAnswer format | User response format |
|---|---|---|
| MC Single | | |
| MC Multi | | |
| True/False | | |
| Drag Order | | |
| Image Map | | |
| Slider | | |
The
QuestionEditor component uses bare UI values internally (e.g., "1" for option ID) and converts at the boundary via toScoringFormat() (save) and fromScoringFormat() (load).
When sanitizing JSON data (options, correctAnswer), ALWAYS check
Array.isArray() BEFORE treating as Record<string, unknown>. JavaScript arrays are objects, so typeof [] === 'object' is true. Using Object.entries() on an array converts ["a","b"] into {"0":"a","1":"b"}, silently corrupting the data.
| Layer | Files |
|---|---|
| Backend routes | |
| Backend services | |
| Backend validators | |
| Frontend types | |
| Frontend API services | |
| Frontend consumers | , |
| Tests | |
/docs/API.md (when created)The project follows a 5-phase build plan:
Refer to the specification for the detailed task list for each phase.