Go Back Home

Scoutcamp: scout camp bookings and management

Scoutcamp: scout camp bookings and management

Scoutcamp is my bachelor's thesis (TFG) project that digitized the full cycle of scout camp discovery, booking, and management: find camps, check availability by date, book plots or bungalows, and let owners handle requests from their own dashboard. It replaces spreadsheets, scattered emails, and manual confirmations with a single web flow.

Live demo: scoutcamp.alejandroquintana.dev. Code: frontend and backend.

Domain and actors

The data model revolves around:

  • Camping — camp listing (location with 2dsphere index, images, rules, check-in/out, payment methods, owner).
  • CampingLodging — accommodation types: campsite, bungalow, or other, with capacity and nightly fee.
  • CampingUnit — bookable units within each lodging.
  • Booking — reservation with dates, units, manager contact, total cost, payment method, and status (pending, accepted, rejected, cancelled).
  • CampingRelation — favorites and reviews per user/camp.

User roles (simple but effective RBAC):

  • user — search camps, book, view bookings, conversations, and profile.
  • manager — create/edit camps, manage lodgings/units, accept or reject bookings, messaging.
  • admin — global panel: users, camps, bookings, conversations.
Scoutcamp — scout camp search and listing

The technical challenge

  • Contextual search: filter by check-in/out dates, geolocation ($geoNear), and real unit availability in that range.
  • Avoid overselling: validate free units before persisting a booking.
  • Rich listings: show camps with lodgings, aggregated capacity, and ratings without naive N+1 queries.
  • Owner authorization: only the camp owner edits listings or changes booking status.
  • i18n: API i18n (es, en, fr, de) for emails; frontend @ngx-translate.
  • Media: image upload to Google Cloud Storage via Document model and Multer.

System architecture

Angular 18 SPA against a Express 5 REST API under /api/v1. JWT auth: checkUser decodes the token on each request; authMiddleware guards routes that require a session.

flowchart TB
  subgraph client [Angular 18 SPA]
    Public[Listing and booking]
    Manager[Manager panel]
    Admin[Admin panel]
  end
  subgraph api [Node Express 5]
    Auth[JWT checkUser]
    Routes[api/v1 routes]
    Ctrl[Controllers and validators]
  end
  subgraph data [Persistence]
    Mongo[(MongoDB Mongoose 9)]
    GCS[Google Cloud Storage]
    Mail[Nodemailer]
  end
  Public --> Routes
  Manager --> Routes
  Admin --> Routes
  Routes --> Auth
  Routes --> Ctrl
  Ctrl --> Mongo
  Ctrl --> GCS
  Ctrl --> Mail
        

Tech stack

  • Frontend: Angular 18, TypeScript 5.4, Angular Material, Bootstrap 5, RxJS, @ngx-translate, Google Maps, JWT with @auth0/angular-jwt.
  • Backend: Express 5, Mongoose 9, JWT, bcrypt, express-validator, Multer, @google-cloud/storage, i18n, Morgan, CORS.

Backend: API and business logic

Main routes mounted in index.js:

  • /api/v1/ — login and signup (auth.routes).
  • /api/v1/campings — camp CRUD, lodgings, bookings, reviews (public or authenticated per endpoint).
  • /api/v1/users — profiles and user bookings.
  • /api/v1/conversations — user messaging.
  • /api/v1/documents — file upload/download on GCS.

All models extend a base databaseSchema with a static search() method: reusable pagination, sorting, filters, and populate for admin and user lists.

Booking flow (createBooking)

  1. Client sends dates, requested lodgings, manager details, and payment method.
  2. Server loads available lodgings via CampingLodging.getAvailableLodgings and free units via CampingUnit.getAvailableUnits.
  3. If stock is insufficient for any lodging, respond with error (no save).
  4. Compute totalCost (nights × rate × units) and create booking as pending.
  5. Email camp owner in their language (i18n.setLocale + Nodemailer).
Scoutcamp — booking flow and lodging management

Aggregations in camp listings

Camping.getCampings builds an aggregation pipeline with $lookup into lodgings, units, and relations; computes average rating; optionally applies $geoNear when coordinates are provided; and filters camps with bookings overlapping the searched dates. Public listings arrive enriched in one database round trip.

Frontend: modules and routes

The Angular app is split by domain:

  • campingcampings-list, camping-view (gallery, map, reviews, booking), camping-booking.
  • manager — my camps, create/edit camp, lodging management, conversations.
  • admin — global users, camps, bookings, conversations.
  • auth / user — signup, login, profile, user area.

Shared components: camp searcher, reusable tables, side panel (left-menu), language selector, and navbar.

Scoutcamp — admin panel and management

Challenges solved

1. Availability without overselling

Problem: two users might try to book the last plot for the same dates.

Solution: before booking.save(), ensure each requested lodging has availables greater than or equal to the requested quantity; assign only units returned by getAvailableUnits. On failure, throw Unauthorized without writing to the database.

2. Listings without naive N+1

Problem: show camps with lodgings, units, and ratings in paginated lists with many chained queries.

Solution: aggregation pipeline in getCampings with $lookup, $addFields for total capacity and average reviews, and date-overlap booking filters.

3. Authorization and ownership

Problem: users must not modify others' camps or bookings.

Solution: JWT in Authorization header; camping.owner.equals(req.user._id) checks on booking management and camp edits; admin role for broader user listings.

4. Multilingual experience

Problem: owners and users speak different languages, especially in email notifications.

Solution: backend i18n package (es, en, fr, de) for email subjects and bodies; frontend @ngx-translate with a language selector in the UI.

5. Images and documents

Problem: each camp needs a reliable photo gallery.

Solution: Multer buffer upload, stream to GCS bucket (scoutcamp_bucket), metadata in Document collection referenced from the camp.

Conclusion

Scoutcamp solidified designing a REST API with Mongoose, aggregations for complex queries, and a role-based modular Angular SPA. As a thesis project, it showed that search, booking, owner management, and global admin can live in one coherent product without inflating the model with organizational hierarchies the domain did not need.

Key takeaways: model availability on the server, enrich listings with pipelines instead of manual joins in the client, and clearly separate public, manager, and admin modules on the frontend.