Skip to content
Marcoh05PPublic

About

University LMS with a course-scoped RAG study assistant. .NET 10, Angular 22 and FastAPI, Docker Compose, YARP gateway, ChromaDB, streaming WebSocket answers with page citations.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

EdOU Logo

EdOU — Developing an LMS Learning Assistant Using RAG

Graduation project - Computer Science - Faculty of Information Technology - Ho Chi Minh City Open University

.NET 10 Angular 22 FastAPI SQL Server 2022 Docker Compose MIT


Project information

Thesis title (Vietnamese): Phát triển Trợ lý hỗ trợ học tập trên LMS sử dụng RAG

Thesis title (English): Developing an LMS Learning Assistant Using RAG

System name EdOU — Education Open University
Demo site edou.id.vn. It runs on the author's own computer, so it is not online all the time.
Student Hoàng Phi Hùng — 2351010082
Major / Faculty Computer Science / Information Technology
Project type Graduation project
Languages and frameworks .NET 10, Angular 22, Python FastAPI
Infrastructure Docker Compose, Cloudflare Tunnel, Cloudflare DNS (the domain is registered at Mắt Bão)

Introduction

Today, most learning management systems (LMS) only share documents. Teachers upload lessons, and students download them and study alone. Many schools now want to use large language models (LLM) to help students. But there is a common problem: the model does not know the school's own materials. So it can give wrong answers, or it can invent information that is not in the lesson.

EdOU is built to solve this problem. It has the normal school features: semesters, subjects, courses, sections and lessons. It also has an assistant that answers questions from the course documents only. The system uses a technique called RAG (Retrieval-Augmented Generation). It reads PDF files, cuts them into small parts and turns them into vectors. When a student asks a question, the system finds the most relevant parts and sends them to the model. The answer also shows the page number, so the student can check it.

Some technical points:

  • Vector data is separated by course (course_id). So the assistant of one course cannot use documents from another course.
  • The answer is sent to the browser little by little through WebSocket. Users do not have to wait a long time.
  • The .NET backend stays behind an API Gateway built with YARP. Behind the gateway there are the main API and an AI microservice built with FastAPI.
  • The whole system runs in Docker Compose and goes to the Internet through a Cloudflare Tunnel. We do not need to open any port on the server.

Main features

The system has three roles:

  • Administrator: manages user accounts, semesters, subjects, courses and student enrollments.
  • Instructor: creates content for the courses he or she teaches, such as sections, lessons, forums and assessments. Instructors also approve enrollments and grade student work.
  • Student: studies lessons, joins forum discussions, submits work and asks the AI assistant of the course.

A course is divided into sections, and each section has modules. There are four types of modules:

  • Document lesson: a PDF file that students can read in the browser.
  • Media lesson: video or audio.
  • Discussion forum: topics and posts in two levels, with pin, lock, show/hide and file attachments.
  • Assessment: essay or quiz, with a pass score, a maximum number of attempts and a due date.

The AI assistant works like this. First, the PDF of a lesson is read and cut into chunks, page by page. Then Ollama (qwen3-embedding:0.6b) turns the chunks into vectors, and the vectors are saved in ChromaDB. When a student asks a question, the system looks for the closest chunks inside that course and lets DeepSeek write the answer. The answer comes back token by token.

For security, the system uses JWT (HMAC-SHA256) for login and for role-based access. The gateway removes the headers X-User-Id and X-User-Role that the client sends, and then creates them again from the verified claims. This stops a user from pretending to have another role. The API also has a rate limit of 20 requests per 60 seconds.


System architecture

The diagram below shows the main parts of the system and the path of a request:

graph TD
    UserClient["Client browser<br/>(Angular 22 SPA :4200)"]
    Gateway["YARP reverse proxy<br/>(:5000 in Docker / :7266 local HTTPS)"]

    subgraph "Backend services"
        DotNetAPI["EdOU Core API (.NET 10)<br/>(:5001 / :80)"]
        SQLServer[("Microsoft SQL Server 2022<br/>(:1433)")]
        Cloudinary[("Cloudinary cloud storage<br/>(media / PDFs / avatars)")]
    end

    subgraph "AI and RAG microservice"
        FastAPI["EdOU RAG service (FastAPI)<br/>(:8000, internal)"]
        ChromaDB[("ChromaDB vector store<br/>(:8001)")]
        Ollama[("Ollama embeddings<br/>(:11434 - qwen3-embedding:0.6b)")]
        DeepSeek["DeepSeek LLM API<br/>(deepseek-chat)"]
    end

    UserClient -->|"/api/auth/*, /api/courses/*..."| Gateway
    UserClient -->|"/api/rag/chat, /api/rag/ws/*"| Gateway

    Gateway -->|"Route /api/* (adds X-User-Id / Role)"| DotNetAPI
    Gateway -->|"Route /api/rag/* (removes the /api/rag prefix)"| FastAPI

    DotNetAPI -->|"EF Core 10 (CRUD / migrations)"| SQLServer
    DotNetAPI -->|"Uploads / deletes files"| Cloudinary
    DotNetAPI -->|"Starts document loading (IRagServiceClient)"| FastAPI

    FastAPI -->|"Stores / searches vectors"| ChromaDB
    FastAPI -->|"Creates embeddings"| Ollama
    FastAPI -->|"Writes the answer and streams it"| DeepSeek
    FastAPI -->|"Downloads the PDF and cuts it into chunks"| Cloudinary
Loading

How a request goes through the gateway

  1. /api/auth/login and /api/auth/register/** are public. They go straight to EdOU.API and they do not need a token.
  2. For all other /api/** routes, the gateway checks the JWT signature and expiry. If the token is valid, the gateway deletes the headers X-User-Id and X-User-Role that come from the client. Then it creates them again from the claims in the token. So a client cannot choose its own role.
  3. Routes that start with /api/rag/** also need a JWT. After that, the gateway removes the /api/rag prefix and forwards the request to the FastAPI microservice.
  4. For WebSocket connections at /api/rag/ws/**, the browser cannot send an Authorization header. So the gateway takes the token from the query string ?access_token=<JWT>.

The Nginx container of the frontend also serves the SPA and forwards /api/ to the gateway.


Technologies

Layer Technology Notes
Backend .NET 10, ASP.NET Core 10 Web API, dependency injection, runs migrations at startup
EF Core 10.0.10 Code-first, TPT inheritance for User, Module and Submission
SQL Server 2022 Relational database
YARP 2.3.0 Reverse proxy; checks the JWT at the edge
IdentityModel JWT 8.14.0 Creates and checks tokens (HMAC-SHA256)
CloudinaryDotNet 1.29.2 Stores PDF files, media and avatars
Frontend Angular 22.1 Standalone components, signals, no zone.js
Angular Material 22.1 Data tables, dialogs, forms, snackbars
RxJS 7.8 HTTP calls and interceptors
AI / RAG FastAPI 0.115 on Python 3.13 Loads documents and streams the answer
ChromaDB 0.5+ Stores vectors and filters by course_id
Ollama, qwen3-embedding:0.6b Creates embeddings on the local machine
DeepSeek deepseek-chat Writes the answer
pdfplumber, LangChain text splitters Reads PDF files and cuts the text into chunks
Testing pytest 9.1.1, Selenium 4.48 RBAC, rate limit, TTFT
Deployment Docker Compose 8 containers on the bridge network edou-network
Cloudflare Tunnel Publishes the system without opening ports on the server

Screenshots

Home page

Home page


Login

Login

Student and instructor registration

Student registration Instructor registration
Student registration Instructor registration

Admin dashboard

Admin dashboard

User management

User management

Semester management

Semester management

Subject management

Subject management

Course management

Course management

Student enrollment management

Enrollment management


Instructor area

Semester list

Semester list

Course list

Course list

Media lesson detail

Lesson detail

Topic detail

Topic detail

Discussion forum

Discussion forum

Assessment detail

Assessment detail


Repository structure

EdOU/
├── Backend/                    # .NET 10 solution
│   ├── EdOU.API/               # ASP.NET Core Web API (controllers, middleware, DI)
│   ├── EdOU.ApiGateway/        # YARP reverse proxy and gateway authentication
│   ├── EdOU.BLL/               # Business layer (services, strategy handlers, RAG client)
│   ├── EdOU.Common/            # Constants, DTOs, enums, exceptions
│   └── EdOU.DAL/               # EF Core (DbContext, entities, repositories, migrations)
├── Frontend/edou-client/       # Angular 22 SPA (features, layouts, shared)
├── Microservices/edou-fastapi/ # RAG microservice (FastAPI, ChromaDB, Ollama, DeepSeek)
├── Test/                       # pytest suites (rag_ws, rate_limit_api, rbac)
├── Document/                   # Documents, logo and screenshots
└── docker-compose.yml          # Runs the 8 containers

Installation and running

Requirements

  • Docker and Docker Compose v2.
  • A .env file created from .env.example. You need to set these values:
    • SQLSERVER_SA_PASSWORD: the SA password of SQL Server.
    • DEEPSEEK_API_KEY: the API key used to write the answers.
    • CHAT_PROMPT_TEMPLATE: a prompt template that contains {context} and {question}. If it is empty, the AI chat shows an error.
    • CLOUDFLARE_TUNNEL_TOKEN: only needed when you run the cloudflared service. This value has no default.
  • The backend reads the Jwt and Cloudinary settings from appsettings.json. This file is in .gitignore, so you have to create it from appsettings.example.json.

Run the whole system

docker compose up --build

After the system starts:

Service Address
Online demo https://edou.id.vn
API Gateway http://localhost:5000
Main API http://localhost:5001
SQL Server 2022 localhost:1433
ChromaDB localhost:8001
Ollama localhost:11434
RAG microservice localhost:8000 (only on loopback)
Cloudflare Tunnel Connects out only; no inbound port

The system is still online at edou.id.vn, but it runs on the author's personal computer, so it is not on 24/7. If you want to see the demo, please contact the author first so he can turn the computer on.

When you work on the frontend, you can run it with ng serve (port 4200) or use the Nginx container. The proxy configuration is in Frontend/edou-client/proxy.conf.json.


Testing

There are three test suites in the Test/ folder:

Suite What it checks
rbac/ Permissions: it calls the API with a student token, checks the Angular route guard and tests the login flow with Selenium
rate_limit_api/ Rate limit: it sends 30 requests at the same time and checks that the quota comes back after 60 seconds
rag_ws/ AI assistant speed: it measures the time to the first token (TTFT) for 20 questions
cd Test
pip install -r requirements.txt
# each suite has its own config.py, so run them one by one
pytest rbac
pytest rate_limit_api
pytest rag_ws

The tests need the system to be running: the backend at https://localhost:7028 and the frontend at http://localhost:4200. The sample accounts are admin@ou.edu.vn / P@ssw0rd, student1@ou.edu.vn and instructor1@ou.edu.vn / 12345678. The rag_ws suite also needs CHAT_PROMPT_TEMPLATE, and it writes the result to Test/rag_ws/reports/ttft_benchmark_results.csv.


Current status and known limits

This section records the real state of each part when the project was finished, including the parts that are not complete:

Item Status
Semesters, subjects, courses, sections, polymorphic modules Done (API and UI)
Discussion forums (topics, posts, pin, lock, show/hide, attachments) Done; replies can be nested only one level
AI assistant that answers from course documents, streams over WebSocket and shows page numbers Done
PDF loading → vectors → ChromaDB when a document lesson is created or updated Done, only for .pdf files
Rate limit, permissions, TTFT measurement Test suites are available
Submissions and grading The API is done; the submission and grading UI is not finished
Assessments (quiz/essay) The UI only shows the settings; students cannot take a test yet
AI practice generation (/api/lessons/{id}/practices/ai-generate) It only creates a practice record; it does not generate questions yet
Video/audio player The lesson type exists in the backend; the UI shows the file as a link to open or download
Server-side rendering There is skeleton code (server.ts), but the production build is still a static SPA served by Nginx

Documents


License

This project is released under the MIT license. See LICENSE.


About the use of AI

While I worked on this project, I used Gemini, ChatGPT and DeepSeek to help me write code: to suggest solutions, to find bugs and to review the code. About 50% of the source code was written with help from these tools.

I did the planning, the requirements analysis, the architecture design, the technology choices, the testing and the final work on the system myself, and I am responsible for them. AI was only a tool and did not decide the direction of the project. This README was also written with help from AI, and I checked the content again.


© 2026 - EdOU - Developed by Hoàng Phi Hùng - Ho Chi Minh City Open University

About

University LMS with a course-scoped RAG study assistant. .NET 10, Angular 22 and FastAPI, Docker Compose, YARP gateway, ChromaDB, streaming WebSocket answers with page citations.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages