Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 37 additions & 38 deletions SMS_LOG_AGGREGATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,19 @@ SMSLogAggregator ← metrics, anomaly detection, export

## New Files

| File | Purpose |
|------|---------|
| `src/lib/sms/types.ts` | SMS types: `SMSMessage`, `SMSSendResult`, `SMSDeliveryLog`, etc. |
| `src/lib/sms/provider.ts` | Provider implementations: Twilio, AWS SNS, Vonage + factory |
| `src/lib/sms/queue.ts` | Queue with retry, exponential backoff, per-job delivery logs |
| `src/lib/sms/service.ts` | High-level service: event dispatch, message templates |
| `src/lib/sms/index.ts` | Barrel export |
| `src/lib/logging/sms-aggregator.ts` | Aggregation layer: metrics, anomaly detection, export |
| `src/app/api/sms/send/route.ts` | `POST /api/sms/send` — send SMS via HTTP |
| `src/app/api/sms/logs/route.ts` | `GET /api/sms/logs` — query logs, metrics, anomalies, export |
| `src/__tests__/sms/queue.test.ts` | Queue unit tests |
| `src/__tests__/sms/service.test.ts` | Service unit tests |
| `src/__tests__/logging/sms-aggregator.test.ts` | Aggregator unit tests |
| File | Purpose |
| ---------------------------------------------- | ---------------------------------------------------------------- |
| `src/lib/sms/types.ts` | SMS types: `SMSMessage`, `SMSSendResult`, `SMSDeliveryLog`, etc. |
| `src/lib/sms/provider.ts` | Provider implementations: Twilio, AWS SNS, Vonage + factory |
| `src/lib/sms/queue.ts` | Queue with retry, exponential backoff, per-job delivery logs |
| `src/lib/sms/service.ts` | High-level service: event dispatch, message templates |
| `src/lib/sms/index.ts` | Barrel export |
| `src/lib/logging/sms-aggregator.ts` | Aggregation layer: metrics, anomaly detection, export |
| `src/app/api/sms/send/route.ts` | `POST /api/sms/send` — send SMS via HTTP |
| `src/app/api/sms/logs/route.ts` | `GET /api/sms/logs` — query logs, metrics, anomalies, export |
| `src/__tests__/sms/queue.test.ts` | Queue unit tests |
| `src/__tests__/sms/service.test.ts` | Service unit tests |
| `src/__tests__/logging/sms-aggregator.test.ts` | Aggregator unit tests |

---

Expand Down Expand Up @@ -85,17 +85,17 @@ LOG_AGGREGATION_URL=https://your-log-aggregation-endpoint.com/logs

Every SMS operation emits structured log records with the following context fields:

| Field | Description |
|-------|-------------|
| `jobId` | Unique queue job identifier |
| `provider` | Active SMS provider (`twilio`, `sns`, `vonage`) |
| `phoneNumber` | Recipient (E.164 format) |
| `messageId` | Provider-assigned message ID on success |
| `status` | `pending` / `sent` / `failed` / `retrying` |
| `attempt` | Current attempt number |
| `maxRetries` | Configured retry limit |
| `eventType` | SMS event type (e.g. `verification-code`) |
| `tags` | Message tags for filtering |
| Field | Description |
| ------------- | ----------------------------------------------- |
| `jobId` | Unique queue job identifier |
| `provider` | Active SMS provider (`twilio`, `sns`, `vonage`) |
| `phoneNumber` | Recipient (E.164 format) |
| `messageId` | Provider-assigned message ID on success |
| `status` | `pending` / `sent` / `failed` / `retrying` |
| `attempt` | Current attempt number |
| `maxRetries` | Configured retry limit |
| `eventType` | SMS event type (e.g. `verification-code`) |
| `tags` | Message tags for filtering |

Performance metrics (`sms.send_duration_ms`, `sms.sent`, `sms.failed`, `sms.retry`, `sms.enqueued`) are emitted via the existing `createCounterMetric` / `measureAsync` infrastructure.

Expand All @@ -107,18 +107,18 @@ Performance metrics (`sms.send_duration_ms`, `sms.sent`, `sms.failed`, `sms.retr

Query aggregated SMS logs.

| Query param | Default | Description |
|-------------|---------|-------------|
| `action` | `query` | `query` \| `metrics` \| `failed` \| `anomalies` \| `store-stats` \| `export` |
| `level` | — | Filter by log level (`info`, `warn`, `error`) |
| `provider` | — | Filter by provider |
| `eventType` | — | Filter by event type |
| `status` | — | Filter by delivery status |
| `since` | — | Unix timestamp (ms) lower bound |
| `limit` | `100` | Max records returned |
| `offset` | `0` | Pagination offset |
| `timeRangeMs` | `86400000` | Time window for `metrics` action (ms) |
| `format` | `json` | `json` \| `csv` for `export` action |
| Query param | Default | Description |
| ------------- | ---------- | ---------------------------------------------------------------------------- |
| `action` | `query` | `query` \| `metrics` \| `failed` \| `anomalies` \| `store-stats` \| `export` |
| `level` | — | Filter by log level (`info`, `warn`, `error`) |
| `provider` | — | Filter by provider |
| `eventType` | — | Filter by event type |
| `status` | — | Filter by delivery status |
| `since` | — | Unix timestamp (ms) lower bound |
| `limit` | `100` | Max records returned |
| `offset` | `0` | Pagination offset |
| `timeRangeMs` | `86400000` | Time window for `metrics` action (ms) |
| `format` | `json` | `json` \| `csv` for `export` action |

**Examples:**

Expand Down Expand Up @@ -184,8 +184,7 @@ const metrics = SMSLogAggregator.getMetrics();
// { totalMessages, successRate, errorRate, averageDeliveryTimeMs, byProvider, byEventType }

// Anomaly detection
const { slowDeliveries, highRetryAttempts, configurationErrors } =
SMSLogAggregator.getAnomalies();
const { slowDeliveries, highRetryAttempts, configurationErrors } = SMSLogAggregator.getAnomalies();

// Export for external systems
const csv = SMSLogAggregator.exportLogs('csv');
Expand Down
74 changes: 35 additions & 39 deletions docs/QUOTE_COMPONENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,25 @@ import type { QuoteProps } from '@/components/ui/Quote';
## Basic Usage

```tsx
<Quote
text="The only way to do great work is to love what you do."
author="Steve Jobs"
/>
<Quote text="The only way to do great work is to love what you do." author="Steve Jobs" />
```

## Props

| Prop | Type | Default | Description |
| ---------------- | ------------------------- | ----------- | ---------------------------------------------------- |
| `text` | `string` | — | **Required.** The quote text to display. |
| `author` | `string` | `undefined` | Name of the person being quoted. |
| `source` | `string` | `undefined` | Optional source, book title, or citation. |
| `onCopy` | `(text: string) => void` | `undefined` | Called with the quote text after it is copied. |
| `onSwipeLeft` | `() => void` | `undefined` | Called on a left swipe (or left-arrow click). |
| `onSwipeRight` | `() => void` | `undefined` | Called on a right swipe (or right-arrow click). |
| `onPinchIn` | `() => void` | `undefined` | Called when a pinch-in gesture is detected. |
| `onPinchOut` | `() => void` | `undefined` | Called when a pinch-out gesture is detected. |
| `className` | `string` | `''` | Extra Tailwind classes applied to the root element. |
| `showNavigation` | `boolean` | `false` | Show previous/next arrow buttons. |
| `showCopyButton` | `boolean` | `true` | Show the clipboard copy button. |
| `icon` | `React.ReactNode` | `undefined` | Replace the default quote icon with a custom node. |
| Prop | Type | Default | Description |
| ---------------- | ------------------------ | ----------- | --------------------------------------------------- |
| `text` | `string` | — | **Required.** The quote text to display. |
| `author` | `string` | `undefined` | Name of the person being quoted. |
| `source` | `string` | `undefined` | Optional source, book title, or citation. |
| `onCopy` | `(text: string) => void` | `undefined` | Called with the quote text after it is copied. |
| `onSwipeLeft` | `() => void` | `undefined` | Called on a left swipe (or left-arrow click). |
| `onSwipeRight` | `() => void` | `undefined` | Called on a right swipe (or right-arrow click). |
| `onPinchIn` | `() => void` | `undefined` | Called when a pinch-in gesture is detected. |
| `onPinchOut` | `() => void` | `undefined` | Called when a pinch-out gesture is detected. |
| `className` | `string` | `''` | Extra Tailwind classes applied to the root element. |
| `showNavigation` | `boolean` | `false` | Show previous/next arrow buttons. |
| `showCopyButton` | `boolean` | `true` | Show the clipboard copy button. |
| `icon` | `React.ReactNode` | `undefined` | Replace the default quote icon with a custom node. |

## Examples

Expand All @@ -53,15 +50,17 @@ Pass `showNavigation` together with `onSwipeLeft` / `onSwipeRight` to enable bot

```tsx
const [index, setIndex] = useState(0);
const quotes = [/* ... */];
const quotes = [
/* ... */
];

<Quote
text={quotes[index].text}
author={quotes[index].author}
showNavigation
onSwipeLeft={() => setIndex((i) => Math.min(i + 1, quotes.length - 1))}
onSwipeRight={() => setIndex((i) => Math.max(i - 1, 0))}
/>
/>;
```

### Copy Callback
Expand Down Expand Up @@ -98,13 +97,13 @@ const quotes = [/* ... */];

The component wraps its content in `GestureHandler`, which listens for touch events and maps them to the corresponding callbacks:

| Gesture | Prop triggered | Notes |
| --------------- | ---------------- | ------------------------------------------------------------------ |
| Swipe left | `onSwipeLeft` | Default threshold: 50 px |
| Swipe right | `onSwipeRight` | Default threshold: 50 px |
| Pinch in | `onPinchIn` | Two-finger pinch closed |
| Pinch out | `onPinchOut` | Two-finger pinch open |
| Tap | — | Triggers the copy action when `showCopyButton` is `true` |
| Gesture | Prop triggered | Notes |
| ----------- | -------------- | -------------------------------------------------------- |
| Swipe left | `onSwipeLeft` | Default threshold: 50 px |
| Swipe right | `onSwipeRight` | Default threshold: 50 px |
| Pinch in | `onPinchIn` | Two-finger pinch closed |
| Pinch out | `onPinchOut` | Two-finger pinch open |
| Tap | — | Triggers the copy action when `showCopyButton` is `true` |

### iOS behaviour

Expand All @@ -118,23 +117,20 @@ On iOS, custom gestures are disabled by default to avoid conflicting with the na

## Accessibility

| Feature | Implementation |
| --------------------------- | ------------------------------------------------------------------ |
| Landmark role | `role="article"` on the root element |
| Accessible name | `aria-label="Quote by {author}"` (falls back to "Unknown author") |
| Copy button label | `aria-label="Copy quote to clipboard"` / `"Copied to clipboard"` |
| Navigation button labels | `aria-label="Previous quote"` / `"Next quote"` |
| Semantic quote markup | `<blockquote>` + `<cite>` for screen-reader context |
| Keyboard focus ring | `focus:ring-2 focus:ring-purple-500` on interactive controls |
| Feature | Implementation |
| ------------------------ | ----------------------------------------------------------------- |
| Landmark role | `role="article"` on the root element |
| Accessible name | `aria-label="Quote by {author}"` (falls back to "Unknown author") |
| Copy button label | `aria-label="Copy quote to clipboard"` / `"Copied to clipboard"` |
| Navigation button labels | `aria-label="Previous quote"` / `"Next quote"` |
| Semantic quote markup | `<blockquote>` + `<cite>` for screen-reader context |
| Keyboard focus ring | `focus:ring-2 focus:ring-purple-500` on interactive controls |

## Dark Mode

All colours use Tailwind `dark:` variants and adapt automatically when the application switches to dark mode. No extra configuration is required.

```tsx
// Renders correctly in both light and dark themes
<Quote
text="The best way to predict the future is to create it."
author="Peter Drucker"
/>
<Quote text="The best way to predict the future is to create it." author="Peter Drucker" />
```
29 changes: 23 additions & 6 deletions docs/REFERRAL_PROGRAM.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Referral Program Implementation

## Overview

This document describes the Referral Program implementation for the Authentication Flow in TeachLink. The referral program allows users to invite others to join the platform using unique referral codes, tracking referrals and providing benefits for both the referrer and the referred user.

## Features

### Core Functionality

- **Referral Code Generation**: Each user receives a unique 8-character referral code upon signup
- **Referral Tracking**: Users can enter a referral code during signup to track who referred them
- **Referral Validation**: The system validates referral codes before accepting them
Expand All @@ -15,9 +17,11 @@ This document describes the Referral Program implementation for the Authenticati
### API Endpoints

#### POST /api/auth/signup

Enhanced to support referral codes during user registration.

**Request Body:**

```json
{
"name": "John Doe",
Expand All @@ -29,6 +33,7 @@ Enhanced to support referral codes during user registration.
```

**Response:**

```json
{
"message": "Account created successfully",
Expand All @@ -46,12 +51,15 @@ Enhanced to support referral codes during user registration.
```

#### GET /api/referral/validate

Validates a referral code before use during signup.

**Query Parameters:**

- `code` (required): The referral code to validate

**Response:**

```json
{
"valid": true,
Expand All @@ -60,12 +68,14 @@ Validates a referral code before use during signup.
```

**Error Responses:**

- `400`: Invalid referral code format
- `404`: Referral code not found

## Data Model

### User Schema Extensions

The user schema has been extended to include referral-related fields:

```typescript
Expand All @@ -91,6 +101,7 @@ The format excludes confusing characters (I, O, 0, 1) to improve readability and
## Implementation Details

### Utilities

The referral functionality is implemented in `/src/lib/referral.ts` with the following utilities:

- `generateReferralCode()`: Generates a unique referral code
Expand All @@ -104,13 +115,11 @@ The referral functionality is implemented in `/src/lib/referral.ts` with the fol
### Frontend Integration

#### Signup Form

The signup form now includes an optional referral code field:

```tsx
<input
type="text"
placeholder="Enter referral code"
{...register('referralCode')}
/>
<input type="text" placeholder="Enter referral code" {...register('referralCode')} />
```

The field is optional and allows users to enter a referral code during registration.
Expand All @@ -125,20 +134,26 @@ The field is optional and allows users to enter a referral code during registrat
## Testing

### Unit Tests

Unit tests for referral utilities are located in `/src/lib/__tests__/referral.test.ts`:

- Code generation uniqueness and format
- Format validation
- Storage and retrieval operations
- Referral count tracking

### Integration Tests

Integration tests for API endpoints are located in `/src/app/api/referral/__tests__/validate.test.ts`:

- Referral validation endpoint behavior
- Error handling for invalid codes
- Rate limiting compliance

### E2E Tests

E2E tests for the referral flow are in `/e2e/auth/signup.spec.ts`:

- Signup with valid referral code
- Signup without referral code
- Error handling for invalid referral codes
Expand Down Expand Up @@ -183,6 +198,7 @@ When migrating from a system without referral support:
## Support and Maintenance

For issues or questions related to the referral program:

- Check the unit tests for usage examples
- Review the API endpoint documentation
- Contact the development team for complex scenarios
Expand All @@ -191,6 +207,7 @@ For issues or questions related to the referral program:
## Changelog

### Version 1.0.0 (Current)

- Initial implementation of referral program
- Referral code generation and validation
- Integration with signup flow
Expand All @@ -200,4 +217,4 @@ For issues or questions related to the referral program:
---

**Last Updated**: 2025-05-30
**Maintained By**: TeachLink Development Team
**Maintained By**: TeachLink Development Team
5 changes: 4 additions & 1 deletion k8s/notification-system-persistence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { describe, expect, it } from 'vitest';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';

const manifest = readFileSync(join(process.cwd(), 'k8s/notification-system-persistence.yaml'), 'utf8');
const manifest = readFileSync(
join(process.cwd(), 'k8s/notification-system-persistence.yaml'),
'utf8',
);

describe('notification system persistence manifest', () => {
it('declares a persistent volume claim for notification data', () => {
Expand Down
Loading
Loading