https://github.com/williamw-dev/travel-hub
https://github.com/williamw-dev/travel-hub
Last synced: about 1 month ago
JSON representation
- Host: GitHub
- URL: https://github.com/williamw-dev/travel-hub
- Owner: williamw-dev
- Created: 2025-06-17T11:59:06.000Z (12 months ago)
- Default Branch: main
- Last Pushed: 2025-06-20T10:49:36.000Z (12 months ago)
- Last Synced: 2025-09-20T15:54:11.490Z (9 months ago)
- Language: TypeScript
- Size: 961 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# 🛬 Travel Hub
> A travel booking API with caching, real-time notifications, and recommendations.

## 🚀 Start Stack
- Use `docker-compose up` to start all services:
```bash
cd apps/server && pnpm i
cd ../client && pnpm i
cd ../.. && chmod +x docker/entrypoint.sh
cp .env.example .env
docker-compose up -d
```
## 📦 Setup & Environment
- Set up project with your chosen framework (FastAPI, Express, Spring Boot, etc.)
- Add support for environment variables
- Configure Docker and `docker-compose` to run:
- API service
- Redis
- MongoDB
- Neo4j
---
### ⚠️ Warnings
- Ensure you don't have redis or mongo running on your host machine, as the containers will use the same ports.
> MacOS
```sh
brew services stop redis
brew services stop mongodb-community
```
- On prometheus container, it's possible to have chmod permissions issues with the `entrypoint.sh` file. If you encounter this, run the following command to fix it:
```bash
chmod +x docker/entrypoint.sh
```
### 🗄️ Data Sources
- **MongoDB**: Store flight offers, hotels, and activities
```ts
use travelhub;
db.offers.insertMany([
{
from: "PAR",
to: "TYO",
departDate: ISODate("2025-07-10T08:00:00Z"),
returnDate: ISODate("2025-07-20T18:00:00Z"),
provider: "AirZen",
price: 750.0,
currency: "EUR",
legs: [
{ flightNum: "AZ123", dep: "CDG", arr: "HND", duration: "12h45" }
],
hotel: { name: "Tokyo Central Hotel", nights: 10, price: 400.0 },
activity: { title: "Mt. Fuji Day Tour", price: 120.0 }
},
{
from: "NYC",
to: "PAR",
departDate: ISODate("2025-07-15T12:00:00Z"),
returnDate: ISODate("2025-07-25T19:00:00Z"),
provider: "SkyWorld",
price: 540.0,
currency: "USD",
legs: [
{ flightNum: "SW456", dep: "JFK", arr: "CDG", duration: "7h10" }
],
hotel: null,
activity: null
},
{
from: "NYC",
to: "BER",
departDate: ISODate("2025-07-10T08:00:00Z"),
returnDate: ISODate("2025-07-20T18:00:00Z"),
provider: "SkyWorld",
price: 500,
currency: "USD",
legs: [{ flightNum: "SW100", dep: "JFK", arr: "BER", duration: "8h00" }],
hotel: null,
activity: null
}
])
```
- Neo4j cities and relationships:
```cypher
CREATE (par:City { code: "PAR", name: "Paris", country: "FR" });
CREATE (tyo:City { code: "TYO", name: "Tokyo", country: "JP" });
CREATE (nyc:City { code: "NYC", name: "New York", country: "US" });
CREATE (lon:City { code: "LON", name: "London", country: "UK" });
MATCH (par:City {code: "PAR"}), (lon:City {code: "LON"})
CREATE (par)-[:NEAR { weight: 0.9 }]->(lon);
MATCH (par:City {code: "PAR"}), (nyc:City {code: "NYC"})
CREATE (par)-[:NEAR { weight: 0.7 }]->(nyc);
MATCH (nyc:City {code: "NYC"}), (par:City {code: "PAR"})
CREATE (nyc)-[:NEAR { weight: 0.85 }]->(par);
MATCH (tyo:City {code: "TYO"}), (par:City {code: "PAR"})
CREATE (tyo)-[:NEAR { weight: 0.6 }]->(par);
```
---
### 🔌 Core Features
#### 1. `GET /offers`
- Implement Redis caching with TTL 60s: `offers::`
- On cache miss, query MongoDB (`from`, `to`, sort by `price` ascending)
- Store compressed JSON back into Redis
- Return array of offers with fields:
- `id`, `provider`, `price`, `currency`, `legs[]`, `hotel?`, `activity?`
#### 2. `GET /offers/{id}`
- Read from Redis cache (`offers:`, TTL 300s), fallback to MongoDB
- Return full offer details and `relatedOffers` (3 related IDs from Neo4j)
#### 3. `GET /reco`
- Accept query params: `city`, `k`
- Execute Cypher query in Neo4j:
```cypher
MATCH (c:City {code:$city})-[:NEAR]->(n:City)
RETURN n.code AS city
ORDER BY n.weight DESC
LIMIT $k
```
- Return list of city codes with scores
#### 4. `POST /login`
- Accept payload: `{ "userId": "u42" }`
- Generate UUID v4, store as `session: → userId` (EX 900s)
- Return: `{ "token": "", "expires_in": 900 }`
---
### 🔔 Real-Time Notifications
- Publish new offer notifications to Redis Pub/Sub channel `offers:new`
- Payload format:
```json
{
"offerId": "abc123",
"from": "PAR",
"to": "TYO"
}
```
---
### 📊 Indexes & Data Modeling
#### MongoDB
- Ensure `offers` collection schema:
- Fields: `_id`, `from`, `to`, `departDate`, `returnDate`, `provider`, `price`, `currency`, `legs`, `hotel`, `activity`
- Create indexes:
- `{ from: 1, to: 1, price: 1 }`
- Full-text index on `provider`
#### Neo4j
- Model nodes: `(c:City {code, name, country})`
- Model relationships: `(c1)-[:NEAR {weight}]->(c2)`
---
### 🚀 Non-functional Requirements
- Average latency for `/offers` should be 200ms (cache hit), 700ms max (cache miss)
- All responses should be `application/json; charset=utf-8`
- Log request durations and HTTP error codes
---
### ✨ Optional Features
- Text search support via `q=hotel` param using MongoDB text index
- `/stats/top-destinations` route using MongoDB aggregation + Redis caching
- `/metrics` route compatible with Prometheus (avg time, cache hit rate)