From c49a49dc19a2fe3aa8b83e4ba953f4578af3e9da Mon Sep 17 00:00:00 2001 From: Nurfog Date: Fri, 13 Mar 2026 12:43:32 -0300 Subject: [PATCH] feat: Add comprehensive project context documentation and refactor CMS service to include HTTP tracing and streamline CORS configuration. --- QWEN.md | 389 +++++++++++++++++++++++++++ services/cms-service/src/handlers.rs | 35 ++- services/cms-service/src/main.rs | 51 ++-- 3 files changed, 431 insertions(+), 44 deletions(-) create mode 100644 QWEN.md diff --git a/QWEN.md b/QWEN.md new file mode 100644 index 0000000..bd5264c --- /dev/null +++ b/QWEN.md @@ -0,0 +1,389 @@ +# OpenCCB - Project Context + +## Project Overview + +**OpenCCB (Open Comprehensive Course Backbone)** is an open-source Learning Management System (LMS) and Content Management System (CMS) platform built for performance, security, and scalability. It provides a complete infrastructure for course creation, student management, and AI-powered learning features. + +### Architecture + +The project uses a **unified container architecture** with the following structure: + +| Service | Ports | Description | +|---------|-------|-------------| +| **Studio + CMS** | 3000/3001 | Next.js admin frontend + Rust CMS API | +| **Experience + LMS** | 3003/3002 | Next.js student frontend + Rust LMS API | +| **Database** | 5433 | PostgreSQL 16 (shared, separate DBs: `openccb_cms`, `openccb_lms`) | + +### Technology Stack + +**Backend:** +- Rust Edition 2024 (workspace with 3 crates) +- Web Framework: Axum 0.8 +- Database: SQLx 0.8 with PostgreSQL 16 +- Authentication: JWT (jsonwebtoken 9.3), bcrypt +- Security: HMAC, SHA2, OpenID Connect (SSO) +- Rate Limiting: tower-governor 0.7 + +**Frontend:** +- Next.js 14 (App Router) +- React 18 + TypeScript 5 +- Styling: Tailwind CSS 3.4 +- UI: Lucide React, Framer Motion, React Markdown +- Mermaid diagrams for dynamic visualization + +**Infrastructure:** +- Docker & Docker Compose +- Single-tenant design (premium module) +- Local AI: Ollama (Llama 3.2) + Faster-Whisper + +**Key Features:** +- AI-powered course generation and quiz creation +- Discussion forums with nested replies +- LTI 1.3 Tool Provider (Canvas, Moodle integration) +- Monetization via Mercado Pago +- Live learning with Jitsi integration +- Student portfolios with Open Badges +- Predictive analytics (dropout risk detection) +- Multi-language support (EN, ES, PT) +- Gamification (XP, levels, badges, leaderboards) + +## Project Structure + +``` +openccb/ +├── services/ +│ ├── cms-service/ # Rust CMS API (course management, content creation) +│ │ ├── migrations/ # SQLx database migrations +│ │ └── src/ +│ └── lms-service/ # Rust LMS API (student experience, grades) +│ └── src/ +├── shared/ +│ └── common/ # Shared Rust library (auth, models, utils) +├── web/ +│ ├── studio/ # Next.js CMS frontend (admin/instructor) +│ └── experience/ # Next.js LMS frontend (student) +├── e2e/ # Playwright end-to-end tests +├── scripts/ # Utility scripts (auth, database) +└── [config files] +``` + +### Rust Workspace Members + +```toml +[workspace] +members = [ + "services/cms-service", + "services/lms-service", + "shared/common", +] +``` + +## Building and Running + +### Docker (Recommended) + +```bash +# Start all services +docker-compose up --build + +# Start in detached mode +docker-compose up -d + +# Rebuild and start +docker-compose up -d --build + +# Clean install (removes volumes) +docker-compose down -v && docker-compose up --build + +# Run E2E tests +docker-compose --profile test up e2e +``` + +### Local Development + +**Prerequisites:** +- Rust (Edition 2024) +- Node.js 18+ +- PostgreSQL 16 +- sqlx-cli: `cargo install sqlx-cli --no-default-features --features postgres` + +**Backend (Rust):** + +```bash +# CMS Service (port 3001) +cd services/cms-service +DATABASE_URL=postgresql://user:password@localhost:5433/openccb_cms cargo run + +# LMS Service (port 3002) +cd services/lms-service +DATABASE_URL=postgresql://user:password@localhost:5433/openccb_lms cargo run + +# With debug logging +RUST_LOG=debug cargo run -p cms-service +``` + +**Frontend (Next.js):** + +```bash +# Studio (CMS Frontend - port 3000) +cd web/studio +npm install +npm run dev + +# Experience (LMS Frontend - port 3003) +cd web/experience +npm install +npm run dev +``` + +### Installation Script + +```bash +# Full installation with database setup +./install.sh + +# Fast mode (skip dependency checks) +./install.sh --fast +``` + +## Development Commands + +### Code Quality + +```bash +# Frontend linting and formatting +cd web/studio && npm run lint:fix +cd web/studio && npm run format +cd web/studio && npm run type-check + +# Same commands available in web/experience +``` + +### Database Management + +```bash +# Reset database (delete and recreate) +./reset_db.sh + +# Run migrations manually +DATABASE_URL=postgresql://user:password@localhost:5433/openccb_cms \ + sqlx migrate run --source services/cms-service/migrations + +DATABASE_URL=postgresql://user:password@localhost:5433/openccb_lms \ + sqlx migrate run --source services/lms-service/migrations +``` + +### Health Checks + +```bash +# CMS Service +curl http://localhost:3001/health +curl http://localhost:3001/health/live +curl http://localhost:3001/health/ready + +# LMS Service +curl http://localhost:3002/health +curl http://localhost:3002/health/live +curl http://localhost:3002/health/ready +``` + +### Utilities + +```bash +# Generate secure JWT secret +./generate_jwt_secret.sh + +# Clear session +./clear_session.sh + +# Validate authentication +./validate_auth.sh + +# Diagnose auth issues +./diagnose_auth.sh +``` + +## API Endpoints + +### Authentication (CMS - port 3001) + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/auth/register` | Create new user | +| POST | `/auth/login` | Login and get JWT token | +| GET | `/auth/profile` | Get current user profile | + +### Course Management (CMS) + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/courses` | Create course | +| POST | `/courses/generate` | AI-generate course structure | +| GET | `/courses/{id}/export` | Export course to JSON | +| POST | `/courses/import` | Import course from JSON | +| DELETE | `/courses/{id}` | Delete course | +| POST | `/lessons` | Add lesson to module | +| POST | `/assets/upload` | Upload media/document | + +### Learning Experience (LMS - port 3002) + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/enroll` | Enroll in course | +| POST | `/grades` | Submit lesson score | +| GET | `/notifications` | Get user notifications | +| POST | `/notifications/{id}/read` | Mark notification as read | + +### Discussion Forums (LMS) + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/courses/{id}/discussions` | List threads | +| POST | `/courses/{id}/discussions` | Create thread | +| GET | `/discussions/{id}` | Get thread with replies | +| POST | `/discussions/{id}/posts` | Reply to thread | +| POST | `/posts/{id}/vote` | Vote on post | +| POST | `/posts/{id}/endorse` | Mark post as correct (instructor) | + +### AI Features + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/lessons/{id}/transcribe` | Start transcription | +| POST | `/lessons/{id}/generate-quiz` | Generate quiz with AI | +| POST | `/lessons/{id}/chat` | Chat with lesson tutor | +| GET | `/lessons/{id}/feedback` | Get AI feedback | +| GET | `/courses/{id}/dropout-risks` | Get dropout risk analysis | + +## Environment Configuration + +Copy `.env.example` to `.env` and configure: + +```bash +# Database +CMS_DATABASE_URL=postgresql://user:password@localhost:5433/openccb_cms +LMS_DATABASE_URL=postgresql://user:password@localhost:5433/openccb_lms + +# JWT Secret (generate with ./generate_jwt_secret.sh) +JWT_SECRET=your_secure_secret + +# AI Configuration +AI_PROVIDER=local +LOCAL_WHISPER_URL=http://localhost:9000 +LOCAL_OLLAMA_URL=http://localhost:11434 +LOCAL_LLM_MODEL=llama3.2:3b + +# Frontend URLs +NEXT_PUBLIC_CMS_API_URL=http://localhost:3001 +NEXT_PUBLIC_LMS_API_URL=http://localhost:3002 +``` + +## Testing + +### E2E Tests (Playwright) + +```bash +# Run all tests +cd e2e && npx playwright test + +# Run with UI +cd e2e && npx playwright test --ui + +# Run specific test file +cd e2e && npx playwright test tests/auth.spec.ts + +# Generate report +cd e2e && npx playwright show-report +``` + +### Backend Tests + +```bash +# Run Rust tests +cargo test -p cms-service +cargo test -p lms-service +cargo test -p common +``` + +## Key Conventions + +### Rust Code Style + +- Use workspace dependencies from root `Cargo.toml` +- Shared code goes in `shared/common` +- Use `sqlx::query!` macros for compile-time SQL verification +- Error handling with `thiserror` and `anyhow` +- Tracing for logging (`tracing::info!`, `tracing::debug!`) + +### Frontend Code Style + +- TypeScript strict mode enabled +- Tailwind CSS for styling +- Lucide React for icons +- Framer Motion for animations +- Components in `src/components/` +- Pages in `src/app/` (Next.js App Router) + +### Database + +- Separate databases for CMS and LMS +- Migrations managed by SQLx +- UUIDs for primary keys +- Timestamps with timezone (timestamptz) + +### Authentication + +- JWT-based authentication +- Bcrypt password hashing +- Role-based access control (admin, instructor, student) +- OpenID Connect support for SSO + +## Common Issues + +### Port Conflicts + +If port 5432 is occupied, the setup uses 5433: +```bash +# Check if port is in use +lsof -i :5432 +lsof -i :5433 +``` + +### Database Connection Issues + +```bash +# Check if PostgreSQL is running +docker ps | grep postgres + +# Check database connectivity +docker exec openccb-db-1 pg_isready -U user +``` + +### Frontend Build Issues + +```bash +# Clear Next.js cache +rm -rf web/studio/.next +rm -rf web/experience/.next + +# Reinstall dependencies +cd web/studio && rm -rf node_modules && npm install +``` + +### Rust Compilation Issues + +```bash +# Clean and rebuild +cargo clean +cargo build + +# Update dependencies +cargo update +``` + +## Related Documentation + +- `README.md` - Comprehensive user documentation with API manual +- `OPTIMIZATIONS.md` - Performance optimizations implemented +- `roadmap.md` - Project roadmap and feature status +- `diagnose_auth.sh` - Authentication debugging script diff --git a/services/cms-service/src/handlers.rs b/services/cms-service/src/handlers.rs index b3c9371..ca50968 100644 --- a/services/cms-service/src/handlers.rs +++ b/services/cms-service/src/handlers.rs @@ -2336,28 +2336,45 @@ pub async fn login( State(pool): State, Json(payload): Json, ) -> Result, (StatusCode, String)> { + tracing::info!("Login attempt for email: {}", payload.email); + let user = sqlx::query_as::<_, User>("SELECT * FROM fn_get_user_by_email($1)") .bind(&payload.email) .fetch_one(&pool) .await - .map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid credentials".into()))?; + .map_err(|e| { + tracing::error!("Failed to fetch user: {}", e); + (StatusCode::UNAUTHORIZED, "Invalid credentials".into()) + })?; - if !verify(payload.password, &user.password_hash).map_err(|_| { - ( - StatusCode::INTERNAL_SERVER_ERROR, - "Verification failed".into(), - ) - })? { - return Err((StatusCode::UNAUTHORIZED, "Credenciales inválidas".into())); + tracing::info!("User found: {}", user.email); + + let verify_result = verify(payload.password, &user.password_hash); + match verify_result { + Ok(valid) => { + if !valid { + tracing::warn!("Invalid password for user: {}", user.email); + return Err((StatusCode::UNAUTHORIZED, "Credenciales inválidas".into())); + } + }, + Err(e) => { + tracing::error!("Password verification failed: {}", e); + return Err((StatusCode::INTERNAL_SERVER_ERROR, "Verification failed".into())); + } } - let token = create_jwt(user.id, user.organization_id, &user.role).map_err(|_| { + tracing::info!("Password verified for user: {}", user.email); + + let token = create_jwt(user.id, user.organization_id, &user.role).map_err(|e| { + tracing::error!("JWT generation failed: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, "JWT generation failed".into(), ) })?; + tracing::info!("Login successful for user: {}", user.email); + Ok(Json(AuthResponse { user: UserResponse { id: user.id, diff --git a/services/cms-service/src/main.rs b/services/cms-service/src/main.rs index d6ec66e..f55605d 100644 --- a/services/cms-service/src/main.rs +++ b/services/cms-service/src/main.rs @@ -12,9 +12,7 @@ mod webhooks; use axum::{ Router, extract::DefaultBodyLimit, - http::{StatusCode, HeaderValue}, middleware, - response::Response, routing::{delete, get, post, put}, }; use common::health::{self, HealthState}; @@ -28,25 +26,7 @@ use tower_governor::governor::GovernorConfigBuilder; use tower_governor::GovernorLayer; use tower_http::cors::{Any, CorsLayer}; use tower_http::set_header::SetResponseHeaderLayer; - -/// Handler para requests OPTIONS (CORS preflight) -async fn handle_options() -> Response { - let mut res = Response::new(String::new().into()); - *res.status_mut() = StatusCode::NO_CONTENT; - res.headers_mut().insert( - "Access-Control-Allow-Origin", - HeaderValue::from_static("*"), - ); - res.headers_mut().insert( - "Access-Control-Allow-Methods", - HeaderValue::from_static("GET, POST, PUT, DELETE, OPTIONS"), - ); - res.headers_mut().insert( - "Access-Control-Allow-Headers", - HeaderValue::from_static("Content-Type, Authorization, Origin, Accept"), - ); - res -} +use tower_http::trace::TraceLayer; #[tokio::main] async fn main() { @@ -329,26 +309,23 @@ async fn main() { ); // Rutas públicas que no requieren autenticación - let public_routes = Router::new() - .nest("/api/external", api_routes) - .route("/auth/register", post(handlers::register).options(handle_options)) - .route("/auth/login", post(handlers::login).options(handle_options)) - .route("/auth/sso/login/{org_id}", get(handlers::sso_login_init).options(handle_options)) - .route("/auth/sso/callback", get(handlers::sso_callback).options(handle_options)) + let auth_routes = Router::new() + .route("/auth/register", post(handlers::register)) + .route("/auth/login", post(handlers::login)) + .route("/auth/sso/login/{org_id}", get(handlers::sso_login_init)) + .route("/auth/sso/callback", get(handlers::sso_callback)) .route( "/branding", - get(handlers_branding::get_organization_branding).options(handle_options), - ) + get(handlers_branding::get_organization_branding), + ); + + let public_routes = Router::new() + .nest("/api/external", api_routes) // Health check routes .merge(health::health_routes(pool.clone()).with_state(health_state)) .nest_service("/assets", tower_http::services::ServeDir::new("uploads")) + .merge(auth_routes) .merge(protected_routes) - // CORS layer - debe estar PRIMERO (más cerca del servicio) para ejecutarse ULTIMO - .layer(cors) - // Rate limiting - .layer(GovernorLayer { - config: governor_conf, - }) // Security headers .layer(SetResponseHeaderLayer::overriding( http::header::STRICT_TRANSPORT_SECURITY, @@ -370,6 +347,10 @@ async fn main() { http::header::REFERRER_POLICY, http::HeaderValue::from_static("strict-origin-when-cross-origin"), )) + // CORS layer - MUST be last to execute first on response + .layer(cors) + // Trace layer for logging requests/responses + .layer(TraceLayer::new_for_http()) .with_state(pool); let addr = SocketAddr::from(([0, 0, 0, 0], 3001));