# Travlr Getaways — CS 499 Capstone Enhancements

This repository contains my enhanced version of **Travlr Getaways**, the MEAN-stack travel booking application originally built in CS 465: Full Stack Development I. It serves as the artifact for all three enhancement categories of my CS 499 Computer Science Capstone ePortfolio: software design and engineering, algorithms and data structures, and databases. The original, unmodified CS-465 version is preserved in its own repository for comparison.

## Enhancement One: Software Design and Engineering (complete)

- **Full frontend migration**: the Angular admin SPA (`app_admin/`, retained for reference) was rebuilt as a React 18 + strict TypeScript application in **`app_react/`**, built with Vite and tested with Jest and React Testing Library (16 tests). See `app_react/README.md` for a table mapping every Angular construct to its React replacement.
- **Design fixes**: a single metadata-driven `TripForm` replaces ~240 lines of duplicated form code; the edit workflow uses a URL route parameter (`/edit-trip/:tripCode`) instead of shared `localStorage` state; login resolves as a Promise, removing a polling race condition; trip descriptions render as escaped text instead of `innerHTML`, closing a stored-XSS vector (verified by a regression test).
- **Backend refactor**: centralized Express error-handling middleware with an `asyncHandler` pattern, repaired JWT verification middleware, JWT protection added to the previously open DELETE route, and CORS preflight handling.

## Enhancement Two: Algorithms and Data Structures (complete)

- **Stable merge sort** (`app_api/algorithms/mergeSort.js`): explicit O(n log n) divide-and-conquer implementation with a documented stability guarantee, powering server-side sorting by name, price, or start date.
- **Relevance-scored search** (`app_api/algorithms/relevance.js`): field-weighted scoring (name 3, resort 2, description 1, prefix bonus) over tokenized queries — no regular expressions are built from user input, eliminating ReDoS exposure.
- **Cursor-based pagination** (`app_api/algorithms/cursor.js`): the next page resumes after an encoded (sort value, code) position instead of an offset, so pages stay consistent as data changes; malformed or replayed cursors are rejected with clean 400s.
- **Top-k recommendations** (`app_api/algorithms/minHeap.js`): a hash map index gives O(1) seed lookup and a bounded min-heap selects the k most similar trips in O(n log k) time and O(k) space. Similarity combines price proximity, star rating, trip length, and shared vocabulary weighted by inverse document frequency, so rare destination terms (alpine, dunes, canopy) outweigh generic travel words and destination clusters emerge without the algorithm being told categories exist. Scores are displayed on the recommendation cards.
- **Endpoints**: `GET /api/trips/search` (search, sortBy, order, limit, cursor) and `GET /api/trips/recommendations` (basedOn, k), surfaced in the React UI as a search box, sort selector, "Load more" pagination, and a "Similar to" panel, over a 27-trip catalog spanning reef, mountain, desert, and rainforest destinations.
- **Tested**: 36 backend Jest tests (21 algorithm unit tests plus 15 endpoint integration tests against an in-memory MongoDB, including two that assert destination clusters emerge in the recommendations), alongside the frontend suite, now 19 tests. Run with `npm test` from the project root.

## Enhancement Three: Databases (complete)

- **Schema validation** (`app_api/models/travlr.js`): the trip schema now enforces data integrity at the database layer — `perPerson` is a Number (fixing the String type the code review flagged; prices can now sort numerically and be aggregated), trip codes are unique and format-constrained, the image field accepts only bare filenames (blocking path-traversal values), and every field carries trim/length constraints with descriptive messages.
- **Weighted text index**: a MongoDB full-text index over name/resort/description with weights 3/2/1 — deliberately mirroring Enhancement Two's application-level field weights so the two relevance implementations are directly comparable.
- **Aggregation-pipeline query engine** (`app_api/controllers/tripsDb.js`): search, sort, and cursor pagination re-implemented inside MongoDB ($text, $sort with case-insensitive collation, range-based cursor $match, $facet for page + total in one round trip). The search endpoint takes `?engine=db|app` — `db` is the default; `app` runs the Enhancement Two implementation, and both share one cursor format and response shape for head-to-head comparison.
- **Catalog analytics** (`GET /api/trips/stats`): a single pipeline extracts star ratings from resort strings ($regexFind + $toInt), $groups per-rating counts and price statistics, and $buckets the catalog into price bands — surfaced in the React UI as a catalog-insights line.
- **Role-based access control**: users carry a `role` (`user` by default, `admin` by promotion only — registration ignores any role in the request body). The role rides in the JWT and `requireRole('admin')` guards POST/PUT/DELETE on trips; the React UI hides Add/Edit from non-admins, and a new Register page creates accounts (always as standard users). Promote with `node scripts/promote-admin.js <email>`.
- **NoSQL injection defenses**: middleware strips `$`-prefixed and dotted keys from every inbound body/query/params before controllers run, and the auth handlers additionally require credentials to be plain strings — so `{"email": {"$gt": ""}}` login attacks die twice. Dollar signs in *values* (a description mentioning "$400") are untouched. CORS is tightened from `*` to the SPA origin.
- **Tested**: 52 backend Jest tests — the prior suites plus 16 new database-integration tests covering validation rejections, engine parity (db vs app price ordering must match exactly), RBAC (401/403/admin CRUD), mass-assignment protection, and injection defenses.

## Setup

Prerequisites: Node.js and MongoDB Community Server running locally on the default port 27017 (on Windows, the MongoDB service starts automatically after installation). A working `.env` is required — copy `.env.example` to `.env` and set any long random string as `JWT_SECRET`.

1. From this folder: `npm install`, then `node app_api/models/seed.js` to load sample trips (re-run this after upgrading to Enhancement Three — the schema now stores prices as Numbers and requires the text index the seed builds), then `npm start`.
2. In a second terminal: `cd app_react && npm install && npm run dev`, then open http://localhost:4200.
3. For admin features (add/edit/delete trips): create an account through the Register page (linked from the Login page — new accounts are always standard users), then run `node scripts/promote-admin.js your@email.com` and log in again; the fresh token carries the admin role.

---

# CS 465 Full Stack Development - Travlr Getaways

## Architecture

This project used three distinct frontend approaches across its development. The customer-facing site started as static HTML pages, which were then refactored into Handlebars (HBS) templates running on Express. With this server-rendered approach, every page request goes to the server, the controller queries MongoDB through Mongoose, and a complete HTML page is built and sent back to the browser. This works well for public content that benefits from fast initial loads and search engine visibility, but every user interaction that needs fresh data triggers a full page reload.

The admin side was built as an Angular single-page application (SPA). Unlike the Express site, the SPA loads once and then handles all navigation and rendering in the browser. Angular components manage their own state, services handle HTTP calls to the API, and the router swaps views without contacting the server for a new page. The result is a more responsive, app-like experience suited to administrators who perform repeated create, read, update, and delete operations throughout a session. JavaScript ties all of this together since it runs on both the client (Angular) and the server (Node.js/Express), which is the core advantage of the MEAN stack.

The backend uses a NoSQL MongoDB database because the trip data fits naturally into a document model. Each trip is a self-contained JSON-like document with all its fields in one place, which eliminates the joins that a relational database would require. MongoDB also pairs well with Node.js and Mongoose since data flows as JavaScript objects from the database through the API and into the frontend without needing translation between formats.

## Functionality

JSON (JavaScript Object Notation) is a lightweight data format, while JavaScript is a full programming language. JSON uses JavaScript's object syntax (key-value pairs, arrays) but is purely data with no functions, logic, or execution capability. In this project, JSON is the common language that connects every layer: trip data is stored as BSON documents in MongoDB, served as JSON through the Express REST API, and consumed as JSON by both the Handlebars templates and the Angular SPA. This consistency means no format conversion is needed at any tier boundary.

Refactoring was a recurring theme throughout the course. The customer-facing site was refactored from static HTML files into Handlebars templates backed by a controller layer, which separated data from presentation and made the pages dynamic. The trip data was refactored from a local JSON file into a MongoDB collection accessed through Mongoose, centralizing the data so both front ends could share it. On the Angular side, trip-card was built as a reusable UI component: it accepts a trip object as input and renders the same card layout wherever it appears in the listing. This means changes to the card design happen in one place and propagate everywhere the component is used, reducing duplication and the risk of inconsistencies.

## Testing

API testing in a full stack application involves verifying that each HTTP method (GET, POST, PUT, DELETE) behaves correctly at its endpoint. GET requests to /api/trips should return the full collection, while GET to /api/trips/:tripCode should return a single matching document. POST to /api/trips should create a new record, and PUT should update an existing one. Each of these was tested using Postman to confirm the correct status codes and response bodies.

Adding security introduced an additional layer of complexity to testing. The /api/register and /api/login endpoints were tested first to verify that they return valid JWTs. Then the protected endpoints (POST and PUT on /api/trips) were tested both with and without a Bearer token in the Authorization header. Without a token, the server returns a 401 Unauthorized status, confirming that the authenticateJWT middleware blocks unauthenticated access. With a valid token, the request succeeds. This layered approach, testing the authentication endpoints first and then using the returned tokens to test the protected endpoints, ensures that the security chain works end to end.

## Reflection

This course gave me hands-on experience building a complete web application from the ground up, which is directly aligned with my goal of becoming a full stack developer. I gained practical skills with every layer of the MEAN stack: setting up an Express server with MVC routing, designing Mongoose schemas for MongoDB, building a RESTful API with full CRUD operations, creating an Angular SPA with components, services, and reactive state management, and implementing JWT authentication with Passport. Beyond the individual technologies, the most valuable takeaway was understanding how all the pieces connect, seeing how a single API serves multiple frontends, how middleware secures endpoints, and how data flows from the database through the server and into the browser. These are the kinds of integration skills that employers look for in a full stack candidate, and this project gave me a working example I can demonstrate in my portfolio.
