Skip to content

Latest commit

 

History

History
1303 lines (984 loc) · 43 KB

File metadata and controls

1303 lines (984 loc) · 43 KB

API Reference

Generated by npm run docs:generate from the @openapi JSDoc blocks in src/routes/*.routes.js — do not hand-edit this file. Change the route annotations and regenerate instead. An interactive version of this same spec is served at /api/docs whenever the server is running.

Base URL: http://localhost:5080/api (configurable via PORT)

Authentication

Protected endpoints require a JWT in the Authorization header:

Authorization: Bearer <token>

Obtain a token from POST /auth/register, POST /auth/login, or POST /auth/setup-admin, then send it as Authorization: Bearer <token>.

Response envelope

All endpoints respond with JSON in a consistent shape:

{
	"success": true,
	"message": "Human-readable summary",
	"data": {},
	"pagination": {}
}

Errors:

{
	"success": false,
	"message": "What went wrong",
	"error": "Detail (omitted in production for 500s)"
}

Status codes

Code Meaning
200 Success
201 Created
400 Bad request / validation error
401 Unauthorized (missing/invalid/expired/revoked token)
403 Forbidden (authenticated, wrong role/owner)
404 Not found
409 Conflict (duplicate, business rule violation)
429 Rate limited
500 Internal server error

Pagination

Applies to every list endpoint. Response includes:

"pagination": {
  "current_page": 1,
  "total_pages": 5,
  "total_items": 47,
  "items_per_page": 10,
  "has_next": true,
  "has_prev": false
}

Rate limits

Two layers, both active on every /api/* request:

  • A flat, IP-based backstop: 100 requests / 15 min (express-rate-limit).
  • A role-aware limit, requests/minute, backed by Redis when REDIS_URL is set (falls back to in-process otherwise — see docs/SETUP.md):
Role Limit
Guest 20 req / min
User 60 req / min
Librarian 120 req / min
Admin 300 req / min

Disabled entirely under NODE_ENV=test so the automated test suite isn't flaky against shared per-minute buckets — see the manual curl procedures in SECURITY_TESTING.md to exercise this for real.


Health

Liveness/readiness checks

GET /health

Access: Public

Verifies the process is up and its dependencies (database, and Redis when configured) are reachable. Used by the Docker healthcheck and any future load balancer/uptime monitor.

Responses:

Status Description
200 Healthy — database (and Redis, if configured) reachable.
503 Degraded — database or Redis unreachable.
// 200 — Healthy — database (and Redis, if configured) reachable.
{
	"status": "ok",
	"uptime_seconds": 42,
	"timestamp": "2026-08-19T12:00:00.000Z",
	"database": "connected",
	"redis": "connected"
}
// 503 — Degraded — database or Redis unreachable.
{
	"status": "degraded",
	"uptime_seconds": 42,
	"timestamp": "2026-08-19T12:00:00.000Z",
	"database": "disconnected",
	"redis": "connected"
}

Auth

Registration, login, and session management

POST /auth/change-password

Access: Authenticated (Bearer JWT)

Request body:

Field Type Required Description
current_password string yes
new_password string yes 8+ characters, at least one uppercase, one lowercase, one number. Must differ from the current password.

Responses:

Status Description
200 Password changed.
400 Request body failed schema validation
401 Missing, invalid, or expired token
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}

POST /auth/forgot-password

Access: Public

Always responds 200 with the same generic message, whether or not the email is registered, to avoid leaking which emails exist. If the account exists, emails a single-use, 1-hour reset token (or logs it server-side as a dev-mode fallback if SMTP isn't configured) — the token itself is never returned in this response.

Request body:

Field Type Required Description
email string yes

Responses:

Status Description
200 Generic acknowledgement — see description.
// 200 — Generic acknowledgement — see description.
{
	"success": true,
	"message": "If an account with that email exists, a password reset link has been sent."
}

POST /auth/login

Access: Public

Request body:

Field Type Required Description
emailOrUsername string yes
password string yes

Responses:

Status Description
200 Login successful.
401 Invalid credentials, or account deactivated.
429 Too many failed attempts — account temporarily locked out.

POST /auth/logout

Access: Authenticated (Bearer JWT)

Revokes the token used on this request — it's rejected on every subsequent request even though it hasn't naturally expired yet.

Responses:

Status Description
200 Logged out.
401 Missing, invalid, or expired token
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}

GET /auth/me

Access: Authenticated (Bearer JWT)

Responses:

Status Description
200 Current user (no password field).
401 Missing, invalid, or expired token
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}

POST /auth/refresh

Access: Authenticated (Bearer JWT)

Responses:

Status Description
200 New token issued.
401 Missing, invalid, or expired token
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}

POST /auth/register

Access: Public

Request body:

Field Type Required Description
first_name string yes
last_name string yes
user_name string yes
email string yes
phone string no
password string yes 8+ characters, at least one uppercase, one lowercase, one number.
image_url string no

Responses:

Status Description
201 Account created.
400 Request body failed schema validation
409 Email or username already taken.

POST /auth/reset-password

Access: Public

Authorization comes from possessing a valid reset token, not from a session. Single-use — a second attempt with the same token returns 400.

Request body:

Field Type Required Description
token string yes Token from the forgot-password email.
new_password string yes

Responses:

Status Description
200 Password reset.
400 Invalid, expired, or already-used reset token; or validation failure.

POST /auth/setup-admin

Access: Public

Public, but gated by INITIAL_SETUP_KEY and refuses if any user already exists in the system — intended for first-run initialization only, not general admin creation.

Request body:

Field Type Required Description
admin_email string yes
admin_password string yes
setup_key string yes Value of the INITIAL_SETUP_KEY env var.
first_name string no
last_name string no

Responses:

Status Description
201 Admin account created.
401 Invalid setup key.
403 System already initialized (a user already exists).

Authors

Author records

GET /authors

Access: Public

Responses are cached for AUTHORS_CACHE_TTL_SECONDS (default 30s), keyed by the full query string, and invalidated on any create/update/delete.

Param In Type Required Default Description
page query integer no 1
limit query integer no 10
search query string no Matches first_name, last_name, or email.
sort_by query string no created_at
order query "asc" | "desc" no desc

Responses:

Status Description
200 Paginated author list.

POST /authors

Access: Authenticated (Bearer JWT)

Requires the Admin or Librarian role.

Request body:

Field Type Required Description
first_name string yes
last_name string yes
email string yes
date_of_birth string no
biography string no
phone string no
image string no

Responses:

Status Description
201 Author created.
400 Request body failed schema validation
401 Missing, invalid, or expired token
403 Authenticated, but wrong role or not the resource owner
409 An author with this email already exists.
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}
// 403 — Authenticated, but wrong role or not the resource owner
{
	"success": false,
	"message": "Access denied. Required role(s): Admin. Your role: User"
}

DELETE /authors/{id}

Access: Authenticated (Bearer JWT)

Requires the Admin or Librarian role. Fails if the author still has books attached.

Param In Type Required Default Description
id path integer yes

Responses:

Status Description
200 Author deleted.
401 Missing, invalid, or expired token
403 Authenticated, but wrong role or not the resource owner
404 Resource not found
409 Author has books attached — remove or reassign them first.
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}
// 403 — Authenticated, but wrong role or not the resource owner
{
	"success": false,
	"message": "Access denied. Required role(s): Admin. Your role: User"
}

GET /authors/{id}

Access: Public

Param In Type Required Default Description
id path integer yes
include_books query boolean no false Embed the author's books and a books_count.

Responses:

Status Description
200 The author.
404 Resource not found

PUT /authors/{id}

Access: Authenticated (Bearer JWT)

Requires the Admin or Librarian role. Partial update — every field is optional.

Param In Type Required Default Description
id path integer yes

Request body:

Field Type Required Description
first_name string no
last_name string no
email string no
date_of_birth string no
biography string no
phone string no
image string no

Responses:

Status Description
200 Author updated.
400 Request body failed schema validation
401 Missing, invalid, or expired token
403 Authenticated, but wrong role or not the resource owner
404 Resource not found
409 Another author already has this email.
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}
// 403 — Authenticated, but wrong role or not the resource owner
{
	"success": false,
	"message": "Access denied. Required role(s): Admin. Your role: User"
}

Books

Book catalog, borrowing, and returns

GET /books

Access: Public

Responses are cached for BOOKS_CACHE_TTL_SECONDS (default 30s), keyed by the full query string, and invalidated on any create/update/delete.

Param In Type Required Default Description
page query integer no 1
limit query integer no 10
search query string no
author_id query integer no
genre query string no
status query "Available" | "Borrowed" | "Reserved" | "Lost" no

Responses:

Status Description
200 Paginated book list.

POST /books

Access: Authenticated (Bearer JWT)

Requires the Admin or Librarian role.

Request body:

Field Type Required Description
isbn string yes
title string yes
author_id integer yes
published_date string no
description string no
cover_image string no
genre string no
language string no
pages integer no
publisher string no
available_copies integer no
total_copies integer no
status "Available" | "Borrowed" | "Reserved" | "Lost" no

Responses:

Status Description
201 Book created.
400 Validation failure, or available_copies exceeds total_copies.
401 Missing, invalid, or expired token
403 Authenticated, but wrong role or not the resource owner
404 author_id does not exist.
409 A book with this ISBN already exists.
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}
// 403 — Authenticated, but wrong role or not the resource owner
{
	"success": false,
	"message": "Access denied. Required role(s): Admin. Your role: User"
}

DELETE /books/{id}

Access: Authenticated (Bearer JWT)

Requires the Admin or Librarian role. Fails if the book has active borrows.

Param In Type Required Default Description
id path integer yes

Responses:

Status Description
200 Book deleted.
401 Missing, invalid, or expired token
403 Authenticated, but wrong role or not the resource owner
404 Resource not found
409 Book has active borrows.
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}
// 403 — Authenticated, but wrong role or not the resource owner
{
	"success": false,
	"message": "Access denied. Required role(s): Admin. Your role: User"
}

GET /books/{id}

Access: Public

Param In Type Required Default Description
id path integer yes
include_author query boolean no false
include_borrows query boolean no false

Responses:

Status Description
200 The book.
404 Resource not found

PUT /books/{id}

Access: Authenticated (Bearer JWT)

Requires the Admin or Librarian role. Partial update — every field is optional.

Param In Type Required Default Description
id path integer yes

Request body:

Field Type Required Description
isbn string no
title string no
author_id integer no
published_date string no
description string no
cover_image string no
genre string no
language string no
pages integer no
publisher string no
available_copies integer no
total_copies integer no
status "Available" | "Borrowed" | "Reserved" | "Lost" no

Responses:

Status Description
200 Book updated.
400 Request body failed schema validation
401 Missing, invalid, or expired token
403 Authenticated, but wrong role or not the resource owner
404 Resource not found
409 ISBN already taken by another book.
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}
// 403 — Authenticated, but wrong role or not the resource owner
{
	"success": false,
	"message": "Access denied. Required role(s): Admin. Your role: User"
}

POST /books/{id}/borrow

Access: Authenticated (Bearer JWT)

Requires the book to have available_copies > 0 and status "Available", the caller to have fewer than 5 active borrows, and no existing active borrow of the same book by the same caller.

Param In Type Required Default Description
id path integer yes

Request body:

Field Type Required Description
due_days integer no

Responses:

Status Description
201 Book borrowed.
401 Missing, invalid, or expired token
404 Resource not found
409 Book unavailable, borrow limit exceeded, or already borrowed by this user.
// 201 — Book borrowed.
{
	"success": true,
	"message": "Book borrowed successfully",
	"data": {
		"borrow_record": {
			"id": 10,
			"due_date": "2026-08-31T00:00:00.000Z",
			"status": "Borrowed"
		},
		"book": {
			"id": 1,
			"title": "1984",
			"available_copies": 4,
			"status": "Available"
		},
		"due_date": "2026-08-31T00:00:00.000Z",
		"days_allowed": 14
	}
}
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}

POST /books/{id}/return

Access: Authenticated (Bearer JWT)

Applies a $1/day late fee if returned past the due date.

Param In Type Required Default Description
id path integer yes

Responses:

Status Description
200 Book returned.
401 Missing, invalid, or expired token
404 Book not found, or no active borrow record for this book/user.
// 200 — Book returned.
{
	"success": true,
	"message": "Book returned successfully",
	"data": {
		"borrow_record": {
			"id": 10,
			"status": "Returned"
		},
		"book": {
			"id": 1,
			"available_copies": 5,
			"status": "Available"
		},
		"return_details": {
			"is_overdue": false,
			"days_late": 0,
			"late_fee": 0
		}
	}
}
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}

Users

User accounts and profiles

GET /users

Access: Authenticated (Bearer JWT)

Requires the Admin role.

Param In Type Required Default Description
page query integer no 1
limit query integer no 10
search query string no
role query "Admin" | "Librarian" | "User" no
is_active query boolean no

Responses:

Status Description
200 Paginated user list.
401 Missing, invalid, or expired token
403 Authenticated, but wrong role or not the resource owner
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}
// 403 — Authenticated, but wrong role or not the resource owner
{
	"success": false,
	"message": "Access denied. Required role(s): Admin. Your role: User"
}

POST /users

Access: Authenticated (Bearer JWT)

Requires the Admin role. Can create any role, unlike self-registration.

Request body:

Field Type Required Description
first_name string yes
last_name string yes
user_name string yes
email string yes
phone string no
password string yes
image_url string no
role "Admin" | "Librarian" | "User" no
is_active boolean no
email_verified boolean no

Responses:

Status Description
201 User created.
400 Request body failed schema validation
401 Missing, invalid, or expired token
403 Authenticated, but wrong role or not the resource owner
409 Email or username already taken.
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}
// 403 — Authenticated, but wrong role or not the resource owner
{
	"success": false,
	"message": "Access denied. Required role(s): Admin. Your role: User"
}

DELETE /users/{id}

Access: Authenticated (Bearer JWT)

Requires the Admin role. Fails if the user has active borrows, or is the last active Admin.

Param In Type Required Default Description
id path integer yes

Responses:

Status Description
200 User deleted.
401 Missing, invalid, or expired token
403 Authenticated, but wrong role or not the resource owner
404 Resource not found
409 User has active borrows, or is the last active Admin.
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}
// 403 — Authenticated, but wrong role or not the resource owner
{
	"success": false,
	"message": "Access denied. Required role(s): Admin. Your role: User"
}

GET /users/{id}

Access: Authenticated (Bearer JWT)

Requires the Admin/Librarian role, or that the caller is requesting their own profile.

Param In Type Required Default Description
id path integer yes
include_borrows query boolean no false
include_stats query boolean no false

Responses:

Status Description
200 The user.
401 Missing, invalid, or expired token
403 Authenticated, but wrong role or not the resource owner
404 Resource not found
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}
// 403 — Authenticated, but wrong role or not the resource owner
{
	"success": false,
	"message": "Access denied. Required role(s): Admin. Your role: User"
}

PUT /users/{id}

Access: Authenticated (Bearer JWT)

Requires the resource owner, a Librarian, or an Admin. Which fields a given caller may actually set is enforced server-side and depends on who's calling — see the field-restriction table in docs/API.md. Attempting to set role without the Admin role returns 403; every other disallowed field is silently dropped rather than rejected.

Param In Type Required Default Description
id path integer yes

Request body:

Field Type Required Description
first_name string no
last_name string no
phone string no
email string no
password string no
image_url string no
role "Admin" | "Librarian" | "User" no Admin-only.
is_active boolean no Admin-only.
email_verified boolean no Admin-only.

Responses:

Status Description
200 User updated.
400 Request body failed schema validation
401 Missing, invalid, or expired token
403 Not the resource owner/Librarian/Admin, or attempted to set role without Admin.
404 Resource not found
409 Email already taken by another user.
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}

GET /users/{id}/borrow-records

Access: Authenticated (Bearer JWT)

Requires the Admin/Librarian role, or that the caller owns the records.

Param In Type Required Default Description
id path integer yes
page query integer no 1
limit query integer no 10
status query "Borrowed" | "Returned" | "Overdue" no

Responses:

Status Description
200 Paginated borrow records for this user.
401 Missing, invalid, or expired token
403 Authenticated, but wrong role or not the resource owner
404 Resource not found
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}
// 403 — Authenticated, but wrong role or not the resource owner
{
	"success": false,
	"message": "Access denied. Required role(s): Admin. Your role: User"
}

GET /users/profile

Access: Authenticated (Bearer JWT)

Responses:

Status Description
200 Current user's profile.
401 Missing, invalid, or expired token
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}

GET /users/public

Access: Public

Param In Type Required Default Description
page query integer no 1
limit query integer no 20
role query "Admin" | "Librarian" | "User" no
is_active query boolean no true

Responses:

Status Description
200 Paginated public user list.

Borrow Records

Borrow/return history, overdue tracking, statistics

GET /borrow-records

Access: Authenticated (Bearer JWT)

Requires the Admin or Librarian role.

Param In Type Required Default Description
page query integer no 1
limit query integer no 10
user_id query integer no
book_id query integer no
status query "Borrowed" | "Returned" | "Overdue" no
overdue_only query boolean no false

Responses:

Status Description
200 Paginated borrow records.
401 Missing, invalid, or expired token
403 Authenticated, but wrong role or not the resource owner
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}
// 403 — Authenticated, but wrong role or not the resource owner
{
	"success": false,
	"message": "Access denied. Required role(s): Admin. Your role: User"
}

POST /borrow-records/{id}/extend

Access: Authenticated (Bearer JWT)

Requires the record's owner, a Librarian, or an Admin.

Param In Type Required Default Description
id path integer yes

Request body:

Field Type Required Description
extension_days integer no

Responses:

Status Description
200 Due date extended.
401 Missing, invalid, or expired token
403 Authenticated, but wrong role or not the resource owner
404 Resource not found
409 Record is not currently active (already returned).
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}
// 403 — Authenticated, but wrong role or not the resource owner
{
	"success": false,
	"message": "Access denied. Required role(s): Admin. Your role: User"
}

GET /borrow-records/overdue

Access: Authenticated (Bearer JWT)

Requires the Admin or Librarian role.

Param In Type Required Default Description
page query integer no 1
limit query integer no 10

Responses:

Status Description
200 Paginated overdue borrow records.
401 Missing, invalid, or expired token
403 Authenticated, but wrong role or not the resource owner
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}
// 403 — Authenticated, but wrong role or not the resource owner
{
	"success": false,
	"message": "Access denied. Required role(s): Admin. Your role: User"
}

GET /borrow-records/statistics

Access: Authenticated (Bearer JWT)

Requires the Admin or Librarian role.

Responses:

Status Description
200 Aggregate borrowing statistics.
401 Missing, invalid, or expired token
403 Authenticated, but wrong role or not the resource owner
// 200 — Aggregate borrowing statistics.
{
	"success": true,
	"message": "Borrowing statistics retrieved successfully",
	"data": {
		"total_borrows": 120,
		"active_borrows": 34,
		"returned_borrows": 80,
		"overdue_borrows": 6,
		"avg_borrow_days": 12.4,
		"generated_at": "2026-08-19T12:00:00.000Z"
	}
}
// 401 — Missing, invalid, or expired token
{
	"success": false,
	"message": "Access denied. No token provided"
}
// 403 — Authenticated, but wrong role or not the resource owner
{
	"success": false,
	"message": "Access denied. Required role(s): Admin. Your role: User"
}