booting portfolio
ankit.kumar
cloud-native · ai-adjacent · reliable
All projects
Production hotel booking REST platform

StayNest

Java 21Spring Boot 3Spring SecurityHibernate/JPAPostgreSQL + pgvectorSpring AIStripe (webhooks)CloudinaryJWT + BCrypt
overview

StayNest is a production hospitality reservation REST API built on Java 21 and Spring Boot 390+ source files, 25+ tests, layered architecture, and a Decorator-pattern pricing calculator for surge, occupancy and holiday rules that keeps business logic composable and unit-testable.

Concurrency is guarded by pessimistic row locks on room inventory to eliminate double-booking anomalies, paired with a 5-minute scheduled jobthat expires unpaid reservations. Stripe checkout uses signature-verified webhooks and transactional refund handlers that persist state to PostgreSQLbefore invoking the gateway — preventing financial discrepancies on retries.

Three Spring AI integrations run on top: dynamic pricing adjustments, semantic property search, and a pgvector-backed review Q&A chatbot — each wrapped with a fallback handler. Sessions are hardened with HttpOnly JWT cookies, BCrypt hashing, role-based access control and token versioning for global sign-out on password reset.

Spring 3
Runtime
Stripe
Payments
Cloudinary
Media
pgvector
AI
architecture & flows

Every diagram, animated live.

StayNest · System architecture
// Layered Spring Boot service with JPA, Stripe & Cloudinary integrations, plus a Spring AI review chatbot.
StayNest · Booking flow
// Pessimistic locks prevent double-booking; Stripe webhook handler is idempotent on stripe_id.
StayNest · Layered pricing (decorator chain)
// PricingCalculator wraps a BasePriceStrategy in Occupancy, Holiday and Surge decorators; a Spring AI advisor injects a demand-based adjustment before the final quote.
StayNest · Review Q&A (RAG)
// Semantic review search with an LLM-grounded answer, plus a fallback path when the model is unavailable.
StayNest · ERD
schema.sql

Database schema

▸ schema.sql
CREATE TABLE hotels (
  id         SERIAL PRIMARY KEY,
  name       TEXT NOT NULL,
  city       TEXT,
  manager_id INT REFERENCES users(id)
);

CREATE TABLE rooms (
  id         SERIAL PRIMARY KEY,
  hotel_id   INT REFERENCES hotels(id) ON DELETE CASCADE,
  type       TEXT,
  base_price DECIMAL(10,2)
);

CREATE TABLE inventory (
  room_id INT REFERENCES rooms(id) ON DELETE CASCADE,
  day     DATE NOT NULL,
  count   INT NOT NULL,
  PRIMARY KEY (room_id, day)
);

CREATE TABLE bookings (
  id             SERIAL PRIMARY KEY,
  guest_id       INT REFERENCES users(id),
  hotel_id       INT REFERENCES hotels(id),
  room_id        INT REFERENCES rooms(id),
  start_date     DATE,
  end_date       DATE,
  status         TEXT,
  payment_status TEXT
);

CREATE TABLE payments (
  id         SERIAL PRIMARY KEY,
  booking_id INT REFERENCES bookings(id),
  stripe_id  TEXT UNIQUE,
  amount     DECIMAL(10,2),
  status     TEXT
);
lessons_learned

Deep dive & lessons

  • 01
    Pessimistic locks on the inventory table prevent double-booking under concurrent checkout without needing a distributed lock service.
  • 02
    Stripe webhook handler is idempotent: stripe_id is unique on payments so retries fold safely, and refunds check current status before mutating.
  • 03
    The pricing decorator chain keeps rules composable — new campaigns become one class instead of a spider of if-branches.
  • 04
    A scheduler expires unpaid bookings after 15 minutes, releasing inventory and closing the checkout window cleanly.
  • 05
    Semantic review search + a fallback answer path keeps the AI chatbot useful even when the LLM is unavailable.