- π Motivation
- 𧱠Build Status
- π¨ Code Style
- βοΈ Tech and Frameworks used
- π₯ Features & Screenshots
- π» Code Examples
- βοΈ Installation
- π API Reference
- π§ͺ Tests
- π§π»βπ« How to Use
- π€ Contribute
- Β©οΈ Credits
- π License
Welcome to Tripify! πβ¨ Planning trips can often be a daunting task, with numerous details to manage and choices to make. We created Tripify to simplify and streamline this process for travelers. Our goal is to provide users with a one-stop platform to plan, book, and manage their travel itineraries effortlessly.
Whether you're a seasoned traveler looking for curated experiences or a first-timer seeking guidance, Tripify empowers you with tools and insights to make your journey memorable.
The motivation behind Tripify stems from a shared passion for travel and a desire to eliminate the frustration often associated with planning trips. By integrating advanced features, automation, and user-friendly design, we aim to inspire people to explore the world with confidence and ease.
Experience Tripify in action! This quick preview showcases our key features:
- User-friendly registration and authentication
- Seamless booking experience
- Interactive itinerary planning
- Real-time notifications
- And much more!
- Current Build Status: Under active development, not yet stable for production use.
- Known Issues: Please check the Issues section for a list of reported bugs or feature requests.
- Automated Tests: We plan to add more automated tests in the future to improve reliability.
- Documentation: Additional documentation is in progress to provide more details on setup, features, and usage.
We use Prettier and ESLint to enforce a consistent code style. We use an edited version of the default ESLint TypeScript config. You can check the config in the .eslintrc.js file.
Useful Commands
- Check formatting using Prettier
npm run format
- And then fix formatting using Prettier
npm run format:fix
- Check linting using ESLint
npm run lint
- And then fix linting using ESLint
npm run lint:fix
- Check compilation of all subpackages using TypeScript
npm run compile:all
- NodeJs
- Express
- ReactJs
- MongoDB
- Mongoose
- ESLint
- Prettier
- jwt-decode
- Bootstrap
- Framer
- React Router
- React Hook Form
- React Query
- NodeMailer
- Bcrypt
- Postman
- jwt
- mutler
- fs
User Registration π
- Register as a Tourist,Tour Guide, Advertiser or A Seller each with their essential and required documents.
- Upload/remove licensce for tour guide or permit for Seller/Advertiser(PDF, JPEG, JPG, PNG).
- Submit a request to register as a Tour Guide/Seller?Advertiser with professional details.
- Upload required documents for Tour Guide registration.
Administrator Functions π©βπΌ
- Add/remove another administrator. - Manage Users and accounts. - Accept or reject Tour Guide/Seller/Advertiser registration requests. - View information uploaded by Tour Guide/Seller/Advertiser. - Moderate and flag/unflag content. - Add Prodcuts to the website. - Get notified whenever a product runs out os stock - View Complaints of Users - Add or Remove a Promo Code - View a Sales ReportAccount Management π
- Change password. - Reset forgotten password via email. - Edit/update editable User info with respect to the account type.
Tourist Functions
- View/Book Upcoming Events. - View/Purchase Items - Rate Purchased Items, Tour Guides , Sellers and activities - Cancel Booked Events - Book Flights/Hotels/Transportation - Create a wishlist and add to it - File a complaint - Receive a refund in the wallet for canceled appointments. - View the amount in the wallet. - Add/Edit Delivery Address. - Pay with Card/Wallet/Cash on Delivery - Earn and Redeem loyalty points for cashTour Guide Functions
- View/Add/Edit/Remove Itineraries - View The Sales Report of Booked Events - Edit Profile and Upload a Profile PictureAdvertiser Functions π
- View/Create/Edit/Delete activities. - View/Create/Edit/Delete Transportation Services. - View a Sales Report of your Activites and how frequently they were booked.Tourism Governor Functions π
- View/Create/Edit/Delete Historical Places - View/Create/Edit/Delete Tags for Histroical PlacesSeller Functions π°
- Accept and View the Terms and Conditions. - Add/Edit/Remove a Product. - View A Sales report of last product sales. - Upload a product image.General User π¬
- All users are authenticated and can change their password, delete their account and have general rights - All users must accept the terms and conditons before they start using the system.
BE Routes Example
app.use("/api/tourist", touristRoutes);
app.use("/api/tourguide", tourguideRoutes);
app.use("/api/toursimGovernor", tourismGovernorRoutes);
app.use("/api/seller", sellerRoutes);
app.use("/api/admin", adminRoutes);
app.use("/api/advertiser", advertiserRoutes);
app.use("/api/preference-tags", preferenceTagRoutes);
app.use("/api/tags", tagRoutes);
app.use("/api/products", productRoutes);
app.use("/api/complaints", complaintRoutes);
app.use("/api/flights", flightRoutes);
app.use("/api/hotels", hotelRoutes);
app.use("/api/bookings", bookingRoutes);
app.use("/api/transportation", transportationRoutes);
app.use("/api/notifications", notificationRoutes);
app.use("/api/stripe", stripeRoutes);
app.use('/api/wishlist', wishlistRoutes);
BE Deduct funds Tourist Controller Example
export const deductFromWallet = async (req, res) => {
try {
const { id } = req.params;
const { amount } = req.body;
console.log("Deduct from wallet request:", {
userId: id,
amount,
body: req.body,
});
if (!amount || amount <= 0) {
console.log("Invalid amount:", amount);
return res.status(400).json({ message: "Invalid amount" });
}
const tourist = await Tourist.findById(id);
if (!tourist) {
console.log("Tourist not found:", id);
return res.status(404).json({ message: "Tourist not found" });
}
console.log("Current wallet balance:", tourist.wallet);
console.log("Attempting to deduct:", amount);
if (tourist.wallet < amount) {
console.log("Insufficient funds:", {
balance: tourist.wallet,
required: amount,
});
return res.status(400).json({
message: "Insufficient funds",
currentBalance: tourist.wallet,
requiredAmount: amount,
});
}
// Calculate and add loyalty points based on level
const earnedPoints = calculateLoyaltyPoints(tourist.level, amount);
tourist.loyaltypoints += earnedPoints;
// Update tourist level based on total points
tourist.level = determineTouristLevel(tourist.loyaltypoints);
tourist.wallet = tourist.wallet - amount;
await tourist.save();
console.log("New wallet balance:", tourist.wallet);
res.status(200).json({
success: true,
message: "Amount deducted from wallet successfully",
currentBalance: tourist.wallet,
earnedPoints,
totalPoints: tourist.loyaltypoints,
newLevel: tourist.level,
});
} catch (error) {
console.error("Deduct from wallet error:", error);
res.status(500).json({
message: "Server error",
error: error.message,
stack: process.env.NODE_ENV === "development" ? error.stack : undefined,
});
}
};
BE Create a Promo Code Admin Controller Example
export const createPromoCode = async (req, res) => {
const { code, discount, expiryDate, usageLimit } = req.body;
if (!code || !discount || !expiryDate || !usageLimit) {
return res.status(400).json({ message: "All fields are required" });
}
try {
// Check if promo code already exists
const existingPromoCode = await PromoCode.findOne({ code });
if (existingPromoCode) {
return res.status(400).json({ message: "Promo code already exists" });
}
// Create new promo code
const newPromoCode = new PromoCode({
code,
discount,
expiryDate,
usageLimit,
});
await newPromoCode.save();
res.status(201).json({
message: "Promo code created successfully",
promoCode: newPromoCode,
});
} catch (error) {
res.status(500).json({ message: "Error creating promo code", error });
}
};
BE Send Emails To Users Example
import nodemailer from "nodemailer";
import dotenv from "dotenv";
dotenv.config();
// Create a transporter using Brevo's SMTP server
const sendEmail = async (to, subject, text, html = "") => {
try {
const transporter = nodemailer.createTransport({
host: "smtp-relay.brevo.com", // Brevo's SMTP server
port: 587, // STARTTLS
secure: false, // false for STARTTLS
auth: {
user: process.env.BREVO_EMAIL, // Your Brevo email address (configured in .env)
pass: process.env.BREVO_API_KEY, // Your Brevo API key (configured in .env)
},
});
const mailOptions = {
from: "[email protected]", // Hardcoded sender email
to, // Recipient(s)
subject, // Subject line
text, // Plain text body
html, // Optional: HTML body
};
const info = await transporter.sendMail(mailOptions);
console.log("Email sent: ", info.response);
} catch (error) {
console.error("Error sending email: ", error);
throw error;
}
};
export default sendEmail;
BE Activity Package Model Example
import mongoose from "mongoose";
const activitySchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
trim: true,
},
description: {
type: String,
required: true,
trim: true,
},
date: {
type: Date,
required: true,
},
time: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: "ActivityCategory",
required: true,
},
tags: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Tag",
},
],
discounts: {
type: String,
},
bookingOpen: {
type: Boolean,
default: false,
},
location: {
type: {
type: String,
enum: ["Point"],
required: true,
},
coordinates: {
type: [Number],
required: true,
},
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "Advertiser",
required: true,
},
flagged: {
type: Boolean,
default: false,
required: true,
},
},
{ timestamps: true }
);
activitySchema.index({ location: "2dsphere" });
const Activity = mongoose.model("Activity", activitySchema);
export default Activity;
Birthday Promo Code Generator Example
export const checkAndSendBirthdayPromos = async () => {
try {
const today = new Date();
console.log('Checking for birthdays:', {
month: today.getMonth() + 1,
day: today.getDate()
});
// Updated query to use dob instead of birthDate
const users = await Tourist.find({
$expr: {
$and: [
{ $eq: [{ $month: '$dob' }, today.getMonth() + 1] },
{ $eq: [{ $dayOfMonth: '$dob' }, today.getDate()] }
]
}
});
console.log('Found users with birthdays:', users);
const results = await Promise.all(users.map(createAndSendBirthdayPromo));
return results;
} catch (error) {
console.error('Error processing birthday promos:', error);
throw error;
}
};
FE Admin Dashboard Routes Example
<Route path="/admin" element={<AdminHomePage />} />
<Route path="/admin/manage-users" element={<ListUsers />} />
<Route
path="/admin/activity-categories"
element={<ActivityCategoryManagement />}
/>
<Route
path="/admin/forgot-password"
element={<ForgetPasswordAdmin />}
/>
<Route path="/admin/promo-codes" element={<PromoCodeManager />} />
<Route
path="/admin/preference-tags"
element={<PreferenceTagManagement />}
/>
<Route path="/admin/complaints" element={<Complaints />} />
<Route
path="/admin/content-moderation"
element={<ContentModeration />}
/>
<Route path="/admin/view-documents" element={<ViewDocuemnts />} />
<Route
path="/admin/change-password"
element={<AdminChangePassword />}
/>
<Route path="/admin/sales-report" element={<AdminSalesReport />} />
FE Tourist Dashboard Page Routes Example
{/* Tourist Routes */}
<Route path="/tourist" element={<TouristHomePage />} />
<Route path="/tourist/register" element={<TouristRegister />} />
<Route path="/tourist/view-events" element={<ViewEvents />} />
<Route path="/tourist/my-profile" element={<MyProfile />} />
<Route
path="/tourist/forgot-password"
element={<ForgetPasswordTourist />}
/>
<Route
path="/tourist/my-complaints"
element={<TouristComplaints />}
/>
<Route
path="/tourist/itinerary-filter"
element={<ItineraryFilter />}
/>
<Route
path="/tourist/filtered-activities"
element={<FilteredActivities />}
/>
<Route path="/tourist/wishlist" element={<WishlistPage />} />
<Route path="/tourist/products" element={<ProductTouristPage />} />
<Route path="/tourist/purchases" element={<MyPurchasesPage />} />
<Route path="/tourist/complaints" element={<CreateComplaint />} />
<Route path="/tourist/book-flight" element={<FlightBooking />} />
<Route path="/tourist/book-hotel" element={<HotelBooking />} />
<Route path="/tourist/saved-events" element={<SavedEvents />} />
<Route
path="/tourist/notifications"
element={<NotificationsPage />}
/>
<Route path="/tourist/guide" element={<VacationGuide />} />
<Route
path="/tourist/hotel-bookings"
element={<HotelBookings />}
/>{" "}
<Route path="/tourist/view-bookings" element={<ViewBookings />} />
<Route
path="/tourist/book-transportation"
element={<BookTransportation />}
/>
<Route
path="/tourist/change-password"
element={<TouristChangePassword />}
/>
{/* Review System Routes */}
<Route
path="/tourist/reviews/tour-guides"
element={<RateTourGuides />}
/>
<Route
path="/tourist/delivery-addresses"
element={<DeliveryAddresses />}
/>
<Route path="/tourist/reviews/events" element={<EventReviews />} />
<Route
path="/tourist/reviews/products"
element={<ProductReviews />}
/>
<Route path="/tourist/my-reviews" element={<MyReviews />} />
<Route path="/tourist/reviews" element={<ReviewsSystem />} />{" "}
mkdir Tripify
cd Tripify
- Clone this repo
git clone https://github.com/Advanced-computer-lab-2024/Tripify
- Install dependencies for frontend
cd Tripify/frontend
npm install
- Install dependencies for backend
cd Tripify/backend
npm install
Admin Endpoints
- POST
/api/admin/register
- Description: Register a new admin user
- Request Body:
{ "username": "string", "email": "string", "password": "string" }
- Response:
- Status: 201 Created
{ "message": "Admin registered successfully", "admin": { "id": "string", "username": "string", "email": "string" }, "token": "string" }
- POST
/api/admin/login
- Description: Authenticate an admin user
- Request Body:
{ "username": "string", // Can be username or email "password": "string" }
- Response:
- Status: 200 OK
{ "message": "Login successful", "admin": { "id": "string", "username": "string", "email": "string" }, "token": "string" }
- POST
/api/admin/change-password
- Description: Change admin's password (requires authentication)
- Request Body:
{ "currentPassword": "string", "newPassword": "string" }
- Response:
- Status: 200 OK
{ "message": "Password updated successfully" }
- GET
/api/admin/users
- Description: Get list of all users across all user types (requires admin authentication)
- Response:
- Status: 200 OK
[ { "username": "string", "email": "string", "userType": "string", // "Tour Guide" | "Tourist" | "Advertiser" | "Seller" | "Admin" "_id": "string", // Other user properties... } ]
- DELETE
/api/admin/users
- Description: Delete a user of any type (requires admin authentication)
- Request Body:
{ "userId": "string", "userType": "string" // "Tour Guide" | "Tourist" | "Advertiser" | "Seller" | "Admin" }
- Response:
- Status: 200 OK
{ "message": "User deleted successfully", "deletedUser": { "id": "string", "userType": "string", "email": "string", "username": "string" } }
- GET
/api/admin/unverified/api/seller
- Get unverified sellers - GET
/api/admin/unverified/api/advertiser
- Get unverified advertisers - GET
/api/admin/unverified/tour-guides
- Get unverified tour guides - Response:
- Status: 200 OK
[ { "username": "string", "email": "string", "createdAt": "date", "isVerified": false, // Documents specific to user type... } ]
- POST
/api/admin/verify/seller/:id
- POST
/api/admin/verify/advertiser/:id
- POST
/api/admin/verify/tour-guide/:id
- Request Body:
{ "isApproved": "boolean" }
- Response:
- Status: 200 OK
{ "message": "User approved/rejected successfully" }
- POST
/api/admin/promo-codes
- Request Body:
{ "code": "string", "discount": "number", "expiryDate": "date", "usageLimit": "number" }
- Response:
- Status: 201 Created
{ "message": "Promo code created successfully", "promoCode": { // Promo code details } }
- PUT
/api/admin/promo-codes/:id
- Request Body:
{ "code": "string", "discount": "number", "expiryDate": "date", "usageLimit": "number", "isActive": "boolean" }
- DELETE
/api/admin/promo-codes/:id
- GET
/api/admin/promo-codes
- GET
/api/admin/stats/users
- Response:
{ "totalUsers": "number", "newUsersThisMonth": "number" }
- GET
/api/admin/sales/itineraries
- Get itinerary sales - GET
/api/admin/sales/activities
- Get activity sales - GET
/api/admin/sales/products
- Get product sales - GET
/api/admin/sales/historical-places
- Get historical place sales - Response:
[ { "purchaseDate": "date", "totalPrice": "number", "itemName": "string", "bookingId/purchaseId": "string" } ]
- POST
/api/admin/send-password-reset-otp
- Request Body:
{ "email": "string" }
- POST
/api/admin/verify-password-reset-otp
- Request Body:
{ "email": "string", "otp": "string" }
- POST
/api/admin/reset-password
- Request Body:
{ "email": "string", "newPassword": "string" }
Tourist Endpoints
- POST
/api/tourist/register
- Description: Register a new tourist user
- Request Body:
{ "email": "string", "username": "string", "password": "string", "mobileNumber": "string", "nationality": "string", "dob": "date", "jobStatus": "student" | "job", "jobTitle": "string" // Required if jobStatus is "job" }
- Response:
- Status: 201 Created
{ "message": "Tourist registered successfully", "tourist": { "id": "string", "email": "string", "username": "string", "mobileNumber": "string", "nationality": "string", "dob": "date", "jobStatus": "string", "jobTitle": "string", "wallet": 0 }, "token": "string" }
- POST
/api/tourist/login
- Description: Authenticate a tourist user
- Request Body:
{ "username": "string", // Can be username or email "password": "string" }
- Response:
- Status: 200 OK
{ "message": "Login successful", "tourist": { "id": "string", "email": "string", "username": "string", "mobileNumber": "string", "nationality": "string", "dob": "date", "jobStatus": "string", "jobTitle": "string", "wallet": "number", "preferences": "object" }, "token": "string" }
- POST
/api/tourist/change-password
- Description: Change tourist's password (requires authentication)
- Request Body:
{ "currentPassword": "string", "newPassword": "string" }
- Response:
- Status: 200 OK
{ "message": "Password updated successfully" }
- GET
/api/tourist/:username
- Description: Get tourist profile details (requires authentication)
- Response:
{ "message": "Tourist profile fetched successfully", "tourist": { // Tourist profile details } }
- PUT
/api/tourist/:username
- Description: Update tourist profile (requires authentication)
- Request Body:
{ "email": "string", "mobileNumber": "string", "nationality": "string", "jobStatus": "string", "jobTitle": "string", "preferences": "object" }
- POST
/api/tourist/:id/wallet/add
- Request Body:
{ "amount": "number" }
- POST
/api/tourist/:id/wallet/deduct
- Request Body:
{ "amount": "number" }
- POST
/api/tourist/:id/wallet/refund
- Request Body:
{ "amount": "number" }
- GET
/api/tourist/:id/loyalty
- Response:
{ "success": true, "loyaltyStatus": { "points": "number", "level": "number", "nextLevelPoints": "number", "pointsToNextLevel": "number" } }
- POST
/api/tourist/:id/loyalty/redeem
- Request Body:
{ "pointsToRedeem": "number" // Must be multiple of 10,000 }
- POST
/api/tourist/addresses
- Request Body:
{ "street": "string", "city": "string", "state": "string", "postalCode": "string", "isDefault": "boolean" }
- GET
/api/tourist/addresses
- PUT
/api/tourist/addresses/:addressId
- DELETE
/api/tourist/addresses/:addressId
- PUT
/api/tourist/addresses/:addressId/default
- POST
/api/tourist/bookmarks
- Request Body:
{ "eventId": "string", "eventType": "HistoricalPlace" | "Activity" | "Itinerary" }
- GET
/api/tourist/bookmarks
- DELETE
/api/tourist/bookmarks/:eventId
- POST
/api/tourist/tour-guides/:tourGuideId/rate
- Request Body:
{ "rating": "number", // 1-5 "comment": "string" }
- GET
/api/tourist/:id/deletion-eligibility
- DELETE
/api/tourist/:id
- POST
/api/tourist/send-password-reset-otp
- Request Body:
{ "email": "string" }
- POST
/api/tourist/verify-password-reset-otp
- Request Body:
{ "email": "string", "otp": "string" }
- POST
/api/tourist/reset-password
- Request Body:
{ "email": "string", "newPassword": "string" }
Tour Guide Endpoints
# Tour Guide API Documentation- POST
/api/tourguide/register
- Description: Register a new tour guide
- Content Type:
multipart/form-data
- Request Body:
{ "username": "string", "email": "string", "password": "string", "mobileNumber": "string", "yearsOfExperience": "number", "previousWork": "array" }
- Required Files:
identificationDocument
: Filecertificate
: File
- Response:
- Status: 201 Created
{ "message": "Tour guide registered successfully", "tourguide": { "id": "string", "username": "string", "email": "string", "mobileNumber": "string", "yearsOfExperience": "number", "previousWork": "array", "identificationDocument": "string", "certificate": "string", "TandC": "boolean" }, "token": "string" }
- POST
/api/tourguide/login
- Description: Authenticate a tour guide
- Request Body:
{ "username": "string", // Can be username or email "password": "string" }
- Response:
- Status: 200 OK
{ "message": "Login successful", "tourguide": { "id": "string", "username": "string", "email": "string", "mobileNumber": "string", "yearsOfExperience": "number", "previousWork": "array", "TandC": "boolean" }, "token": "string" }
- GET
/api/tourguide/:username
- Description: Get tour guide profile by username (requires authentication)
- Response: Profile data excluding password
- GET
/api/tourguide/profile
- Description: Get tour guide profile using JWT token (requires authentication)
- Response:
{ "message": "Profile fetched successfully", "tourGuide": { "id": "string", "username": "string", "email": "string", "mobileNumber": "string", "yearsOfExperience": "number", "previousWork": "array", "TandC": "boolean" } }
- PUT
/api/tourguide/:username
- Description: Update tour guide profile (requires authentication)
- Request Body:
{ "email": "string", "mobileNumber": "string", "yearsOfExperience": "number", "previousWork": [ { "jobTitle": "string", "company": "string", "description": "string", "startDate": "date", "endDate": "date" } ], "TandC": "boolean" }
- POST
/api/tourguide/profile-picture
- Description: Upload or update profile picture (requires authentication)
- Content Type:
multipart/form-data
- Request Body:
profilePicture
: File
- Response:
{ "message": "Profile picture uploaded successfully", "profilePicture": { "filename": "string", "path": "string", "mimetype": "string", "size": "number", "uploadDate": "date" } }
- POST
/api/tourguide/change-password
- Description: Change tour guide password (requires authentication)
- Request Body:
{ "currentPassword": "string", "newPassword": "string" }
- POST
/api/tourguide/send-password-reset-otp
- Request Body:
{ "email": "string" }
- POST
/api/tourguide/verify-password-reset-otp
- Request Body:
{ "email": "string", "otp": "string" }
- POST
/api/tourguide/reset-password
- Request Body:
{ "email": "string", "newPassword": "string" }
- GET
/api/tourguide/itineraries
- Description: Get all itineraries created by the tour guide (requires authentication)
- Response: Array of itineraries with preference tags, sorted by creation date
- GET
/api/tourguide
- Description: Get list of all tour guides
- Response: Array of tour guide profiles (excluding passwords)
- DELETE
/api/tourguide/:id
- Description: Delete tour guide account (requires authentication)
- Side Effects:
- All associated itineraries are marked as inactive
- Tour guide profile and related data are deleted
Seller Endpoints
# Seller API Documentation- POST
/api/seller/register
- Description: Register a new seller
- Content Type:
multipart/form-data
- Request Body:
{ "username": "string", "email": "string", "password": "string", "name": "string", "description": "string", "mobileNumber": "string" }
- Required Files:
businessLicense
: FileidentificationDocument
: File
- Response:
- Status: 201 Created
{ "message": "Seller registered successfully", "seller": { "id": "string", "username": "string", "email": "string", "name": "string", "description": "string", "mobileNumber": "string", "businessLicense": "string", "identificationDocument": "string", "TandC": "boolean" }, "token": "string" }
- POST
/api/seller/login
- Description: Authenticate a seller
- Request Body:
{ "username": "string", // Can be username or email "password": "string" }
- Response:
- Status: 200 OK
{ "message": "Login successful", "seller": { "id": "string", "username": "string", "email": "string", "name": "string", "description": "string" }, "token": "string" }
- GET
/api/seller/:username
- Description: Get seller profile (requires authentication)
- Response:
{ "seller": { "id": "string", "username": "string", "email": "string", "name": "string", "description": "string" } }
- PUT
/api/seller/:id
- Description: Update seller profile (requires authentication)
- Request Body:
{ "username": "string", "email": "string", "name": "string", "description": "string", "TandC": "boolean" }
- Response:
{ "message": "Seller updated successfully", "seller": { "id": "string", "username": "string", "email": "string", "name": "string", "description": "string", "TandC": "boolean" } }
- POST
/api/seller/change-password
- Description: Change seller password (requires authentication)
- Request Body:
{ "currentPassword": "string", "newPassword": "string" }
- POST
/api/seller/send-password-reset-otp
- Request Body:
{ "email": "string" }
- POST
/api/seller/verify-password-reset-otp
- Request Body:
{ "email": "string", "otp": "string" }
- POST
/api/seller/reset-password
- Request Body:
{ "email": "string", "newPassword": "string" }
- GET
/seller
- Description: Get list of all sellers
- Response: Array of seller profiles (excluding passwords)
- DELETE
/seller/:id
- Description: Delete seller account (requires authentication)
- Response:
{ "message": "Seller account deleted successfully" }
Common error responses across all endpoints:
- Status: 401 Unauthorized
{ "message": "Invalid username or password" }
- Status: 403 Forbidden
{ "message": "Unauthorized access" }
- Status: 404 Not Found
{ "message": "Seller not found" }
- Status: 400 Bad Request
{ "message": "Error message describing the validation failure" }
- Status: 500 Internal Server Error
{ "message": "Error message describing the problem", "error": "Detailed error information" }
Tourism Governor Endpoints
- POST
/api/tourismGovernor/register
- Description: Register a new tourism governor
- Request Body:
{ "username": "string", "email": "string", "password": "string" }
- Response:
- Status: 201 Created
{ "message": "Tourism Governor registered successfully", "governor": { "id": "string", "username": "string", "email": "string" }, "token": "string" }
- POST
/api/tourismGovernor/login
- Description: Authenticate a tourism governor
- Request Body:
{ "username": "string", // Can be username or email "password": "string" }
- Response:
- Status: 200 OK
{ "message": "Login successful", "governor": { "id": "string", "username": "string", "email": "string" }, "token": "string" }
- GET
/api/tourismGovernor/profile
- Description: Get tourism governor profile (requires authentication)
- Response:
{ "message": "Profile retrieved successfully", "governor": { // Governor profile details excluding password } }
- PUT
/api/tourismGovernor/profile
- Description: Update tourism governor profile (requires authentication)
- Request Body: Any profile fields to update (password, _id, and role cannot be updated)
- Response:
{ "message": "Profile updated successfully", "governor": { // Updated governor profile } }
- GET
/api/tourismGovernor
- Description: Get list of all tourism governors
- Optional Parameter:
:id
- Get specific governor by ID - Response: Array of governor profiles or single governor profile
- POST
/api/tourismGovernor/change-password
- Description: Change governor's password (requires authentication)
- Request Body:
{ "currentPassword": "string", "newPassword": "string" }
- POST
/api/tourismGovernor/send-password-reset-otp
- Request Body:
{ "email": "string" }
- POST
/api/tourismGovernor/verify-password-reset-otp
- Request Body:
{ "email": "string", "otp": "string" }
- POST
/api/tourismGovernor/reset-password
- Request Body:
{ "email": "string", "newPassword": "string" }
- GET
/api/tourismGovernor/places
- Description: Get all historical places created by the governor (requires authentication)
- Response:
{ "message": "Places retrieved successfully", "places": [ // Array of historical places with tags populated ] }
- POST
/api/tourismGovernor/places
- Description: Create a new historical place (requires authentication)
- Request Body: Historical place details
- Response:
{ "message": "Historical place created successfully", "place": { // Created place details with tags and creator populated } }
- PUT
/api/tourismGovernor/places/:id
- Description: Update a historical place (requires authentication)
- Request Body: Fields to update
- Response:
{ "message": "Historical place updated successfully", "place": { // Updated place details } }
- DELETE
/api/tourismGovernor/places/:id
- Description: Delete a historical place (requires authentication)
- Response:
{ "message": "Historical place deleted successfully" }
- Status: 401 Unauthorized
{ "message": "Invalid credentials" }
- Status: 403 Forbidden
{ "message": "Unauthorized access" }
- Status: 404 Not Found
{ "message": "Tourism Governor not found" }
- Status: 404 Not Found
{ "message": "Historical place not found or unauthorized" }
- Status: 400 Bad Request
{ "message": "Error description" }
- Status: 500 Internal Server Error
{ "message": "Error description", "error": "Detailed error message" }
Advertiser Endpoints
- POST
/api/advertiser/register
- Description: Register a new advertiser
- Content Type:
multipart/form-data
- Request Body:
{ "username": "string", "email": "string", "password": "string", "companyName": "string", "companyDescription": "string", "website": "string", "hotline": "string" }
- Required Files:
businessLicense
: FileidentificationDocument
: File
- Response:
- Status: 201 Created
{ "message": "Advertiser registered successfully", "advertiser": { "id": "string", "username": "string", "email": "string", "companyName": "string", "companyDescription": "string", "website": "string", "hotline": "string", "businessLicense": "string", "identificationDocument": "string" }, "token": "string" }
- POST
/api/advertiser/login
- Description: Authenticate an advertiser
- Request Body:
{ "username": "string", // Can be username or email "password": "string" }
- Response:
- Status: 200 OK
{ "message": "Login successful", "advertiser": { "id": "string", "username": "string", "email": "string", "companyName": "string", "companyDescription": "string", "website": "string", "hotline": "string", "companyLogo": "string" }, "token": "string" }
- GET
/api/advertiser/:username
- Description: Get advertiser profile (requires authentication)
- Response:
{ "id": "string", "username": "string", "email": "string", "companyName": "string", "companyDescription": "string", "website": "string", "hotline": "string", "companyLogo": "string", "TandC": "boolean" }
- PUT
/api/advertiser/:username
- Description: Update advertiser profile (requires authentication)
- Request Body:
{ "email": "string", "companyName": "string", "companyDescription": "string", "website": "string", "hotline": "string", "TandC": "boolean" }
- POST
/api/advertiser/change-password
- Description: Change advertiser password (requires authentication)
- Request Body:
{ "currentPassword": "string", "newPassword": "string" }
- POST
/api/advertiser/send-password-reset-otp
- Request Body:
{ "email": "string" }
- POST
/api/advertiser/verify-password-reset-otp
- Request Body:
{ "email": "string", "otp": "string" }
- POST
/api/advertiser/reset-password
- Request Body:
{ "email": "string", "newPassword": "string" }
- GET
/api/advertiser/activities
- Description: Get all activities created by the advertiser (requires authentication)
- Response: Array of activities with category and tags populated
- GET
/api/advertiser/:id/sales-report
- Description: Get sales report for advertiser's activities
- Response:
{ "success": true, "data": { "bookings": [ // Array of confirmed/attended bookings ], "activities": [ // Array of non-flagged activities ] } }
- GET
/api/advertiser
- Description: Get list of all advertisers
- Response: Array of advertiser profiles (excluding passwords)
- GET
/api/advertiser/:id
- Description: Get specific advertiser by ID (requires authentication)
- Response: Advertiser profile with active ads populated
- DELETE
/api/advertiser/:id
- Description: Delete advertiser account (requires authentication)
- Side Effects:
- All associated activities are marked as inactive
- Advertiser profile and related data are deleted
- Status: 401 Unauthorized
{ "message": "Invalid username or password" }
- Status: 403 Forbidden
{ "message": "Unauthorized access" }
- Status: 404 Not Found
{ "message": "Advertiser not found" }
- Status: 400 Bad Request
{ "message": "Error description" }
- Status: 500 Internal Server Error
{ "message": "Error description", "error": "Detailed error message" }
Activity Endpoints
# Activity API Documentation- POST
/activities
- Description: Create a new activity
- Request Body: Activity details
{ "name": "string", "category": "ObjectId", "tags": ["ObjectId"], "createdBy": "ObjectId", // Advertiser ID "description": "string", "price": "number", // Other activity fields }
- Response:
- Status: 201 Created
{ "_id": "string", "name": "string", "category": "ObjectId", "tags": ["ObjectId"], "createdBy": "ObjectId", // All activity fields }
- GET
/activities
- Description: Retrieve all non-flagged activities with populated references
- Response:
- Status: 200 OK
[ { "_id": "string", "name": "string", "category": { "_id": "string", "name": "string", // Other category fields }, "tags": [ { "_id": "string", "name": "string", // Other tag fields } ], "advertiser": [ { // Advertiser information } ], // Other activity fields } ]
- Notes:
- Excludes activities where advertiser no longer exists
- Excludes flagged activities
- Populates category and tags information
- GET
/activities/:id
- Description: Retrieve a specific activity by ID
- Response:
- Status: 200 OK
{ "_id": "string", "name": "string", "category": { "_id": "string", "name": "string" }, "tags": [ { "_id": "string", "name": "string" } ], // Other activity fields }
- PUT
/activities/:id
- Description: Update an activity by ID
- Request Body: Fields to update
{ "name": "string", "category": "ObjectId", "tags": ["ObjectId"], // Any other fields to update }
- Response:
- Status: 200 OK
- Returns updated activity with populated category and tags
- DELETE
/activities/:id
- Description: Delete an activity by ID
- Response:
- Status: 204 No Content
- PATCH
/activities/:id/flag
- Description: Flag or unflag an activity for review
- Request Body:
{ "flagged": "boolean" }
- Response:
- Status: 200 OK
- Returns updated activity
- Side Effects:
- Sends email notification to activity creator
- Creates system notification for advertiser
- Email Template:
<div> <h2>Activity Flagged Notification</h2> <p>Activity Details:</p> <ul> <li>Name: [activity name]</li> <li>Category: [category name]</li> <li>Status: Flagged</li> </ul> </div>
- Status: 404 Not Found
{ "message": "Activity not found" }
- Status: 400 Bad Request
{ "message": "Error description" }
- Status: 500 Internal Server Error
{ "message": "Error description" }
-
All responses include populated category and tags information where applicable
-
Activities are automatically filtered to exclude those with non-existent advertisers
-
Flagged activities are excluded from general queries
-
System notifications are created with high priority for flagged activities
-
Email notifications are sent using HTML templates with activity details
Itinerary Endpoints
# Itinerary API Documentation- POST
/itineraries
- Description: Create a new itinerary
- Request Body:
{ "itineraryData": { "name": "string", "preferenceTags": ["ObjectId"], "timeline": [ { "activity": "ObjectId", "time": "string" } ], // Other itinerary fields }, "tourGuideId": "ObjectId" }
- Response:
- Status: 201 Created
{ "_id": "string", "name": "string", "preferenceTags": ["ObjectId"], "timeline": [ { "activity": "ObjectId", "time": "string" } ], "createdBy": "ObjectId" }
- GET
/itineraries/:id
- Description: Retrieve a specific itinerary with populated references
- Response:
{ "_id": "string", "name": "string", "bookings": [ // Populated booking information ], "createdBy": { "name": "string" }, "timeline": [ { "activity": { "name": "string" }, "time": "string" } ], "preferenceTags": [ { "name": "string" } ] }
- GET
/itineraries
- Description: Retrieve all itineraries with populated references
- Response:
[ { "_id": "string", "createdBy": { "username": "string" }, "preferenceTags": [ { "name": "string" } ], "flagged": "boolean" } ]
- PUT
/itineraries/:id
- Description: Update an itinerary by ID
- Request Body: Fields to update
- Response: Updated itinerary with populated references
- DELETE
/itineraries/:id
- Description: Delete an itinerary by ID
- Response:
{ "message": "Itinerary deleted successfully" }
- GET
/itineraries/search
- Query Parameters:
query
: Search term
- Description: Search itineraries by name, pickup location, or dropoff location
- Notes:
- Case-insensitive search
- Only returns active itineraries
- Response: Array of matching itineraries with populated references
- PATCH
/itineraries/:id/flag
- Description: Flag or unflag an itinerary for review
- Request Body:
{ "flagged": "boolean" }
- Side Effects:
- Sends email notification to tour guide
- Creates system notification
- Email Template:
<div> <h2>Itinerary Flagged Notification</h2> <p>Itinerary Details:</p> <ul> <li>Name: [itinerary name]</li> <li>Tags: [preference tags]</li> <li>Status: Flagged</li> </ul> </div>
- POST
/itineraries/:itineraryId/comments
- Description: Add a comment to an itinerary (requires authentication)
- Request Body:
{ "content": "string" }
- Response:
- Status: 201 Created
{ "_id": "string", "tourist": { "username": "string" }, "itinerary": "string", "content": "string", "createdAt": "date" }
- GET
/itineraries/:itineraryId/comments
- Description: Get all comments for an itinerary
- Response: Array of comments sorted by creation date (newest first)
[ { "_id": "string", "tourist": { "username": "string" }, "content": "string", "createdAt": "date" } ]
- Status: 404 Not Found
{ "message": "Itinerary not found" }
- Status: 400 Bad Request
{ "message": "Error description" }
- Status: 500 Internal Server Error
{ "message": "Error description" }
- All responses include populated references where applicable
- Comments are sorted by creation date in descending order
- Search functionality only returns active itineraries
- System notifications are created with high priority for flagged itineraries
- Email notifications include HTML templates with itinerary details
Historical Places Endpoints
# Historical Places API Documentation- POST
/historical-places
- Description: Create a new historical place
- Request Body:
{ "name": "string", "description": "string", "location": "string", "tags": ["ObjectId"], // Array of tag IDs "createdBy": "ObjectId", "ticketPrices": [ { "category": "string", "price": "number" } ], // Other historical place fields }
- Response:
- Status: 201 Created
{ "_id": "string", "name": "string", "description": "string", "location": "string", "tags": ["ObjectId"], "createdBy": "ObjectId", "ticketPrices": [ { "category": "string", "price": "number" } ] }
- Validation:
tags
must be an array of valid IDs
- GET
/historical-places
- Description: Retrieve all historical places with populated references
- Response:
[ { "_id": "string", "name": "string", "description": "string", "tags": [ { "_id": "string", "name": "string" // Other tag fields } ], "createdBy": { "_id": "string", "username": "string" // Other creator fields } // Other historical place fields } ]
- GET
/historical-places/:id
- Description: Retrieve a specific historical place with populated references
- Response:
{ "_id": "string", "name": "string", "description": "string", "tags": [ { "_id": "string", "name": "string" } ], "createdBy": { "_id": "string", "username": "string" } // Other fields }
- GET
/historical-places/filter
- Query Parameters:
tags
: Comma-separated list of tag IDs
- Description: Filter historical places by tags
- Example:
/historical-places/filter?tags=tag1Id,tag2Id
- Response:
[ { "_id": "string", "name": "string", "tags": [ { "_id": "string", "name": "string" } ], "createdBy": { "_id": "string", "username": "string" } } ]
- Notes:
- Returns all places if no tags are provided
- Returns 404 if no places match the tags
- PUT
/historical-places/:id
- Description: Update a historical place by ID
- Request Body: Fields to update
{ "name": "string", "description": "string", "tags": ["ObjectId"], // Any other fields to update }
- Response: Updated historical place with populated references
- DELETE
/historical-places/:id
- Description: Delete a historical place by ID
- Response:
{ "message": "Historical place deleted successfully" }
- Status: 404 Not Found
{ "message": "Historical place not found" }
- Status: 400 Bad Request
or
{ "message": "Tags must be an array of valid IDs" }
{ "message": "Error description" }
- Status: 500 Internal Server Error
{ "message": "Error description" }
- All GET responses use lean queries for better performance
- Responses include populated tag and creator references
- Tag filtering supports multiple tags using comma-separated values
- Creation and updates include validation for tag array format
- Deletion is performed using findByIdAndDelete for clean removal
Booking An Event Endpoints
# Booking API Documentation- POST
/bookings
- Description: Create a new booking for an activity, historical place, or itinerary
- Request Body:
{ "userId": "string", "bookingType": "Activity" | "HistoricalPlace" | "Itinerary", "itemId": "string", "bookingDate": "date" }
- Validation:
- Cannot book for past dates
- Activities must be booked for their specific date
- Itineraries must be booked on available dates
- Cannot double-book the same item on the same date
- GET
/bookings/check-availability
- Query Parameters:
{ "bookingType": "Activity" | "HistoricalPlace" | "Itinerary", "itemId": "string", "bookingDate": "date" }
- Response:
{ "success": true, "available": "boolean", "existingBookings": "number" }
- GET
/bookings/user/:userId
- Description: Get all bookings for a specific user
- Response: Array of bookings with populated item details
- GET
/bookings/user/:userId/upcoming
- Description: Get user's future bookings that aren't cancelled
- Response: Array of upcoming bookings sorted by date
- GET
/bookings/:bookingType/:itemId
- Description: Get all non-cancelled bookings for a specific item
- Response: Array of bookings with populated user details
- PATCH
/bookings/:bookingId/status
- Request Body:
{ "status": "confirmed" | "cancelled" | "attended" }
- POST
/bookings/:bookingId/cancel
- Validation:
- Must be cancelled at least 24 hours before booking date
- POST
/bookings/:bookingId/rate
- Request Body:
{ "rating": "number", // 1-5 "review": "string" }
- Validation:
- Only for attended bookings
- Rating must be 1-5
- Can only rate once
- PUT
/bookings/:bookingId/rate
- Request Body:
{ "rating": "number", // 1-5 "review": "string" }
- Validation:
- Must have existing rating
- Rating must be 1-5
- GET
/bookings/activities/:activityId/ratings
- Query Parameters:
page
: number (default: 1)limit
: number (default: 10)
- GET
/bookings/historical-places/:placeId/ratings
- Query Parameters:
page
: number (default: 1)limit
: number (default: 10)
- GET
/bookings/guides/:guideId/ratings
- Query Parameters:
page
: number (default: 1)limit
: number (default: 10)
- GET
/bookings/guides/:guideId/rating-stats
- Response:
{ "averageRating": "number", "totalRatings": "number", "ratingDistribution": { "1": "number", "2": "number", "3": "number", "4": "number", "5": "number" } }
- GET
/bookings/guides/:guideId/sales
- Response:
{ "bookings": [ { "platformFee": "number", "netAmount": "number" } ], "summary": { "totalRevenue": "number", "platformFees": "number", "netRevenue": "number", "totalBookings": "number" } }
- Status: 400 Bad Request
Common messages:
{ "success": false, "message": "Error description" }
- "Missing required fields"
- "Cannot book for past dates"
- "This date is already booked"
- "Rating must be between 1 and 5"
- "Can only rate attended bookings"
- Status: 404 Not Found
{ "success": false, "message": "Item/Booking not found" }
- Status: 500 Internal Server Error
{ "success": false, "message": "Error description" }
- All dates are handled in UTC
- Bookings can only be cancelled 24 hours before the booking date
- Platform fee is calculated at 10% of the total price
- Ratings are only allowed for attended bookings
- Pagination is available for rating endpoints
- Sales reports include both gross and net amounts after platform fees
We use Postman
to manually test all our api references by making sure the response is as expected. We use it as some kind of sanity-check. Automatic Jest tests will be available in the near future. Our current test suite includes 19 test cases covering:
- User Authentication & Registration
- Booking Management
- Payment Operations
- Content Management (Activities, Historical Places, Itineraries)
- Review Systems
- Complaint Handling
- Loyalty Program Operations
You can run our complete test suite yourself:
- Access our Tripify API Tests Collection
- Click "Run in Postman"
- Set up your environment variables: baseUrl: http://localhost:5000/api touristToken: [Your JWT token] advertiserToken: [Your JWT token] governorToken: [Your JWT token] guideToken: [Your JWT token]
- Run the tests individually or as a complete suite
Here's an example of testing one of our endpoints:
The test collection includes automated token handling and comprehensive validation for responses. Future updates will include automated Jest tests for continuous integration.
-
Make sure to follow the Installation steps first
-
Add a
.env
in thebackend
,frontend
of repoTripify
with the following variables (replace the values with your own).
NOTE We use port 5000 for the Backend Server and port 3000 for the Frontend Server but feel free to adjust it to your liking
MONGO_URI="<Your Mongo Connection String>"
BCRYPT_SALT="<A secret string to use for encrypting passwords>"
JWT_SECRET="<A secret string to use for hashing JWT tokens>"
REACT_APP_AMADEUS_API_KEY="<Your AMADEUS API KEY>"
REACT_APP_AMADEUS_API_SECRET="<Your AMADEUS API SECRET>"
BREVO_EMAIL="<Your Brevo EMail>"
BREVO_API_KEY="<Your Brevo API Key>"
REACT_APP_GOOGLE_MAPS_API_KEY="<Your Google Maps Developer API Key>"
- Start Backend Server
cd Tripify/backend
npm run dev
- Start Frontend Server in a different terminal
cd Tripify/frontend
npm start
- Visit our website
- Choose your user type
- Register/Login to access features
- Accept Terms & Conditions
-
Dashboard Access
- Log in with admin credentials
- View system statistics and user metrics
- Monitor all activities
-
User Management
- Verify new Tour Guides/Sellers/Advertisers
- Review uploaded documents
- Manage user accounts
-
Content Moderation
- Review flagged content
- Approve/reject activities
- Monitor historical places
-
Promotions
- Create promo codes
- Set discounts and validity periods
- Track promo code usage
-
Account Setup
- Create your profile
- Set preferences
- Add delivery addresses
-
Booking Activities
- Browse available activities
- Check dates and pricing
- Use filters to find perfect matches
- Book and pay securely
-
Transportation & Accommodation
- Book flights
- Reserve hotels
- Arrange transportation
-
Wallet & Loyalty
- Add funds to wallet
- Track loyalty points
- Redeem points for rewards
-
Social Features
- Rate experiences
- Write reviews
- Add items to wishlist
- File complaints if needed
-
Profile Management
- Upload required documents
- Set availability
- Update experience details
-
Itinerary Management
- Create new itineraries
- Set pricing and dates
- Add activities and details
- Manage bookings
-
Business Tools
- View booking calendar
- Track earnings
- Generate sales reports
- View tourist feedback
-
Store Setup
- Complete verification
- Upload business documents
- Set up product catalog
-
Product Management
- Add new products
- Update inventory
- Set prices
- Manage product images
-
Order Processing
- View incoming orders
- Process shipments
- Track deliveries
- Handle returns
-
Performance Tracking
- View sales reports
- Monitor ratings
- Track revenue
-
Account Setup
- Complete business verification
- Upload required documents
- Set company details
-
Activity Management
- Create new activities
- Set dates and pricing
- Upload activity images
- Manage bookings
-
Transportation Services
- List transportation options
- Set availability
- Manage bookings
- Update pricing
-
Analytics
- View booking statistics
- Track revenue
- Monitor activity performance
- Generate reports
-
Historical Place Management
- Add new historical places
- Update information
- Set ticket prices
- Manage availability
-
Content Control
- Update place descriptions
- Add historical context
- Upload images
- Manage tags
-
Monitoring
- Track visitor numbers
- View booking statistics
- Monitor ratings and reviews
-
Security
- Use strong passwords
- Enable notifications
- Keep contact info updated
- Review account activity
-
Support
- Access help documentation
- Contact customer service
- Report issues
- Submit feedback
-
Updates
- Check for new features
- Read system notifications
- Stay informed about changes
Need more help? Contact our support team at [email protected]
We welcome contributions to Tripify. If you want to contribute, it's as easy as:
- Fork the repo
- Create a new branch (
git checkout -b my-new-feature
) - Make changes
- Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
- Wait for your PR to be reviewed and merged
NOTE
We welcome all contributions, but please make sure to follow our code style and limting rules. You can check the Code Style section for more details.
- Mongoose docs
- Express docs
- ReactJs docs
- NodeJs docs
- TypeScript docs
- Docker docs
- Docker Compose docs
- ESLint docs
- Prettier docs
- MUI docs
- React Router docs
- React Hook Form docs
- React Query docs
- JSON Web Token
- Mongoose Crash Course
- Express Crash Course
- ReactJs Crash Course
- React Router Crash Course
- React Hook Form Crash Course
- React Query Crash Course
The software is open source under the Apache 2.0 License
.