diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c477b00..992d3fd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,18 +1,18 @@ name: Lint on: - push: + push: jobs: lint: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: '18.x' - - name: Install dependencies - run: yarn - - name: Run ESLint - run: npx eslint . \ No newline at end of file + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "18.x" + - name: Install dependencies + run: yarn + - name: Run ESLint + run: npx eslint . diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 150599e..f4c3651 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -1,18 +1,18 @@ -name: Prettier - -on: - push: - -jobs: - prettier: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: "18.x" - - name: Install dependencies - run: yarn - - name: Run Prettier - run: npx prettier --write . +name: Prettier + +on: + push: + +jobs: + prettier: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "18.x" + - name: Install dependencies + run: yarn + - name: Run Prettier + run: npx prettier --write . diff --git a/README.md b/README.md index 781821c..cb79c58 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # API for Reflections | Projections 2024 Contributors: -- Aydan Pirani -- Divya Koya -- Riya Patel -- Jacob Chang -- Alex Yang -- Shreenija Daggavolu + +- Aydan Pirani +- Divya Koya +- Riya Patel +- Jacob Chang +- Alex Yang +- Shreenija Daggavolu diff --git a/appspec.yml b/appspec.yml index 2e74e8d..7e68363 100644 --- a/appspec.yml +++ b/appspec.yml @@ -1,22 +1,22 @@ -version: 0.0 -os: linux -files: - - source: / - destination: /home/ubuntu/rp-api -file_exists_behavior: OVERWRITE -hooks: - BeforeInstall: - - location: scripts/install_dependencies.sh - timeout: 300 - runas: root - AfterInstall: - - location: scripts/build.sh - timeout: 300 - runas: root - ApplicationStart: - - location: scripts/install_dependencies.sh - timeout: 300 - runas: root - - location: scripts/reload_server.sh - timeout: 300 - runas: root \ No newline at end of file +version: 0.0 +os: linux +files: + - source: / + destination: /home/ubuntu/rp-api +file_exists_behavior: OVERWRITE +hooks: + BeforeInstall: + - location: scripts/install_dependencies.sh + timeout: 300 + runas: root + AfterInstall: + - location: scripts/build.sh + timeout: 300 + runas: root + ApplicationStart: + - location: scripts/install_dependencies.sh + timeout: 300 + runas: root + - location: scripts/reload_server.sh + timeout: 300 + runas: root diff --git a/src/app.ts b/src/app.ts index 7f9f4b5..ea6d818 100644 --- a/src/app.ts +++ b/src/app.ts @@ -8,8 +8,10 @@ import morgan from "morgan"; import bodyParser from "body-parser"; import errorHandler from "./middleware/error-handler"; +import attendeeRouter from "./services/attendees/attendee-router"; import authRouter from "./services/auth/auth-router"; -import eventRouter from "./services/events/events-router"; +import eventRouter from "./services/events/event-router"; +import registrationRouter from "./services/registration/registration-router"; import subscriptionRouter from "./services/subscription/subscription-router"; const app = express(); @@ -26,8 +28,10 @@ app.use("/", morgan("dev")); app.use("/", bodyParser.json()); // API routes +app.use("/attendee", attendeeRouter); app.use("/auth", authRouter); app.use("/event", eventRouter); +app.use("/registration", registrationRouter); app.use("/subscription", subscriptionRouter); app.get("/status", (_, res) => { diff --git a/src/database.ts b/src/database.ts index 7e0a9e6..b1e4efa 100644 --- a/src/database.ts +++ b/src/database.ts @@ -1,6 +1,14 @@ import mongoose, { Schema } from "mongoose"; +import { + AttendeeSchema, + AttendeeValidator, +} from "./services/attendees/attendee-schema"; import { RoleValidator, RoleSchema } from "./services/auth/auth-schema"; import { EventSchema, EventValidator } from "./services/events/events-schema"; +import { + RegistrationSchema, + RegistrationValidator, +} from "./services/registration/registration-schema"; import { SubscriptionSchemaValidator, SubscriptionSchema, @@ -43,4 +51,10 @@ export const Database = { SubscriptionSchema, SubscriptionSchemaValidator ), + ATTENDEES: initializeModel("attendees", AttendeeSchema, AttendeeValidator), + REGISTRATION: initializeModel( + "registration", + RegistrationSchema, + RegistrationValidator + ), }; diff --git a/src/services/attendees/attendee-router.ts b/src/services/attendees/attendee-router.ts new file mode 100644 index 0000000..8006361 --- /dev/null +++ b/src/services/attendees/attendee-router.ts @@ -0,0 +1,43 @@ +import { Router } from "express"; +import { StatusCodes } from "http-status-codes"; +import { AttendeeValidator } from "./attendee-schema"; +import { Database } from "../../database"; + +const attendeeRouter = Router(); + +// Create a new attendee +attendeeRouter.post("/", async (req, res, next) => { + try { + const attendeeData = AttendeeValidator.parse(req.body); + const attendee = new Database.ATTENDEES(attendeeData); + await attendee.save(); + + return res.status(StatusCodes.CREATED).json(attendeeData); + } catch (error) { + next(error); + } +}); + +// Check if a user email exists +attendeeRouter.get("/:email", async (req, res, next) => { + try { + const { email } = req.params; + + // Check if the user exists in the database + const userExists = await Database.ATTENDEES.exists({ email }); + + if (!userExists) { + return { error: "DoesNotExist" }; + } + + const user = await Database.ATTENDEES.findOne({ + email, + }); + + return res.status(StatusCodes.OK).json(user); + } catch (error) { + next(error); + } +}); + +export default attendeeRouter; diff --git a/src/services/attendees/attendee-schema.ts b/src/services/attendees/attendee-schema.ts new file mode 100644 index 0000000..358795d --- /dev/null +++ b/src/services/attendees/attendee-schema.ts @@ -0,0 +1,26 @@ +import mongoose from "mongoose"; +import { z } from "zod"; + +// Zod schema for attendee +const AttendeeValidator = z.object({ + userId: z.string(), + name: z.string(), + email: z.string().email(), + events: z.array(z.string()), + dietary_restrictions: z.string(), + priority_expiry: z.date().nullable().optional(), + points: z.number().min(0).default(0), +}); + +// Mongoose schema for attendee +const AttendeeSchema = new mongoose.Schema({ + userId: { type: String, required: true, unique: true }, + name: { type: String, required: true }, + email: { type: String, required: true, unique: true }, + events: [{ type: mongoose.Schema.Types.ObjectId, ref: "Event" }], + dietary_restrictions: { type: String, required: true }, + priority_expiry: { type: Date, default: null }, + points: { type: Number, default: 0 }, +}); + +export { AttendeeSchema, AttendeeValidator }; diff --git a/src/services/events/event-router.ts b/src/services/events/event-router.ts new file mode 100644 index 0000000..f7e154a --- /dev/null +++ b/src/services/events/event-router.ts @@ -0,0 +1,82 @@ +import { Router } from "express"; +import { StatusCodes } from "http-status-codes"; +import { EventValidator } from "./event-schema"; +import { Database } from "../../database"; + +const eventRouter = Router(); + +// Create a new event +eventRouter.post("/", async (req, res, next) => { + try { + const eventData = EventValidator.parse(req.body); + const event = new Database.EVENTS(eventData); + await event.save(); + return res.status(StatusCodes.CREATED).json(event); + } catch (error) { + next(error); + } +}); + +// Get all events +eventRouter.get("/", async (req, res, next) => { + try { + const events = await Database.EVENTS.find(); + return res.status(StatusCodes.OK).json(events); + } catch (error) { + next(error); + } +}); + +// Get event by ID +eventRouter.get("/:id", async (req, res, next) => { + try { + const event = await Database.EVENTS.findOneAndUpdate( + { _id: req.params.id }, + { new: true } + ); + + if (!event) { + return { error: "DoesNotExist" }; + } + + return res.status(StatusCodes.OK).json(event); + } catch (error) { + next(error); + } +}); + +// Update event +eventRouter.patch("/:id", async (req, res, next) => { + try { + const event = await Database.EVENTS.findOneAndUpdate( + { _id: req.params.id }, + req.body, + { new: true } + ); + + if (!event) { + return { error: "DoesNotExist" }; + } + + return res.status(StatusCodes.OK).json(event); + } catch (error) { + next(error); + } +}); + +// Delete event +eventRouter.delete("/:id", async (req, res, next) => { + try { + const event = await Database.EVENTS.findByIdAndDelete(req.params.id); + + if (!event) { + return { error: "DoesNotExist" }; + } + + return res.status(StatusCodes.OK).json(event); + } catch (error) { + next(error); + } +}); + +export default eventRouter; diff --git a/src/services/events/event-schema.ts b/src/services/events/event-schema.ts new file mode 100644 index 0000000..73890cd --- /dev/null +++ b/src/services/events/event-schema.ts @@ -0,0 +1,47 @@ +import mongoose from "mongoose"; +import { z } from "zod"; + +// Zod schema for event +const EventValidator = z.object({ + name: z.string(), + description: z.string(), + start_time: z.date(), + end_time: z.date(), + attendees: z.array(z.string()), + location: z.array( + z.object({ + description: z.string(), + tags: z.array(z.string()), + latitude: z.number(), + longitude: z.number(), + }) + ), + virtual: z.boolean(), + points: z.number().min(0).default(0), + imageUrl: z.string().nullable().optional(), + visible: z.boolean().default(false), +}); + +// Mongoose schema for location +const LocationSchema = new mongoose.Schema({ + description: { type: String, required: true }, + tags: [{ type: String }], + latitude: { type: Number, required: true }, + longitude: { type: Number, required: true }, +}); + +// Mongoose schema for event +const EventSchema = new mongoose.Schema({ + name: { type: String, required: true }, + description: { type: String, required: true }, + start_time: { type: Date, required: true }, + end_time: { type: Date, required: true }, + attendees: [{ type: mongoose.Schema.Types.ObjectId, ref: "Attendee" }], + location: [LocationSchema], + virtual: { type: Boolean, required: true }, + points: { type: Number, default: 0 }, + imageUrl: { type: String, default: null }, + visible: { type: Boolean, default: false }, +}); + +export { EventSchema, EventValidator, LocationSchema }; diff --git a/src/services/registration/registration-router.ts b/src/services/registration/registration-router.ts new file mode 100644 index 0000000..8b18e65 --- /dev/null +++ b/src/services/registration/registration-router.ts @@ -0,0 +1,10 @@ +import { Router } from "express"; +// import { StatusCodes } from "http-status-codes"; +// import { RegistrationValidator } from "./registration-schema"; +// import { Database } from "../../database"; + +const registrationRouter = Router(); + +// TODO: registration routes + +export default registrationRouter; diff --git a/src/services/registration/registration-schema.ts b/src/services/registration/registration-schema.ts new file mode 100644 index 0000000..ab19968 --- /dev/null +++ b/src/services/registration/registration-schema.ts @@ -0,0 +1,50 @@ +import mongoose from "mongoose"; +import { z } from "zod"; + +// Zod schema for registration +const RegistrationValidator = z.object({ + userId: z.string(), + name: z.string(), + email: z.string().email(), + studentInfo: z.object({ + university: z.string().nonempty(), + graduation: z.string().nullable().optional(), + major: z.string().nullable().optional(), + }), + dietary_restrictions: z.string(), + age: z.number().nullable().optional(), + gender: z.string().nullable().optional(), + race: z.array(z.string()).nullable().optional(), + ethnicity: z.array(z.string()).nullable().optional(), + first_gen: z.string().nullable().optional(), + hear_about_rp: z.array(z.string()).nullable().optional(), + portfolio: z.string().nullable().optional(), + job_interest: z.array(z.string()).nullable().optional(), + interest_mech_puzzle: z.array(z.string()).nullable().optional(), + has_resume: z.boolean(), +}); + +// Mongoose schema for registration +const RegistrationSchema = new mongoose.Schema({ + userId: { type: String, required: true, unique: true }, + name: { type: String, required: true }, + email: { type: String, required: true, unique: true }, + studentInfo: { + university: { type: String, required: true }, + graduation: { type: String, default: null }, + major: { type: String, default: null }, + }, + dietary_restrictions: { type: String, required: true }, + age: { type: Number, default: null }, + gender: { type: String, default: null }, + race: [{ type: String }], + ethnicity: [{ type: String }], + first_gen: { type: String, default: null }, + hear_about_rp: [{ type: String }], + portfolio: { type: String, default: null }, + job_interest: [{ type: String }], + interest_mech_puzzle: [{ type: String }], + has_resume: { type: Boolean, default: false }, +}); + +export { RegistrationSchema, RegistrationValidator };