diff --git a/README.md b/README.md index d18a160..a7ce33f 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,13 @@ Please contact leadership to obtain the following: Branch protections are enabled on this repository. To contribute, please create a new branch and make a pull request. +The rules for branch names are lax, just be sure to include your name. + +An example branch name for a card that adds a reset password email would be: + +```text +rudra-reset-password-email +``` Your pull request title must follow the conventional commits specification. An example of a valid pull request title is: diff --git a/src/server/api/programs/mutations.ts b/src/server/api/programs/mutations.ts new file mode 100644 index 0000000..aa38084 --- /dev/null +++ b/src/server/api/programs/mutations.ts @@ -0,0 +1,72 @@ +"use server"; + +import { ProgramEnrollmentModel } from "@/server/models"; +import { ApiResponse, ProgramEnrollment } from "@/types"; +import authenticateServerFunction from "@/utils/authenticateServerFunction"; +import apiErrors from "@/utils/constants/apiErrors"; +import handleMongooseError from "@/utils/handleMongooseError"; + +export async function createProgramEnrollment( + programEnrollment: ProgramEnrollment, +): Promise> { + try { + // don't create program enrollment if one already exists + const existingProgramEnrollment = await ProgramEnrollmentModel.findOne({ + email: programEnrollment.email, + program: programEnrollment.program, + }); + + if (existingProgramEnrollment) { + return { + success: false, + error: apiErrors.programEnrollment.programEnrollmentAlreadyExists, + }; + } + + const newProgramEnrollmentDocument = + await ProgramEnrollmentModel.create(programEnrollment); + + // convert ObjectId to string + const newProgramEnrollment = newProgramEnrollmentDocument.toObject(); + newProgramEnrollment._id = String(newProgramEnrollment._id); + + return { success: true, data: newProgramEnrollment }; + } catch (error) { + return { success: false, error: handleMongooseError(error) }; + } +} + +export async function updateProgramEnrollment( + newProgramEnrollment: ProgramEnrollment, +): Promise> { + const authResposne = await authenticateServerFunction("admin"); + + if (!authResposne.success) { + return authResposne; + } + + try { + const updatedProgramEnrollmentDocument = + await ProgramEnrollmentModel.findOneAndUpdate( + { _id: newProgramEnrollment._id }, + newProgramEnrollment, + { new: true }, + ); + + if (!updatedProgramEnrollmentDocument) { + return { + success: false, + error: apiErrors.programEnrollment.programEnrollmentNotFound, + }; + } + + // convert ObjectId to string + const updatedProgramEnrollment = + updatedProgramEnrollmentDocument.toObject(); + updatedProgramEnrollment._id = String(updatedProgramEnrollment._id); + + return { success: true, data: updatedProgramEnrollment }; + } catch (error) { + return { success: false, error: handleMongooseError(error) }; + } +} diff --git a/src/server/api/programs/queries.ts b/src/server/api/programs/queries.ts new file mode 100644 index 0000000..0d6ccc0 --- /dev/null +++ b/src/server/api/programs/queries.ts @@ -0,0 +1,40 @@ +import dbConnect from "@/server/dbConnect"; +import { ProgramEnrollmentModel } from "@/server/models"; +import { ProgramEnrollment } from "@/types"; +import authenticateServerFunction from "@/utils/authenticateServerFunction"; +import handleMongooseError from "@/utils/handleMongooseError"; + +type ProgramEnrollmentFilters = Partial; + +export async function getProgramEnrollments(filters: ProgramEnrollmentFilters) { + await dbConnect(); + + const authResponse = await authenticateServerFunction(); + + if (!authResponse.success) { + return authResponse; + } + + try { + const programEnrollments = await ProgramEnrollmentModel.find(filters) + .lean() + .exec(); + + // convert ObjectId to string + programEnrollments.forEach((programEnrollment) => { + programEnrollment._id = String(programEnrollment._id); + }); + + return { success: true, data: programEnrollments }; + } catch (error) { + return { success: false, error: handleMongooseError(error) }; + } +} + +export async function getPendingProgramEnrollments() { + return getProgramEnrollments({ status: "pending" }); +} + +export async function getClientProgramEnrollments(email: string) { + return getProgramEnrollments({ email, status: "accepted" }); +} diff --git a/src/server/models/EnrollmentForm.ts b/src/server/models/EnrollmentForm.ts new file mode 100644 index 0000000..4909780 --- /dev/null +++ b/src/server/models/EnrollmentForm.ts @@ -0,0 +1,99 @@ +import mongoose, { Schema } from "mongoose"; + +import { EnrollmentForm } from "@/types"; + +const EnrollmentFormSchema = new Schema( + { + _id: { + type: String, + required: true, + }, + dateCreated: { + type: String, + required: true, + }, + firstName: { + type: String, + required: true, + }, + lastName: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + }, + password: { + type: String, + required: true, + }, + confirmPassword: { + type: String, + required: true, + }, + address: { + type: String, + required: true, + }, + phoneNumber: { + type: String, + required: true, + }, + hasClassACdl: { + type: Boolean, + required: true, + }, + classBDescription: { + type: String, + }, + dateOfBirth: { + type: String, + required: true, + }, + healthConditions: { + type: String, + }, + recentIllnessOrInjuryDetails: { + type: String, + }, + drivesSemiTruckOverRoad: { + type: Boolean, + required: true, + }, + isUsCitizen: { + type: Boolean, + required: true, + }, + referralSource: { + type: String, + }, + doctors: { + type: Array, + }, + employer: { + type: Object, + }, + monthlyHouseholdExpenses: { + type: Number, + required: true, + }, + ownerOperatorInfo: { + type: Object, + }, + healthMetrics: { + type: Object, + }, + healthGoals: { + type: Object, + }, + devices: { + type: Array, + }, + }, + { versionKey: false }, +); + +export default (mongoose.models + .EnrollmentForm as mongoose.Model) || + mongoose.model("EnrollmentForm", EnrollmentFormSchema); diff --git a/src/server/models/ProgramEnrollment.ts b/src/server/models/ProgramEnrollment.ts new file mode 100644 index 0000000..7006648 --- /dev/null +++ b/src/server/models/ProgramEnrollment.ts @@ -0,0 +1,37 @@ +import mongoose, { Schema } from "mongoose"; + +import { ProgramEnrollment } from "@/types"; + +const ProgramEnrollmentSchema = new Schema( + { + status: { + type: String, + required: true, + }, + program: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + }, + enrollmentForm: { + type: Schema.Types.ObjectId, + ref: "EnrollmentForm", + required: true, + }, + dateEnrolled: { + type: String, + required: true, + }, + }, + { versionKey: false }, +); + +export default (mongoose.models + .ProgramEnrollment as mongoose.Model) || + mongoose.model( + "ProgramEnrollment", + ProgramEnrollmentSchema, + ); diff --git a/src/server/models/User.ts b/src/server/models/User.ts index 1e6dba6..b38e0c6 100644 --- a/src/server/models/User.ts +++ b/src/server/models/User.ts @@ -32,4 +32,5 @@ const UserSchema = new Schema( { versionKey: false }, ); -export default mongoose.models.User || mongoose.model("User", UserSchema); +export default (mongoose.models.User as mongoose.Model) || + mongoose.model("User", UserSchema); diff --git a/src/server/models/index.ts b/src/server/models/index.ts index d3090c7..3a71b98 100644 --- a/src/server/models/index.ts +++ b/src/server/models/index.ts @@ -1 +1,3 @@ +export { default as EnrollmentFormModel } from "@/server/models/EnrollmentForm"; +export { default as ProgramEnrollmentModel } from "@/server/models/ProgramEnrollment"; export { default as UserModel } from "@/server/models/User"; diff --git a/src/types/Program.ts b/src/types/Program.ts new file mode 100644 index 0000000..956b417 --- /dev/null +++ b/src/types/Program.ts @@ -0,0 +1,6 @@ +export type Program = + | "Healthy Habits For The Long Haul" + | "Diabetes Prevention" + | "Rigs without Cigs" + | "Vaccine Voucher" + | "GPS (Get Preventative Screenings"; diff --git a/src/types/ProgramEnrollment.ts b/src/types/ProgramEnrollment.ts new file mode 100644 index 0000000..b151978 --- /dev/null +++ b/src/types/ProgramEnrollment.ts @@ -0,0 +1,10 @@ +import { EnrollmentForm, Program } from "@/types"; + +export type ProgramEnrollment = { + _id?: string; + status: "pending" | "accepted" | "rejected"; + program: Program; + email: string; + enrollmentForm: EnrollmentForm; + dateEnrolled: string; +}; diff --git a/src/types/index.ts b/src/types/index.ts index 41ff40a..bb58df4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,3 +1,5 @@ export * from "@/types/ApiResponse"; export * from "@/types/EnrollmentForm"; +export * from "@/types/Program"; +export * from "@/types/ProgramEnrollment"; export * from "@/types/User"; diff --git a/src/utils/authenticateServerFunction.ts b/src/utils/authenticateServerFunction.ts new file mode 100644 index 0000000..7a208be --- /dev/null +++ b/src/utils/authenticateServerFunction.ts @@ -0,0 +1,19 @@ +import { ApiResponse } from "@/types"; +import apiErrors from "@/utils/constants/apiErrors"; +import getUserSession from "@/utils/getUserSession"; + +export default async function authenticateServerFunction( + role?: "admin" | "client", +): Promise> { + const session = await getUserSession(); + + if (!session) { + return { success: false, error: apiErrors.unauthorized }; + } + + if (role && session.user.role !== role) { + return { success: false, error: apiErrors.unauthorized }; + } + + return { success: true, data: null }; +} diff --git a/src/utils/constants/apiErrors.ts b/src/utils/constants/apiErrors.ts index c50eddd..a89c474 100644 --- a/src/utils/constants/apiErrors.ts +++ b/src/utils/constants/apiErrors.ts @@ -1,4 +1,5 @@ const apiErrors = { + unauthorized: "You must be logged in to access this resource", mongoose: { CastError: "Error casting value", DivergentArrayError: "You modified an array in the middle of a save.", @@ -18,6 +19,10 @@ const apiErrors = { userNotFound: "User not found", userAlreadyExists: "User already exists", }, + programEnrollment: { + programEnrollmentNotFound: "Program enrollment not found", + programEnrollmentAlreadyExists: "Program enrollment already exists", + }, }; export default apiErrors;