Skip to content
Snippets Groups Projects
Commit 84193729 authored by Fin Wallis's avatar Fin Wallis
Browse files

Merge branch 'master' into 'main'

Buy tickets, view owned tickets and miscellaneous files added

See merge request !14
parents 473b5aba 51b27735
No related branches found
No related tags found
1 merge request!14Buy tickets, view owned tickets and miscellaneous files added
Showing
with 1226 additions and 120 deletions
Architecture_diagrams/component.PNG

921 KiB

Architecture_diagrams/container.PNG

755 KiB

Architecture_diagrams/context.PNG

443 KiB

......@@ -18,8 +18,8 @@ exports.eventValidator = [
body('startTime').isLength({min: 5}).withMessage("Event date must be 5 characters long"),
body('endTime').not().isEmpty().trim().withMessage("End time is required"),
body('ticketPrice').not().isEmpty().trim().withMessage("Ticket price is required"),
body('ticketQty').isEmail().normalizeEmail().withMessage("Ticket quantity is required"),
body('eventImage').not().isEmpty().trim().withMessage("Image is required"),
body('ticketQty').not().isEmpty().withMessage("Ticket quantity is required"),
// body('eventImage').not().isEmpty().trim().withMessage("Image is required"),
body('eventDescription').isLength({min: 6}).withMessage("Description is required"),
]
......@@ -46,3 +46,16 @@ exports.validatorResult = (req, res, next) => {
next();
}
// Validate purchase details
exports.purchaseValidator = [
body('addressLine1').not().isEmpty().trim().withMessage("Address line 1 is required"),
body('addressLine1').isLength({min: 3, max: 50}).withMessage("Address line 1 must be between 3 - 50 characters long"),
body('city').not().isEmpty().trim().withMessage("City is required"),
body('county').not().isEmpty().trim().withMessage("County is required"),
body('postCode').not().isEmpty().trim().withMessage("Postcode is required"),
body('cardholderName').isLength({min: 3, max: 50}).withMessage("Cardholder name must be 3 - 50 characters long"),
body('cardNumber').isLength({min: 16, max: 16}).withMessage("Card number must be 16 numbers"),
body('cardExpiryDate').isLength({min: 5, max: 5}).withMessage("Card expiry date must be valid"),
body('cardCVC').isLength({min: 3, max: 3}).withMessage("CVC number must be 3 numbers"),
]
[
{
"id": 1,
"eventTitle": "Stormzy - London Arena",
"organiser": "O2",
"venueName": "O2 Arena",
"venueAddress": "Peninsula Square, London SE10 0DX",
"eventGenre": "Grime",
"startDate": "13 February 2022",
"endDate": "13 February 2022",
"startTime": "20:00",
"endTime": "23:00",
"ticketPrice": "50.00",
"ticketQty": "150",
"eventImage": "https://www.stormzy.com/files/2021/05/OGIMAGE-compressed.jpg",
"eventDescription": "Off the back of his acclaimed 2019 album Heavy Is The Head, Stormzy had previously announced a 2021 UK tour. He has now confirmed that the supporting tour has been moved to March and April 2022."
},
{
"id": 2,
"eventTitle": "Dave - London Arena",
"organiser": "O2",
"venueName": "O2 Arena",
"venueAddress": "Peninsula Square, London SE10 0DX",
"eventGenre": "Grime",
"startDate": "16 February 2022",
"endDate": "16 February 2022",
"startTime": "20:00",
"endTime": "23:00",
"ticketPrice": "50.00",
"ticketQty": "150",
"eventImage": "https://static.independent.co.uk/s3fs-public/thumbnails/image/2019/03/07/17/dave.jpg?width=982&height=726&auto=webp&quality=75",
"eventDescription": "Brixton-born rapper and musical prodigy Dave, whose real name is David Orobosa Omoregie, began composing from an early age and writing lyrics in his teens. Growing up in Streatham, South London wasn’t easy so focusing on music helped him cope."
},
{
"id": 3,
"eventTitle": "Drake - Wembley Arena",
"organiser": "Wembley Organisers LTD",
"venueName": "Wembley Arena",
"venueAddress": "Arena Square, Engineers Way, London HA9 0AA",
"eventGenre": "Pop",
"startDate": "28 February 2022",
"endDate": "28 February 2022",
"startTime": "20:00",
"endTime": "23:00",
"ticketPrice": "89.00",
"ticketQty": "150",
"eventImage": "https://www.biography.com/.image/ar_1:1%2Cc_fill%2Ccs_srgb%2Cfl_progressive%2Cq_auto:good%2Cw_1200/MTQ3NTI2OTA4NzY5MjE2MTI4/drake_photo_by_prince_williams_wireimage_getty_479503454.jpg",
"eventDescription": "Platinum selling, chart dominating powerhouse Drake announces the ‘ASSASSINATION VACATION TOUR’ with support from fellow Canadian rapper, singer, songwriter Tory Lanez. Fresh off the back of his sell out U.S tour, Drake will be taking his stellar production onto the U.K and Europe."
}
]
\ No newline at end of file
module.exports = {
url: "mongodb://localhost:27017/TickcryptDB"
};
\ No newline at end of file
import mongoose from 'mongoose';
import { EventSchema } from '../models/eventModel';
import { CardDetailSchema } from '../models/cardDetailModel';
import { PurchaseSchema } from '../models/purchaseModel';
// Creating event object
const Event = mongoose.model('Event', EventSchema);
// Creating card details object
const CardDetail = mongoose.model('CardDetail', CardDetailSchema);
// Creating purchase details object
const Purchase = mongoose.model('Purchase', PurchaseSchema);
exports.addNewEvent = async (req, res) => {
console.log("req", req)
console.log("req body", req.body)
console.log("server request body:", req.body)
console.log("server request file:", req.file)
console.log("server request user:", req.user)
res.json({
message: "Inside create event fucntion in controller"
})
// Destructuring sign up post request body into seperate variables for ref
const {eventTitle, organiser, venueName, venueAddress, eventGenre, startDate, endDate, startTime, endTime, ticketPrice, ticketQty, eventDescription } = req.body;
try {
const event = await Event.findOne({ eventTitle });
if (event) {
return res.status(400).json({
errorMessage: "We're already hosting this event :)",
});
}
// Creating new instance of user from model
const newEvent = new Event();
// Assigning database fields to req fields
newEvent.eventTitle = eventTitle;
newEvent.organiser = organiser;
newEvent.venueName = venueName;
newEvent.venueAddress = venueAddress;
newEvent.eventGenre = eventGenre;
newEvent.startDate = startDate;
newEvent.endDate = endDate;
newEvent.startTime = startTime;
newEvent.endTime = endTime;
newEvent.ticketPrice = ticketPrice;
newEvent.ticketQty = ticketQty;
newEvent.eventDescription = eventDescription;
// Saving user model to database
await newEvent.save();
res.json({
successMessage: "Successfully created your event",
});
} catch (err) {
console.log("addEvent controller error", err)
res.status(500).json({
errorMessage: "Server error"
})
}
// let newEvent = new Event(req.body);
// newEvent.save((err, Event) => {
......@@ -38,3 +84,5 @@ export const getEvents = (req, res) => {
import mongoose from 'mongoose';
import { EventSchema } from '../models/eventModel';
import { CardDetailSchema } from '../models/cardDetailModel';
import { PurchaseSchema } from '../models/purchaseModel';
const Event = mongoose.model('Event', EventSchema);
const CardDetail = mongoose.model('CardDetail', CardDetailSchema);
const Purchase = mongoose.model('Purchase', PurchaseSchema);
export const getPurchases = (req, res) => {
Purchase.find({}, (err, Purchase) => {
if (err) {
res.send(err);
}
res.json(Purchase)
});
};
// export vs module exports (which works) - fix refactor later
exports.purchaseController = async (req, res) => {
console.log("inside controller", req.body);
// Destructuring sign up post request body into seperate variables for ref
const { addressLine1, addressLine2, city, county, postCode, cardholderName, cardNumber, cardExpiryDate, cardCVC, event_id, amount, user_id } = req.body;
try {
// Creating new instance of card detail from model
const newCardDetail = new CardDetail();
// Assigning database fields to req fields
newCardDetail.addressLine1 = addressLine1;
newCardDetail.addressLine2 = addressLine2;
newCardDetail.city = city;
newCardDetail.county = county;
newCardDetail.postCode = postCode;
newCardDetail.cardholderName = cardholderName;
newCardDetail.cardNumber = cardNumber;
newCardDetail.cardExpiryDate = cardExpiryDate;
newCardDetail.cardCVC = cardCVC;
newCardDetail.user_id = user_id;
// Creating new instance of purchase from model
const newPurchase = new Purchase();
newPurchase.user_id = user_id;
newPurchase.event_id = event_id;
newPurchase.card_details_id = newCardDetail._id;
newPurchase.amount = amount;
newPurchase.service_fee = amount * 0.025;
// Saving card details model and purchase to database
await newCardDetail.save();
await newPurchase.save();
res.json({
successMessage: "Successfully bought ticket",
});
} catch (err) {
console.log("SignupController error:", err)
res.status(500).json({
errorMessage: "Server error"
})
}
};
\ No newline at end of file
......@@ -4,20 +4,27 @@ import cors from 'cors';
import cookieParser from 'cookie-parser'
import eventRoutes from './routes/eventRoutes';
import authRoutes from './routes/authRoutes';
import purchaseRoutes from './routes/purchaseRoutes';
// Creating express object and declaring hosting server port
const app = express();
const PORT = 4000;
var bodyParser = require('body-parser')
const db = require("./models")
// MongoDB connection
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost/TickcryptDB', {
db.mongoose.Promise = global.Promise;
db.mongoose.connect('mongodb://localhost/TickcryptDB', {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then((result) => console.log('Connected to DB'))
.catch((err) => console.log(err));
.catch((err) => {
// Close process after
console.log(err)
process.exit();
});
// Body parser setup
app.use(express.urlencoded({extended: true}));
......@@ -39,6 +46,7 @@ app.set("view engine", "ejs");
// Declaring routes to the express server
eventRoutes(app)
authRoutes(app)
purchaseRoutes(app)
app.get('/', (req, res) =>
res.send(`Tickcrypt app is running on ${PORT}`)
......
import mongoose from 'mongoose';
const Schema = mongoose.Schema
export const CardDetailSchema = new Schema({
addressLine1: {
type: String,
required: true,
trim: true
},
addressLine2: {
type: String,
required: false,
trim: true
},
city: {
type: String,
required: true,
trim: true
},
county: {
type: String,
required: true,
trim: true
},
postCode: {
type: String,
required: true
},
cardholderName: {
type: String,
required: true
},
cardNumber: {
type: String,
required: true
},
cardExpiryDate: {
type: String,
required: true
},
cardCVC: {
type: String,
required: true
},
user_id: {
type: String,
required: true
},
})
const CardDetail = mongoose.model('CardDetail', CardDetailSchema);
module.exports = CardDetail
\ No newline at end of file
......@@ -61,3 +61,6 @@ export const EventSchema = new Schema({
}
})
const Event = mongoose.model('Event', EventSchema);
module.exports = Event
const dbConfig = require("../config/db.config.js");
const mongoose = require("mongoose");
mongoose.Promise = global.Promise;
const db = {};
db.mongoose = mongoose;
db.url = dbConfig.url;
db.events = require("./eventModel.js")(mongoose);
db.users = require("./userModel.js")(mongoose);
module.exports = db;
\ No newline at end of file
import mongoose from 'mongoose';
const Schema = mongoose.Schema
export const PurchaseSchema = new Schema({
user_id: {
type: String,
required: true,
},
event_id: {
type: String,
required: true,
},
card_details_id: {
type: String,
required: true,
},
amount: {
type: Number,
required: true,
},
service_fee: {
type: Number,
required: true,
}
})
const Purchase = mongoose.model('Purchase', PurchaseSchema);
module.exports = Purchase
\ No newline at end of file
// Import controllers to connect to routes so the req can call the controller function based on the URL route
import {addNewEvent, getEvents} from "../controllers/eventControllers"
import {addNewEvent, getEvents} from "../controllers/eventControllers";
import { eventValidator, validatorResult } from "../Middleware/validator";
import {authenticateJWT} from "../middleware/authenticator"
import {upload} from "../middleware/multer"
import {eventValidator, validatorResult} from "../middleware/validator";
// For Multer image upload but returns an error
// upload.single("eventImage")
const eventRoutes = (app) => {
app.route('/api/event')
.post(eventValidator, validatorResult, addNewEvent)
.get(getEvents)
};
export default eventRoutes;
\ No newline at end of file
// Import controllers to connect to routes so the req can call the controller function based on the URL route
import {getPurchases, purchaseController} from "../controllers/purchaseControllers";
import { purchaseValidator, validatorResult } from "../Middleware/validator";
const purchaseRoutes = (app) => {
app.route('/api/purchase')
.get(getPurchases)
.post(purchaseValidator, validatorResult, purchaseController)
};
export default purchaseRoutes;
\ No newline at end of file
Requirements.png

1.46 MiB

__v,_id,createdDate,endDate,endTime,eventDescription,eventGenre,eventImage,eventTitle,organiser,startDate,startTime,ticketPrice,ticketQty,venueAddress,venueName
0,61ecb90e72c716fb31dc3abf,2022-01-23T02:10:22.805Z,12/12/22,12:13,This is an event. This is an example of an event description used to inform the user of any details about the event. This is an event. This is an example of an event description,Grime,https://static.hiphopdx.com/2019/12/191218-ASAP-Rocky-827x620.jpg,ASAP Rocky,Motion Events,12/12/22,12:12,14,100,St mary street cardiff CF10 4FD,Cardiff motorpoint arena
0,61ecb93b72c716fb31dc3ac3,2022-01-23T02:11:07.405Z,12/12/22,12:13,This is an event. This is an example of an event description used to inform the user of any details about the event. This is an event. This is an example of an event description,Rap,https://www.telegraph.co.uk/content/dam/music/2021/09/12/TELEMMGLPICT000271018725_trans_NvBQzQNjv4BqpVlberWd9EgFPZtcLiMQf0Rf_Wk3V23H2268P_XkPxc.jpeg?imwidth=1280,Dave,Motion Events,12/12/22,12:12,14,100,St mary street cardiff CF10 4FD,Cardiff motorpoint arena
0,61ecb98a72c716fb31dc3ac6,2022-01-23T02:12:26.826Z,12/12/22,12:13,This is an event. This is an example of an event description used to inform the user of any details about the event. This is an event. This is an example of an event description,Rap,https://ichef.bbci.co.uk/news/976/cpsprodpb/9CCD/production/_97314104_gigg1.jpg,Giggs,Motion Events,12/12/22,12:12,14,100,St mary street cardiff CF10 4FD,Cardiff motorpoint arena
0,61ecb99672c716fb31dc3ac9,2022-01-23T02:12:38.005Z,12/12/22,12:13,This is an event. This is an example of an event description used to inform the user of any details about the event. This is an event. This is an example of an event description,Rap,https://img.buzzfeed.com/buzzfeed-static/static/2019-11/11/23/asset/2b4d888c724d/sub-buzz-1301-1573515278-1.jpg?downsize=700%3A%2A&output-quality=auto&output-format=auto,Drake,Motion Events,12/12/22,12:12,14,100,St mary street cardiff CF10 4FD,Cardiff motorpoint arena
0,61ecb9ba72c716fb31dc3acc,2022-01-23T02:13:14.220Z,12/12/22,12:13,This is an event. This is an example of an event description used to inform the user of any details about the event. This is an event. This is an example of an event description,Rap,https://i0.wp.com/images.squarespace-cdn.com/content/v1/5c5066e1266c07b63c44dbc6/1558183706065-QEAPLQ6RPLP09ANIJ9YA/ke17ZwdGBToddI8pDm48kFyD7pzB8zoMIVY5aiUuFlp7gQa3H78H3Y0txjaiv_0fDoOvxcdMmMKkDsyUqMSsMWxHk725yiiHCCLfrh8O1z4YTzHvnKhyp6Da-NYroOW3ZGjoBKy3azqku80C789l0jG2lbcDYBOeMi4OFSYem8DMb5PTLoEDdB05UqhYu-xbnSznFxIRsaAU-3g5IaylIg/FREDO+190517-15.jpg?w=1080&ssl=1,Fredo,Motion Events,12/12/22,12:12,14,100,St mary street cardiff CF10 4FD,Cardiff motorpoint arena
0,61ecb9c572c716fb31dc3acf,2022-01-23T02:13:25.795Z,12/12/22,12:13,This is an event. This is an example of an event description used to inform the user of any details about the event. This is an event. This is an example of an event description,Rap,https://dazedimg-dazedgroup.netdna-ssl.com/527/azure/dazed-prod/1310/1/1311102.JPG,Kanye,Motion Events,12/12/22,12:12,14,100,St mary street cardiff CF10 4FD,Cardiff motorpoint arena
\ No newline at end of file
This diff is collapsed.
......@@ -4,6 +4,10 @@
"private": true,
"dependencies": {
"@craco/craco": "^6.4.2",
"@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0",
"@mui/icons-material": "^5.3.0",
"@mui/material": "^5.2.8",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
......@@ -45,6 +49,5 @@
"postcss": "^7.0.39",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17"
},
"proxy": "http://localhost:4000"
"proxy": "http://localhost:4000"
}
import React, { useState } from "react";
import errorAlert from "../Helpers/errorAlert";
import isEmpty from 'validator/lib/isEmpty';
import {createEvent} from '../api/event';
import {createEvent} from '../api/events';
import successAlert from '../Helpers/successAlert';
const CreateEventForm = () => {
const [eventData, setEventData] = useState({
eventTitle: "Awesome event",
organiser: "Finlay wallis",
eventTitle: "Stormzy",
organiser: "Motion Events",
venueName: "Cardiff motorpoint arena",
venueAddress: "St mary street cardiff CF10 4FD",
eventGenre: "Pop",
eventGenre: "Rap",
startDate: "12/12/22",
endDate: "12/12/22",
startTime: "12:12",
endTime: "12:13",
ticketPrice: "£14",
ticketPrice: "14",
ticketQty: "100",
eventImage: null,
eventDescription: "This is an event.",
// eventImage: null,
eventDescription: "This is an event. This is an example of an event description used to inform the user of any details about the event. This is an event. This is an example of an event description",
errorMsg: false
});
......@@ -60,19 +60,36 @@ const CreateEventForm = () => {
const handleEventSubmit = (event) => {
event.preventDefault()
if (eventImage === null) {
setEventData({
...eventData,
errorMsg: "Image is required"
})
} else if (isEmpty(eventTitle) || isEmpty(organiser) || isEmpty(venueName) || isEmpty(venueAddress) || isEmpty(eventGenre) || isEmpty(startDate) || isEmpty(endDate) || isEmpty(startTime) || isEmpty(endTime) || isEmpty(ticketPrice) || isEmpty(ticketQty) || isEmpty(eventDescription)) {
setEventData({
...eventData,
errorMsg: "Image is required"
})
} else {
// Event image upload validation and adding appending form data to FormData() object for Multer middleware to interpret
// if (eventImage === null) {
// setEventData({
// ...eventData,
// errorMsg: "Image is required"
// })
// } else if (isEmpty(eventTitle) || isEmpty(organiser) || isEmpty(venueName) || isEmpty(venueAddress) || isEmpty(eventGenre) || isEmpty(startDate) || isEmpty(endDate) || isEmpty(startTime) || isEmpty(endTime) || isEmpty(ticketPrice) || isEmpty(ticketQty) || isEmpty(eventDescription)) {
// setEventData({
// ...eventData,
// errorMsg: "Image is required"
// })
// } else {
// let formData = new FormData()
// formData.append('eventTitle', eventTitle)
// formData.append('organiser', organiser)
// formData.append('venueName', venueName)
// formData.append('venueAddress', venueAddress)
// formData.append('eventGenre', eventGenre)
// formData.append('startDate', startDate)
// formData.append('endDate', endDate)
// formData.append('startTime', startTime)
// formData.append('endTime', endTime)
// formData.append('ticketPrice', ticketPrice)
// formData.append('ticketQty', ticketQty)
// formData.append('eventImage', eventImage)
// formData.append('eventDescription', eventDescription)
const formData = {eventTitle, organiser, venueName, venueAddress, eventGenre, startDate, endDate, startTime, endTime, ticketPrice, ticketQty, eventImage, eventDescription }
const formData = {eventTitle, organiser, venueName, venueAddress, eventGenre, startDate, endDate, startTime, endTime, ticketPrice, ticketQty, eventDescription }
// for (var pair of formData.entries()) {
// console.log("axios got it", pair[0]+ ', ' + pair[1]);
......@@ -82,6 +99,22 @@ const CreateEventForm = () => {
createEvent(formData)
.then(response => {
console.log("Server response", response)
setEventData({
eventTitle: '',
organiser: '',
venueName: '',
venueAddress: '',
eventGenre: '',
startDate: '',
endDate: '',
startTime: '',
endTime: '',
ticketPrice: '',
ticketQty: '',
eventDescription: '',
successMsg: response.data.successMessage
})
})
.catch((err) => {
console.log("error", err)
......@@ -91,7 +124,7 @@ const CreateEventForm = () => {
})
})
}
// }
}
......@@ -106,6 +139,7 @@ const CreateEventForm = () => {
Create event
</h1>
{errorMsg && errorAlert(errorMsg)}
{eventData.successMsg && successAlert(eventData.successMsg)}
<input
className="font-montserrat font-medium rounded-full w-full py-4 px-7 shadow-lg"
name='eventTitle'
......@@ -200,7 +234,7 @@ const CreateEventForm = () => {
name="ticketPrice"
value={ticketPrice}
type="text"
placeholder="Ticket price"
placeholder="Ticket price (£)"
onChange={handleEventChange}
></input>
<input
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment