Skip to content

v2.0.0

Latest

Choose a tag to compare

@dileepadev dileepadev released this 31 Aug 20:05
02f42ea

The backend moves from NestJS 11 on Vercel serverless to FastAPI on Python 3.13, hosted on FastAPI Cloud, and gains two new resources. This is an architectural migration, not a framework swap: the same MongoDB cluster and the same collections are read, and nothing was re-seeded.

The cutover is complete. https://api.dileepa.dev is served by FastAPI Cloud against the production database, and the health check is /health.

Important

This is a single cutover, not a staged one. The v1 paths that are gone return 404 from the moment this ships, and the consumers move with it — nothing is carried behind a deprecation.

No password reset is required. The JWT algorithm, secret and claim names are unchanged, and Node bcrypt hashes verify as-is and are rewritten to argon2id on the next sign-in. A token minted by the NestJS app is still accepted, so a session live at the cutover was not signed out.

Breaking changes

v1 endpoint Successor
POST /auth/sign-in POST /auth/login — same body, same token shape
POST GET DELETE /upload the /uploads equivalents
  • Collection endpoints return an envelope, { items, total, limit, offset }, rather than a bare array.
  • An empty collection is 200 with an empty list, not 404. v1 threw NotFoundException when a list came back empty, which made an empty section indistinguishable from a broken endpoint.
  • Errors return { error: { code, message, details } } on every endpoint, replacing { statusCode, timestamp, path, message }.
  • Records expose id, not _id, and never __v. index is now order — the API reads either name.
  • /events is reshaped, keeping its path and collection name.
  • Blog posts carry a relative path and a composed canonicalUrl instead of an absolute blog.dileepa.dev link, a real publishedDate instead of a date string, and description instead of excerpt.

Added

New resources/projects (net-new: slug, status, period, stack, gallery, metrics, SEO), /pillars and /speaking-topics (site copy that was previously compiled into the website), GET /api-links (the endpoint catalogue, derived from the live dependency graph rather than hand-maintained), GET /status, GET /health, GET /version, and GET/POST /maintenance/database behind five layered guards and unregistered in production.

Blog engagement — views de-duplicated per reader per 24 hours through a unique index and a TTL rather than a check in the handler, four reactions, comments with one level of replies, comment reactions, admin-only moderation, and a denormalised commentCount maintained with $inc.

AuthPOST /auth/refresh and GET /auth/profile, with refresh tokens alongside access tokens.

AlsoPATCH /{resource}/order for bulk reordering, ?hasPhotos= on /events, description on videos, location/shortBio/fullBio on about, and contact submissions persisted to the database in addition to the Resend notification.

Security

Eight security headers on every response, verified against the live domain. CORS is an allowlist, never a wildcard. Startup refuses a misconfigured production — a placeholder JWT_SECRET, a localhost MONGODB_URI, a wildcard CORS_ORIGINS or an empty BLOG_SYNC_API_KEY each abort the boot. Errors never carry internals, and sign-in failures do not distinguish an unknown address from a wrong password. The API reference and its spec are unregistered in production, so neither /docs nor /api-json exists to be found.

Fixed

Four defects found by the pre-release review, each pinned by a test:

  • Rate limiting failed open. Since FastAPI 0.141, include_router keeps routers nested, so slowapi's middleware found no endpoint to read a limit from and applied RATE_LIMIT_DEFAULT to nothing — /auth/login included, with no 429 and no X-RateLimit-* header to notice.
  • The strictest limits were the only bypassable ones. A @limiter.limit decorator is checked inside the endpoint and FastAPI validates the body first, so a malformed request consumed no budget — leaving /contact and the comment routes, the two tightest configured limits, with no limit at all on invalid input.
  • A disabled account was identifiable without its password. account_disabled was returned before password verification, answering "is this address registered here?" for anyone who asked.
  • Comment moderation required only a valid token, not the admin role — four routes that return commenter email addresses and write owner-badged replies.

Also: an oversized upload is rejected without being read into memory first, and a slug miss no longer costs two identical queries.

Removed

The NestJS applicationsrc/, test/, package.json and the whole Node toolchain, 95 files. Azure Blob Storage; Cloudinary is the only image backend. The legacy field on blog rows, archived into blogs_v1_legacy_backup first.

Nothing is deprecated. tests/contract/test_v1_parity.py records every dropped v1 route with its successor, and tests/test_openapi.py fails if any operation is ever published with deprecated: true.

Verified at release

/health reports the database up · /version reports 2.0.0 in production · /docs and /api-json return 404 · all eight security headers present · /pillars 6, /speaking-topics 4, /projects 7, /events 30, /blogs 22 · 424 tests passing, ruff and mypy --strict clean.


Full changelog: CHANGELOG.md · Scope: #13 · Compare: v1.2.1...v2.0.0