Skip to content

Commit 90f2eb6

Browse files
committed
backend dot net
1 parent 38b39b8 commit 90f2eb6

44 files changed

Lines changed: 2682 additions & 226 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 34 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,48 @@
11
name: CI Pipeline
2-
32
on:
43
push:
5-
branches: [ "main" ]
4+
branches: [main]
65
pull_request:
7-
branches: [ "main" ]
8-
6+
branches: [main]
97
jobs:
10-
build-and-test:
8+
dotnet-api:
119
runs-on: ubuntu-latest
12-
1310
services:
1411
postgres:
1512
image: postgres:15
1613
env:
17-
POSTGRES_USER: testuser
18-
POSTGRES_PASSWORD: testpassword
19-
POSTGRES_DB: testdb
20-
ports:
21-
- 5434:5432
14+
POSTGRES_USER: lms_test
15+
POSTGRES_PASSWORD: lms_test_password
16+
POSTGRES_DB: postgres
17+
ports: ["5436:5432"]
2218
options: >-
23-
--health-cmd pg_isready
24-
--health-interval 10s
25-
--health-timeout 5s
26-
--health-retries 5
27-
19+
--health-cmd "pg_isready -U lms_test -d postgres"
20+
--health-interval 2s --health-timeout 5s --health-retries 20
2821
env:
29-
DATABASE_URL: postgresql://testuser:testpassword@localhost:5434/testdb
30-
JWT_SECRET: super_secret_dummy_jwt_key_that_is_at_least_32_bytes
31-
NODE_ENV: test
32-
CORS_ORIGIN: http://localhost:3000
33-
RATE_LIMIT_LOGIN_MAX: 3
34-
RATE_LIMIT_REGISTER_MAX: 5
35-
22+
LMS_TEST_ADMIN_CONNECTION: Host=127.0.0.1;Port=5436;Database=postgres;Username=lms_test;Password=lms_test_password
23+
defaults:
24+
run:
25+
working-directory: backend-dotnet
3626
steps:
37-
- name: Checkout repository
38-
uses: actions/checkout@v4
39-
40-
- name: Setup Node.js
41-
uses: actions/setup-node@v4
27+
- uses: actions/checkout@v4
28+
- uses: actions/setup-dotnet@v4
4229
with:
43-
node-version: '20'
44-
cache: 'npm'
45-
cache-dependency-path: backend/package-lock.json
46-
47-
- name: Install Dependencies
48-
working-directory: ./backend
49-
run: npm ci
50-
51-
- name: Generate Prisma Client
52-
working-directory: ./backend
53-
run: npx prisma generate
54-
55-
- name: Run Prisma Migrations
56-
working-directory: ./backend
57-
run: npx prisma migrate deploy
58-
59-
- name: Run Linter
60-
working-directory: ./backend
61-
run: npm run lint
62-
63-
- name: Run Tests
64-
working-directory: ./backend
65-
run: npm run test:ci
66-
67-
# Advisory only. Two high-severity findings (deepmerge-ts, mysql2) are
68-
# transitive dependencies of the Prisma CLI. Neither is reachable at
69-
# runtime — this project uses Postgres, so mysql2 never loads, and
70-
# deepmerge-ts is build-time only. The only available fix is a breaking
71-
# downgrade to Prisma 6. Revisit once Prisma updates its dependencies.
72-
- name: Run Audit
73-
working-directory: ./backend
74-
continue-on-error: true
75-
run: npm audit --audit-level=high --omit=dev
30+
dotnet-version: 10.0.x
31+
- run: dotnet restore Lms.Api.Tests/Lms.Api.Tests.csproj
32+
- run: dotnet build Lms.Api.Tests/Lms.Api.Tests.csproj --no-restore --configuration Release
33+
- run: dotnet test Lms.Api.Tests/Lms.Api.Tests.csproj --no-build --configuration Release
34+
- run: dotnet publish Lms.Api/Lms.Api.csproj --no-restore --configuration Release --output publish
35+
react:
36+
runs-on: ubuntu-latest
37+
defaults:
38+
run:
39+
working-directory: frontend
40+
steps:
41+
- uses: actions/checkout@v4
42+
- uses: actions/setup-node@v4
43+
with:
44+
node-version: '22'
45+
cache: npm
46+
cache-dependency-path: frontend/package-lock.json
47+
- run: npm ci
48+
- run: npm run build

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ database/*.sqlite
99
task.md
1010
implementation_plan.md
1111
walkthrough.md
12+
**/bin/
13+
**/obj/
14+
**/TestResults/
15+
frontend/dist/

README.md

Lines changed: 39 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,166 +1,58 @@
1-
# Mini LMS (Learning Management System) Backend
2-
3-
[![CI Pipeline](https://github.com/PiyushInt/learning-management-system-fullstack/actions/workflows/ci.yml/badge.svg)](https://github.com/PiyushInt/learning-management-system-fullstack/actions/workflows/ci.yml)
4-
5-
An advanced, resilient, and secure RESTful API for a Learning Management System designed to handle core educational workflows: course creation, student enrollment, assignment distribution, and grade-less submission tracking.
6-
7-
## Overview
8-
This backend powers a multi-tenant learning environment where Teachers manage courses and assignments, and Students enroll in those courses and submit their work. It heavily emphasizes security boundaries, rate-limiting resilience, strict data consistency logic, and robust automated testing utilizing modern Node.js backend practices.
9-
10-
## Tech Stack
11-
* **Language & Runtime:** Node.js (v20+), ES Modules
12-
* **Framework:** Express.js (v5)
13-
* **Database & ORM:** PostgreSQL (v15), Prisma ORM
14-
* **Security & Hardening:** Helmet, express-rate-limit, CORS, bcrypt, Joi
15-
* **Observability:** Winston (Structured logging, Recursive redaction)
16-
* **Testing:** Jest, Supertest
17-
* **CI/CD:** GitHub Actions (Clean-clone DB migration tests, Linting, Audit)
18-
19-
## Architecture
20-
21-
```mermaid
22-
graph TD
23-
Client[Client App / Postman] --> |HTTP / JSON| API[Express API Gateway]
24-
25-
subgraph Express Backend
26-
API --> RateLimit[Rate Limiter]
27-
RateLimit --> Auth[Auth Middleware]
28-
Auth --> Validation[Joi Boundary Validation]
29-
Validation --> Controllers[Controllers]
30-
Controllers --> CoreAuth[Core Authorization]
31-
CoreAuth --> Services[Domain Services]
32-
end
33-
34-
Services --> |Prisma Client| DB[(PostgreSQL)]
35-
36-
subgraph Observability
37-
Controllers -.-> Logger[Winston Logger]
38-
Services -.-> Logger
39-
Logger -.-> |Redacts PII/Tokens| LogOutput[Stdout/JSON]
40-
end
41-
```
1+
# Learning Management System
422

43-
## Project Structure
44-
```text
45-
backend/
46-
├── src/
47-
│ ├── app.js # Express app configuration & hardening
48-
│ ├── server.js # Entry point & Graceful shutdown
49-
│ ├── config/ # Centralized environment variable parsing
50-
│ ├── controllers/ # HTTP request/response handlers
51-
│ ├── core/ # Core domain logic (authorization.js)
52-
│ ├── middlewares/ # Joi validation, error handling, logging, JWT auth
53-
│ ├── routes/ # Route definitions
54-
│ ├── services/ # Business logic and Prisma DB interactions
55-
│ ├── utils/ # Helpers (e.g., JWT signing/verification)
56-
│ └── validations/ # Joi schema definitions
57-
├── tests/ # Jest integration test suite & fixtures
58-
├── prisma/ # Prisma schema and migrations
59-
├── .env.example # Template for environment variables
60-
├── package.json # NPM dependencies & scripts
61-
└── eslint.config.js # Linter configuration
62-
.github/workflows/ # CI/CD pipelines
63-
```
3+
A React application backed by a C# ASP.NET Core API and PostgreSQL. Teachers create courses and assignments; students enroll and submit work.
644

65-
## Getting Started
5+
## Stack
666

67-
### Prerequisites
68-
* Node.js (v20+)
69-
* Docker & Docker Compose (for the local test database)
70-
* PostgreSQL (if running locally without Docker)
7+
- Frontend: React, Vite, Redux Toolkit, Axios
8+
- Backend: .NET 10, ASP.NET Core controllers, Entity Framework Core, Npgsql
9+
- Database: PostgreSQL 15+ (the existing Prisma table structure is preserved)
10+
- Authentication: bcrypt passwords and HS256 JWTs with teacher/student authorization
11+
- Tests: xUnit integration tests against isolated PostgreSQL databases
7112

72-
### Environment Setup
73-
Create a `.env` file in the `backend/` directory:
13+
## Structure
7414

75-
```env
76-
PORT=3000
77-
DATABASE_URL="postgresql://user:password@localhost:5432/dbname"
78-
JWT_SECRET="replace_with_a_secure_random_string"
79-
JWT_EXPIRES_IN="1d"
15+
```text
16+
frontend/ Existing React application
17+
backend-dotnet/Lms.Api/ C# API, entities, services, EF migrations
18+
backend-dotnet/Lms.Api.Tests/ API and database adoption tests
19+
backend/ Legacy Express API and original Prisma migrations
20+
start_app.sh Starts React and .NET; supports Express rollback
8021
```
81-
*(Note: A test database configuration is automatically managed via the `run-tests.sh` script when running `npm test`.)*
8222

83-
### Installation & Execution
23+
## Run with a fresh local database
24+
25+
Install .NET 10 SDK, Node.js 22.12+ and Docker Compose (or use an existing PostgreSQL server).
26+
8427
```bash
85-
cd backend/
28+
cp backend-dotnet/.env.example backend-dotnet/.env
29+
# Edit backend-dotnet/.env and replace the JWT secret.
30+
docker compose -f backend-dotnet/compose.yaml up -d --wait db
31+
bash backend-dotnet/run.sh --migrate
32+
bash start_app.sh
33+
```
8634

87-
# Install dependencies using ci for exact lockfile match
88-
npm ci
35+
React uses port 5173 and proxies `/api` to the .NET API at port 5050. Schema migrations are explicit commands; starting the API never recreates tables or applies migrations.
8936

90-
# Generate the Prisma Client
91-
npm run prisma:generate
37+
**Existing LMS database:** follow the backup, inspection, and adoption procedure in [the migration guide](backend-dotnet/README.md#existing-prisma-database). Do not initialize or reset it as a fresh database.
9238

93-
# Apply database migrations
94-
npm run prisma:migrate
39+
## Test
9540

96-
# Start the server (Development)
97-
npm run dev
41+
```bash
42+
bash backend-dotnet/run-tests.sh
43+
cd frontend
44+
npm ci
45+
npm run build
9846
```
9947

100-
## API Reference
101-
102-
### Authentication
103-
| Method | Endpoint | Description | Auth Required |
104-
| :--- | :--- | :--- | :--- |
105-
| `POST` | `/auth/register` | Register a new user (Teacher/Student) | No |
106-
| `POST` | `/auth/login` | Authenticate and receive a JWT | No |
107-
108-
### Courses & Enrollments
109-
| Method | Endpoint | Description | Auth Required (Role) |
110-
| :--- | :--- | :--- | :--- |
111-
| `GET` | `/courses` | List all available courses | No |
112-
| `POST` | `/courses` | Create a new course | Yes (TEACHER) |
113-
| `GET` | `/courses/enrolled` | List courses the student is enrolled in | Yes (STUDENT) |
114-
| `POST` | `/courses/:id/enroll` | Enroll the current student into a course | Yes (STUDENT) |
115-
| `GET` | `/courses/:id/assignments` | List assignments for a course | Yes (Enrolled STUDENT / Owning TEACHER) |
116-
| `POST` | `/courses/:id/assignments` | Create a new assignment for a course | Yes (Owning TEACHER) |
117-
118-
### Assignments & Submissions
119-
| Method | Endpoint | Description | Auth Required (Role) |
120-
| :--- | :--- | :--- | :--- |
121-
| `POST` | `/assignments/:id/submit` | Submit work for an assignment | Yes (Enrolled STUDENT) |
122-
| `GET` | `/assignments/:id/submissions` | List all submissions for an assignment | Yes (Owning TEACHER) |
123-
124-
## Security
125-
126-
This system implements a strict defense-in-depth security architecture with a clear separation of concerns:
127-
128-
1. **Role Checks vs. Ownership Checks**:
129-
- **Role Checks** are performed early at the HTTP boundary via the JWT middleware (e.g., verifying a user is a `TEACHER` before allowing access to course creation).
130-
- **Ownership & Domain Guards** live deeper in the stack within `src/core/authorization.js`. This module strictly handles data-level authorization, ensuring that a standard Prisma query always verifies a Teacher actually owns the course they are modifying, or a Student is genuinely enrolled in the course they are interacting with.
131-
132-
2. **Route Coverage Matrix**:
133-
134-
| Route | Role Check (Middleware) | Ownership / Domain Check (`authorization.js`) |
135-
| :--- | :--- | :--- |
136-
| `/auth/register` | None | None |
137-
| `/auth/login` | None | None |
138-
| `GET /courses` | None | None |
139-
| `POST /courses` | `TEACHER` | None |
140-
| `GET /courses/enrolled` | `STUDENT` | None |
141-
| `POST /courses/:id/enroll` | `STUDENT` | Implicit (creates enrollment) |
142-
| `GET /courses/:id/assignments` | `STUDENT` or `TEACHER` | `assertEnrolled` (Student) OR `assertOwnsCourse` (Teacher) |
143-
| `POST /courses/:id/assignments`| `TEACHER` | `assertOwnsCourse` |
144-
| `POST /assignments/:id/submit` | `STUDENT` | `assertEnrolled` (via Course) |
145-
| `GET /assignments/:id/submissions`| `TEACHER`| `assertOwnsCourse` (via Course) |
146-
147-
3. **Strict Boundary Validation**: All incoming requests pass through a global `validateBody` middleware using strict Joi schemas, dropping malformed payloads before they reach business logic.
148-
4. **Recursive Log Redaction**: The Winston logger utilizes a recursive object traversal function to guarantee PII (`password`, `token`, etc.) is redacted (`[REDACTED]`), regardless of nesting depth.
149-
150-
## Testing
151-
The repository maintains strict integration test coverage spanning the full API boundary, database constraint validation, rate limiting, and log redaction.
48+
Tests use disposable, uniquely named PostgreSQL databases. An external local test server can be selected using `LMS_TEST_ADMIN_CONNECTION`.
49+
50+
## Roll back to Express
15251

15352
```bash
154-
# Run the full test suite (automatically spins up a clean Postgres container on port 5434)
155-
npm test
53+
LMS_BACKEND=node bash start_app.sh
15654
```
15755

158-
## Design Decisions
159-
- **Hard Delete cascades**: `onDelete: Cascade` rules were implemented strictly across the schema. A course serves as a container; deleting a course destroys its assignments and submissions.
160-
- **UUIDs and API Coercion**: A dedicated integer coercion middleware shields the database from Prisma's `NaN` panics.
161-
- **Graceful Shutdown & Resilience**: The application leverages `SIGTERM`/`SIGINT` traps and explicitly delays startup until the database emits a successful connection ping.
162-
- **Audit Exception (`deepmerge-ts`, `mysql2`)**: The `npm audit` step currently returns advisory warnings for high-severity findings related to `deepmerge-ts` and `mysql2`. These are transitive dependencies of the Prisma CLI. Since this project strictly uses PostgreSQL, the `mysql2` vulnerability is unreachable at runtime. Resolving these warnings would require a breaking downgrade of Prisma. The audit step remains advisory (non-blocking) as no runtime risk is present.
56+
This selects the old API on port 3000 and changes the development proxy. Retain the same database and JWT secret during the compatibility window; do not run Prisma migrations after transferring migration ownership to EF Core.
16357

164-
## Limitations
165-
- **No Frontend**: This repository is strictly a backend API. The GitHub Actions CI pipeline verifies backend linting, database migrations from scratch, and integration tests, but intentionally does not build or assert UI artifacts.
166-
- **In-Memory Rate Limiting**: The `express-rate-limit` configuration uses the default memory store. In a multi-node production deployment, this should be migrated to a Redis store.
58+
See [backend setup, API contract, and deployment](backend-dotnet/README.md) for details.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"version": 1,
3+
"isRoot": true,
4+
"tools": {
5+
"dotnet-ef": {
6+
"version": "10.0.12",
7+
"commands": ["dotnet-ef"]
8+
}
9+
}
10+
}

backend-dotnet/.env.example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
ASPNETCORE_ENVIRONMENT=Development
2+
ASPNETCORE_URLS=http://localhost:5050
3+
DATABASE_URL=postgresql://lms:lms_local_password@localhost:5435/lms
4+
JWT_SECRET=replace_with_a_random_secret_at_least_32_bytes_long
5+
JWT_EXPIRES_IN=1d
6+
CORS_ORIGIN=http://localhost:5173
7+
RATE_LIMIT_LOGIN_MAX=5
8+
RATE_LIMIT_REGISTER_MAX=10
9+
# Set only when deployed behind a known reverse proxy (comma-separated IPs):
10+
# TRUSTED_PROXIES=127.0.0.1

0 commit comments

Comments
 (0)