diff --git a/src/app.ts b/src/app.ts deleted file mode 100644 index c79eac8..0000000 --- a/src/app.ts +++ /dev/null @@ -1,69 +0,0 @@ -import express from "express"; -import { StatusCodes } from "http-status-codes"; -import { Config } from "./config"; -import { rateLimiter } from "./middleware/rateLimiter"; -import { isTest } from "./utilities"; - -import databaseMiddleware from "./middleware/database-middleware"; -import customCors from "./middleware/cors-middleware"; -import morgan from "morgan"; -import bodyParser from "body-parser"; -import errorHandler from "./middleware/error-handler"; - -import attendeeRouter from "./services/attendee/attendee-router"; -import checkinRouter from "./services/checkin/checkin-router"; -import authRouter from "./services/auth/auth-router"; -import eventsRouter from "./services/events/events-router"; -import notificationsRouter from "./services/notifications/notifications-router"; -import registrationRouter from "./services/registration/registration-router"; -import s3Router from "./services/s3/s3-router"; -import statsRouter from "./services/stats/stats-router"; -import sponsorRouter from "./services/sponsor/sponsor-router"; -import subscriptionRouter from "./services/subscription/subscription-router"; -import speakersRouter from "./services/speakers/speakers-router"; - -const app = express(); - -// to prevent server-side caching/returning status code 200 -// (we can remove this later) -app.disable("etag"); - -app.use(rateLimiter); - -app.use(customCors); - -// To display the logs every time -app.use("/", morgan("dev")); - -app.use("/", bodyParser.json()); - -// API routes -app.use("/attendee", databaseMiddleware, attendeeRouter); -app.use("/auth", databaseMiddleware, authRouter); -app.use("/checkin", databaseMiddleware, checkinRouter); -app.use("/events", databaseMiddleware, eventsRouter); -app.use("/notifications", databaseMiddleware, notificationsRouter); -app.use("/registration", databaseMiddleware, registrationRouter); -app.use("/s3", databaseMiddleware, s3Router); -app.use("/stats", databaseMiddleware, statsRouter); -app.use("/sponsor", databaseMiddleware, sponsorRouter); -app.use("/subscription", databaseMiddleware, subscriptionRouter); -app.use("/speakers", databaseMiddleware, speakersRouter); - -app.get("/status", (_, res) => { - return res.status(StatusCodes.OK).send("API is alive!"); -}); - -app.use("/", (_, res) => - res.status(StatusCodes.NOT_FOUND).send("No endpoint here!") -); - -app.use(errorHandler); - -if (!isTest()) { - app.listen(Config.DEFAULT_APP_PORT, async () => { - process.send?.("ready"); - console.log("Server is listening on port 3000..."); - }); -} -export default app; diff --git a/src/config.ts b/src/config.ts index 259f97a..be4d15e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -9,6 +9,12 @@ export const Environment = z.enum(["PRODUCTION", "DEVELOPMENT", "TESTING"]); export const MailingListName = z.enum(["rp_interest"]); +// Native enum for bucket names +export enum BucketName { + RP_2024_RESUMES = "rp-2024-resumes", + RP_2024_SPEAKERS = "rp-2024-speakers", +} + export const Config = { DEFAULT_APP_PORT: 3000, ALLOWED_CORS_ORIGIN_PATTERNS: [ @@ -71,7 +77,6 @@ export const Config = { S3_ACCESS_KEY: getEnv("S3_ACCESS_KEY"), S3_SECRET_KEY: getEnv("S3_SECRET_KEY"), - S3_BUCKET_NAME: getEnv("S3_BUCKET_NAME"), S3_REGION: getEnv("S3_REGION"), MAX_RESUME_SIZE_BYTES: 6 * 1024 * 1024, RESUME_URL_EXPIRY_SECONDS: 60, diff --git a/src/database.ts b/src/database.ts deleted file mode 100644 index c68d14f..0000000 --- a/src/database.ts +++ /dev/null @@ -1,97 +0,0 @@ -import mongoose, { Schema, Document } from "mongoose"; -import { - AttendeeSchema, - AttendeeValidator, -} from "./services/attendee/attendee-schema"; -import { - AttendeeAttendanceSchema, - AttendeeAttendanceValidator, -} from "./services/attendee/attendee-schema"; -import { - EventSchema, - privateEventValidator, -} from "./services/events/events-schema"; -import { - EventAttendanceSchema, - EventAttendanceValidator, -} from "./services/events/events-schema"; -import { RoleValidator, RoleSchema } from "./services/auth/auth-schema"; -import { - RegistrationSchema, - RegistrationValidator, -} from "./services/registration/registration-schema"; -import { - SubscriptionSchemaValidator, - SubscriptionSchema, -} from "./services/subscription/subscription-schema"; -import { - NotificationsSchema, - NotificationsValidator, -} from "./services/notifications/notifications-schema"; -import { - SpeakerSchema, - SpeakerValidator, -} from "./services/speakers/speakers-schema"; - -mongoose.set("toObject", { versionKey: false }); - -function initializeModel( - modelName: string, - schema: Schema, - object: Zod.AnyZodObject -) { - schema.pre("validate", function (next) { - const data = this.toObject(); - try { - // Validate the data against the Zod schema - object.parse(data); - next(); - } catch (error) { - next(new Error(error as string)); - } - }); - - schema.set("toObject", { - transform(doc, ret) { - delete ret._id; - delete ret.__v; - }, - }); - - type objectType = Zod.infer<typeof object>; - interface modelType extends Document, objectType {} - return mongoose.model<modelType>(modelName, schema); -} - -// Example usage -export const Database = { - ROLES: initializeModel("roles", RoleSchema, RoleValidator), - EVENTS: initializeModel("events", EventSchema, privateEventValidator), - EVENTS_ATTENDANCE: initializeModel( - "events_attendance", - EventAttendanceSchema, - EventAttendanceValidator - ), - ATTENDEE: initializeModel("attendee", AttendeeSchema, AttendeeValidator), - ATTENDEE_ATTENDANCE: initializeModel( - "attendee_attendance", - AttendeeAttendanceSchema, - AttendeeAttendanceValidator - ), - SUBSCRIPTIONS: initializeModel( - "subscriptions", - SubscriptionSchema, - SubscriptionSchemaValidator - ), - REGISTRATION: initializeModel( - "registration", - RegistrationSchema, - RegistrationValidator - ), - NOTIFICATIONS: initializeModel( - "notifications", - NotificationsSchema, - NotificationsValidator - ), - SPEAKERS: initializeModel("speakers", SpeakerSchema, SpeakerValidator), -}; diff --git a/src/services/s3/s3-router.ts b/src/services/s3/s3-router.ts index 9902c8a..64cf466 100644 --- a/src/services/s3/s3-router.ts +++ b/src/services/s3/s3-router.ts @@ -7,6 +7,7 @@ import { Role } from "../auth/auth-models"; import { S3 } from "@aws-sdk/client-s3"; import { getResumeUrl, postResumeUrl } from "./s3-utils"; import BatchResumeDownloadValidator from "./s3-schema"; +import { BucketName } from "../../config"; const s3Router: Router = Router(); @@ -21,7 +22,11 @@ s3Router.get( const userId: string = payload.userId; try { - const { url, fields } = await postResumeUrl(userId, s3); + const { url, fields } = await postResumeUrl( + userId, + s3, + BucketName.RP_2024_RESUMES + ); return res.status(StatusCodes.OK).send({ url, fields }); } catch (error) { next(error); @@ -40,7 +45,11 @@ s3Router.get( const s3 = res.locals.s3 as S3; try { - const downloadUrl = await getResumeUrl(userId, s3); + const downloadUrl = await getResumeUrl( + userId, + s3, + BucketName.RP_2024_RESUMES + ); return res.status(StatusCodes.OK).send({ url: downloadUrl }); } catch (error) { next(error); @@ -57,7 +66,11 @@ s3Router.get( const s3 = res.locals.s3 as S3; try { - const downloadUrl = await getResumeUrl(userId, s3); + const downloadUrl = await getResumeUrl( + userId, + s3, + BucketName.RP_2024_RESUMES + ); return res.status(StatusCodes.OK).send({ url: downloadUrl }); } catch (error) { next(error); @@ -76,7 +89,7 @@ s3Router.get( const { userIds } = BatchResumeDownloadValidator.parse(req.body); const batchDownloadPromises = userIds.map((userId) => - getResumeUrl(userId, s3) + getResumeUrl(userId, s3, BucketName.RP_2024_RESUMES) .then((url) => ({ userId, url: url })) .catch(() => ({ userId, url: null })) ); diff --git a/src/services/s3/s3-utils.ts b/src/services/s3/s3-utils.ts index c6eb2da..2b39811 100644 --- a/src/services/s3/s3-utils.ts +++ b/src/services/s3/s3-utils.ts @@ -1,11 +1,15 @@ import { GetObjectCommand, S3 } from "@aws-sdk/client-s3"; -import Config from "../../config"; +import Config, { BucketName } from "../../config"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import { createPresignedPost } from "@aws-sdk/s3-presigned-post"; -export async function postResumeUrl(userId: string, client: S3) { +export async function postResumeUrl( + userId: string, + client: S3, + bucketName: BucketName +) { const { url, fields } = await createPresignedPost(client, { - Bucket: Config.S3_BUCKET_NAME, + Bucket: bucketName, Key: `${userId}.pdf`, Conditions: [ ["content-length-range", 0, Config.MAX_RESUME_SIZE_BYTES], // 6 MB max @@ -20,9 +24,13 @@ export async function postResumeUrl(userId: string, client: S3) { return { url, fields }; } -export async function getResumeUrl(userId: string, client: S3) { +export async function getResumeUrl( + userId: string, + client: S3, + bucketName: BucketName +) { const command = new GetObjectCommand({ - Bucket: Config.S3_BUCKET_NAME, + Bucket: bucketName, Key: `${userId}.pdf`, }); diff --git a/src/services/speakers/speakers-router.ts b/src/services/speakers/speakers-router.ts deleted file mode 100644 index 1eddac0..0000000 --- a/src/services/speakers/speakers-router.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Router } from "express"; -import { StatusCodes } from "http-status-codes"; -import { SpeakerValidator } from "./speakers-schema"; -import { Database } from "../../database"; -import RoleChecker from "../../middleware/role-checker"; -import { Role } from "../auth/auth-models"; - -const speakersRouter = Router(); - -// Get all speakers -speakersRouter.get("/", RoleChecker([], true), async (req, res, next) => { - try { - const speakers = await Database.SPEAKERS.find(); - return res.status(StatusCodes.OK).json(speakers); - } catch (error) { - next(error); - } -}); - -// Get a specific speaker -speakersRouter.get( - "/:SPEAKERID", - RoleChecker([], true), - async (req, res, next) => { - const speakerId = req.params.SPEAKERID; - - try { - const speaker = await Database.SPEAKERS.findOne({ speakerId }); - - if (!speaker) { - return res - .status(StatusCodes.NOT_FOUND) - .json({ error: "DoesNotExist" }); - } - - return res.status(StatusCodes.OK).json(speaker); - } catch (error) { - next(error); - } - } -); - -// Create a new speaker -speakersRouter.post( - "/", - RoleChecker([Role.Enum.STAFF]), - async (req, res, next) => { - try { - const validatedData = SpeakerValidator.parse(req.body); - const speaker = new Database.SPEAKERS(validatedData); - await speaker.save(); - return res.status(StatusCodes.CREATED).json(speaker); - } catch (error) { - next(error); - } - } -); - -// Update a speaker -speakersRouter.put( - "/:SPEAKERID", - RoleChecker([Role.Enum.STAFF], true), - async (req, res, next) => { - const speakerId = req.params.SPEAKERID; - - try { - const validatedData = SpeakerValidator.parse(req.body); - const speaker = await Database.SPEAKERS.findOneAndUpdate( - { speakerId }, - { $set: validatedData }, - { new: true, runValidators: true } - ); - - if (!speaker) { - return res - .status(StatusCodes.NOT_FOUND) - .json({ error: "DoesNotExist" }); - } - - return res.status(StatusCodes.OK).json(speaker); - } catch (error) { - next(error); - } - } -); - -// Delete a speaker -speakersRouter.delete( - "/:SPEAKERID", - RoleChecker([Role.Enum.STAFF], true), - async (req, res, next) => { - const speakerId = req.params.SPEAKERID; - - try { - await Database.SPEAKERS.findOneAndDelete({ speakerId }); - - return res.sendStatus(StatusCodes.NO_CONTENT); - } catch (error) { - next(error); - } - } -); - -export default speakersRouter; diff --git a/src/services/speakers/speakers-schema.ts b/src/services/speakers/speakers-schema.ts deleted file mode 100644 index bf69b4f..0000000 --- a/src/services/speakers/speakers-schema.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Schema } from "mongoose"; -import { z } from "zod"; -import { v4 as uuidv4 } from "uuid"; - -// Zod schema for speaker -export const SpeakerValidator = z.object({ - speakerId: z.coerce.string().default(() => uuidv4()), - name: z.string(), - title: z.string(), - bio: z.string(), - eventTitle: z.string(), - eventDescription: z.string(), - imgUrl: z.string(), -}); - -// Mongoose schema for speaker -export const SpeakerSchema = new Schema({ - speakerId: { - type: String, - required: true, - unique: true, - default: () => uuidv4(), - }, - name: { - type: String, - required: true, - }, - title: { - type: String, - required: true, - }, - bio: { - type: String, - required: true, - }, - eventTitle: { - type: String, - required: true, - }, - eventDescription: { - type: String, - required: true, - }, - imgUrl: { - type: String, - required: true, - }, -}); diff --git a/src/services/speakers/speakers-utils.ts b/src/services/speakers/speakers-utils.ts deleted file mode 100644 index e69de29..0000000