Initial commit
This commit is contained in:
@@ -0,0 +1,490 @@
|
||||
# MetaCourse API — Frontend Integration Guide
|
||||
|
||||
> Target stack: Angular + Capacitor
|
||||
> Base URL (local): `http://localhost:8080`
|
||||
> Base URL (production): `http://romaric-thibault.fr`
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
### Requirements
|
||||
- Docker + Docker Compose installed on the server
|
||||
|
||||
### Start the stack
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
The API will be available on port **8080**. SQL Server runs on port **1433** (internal). Migrations run automatically on startup.
|
||||
|
||||
### Swagger UI
|
||||
```
|
||||
http://<host>:8080/swagger
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
There is **no JWT** currently. Login returns user info directly. Store `UserId` in local storage / Capacitor Preferences to identify the current user across requests.
|
||||
|
||||
---
|
||||
|
||||
## Data Models (TypeScript interfaces)
|
||||
|
||||
```typescript
|
||||
// Enums
|
||||
type CourseStatus = 'Draft' | 'Published';
|
||||
type ResourceType = 'Url' | 'Video' | 'Text' | 'File';
|
||||
|
||||
interface User {
|
||||
id: string; // Guid
|
||||
name: string;
|
||||
email: string;
|
||||
createdAt: string; // ISO date
|
||||
}
|
||||
|
||||
interface LoginResponse {
|
||||
userId: string;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface Course {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
status: CourseStatus;
|
||||
creatorId: string;
|
||||
creatorName: string;
|
||||
topicCount: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface CourseDetails extends Course {
|
||||
topics: Topic[];
|
||||
}
|
||||
|
||||
interface Topic {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
position: number;
|
||||
courseId: string;
|
||||
resources: Resource[];
|
||||
}
|
||||
|
||||
interface Resource {
|
||||
id: string;
|
||||
type: ResourceType;
|
||||
title: string;
|
||||
content: string; // URL, video URL, text body, or file path
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface Enrollment {
|
||||
userId: string;
|
||||
courseId: string;
|
||||
courseTitle: string;
|
||||
enrolledAt: string;
|
||||
completedAt?: string;
|
||||
}
|
||||
|
||||
interface CourseProgress {
|
||||
courseId: string;
|
||||
userId: string;
|
||||
totalTopics: number;
|
||||
completedTopics: number;
|
||||
totalResources: number;
|
||||
completedResources: number;
|
||||
progressPercentage: number; // 0.0 - 100.0
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Users
|
||||
|
||||
#### Register
|
||||
```
|
||||
POST /api/users/register
|
||||
```
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"name": "Alice",
|
||||
"email": "alice@example.com",
|
||||
"password": "Password1"
|
||||
}
|
||||
```
|
||||
**Validation:** name 2–100 chars, valid email, password ≥8 chars + 1 uppercase + 1 digit
|
||||
**Response:** `201` → `User`
|
||||
**Errors:** `400` validation | `409` email already taken
|
||||
|
||||
---
|
||||
|
||||
#### Login
|
||||
```
|
||||
POST /api/users/login
|
||||
```
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"email": "alice@example.com",
|
||||
"password": "Password1"
|
||||
}
|
||||
```
|
||||
**Response:** `200` → `LoginResponse`
|
||||
**Errors:** `401` wrong credentials
|
||||
|
||||
---
|
||||
|
||||
#### Get User Profile
|
||||
```
|
||||
GET /api/users/{id}
|
||||
```
|
||||
**Response:** `200` → `User` | `404`
|
||||
|
||||
---
|
||||
|
||||
### Courses
|
||||
|
||||
#### List Published Courses
|
||||
```
|
||||
GET /api/courses
|
||||
```
|
||||
Optional query param: `?search=angular`
|
||||
**Response:** `200` → `Course[]` (only Published courses, newest first)
|
||||
|
||||
---
|
||||
|
||||
#### Get Course with Topics & Resources
|
||||
```
|
||||
GET /api/courses/{id}
|
||||
```
|
||||
**Response:** `200` → `CourseDetails` | `404`
|
||||
|
||||
---
|
||||
|
||||
#### Create Course
|
||||
```
|
||||
POST /api/courses
|
||||
```
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"title": "Introduction to Angular",
|
||||
"description": "Learn Angular from scratch",
|
||||
"creatorId": "<userId>"
|
||||
}
|
||||
```
|
||||
**Response:** `201` → `Course` (status = `Draft`)
|
||||
|
||||
---
|
||||
|
||||
#### Update Course
|
||||
```
|
||||
PUT /api/courses/{id}
|
||||
```
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"id": "<courseId>",
|
||||
"title": "Updated Title",
|
||||
"description": "Updated description"
|
||||
}
|
||||
```
|
||||
**Response:** `200` → `Course` | `404`
|
||||
|
||||
---
|
||||
|
||||
#### Publish Course
|
||||
```
|
||||
PATCH /api/courses/{id}/publish
|
||||
```
|
||||
**Response:** `200` → `Course` | `422` if no topics yet | `404`
|
||||
|
||||
---
|
||||
|
||||
#### Delete Course
|
||||
```
|
||||
DELETE /api/courses/{id}
|
||||
```
|
||||
**Response:** `204` | `409` if course has enrollments | `404`
|
||||
|
||||
---
|
||||
|
||||
#### Courses Created by a User
|
||||
```
|
||||
GET /api/users/{userId}/courses
|
||||
```
|
||||
**Response:** `200` → `Course[]`
|
||||
|
||||
---
|
||||
|
||||
### Topics
|
||||
|
||||
#### Get Topic
|
||||
```
|
||||
GET /api/topics/{id}
|
||||
```
|
||||
**Response:** `200` → `Topic` (includes resources ordered by position) | `404`
|
||||
|
||||
---
|
||||
|
||||
#### Create Topic
|
||||
```
|
||||
POST /api/topics
|
||||
```
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"courseId": "<courseId>",
|
||||
"title": "Components",
|
||||
"description": "Optional description",
|
||||
"position": 1
|
||||
}
|
||||
```
|
||||
**Response:** `201` → `Topic`
|
||||
|
||||
---
|
||||
|
||||
#### Update Topic
|
||||
```
|
||||
PUT /api/topics/{id}
|
||||
```
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"id": "<topicId>",
|
||||
"title": "Updated Title",
|
||||
"description": "Updated",
|
||||
"position": 2
|
||||
}
|
||||
```
|
||||
**Response:** `200` → `Topic` | `404`
|
||||
|
||||
---
|
||||
|
||||
#### Delete Topic
|
||||
```
|
||||
DELETE /api/topics/{id}
|
||||
```
|
||||
**Response:** `204` | `404`
|
||||
|
||||
---
|
||||
|
||||
#### Link Resource to Topic
|
||||
```
|
||||
POST /api/topics/{topicId}/resources/{resourceId}
|
||||
```
|
||||
**Body:**
|
||||
```json
|
||||
{ "position": 1 }
|
||||
```
|
||||
**Response:** `204`
|
||||
|
||||
---
|
||||
|
||||
#### Unlink Resource from Topic
|
||||
```
|
||||
DELETE /api/topics/{topicId}/resources/{resourceId}
|
||||
```
|
||||
**Response:** `204`
|
||||
|
||||
---
|
||||
|
||||
### Resources
|
||||
|
||||
#### List All Resources
|
||||
```
|
||||
GET /api/resources
|
||||
```
|
||||
**Response:** `200` → `Resource[]` (ordered by creation date desc)
|
||||
|
||||
---
|
||||
|
||||
#### Get Resource
|
||||
```
|
||||
GET /api/resources/{id}
|
||||
```
|
||||
**Response:** `200` → `Resource` | `404`
|
||||
|
||||
---
|
||||
|
||||
#### Create Resource
|
||||
```
|
||||
POST /api/resources
|
||||
```
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"type": "Url",
|
||||
"title": "Angular Docs",
|
||||
"content": "https://angular.io"
|
||||
}
|
||||
```
|
||||
**Types & content:**
|
||||
| Type | content field |
|
||||
|------|--------------|
|
||||
| `Url` | Full URL (https required) |
|
||||
| `Video` | Video URL (https required) |
|
||||
| `Text` | Markdown or plain text body |
|
||||
| `File` | File path or download URL |
|
||||
|
||||
**Response:** `201` → `Resource`
|
||||
|
||||
---
|
||||
|
||||
#### Update Resource
|
||||
```
|
||||
PUT /api/resources/{id}
|
||||
```
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"id": "<resourceId>",
|
||||
"type": "Video",
|
||||
"title": "Updated",
|
||||
"content": "https://youtube.com/..."
|
||||
}
|
||||
```
|
||||
**Response:** `200` → `Resource` | `404`
|
||||
|
||||
---
|
||||
|
||||
#### Delete Resource
|
||||
```
|
||||
DELETE /api/resources/{id}
|
||||
```
|
||||
**Response:** `204` | `404`
|
||||
|
||||
---
|
||||
|
||||
### Enrollments
|
||||
|
||||
#### Enroll in a Course
|
||||
```
|
||||
POST /api/courses/{courseId}/enroll
|
||||
```
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"userId": "<userId>",
|
||||
"courseId": "<courseId>"
|
||||
}
|
||||
```
|
||||
**Response:** `201` → `Enrollment`
|
||||
**Errors:** `409` already enrolled | `422` course not Published | `404`
|
||||
|
||||
---
|
||||
|
||||
#### User's Enrollments
|
||||
```
|
||||
GET /api/users/{userId}/enrollments
|
||||
```
|
||||
**Response:** `200` → `Enrollment[]`
|
||||
|
||||
---
|
||||
|
||||
### Progress
|
||||
|
||||
#### Mark Topic Progress
|
||||
```
|
||||
POST /api/topics/{topicId}/progress
|
||||
```
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"userId": "<userId>",
|
||||
"topicId": "<topicId>",
|
||||
"completed": true
|
||||
}
|
||||
```
|
||||
**Response:** `204` (upsert — safe to call multiple times)
|
||||
|
||||
---
|
||||
|
||||
#### Mark Resource Progress
|
||||
```
|
||||
POST /api/resources/{resourceId}/progress
|
||||
```
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"userId": "<userId>",
|
||||
"resourceId": "<resourceId>",
|
||||
"completed": true
|
||||
}
|
||||
```
|
||||
**Response:** `204` (upsert)
|
||||
|
||||
---
|
||||
|
||||
#### Get Course Progress
|
||||
```
|
||||
GET /api/courses/{courseId}/progress?userId={userId}
|
||||
```
|
||||
**Response:** `200` → `CourseProgress`
|
||||
```json
|
||||
{
|
||||
"courseId": "...",
|
||||
"userId": "...",
|
||||
"totalTopics": 5,
|
||||
"completedTopics": 3,
|
||||
"totalResources": 10,
|
||||
"completedResources": 7,
|
||||
"progressPercentage": 66.67
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Response Format
|
||||
|
||||
All validation and business errors follow this shape:
|
||||
```json
|
||||
{
|
||||
"statusCode": 400,
|
||||
"errors": {
|
||||
"email": ["'Email' is not a valid email address."],
|
||||
"password": ["Password must be at least 8 characters."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CORS
|
||||
|
||||
All origins, methods, and headers are allowed (`AllowAll` policy). No special headers needed from Angular/Capacitor.
|
||||
|
||||
---
|
||||
|
||||
## Typical User Flow (Angular/Capacitor)
|
||||
|
||||
```
|
||||
1. POST /api/users/register → store userId in Capacitor Preferences
|
||||
2. POST /api/users/login → verify credentials
|
||||
3. GET /api/courses → show published course catalog
|
||||
4. GET /api/courses/{id} → show course detail with topics
|
||||
5. POST /api/courses/{id}/enroll → enroll user
|
||||
6. GET /api/users/{id}/enrollments → show My Courses
|
||||
7. POST /api/topics/{id}/progress → mark topic done
|
||||
8. POST /api/resources/{id}/progress → mark resource done
|
||||
9. GET /api/courses/{id}/progress?userId=... → show progress bar
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes for Angular Service Layer
|
||||
|
||||
- All IDs are **UUIDs (string)** — no integer IDs.
|
||||
- Dates are **ISO 8601 UTC strings** — use `new Date(dateString)` or a pipe.
|
||||
- `progressPercentage` is a `number` (float 0–100), round for display.
|
||||
- `status` and `type` come back as **strings**, not numbers.
|
||||
- No auth headers needed currently — all endpoints are `AllowAnonymous`.
|
||||
Reference in New Issue
Block a user