feat: mock exercise template api (#613)

* feat: move mock exercise template to api

* feat: add template migration file

* chore:
- remove exercise templates form api
- rework api to pull exercise templates from db
- add exercise template seed to seed file
- declutter seed file

* chore:
- refactor template fetch to be dependent on the selected tag
- add tag to schema, remote db and template's seed file
- clear remote db's template table and replace data and their respective tags

* chore: add number of questions and center loading screen on the frontend

* chore: pr fixes

* chore: type fixes

* chore:
- move seed functionality to /seed folder
- add typing for individual seed functions
- pr fixes

* chore: pr fixes v2

* update output css

* refactor: update seeding script and schema definitions

- Renamed `seedTypes` to `seedQuestionTypes` for clarity in the seeding process.
- Enhanced the seeding script to include a command-line interface for better usability, allowing specific seed functions to be executed based on flags.
- Removed redundant unique constraints in the schema definitions for `courseNewsfeedComment` and `exerciseTemplate` to streamline database structure.

* feat: add email verification error handling page and update session management

- Introduced a new page for handling email verification errors, providing user-friendly messages based on error types.
- Updated session management logic to improve handling of public routes and session states.
- Enhanced security policies in the Svelte configuration to include additional sources for styles and scripts.
- Removed the old email verification error page to streamline the codebase.

* move the exercise template to cloudflare cdn

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
This commit is contained in:
Adeyemi Adetunji
2025-12-25 05:09:27 +01:00
committed by GitHub
co-authored by Rotimi Best
parent ac3f35a97f
commit 50d6ee3111
263 changed files with 1568 additions and 37195 deletions
+35 -24
View File
@@ -821,6 +821,7 @@ import { authOrApiKeyMiddleware } from '@api/middlewares/auth-or-api-key';
```
**API Key Authentication:**
- Add `API_KEY` to `apps/api/src/config/env.ts` environment schema
- Use `Authorization: Bearer <api-key>` header format (standard across popular APIs)
- Use `apiKeyMiddleware` for API-key-only endpoints
@@ -1100,7 +1101,7 @@ export const app = new Hono().route('/account', accountRouter); // Route availab
### Step 5: Run the api build command (at the root directory)
```
sudo pnpm --filter @cio/api build
pnpm --filter @cio/api build
```
---
@@ -1219,12 +1220,7 @@ export async function createOrg(profileId: string, data: { name: string; siteNam
// Business Logic: Check sitename availability
const exists = await checkSiteNameExists(data.siteName);
if (exists) {
throw new AppError(
`Site name '${data.siteName}' already exists`,
ErrorCodes.SITENAME_EXISTS,
409,
'siteName'
);
throw new AppError(`Site name '${data.siteName}' already exists`, ErrorCodes.SITENAME_EXISTS, 409, 'siteName');
}
// Business Logic: Create org and member in a transaction
@@ -1256,12 +1252,7 @@ export async function createOrg(profileId: string, data: { name: string; siteNam
} catch (error) {
// Handle database constraint violations
if (error.code === '23505') {
throw new AppError(
`Site name '${data.siteName}' already exists`,
ErrorCodes.SITENAME_EXISTS,
409,
'siteName'
);
throw new AppError(`Site name '${data.siteName}' already exists`, ErrorCodes.SITENAME_EXISTS, 409, 'siteName');
}
throw new AppError('Failed to create organization', ErrorCodes.ORG_CREATE_FAILED, 500);
}
@@ -1282,8 +1273,11 @@ import { authMiddleware } from '@api/middlewares/auth';
import { handleError } from '@api/utils/errors';
import { zValidator } from '@hono/zod-validator';
export const organizationRouter = new Hono()
.post('/', authMiddleware, zValidator('json', ZCreateOrganization), async (c) => {
export const organizationRouter = new Hono().post(
'/',
authMiddleware,
zValidator('json', ZCreateOrganization),
async (c) => {
try {
const user = c.get('user');
const data = c.req.valid('json');
@@ -1294,7 +1288,8 @@ export const organizationRouter = new Hono()
} catch (error) {
return handleError(c, error, 'Failed to create organization');
}
});
}
);
```
**Note**: Use `201 Created` status code for successful POST requests that create resources.
@@ -1310,8 +1305,7 @@ export * from './organization';
// apps/api/src/app.ts
import { organizationRouter } from '@api/routes/organization';
export const app = new Hono()
.route('/organization', organizationRouter); // Route available at POST /organization
export const app = new Hono().route('/organization', organizationRouter); // Route available at POST /organization
```
---
@@ -1373,6 +1367,7 @@ export const orgApi = new OrgApi();
```
**Key Points:**
- Use `this.execute()` from `BaseApiWithErrors` for automatic loading/error state
- Field-specific errors are automatically mapped to `this.errors[field]`
- Update stores in `onSuccess` callback
@@ -1385,11 +1380,13 @@ export const orgApi = new OrgApi();
Sometimes a service function exists in a different context (e.g., `createOrganizationWithOwner` in onboarding service).
**When to Reuse:**
- ✅ The function does exactly what you need
- ✅ It's in the same domain (organization operations)
- ✅ You can call it from your new route without modification
**When to Create New:**
- ❌ The existing function has domain-specific logic you don't want (e.g., onboarding-specific)
- ❌ You need different parameters or return values
- ❌ The function is in a different domain and mixing concerns would be inappropriate
@@ -1455,18 +1452,17 @@ When you have existing components using direct Supabase calls, follow this migra
#### Step 1: Identify the Operation
Analyze the existing Supabase call:
- **What database operation?** (CREATE, UPDATE, DELETE, SELECT)
- **What tables are involved?**
- **Are there related operations?** (e.g., create org + create member should be atomic)
- **What validation is currently done?** (client-side only or also server-side?)
**Example Analysis:**
```typescript
// Current code
const { data: org, error } = await supabase
.from('organization')
.insert({ name: orgName, siteName: siteName })
.select();
const { data: org, error } = await supabase.from('organization').insert({ name: orgName, siteName: siteName }).select();
const { data, error } = await supabase
.from('organizationmember')
@@ -1482,21 +1478,25 @@ const { data, error } = await supabase
#### Step 2: Check for Existing Services
Search for existing service functions that might handle this:
- Check `apps/api/src/services/` for similar operations
- Look in related domains (e.g., onboarding might have org creation)
- Check if the operation exists but in a different context
**Decision:**
- **Reuse existing service** if it does what you need
- **Create new service** if you need different logic or parameters
#### Step 3: Create/Update API Route
If the route doesn't exist:
- Follow the "Creating a New Route" workflow above
- Use appropriate HTTP method: `POST` (create), `PUT` (update), `DELETE` (delete)
If the route exists:
- Add new endpoint to existing router
- Follow route naming conventions (see [`apps/api/ROUTE_NAMING_BEST_PRACTICES.md`](apps/api/ROUTE_NAMING_BEST_PRACTICES.md))
@@ -1542,6 +1542,7 @@ class OrgApi extends BaseApiWithErrors {
```
**Key Points:**
- Always use `this.execute()` from `BaseApiWithErrors`
- Client-side validation before API call
- Update stores in `onSuccess` callback
@@ -1552,6 +1553,7 @@ class OrgApi extends BaseApiWithErrors {
Replace Supabase calls with API class method:
**Before (Direct Supabase):**
```typescript
<script lang="ts">
import { supabase } from '$lib/utils/functions/supabase';
@@ -1594,6 +1596,7 @@ Replace Supabase calls with API class method:
```
**After (API Route):**
```typescript
<script lang="ts">
import { orgApi } from '$features/org/api/org.svelte';
@@ -1627,6 +1630,7 @@ Replace Supabase calls with API class method:
```
**Key Differences:**
- ✅ **No manual transaction cleanup** - Handled by service layer
- ✅ **No separate member creation** - Handled atomically by service
- ✅ **Automatic store updates** - Handled by API class `onSuccess`
@@ -1639,6 +1643,7 @@ Replace Supabase calls with API class method:
The `BaseApiWithErrors` class automatically maps field-specific errors from the API response:
**API Response Structure:**
```typescript
// Field-specific error
{
@@ -1658,6 +1663,7 @@ The `BaseApiWithErrors` class automatically maps field-specific errors from the
```
**In Component:**
```typescript
await orgApi.create({ name: orgName, siteName });
@@ -1666,7 +1672,7 @@ if (!orgApi.success) {
if (orgApi.errors.siteName) {
errors.siteName = orgApi.errors.siteName;
}
// General error automatically in orgApi.errors.general
if (orgApi.errors.general) {
errors.general = orgApi.errors.general;
@@ -1675,6 +1681,7 @@ if (!orgApi.success) {
```
**How It Works:**
1. API returns error with `field` property
2. `BaseApiWithErrors.execute()` automatically sets `this.errors[field] = error`
3. Component can access `orgApi.errors.fieldName` directly
@@ -1732,7 +1739,7 @@ export const app = new Hono()
### Step 5: Run the api build command (at the root directory)
```
sudo pnpm --filter @cio/api build
pnpm --filter @cio/api build
```
---
@@ -2117,6 +2124,7 @@ export const communityApi = new CommunityApi();
#### Using the `execute` Method
**Pattern:**
```typescript
import { BaseApiWithErrors, classroomio } from '$lib/utils/services/api';
import { ZItemUpdate } from '@cio/utils/validation/items';
@@ -2177,6 +2185,7 @@ export const itemApi = new ItemApi();
```
**Key Points:**
- **Always use `this.execute`** - Never call the RPC client directly
- **Always validate first** - Use Zod's `safeParse()` before API call
- **Type the execute call** with `typeof classroomio.{route}.{method}` for type safety
@@ -2187,6 +2196,7 @@ export const itemApi = new ItemApi();
- **Return early on validation failure** - Don't make API call if validation fails
**Benefits:**
- ✅ Automatic loading state management (`this.isLoading`)
- ✅ Automatic error state management (`this.error` or `this.errors`)
- ✅ Consistent error handling across all API calls
@@ -2326,6 +2336,7 @@ const result = await OrgPlanApiServer.createOrgPlan(planData);
4. Create `.server.ts` files with static methods that use `getApiKeyHeaders()` helper
**Helper Function**: Use `getApiKeyHeaders()` from `$lib/utils/services/api/server` to get API key headers. This helper:
- Returns the correct headers format for RPC client
- Validates that `API_KEY` is configured
- Provides consistent API key authentication across all server-side files