diff --git a/README.md b/README.md index 48bb1d3..2049ab8 100644 --- a/README.md +++ b/README.md @@ -1,290 +1,252 @@ -# 📚 LibreCore — Library Management System +# 📚 LibreCore -> A production-grade Library Management REST API built with Go +A modular Library Management REST API built with Go, following clean architecture principles and a strict separation of domain, service, and infrastructure layers. -![CI](https://github.com/mohammad-farrokhnia/library/actions/workflows/ci.yml/badge.svg) -![Go](https://img.shields.io/badge/Go-1.23-00ADD8?style=flat&logo=go) -![License](https://img.shields.io/badge/License-MIT-green?style=flat) -![PostgreSQL](https://img.shields.io/badge/PostgreSQL-16-336791?style=flat&logo=postgresql) -![Elasticsearch](https://img.shields.io/badge/Elasticsearch-8.x-005571?style=flat&logo=elasticsearch) -![Redis](https://img.shields.io/badge/Redis-7-DC382D?style=flat&logo=redis) --- -## What Is This? +## Tech Stack -**LibreCore** is a fully-featured backend API for managing a public library — books, customers, -employees, and borrowing — built from the ground up with Go. ---- - -## Technologies - -| Area | Technology | -|---|---| -| Language | Go 1.23 | -| Web Framework | `net/http` (stdlib) | -| Database | PostgreSQL 16 | -| Search Engine | Elasticsearch 8 | -| Caching | Redis | -| Auth | JWT (RS256) | -| Migrations | `golang-migrate` | -| Containerization | Docker + Docker Compose | -| CI/CD | GitHub Actions | -| API Docs | OpenAPI 3.0 (Swagger UI) | +* Go 1.26 +* Gin (HTTP framework) +* PostgreSQL 16 +* Redis +* Elasticsearch 8 +* JWT (RS256) +* Swagger / OpenAPI 3 +* Docker + Docker Compose +* GitHub Actions (CI) --- -## Features +## Architecture + +```text +HTTP (Gin Router) + → Middleware (auth, logging, rate limit, recovery) + → Handler layer (request/response) + → Service layer (business logic) + → Repository layer (PostgreSQL) + → External systems (Redis, Elasticsearch) +``` -- **Book Inventory** — full CRUD, multi-copy tracking, availability management -- **Customer Management** — registration, profiles, borrow history -- **Employee System** — role-based access (librarian / manager) -- **Borrowing System** — enforced business rules, due dates, overdue tracking -- **Full-Text Search** — powered by Elasticsearch with fuzzy matching and autocomplete -- **Book Recommendations** — "readers who borrowed this also borrowed..." engine -- **Reports & Analytics** — borrow trends, top books, overdue summaries -- **JWT Authentication** — stateless auth with role-based route protection -- **Pagination & Filtering** — all list endpoints support cursor-based pagination +* `handler/` → HTTP transport layer only +* `service/` → business rules +* `repository/` → DB access (Postgres) +* `search/` → Elasticsearch integration +* `middleware/` → cross-cutting concerns +* `domain/` → core models & domain logic --- ## Project Structure +```text +cmd/ + api/ REST API server (routers, DI, bootstrap) + migrate/ DB migration CLI + seed/ database seeding tool + +internal/ + domain/ business models + domain rules + handler/ HTTP controllers (backoffice + portal) + service/ business logic layer + repository/ database layer + middleware/ auth, logging, security, rate limit + search/ Elasticsearch client + queries + +pkg/ + cache/ Redis abstraction + database/ Postgres helpers + errors/ typed error system + i18n/ localization + logger/ structured logging + mailer/ email service + otp/ OTP generation/validation + response/ HTTP response helpers + session/ session utilities + validator/ input validation + +configs/ configuration (db, redis, env) +migrations/ SQL migrations +docs/ Swagger + architecture docs +tests/ integration tests + mocks ``` -librecore/ -├── cmd/ -│ ├── api/ ← main REST API server -│ └── cli/ ← admin CLI (seed, migrate, reports) -├── internal/ -│ ├── domain/ ← core structs (Book, Customer, Employee, Borrow) -│ ├── repository/ ← database access layer (PostgreSQL) -│ ├── service/ ← business logic -│ ├── handler/ ← HTTP handlers -│ └── middleware/ ← auth, logging, rate limiting -├── pkg/ -│ ├── response/ ← shared HTTP response helpers -│ ├── pagination/ ← cursor-based pagination -│ └── validator/ ← reusable input validation -├── search/ ← Elasticsearch indexing and query logic -├── recommender/ ← book recommendation engine -├── configs/ ← configuration loading -├── migrations/ ← SQL migration files -├── scripts/ ← DB seed, utility scripts -├── api/ ← OpenAPI spec -├── docs/ ← architecture diagrams and decisions -└── .github/ - └── workflows/ ← CI/CD pipelines -``` - -> Follows the [Standard Go Project Layout](https://github.com/golang-standards/project-layout). --- -## Getting Started - -### Prerequisites +## API Overview -- Go 1.23+ -- Docker & Docker Compose +### Backoffice (Admin APIs) -### Run Locally +#### Auth -```bash -git clone https://github.com/YOUR_USERNAME/librecore.git -cd librecore +```text +POST /api/v1/backoffice/auth/login +POST /api/v1/backoffice/auth/refresh +POST /api/v1/backoffice/auth/forgot-password +POST /api/v1/backoffice/auth/reset-password +POST /api/v1/backoffice/auth/logout +``` -cp .env.example .env -# Edit .env if needed (see Configuration section below) +#### Books -docker compose up -d +```text +GET /api/v1/backoffice/books +GET /api/v1/backoffice/books/:id +POST /api/v1/backoffice/books +PUT /api/v1/backoffice/books/:id +DELETE /api/v1/backoffice/books/:id +POST /api/v1/backoffice/books/:id/copies/add +POST /api/v1/backoffice/books/:id/copies/remove +``` -make migrate +#### Customers -make run +```text +GET /api/v1/backoffice/customers +GET /api/v1/backoffice/customers/:id +POST /api/v1/backoffice/customers +PUT /api/v1/backoffice/customers/:id +DELETE /api/v1/backoffice/customers/:id ``` -The API will be available at `http://localhost:8080`. -Swagger UI at `http://localhost:8080/docs`. - -### Configuration +#### Employees -The `.env` file contains different connection strings depending on how you run the application: +```text +GET /api/v1/backoffice/employees +GET /api/v1/backoffice/employees/:id +POST /api/v1/backoffice/employees +PUT /api/v1/backoffice/employees/:id +DELETE /api/v1/backoffice/employees/:id +``` -**For local development (`make run-api`):** -- Use `localhost` with host-mapped ports -- Redis: `redis://localhost:6380` (Docker maps 6380→6379) -- Elasticsearch: `http://localhost:9200` +#### Borrows -**For Docker (`make docker-up`):** -- Environment variables are automatically overridden in `docker-compose.yml` -- Redis: `redis://redis:6379` (internal Docker network) -- Elasticsearch: `http://elasticsearch:9200` (internal Docker network) +```text +GET /api/v1/backoffice/borrows +POST /api/v1/backoffice/borrows +PUT /api/v1/backoffice/borrows/:id/return -**For production:** -- Point to your managed services (AWS ElastiCache, Elastic Cloud, etc.) -- Set environment variables to override defaults: - ```bash - export REDIS_URL=redis://your-redis-cluster.amazonaws.com:6379 - export ELASTICSEARCH_URL=https://your-es-cluster.elastic-cloud.com:9200 - docker compose up -d - ``` -- Or update your production `.env` file directly with external service URLs +GET /api/v1/backoffice/borrows/customers/:id +``` -### Makefile Commands +#### Search -```bash -make run # start the API server -make test # run all tests -make migrate # run pending migrations -make seed # seed the database with sample data -make docker # build and start all containers -make lint # run golangci-lint +```text +GET /api/v1/backoffice/search/books +GET /api/v1/backoffice/search/books/suggest ``` -### Building with Version +#### Reports -The API binary embeds version information at build time using git tags or commit hash: +```text +GET /api/v1/backoffice/reports/borrows/trends +GET /api/v1/backoffice/reports/borrows/overdue +GET /api/v1/backoffice/reports/books/top +GET /api/v1/backoffice/reports/books/low-availability +GET /api/v1/backoffice/reports/customers/top +GET /api/v1/backoffice/reports/genres/popularity +GET /api/v1/backoffice/reports/summary/monthly +``` -```bash -# Build locally with version (uses git tag or commit hash) -make build-api +--- -# Build and run with Docker (automatically injects version) -make docker-up +### Portal (Customer APIs) + +#### Auth -# The version is injected at compile time and logged on startup: -# [library] listening on :8080 env=development version=e5d4239 +```text +POST /api/v1/portal/auth/signup +POST /api/v1/portal/auth/login +POST /api/v1/portal/auth/refresh +POST /api/v1/portal/auth/logout +POST /api/v1/portal/auth/forgot-password +POST /api/v1/portal/auth/reset-password +GET /api/v1/portal/auth/google +GET /api/v1/portal/auth/google/callback ``` -For manual builds: +#### Books -```bash -# Local build with version from git -go build -ldflags "-X main.Version=$(git describe --tags --always --dirty)" -o bin/library ./cmd/api +```text +GET /api/v1/portal/books +GET /api/v1/portal/books/:id +GET /api/v1/portal/books/search +GET /api/v1/portal/books/suggest +GET /api/v1/portal/books/:id/recommendations +``` -# Docker build with specific version -docker build --build-arg VERSION=1.2.3 -t library-api . +#### Customer profile -# Docker Compose with custom version -VERSION=1.2.3 docker compose up -d --build +```text +GET /api/v1/portal/me +PUT /api/v1/portal/me +GET /api/v1/portal/me/borrows +GET /api/v1/portal/me/recommendations ``` --- -## API Overview +### Public Search +```text +GET /api/v1/search/books +GET /api/v1/search/books/suggest ``` -# Auth -POST /auth/login -POST /auth/refresh - -# Books -GET /books -GET /books/{id} -POST /books 🔒 -PUT /books/{id} 🔒 -DELETE /books/{id} 🔒 - -# Search -GET /search/books?q=orwell&fuzzy=true -GET /search/suggest?q=ani - -# Customers -GET /customers 🔒 -GET /customers/{id} 🔒 -POST /customers -PUT /customers/{id} 🔒 - -# Employees -GET /employees 🔒 manager -POST /employees 🔒 manager -PUT /employees/{id} 🔒 manager - -# Borrowing -POST /borrows 🔒 -PUT /borrows/{id}/return 🔒 -GET /borrows 🔒 -GET /customers/{id}/borrows - -# Recommendations -GET /books/{id}/recommendations -GET /customers/{id}/recommendations - -# Reports -GET /reports/books/by-genre -GET /reports/customers/top-borrowers -GET /reports/overdue -``` -🔒 = requires JWT token +--- + +## Features + +* Book inventory management (copies, availability tracking) +* Customer & employee management system +* Role-based access control (backoffice vs portal) +* Borrow/return lifecycle management +* Full-text search with Elasticsearch +* Recommendation engine (item + profile based) +* Reporting system (borrows, trends, analytics) +* JWT authentication with refresh tokens +* Google OAuth login support +* Pagination + filtering across all list endpoints --- -## Architecture +## Build & Run +```bash +make docker-up +make migrate +make run-api ``` -Request → Middleware (auth, logger) → Handler → Service → Repository → PostgreSQL - ↓ - Elasticsearch (search) - ↓ - Recommender (engine) -``` -Each layer has one responsibility and only communicates with the layer directly below it. -Business rules live exclusively in `service/`. Handlers never touch the database. +--- + +## Makefile (core commands) + +```bash +make run-api +make build +make test +make lint +make migrate +make seed +make docker-up +``` --- -## Security Considerations & Known Limitations - -⚠️ **This project is for educational/portfolio purposes. Before deploying to production, address the following:** - -### 🔴 Critical Security Issues - -1. **Google OAuth State Parameter (CSRF Vulnerability)** - - **Issue**: The `GoogleCallback` handler does not verify the OAuth `state` parameter - - **Risk**: Attackers can forge OAuth callbacks and potentially hijack authentication flows - - **Fix Required**: Store state in Redis with 5-minute TTL before redirecting to Google, then verify it in the callback: - ```go - // Before redirect - state := generateRandomState() - rdb.Set(ctx, "oauth:state:"+state, userID, 5*time.Minute) - - // In callback - storedUserID := rdb.Get(ctx, "oauth:state:"+state) - if storedUserID == "" { - return errors.New("invalid state") - } - ``` - -2. **Hardcoded OTP for Password Reset** - - **Issue**: Email OTP verification is not implemented; OTPs are logged but not sent - - **Risk**: Password reset flow is non-functional in production - - **Fix Required**: Integrate a real email service (SendGrid, AWS SES, etc.) in `pkg/mailer` - -### 🟡 Data Integrity Limitations - -3. **No Soft Deletes** - - **Issue**: Deleting customers/employees permanently removes records and orphans borrow history - - **Impact**: Historical data is lost; reports become inaccurate - - **Recommendation**: Add `deleted_at TIMESTAMPTZ` columns and filter with `WHERE deleted_at IS NULL` - -4. **Elasticsearch Sync Has No Recovery** - - **Issue**: Book indexing is fire-and-forget; if the API crashes between DB write and ES index, search results become stale - - **Impact**: Search index can drift from database state - - **Proper Solution**: Implement an outbox pattern with `es_sync_queue` table and background worker - - **Current Workaround**: Manually reindex via admin script if drift is detected - -### ✅ Already Implemented Correctly - -- **Unique Constraint Checking**: Uses pgx error codes (`23505`) instead of fragile string matching -- **JWT Security**: RS256 with proper key rotation support -- **SQL Injection Protection**: All queries use parameterized statements -- **Password Hashing**: bcrypt with cost factor 12 +## Notes on Design + +* Clean separation of backoffice and portal APIs +* Domain-driven internal structure +* Elasticsearch used as a derived read model +* Services are the only layer containing business logic +* Handlers are strictly transport adapters --- ## License -MIT — see [LICENSE](./LICENSE). - +MIT +--- diff --git a/configs/db.go b/configs/db.go index a61ccb4..b9ff28b 100644 --- a/configs/db.go +++ b/configs/db.go @@ -1,10 +1,12 @@ +//nolint:staticcheck package configs import ( "fmt" "log" "time" - + // Register the pgx driver for database/sql. + _ "github.com/jackc/pgx/v5/stdlib" "github.com/jmoiron/sqlx" )