diff --git a/README.md b/README.md index f12aecd..de3c8e6 100644 --- a/README.md +++ b/README.md @@ -1,121 +1,145 @@ + # LMS App Server -Django + DRF backend for the LMS platform. +[![Django](https://img.shields.io/badge/Django-5.2-092e20?logo=django)](https://www.djangoproject.com/) +[![DRF](https://img.shields.io/badge/DRF-3.15-a30000?logo=django)](https://www.django-rest-framework.org/) +[![Python](https://img.shields.io/badge/Python-3.10+-3776ab?logo=python)](https://www.python.org/) +[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-16-336791?logo=postgresql)](https://www.postgresql.org/) +[![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +[![OpenAPI](https://img.shields.io/badge/OpenAPI-3.0-6ba539?logo=openapi-initiative)](openapi.yaml) +[![GitHub Repo](https://img.shields.io/badge/GitHub-PsymoNiko%2Flms_api-181717?logo=github)](https://github.com/PsymoNiko/lms_api) -## Tech Stack +Backend for the LMS platform built with **Django 5.2** + **Django REST Framework** + **JWT** + **WebSockets** (Django Channels). +Handles user management, course enrolment, messaging, notifications, and live chat. -- Django 5.2 -- Django REST Framework -- JWT auth (`djangorestframework-simplejwt`) -- Django Channels (WebSocket chat + notifications) -- PostgreSQL -- S3-compatible storage via `django-storages` + `boto3` +--- -## Apps +## 🧱 Tech Stack -- `accounts`: auth, registration, profile, user management, site-owner overview -- `courses`: course tree, enrollment, grades, messaging, notifications, websocket chat +- **Django 5.2** & **Django REST Framework** +- **JWT** (`djangorestframework-simplejwt`) +- **Django Channels** (WebSocket chat + notifications) +- **PostgreSQL** (primary database) +- **S3‑compatible storage** (`django-storages` + `boto3`) +- **Docker Compose** (for local Postgres + RustFS) -## Quick Start +--- -### 1) Install dependencies +## 🚀 Quick Start +### 1. Clone & environment ```bash python -m venv .venv -source .venv/bin/activate +source .venv/bin/activate # or .venv\Scripts\activate on Windows pip install -r requirements.txt ``` -### 2) Start infrastructure (Postgres + RustFS) +2. Start services with Docker ```bash docker compose up -d ``` -### 3) Run migrations +3. Run migrations ```bash python manage.py migrate ``` -### 4) Run server +4. Run the server ```bash python manage.py runserver ``` -Server default: `http://127.0.0.1:8000` +Server will be available at http://127.0.0.1:8000 + +--- -## Seed Demo Data +🧪 Seed demo data -Populate rich demo users/courses/messages/grades/newsletter: +Populate the database with rich demo users, courses, messages, grades, and newsletter entries: ```bash python manage.py seed_lms ``` -Reset and re-seed: +Reset and re‑seed: ```bash python manage.py seed_lms --clear ``` -Seed password for all generated users: `seedpass123` +All seeded users have the password: seedpass123 + +--- + +📚 API Documentation + +The full REST API is described in openapi.yaml (OpenAPI 3.0). +All routes are prefixed with /api/v1/. + +🔓 Public endpoints + +· GET /courses/ – list courses +· GET /courses/{id}/ – course outline (anonymous access) +· GET /stats/ – platform statistics +· POST /newsletter/ – subscribe to newsletter +· POST /login/ – obtain JWT pair +· POST /register/ – new user registration +· POST /auth/register/ (alias) +· POST /refresh-token/ – refresh access token -## API Base +🔐 Authenticated endpoints -All REST routes are under: +· GET /me/ – current user profile + role flags +· GET /admin/overview/ – platform admin overview +· GET /enrollments/ – my enrolled courses +· POST /courses/{id}/enroll/ – enrol into a course +· GET /messages/conversations/ – list my conversations +· GET /notifications/ – unread notifications -- `/api/v1/` +💬 WebSocket chat -### Public endpoints +Endpoint: ws://127.0.0.1:8000/ws/chat/?token= -- `GET /api/v1/courses/` -- `GET /api/v1/courses/{id}/` (outline for anonymous users) -- `GET /api/v1/stats/` -- `POST /api/v1/newsletter/` -- `POST /api/v1/login/` -- `POST /api/v1/register/` -- `POST /api/v1/auth/register/` -- `POST /api/v1/refresh-token/` +Supports: -### Authenticated highlights +· sending / receiving messages +· typing indicators +· read receipts +· real‑time notification pushes -- `GET /api/v1/me/` -- `GET /api/v1/admin/overview/` (platform admin only) -- `GET /api/v1/enrollments/` -- `POST /api/v1/courses/{id}/enroll/` -- `GET /api/v1/messages/conversations/` -- `GET /api/v1/notifications/` +--- -## WebSocket Chat +👥 Roles -Endpoint: +· student – can browse courses, enrol, participate in chat +· instructor – can manage their courses (admin UI / future endpoints) +· admin – full platform oversight (e.g. /admin/overview/) -- `ws://127.0.0.1:8000/ws/chat/?token=` +Check GET /api/v1/me/ for frontend feature gating. -Supports message send, typing events, read receipts, and live notification push. +--- -## Roles +⚠️ Important notes -- `student` -- `instructor` -- `admin` +· core/settings.py currently uses development defaults (DEBUG=True, hardcoded DB credentials). + Switch to environment variables before production! +· Channel layer is in‑memory. Use Redis for production. +· Browsable API is enabled only when DEBUG=True. -Use `GET /api/v1/me/` to read access flags for frontend feature gating. +--- -## Important Notes +📁 Documentation -- `core/settings.py` currently contains development defaults (including `DEBUG=True` and sample DB credentials). Move to environment-based config before production. -- Channel layer is in-memory by default. For production, switch to Redis channel layer. -- Browsable API is enabled only when `DEBUG=True`. +Detailed contracts and checklists are in the docs/ folder: -## Documentation +· backend-api-contract-fa.md +· lms-api-contract.md +· frontend-websocket-chat-fa.md +· server-api-checklist.md -See detailed contracts and checklists in `docs/`: -- `docs/backend-api-contract-fa.md` -- `docs/lms-api-contract.md` -- `docs/frontend-websocket-chat-fa.md` -- `docs/server-api-checklist.md` +! \ No newline at end of file diff --git a/server.md b/STRUCTURE.md similarity index 100% rename from server.md rename to STRUCTURE.md diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000..8284eee --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,323 @@ +openapi: 3.0.3 +: Local development server + - url: https://your-production-domain.com/api/v1 + description: Production server (override when deployed) + +security: + - bearerAuth: [] + +paths: + /login/: + post: + summary: Obtain JWT access & refresh tokens + tags: [Authentication] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [email, password] + properties: + email: { type: string, format: email } + password: { type: string, format: password } + responses: + '200': + description: Tokens obtained + content: + application/json: + schema: + type: object + properties: + access: { type: string } + refresh: { type: string } + '401': { description: Invalid credentials } + + /register/: + post: + summary: Register a new user + tags: [Authentication] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [email, password, first_name, last_name] + properties: + email: { type: string, format: email } + password: { type: string, format: password } + first_name: { type: string } + last_name: { type: string } + responses: + '201': { description: User created } + '400': { description: Validation error } + + /auth/register/: + post: + $ref: '#/paths/~1register~/post' + + /refresh-token/: + post: + summary: Refresh access token + tags: [Authentication] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [refresh] + properties: + refresh: { type: string } + responses: + '200': + description: New access token + content: + application/json: + schema: + type: object + properties: + access: { type: string } + + /courses/: + get: + summary: List all courses (public) + tags: [Courses] + responses: + '200': + description: List of courses + content: + application/json: + schema: + type: array + items: { $ref: '#/components/schemas/Course' } + + /courses/{id}/: + get: + summary: Course outline (public) + tags: [Courses] + parameters: + - name: id + in: path + required: true + schema: { type: integer } + responses: + '200': + description: Course details + content: + application/json: + schema: { $ref: '#/components/schemas/CourseDetail' } + '404': { description: Not found } + + /stats/: + get: + summary: Platform statistics (public) + tags: [Stats] + responses: + '200': + description: Statistics object + content: + application/json: + schema: { $ref: '#/components/schemas/Stats' } + + /newsletter/: + post: + summary: Subscribe to newsletter + tags: [Newsletter] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [email] + properties: + email: { type: string, format: email } + responses: + '201': { description: Subscribed } + '400': { description: Invalid email or already subscribed } + + /me/: + get: + summary: Current user profile (authenticated) + tags: [Users] + security: [{ bearerAuth: [] }] + responses: + '200': + description: User details + role flags + content: + application/json: + schema: { $ref: '#/components/schemas/UserMe' } + '401': { description: Not authenticated } + + /admin/overview/: + get: + summary: Platform admin overview + tags: [Admin] + security: [{ bearerAuth: [] }] + responses: + '200': + description: Admin dashboard data + content: + application/json: + schema: { $ref: '#/components/schemas/AdminOverview' } + '403': { description: Admin only } + + /enrollments/: + get: + summary: My enrolled courses + tags: [Enrollments] + security: [{ bearerAuth: [] }] + responses: + '200': + description: List of enrollments + content: + application/json: + schema: + type: array + items: { $ref: '#/components/schemas/Enrollment' } + + /courses/{id}/enroll/: + post: + summary: Enrol into a course + tags: [Enrollments] + security: [{ bearerAuth: [] }] + parameters: + - name: id + in: path + required: true + schema: { type: integer } + responses: + '201': { description: Enrolled successfully } + '400': { description: Already enrolled or course invalid } + '401': { description: Not authenticated } + + /messages/conversations/: + get: + summary: List my conversations + tags: [Messaging] + security: [{ bearerAuth: [] }] + responses: + '200': + description: Conversations + content: + application/json: + schema: + type: array + items: { $ref: '#/components/schemas/Conversation' } + + /notifications/: + get: + summary: Get unread notifications + tags: [Notifications] + security: [{ bearerAuth: [] }] + responses: + '200': + description: List of notifications + content: + application/json: + schema: + type: array + items: { $ref: '#/components/schemas/Notification' } + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + + schemas: + User: + type: object + properties: + id: { type: integer } + email: { type: string, format: email } + first_name: { type: string } + last_name: { type: string } + role: { type: string, enum: [student, instructor, admin] } + + UserMe: + allOf: + - $ref: '#/components/schemas/User' + - type: object + properties: + is_student: { type: boolean } + is_instructor: { type: boolean } + is_admin: { type: boolean } + + Course: + type: object + properties: + id: { type: integer } + title: { type: string } + description: { type: string } + instructor: { $ref: '#/components/schemas/User' } + + CourseDetail: + allOf: + - $ref: '#/components/schemas/Course' + - type: object + properties: + syllabus: { type: string } + enrolled_users_count: { type: integer } + + Stats: + type: object + properties: + total_courses: { type: integer } + total_students: { type: integer } + total_instructors: { type: integer } + active_enrollments: { type: integer } + + Enrollment: + type: object + properties: + id: { type: integer } + user_id: { type: integer } + course: { $ref: '#/components/schemas/Course' } + enrolled_at: { type: string, format: date-time } + status: { type: string, enum: [active, completed, dropped] } + + Conversation: + type: object + properties: + id: { type: integer } + participants: { type: array, items: { $ref: '#/components/schemas/User' } } + last_message: { type: string } + updated_at: { type: string, format: date-time } + + Message: + type: object + properties: + id: { type: integer } + conversation: { type: integer } + sender: { $ref: '#/components/schemas/User' } + content: { type: string } + created_at: { type: string, format: date-time } + is_read: { type: boolean } + + Notification: + type: object + properties: + id: { type: integer } + type: { type: string } + content: { type: string } + is_read: { type: boolean } + created_at: { type: string, format: date-time } + + AdminOverview: + type: object + properties: + total_users: { type: integer } + total_courses: { type: integer } + total_enrollments: { type: integer } + recent_activity: { type: array, items: { type: object } } + + # WebSocket documentation (informational) + x-webSocket: + description: | + Live chat and notifications are available via WebSocket at: + `ws://127.0.0.1:8000/ws/chat/?token=` + Supports message sending, typing events, read receipts, and notifications.