diff --git a/backend/src/models/availability.ts b/backend/src/models/availability.ts new file mode 100644 index 0000000..bc8d639 --- /dev/null +++ b/backend/src/models/availability.ts @@ -0,0 +1,85 @@ +import mongoose, { Document, Schema } from "mongoose"; + +export interface ITimeSlot { + startTime: Date, + endTime: Date, +} + +export enum DayOfTheWeek { + MON = "Monday", + TUE = "Tuesday", + WED = "Wednesday", + THU = "Thursday", + FRI = "Friday", + SAT = "Saturday", + SUN = "Sunday", +} + +/** + * Represents the times of availability for a specific user on a given day of the week. + * @property userId - the user that defined this availability + * @property timeslots - the sections of the day where this User is available + * @property dayOfWeek - the day of the week that the availability pertains to + * @property isRecurring - whether the specified availability is recurring + * @property createdAt - the date this Availability was created + * @property updatedAt - the date this Availability was last updated + */ +export interface IAvailability extends Document { + userId: Schema.Types.ObjectId, + timeslots: ITimeSlot[], + dayOfWeek: DayOfTheWeek, + isRecurring: boolean, + createdAt: Date, + updatedAt: Date, +} + +export const TimeSlotSchema: Schema = new Schema( + { + startTime: { + type: Date, + required: [true, "A start time is required"], + index: true, + + }, + endTime: { + type: Date, + required: [true, "An end time is required"], + index: true, + + }, + } +) + +export const AvailabilitySchema: Schema = new Schema( + { + userId: { + type: Schema.Types.ObjectId, + required: [true, "User ID is required"], + }, + timeslots: { + type: [TimeSlotSchema], + required: true, + default: [] + }, + dayOfWeek: { + type: String, + enum: Object.values(DayOfTheWeek), + required: [true, "A day of the week must be specified"], + }, + isRecurring: { + type: Boolean, + required: true, + default: false, + } + }, + { + timestamps: true, + }, +); + +AvailabilitySchema.index({userId: 1}); +AvailabilitySchema.index({dayOfWeek: 1}); + +const Availability = mongoose.model("Availability", AvailabilitySchema); + +export default Availability; \ No newline at end of file diff --git a/backend/src/models/group.ts b/backend/src/models/group.ts new file mode 100644 index 0000000..cf74ac1 --- /dev/null +++ b/backend/src/models/group.ts @@ -0,0 +1,52 @@ +import mongoose, { Document, Schema } from "mongoose"; + +/** + * Represents a Group of Interviewers and Candidates + * @property name - the name of the group + * @property members - an array of references to some Users + * @property teamId - a reference to the Team for which this group is for + * @property createdBy - a reference to the User who created the group + * @property createdAt - the Date of creation + * @property updatedAt - the last time this was updated + */ +export interface IGroup extends Document { + name: string, + members: Schema.Types.ObjectId[], + teamId: Schema.Types.ObjectId, + createdBy: Schema.Types.ObjectId, + createdAt: Date, + updatedAt: Date +} + +const GroupSchema: Schema = new Schema( + { + name: { + type: String, + required: [true, "A group name is required"], + trim: true, + }, + members: { + type: [Schema.Types.ObjectId], + ref: "User", + required: true, + default: [], + }, + teamId: { + type: Schema.Types.ObjectId, + ref: "Team", + required: [true, "A team reference is required"], + }, + createdBy: { + type: Schema.Types.ObjectId, + ref: "User", + required: [true, "The creator of the group must be specified"], + }, + }, + { + timestamps: true, + }, +); + +const Group = mongoose.model("Group", GroupSchema); + +export default Group; \ No newline at end of file diff --git a/backend/src/models/meeting.ts b/backend/src/models/meeting.ts new file mode 100644 index 0000000..74f84c8 --- /dev/null +++ b/backend/src/models/meeting.ts @@ -0,0 +1,102 @@ +import mongoose, { Document, Schema, Types } from "mongoose"; + +export enum MeetingStatus { + SCHEDULED = "scheduled", + CONFIRMED = "confirmed", + RESCHEDULED = "rescheduled", + CANCELLED = "cancelled" +} +/** + * Represents a Meeting instance for a specific Candidate + * @property title - the title of the meeting + * @property description - a description of the meeting's purpose + * @property startTime - the scheduled start time of the meeting + * @property endTime - the scheduled end time of the meeting + * @property interviewerIds - an array of the ids of the assigned interviewers + * @property candidateId - the id of the Candidate + * @property teamId - the Team of which this meeting is held by + * @property status - the status of the meeting + * @property link - the link to the online meeting + * @property createdBy - a reference to the User who created the meeting + * @property createdAt - the time this Meeting was created + * @property updatedAt - the last time this Meeting was updated + */ +export interface IMeeting extends Document { + title: string, + description?: string, + startTime: Date, + endTime: Date, + interviewerIds: Schema.Types.ObjectId[], + candidateId: Schema.Types.ObjectId, + teamId: Schema.Types.ObjectId, + status: MeetingStatus, + link?: string, + createdBy: Schema.Types.ObjectId, + createdAt: Date, + updatedAt: Date, +} + +const MeetingSchema: Schema = new Schema( + { + title: { + type: String, + required: [true, "Meeting title is required"], + trim: true, + }, + description: { + type: String, + required: false + }, + startTime: { + type: Date, + required: [true, "A start time is required"], + }, + endTime: { + type: Date, + required: [true, "An end time is required"], + }, + interviewerIds: { + type: [Schema.Types.ObjectId], + ref: "User", + required: true, + validate: { + validator: (arr: Schema.Types.ObjectId[]) => arr.length > 0, + message: "At least one interviewer is required" + } + }, + candidateId: { + type: Schema.Types.ObjectId, + ref: "User", + required: true, + }, + teamId: { + type: Schema.Types.ObjectId, + ref: "Team", + required: true, + }, + status: { + type: String, + enum: Object.values(MeetingStatus), + default: MeetingStatus.SCHEDULED, + }, + link: { + type: String, + required: false + }, + createdBy: { + type: Schema.Types.ObjectId, + ref: "User", + required: [true, "The meeting creator must be specified"] + } + }, + { + timestamps: true, + }, +); + +MeetingSchema.index({start: 1, end: 1}); +MeetingSchema.index({status: 1, createdBy: 1}); + +const Meeting = mongoose.model("Meeting", MeetingSchema); + +export default Meeting; \ No newline at end of file diff --git a/backend/src/models/team.ts b/backend/src/models/team.ts new file mode 100644 index 0000000..f254ff4 --- /dev/null +++ b/backend/src/models/team.ts @@ -0,0 +1,51 @@ +import mongoose, { Document, Schema } from "mongoose"; + +/** + * Represents a Team in the app + * @property name - The name of the team + * @property description - A description of the team + * @property adminId - The id of the User who is the admin of the team + * @property isActive - Whether the Team is active or not + * @property createdAt - the date that the Team was created + * @property updatedAt - the date the Team was last updated on + */ +export interface ITeam extends Document { + name: string, + description?: string, + adminId: Schema.Types.ObjectId; + isActive: boolean, + createdAt: Date, + updatedAt: Date +} + +const TeamSchema: Schema = new Schema( + { + name: { + type: String, + required: [true, "A team name is required."], + trim: true, + }, + description: { + type: String, + required: false, + }, + adminId: { + type: Schema.Types.ObjectId, + ref: 'User', + required: [true, "A team admin is required."], + }, + isActive: { + type: Boolean, + default: true, + }, + }, + { + timestamps: true, + } +); + +const Team = mongoose.model("Team", TeamSchema); + +TeamSchema.index({adminId: 1}); + +export default Team; diff --git a/backend/src/models/user.ts b/backend/src/models/user.ts new file mode 100644 index 0000000..08aa4fa --- /dev/null +++ b/backend/src/models/user.ts @@ -0,0 +1,88 @@ +import mongoose, { Document, Schema } from "mongoose"; + +export enum UserRole { + ADMIN = "admin", + CANDIDATE = "candidate", + INTERVIEWER = "interviewer", +} + + +/** + * Represents a User of the app + * @property name - Full name of the user + * @property email - Email of the user + * @property password - The hashed password of the user + * @property teamId - The id of the Team this user belongs to + * @property role - the Users assigned role + * @property groupIds - Array of ids representing the Groups this user belongs to + * @property lastLogin - The date of this user's last login + * @property createdAt - The date this user object was created + * @property updatedAt - The date this user object was last updated + */ +export interface IUser extends Document { + name: string; + email: string; + password: string; + teamId?: Schema.Types.ObjectId; + role?: UserRole, + groupIds: Schema.Types.ObjectId[]; + lastLogin?: Date; + createdAt: Date; // Auto-managed by Mongoose + updatedAt: Date; // Auto-managed by Mongoose +} + +const UserSchema: Schema = new Schema( + { + name: { + type: String, + required: [true, "A name is required."], + trim: true + }, + email: { + type: String, + required: [true, "An email is required."], + trim: true, + lowercase: true, + match: [/[a-z0-9]+@[a-z]+\.[a-z]{2,3}/, "Invalid email address"] + }, + password: { + type: String, + required: [true, "A password is required."], + minLength: [8, "Password must be at least 8 characters long."], + }, + teamId: { + type: Schema.Types.ObjectId, + required: false, + ref: "Team", + }, + role: { + type: Object.values(UserRole), + required: false, + }, + groupIds: { + type: [ + { + type: Schema.Types.ObjectId, + ref: "Group" + } + ], + required: true, + default: [] + }, + lastLogin: { + type: Date, + required: false, + } + }, + { + timestamps: true // Automatically adds and updates createdAt and updatedAt + } +); + +UserSchema.index({name: 1, email: 1}); +UserSchema.index({role: 1, teamId: 1}); + + +const User = mongoose.model("User", UserSchema); + +export default User;