Codular is an educational platform for interactive programming exercises. This repository contains the backend API, built with Go, designed to generate AI-powered coding tasks (e.g., code with intentional gaps or "noise") and validate user solutions. It integrates with Large Language Models (LLMs) via OpenRouter to create and check tasks in Python, C++, or Java, supporting user authentication, task management, and asynchronous processing.
The backend handles task generation (skips or noises), solution submission, scoring, and progress tracking, making it suitable for learning platforms where users practice debugging and code restoration.
- User Authentication: Secure JWT-based login, registration, token refresh, and logout with HTTP-only cookies for refresh tokens.
- Task Generation:
- Skips: Remove key code fragments and replace with placeholders, requiring users to fill them in.
- Noises: Add subtle "noise" (redundant code or minor semantic changes) to clean source code, challenging users to refactor.
- Task Management: Create, list, retrieve, regenerate, and toggle public access for tasks. Supports random task selection.
- Solution Checking: Asynchronous LLM-based validation of submissions, providing scores (0-100) and hints.
- API Documentation: Auto-generated Swagger UI for easy exploration.
- Scalability: PostgreSQL for persistent data (users, tasks, submissions), Redis for real-time status tracking.
- Security: JWT authorization, input validation, CORS, and secure cookies.
- Logging: Structured logging with pretty-print in development and JSON in production.
The backend is a monolithic Go application with a clean separation of concerns:
- HTTP Server: Uses Chi router for RESTful API endpoints under
/api/v1. Middleware includes authentication, logging, and recovery. - Storage Layer (
internal/storage/database): Abstracts PostgreSQL interactions for users, tasks, submissions, and statuses. Redis handles transient task statuses (e.g., "Processing", "Done"). - LLM Integration (
lib/api/openrouter): Custom client for OpenRouter API, loading system prompts from YAML configs for task generation and checking. - Handlers (
internal/http_server/handlers): Modular handlers for auth, tasks, generation, solving, and status checks. All async operations (e.g., LLM calls) use goroutines. - Config (
config/): YAML files for LLM prompts (e.g.,system_prompts.yamlfor skips generation). - Lib Utilities:
lib/logger: Custom slog handlers (pretty for local, JSON for prod/dev).lib/api/response: Standardized JSON responses with status and errors.
- Deployment: Multi-stage Docker build for optimized binaries. Docker Compose orchestrates PostgreSQL, Redis, and the backend with health checks and dependency waiting (
wait-for-it.sh).
Data Flow Example:
- User submits code via
/skips/generate→ Generate alias → Save to DB → Async LLM processing → Update Redis status. - User solves via
/skips/solve→ Save submission → Async LLM check → Update score/hints in DB.
All endpoints are under /api/v1. Authentication required for protected routes (Bearer JWT).
| Endpoint | Method | Description | Auth | Tags |
|---|---|---|---|---|
/auth/register |
POST | Register new user (email/password) | No | Auth |
/auth/login |
POST | Login and get access/refresh tokens | No | Auth |
/auth/refresh |
POST | Refresh access token using cookie | Yes | Auth |
/auth/logout |
POST | Invalidate refresh token | Yes | Auth |
/task/random |
GET | Get random public task alias (?type=skips|noises|any) | No | Task |
/tasks |
GET | List public tasks (?type=any|skips|noises&offset=0&limit=10) | No | Task |
/task/{alias} |
GET | Get task details (code, description, edit perms) | Yes | Task |
/task/{alias}/regenerate |
PATCH | Regenerate task with new params | Yes (owner) | Task |
/task/{alias}/set-access |
PATCH | Toggle public status | Yes (owner) | Task |
/skips/generate |
POST | Generate skips task from source code | Yes | Skips |
/noises/generate |
POST | Generate noises task from source code | Yes | Noises |
/skips/solve |
POST | Submit answers for skips task | Yes | Skips |
/noises/solve |
POST | Submit cleaned code for noises task | Yes | Noises |
/submission-status/{submission_id} |
GET | Get submission status (score, hints) | Yes | Submissions |
/task-status/{alias} |
GET | Get task processing status | No | Task |
/user/email |
GET | Get authenticated user email | Yes | User |
/user/tasks |
GET | List user's tasks | Yes | User |
/docs |
GET | Swagger UI for API docs | No | - |
Example Request (Generate Skips):
POST /api/v1/skips/generate
{
"sourceCode": "def add(a, b): return a + b",
"skipsNumber": 2,
"programmingLanguage": "Python"
}Response: {"responseInfo": {"status": "OK"}, "taskAlias": "abc123"}
- Language: Go 1.23
- Web Framework: go-chi/chi (routing/middleware)
- Auth: golang-jwt/jwt (JWT), golang.org/x/crypto/bcrypt (hashing)
- Validation: go-playground/validator
- Rendering: go-chi/render
- Database: PostgreSQL (via pgx/sqlx), Redis (go-redis)
- LLM Client: Custom OpenRouter integration (HTTP client with YAML prompts)
- Logging: Go's slog with custom pretty handler
- API Docs: swaggo/http-swagger
- Other: YAML (gopkg.in/yaml.v3), Base64 (crypto/rand), CORS (go-chi/cors)
- Containerization: Docker (multi-stage), Docker Compose
-
Prerequisites:
- Go 1.23+
- Docker & Docker Compose
- PostgreSQL client (optional, for manual DB ops)
-
Clone Repository:
git clone <repo-url> cd codular-backend
-
Environment Variables: Create
.env(or use Docker secrets):POSTGRES_USER=postgres POSTGRES_PASSWORD=secret_postgres_passw POSTGRES_DB=codular POSTGRES_ADMIN_USER=admin_db POSTGRES_ADMIN_PASSWORD=admin_secret_pass REDIS_PASSWORD=redis_password REDIS_HOSTNAME=redis REDIS_PORT=redis_port POSTGRES_HOST_NAME=localhost POSTGRES_PORT=postgres_port OPENROUTER_API_KEY=your_openrouter_key MODEL=gpt-4o-mini # Or preferred model -
Database Initialization:
- Docker Compose handles init scripts in
./docker/postgres/init.
- Docker Compose handles init scripts in
-
Development (with Docker):
docker-compose up --build
- Backend: http://localhost:8082
- Swagger: http://localhost:8082/docs
-
Local Go Run (without Docker):
go mod tidy cp .env.local .env # For local.yaml go run cmd/codular-backend/main.go -
Production: Set
ENV=productionin.env. Use multi-stage Dockerfile for slim images.
- App Config:
config/local.yaml(dev) or env vars (prod). Includes server address, timeouts, alias length. - LLM Prompts: YAML files in
config/define system prompts for generation/checking (e.g., descriptions ≤45 chars, English, neutral).