I'll create a comprehensive tour agency API system with Node.js, MySQL database, and external API integration. Here's the setup:
<jitProject id="tour-agency-api" title="Tour Agency API with Node.js and MySQL">
<jitFile type="file" filePath="package.json">
{
"name": "tour-agency-api",
"version": "1.0.0",
"description": "Tour agency API with MySQL and external API integration",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"express": "^4.18.2",
"mysql2": "^3.6.0",
"sequelize": "^6.35.0",
"dotenv": "^16.3.1",
"cors": "^2.8.5",
"axios": "^1.6.2",
"jsonwebtoken": "^9.0.2",
"bcryptjs": "^2.4.3",
"morgan": "^1.10.0"
},
"devDependencies": {
"nodemon": "^3.0.2"
}
}
</jitFile>
<jitFile type="file" filePath="server.js">
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const morgan = require('morgan');
const { Sequelize } = require('sequelize');
const tourRoutes = require('./routes/tours');
const bookingRoutes = require('./routes/bookings');
const userRoutes = require('./routes/users');
const externalApiRoutes = require('./routes/externalApi');
const app = express();
// Middleware
app.use(cors());
app.use(express.json());
app.use(morgan('dev'));
// Database connection
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
host: process.env.DB_HOST,
dialect: 'mysql'
}
);
// Test DB connection
sequelize.authenticate()
.then(() => console.log('Database connected'))
.catch(err => console.error('Unable to connect to the database:', err));
// Routes
app.use('/api/tours', tourRoutes);
app.use('/api/bookings', bookingRoutes);
app.use('/api/users', userRoutes);
app.use('/api/external', externalApiRoutes);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
</jitFile>
<jitFile type="file" filePath=".env">
DB_NAME=tour_agency
DB_USER=root
DB_PASSWORD=your_mysql_password
DB_HOST=localhost
JWT_SECRET=your_jwt_secret_key
WEATHER_API_KEY=your_weather_api_key
CURRENCY_API_KEY=your_currency_api_key
</jitFile>
<jitFile type="file" filePath="models/Tour.js">
const { DataTypes } = require('sequelize');
const sequelize = require('../config/database');
const Tour = sequelize.define('Tour', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
title: {
type: DataTypes.STRING,
allowNull: false
},
description: {
type: DataTypes.TEXT,
allowNull: false
},
location: {
type: DataTypes.STRING,
allowNull: false
},
duration: {
type: DataTypes.INTEGER,
allowNull: false
},
price: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false
},
startDate: {
type: DataTypes.DATE,
allowNull: false
},
endDate: {
type: DataTypes.DATE,
allowNull: false
},
maxParticipants: {
type: DataTypes.INTEGER,
allowNull: false
},
imageUrl: {
type: DataTypes.STRING,
allowNull: true
}
}, {
timestamps: true
});
module.exports = Tour;
</jitFile>
<jitFile type="file" filePath="models/Booking.js">
const { DataTypes } = require('sequelize');
const sequelize = require('../config/database');
const Booking = sequelize.define('Booking', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
tourId: {
type: DataTypes.INTEGER,
allowNull: false
},
userId: {
type: DataTypes.INTEGER,
allowNull: false
},
bookingDate: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
participants: {
type: DataTypes.INTEGER,
allowNull: false
},
totalPrice: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false
},
status: {
type: DataTypes.ENUM('pending', 'confirmed', 'cancelled'),
defaultValue: 'pending'
}
}, {
timestamps: true
});
module.exports = Booking;
</jitFile>
<jitFile type="file" filePath="models/User.js">
const { DataTypes } = require('sequelize');
const sequelize = require('../config/database');
const bcrypt = require('bcryptjs');
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false
},
role: {
type: DataTypes.ENUM('user', 'admin'),
defaultValue: 'user'
}
}, {
timestamps: true,
hooks: {
beforeCreate: async (user) => {
user.password = await bcrypt.hash(user.password, 10);
}
}
});
User.prototype.validPassword = async function(password) {
return await bcrypt.compare(password, this.password);
};
module.exports = User;
</jitFile>
<jitFile type="file" filePath="config/database.js">
const { Sequelize } = require('sequelize');
require('dotenv').config();
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
host: process.env.DB_HOST,
dialect: 'mysql'
}
);
module.exports = sequelize;
</jitFile>
<jitFile type="file" filePath="routes/tours.js">
const express = require('express');
const router = express.Router();
const Tour = require('../models/Tour');
const { Op } = require('sequelize');
// Get all tours
router.get('/', async (req, res) => {
try {
const tours = await Tour.findAll();
res.json(tours);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
// Get single tour
router.get('/:id', async (req, res) => {
try {
const tour = await Tour.findByPk(req.params.id);
if (!tour) {
return res.status(404).json({ message: 'Tour not found' });
}
res.json(tour);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
// Create tour (admin only)
router.post('/', async (req, res) => {
try {
const tour = await Tour.create(req.body);
res.status(201).json(tour);
} catch (err) {
res.status(400).json({ message: err.message });
}
});
// Update tour (admin only)
router.put('/:id', async (req, res) => {
try {
const tour = await Tour.findByPk(req.params.id);
if (!tour) {
return res.status(404).json({ message: 'Tour not found' });
}
await tour.update(req.body);
res.json(tour);
} catch (err) {
res.status(400).json({ message: err.message });
}
});
// Delete tour (admin only)
router.delete('/:id', async (req, res) => {
try {
const tour = await Tour.findByPk(req.params.id);
if (!tour) {
return res.status(404).json({ message: 'Tour not found' });
}
await tour.destroy();
res.json({ message: 'Tour deleted' });
} catch (err) {
res.status(500).json({ message: err.message });
}
});
// Search tours
router.get('/search/:query', async (req, res) => {
try {
const tours = await Tour.findAll({
where: {
[Op.or]: [
{ title: { [Op.like]: `%${req.params.query}%` } },
{ location: { [Op.like]: `%${req.params.query}%` } },
{ description: { [Op.like]: `%${req.params.query}%` } }
]
}
});
res.json(tours);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
module.exports = router;
</jitFile>
<jitFile type="file" filePath="routes/bookings.js">
const express = require('express');
const router = express.Router();
const Booking = require('../models/Booking');
const Tour = require('../models/Tour');
const User = require('../models/User');
const { authenticate } = require('../middleware/auth');
// Get all bookings (admin only)
router.get('/', authenticate, async (req, res) => {
try {
if (req.user.role !== 'admin') {
return res.status(403).json({ message: 'Unauthorized' });
}
const bookings = await Booking.findAll({
include: [Tour, User]
});
res.json(bookings);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
// Get user's bookings
router.get('/my-bookings', authenticate, async (req, res) => {
try {
const bookings = await Booking.findAll({
where: { userId: req.user.id },
include: Tour
});
res.json(bookings);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
// Create booking
router.post('/', authenticate, async (req, res) => {
try {
const tour = await Tour.findByPk(req.body.tourId);
if (!tour) {
return res.status(404).json({ message: 'Tour not found' });
}
const booking = await Booking.create({
tourId: req.body.tourId,
userId: req.user.id,
participants: req.body.participants,
totalPrice: tour.price * req.body.participants
});
res.status(201).json(booking);
} catch (err) {
res.status(400).json({ message: err.message });
}
});
// Cancel booking
router.put('/:id/cancel', authenticate, async (req, res) => {
try {
const booking = await Booking.findByPk(req.params.id);
if (!booking) {
return res.status(404).json({ message: 'Booking not found' });
}
if (booking.userId !== req.user.id && req.user.role !== 'admin') {
return res.status(403).json({ message: 'Unauthorized' });
}
await booking.update({ status: 'cancelled' });
res.json(booking);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
module.exports = router;
</jitFile>
<jitFile type="file" filePath="routes/users.js">
const express = require('express');
const router = express.Router();
const User = require('../models/*_
<jitProject id="tour-agency-api" title="Tour Agency API with Node.js and MySQL">
<jitFile type="file" filePath="package.json">
{
"name": "tour-agency-api",
"version": "1.0.0",
"description": "Tour agency API with MySQL and external API integration",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"express": "^4.18.2",
"mysql2": "^3.6.0",
"sequelize": "^6.35.0",
"dotenv": "^16.3.1",
"cors": "^2.8.5",
"axios": "^1.6.2",
"jsonwebtoken": "^9.0.2",
"bcryptjs": "^2.4.3",
"morgan": "^1.10.0"
},
"devDependencies": {
"nodemon": "^3.0.2"
}
}
</jitFile>
<jitFile type="file" filePath="server.js">
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const morgan = require('morgan');
const { Sequelize } = require('sequelize');
const tourRoutes = require('./routes/tours');
const bookingRoutes = require('./routes/bookings');
const userRoutes = require('./routes/users');
const externalApiRoutes = require('./routes/externalApi');
const app = express();
// Middleware
app.use(cors());
app.use(express.json());
app.use(morgan('dev'));
// Database connection
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
host: process.env.DB_HOST,
dialect: 'mysql'
}
);
// Test DB connection
sequelize.authenticate()
.then(() => console.log('Database connected'))
.catch(err => console.error('Unable to connect to the database:', err));
// Routes
app.use('/api/tours', tourRoutes);
app.use('/api/bookings', bookingRoutes);
app.use('/api/users', userRoutes);
app.use('/api/external', externalApiRoutes);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
</jitFile>
<jitFile type="file" filePath=".env">
DB_NAME=tour_agency
DB_USER=root
DB_PASSWORD=your_mysql_password
DB_HOST=localhost
JWT_SECRET=your_jwt_secret_key
WEATHER_API_KEY=your_weather_api_key
CURRENCY_API_KEY=your_currency_api_key
</jitFile>
<jitFile type="file" filePath="models/Tour.js">
const { DataTypes } = require('sequelize');
const sequelize = require('../config/database');
const Tour = sequelize.define('Tour', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
title: {
type: DataTypes.STRING,
allowNull: false
},
description: {
type: DataTypes.TEXT,
allowNull: false
},
location: {
type: DataTypes.STRING,
allowNull: false
},
duration: {
type: DataTypes.INTEGER,
allowNull: false
},
price: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false
},
startDate: {
type: DataTypes.DATE,
allowNull: false
},
endDate: {
type: DataTypes.DATE,
allowNull: false
},
maxParticipants: {
type: DataTypes.INTEGER,
allowNull: false
},
imageUrl: {
type: DataTypes.STRING,
allowNull: true
}
}, {
timestamps: true
});
module.exports = Tour;
</jitFile>
<jitFile type="file" filePath="models/Booking.js">
const { DataTypes } = require('sequelize');
const sequelize = require('../config/database');
const Booking = sequelize.define('Booking', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
tourId: {
type: DataTypes.INTEGER,
allowNull: false
},
userId: {
type: DataTypes.INTEGER,
allowNull: false
},
bookingDate: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
participants: {
type: DataTypes.INTEGER,
allowNull: false
},
totalPrice: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false
},
status: {
type: DataTypes.ENUM('pending', 'confirmed', 'cancelled'),
defaultValue: 'pending'
}
}, {
timestamps: true
});
module.exports = Booking;
</jitFile>
<jitFile type="file" filePath="models/User.js">
const { DataTypes } = require('sequelize');
const sequelize = require('../config/database');
const bcrypt = require('bcryptjs');
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false
},
role: {
type: DataTypes.ENUM('user', 'admin'),
defaultValue: 'user'
}
}, {
timestamps: true,
hooks: {
beforeCreate: async (user) => {
user.password = await bcrypt.hash(user.password, 10);
}
}
});
User.prototype.validPassword = async function(password) {
return await bcrypt.compare(password, this.password);
};
module.exports = User;
</jitFile>
<jitFile type="file" filePath="config/database.js">
const { Sequelize } = require('sequelize');
require('dotenv').config();
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
host: process.env.DB_HOST,
dialect: 'mysql'
}
);
module.exports = sequelize;
</jitFile>
<jitFile type="file" filePath="routes/tours.js">
const express = require('express');
const router = express.Router();
const Tour = require('../models/Tour');
const { Op } = require('sequelize');
// Get all tours
router.get('/', async (req, res) => {
try {
const tours = await Tour.findAll();
res.json(tours);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
// Get single tour
router.get('/:id', async (req, res) => {
try {
const tour = await Tour.findByPk(req.params.id);
if (!tour) {
return res.status(404).json({ message: 'Tour not found' });
}
res.json(tour);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
// Create tour (admin only)
router.post('/', async (req, res) => {
try {
const tour = await Tour.create(req.body);
res.status(201).json(tour);
} catch (err) {
res.status(400).json({ message: err.message });
}
});
// Update tour (admin only)
router.put('/:id', async (req, res) => {
try {
const tour = await Tour.findByPk(req.params.id);
if (!tour) {
return res.status(404).json({ message: 'Tour not found' });
}
await tour.update(req.body);
res.json(tour);
} catch (err) {
res.status(400).json({ message: err.message });
}
});
// Delete tour (admin only)
router.delete('/:id', async (req, res) => {
try {
const tour = await Tour.findByPk(req.params.id);
if (!tour) {
return res.status(404).json({ message: 'Tour not found' });
}
await tour.destroy();
res.json({ message: 'Tour deleted' });
} catch (err) {
res.status(500).json({ message: err.message });
}
});
// Search tours
router.get('/search/:query', async (req, res) => {
try {
const tours = await Tour.findAll({
where: {
[Op.or]: [
{ title: { [Op.like]: `%${req.params.query}%` } },
{ location: { [Op.like]: `%${req.params.query}%` } },
{ description: { [Op.like]: `%${req.params.query}%` } }
]
}
});
res.json(tours);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
module.exports = router;
</jitFile>
<jitFile type="file" filePath="routes/bookings.js">
const express = require('express');
const router = express.Router();
const Booking = require('../models/Booking');
const Tour = require('../models/Tour');
const User = require('../models/User');
const { authenticate } = require('../middleware/auth');
// Get all bookings (admin only)
router.get('/', authenticate, async (req, res) => {
try {
if (req.user.role !== 'admin') {
return res.status(403).json({ message: 'Unauthorized' });
}
const bookings = await Booking.findAll({
include: [Tour, User]
});
res.json(bookings);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
// Get user's bookings
router.get('/my-bookings', authenticate, async (req, res) => {
try {
const bookings = await Booking.findAll({
where: { userId: req.user.id },
include: Tour
});
res.json(bookings);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
// Create booking
router.post('/', authenticate, async (req, res) => {
try {
const tour = await Tour.findByPk(req.body.tourId);
if (!tour) {
return res.status(404).json({ message: 'Tour not found' });
}
const booking = await Booking.create({
tourId: req.body.tourId,
userId: req.user.id,
participants: req.body.participants,
totalPrice: tour.price * req.body.participants
});
res.status(201).json(booking);
} catch (err) {
res.status(400).json({ message: err.message });
}
});
// Cancel booking
router.put('/:id/cancel', authenticate, async (req, res) => {
try {
const booking = await Booking.findByPk(req.params.id);
if (!booking) {
return res.status(404).json({ message: 'Booking not found' });
}
if (booking.userId !== req.user.id && req.user.role !== 'admin') {
return res.status(403).json({ message: 'Unauthorized' });
}
await booking.update({ status: 'cancelled' });
res.json(booking);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
module.exports = router;
</jitFile>
<jitFile type="file" filePath="routes/users.js">
const express = require('express');
const router = express.Router();
const User = require('../models/*_
I'll create a simple tour agency website that connects to the Node.js API we just set up. Here's the complete implementation:
<jitProject id="tour-agency-website" title="Tour Agency Website with API">
<jitFile type="file" filePath="index.html">
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Adventure Tours</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<div class="container">
<h1>Adventure Tours</h1>
<nav>
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#tours">Tours</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
<li><button id="loginBtn">Login</button></li>
</ul>
</nav>
</div>
</header>
<main>
<section id="home" class="hero">
<div class="container">
<h2>Discover Amazing Destinations</h2>
<p>Book your next adventure with us and create unforgettable memories.</p>
<button id="exploreBtn">Explore Tours</button>
</div>
</section>
<section id="tours" class="tours">
<div class="container">
<h2>Featured Tours</h2>
<div id="toursContainer" class="tours-grid"></div>
</div>
</section>
<section id="about" class="about">
<div class="container">
<h2>About Us</h2>
<p>We specialize in creating unique travel experiences around the world.</p>
</div>
</section>
<section id="contact" class="contact">
<div class="container">
<h2>Contact Us</h2>
<form id="contactForm">
<input type="text" placeholder="Your Name" required>
<input type="email" placeholder="Your Email" required>
<textarea placeholder="Your Message" required></textarea>
<button type="submit">Send Message</button>
</form>
</div>
</section>
</main>
<footer>
<div class="container">
<p>© 2023 Adventure Tours. All rights reserved.</p>
</div>
</footer>
<div id="loginModal" class="modal">
<div class="modal-content">
<span class="close">×</span>
<h2>Login</h2>
<form id="loginForm">
<input type="email" id="email" placeholder="Email" required>
<input type="password" id="password" placeholder="Password" required>
<button type="submit">Login</button>
</form>
</div>
</div>
<div id="bookingModal" class="modal">
<div class="modal-content">
<span class="close">×</span>
<h2>Book Tour</h2>
<form id="bookingForm">
<input type="hidden" id="tourId">
<input type="text" id="name" placeholder="Full Name" required>
<input type="email" id="email" placeholder="Email" required>
<input type="number" id="participants" placeholder="Number of Participants" min="1" required>
<button type="submit">Confirm Booking</button>
</form>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
</jitFile>
<jitFile type="file" filePath="styles.css">
<jitProject id="tour-agency-website" title="Tour Agency Website with API">
<jitFile type="file" filePath="index.html">
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Adventure Tours</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<div class="container">
<h1>Adventure Tours</h1>
<nav>
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#tours">Tours</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
<li><button id="loginBtn">Login</button></li>
</ul>
</nav>
</div>
</header>
<main>
<section id="home" class="hero">
<div class="container">
<h2>Discover Amazing Destinations</h2>
<p>Book your next adventure with us and create unforgettable memories.</p>
<button id="exploreBtn">Explore Tours</button>
</div>
</section>
<section id="tours" class="tours">
<div class="container">
<h2>Featured Tours</h2>
<div id="toursContainer" class="tours-grid"></div>
</div>
</section>
<section id="about" class="about">
<div class="container">
<h2>About Us</h2>
<p>We specialize in creating unique travel experiences around the world.</p>
</div>
</section>
<section id="contact" class="contact">
<div class="container">
<h2>Contact Us</h2>
<form id="contactForm">
<input type="text" placeholder="Your Name" required>
<input type="email" placeholder="Your Email" required>
<textarea placeholder="Your Message" required></textarea>
<button type="submit">Send Message</button>
</form>
</div>
</section>
</main>
<footer>
<div class="container">
<p>© 2023 Adventure Tours. All rights reserved.</p>
</div>
</footer>
<div id="loginModal" class="modal">
<div class="modal-content">
<span class="close">×</span>
<h2>Login</h2>
<form id="loginForm">
<input type="email" id="email" placeholder="Email" required>
<input type="password" id="password" placeholder="Password" required>
<button type="submit">Login</button>
</form>
</div>
</div>
<div id="bookingModal" class="modal">
<div class="modal-content">
<span class="close">×</span>
<h2>Book Tour</h2>
<form id="bookingForm">
<input type="hidden" id="tourId">
<input type="text" id="name" placeholder="Full Name" required>
<input type="email" id="email" placeholder="Email" required>
<input type="number" id="participants" placeholder="Number of Participants" min="1" required>
<button type="submit">Confirm Booking</button>
</form>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
</jitFile>
<jitFile type="file" filePath="styles.css">
Here's the complete implementation with all components working together:
// server.js
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const morgan = require('morgan');
const { Sequelize } = require('sequelize');
const path = require('path');
const app = express();
// Middleware
app.use(cors());
app.use(express.json());
app.use(morgan('dev'));
app.use(express.static(path.join(__dirname, 'public')));
// Database connection
const sequelize = new Sequelize(
process.env.DB_NAME || 'tour_agency',
process.env.DB_USER || 'root',
process.env.DB_PASSWORD || '',
{
host: process.env.DB_HOST || 'localhost',
dialect: 'mysql'
}
);
// Models
const Tour = sequelize.define('Tour', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
title: { type: Sequelize.STRING, allowNull: false },
description: { type: Sequelize.TEXT, allowNull: false },
location: { type: Sequelize.STRING, allowNull: false },
duration: { type: Sequelize.INTEGER, allowNull: false },
price: { type: Sequelize.DECIMAL(10, 2), allowNull: false },
startDate: { type: Sequelize.DATE, allowNull: false },
endDate: { type: Sequelize.DATE, allowByDefault: false },
maxParticipants: { type: Sequelize.INTEGER, allowNull: false },
imageUrl: { type: Sequelize.STRING, allowNull: true }
});
const Booking = sequelize.define('Booking', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
tourId: { type: Sequelize.INTEGER, allowNull: false },
name: { type: Sequelize.STRING, allowNull: false },
email: { type: Sequelize.STRING, allowNull: false },
participants: { type: Sequelize.INTEGER, allowNull: false },
status: { type: Sequelize.ENUM('pending', 'confirmed', 'cancelled'), defaultValue: 'pending' }
});
// Routes
app.get('/api/tours', async (req, res) => {
try {
const tours = await Tour.findAll();
res.json(tours);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.post('/api/bookings', async (req, res) => {
try {
const { tourId, name, email, participants } = req.body;
const booking = await Booking.create({ tourId, name, email, participants });
res.status(201).json(booking);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// Serve frontend
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// Initialize database and start server
async function startServer() {
try {
await sequelize.authenticate();
await sequelize.sync();
console.log('Database connected and synced');
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
} catch (err) {
console.error('Unable to connect to the database:', err);
}
}
startServer();
// server.js
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const morgan = require('morgan');
const { Sequelize } = require('sequelize');
const path = require('path');
const app = express();
// Middleware
app.use(cors());
app.use(express.json());
app.use(morgan('dev'));
app.use(express.static(path.join(__dirname, 'public')));
// Database connection
const sequelize = new Sequelize(
process.env.DB_NAME || 'tour_agency',
process.env.DB_USER || 'root',
process.env.DB_PASSWORD || '',
{
host: process.env.DB_HOST || 'localhost',
dialect: 'mysql'
}
);
// Models
const Tour = sequelize.define('Tour', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
title: { type: Sequelize.STRING, allowNull: false },
description: { type: Sequelize.TEXT, allowNull: false },
location: { type: Sequelize.STRING, allowNull: false },
duration: { type: Sequelize.INTEGER, allowNull: false },
price: { type: Sequelize.DECIMAL(10, 2), allowNull: false },
startDate: { type: Sequelize.DATE, allowNull: false },
endDate: { type: Sequelize.DATE, allowByDefault: false },
maxParticipants: { type: Sequelize.INTEGER, allowNull: false },
imageUrl: { type: Sequelize.STRING, allowNull: true }
});
const Booking = sequelize.define('Booking', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
tourId: { type: Sequelize.INTEGER, allowNull: false },
name: { type: Sequelize.STRING, allowNull: false },
email: { type: Sequelize.STRING, allowNull: false },
participants: { type: Sequelize.INTEGER, allowNull: false },
status: { type: Sequelize.ENUM('pending', 'confirmed', 'cancelled'), defaultValue: 'pending' }
});
// Routes
app.get('/api/tours', async (req, res) => {
try {
const tours = await Tour.findAll();
res.json(tours);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.post('/api/bookings', async (req, res) => {
try {
const { tourId, name, email, participants } = req.body;
const booking = await Booking.create({ tourId, name, email, participants });
res.status(201).json(booking);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// Serve frontend
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// Initialize database and start server
async function startServer() {
try {
await sequelize.authenticate();
await sequelize.sync();
console.log('Database connected and synced');
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
} catch (err) {
console.error('Unable to connect to the database:', err);
}
}
startServer();
// public/script.js
document.addEventListener('DOMContentLoaded', () => {
// DOM elements
const toursContainer = document.getElementById('toursContainer');
const exploreBtn = document.getElementById('exploreBtn');
const loginBtn = document.getElementById('loginBtn');
const loginModal = document.getElementById('loginModal');
const loginForm = document.getElementById('loginForm');
const bookingModal = document.getElementById('bookingModal');
const bookingForm = document.getElementById('bookingForm');
const closeButtons = document.querySelectorAll('.close');
// Fetch tours from API
async function fetchTours() {
try {
const response = await fetch('/api/tours');
const tours = await response.json();
displayTours(tours);
} catch (err) {
console.error('Error fetching tours:', err);
}
}
// Display tours
function displayTours(tours) {
toursContainer.innerHTML = '';
tours.forEach(tour => {
const tourCard = document.createElement('div');
tourCard.className = 'tour-card';
tourCard.innerHTML = `
<img src="${tour.imageUrl || 'https://via.placeholder.com/300'}" alt="${tour.title}">
<h3>${tour.title}</h3>
<p>${tour.location} - ${tour.duration} days</p>
<p>$${tour.price}</p>
<button class="book-btn" data-id="${tour.id}">Book Now</button>
`;
toursContainer.appendChild(tourCard);
});
// Add event listeners to book buttons
document.querySelectorAll('.book-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const tourId = e.target.getAttribute('data-id');
document.getElementById('tourId').value = tourId;
bookingModal.style.display = 'block';
});
});
}
// Handle booking form submission
bookingForm.addEventListener('submit', async (e) => {
e.preventDefault();
const tourId = document.getElementById('tourId').value;
const name = document.getElementById('name').value;
const email = document.getElementById('email').value;
const participants = document.getElementById('participants').value;
try {
const response = await fetch('/api/bookings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ tourId, name, email, participants }),
});
if (response.ok) {
alert('Booking successful!');
bookingModal.style.display = 'none';
bookingForm.reset();
} else {
const error = await response.json();
alert(`Error: ${error.error}`);
}
} catch (err) {
alert('Error submitting booking');
console.error(err);
}
});
// Modal controls
loginBtn.addEventListener('click', () => {
loginModal.style.display = 'block';
});
exploreBtn.addEventListener('click', () => {
document.getElementById('tours').scrollIntoView({ behavior: 'smooth' });
});
closeButtons.forEach(btn => {
btn.addEventListener('click', () => {
loginModal.style.display = 'none';
bookingModal.style.display = 'none';
});
});
window.addEventListener('click', (e) => {
if (e.target === loginModal) {
loginModal.style.display = 'none';
}
if (e.target === bookingModal) {
bookingModal.style.display = 'none';
}
});
// Initialize
fetchTours();
});
// public/script.js
document.addEventListener('DOMContentLoaded', () => {
// DOM elements
const toursContainer = document.getElementById('toursContainer');
const exploreBtn = document.getElementById('exploreBtn');
const loginBtn = document.getElementById('loginBtn');
const loginModal = document.getElementById('loginModal');
const loginForm = document.getElementById('loginForm');
const bookingModal = document.getElementById('bookingModal');
const bookingForm = document.getElementById('bookingForm');
const closeButtons = document.querySelectorAll('.close');
// Fetch tours from API
async function fetchTours() {
try {
const response = await fetch('/api/tours');
const tours = await response.json();
displayTours(tours);
} catch (err) {
console.error('Error fetching tours:', err);
}
}
// Display tours
function displayTours(tours) {
toursContainer.innerHTML = '';
tours.forEach(tour => {
const tourCard = document.createElement('div');
tourCard.className = 'tour-card';
tourCard.innerHTML = `
<img src="${tour.imageUrl || 'https://via.placeholder.com/300'}" alt="${tour.title}">
<h3>${tour.title}</h3>
<p>${tour.location} - ${tour.duration} days</p>
<p>$${tour.price}</p>
<button class="book-btn" data-id="${tour.id}">Book Now</button>
`;
toursContainer.appendChild(tourCard);
});
// Add event listeners to book buttons
document.querySelectorAll('.book-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const tourId = e.target.getAttribute('data-id');
document.getElementById('tourId').value = tourId;
bookingModal.style.display = 'block';
});
});
}
// Handle booking form submission
bookingForm.addEventListener('submit', async (e) => {
e.preventDefault();
const tourId = document.getElementById('tourId').value;
const name = document.getElementById('name').value;
const email = document.getElementById('email').value;
const participants = document.getElementById('participants').value;
try {
const response = await fetch('/api/bookings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ tourId, name, email, participants }),
});
if (response.ok) {
alert('Booking successful!');
bookingModal.style.display = 'none';
bookingForm.reset();
} else {
const error = await response.json();
alert(`Error: ${error.error}`);
}
} catch (err) {
alert('Error submitting booking');
console.error(err);
}
});
// Modal controls
loginBtn.addEventListener('click', () => {
loginModal.style.display = 'block';
});
exploreBtn.addEventListener('click', () => {
document.getElementById('tours').scrollIntoView({ behavior: 'smooth' });
});
closeButtons.forEach(btn => {
btn.addEventListener('click', () => {
loginModal.style.display = 'none';
bookingModal.style.display = 'none';
});
});
window.addEventListener('click', (e) => {
if (e.target === loginModal) {
loginModal.style.display = 'none';
}
if (e.target === bookingModal) {
bookingModal.style.display = 'none';
}
});
// Initialize
fetchTours();
});
/* public/styles.css */
body {
font-family: 'Arial', sans-serif;
margin: 0;
padding: 0;
line-height: 1.6;
color: #333;
}
.container {
width: 90%;
max-width: 1200px;
margin: 0 auto;
}
header {
background: #2c3e50;
color: white;
padding: 1rem 0;
}
header h1 {
margin: 0;
font-size: 1.8rem;
}
nav ul {
display: flex;
list-style: none;
padding: 0;
margin: 0;
}
nav li {
margin-left: 1rem;
}
nav a {
color: white;
text-decoration: none;
}
.hero {
background: #3498db;
color: white;
padding: 3rem 0;
text-align: center;
}
.tour-card {
border: 1px solid #ddd;
border-radius: 5px;
padding: 1rem;
margin-bottom: 1rem;
}
.tour-card img {
width: 100%;
height: 200px;
object-fit: cover;
}
.tour-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
}
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-content {
background-color: white;
margin: 10% auto;
padding: 2rem;
width: 80%;
max-width: 500px;
border-radius: 5px;
}
.close {
color: #aaa;
float: right;
font-size: 1.5rem;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: black;
}
form {
display: flex;
flex-direction: column;
gap: 1rem;
}
input, textarea {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
padding: 0.5rem 1rem;
background: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background*_
/* public/styles.css */
body {
font-family: 'Arial', sans-serif;
margin: 0;
padding: 0;
line-height: 1.6;
color: #333;
}
.container {
width: 90%;
max-width: 1200px;
margin: 0 auto;
}
header {
background: #2c3e50;
color: white;
padding: 1rem 0;
}
header h1 {
margin: 0;
font-size: 1.8rem;
}
nav ul {
display: flex;
list-style: none;
padding: 0;
margin: 0;
}
nav li {
margin-left: 1rem;
}
nav a {
color: white;
text-decoration: none;
}
.hero {
background: #3498db;
color: white;
padding: 3rem 0;
text-align: center;
}
.tour-card {
border: 1px solid #ddd;
border-radius: 5px;
padding: 1rem;
margin-bottom: 1rem;
}
.tour-card img {
width: 100%;
height: 200px;
object-fit: cover;
}
.tour-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
}
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-content {
background-color: white;
margin: 10% auto;
padding: 2rem;
width: 80%;
max-width: 500px;
border-radius: 5px;
}
.close {
color: #aaa;
float: right;
font-size: 1.5rem;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: black;
}
form {
display: flex;
flex-direction: column;
gap: 1rem;
}
input, textarea {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
padding: 0.5rem 1rem;
background: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background*_