diff --git a/packages/backend/prisma/migrations/20250706231936_mentorship_sessions/migration.sql b/packages/backend/prisma/migrations/20250706231936_mentorship_sessions/migration.sql
new file mode 100644
index 0000000..52cad54
--- /dev/null
+++ b/packages/backend/prisma/migrations/20250706231936_mentorship_sessions/migration.sql
@@ -0,0 +1,85 @@
+-- CreateTable
+CREATE TABLE "MentorshipSessions" (
+ "id" SERIAL NOT NULL,
+ "mentor_id" INTEGER NOT NULL,
+ "description" TEXT,
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+ CONSTRAINT "MentorshipSessions_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "MentorshipSessionsMentees" (
+ "id" SERIAL NOT NULL,
+ "mentee_id" INTEGER NOT NULL,
+ "session_id" INTEGER NOT NULL,
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+ CONSTRAINT "MentorshipSessionsMentees_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "MentorshipSessionsDates" (
+ "id" SERIAL NOT NULL,
+ "session_id" INTEGER NOT NULL,
+ "date" TIMESTAMP(3) NOT NULL,
+ "link" TEXT,
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+ CONSTRAINT "MentorshipSessionsDates_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "MentorshipSessionsDatesNotes" (
+ "id" SERIAL NOT NULL,
+ "session_date_id" INTEGER NOT NULL,
+ "user_id" INTEGER NOT NULL,
+ "notes" TEXT,
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+ CONSTRAINT "MentorshipSessionsDatesNotes_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "MentorshipSessionsRating" (
+ "id" SERIAL NOT NULL,
+ "session_id" INTEGER NOT NULL,
+ "rating_user_id" INTEGER NOT NULL,
+ "rating_user_role" "users_roles" NOT NULL,
+ "rated_user_id" INTEGER NOT NULL,
+ "rated_user_role" "users_roles" NOT NULL,
+ "comments" TEXT,
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+ CONSTRAINT "MentorshipSessionsRating_pkey" PRIMARY KEY ("id")
+);
+
+-- AddForeignKey
+ALTER TABLE "MentorshipSessions" ADD CONSTRAINT "MentorshipSessions_mentor_id_fkey" FOREIGN KEY ("mentor_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "MentorshipSessionsMentees" ADD CONSTRAINT "MentorshipSessionsMentees_mentee_id_fkey" FOREIGN KEY ("mentee_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "MentorshipSessionsMentees" ADD CONSTRAINT "MentorshipSessionsMentees_session_id_fkey" FOREIGN KEY ("session_id") REFERENCES "MentorshipSessions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "MentorshipSessionsDates" ADD CONSTRAINT "MentorshipSessionsDates_session_id_fkey" FOREIGN KEY ("session_id") REFERENCES "MentorshipSessions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "MentorshipSessionsDatesNotes" ADD CONSTRAINT "MentorshipSessionsDatesNotes_session_date_id_fkey" FOREIGN KEY ("session_date_id") REFERENCES "MentorshipSessionsDates"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "MentorshipSessionsDatesNotes" ADD CONSTRAINT "MentorshipSessionsDatesNotes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "MentorshipSessionsRating" ADD CONSTRAINT "MentorshipSessionsRating_session_id_fkey" FOREIGN KEY ("session_id") REFERENCES "MentorshipSessions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "MentorshipSessionsRating" ADD CONSTRAINT "MentorshipSessionsRating_rating_user_id_fkey" FOREIGN KEY ("rating_user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "MentorshipSessionsRating" ADD CONSTRAINT "MentorshipSessionsRating_rated_user_id_fkey" FOREIGN KEY ("rated_user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/packages/backend/prisma/migrations/20250707001658_mentorship_sessions_restructure/migration.sql b/packages/backend/prisma/migrations/20250707001658_mentorship_sessions_restructure/migration.sql
new file mode 100644
index 0000000..4bca645
--- /dev/null
+++ b/packages/backend/prisma/migrations/20250707001658_mentorship_sessions_restructure/migration.sql
@@ -0,0 +1,154 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `created_at` on the `MentorshipSessions` table. All the data in the column will be lost.
+ - You are about to drop the column `description` on the `MentorshipSessions` table. All the data in the column will be lost.
+ - You are about to drop the column `mentor_id` on the `MentorshipSessions` table. All the data in the column will be lost.
+ - You are about to drop the `MentorshipSessionsDates` table. If the table is not empty, all the data it contains will be lost.
+ - You are about to drop the `MentorshipSessionsDatesNotes` table. If the table is not empty, all the data it contains will be lost.
+ - You are about to drop the `MentorshipSessionsMentees` table. If the table is not empty, all the data it contains will be lost.
+ - You are about to drop the `MentorshipSessionsRating` table. If the table is not empty, all the data it contains will be lost.
+ - Added the required column `dateEnd` to the `MentorshipSessions` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `dateStart` to the `MentorshipSessions` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `menteeId` to the `MentorshipSessions` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `mentorId` to the `MentorshipSessions` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `updatedAt` to the `MentorshipSessions` table without a default value. This is not possible if the table is not empty.
+
+*/
+-- CreateEnum
+CREATE TYPE "matching_status" AS ENUM ('PENDING', 'APPROVED', 'REJECTED', 'SUSPENDED');
+
+-- DropForeignKey
+ALTER TABLE "MentorshipSessions" DROP CONSTRAINT "MentorshipSessions_mentor_id_fkey";
+
+-- DropForeignKey
+ALTER TABLE "MentorshipSessionsDates" DROP CONSTRAINT "MentorshipSessionsDates_session_id_fkey";
+
+-- DropForeignKey
+ALTER TABLE "MentorshipSessionsDatesNotes" DROP CONSTRAINT "MentorshipSessionsDatesNotes_session_date_id_fkey";
+
+-- DropForeignKey
+ALTER TABLE "MentorshipSessionsDatesNotes" DROP CONSTRAINT "MentorshipSessionsDatesNotes_user_id_fkey";
+
+-- DropForeignKey
+ALTER TABLE "MentorshipSessionsMentees" DROP CONSTRAINT "MentorshipSessionsMentees_mentee_id_fkey";
+
+-- DropForeignKey
+ALTER TABLE "MentorshipSessionsMentees" DROP CONSTRAINT "MentorshipSessionsMentees_session_id_fkey";
+
+-- DropForeignKey
+ALTER TABLE "MentorshipSessionsRating" DROP CONSTRAINT "MentorshipSessionsRating_rated_user_id_fkey";
+
+-- DropForeignKey
+ALTER TABLE "MentorshipSessionsRating" DROP CONSTRAINT "MentorshipSessionsRating_rating_user_id_fkey";
+
+-- DropForeignKey
+ALTER TABLE "MentorshipSessionsRating" DROP CONSTRAINT "MentorshipSessionsRating_session_id_fkey";
+
+-- AlterTable
+ALTER TABLE "MentorshipSessions" DROP COLUMN "created_at",
+DROP COLUMN "description",
+DROP COLUMN "mentor_id",
+ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ADD COLUMN "dateEnd" TIMESTAMP(3) NOT NULL,
+ADD COLUMN "dateStart" TIMESTAMP(3) NOT NULL,
+ADD COLUMN "link" TEXT,
+ADD COLUMN "menteeId" INTEGER NOT NULL,
+ADD COLUMN "mentorId" INTEGER NOT NULL,
+ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;
+
+-- DropTable
+DROP TABLE "MentorshipSessionsDates";
+
+-- DropTable
+DROP TABLE "MentorshipSessionsDatesNotes";
+
+-- DropTable
+DROP TABLE "MentorshipSessionsMentees";
+
+-- DropTable
+DROP TABLE "MentorshipSessionsRating";
+
+-- CreateTable
+CREATE TABLE "MentorshipSessionsDescriptions" (
+ "id" SERIAL NOT NULL,
+ "sessionId" INTEGER NOT NULL,
+ "description" TEXT NOT NULL,
+
+ CONSTRAINT "MentorshipSessionsDescriptions_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "MentorshipSessionsMenteeNotes" (
+ "id" SERIAL NOT NULL,
+ "sessionId" INTEGER NOT NULL,
+ "notes" TEXT NOT NULL,
+
+ CONSTRAINT "MentorshipSessionsMenteeNotes_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "MentorshipSessionsMentorRating" (
+ "id" SERIAL NOT NULL,
+ "sessionId" INTEGER NOT NULL,
+ "rate" INTEGER NOT NULL,
+ "comment" TEXT,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+ CONSTRAINT "MentorshipSessionsMentorRating_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "MentorshipSessionsMenteeRating" (
+ "id" SERIAL NOT NULL,
+ "sessionId" INTEGER NOT NULL,
+ "rate" INTEGER NOT NULL,
+ "comment" TEXT,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+ CONSTRAINT "MentorshipSessionsMenteeRating_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "MatchedMentorMentee" (
+ "id" SERIAL NOT NULL,
+ "mentorId" INTEGER NOT NULL,
+ "menteeId" INTEGER NOT NULL,
+ "status" "matching_status" NOT NULL DEFAULT 'PENDING',
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+ CONSTRAINT "MatchedMentorMentee_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "MentorshipSessionsMentorRating_sessionId_key" ON "MentorshipSessionsMentorRating"("sessionId");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "MentorshipSessionsMenteeRating_sessionId_key" ON "MentorshipSessionsMenteeRating"("sessionId");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "MatchedMentorMentee_mentorId_menteeId_key" ON "MatchedMentorMentee"("mentorId", "menteeId");
+
+-- AddForeignKey
+ALTER TABLE "MentorshipSessions" ADD CONSTRAINT "MentorshipSessions_mentorId_fkey" FOREIGN KEY ("mentorId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "MentorshipSessions" ADD CONSTRAINT "MentorshipSessions_menteeId_fkey" FOREIGN KEY ("menteeId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "MentorshipSessionsDescriptions" ADD CONSTRAINT "MentorshipSessionsDescriptions_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "MentorshipSessions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "MentorshipSessionsMenteeNotes" ADD CONSTRAINT "MentorshipSessionsMenteeNotes_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "MentorshipSessions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "MentorshipSessionsMentorRating" ADD CONSTRAINT "MentorshipSessionsMentorRating_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "MentorshipSessions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "MentorshipSessionsMenteeRating" ADD CONSTRAINT "MentorshipSessionsMenteeRating_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "MentorshipSessions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "MatchedMentorMentee" ADD CONSTRAINT "MatchedMentorMentee_mentorId_fkey" FOREIGN KEY ("mentorId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "MatchedMentorMentee" ADD CONSTRAINT "MatchedMentorMentee_menteeId_fkey" FOREIGN KEY ("menteeId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/packages/backend/prisma/schema.prisma b/packages/backend/prisma/schema.prisma
index 8b07e37..64922a9 100644
--- a/packages/backend/prisma/schema.prisma
+++ b/packages/backend/prisma/schema.prisma
@@ -54,10 +54,20 @@ model users {
created_at DateTime?
provider String?
deleted_at Boolean @default(false)
- // MENTOR RELATIONSHIP
+
+ // MENTOR RELATIONSHIP
mentor mentors[]
// MENTEE RELATIONSHIP
mentee mentees[]
+
+ // MENTORSHIP SESSIONS - Updated relations
+ mentorSessions MentorshipSessions[] @relation("MentorSessions")
+ menteeSessions MentorshipSessions[] @relation("MenteeSessions")
+
+ // MATCHING RELATIONS
+ matchedAsMentor MatchedMentorMentee[] @relation("MatchedMentor")
+ matchedAsMentee MatchedMentorMentee[] @relation("MatchedMentee")
+
// EVENTS MANAGERS RELATIONSHIP
event_manager EventsManagers[]
// EVENTS SUBSCRIPTIONS RELATIONSHIP
@@ -548,4 +558,73 @@ model NewsletterSubscriptions {
}
+// MENTORSHIP SESSIONS
+model MentorshipSessions {
+ id Int @id @default(autoincrement())
+ mentorId Int
+ mentor users @relation("MentorSessions", fields: [mentorId], references: [id])
+ menteeId Int
+ mentee users @relation("MenteeSessions", fields: [menteeId], references: [id])
+ link String?
+ dateStart DateTime
+ dateEnd DateTime
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ // Relations
+ descriptions MentorshipSessionsDescriptions[]
+ menteeNotes MentorshipSessionsMenteeNotes[]
+ mentorRating MentorshipSessionsMentorRating?
+ menteeRating MentorshipSessionsMenteeRating?
+}
+
+model MentorshipSessionsDescriptions {
+ id Int @id @default(autoincrement())
+ sessionId Int
+ session MentorshipSessions @relation(fields: [sessionId], references: [id])
+ description String
+}
+
+model MentorshipSessionsMenteeNotes {
+ id Int @id @default(autoincrement())
+ sessionId Int
+ session MentorshipSessions @relation(fields: [sessionId], references: [id])
+ notes String
+}
+
+model MentorshipSessionsMentorRating {
+ id Int @id @default(autoincrement())
+ sessionId Int @unique
+ session MentorshipSessions @relation(fields: [sessionId], references: [id])
+ rate Int
+ comment String?
+ createdAt DateTime @default(now())
+}
+
+model MentorshipSessionsMenteeRating {
+ id Int @id @default(autoincrement())
+ sessionId Int @unique
+ session MentorshipSessions @relation(fields: [sessionId], references: [id])
+ rate Int
+ comment String?
+ createdAt DateTime @default(now())
+}
+enum matching_status {
+ PENDING
+ APPROVED
+ REJECTED
+ SUSPENDED
+}
+
+model MatchedMentorMentee {
+ id Int @id @default(autoincrement())
+ mentorId Int
+ mentor users @relation("MatchedMentor", fields: [mentorId], references: [id])
+ menteeId Int
+ mentee users @relation("MatchedMentee", fields: [menteeId], references: [id])
+ status matching_status @default(PENDING)
+ createdAt DateTime @default(now())
+
+ @@unique([mentorId, menteeId])
+}
diff --git a/packages/backend/src/admin/admin.service.ts b/packages/backend/src/admin/admin.service.ts
index b93ddf2..cd23a2f 100644
--- a/packages/backend/src/admin/admin.service.ts
+++ b/packages/backend/src/admin/admin.service.ts
@@ -796,7 +796,8 @@ export class AdminService {
const mentorApplications = await this.prisma.mentors.findMany({
orderBy: { created_at: 'desc' },
select: {
- id: true,
+ id: true, // This is the mentor application ID
+ resume: true,
user: {
select: {
id: true,
@@ -818,7 +819,8 @@ export class AdminService {
const mappedMentorApplications: MentorshipAdmin[] =
mentorApplications.map((mentor) => ({
- id: mentor.user.id,
+ id: mentor.user.id, // Keep user ID for backward compatibility
+ mentorApplicationId: mentor.id, // Add mentor application ID
identity: {
avatar: mentor.user.picture_upload_link,
firstName: mentor.user.first_name,
@@ -831,6 +833,7 @@ export class AdminService {
status: mentor.status,
capacity: mentor.max_mentees,
availability: mentor.availability,
+ resume: mentor.resume,
}));
return mappedMentorApplications;
@@ -845,7 +848,8 @@ export class AdminService {
const menteeApplications = await this.prisma.mentees.findMany({
orderBy: { created_at: 'desc' },
select: {
- id: true,
+ id: true, // This is the mentee application ID
+ resume: true, // Add resume field
user: {
select: {
id: true,
@@ -865,7 +869,8 @@ export class AdminService {
const mappedMenteeApplications: MenteeAdmin[] = menteeApplications.map(
(mentee) => ({
- id: mentee.user.id,
+ id: mentee.user.id, // Keep user ID for backward compatibility
+ menteeApplicationId: mentee.id, // Add mentee application ID
identity: {
avatar: mentee.user.picture_upload_link,
firstName: mentee.user.first_name,
@@ -877,6 +882,7 @@ export class AdminService {
email: mentee.user.email,
status: mentee.status,
experience: mentee.user.experience,
+ resume: mentee.resume, // Add resume field
}),
);
diff --git a/packages/backend/src/app.module.ts b/packages/backend/src/app.module.ts
index 8cd2fd2..b6d5aab 100644
--- a/packages/backend/src/app.module.ts
+++ b/packages/backend/src/app.module.ts
@@ -30,6 +30,7 @@ import { SettingsModule } from './settings/settings.module';
import { ContactUsModule } from './contact_us/contact_us.module';
import { SkillsModule } from './skills/skills.module';
import { AdminModule } from './admin/admin.module';
+import { MentorshipSessionsModule } from './mentorship_sessions/mentorship_sessions.module';
@Module({
imports: [
ConfigModule.forRoot({
@@ -67,6 +68,7 @@ import { AdminModule } from './admin/admin.module';
ContactUsModule,
SkillsModule,
AdminModule,
+ MentorshipSessionsModule,
],
controllers: [AppController],
providers: [
diff --git a/packages/backend/src/auth/auth.controller.ts b/packages/backend/src/auth/auth.controller.ts
index 4ba2d44..89d40a5 100644
--- a/packages/backend/src/auth/auth.controller.ts
+++ b/packages/backend/src/auth/auth.controller.ts
@@ -31,6 +31,7 @@ import {
LoginUserDto,
ResendVerificationEmailDto,
ResetPasswordDto,
+ ResetPasswordWithTokenDto,
} from './dto';
import { GetUser } from './decorators';
import { GoogleUser, LoginResponse } from './types';
@@ -216,6 +217,15 @@ export class AuthController {
return this.authService.changePassword(userId, changePasswordDto);
}
+ @Post('reset-password-with-token')
+ @Public()
+ @ApiOperation({ summary: 'Reset password using token from email' })
+ async resetPasswordWithToken(
+ @Body() resetPasswordWithTokenDto: ResetPasswordWithTokenDto,
+ ) {
+ return this.authService.resetPasswordWithToken(resetPasswordWithTokenDto);
+ }
+
@Public()
@Get('google')
@UseGuards(AuthGuard('google'))
diff --git a/packages/backend/src/auth/dto/auth.dto.ts b/packages/backend/src/auth/dto/auth.dto.ts
index ab4e36b..c6efce0 100644
--- a/packages/backend/src/auth/dto/auth.dto.ts
+++ b/packages/backend/src/auth/dto/auth.dto.ts
@@ -111,6 +111,31 @@ export class ResendVerificationEmailDto {
email: string;
}
+export class ResetPasswordWithTokenDto {
+ @ApiProperty({
+ description: 'Reset token from email',
+ example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
+ })
+ @IsString()
+ @IsNotEmpty()
+ token: string;
+
+ @ApiProperty({
+ description: 'New password',
+ example: 'NewPassword123!',
+ })
+ @IsString()
+ @MinLength(9)
+ newPassword: string;
+
+ @ApiProperty({
+ description: 'Confirm new password',
+ example: 'NewPassword123!',
+ })
+ @IsString()
+ confirmPassword: string;
+}
+
export class ChangePasswordDto {
@ApiProperty({
description: 'Current password',
diff --git a/packages/backend/src/auth/services/auth.service.ts b/packages/backend/src/auth/services/auth.service.ts
index 95c4c68..6423dd9 100644
--- a/packages/backend/src/auth/services/auth.service.ts
+++ b/packages/backend/src/auth/services/auth.service.ts
@@ -9,6 +9,7 @@ import {
GenerateResetTokenDto,
VerifyPasswordDto,
ResendVerificationEmailDto,
+ ResetPasswordWithTokenDto,
ChangePasswordDto,
} from '../dto/auth.dto';
import { AuthResponse, GoogleUser, LoginResponse, Tokens } from '../types';
@@ -525,4 +526,28 @@ export class AuthService {
throw new UnauthorizedException('Failed to authenticate with Google');
}
}
+
+ async resetPasswordWithToken(
+ resetPasswordWithTokenDto: ResetPasswordWithTokenDto,
+ ) {
+ const { token, newPassword, confirmPassword } = resetPasswordWithTokenDto;
+
+ // Verify token
+ const decoded = this.jwtService.verify(token) as any;
+ const userId = decoded.userId;
+
+ // Validate password
+ if (newPassword !== confirmPassword) {
+ throw new UnauthorizedException('Passwords do not match');
+ }
+
+ // Update password
+ const hashedPassword = await this.hashPassword(newPassword);
+ await this.prisma.users.update({
+ where: { id: userId },
+ data: { password_hash: hashedPassword },
+ });
+
+ return { message: 'Password reset successfully' };
+ }
}
diff --git a/packages/backend/src/auth/services/email.service.ts b/packages/backend/src/auth/services/email.service.ts
index d51e894..fac61a4 100644
--- a/packages/backend/src/auth/services/email.service.ts
+++ b/packages/backend/src/auth/services/email.service.ts
@@ -283,7 +283,8 @@ export class EmailService {
${content}
${footerText ? `` : ''}
diff --git a/packages/backend/src/mentee/mentee.service.ts b/packages/backend/src/mentee/mentee.service.ts
index 1f389c8..8cffc2f 100644
--- a/packages/backend/src/mentee/mentee.service.ts
+++ b/packages/backend/src/mentee/mentee.service.ts
@@ -8,7 +8,7 @@ import { CreateMenteeDto } from './dto/create-mentee.dto';
import { UpdateMenteeDto } from './dto/update-mentee.dto';
import { PrismaService } from 'src/database';
import { FilterMenteeDto } from './dto/filter-mentee.dto';
-import { Prisma, mentees_status } from '@prisma/client';
+import { Prisma, mentees_status, users_roles } from '@prisma/client';
import { FilesService } from 'src/files/files.service';
import { FileValidationEnum } from 'src/files/util/files-validation.enum';
@@ -317,7 +317,28 @@ export class MenteeService {
//const userId = updatedMentee.user_id;
//const userRole: users_roles = updatedMentee.status === 'APPROVED' ? 'MENTEE' : 'USER';
- return updatedMentee;
+ const userId = updatedMentee.user_id;
+ const userRole: users_roles =
+ updatedMentee.status === 'APPROVED' ? 'MENTEE' : 'USER';
+
+ const updatedMenteeUser = await this.prisma.users.update({
+ where: { id: userId },
+ data: { role: userRole },
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ middle_name: true,
+ mentor: true,
+ interests: {
+ select: {
+ interest: true,
+ },
+ },
+ },
+ });
+
+ return updatedMenteeUser;
} else {
throw new InternalServerErrorException(
`There was an error updating the mentee application with ID: ${menteeApplicationId}`,
diff --git a/packages/backend/src/mentorship_sessions/dto/create.dto.ts b/packages/backend/src/mentorship_sessions/dto/create.dto.ts
new file mode 100644
index 0000000..8cb6da2
--- /dev/null
+++ b/packages/backend/src/mentorship_sessions/dto/create.dto.ts
@@ -0,0 +1,85 @@
+import { IsString, IsOptional, IsUrl, IsInt, IsDate } from 'class-validator';
+import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
+
+export class CreateMentorshipSessionDto {
+ @ApiProperty({ description: 'User ID of the mentee', example: '10' })
+ @IsInt()
+ menteeId: number;
+
+ @ApiProperty({ description: 'Call link', example: 'https://meet.google.com' })
+ @IsString()
+ @IsUrl()
+ link: string;
+
+ @ApiProperty({
+ description: 'Start date and time',
+ example: '2025-01-01T10:00:00Z',
+ })
+ @IsDate()
+ dateStart: Date;
+
+ @ApiProperty({
+ description: 'End date and time',
+ example: '2025-01-01T10:00:00Z',
+ })
+ @IsDate()
+ dateEnd: Date;
+
+ @ApiPropertyOptional({
+ description: 'Description',
+ example: 'Description of the session',
+ })
+ @IsString()
+ @IsOptional()
+ description?: string;
+}
+
+export class CreateSessionMenteeNoteDto {
+ @ApiProperty({
+ description: 'ID for the session to take notes',
+ example: '10',
+ })
+ @IsInt()
+ sessionId: number;
+
+ @ApiProperty({
+ description: 'Mentee notes for the session',
+ example: 'Mentee note',
+ })
+ @IsString()
+ note: string;
+}
+
+export class CreateSessionRatingDto {
+ @ApiProperty({ description: 'ID for the rated session', example: '10' })
+ @IsInt()
+ sessionId: number;
+
+ @ApiProperty({ description: 'Mentor rating for the mentee', example: 5 })
+ @IsInt()
+ rate: number;
+
+ @ApiPropertyOptional({
+ description: 'Mentor notes for the mentee',
+ example: 'Mentor note',
+ })
+ @IsString()
+ @IsOptional()
+ note?: string;
+}
+
+export class CreateMatchingDto {
+ @ApiProperty({
+ description: 'ID of the mentor to match with a mentee',
+ example: '10',
+ })
+ @IsInt()
+ mentorId: number;
+
+ @ApiProperty({
+ description: 'ID of the mentee to match with a mentor',
+ example: '10',
+ })
+ @IsInt()
+ menteeId: number;
+}
diff --git a/packages/backend/src/mentorship_sessions/dto/filter.dto.ts b/packages/backend/src/mentorship_sessions/dto/filter.dto.ts
new file mode 100644
index 0000000..622bafd
--- /dev/null
+++ b/packages/backend/src/mentorship_sessions/dto/filter.dto.ts
@@ -0,0 +1,78 @@
+import { IsInt, IsOptional, IsDate } from 'class-validator';
+import { ApiPropertyOptional } from '@nestjs/swagger';
+
+export class FilterMentorshipSessionsDto {
+ @ApiPropertyOptional({
+ description: 'Sessions after the inserted date',
+ example: '2025-01-01T10:00:00Z',
+ })
+ @IsDate()
+ @IsOptional()
+ dateStartBefore?: Date;
+
+ @ApiPropertyOptional({
+ description: 'Sessions before the inserted date',
+ example: '2025-01-01T10:00:00Z',
+ })
+ @IsDate()
+ @IsOptional()
+ dateStartAfter?: Date;
+
+ @ApiPropertyOptional({
+ description: 'Sessions with this mentor ID',
+ example: '10',
+ })
+ @IsInt()
+ @IsOptional()
+ mentorId?: number;
+
+ @ApiPropertyOptional({
+ description: 'Sessions with this mentee ID',
+ example: '10',
+ })
+ @IsInt()
+ @IsOptional()
+ menteeId?: number;
+}
+
+export class FilterMentorshipSessionRatingsDto {
+ @ApiPropertyOptional({
+ description: 'Ratings after the inserted date',
+ example: '2025-01-01T10:00:00Z',
+ })
+ @IsDate()
+ @IsOptional()
+ dateStartBefore?: Date;
+
+ @ApiPropertyOptional({
+ description: 'Ratings before the inserted date',
+ example: '2025-01-01T10:00:00Z',
+ })
+ @IsDate()
+ @IsOptional()
+ dateStartAfter?: Date;
+
+ @ApiPropertyOptional({
+ description: 'Ratings for this session ID',
+ example: '10',
+ })
+ @IsInt()
+ @IsOptional()
+ sessionId?: number;
+
+ @ApiPropertyOptional({
+ description: 'Ratings with this mentor ID',
+ example: '10',
+ })
+ @IsInt()
+ @IsOptional()
+ mentorId?: number;
+
+ @ApiPropertyOptional({
+ description: 'Ratings with this mentee ID',
+ example: '10',
+ })
+ @IsInt()
+ @IsOptional()
+ menteeId?: number;
+}
diff --git a/packages/backend/src/mentorship_sessions/dto/update.dto.ts b/packages/backend/src/mentorship_sessions/dto/update.dto.ts
new file mode 100644
index 0000000..f3bd0fe
--- /dev/null
+++ b/packages/backend/src/mentorship_sessions/dto/update.dto.ts
@@ -0,0 +1,54 @@
+import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
+import { IsDate, IsEnum, IsOptional, IsString, IsUrl } from 'class-validator';
+import { matching_status } from '@prisma/client';
+
+export class UpdateMentorshipSessionDto {
+ @ApiPropertyOptional({
+ description: 'Call link',
+ example: 'https://meet.google.com',
+ })
+ @IsString()
+ @IsUrl()
+ @IsOptional()
+ link?: string;
+
+ @ApiPropertyOptional({
+ description: 'Start date and time',
+ example: '2025-01-01T10:00:00Z',
+ })
+ @IsDate()
+ @IsOptional()
+ dateStart?: Date;
+
+ @ApiPropertyOptional({
+ description: 'End date and time',
+ example: '2025-01-01T10:00:00Z',
+ })
+ @IsDate()
+ @IsOptional()
+ dateEnd?: Date;
+
+ @ApiPropertyOptional({
+ description: 'Description',
+ example: 'Description of the session',
+ })
+ @IsString()
+ @IsOptional()
+ description?: string;
+}
+
+export class UpdateSessionMenteeNoteDto {
+ @ApiPropertyOptional({
+ description: 'Mentee notes for the session',
+ example: 'Mentee note',
+ })
+ @IsString()
+ @IsOptional()
+ note?: string;
+}
+
+export class UpdateMatchingDto {
+ @ApiProperty({ description: 'New matching status', example: 'APPROVED' })
+ @IsEnum(matching_status)
+ status: matching_status;
+}
diff --git a/packages/backend/src/mentorship_sessions/entities/mentorship_sessions.entity.ts b/packages/backend/src/mentorship_sessions/entities/mentorship_sessions.entity.ts
new file mode 100644
index 0000000..adbd5ee
--- /dev/null
+++ b/packages/backend/src/mentorship_sessions/entities/mentorship_sessions.entity.ts
@@ -0,0 +1,290 @@
+import { ApiProperty } from '@nestjs/swagger';
+import {
+ IsOptional,
+ IsString,
+ IsInt,
+ IsNotEmpty,
+ IsUrl,
+ IsDate,
+ IsEnum,
+} from 'class-validator';
+import { matching_status } from '@prisma/client';
+
+export class MentorshipSessionEntity {
+ @ApiProperty({
+ description: 'Unique identifier for the mentorship session',
+ example: 1,
+ })
+ @IsInt()
+ id: number;
+
+ @ApiProperty({
+ description: 'ID of the mentor user',
+ example: 10,
+ })
+ @IsInt()
+ @IsNotEmpty()
+ mentorId: number;
+
+ @ApiProperty({
+ description: 'ID of the mentee user',
+ example: 15,
+ })
+ @IsInt()
+ @IsNotEmpty()
+ menteeId: number;
+
+ @ApiProperty({
+ description: 'Meeting link for the session',
+ example: 'https://meet.google.com/abc-defg-hij',
+ required: false,
+ })
+ @IsOptional()
+ @IsUrl()
+ link?: string;
+
+ @ApiProperty({
+ description: 'Start date and time of the session',
+ example: '2024-01-15T14:00:00Z',
+ })
+ @IsDate()
+ @IsNotEmpty()
+ dateStart: Date;
+
+ @ApiProperty({
+ description: 'End date and time of the session',
+ example: '2024-01-15T15:00:00Z',
+ })
+ @IsDate()
+ @IsNotEmpty()
+ dateEnd: Date;
+
+ @ApiProperty({
+ description: 'Creation timestamp',
+ example: '2024-01-10T10:00:00Z',
+ })
+ createdAt: Date;
+
+ @ApiProperty({
+ description: 'Last update timestamp',
+ example: '2024-01-10T10:00:00Z',
+ })
+ updatedAt: Date;
+}
+
+export class MentorshipSessionDescriptionEntity {
+ @ApiProperty({
+ description: 'Unique identifier for the session description',
+ example: 1,
+ })
+ @IsInt()
+ id: number;
+
+ @ApiProperty({
+ description: 'ID of the mentorship session',
+ example: 1,
+ })
+ @IsInt()
+ @IsNotEmpty()
+ sessionId: number;
+
+ @ApiProperty({
+ description: 'Description of the session',
+ example:
+ 'Technical interview preparation focusing on system design questions',
+ })
+ @IsString()
+ @IsNotEmpty()
+ description: string;
+}
+
+export class MentorshipSessionMenteeNotesEntity {
+ @ApiProperty({
+ description: 'Unique identifier for the mentee notes',
+ example: 1,
+ })
+ @IsInt()
+ id: number;
+
+ @ApiProperty({
+ description: 'ID of the mentorship session',
+ example: 1,
+ })
+ @IsInt()
+ @IsNotEmpty()
+ sessionId: number;
+
+ @ApiProperty({
+ description: 'Notes taken by the mentee during the session',
+ example:
+ 'Learned about microservices architecture and best practices for scaling applications',
+ })
+ @IsString()
+ @IsNotEmpty()
+ notes: string;
+}
+
+export class MentorshipSessionRatingEntity {
+ @ApiProperty({
+ description: 'Unique identifier for the session rating',
+ example: 1,
+ })
+ @IsInt()
+ id: number;
+
+ @ApiProperty({ description: 'ID of the mentorship session', example: 1 })
+ @IsInt()
+ @IsNotEmpty()
+ sessionId: number;
+
+ @ApiProperty({
+ description: 'Rating given (1-5)',
+ example: 4,
+ minimum: 1,
+ maximum: 5,
+ })
+ @IsInt()
+ @IsNotEmpty()
+ rate: number;
+
+ @ApiProperty({
+ description: 'Comment about the session',
+ example: 'Great session!',
+ required: false,
+ })
+ @IsOptional()
+ @IsString()
+ comment?: string;
+
+ @ApiProperty({
+ description: 'Creation timestamp of the rating',
+ example: '2024-01-15T16:00:00Z',
+ })
+ createdAt: Date;
+
+ // Add session property
+ @ApiProperty({ description: 'Session information', required: false })
+ @IsOptional()
+ session?: any;
+}
+
+export class MatchedMentorMenteeEntity {
+ @ApiProperty({
+ description: 'Unique identifier for the mentor-mentee match',
+ example: 1,
+ })
+ @IsInt()
+ id: number;
+
+ @ApiProperty({
+ description: 'ID of the mentor user',
+ example: 10,
+ })
+ @IsInt()
+ @IsNotEmpty()
+ mentorId: number;
+
+ @ApiProperty({
+ description: 'ID of the mentee user',
+ example: 15,
+ })
+ @IsInt()
+ @IsNotEmpty()
+ menteeId: number;
+
+ @ApiProperty({
+ description: 'Status of the mentor-mentee match',
+ example: 'APPROVED',
+ enum: matching_status,
+ })
+ @IsEnum(matching_status)
+ @IsNotEmpty()
+ status: matching_status;
+
+ @ApiProperty({
+ description: 'Creation timestamp of the match',
+ example: '2024-01-10T10:00:00Z',
+ })
+ createdAt: Date;
+}
+
+// Response DTOs for better API documentation
+export class MentorshipSessionResponseEntity extends MentorshipSessionEntity {
+ @ApiProperty({
+ description: 'Mentor user information',
+ type: 'object',
+ properties: {
+ id: { type: 'number', example: 10 },
+ firstName: { type: 'string', example: 'John' },
+ lastName: { type: 'string', example: 'Doe' },
+ email: { type: 'string', example: 'john.doe@example.com' },
+ },
+ })
+ mentor?: any;
+
+ @ApiProperty({
+ description: 'Mentee user information',
+ type: 'object',
+ properties: {
+ id: { type: 'number', example: 15 },
+ firstName: { type: 'string', example: 'Jane' },
+ lastName: { type: 'string', example: 'Smith' },
+ email: { type: 'string', example: 'jane.smith@example.com' },
+ },
+ })
+ mentee?: any;
+
+ @ApiProperty({
+ description: 'Session description',
+ type: MentorshipSessionDescriptionEntity,
+ required: false,
+ })
+ description?: MentorshipSessionDescriptionEntity;
+
+ @ApiProperty({
+ description: 'Mentee notes for the session',
+ type: MentorshipSessionMenteeNotesEntity,
+ required: false,
+ })
+ menteeNotes?: MentorshipSessionMenteeNotesEntity;
+
+ @ApiProperty({
+ description: 'Mentor rating for the session',
+ type: MentorshipSessionRatingEntity,
+ required: false,
+ })
+ mentorRating?: MentorshipSessionRatingEntity;
+
+ @ApiProperty({
+ description: 'Mentee rating for the session',
+ type: MentorshipSessionRatingEntity,
+ required: false,
+ })
+ menteeRating?: MentorshipSessionRatingEntity;
+}
+
+export class MatchedMentorMenteeResponseEntity extends MatchedMentorMenteeEntity {
+ @ApiProperty({
+ description: 'Mentor user information',
+ type: 'object',
+ properties: {
+ id: { type: 'number', example: 10 },
+ firstName: { type: 'string', example: 'John' },
+ lastName: { type: 'string', example: 'Doe' },
+ email: { type: 'string', example: 'john.doe@example.com' },
+ },
+ })
+ mentor?: any;
+
+ @ApiProperty({
+ description: 'Mentee user information',
+ type: 'object',
+ properties: {
+ id: { type: 'number', example: 15 },
+ firstName: { type: 'string', example: 'Jane' },
+ lastName: { type: 'string', example: 'Smith' },
+ email: { type: 'string', example: 'jane.smith@example.com' },
+ },
+ })
+ mentee?: any;
+}
diff --git a/packages/backend/src/mentorship_sessions/mentorship_sessions.controller.ts b/packages/backend/src/mentorship_sessions/mentorship_sessions.controller.ts
new file mode 100644
index 0000000..2c45a7c
--- /dev/null
+++ b/packages/backend/src/mentorship_sessions/mentorship_sessions.controller.ts
@@ -0,0 +1,417 @@
+import {
+ Controller,
+ Get,
+ Post,
+ Body,
+ Param,
+ Delete,
+ UseGuards,
+ Query,
+ Patch,
+ Put,
+} from '@nestjs/common';
+import { MentorshipSessionsService } from './mentorship_sessions.service';
+import {
+ MentorshipSessionEntity,
+ MentorshipSessionMenteeNotesEntity,
+ MentorshipSessionRatingEntity,
+ MatchedMentorMenteeEntity,
+} from './entities/mentorship_sessions.entity';
+import {
+ CreateMentorshipSessionDto,
+ CreateSessionMenteeNoteDto,
+ CreateSessionRatingDto,
+ CreateMatchingDto,
+} from './dto/create.dto';
+import {
+ FilterMentorshipSessionsDto,
+ FilterMentorshipSessionRatingsDto,
+} from './dto/filter.dto';
+import {
+ UpdateMentorshipSessionDto,
+ UpdateSessionMenteeNoteDto,
+ UpdateMatchingDto,
+} from './dto/update.dto';
+import { Roles, GetUser } from 'src/auth/decorators';
+import { JwtAuthGuard, RolesGuard } from 'src/auth/guards';
+import { JwtPayload } from 'src/auth/util/JwtPayload.interface';
+import {
+ ApiTags,
+ ApiBody,
+ ApiOperation,
+ ApiCreatedResponse,
+ ApiInternalServerErrorResponse,
+ ApiBearerAuth,
+ ApiQuery,
+ ApiOkResponse,
+ ApiNotFoundResponse,
+} from '@nestjs/swagger';
+import { User } from 'src/users/entities/user.entity';
+
+@UseGuards(JwtAuthGuard, RolesGuard)
+@ApiTags('Mentorship Sessions')
+@Controller('mentorship-sessions')
+export class MentorshipSessionsController {
+ constructor(
+ private readonly mentorshipSessionsService: MentorshipSessionsService,
+ ) {}
+
+ // MENTORSHIP SESSIONS
+ @Roles('MENTOR')
+ @Post('/mentorship-session')
+ @ApiBody({ type: CreateMentorshipSessionDto })
+ @ApiCreatedResponse({ type: MentorshipSessionEntity })
+ @ApiInternalServerErrorResponse({
+ description: 'Error creating the mentorship session: [ERROR MESSAGE]',
+ })
+ @ApiOperation({
+ summary: 'Create mentorship session',
+ description:
+ 'Creates mentorship session or throws Internal Server Error Exception. \n\n REQUIRED ROLES: **MENTOR**',
+ })
+ @ApiBearerAuth()
+ create(
+ @GetUser() user: JwtPayload,
+ @Body() createMentorshipSessionDto: CreateMentorshipSessionDto,
+ ) {
+ const newMentorshipSession: CreateMentorshipSessionDto = {
+ menteeId: createMentorshipSessionDto.menteeId,
+ link: createMentorshipSessionDto.link,
+ dateStart: createMentorshipSessionDto.dateStart,
+ dateEnd: createMentorshipSessionDto.dateEnd,
+ description: createMentorshipSessionDto.description,
+ };
+ return this.mentorshipSessionsService.createMentorshipSession(
+ user.sub,
+ newMentorshipSession,
+ );
+ }
+
+ @Roles('MENTOR')
+ @Put('/mentorship-session/:id')
+ @ApiBody({ type: UpdateMentorshipSessionDto })
+ @ApiOkResponse({ type: MentorshipSessionEntity })
+ @ApiNotFoundResponse({
+ description: 'There is no mentorship session with ID #[:id]',
+ })
+ @ApiInternalServerErrorResponse({
+ description:
+ 'Error updating mentorship session with ID #[:id]: [ERROR MESSAGE]',
+ })
+ @ApiOperation({
+ summary: 'Update mentorship session',
+ description:
+ 'Update mentorship session with ID. \n\n Only the mentor who created the session can update the mentorship session. \n\n REQUIRED ROLES: **MENTOR**',
+ })
+ @ApiBearerAuth()
+ update(
+ @Param('id') id: string,
+ @GetUser() user: JwtPayload,
+ @Body() updateMentorshipSessionDto: UpdateMentorshipSessionDto,
+ ) {
+ const updateMentorshipSession: UpdateMentorshipSessionDto = {
+ link: updateMentorshipSessionDto.link,
+ dateStart: updateMentorshipSessionDto.dateStart,
+ dateEnd: updateMentorshipSessionDto.dateEnd,
+ description: updateMentorshipSessionDto.description,
+ };
+
+ return this.mentorshipSessionsService.updateMentorshipSession(
+ +id,
+ user.sub,
+ updateMentorshipSession,
+ );
+ }
+
+ @Roles('MENTOR')
+ @Delete('/mentorship-session/:id')
+ @ApiOkResponse({ type: Number })
+ @ApiNotFoundResponse({
+ description: 'There is no mentorship session with ID #[:id] to delete',
+ })
+ @ApiInternalServerErrorResponse({ description: '[ERROR MESSAGE]' })
+ @ApiOperation({
+ summary: 'Delete mentorship session',
+ description:
+ 'Delete mentorship session with ID. \n\n Only the mentor who created the session can delete the mentorship session. \n\n REQUIRED ROLES: **MENTOR**',
+ })
+ @ApiBearerAuth()
+ remove(@Param('id') id: string, @GetUser() user: JwtPayload) {
+ return this.mentorshipSessionsService.removeMentorshipSession(
+ +id,
+ user.sub,
+ );
+ }
+
+ @Roles('MENTOR', 'MENTEE', 'USER')
+ @Get('/mentorship-session')
+ @ApiQuery({ type: FilterMentorshipSessionsDto })
+ @ApiOkResponse({ type: MentorshipSessionEntity, isArray: true })
+ @ApiInternalServerErrorResponse({
+ description: 'Error fetching mentorship sessions: [ERROR MESSAGE]',
+ })
+ @ApiOperation({
+ summary: 'Fetch mentorship sessions',
+ description:
+ 'Fetch mentorship sessions. \n\n REQUIRED ROLES: **MENTOR**, **MENTEE**, **USER** \n\n If you are a mentor, you can fetch all sessions you created. \n\n If you are a mentee, you can fetch all sessions you are matched with. \n\n If you are a user, you can fetch all sessions you are matched with.',
+ })
+ findAllMentorshipSessions(
+ @GetUser() user: JwtPayload,
+ @Query() filterMentorshipSessionsDto: FilterMentorshipSessionsDto,
+ ) {
+ return this.mentorshipSessionsService.findAllMentorshipSessions(
+ filterMentorshipSessionsDto,
+ user.sub,
+ );
+ }
+
+ @Roles('MENTOR', 'MENTEE', 'USER')
+ @Get('/mentorship-session/:id')
+ @ApiOkResponse({ type: MentorshipSessionEntity })
+ @ApiInternalServerErrorResponse({
+ description: 'Error fetching mentorship sessions by ID: [ERROR MESSAGE]',
+ })
+ @ApiOperation({
+ summary: 'Fetch mentorship session by ID',
+ description:
+ 'Fetch mentorship sessions by ID. \n\n REQUIRED ROLES: **MENTOR**, **MENTEE**, **USER** \n\n If you are a mentor, you can fetch all sessions you created. \n\n If you are a mentee, you can fetch all sessions you are matched with. \n\n If you are a user, you can fetch all sessions you are matched with.',
+ })
+ findMentorshipSessionsById(
+ @GetUser() user: JwtPayload,
+ @Param('id') id: string,
+ ) {
+ return this.mentorshipSessionsService.findMentorshipSessionsById(
+ +id,
+ user.sub,
+ );
+ }
+
+ // MENTEE NOTES
+ @Roles('MENTEE')
+ @Post('/mentee-notes')
+ @ApiBody({ type: CreateSessionMenteeNoteDto })
+ @ApiCreatedResponse({ type: MentorshipSessionMenteeNotesEntity })
+ @ApiInternalServerErrorResponse({
+ description:
+ 'Error creating mentorship session mentee notes: [ERROR MESSAGE]',
+ })
+ @ApiOperation({
+ summary: 'Create mentorship session mentee notes',
+ description:
+ 'Create mentorship session mentee notes. \n\n REQUIRED ROLES: **MENTEE**',
+ })
+ @ApiBearerAuth()
+ createMentorshipSessionMenteeNote(
+ @GetUser() user: JwtPayload,
+ @Param('id') id: string,
+ @Body() createSessionMenteeNoteDto: CreateSessionMenteeNoteDto,
+ ) {
+ return this.mentorshipSessionsService.createMentorshipSessionMenteeNote(
+ +id,
+ user.sub,
+ createSessionMenteeNoteDto,
+ );
+ }
+
+ @Roles('MENTEE')
+ @Put('/mentee-notes/:id')
+ @ApiBody({ type: UpdateSessionMenteeNoteDto })
+ @ApiOkResponse({ type: MentorshipSessionMenteeNotesEntity })
+ @ApiNotFoundResponse({
+ description: 'There is no mentorship session mentee notes with ID #[:id]',
+ })
+ @ApiInternalServerErrorResponse({
+ description:
+ 'Error updating mentorship session mentee notes with ID #[:id]: [ERROR MESSAGE]',
+ })
+ @ApiOperation({
+ summary: 'Update mentorship session mentee notes',
+ description:
+ 'Update mentorship session mentee notes. \n\n REQUIRED ROLES: **MENTEE**',
+ })
+ @ApiBearerAuth()
+ updateMentorshipSessionMenteeNote(
+ @GetUser() user: JwtPayload,
+ @Param('id') id: string,
+ @Body() updateSessionMenteeNoteDto: UpdateSessionMenteeNoteDto,
+ ) {
+ return this.mentorshipSessionsService.updateMentorshipSessionMenteeNote(
+ +id,
+ user.sub,
+ updateSessionMenteeNoteDto,
+ );
+ }
+
+ @Roles('MENTEE')
+ @Delete('/mentee-notes/:id')
+ @ApiOkResponse({ type: Number })
+ @ApiNotFoundResponse({
+ description:
+ 'There is no mentorship session mentee notes with ID #[:id] to delete',
+ })
+ @ApiInternalServerErrorResponse({ description: '[ERROR MESSAGE]' })
+ @ApiOperation({
+ summary: 'Delete mentorship session mentee notes',
+ description:
+ 'Delete mentorship session mentee notes. \n\n REQUIRED ROLES: **MENTEE**',
+ })
+ @ApiBearerAuth()
+ deleteMentorshipSessionMenteeNote(
+ @GetUser() user: JwtPayload,
+ @Param('id') id: string,
+ ) {
+ return this.mentorshipSessionsService.deleteMentorshipSessionMenteeNote(
+ +id,
+ user.sub,
+ );
+ }
+
+ @Roles('MENTEE', 'USER')
+ @Get('/mentee-notes/:mentorshipSessionId')
+ @ApiOkResponse({ type: MentorshipSessionMenteeNotesEntity, isArray: true })
+ @ApiInternalServerErrorResponse({
+ description:
+ 'Error fetching mentorship session mentee notes: [ERROR MESSAGE]',
+ })
+ @ApiOperation({
+ summary: 'Fetch mentorship session mentee notes',
+ description:
+ 'Fetch mentorship session mentee notes. \n\n REQUIRED ROLES: **MENTEE**, **USER** \n\n If you are a mentee, you can fetch all notes for all sessions you are matched with. \n\n If you are a user, you can fetch all notes for all sessions you are matched with.',
+ })
+ findAllMenteeNotes(
+ @GetUser() user: JwtPayload,
+ @Param('mentorshipSessionId') mentorshipSessionId: string,
+ ) {
+ return this.mentorshipSessionsService.findAllMentorshipSessionMenteeNotes(
+ +mentorshipSessionId,
+ user.sub,
+ );
+ }
+
+ //RATINGS (MENTOR AND MENTEE)
+
+ @Roles('MENTOR', 'MENTEE', 'USER')
+ @Post('/session-ratings')
+ @ApiBody({ type: CreateSessionRatingDto })
+ @ApiCreatedResponse({ type: MentorshipSessionRatingEntity })
+ @ApiInternalServerErrorResponse({
+ description: 'Error creating mentorship session rating: [ERROR MESSAGE]',
+ })
+ @ApiOperation({
+ summary: 'Create mentorship session rating',
+ description:
+ 'Create mentorship session rating. \n\n Only users involved in a session can rate',
+ })
+ @ApiBearerAuth()
+ createMentorshipSessionRating(
+ @GetUser() user: JwtPayload,
+ @Body() createSessionRatingDto: CreateSessionRatingDto,
+ ) {
+ return this.mentorshipSessionsService.createMentorshipSessionRating(
+ user.sub,
+ createSessionRatingDto,
+ );
+ }
+
+ @Roles('ADMIN', 'MENTOR', 'MENTEE', 'USER')
+ @Get('/session-ratings')
+ @ApiQuery({ type: FilterMentorshipSessionRatingsDto })
+ @ApiOkResponse({ type: MentorshipSessionRatingEntity, isArray: true })
+ @ApiInternalServerErrorResponse({
+ description: 'Error fetching mentorship session rating: [ERROR MESSAGE]',
+ })
+ @ApiOperation({
+ summary: 'Fetch mentorship session ratings',
+ description:
+ 'Fetch mentorship session ratings. \n\n REQUIRED ROLES: **ADMIN**, **MENTOR**, **MENTEE**, **USER** \n\n If you are a mentor, you can fetch all ratings for all sessions you created. \n\n If you are a mentee, you can fetch all ratings for all sessions you are matched with. \n\n If you are a user, you can fetch all ratings for all sessions you are matched with.',
+ })
+ findAllMentorshipSessionRatings(
+ @GetUser() user: JwtPayload,
+ @Query()
+ filterMentorshipSessionRatingsDto: FilterMentorshipSessionRatingsDto,
+ ) {
+ return this.mentorshipSessionsService.findAllMentorshipSessionRatings(
+ filterMentorshipSessionRatingsDto,
+ user.sub,
+ );
+ }
+
+ // MATCHING
+
+ @Roles('ADMIN')
+ @Post('/matching')
+ @ApiBody({ type: CreateMatchingDto })
+ @ApiCreatedResponse({ type: MatchedMentorMenteeEntity })
+ @ApiInternalServerErrorResponse({
+ description: 'Error matching MENTOR with MENTEE: [ERROR MESSAGE]',
+ })
+ @ApiOperation({
+ summary: 'Create matching',
+ description:
+ 'Create matching between MENTOR and MENTEE. \n\n REQUIRED ROLES: **ADMIN**',
+ })
+ @ApiBearerAuth()
+ createMatching(@Body() createMatchingDto: CreateMatchingDto) {
+ return this.mentorshipSessionsService.createMatching(createMatchingDto);
+ }
+
+ @Roles('MENTOR')
+ @Patch('/matching/:id')
+ @ApiBody({ type: UpdateMatchingDto })
+ @ApiOkResponse({ type: MatchedMentorMenteeEntity })
+ @ApiNotFoundResponse({
+ description: 'There is no matching with ID #[:id] to update',
+ })
+ @ApiInternalServerErrorResponse({
+ description: 'Error updating matching with ID #[:id]: [ERROR MESSAGE]',
+ })
+ @ApiOperation({
+ summary: 'Update matching status',
+ description:
+ 'Update the status of a match between MENTOR and MENTEE. \n\n REQUIRED ROLES: **MENTOR**',
+ })
+ @ApiBearerAuth()
+ updateMatchingStatus(
+ @Param('id') id: string,
+ @GetUser() user: JwtPayload,
+ @Body() updateMatchingDto: UpdateMatchingDto,
+ ) {
+ return this.mentorshipSessionsService.updateMatchingStatus(
+ +id,
+ updateMatchingDto,
+ user.sub,
+ );
+ }
+
+ @Roles('ADMIN')
+ @Get('/matching/mentor/:mentorId')
+ @ApiOkResponse({ type: User, isArray: true })
+ @ApiInternalServerErrorResponse({
+ description: 'Error fetching matching users: [ERROR MESSAGE]',
+ })
+ @ApiOperation({
+ summary: 'Get match suggestions for a mentor',
+ description:
+ 'Get match suggestions for a mentor. \n\n REQUIRED ROLES: **ADMIN**',
+ })
+ @ApiBearerAuth()
+ findAllMatchingForMentor(@Param('mentorId') mentorId: string) {
+ return this.mentorshipSessionsService.findAllMatchingForMentor(+mentorId);
+ }
+
+ @Roles('ADMIN')
+ @Get('/matching/mentee/:menteeId')
+ @ApiOkResponse({ type: User, isArray: true })
+ @ApiInternalServerErrorResponse({
+ description: 'Error fetching matching users: [ERROR MESSAGE]',
+ })
+ @ApiOperation({
+ summary: 'Get match suggestions for a mentee',
+ description:
+ 'Get match suggestions for a mentee. \n\n REQUIRED ROLES: **ADMIN**',
+ })
+ @ApiBearerAuth()
+ findAllMatchingForMentee(@Param('menteeId') menteeId: string) {
+ return this.mentorshipSessionsService.findAllMatchingForMentee(+menteeId);
+ }
+}
diff --git a/packages/backend/src/mentorship_sessions/mentorship_sessions.module.ts b/packages/backend/src/mentorship_sessions/mentorship_sessions.module.ts
new file mode 100644
index 0000000..e89de2c
--- /dev/null
+++ b/packages/backend/src/mentorship_sessions/mentorship_sessions.module.ts
@@ -0,0 +1,10 @@
+import { Module } from '@nestjs/common';
+import { MentorshipSessionsService } from './mentorship_sessions.service';
+import { MentorshipSessionsController } from './mentorship_sessions.controller';
+import { PrismaService } from 'src/database';
+
+@Module({
+ controllers: [MentorshipSessionsController],
+ providers: [PrismaService, MentorshipSessionsService],
+})
+export class MentorshipSessionsModule {}
diff --git a/packages/backend/src/mentorship_sessions/mentorship_sessions.service.ts b/packages/backend/src/mentorship_sessions/mentorship_sessions.service.ts
new file mode 100644
index 0000000..98d288a
--- /dev/null
+++ b/packages/backend/src/mentorship_sessions/mentorship_sessions.service.ts
@@ -0,0 +1,1568 @@
+import {
+ InternalServerErrorException,
+ NotFoundException,
+ Injectable,
+} from '@nestjs/common';
+import {
+ MentorshipSessionMenteeNotesEntity,
+ MentorshipSessionRatingEntity,
+ MentorshipSessionResponseEntity,
+ MatchedMentorMenteeResponseEntity,
+} from './entities/mentorship_sessions.entity';
+import {
+ CreateMentorshipSessionDto,
+ CreateSessionMenteeNoteDto,
+ CreateSessionRatingDto,
+ CreateMatchingDto,
+} from './dto/create.dto';
+import {
+ FilterMentorshipSessionsDto,
+ FilterMentorshipSessionRatingsDto,
+} from './dto/filter.dto';
+import { PrismaService } from 'src/database/prisma.service';
+import {
+ UpdateMentorshipSessionDto,
+ UpdateSessionMenteeNoteDto,
+ UpdateMatchingDto,
+} from './dto/update.dto';
+
+@Injectable()
+export class MentorshipSessionsService {
+ constructor(private prisma: PrismaService) {}
+
+ async createMentorshipSession(
+ mentorId: number,
+ createMentorshipSessionDto: CreateMentorshipSessionDto,
+ ): Promise {
+ try {
+ // Check if there's already a matching between this mentor and mentee
+ const existingMatching = await this.prisma.matchedMentorMentee.findFirst({
+ where: {
+ mentorId: mentorId,
+ menteeId: createMentorshipSessionDto.menteeId,
+ status: 'APPROVED',
+ },
+ });
+
+ if (!existingMatching) {
+ throw new NotFoundException(
+ 'No approved matching found between this mentor and mentee',
+ );
+ }
+
+ // Create the mentorship session
+ const mentorshipSession = await this.prisma.mentorshipSessions.create({
+ data: {
+ mentorId: mentorId,
+ menteeId: createMentorshipSessionDto.menteeId,
+ link: createMentorshipSessionDto.link,
+ dateStart: createMentorshipSessionDto.dateStart,
+ dateEnd: createMentorshipSessionDto.dateEnd,
+ },
+ include: {
+ mentor: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ },
+ },
+ mentee: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ },
+ },
+ },
+ });
+
+ // If description is provided, create the description record
+ if (createMentorshipSessionDto.description) {
+ await this.prisma.mentorshipSessionsDescriptions.create({
+ data: {
+ sessionId: mentorshipSession.id,
+ description: createMentorshipSessionDto.description,
+ },
+ });
+ }
+
+ // Fetch the complete session with all related data
+ const completeSession = await this.prisma.mentorshipSessions.findUnique({
+ where: { id: mentorshipSession.id },
+ include: {
+ mentor: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ },
+ },
+ mentee: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ },
+ },
+ descriptions: true,
+ menteeNotes: true,
+ mentorRating: true,
+ menteeRating: true,
+ },
+ });
+
+ // Transform the response to match the entity structure
+ const response: MentorshipSessionResponseEntity = {
+ id: completeSession.id,
+ mentorId: completeSession.mentorId,
+ menteeId: completeSession.menteeId,
+ link: completeSession.link,
+ dateStart: completeSession.dateStart,
+ dateEnd: completeSession.dateEnd,
+ createdAt: completeSession.createdAt,
+ updatedAt: completeSession.updatedAt,
+ mentor: {
+ id: completeSession.mentor.id,
+ firstName: completeSession.mentor.first_name,
+ lastName: completeSession.mentor.last_name,
+ email: completeSession.mentor.email,
+ profession: completeSession.mentor.profession,
+ avatar: completeSession.mentor.picture_upload_link,
+ },
+ mentee: {
+ id: completeSession.mentee.id,
+ firstName: completeSession.mentee.first_name,
+ lastName: completeSession.mentee.last_name,
+ email: completeSession.mentee.email,
+ profession: completeSession.mentee.profession,
+ avatar: completeSession.mentee.picture_upload_link,
+ },
+ description: completeSession.descriptions[0] || undefined,
+ menteeNotes: completeSession.menteeNotes[0] || undefined,
+ mentorRating: completeSession.mentorRating || undefined,
+ menteeRating: completeSession.menteeRating || undefined,
+ };
+
+ return response;
+ } catch (error) {
+ if (error instanceof NotFoundException) {
+ throw error;
+ }
+
+ throw new InternalServerErrorException(
+ `Error creating the mentorship session: ${error.message}`,
+ );
+ }
+ }
+
+ async updateMentorshipSession(
+ id: number,
+ mentorId: number,
+ updateMentorshipSessionDto: UpdateMentorshipSessionDto,
+ ): Promise {
+ try {
+ // Check if the mentorship session exists and belongs to the mentor
+ const existingSession = await this.prisma.mentorshipSessions.findFirst({
+ where: {
+ id: id,
+ mentorId: mentorId,
+ },
+ });
+
+ if (!existingSession) {
+ throw new NotFoundException(
+ `There is no mentorship session with ID #${id}`,
+ );
+ }
+
+ // Prepare the update data
+ const updateData: any = {};
+
+ if (updateMentorshipSessionDto.link !== undefined) {
+ updateData.link = updateMentorshipSessionDto.link;
+ }
+
+ if (updateMentorshipSessionDto.dateStart !== undefined) {
+ updateData.dateStart = updateMentorshipSessionDto.dateStart;
+ }
+
+ if (updateMentorshipSessionDto.dateEnd !== undefined) {
+ updateData.dateEnd = updateMentorshipSessionDto.dateEnd;
+ }
+
+ // Update the mentorship session
+ await this.prisma.mentorshipSessions.update({
+ where: { id: id },
+ data: updateData,
+ include: {
+ mentor: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ },
+ },
+ mentee: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ },
+ },
+ },
+ });
+
+ // Update description if provided
+ if (updateMentorshipSessionDto.description !== undefined) {
+ // Check if description already exists
+ const existingDescription =
+ await this.prisma.mentorshipSessionsDescriptions.findFirst({
+ where: { sessionId: id },
+ });
+
+ if (existingDescription) {
+ // Update existing description
+ await this.prisma.mentorshipSessionsDescriptions.update({
+ where: { id: existingDescription.id },
+ data: { description: updateMentorshipSessionDto.description },
+ });
+ } else {
+ // Create new description
+ await this.prisma.mentorshipSessionsDescriptions.create({
+ data: {
+ sessionId: id,
+ description: updateMentorshipSessionDto.description,
+ },
+ });
+ }
+ }
+
+ // Fetch the complete updated session with all related data
+ const completeSession = await this.prisma.mentorshipSessions.findUnique({
+ where: { id: id },
+ include: {
+ mentor: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ },
+ },
+ mentee: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ },
+ },
+ descriptions: true,
+ menteeNotes: true,
+ mentorRating: true,
+ menteeRating: true,
+ },
+ });
+
+ // Transform the response to match the entity structure
+ const response: MentorshipSessionResponseEntity = {
+ id: completeSession.id,
+ mentorId: completeSession.mentorId,
+ menteeId: completeSession.menteeId,
+ link: completeSession.link,
+ dateStart: completeSession.dateStart,
+ dateEnd: completeSession.dateEnd,
+ createdAt: completeSession.createdAt,
+ updatedAt: completeSession.updatedAt,
+ mentor: {
+ id: completeSession.mentor.id,
+ firstName: completeSession.mentor.first_name,
+ lastName: completeSession.mentor.last_name,
+ email: completeSession.mentor.email,
+ profession: completeSession.mentor.profession,
+ avatar: completeSession.mentor.picture_upload_link,
+ },
+ mentee: {
+ id: completeSession.mentee.id,
+ firstName: completeSession.mentee.first_name,
+ lastName: completeSession.mentee.last_name,
+ email: completeSession.mentee.email,
+ profession: completeSession.mentee.profession,
+ avatar: completeSession.mentee.picture_upload_link,
+ },
+ description: completeSession.descriptions[0] || undefined,
+ menteeNotes: completeSession.menteeNotes[0] || undefined,
+ mentorRating: completeSession.mentorRating || undefined,
+ menteeRating: completeSession.menteeRating || undefined,
+ };
+
+ return response;
+ } catch (error) {
+ if (error instanceof NotFoundException) {
+ throw error;
+ }
+
+ throw new InternalServerErrorException(
+ `Error updating mentorship session with ID #${id}: ${error.message}`,
+ );
+ }
+ }
+
+ async removeMentorshipSession(id: number, mentorId: number): Promise {
+ try {
+ // Check if the mentorship session exists and belongs to the mentor
+ const existingSession = await this.prisma.mentorshipSessions.findFirst({
+ where: {
+ id: id,
+ mentorId: mentorId,
+ },
+ });
+
+ if (!existingSession) {
+ throw new NotFoundException(
+ `There is no mentorship session with ID #${id} to delete`,
+ );
+ }
+
+ // Delete related records first (due to foreign key constraints)
+ // Delete session descriptions
+ await this.prisma.mentorshipSessionsDescriptions.deleteMany({
+ where: { sessionId: id },
+ });
+
+ // Delete mentee notes
+ await this.prisma.mentorshipSessionsMenteeNotes.deleteMany({
+ where: { sessionId: id },
+ });
+
+ // Delete mentor rating
+ await this.prisma.mentorshipSessionsMentorRating.deleteMany({
+ where: { sessionId: id },
+ });
+
+ // Delete mentee rating
+ await this.prisma.mentorshipSessionsMenteeRating.deleteMany({
+ where: { sessionId: id },
+ });
+
+ // Finally, delete the mentorship session
+ await this.prisma.mentorshipSessions.delete({
+ where: { id: id },
+ });
+
+ return id;
+ } catch (error) {
+ if (error instanceof NotFoundException) {
+ throw error;
+ }
+
+ throw new InternalServerErrorException(
+ `Error deleting mentorship session with ID #${id}: ${error.message}`,
+ );
+ }
+ }
+
+ async findAllMentorshipSessions(
+ filterMentorshipSessionsDto: FilterMentorshipSessionsDto,
+ userId: number,
+ ): Promise {
+ try {
+ // Get user role to determine what sessions they can access
+ const user = await this.prisma.users.findFirst({
+ where: { id: userId },
+ select: { role: true },
+ });
+
+ if (!user) {
+ throw new NotFoundException('User not found');
+ }
+
+ // Build the base where clause
+ const whereClause: any = {};
+
+ // Add role-based filtering
+ if (user.role === 'MENTOR') {
+ whereClause.mentorId = userId;
+ } else if (user.role === 'MENTEE') {
+ whereClause.menteeId = userId;
+ } else if (user.role === 'USER') {
+ // For regular users, they can see sessions where they are either mentor or mentee
+ whereClause.OR = [{ mentorId: userId }, { menteeId: userId }];
+ }
+
+ // Add date filters
+ if (filterMentorshipSessionsDto.dateStartBefore) {
+ whereClause.dateStart = {
+ ...whereClause.dateStart,
+ lte: filterMentorshipSessionsDto.dateStartBefore,
+ };
+ }
+
+ if (filterMentorshipSessionsDto.dateStartAfter) {
+ whereClause.dateStart = {
+ ...whereClause.dateStart,
+ gte: filterMentorshipSessionsDto.dateStartAfter,
+ };
+ }
+
+ // Add mentor/mentee ID filters
+ if (filterMentorshipSessionsDto.mentorId) {
+ whereClause.mentorId = filterMentorshipSessionsDto.mentorId;
+ }
+
+ if (filterMentorshipSessionsDto.menteeId) {
+ whereClause.menteeId = filterMentorshipSessionsDto.menteeId;
+ }
+
+ // Fetch sessions with all related data
+ const sessions = await this.prisma.mentorshipSessions.findMany({
+ where: whereClause,
+ include: {
+ mentor: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ },
+ },
+ mentee: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ },
+ },
+ descriptions: true,
+ menteeNotes: true,
+ mentorRating: true,
+ menteeRating: true,
+ },
+ orderBy: {
+ createdAt: 'desc',
+ },
+ });
+
+ // Transform the response to match the entity structure
+ const response: MentorshipSessionResponseEntity[] = sessions.map(
+ (session) => ({
+ id: session.id,
+ mentorId: session.mentorId,
+ menteeId: session.menteeId,
+ link: session.link,
+ dateStart: session.dateStart,
+ dateEnd: session.dateEnd,
+ createdAt: session.createdAt,
+ updatedAt: session.updatedAt,
+ mentor: {
+ id: session.mentor.id,
+ firstName: session.mentor.first_name,
+ lastName: session.mentor.last_name,
+ email: session.mentor.email,
+ profession: session.mentor.profession,
+ avatar: session.mentor.picture_upload_link,
+ },
+ mentee: {
+ id: session.mentee.id,
+ firstName: session.mentee.first_name,
+ lastName: session.mentee.last_name,
+ email: session.mentee.email,
+ profession: session.mentee.profession,
+ avatar: session.mentee.picture_upload_link,
+ },
+ description: session.descriptions[0] || undefined,
+ menteeNotes: session.menteeNotes[0] || undefined,
+ mentorRating: session.mentorRating || undefined,
+ menteeRating: session.menteeRating || undefined,
+ }),
+ );
+
+ return response;
+ } catch (error) {
+ if (error instanceof NotFoundException) {
+ throw error;
+ }
+
+ throw new InternalServerErrorException(
+ `Error fetching mentorship sessions: ${error.message}`,
+ );
+ }
+ }
+
+ async findMentorshipSessionsById(
+ id: number,
+ userId: number,
+ ): Promise {
+ try {
+ // Get user role to determine what sessions they can access
+ const user = await this.prisma.users.findFirst({
+ where: { id: userId },
+ select: { role: true },
+ });
+
+ if (!user) {
+ throw new NotFoundException('User not found');
+ }
+
+ // Build the base where clause
+ const whereClause: any = { id: id };
+
+ // Add role-based filtering
+ if (user.role === 'MENTOR') {
+ whereClause.mentorId = userId;
+ } else if (user.role === 'MENTEE') {
+ whereClause.menteeId = userId;
+ } else if (user.role === 'USER') {
+ // For regular users, they can see sessions where they are either mentor or mentee
+ whereClause.OR = [
+ { id: id, mentorId: userId },
+ { id: id, menteeId: userId },
+ ];
+ }
+
+ // Fetch the session with all related data
+ const session = await this.prisma.mentorshipSessions.findFirst({
+ where: whereClause,
+ include: {
+ mentor: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ },
+ },
+ mentee: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ },
+ },
+ descriptions: true,
+ menteeNotes: true,
+ mentorRating: true,
+ menteeRating: true,
+ },
+ });
+
+ if (!session) {
+ throw new NotFoundException(
+ `There is no mentorship session with ID #${id}`,
+ );
+ }
+
+ // Transform the response to match the entity structure
+ const response: MentorshipSessionResponseEntity = {
+ id: session.id,
+ mentorId: session.mentorId,
+ menteeId: session.menteeId,
+ link: session.link,
+ dateStart: session.dateStart,
+ dateEnd: session.dateEnd,
+ createdAt: session.createdAt,
+ updatedAt: session.updatedAt,
+ mentor: {
+ id: session.mentor.id,
+ firstName: session.mentor.first_name,
+ lastName: session.mentor.last_name,
+ email: session.mentor.email,
+ profession: session.mentor.profession,
+ avatar: session.mentor.picture_upload_link,
+ },
+ mentee: {
+ id: session.mentee.id,
+ firstName: session.mentee.first_name,
+ lastName: session.mentee.last_name,
+ email: session.mentee.email,
+ profession: session.mentee.profession,
+ avatar: session.mentee.picture_upload_link,
+ },
+ description: session.descriptions[0] || undefined,
+ menteeNotes: session.menteeNotes[0] || undefined,
+ mentorRating: session.mentorRating || undefined,
+ menteeRating: session.menteeRating || undefined,
+ };
+
+ return response;
+ } catch (error) {
+ if (error instanceof NotFoundException) {
+ throw error;
+ }
+
+ throw new InternalServerErrorException(
+ `Error fetching mentorship sessions by ID: ${error.message}`,
+ );
+ }
+ }
+
+ async createMentorshipSessionMenteeNote(
+ sessionId: number,
+ menteeId: number,
+ createSessionMenteeNoteDto: CreateSessionMenteeNoteDto,
+ ): Promise {
+ try {
+ // Check if the mentorship session exists and the user is the mentee
+ const existingSession = await this.prisma.mentorshipSessions.findFirst({
+ where: {
+ id: sessionId,
+ menteeId: menteeId,
+ },
+ });
+
+ if (!existingSession) {
+ throw new NotFoundException(
+ `There is no mentorship session with ID #${sessionId} for this mentee`,
+ );
+ }
+
+ // Check if notes already exist for this session
+ const existingNotes =
+ await this.prisma.mentorshipSessionsMenteeNotes.findFirst({
+ where: { sessionId: sessionId },
+ });
+
+ if (existingNotes) {
+ throw new NotFoundException(
+ `Notes already exist for session #${sessionId}. Use update instead.`,
+ );
+ }
+
+ // Create the mentee notes
+ const menteeNotes =
+ await this.prisma.mentorshipSessionsMenteeNotes.create({
+ data: {
+ sessionId: sessionId,
+ notes: createSessionMenteeNoteDto.note,
+ },
+ });
+
+ return {
+ id: menteeNotes.id,
+ sessionId: menteeNotes.sessionId,
+ notes: menteeNotes.notes,
+ };
+ } catch (error) {
+ if (error instanceof NotFoundException) {
+ throw error;
+ }
+
+ throw new InternalServerErrorException(
+ `Error creating mentorship session mentee notes: ${error.message}`,
+ );
+ }
+ }
+
+ async updateMentorshipSessionMenteeNote(
+ id: number,
+ menteeId: number,
+ updateSessionMenteeNoteDto: UpdateSessionMenteeNoteDto,
+ ): Promise {
+ try {
+ // Check if the mentee notes exist and belong to the mentee
+ const existingNotes =
+ await this.prisma.mentorshipSessionsMenteeNotes.findFirst({
+ where: {
+ id: id,
+ session: {
+ menteeId: menteeId,
+ },
+ },
+ include: {
+ session: true,
+ },
+ });
+
+ if (!existingNotes) {
+ throw new NotFoundException(
+ `There is no mentorship session mentee notes with ID #${id}`,
+ );
+ }
+
+ // Prepare the update data
+ const updateData: any = {};
+
+ if (updateSessionMenteeNoteDto.note !== undefined) {
+ updateData.notes = updateSessionMenteeNoteDto.note;
+ }
+
+ // Update the mentee notes
+ const updatedNotes =
+ await this.prisma.mentorshipSessionsMenteeNotes.update({
+ where: { id: id },
+ data: updateData,
+ });
+
+ return {
+ id: updatedNotes.id,
+ sessionId: updatedNotes.sessionId,
+ notes: updatedNotes.notes,
+ };
+ } catch (error) {
+ if (error instanceof NotFoundException) {
+ throw error;
+ }
+
+ throw new InternalServerErrorException(
+ `Error updating mentorship session mentee notes with ID #${id}: ${error.message}`,
+ );
+ }
+ }
+
+ async deleteMentorshipSessionMenteeNote(
+ id: number,
+ menteeId: number,
+ ): Promise {
+ try {
+ // Check if the mentee notes exist and belong to the mentee
+ const existingNotes =
+ await this.prisma.mentorshipSessionsMenteeNotes.findFirst({
+ where: {
+ id: id,
+ session: {
+ menteeId: menteeId,
+ },
+ },
+ include: {
+ session: true,
+ },
+ });
+
+ if (!existingNotes) {
+ throw new NotFoundException(
+ `There is no mentorship session mentee notes with ID #${id} to delete`,
+ );
+ }
+
+ // Delete the mentee notes
+ await this.prisma.mentorshipSessionsMenteeNotes.delete({
+ where: { id: id },
+ });
+
+ return id;
+ } catch (error) {
+ if (error instanceof NotFoundException) {
+ throw error;
+ }
+
+ throw new InternalServerErrorException(
+ `Error deleting mentorship session mentee notes with ID #${id}: ${error.message}`,
+ );
+ }
+ }
+
+ async findAllMentorshipSessionMenteeNotes(
+ mentorshipSessionId: number,
+ userId: number,
+ ): Promise {
+ try {
+ // Check if the mentorship session exists and user has access to it as a mentee
+ const session = await this.prisma.mentorshipSessions.findFirst({
+ where: {
+ id: mentorshipSessionId,
+ menteeId: userId,
+ },
+ });
+
+ if (!session) {
+ throw new NotFoundException(
+ `There is no mentorship session with ID #${mentorshipSessionId} for this user`,
+ );
+ }
+
+ // Fetch all mentee notes for the session
+ const menteeNotes =
+ await this.prisma.mentorshipSessionsMenteeNotes.findMany({
+ where: { sessionId: mentorshipSessionId },
+ orderBy: {
+ id: 'desc',
+ },
+ });
+
+ // Transform the response to match the entity structure
+ const response: MentorshipSessionMenteeNotesEntity[] = menteeNotes.map(
+ (note) => ({
+ id: note.id,
+ sessionId: note.sessionId,
+ notes: note.notes,
+ }),
+ );
+
+ return response;
+ } catch (error) {
+ if (error instanceof NotFoundException) {
+ throw error;
+ }
+
+ throw new InternalServerErrorException(
+ `Error fetching mentorship session mentee notes: ${error.message}`,
+ );
+ }
+ }
+
+ async createMentorshipSessionRating(
+ userId: number,
+ createSessionRatingDto: CreateSessionRatingDto,
+ ): Promise {
+ try {
+ // Check if the mentorship session exists and the user is involved
+ const session = await this.prisma.mentorshipSessions.findFirst({
+ where: {
+ id: createSessionRatingDto.sessionId,
+ OR: [{ mentorId: userId }, { menteeId: userId }],
+ },
+ });
+
+ if (!session) {
+ throw new NotFoundException(
+ `There is no mentorship session with ID #${createSessionRatingDto.sessionId} for this user`,
+ );
+ }
+
+ // Determine if user is mentor or mentee and create appropriate rating
+ if (session.mentorId === userId) {
+ // User is the mentor, so they're rating the mentee
+ // Check if mentor rating already exists
+ const existingMentorRating =
+ await this.prisma.mentorshipSessionsMentorRating.findFirst({
+ where: { sessionId: createSessionRatingDto.sessionId },
+ });
+
+ if (existingMentorRating) {
+ throw new NotFoundException(
+ `Mentor rating already exists for session #${createSessionRatingDto.sessionId}`,
+ );
+ }
+
+ // Create mentor rating (mentor rating the mentee)
+ const mentorRating =
+ await this.prisma.mentorshipSessionsMentorRating.create({
+ data: {
+ sessionId: createSessionRatingDto.sessionId,
+ rate: createSessionRatingDto.rate,
+ comment: createSessionRatingDto.note,
+ },
+ });
+
+ return {
+ id: mentorRating.id,
+ sessionId: mentorRating.sessionId,
+ rate: mentorRating.rate,
+ comment: mentorRating.comment,
+ createdAt: mentorRating.createdAt,
+ };
+ } else if (session.menteeId === userId) {
+ // User is the mentee, so they're rating the mentor
+ // Check if mentee rating already exists
+ const existingMenteeRating =
+ await this.prisma.mentorshipSessionsMenteeRating.findFirst({
+ where: { sessionId: createSessionRatingDto.sessionId },
+ });
+
+ if (existingMenteeRating) {
+ throw new NotFoundException(
+ `Mentee rating already exists for session #${createSessionRatingDto.sessionId}`,
+ );
+ }
+
+ // Create mentee rating (mentee rating the mentor)
+ const menteeRating =
+ await this.prisma.mentorshipSessionsMenteeRating.create({
+ data: {
+ sessionId: createSessionRatingDto.sessionId,
+ rate: createSessionRatingDto.rate,
+ comment: createSessionRatingDto.note,
+ },
+ });
+
+ return {
+ id: menteeRating.id,
+ sessionId: menteeRating.sessionId,
+ rate: menteeRating.rate,
+ comment: menteeRating.comment,
+ createdAt: menteeRating.createdAt,
+ };
+ } else {
+ throw new NotFoundException(
+ 'User is not involved in this mentorship session',
+ );
+ }
+ } catch (error) {
+ if (error instanceof NotFoundException) {
+ throw error;
+ }
+
+ throw new InternalServerErrorException(
+ `Error creating mentorship session rating: ${error.message}`,
+ );
+ }
+ }
+
+ async findAllMentorshipSessionRatings(
+ filterMentorshipSessionRatingsDto: FilterMentorshipSessionRatingsDto,
+ userId: number,
+ ): Promise {
+ try {
+ // Get user role to determine what ratings they can access
+ const user = await this.prisma.users.findFirst({
+ where: { id: userId },
+ select: { role: true },
+ });
+
+ if (!user) {
+ throw new NotFoundException('User not found');
+ }
+
+ let ratings: MentorshipSessionRatingEntity[] = [];
+
+ if (user.role === 'ADMIN') {
+ // ADMIN can fetch all ratings from both tables
+
+ // Build filter conditions for mentor ratings
+ const mentorRatingWhere: any = {};
+ if (filterMentorshipSessionRatingsDto.sessionId) {
+ mentorRatingWhere.sessionId =
+ filterMentorshipSessionRatingsDto.sessionId;
+ }
+ if (filterMentorshipSessionRatingsDto.dateStartBefore) {
+ mentorRatingWhere.createdAt = {
+ ...mentorRatingWhere.createdAt,
+ lte: filterMentorshipSessionRatingsDto.dateStartBefore,
+ };
+ }
+ if (filterMentorshipSessionRatingsDto.dateStartAfter) {
+ mentorRatingWhere.createdAt = {
+ ...mentorRatingWhere.createdAt,
+ gte: filterMentorshipSessionRatingsDto.dateStartAfter,
+ };
+ }
+
+ // Fetch mentor ratings
+ const mentorRatings =
+ await this.prisma.mentorshipSessionsMentorRating.findMany({
+ where: mentorRatingWhere,
+ include: {
+ session: {
+ include: {
+ mentor: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ },
+ },
+ mentee: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ },
+ },
+ },
+ },
+ },
+ orderBy: { createdAt: 'desc' },
+ });
+
+ // Build filter conditions for mentee ratings
+ const menteeRatingWhere: any = {};
+ if (filterMentorshipSessionRatingsDto.sessionId) {
+ menteeRatingWhere.sessionId =
+ filterMentorshipSessionRatingsDto.sessionId;
+ }
+ if (filterMentorshipSessionRatingsDto.dateStartBefore) {
+ menteeRatingWhere.createdAt = {
+ ...menteeRatingWhere.createdAt,
+ lte: filterMentorshipSessionRatingsDto.dateStartBefore,
+ };
+ }
+ if (filterMentorshipSessionRatingsDto.dateStartAfter) {
+ menteeRatingWhere.createdAt = {
+ ...menteeRatingWhere.createdAt,
+ gte: filterMentorshipSessionRatingsDto.dateStartAfter,
+ };
+ }
+
+ // Fetch mentee ratings
+ const menteeRatings =
+ await this.prisma.mentorshipSessionsMenteeRating.findMany({
+ where: menteeRatingWhere,
+ include: {
+ session: {
+ include: {
+ mentor: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ },
+ },
+ mentee: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ },
+ },
+ },
+ },
+ },
+ orderBy: { createdAt: 'desc' },
+ });
+
+ // Transform and combine ratings
+ ratings.push(
+ ...mentorRatings.map((rating) => ({
+ id: rating.id,
+ sessionId: rating.sessionId,
+ rate: rating.rate,
+ comment: rating.comment,
+ createdAt: rating.createdAt,
+ type: 'MENTOR_RATING' as const,
+ session: rating.session,
+ })),
+ ...menteeRatings.map((rating) => ({
+ id: rating.id,
+ sessionId: rating.sessionId,
+ rate: rating.rate,
+ comment: rating.comment,
+ createdAt: rating.createdAt,
+ type: 'MENTEE_RATING' as const,
+ session: rating.session,
+ })),
+ );
+ } else {
+ // Non-admin users can only fetch ratings they gave or received
+
+ // Get sessions where user is involved
+ const userSessions = await this.prisma.mentorshipSessions.findMany({
+ where: {
+ OR: [{ mentorId: userId }, { menteeId: userId }],
+ },
+ select: { id: true, mentorId: true, menteeId: true },
+ });
+
+ const sessionIds = userSessions.map((session) => session.id);
+
+ // Build filter conditions
+ const whereClause: any = {
+ sessionId: { in: sessionIds },
+ };
+
+ if (filterMentorshipSessionRatingsDto.sessionId) {
+ whereClause.sessionId = filterMentorshipSessionRatingsDto.sessionId;
+ }
+ if (filterMentorshipSessionRatingsDto.dateStartBefore) {
+ whereClause.createdAt = {
+ ...whereClause.createdAt,
+ lte: filterMentorshipSessionRatingsDto.dateStartBefore,
+ };
+ }
+ if (filterMentorshipSessionRatingsDto.dateStartAfter) {
+ whereClause.createdAt = {
+ ...whereClause.createdAt,
+ gte: filterMentorshipSessionRatingsDto.dateStartAfter,
+ };
+ }
+
+ // Fetch mentor ratings for user's sessions
+ const mentorRatings =
+ await this.prisma.mentorshipSessionsMentorRating.findMany({
+ where: whereClause,
+ include: {
+ session: {
+ include: {
+ mentor: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ },
+ },
+ mentee: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ },
+ },
+ },
+ },
+ },
+ orderBy: { createdAt: 'desc' },
+ });
+
+ // Fetch mentee ratings for user's sessions
+ const menteeRatings =
+ await this.prisma.mentorshipSessionsMenteeRating.findMany({
+ where: whereClause,
+ include: {
+ session: {
+ include: {
+ mentor: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ },
+ },
+ mentee: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ },
+ },
+ },
+ },
+ },
+ orderBy: { createdAt: 'desc' },
+ });
+
+ // Transform and combine ratings
+ ratings.push(
+ ...mentorRatings.map((rating) => ({
+ id: rating.id,
+ sessionId: rating.sessionId,
+ rate: rating.rate,
+ comment: rating.comment,
+ createdAt: rating.createdAt,
+ type: 'MENTOR_RATING' as const,
+ session: rating.session,
+ })),
+ ...menteeRatings.map((rating) => ({
+ id: rating.id,
+ sessionId: rating.sessionId,
+ rate: rating.rate,
+ comment: rating.comment,
+ createdAt: rating.createdAt,
+ type: 'MENTEE_RATING' as const,
+ session: rating.session,
+ })),
+ );
+ }
+
+ // Apply additional filters for mentor/mentee IDs if provided
+ if (
+ filterMentorshipSessionRatingsDto.mentorId ||
+ filterMentorshipSessionRatingsDto.menteeId
+ ) {
+ ratings = ratings.filter((rating) => {
+ if (
+ filterMentorshipSessionRatingsDto.mentorId &&
+ rating.session.mentor.id !==
+ filterMentorshipSessionRatingsDto.mentorId
+ ) {
+ return false;
+ }
+ if (
+ filterMentorshipSessionRatingsDto.menteeId &&
+ rating.session.mentee.id !==
+ filterMentorshipSessionRatingsDto.menteeId
+ ) {
+ return false;
+ }
+ return true;
+ });
+ }
+
+ return ratings;
+ } catch (error) {
+ if (error instanceof NotFoundException) {
+ throw error;
+ }
+
+ throw new InternalServerErrorException(
+ `Error fetching mentorship session rating: ${error.message}`,
+ );
+ }
+ }
+
+ async createMatching(
+ createMatchingDto: CreateMatchingDto,
+ ): Promise {
+ try {
+ // Validate that the mentor exists and has MENTOR role
+ const mentor = await this.prisma.users.findFirst({
+ where: {
+ id: createMatchingDto.mentorId,
+ deleted_at: false,
+ role: 'MENTOR',
+ },
+ });
+
+ if (!mentor) {
+ throw new NotFoundException(
+ `Mentor with ID ${createMatchingDto.mentorId} not found or not authorized`,
+ );
+ }
+
+ // Validate that the mentee exists and has MENTEE role
+ const mentee = await this.prisma.users.findFirst({
+ where: {
+ id: createMatchingDto.menteeId,
+ deleted_at: false,
+ role: 'MENTEE',
+ },
+ });
+
+ if (!mentee) {
+ throw new NotFoundException(
+ `Mentee with ID ${createMatchingDto.menteeId} not found or not authorized`,
+ );
+ }
+
+ // Check if a matching already exists between this mentor and mentee
+ const existingMatching = await this.prisma.matchedMentorMentee.findFirst({
+ where: {
+ mentorId: createMatchingDto.mentorId,
+ menteeId: createMatchingDto.menteeId,
+ },
+ });
+
+ if (existingMatching) {
+ throw new NotFoundException(
+ 'A matching already exists between this mentor and mentee',
+ );
+ }
+
+ // Create the matching
+ const matching = await this.prisma.matchedMentorMentee.create({
+ data: {
+ mentorId: createMatchingDto.mentorId,
+ menteeId: createMatchingDto.menteeId,
+ status: 'PENDING',
+ },
+ include: {
+ mentor: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ },
+ },
+ mentee: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ },
+ },
+ },
+ });
+
+ // Transform the response to match the entity structure
+ const response: MatchedMentorMenteeResponseEntity = {
+ id: matching.id,
+ mentorId: matching.mentorId,
+ menteeId: matching.menteeId,
+ status: matching.status,
+ createdAt: matching.createdAt,
+ mentor: {
+ id: matching.mentor.id,
+ firstName: matching.mentor.first_name,
+ lastName: matching.mentor.last_name,
+ email: matching.mentor.email,
+ profession: matching.mentor.profession,
+ avatar: matching.mentor.picture_upload_link,
+ },
+ mentee: {
+ id: matching.mentee.id,
+ firstName: matching.mentee.first_name,
+ lastName: matching.mentee.last_name,
+ email: matching.mentee.email,
+ profession: matching.mentee.profession,
+ avatar: matching.mentee.picture_upload_link,
+ },
+ };
+
+ return response;
+ } catch (error) {
+ if (error instanceof NotFoundException) {
+ throw error;
+ }
+
+ throw new InternalServerErrorException(
+ `Error matching MENTOR with MENTEE: ${error.message}`,
+ );
+ }
+ }
+
+ async updateMatchingStatus(
+ id: number,
+ updateMatchingDto: UpdateMatchingDto,
+ mentorId: number,
+ ): Promise {
+ try {
+ // Check if the matching exists and belongs to the mentor
+ const existingMatching = await this.prisma.matchedMentorMentee.findFirst({
+ where: {
+ id: id,
+ mentorId: mentorId,
+ },
+ });
+
+ if (!existingMatching) {
+ throw new NotFoundException(
+ `There is no matching with ID #${id} to update`,
+ );
+ }
+
+ // Update the matching status
+ const updatedMatching = await this.prisma.matchedMentorMentee.update({
+ where: { id: id },
+ data: {
+ status: updateMatchingDto.status,
+ },
+ include: {
+ mentor: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ },
+ },
+ mentee: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ },
+ },
+ },
+ });
+
+ // Transform the response to match the entity structure
+ const response: MatchedMentorMenteeResponseEntity = {
+ id: updatedMatching.id,
+ mentorId: updatedMatching.mentorId,
+ menteeId: updatedMatching.menteeId,
+ status: updatedMatching.status,
+ createdAt: updatedMatching.createdAt,
+ mentor: {
+ id: updatedMatching.mentor.id,
+ firstName: updatedMatching.mentor.first_name,
+ lastName: updatedMatching.mentor.last_name,
+ email: updatedMatching.mentor.email,
+ profession: updatedMatching.mentor.profession,
+ avatar: updatedMatching.mentor.picture_upload_link,
+ },
+ mentee: {
+ id: updatedMatching.mentee.id,
+ firstName: updatedMatching.mentee.first_name,
+ lastName: updatedMatching.mentee.last_name,
+ email: updatedMatching.mentee.email,
+ profession: updatedMatching.mentee.profession,
+ avatar: updatedMatching.mentee.picture_upload_link,
+ },
+ };
+
+ return response;
+ } catch (error) {
+ if (error instanceof NotFoundException) {
+ throw error;
+ }
+
+ throw new InternalServerErrorException(
+ `Error updating matching with ID #${id}: ${error.message}`,
+ );
+ }
+ }
+
+ async findAllMatchingForMentor(mentorId: number): Promise {
+ try {
+ // Validate that the mentor exists
+ const mentor = await this.prisma.users.findFirst({
+ where: {
+ id: mentorId,
+ deleted_at: false,
+ role: 'MENTOR',
+ },
+ });
+
+ if (!mentor) {
+ throw new NotFoundException(`Mentor with ID ${mentorId} not found`);
+ }
+
+ // Get all mentees that are matched with this mentor
+ const matchings = await this.prisma.matchedMentorMentee.findMany({
+ where: {
+ mentorId: mentorId,
+ },
+ include: {
+ mentee: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ bio: true,
+ experience: true,
+ company_name: true,
+ linkedin_link: true,
+ github_link: true,
+ portfolio_link: true,
+ },
+ },
+ },
+ orderBy: {
+ createdAt: 'desc',
+ },
+ });
+
+ // Transform the response to match the User entity structure
+ const mentees = matchings.map((matching) => ({
+ id: matching.mentee.id,
+ first_name: matching.mentee.first_name,
+ last_name: matching.mentee.last_name,
+ email: matching.mentee.email,
+ profession: matching.mentee.profession,
+ picture_upload_link: matching.mentee.picture_upload_link,
+ bio: matching.mentee.bio,
+ experience: matching.mentee.experience,
+ company_name: matching.mentee.company_name,
+ linkedin_link: matching.mentee.linkedin_link,
+ github_link: matching.mentee.github_link,
+ portfolio_link: matching.mentee.portfolio_link,
+ }));
+
+ return mentees;
+ } catch (error) {
+ if (error instanceof NotFoundException) {
+ throw error;
+ }
+
+ throw new InternalServerErrorException(
+ `Error fetching matching users: ${error.message}`,
+ );
+ }
+ }
+
+ async findAllMatchingForMentee(menteeId: number): Promise {
+ try {
+ // Validate that the mentee exists
+ const mentee = await this.prisma.users.findFirst({
+ where: {
+ id: menteeId,
+ deleted_at: false,
+ role: 'MENTEE',
+ },
+ });
+
+ if (!mentee) {
+ throw new NotFoundException(`Mentee with ID ${menteeId} not found`);
+ }
+
+ // Get all mentors that are matched with this mentee
+ const matchings = await this.prisma.matchedMentorMentee.findMany({
+ where: {
+ menteeId: menteeId,
+ },
+ include: {
+ mentor: {
+ select: {
+ id: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ profession: true,
+ picture_upload_link: true,
+ bio: true,
+ experience: true,
+ company_name: true,
+ linkedin_link: true,
+ github_link: true,
+ portfolio_link: true,
+ },
+ },
+ },
+ orderBy: {
+ createdAt: 'desc',
+ },
+ });
+
+ // Transform the response to match the User entity structure
+ const mentors = matchings.map((matching) => ({
+ id: matching.mentor.id,
+ first_name: matching.mentor.first_name,
+ last_name: matching.mentor.last_name,
+ email: matching.mentor.email,
+ profession: matching.mentor.profession,
+ picture_upload_link: matching.mentor.picture_upload_link,
+ bio: matching.mentor.bio,
+ experience: matching.mentor.experience,
+ company_name: matching.mentor.company_name,
+ linkedin_link: matching.mentor.linkedin_link,
+ github_link: matching.mentor.github_link,
+ portfolio_link: matching.mentor.portfolio_link,
+ }));
+
+ return mentors;
+ } catch (error) {
+ if (error instanceof NotFoundException) {
+ throw error;
+ }
+
+ throw new InternalServerErrorException(
+ `Error fetching matching users: ${error.message}`,
+ );
+ }
+ }
+
+ // ... other methods will be implemented here
+}
diff --git a/packages/frontend/src/app/auth/forgot-password/page.tsx b/packages/frontend/src/app/auth/forgot-password/page.tsx
index 1026ecb..394f1d2 100644
--- a/packages/frontend/src/app/auth/forgot-password/page.tsx
+++ b/packages/frontend/src/app/auth/forgot-password/page.tsx
@@ -1,4 +1,5 @@
import { Metadata } from 'next';
+import { Suspense } from 'react';
import { AuthCarousel } from '@/features/auth/components';
import { Separator } from '@/shared/components/ui/separator';
import { ForgotPasswordForm } from '@/features/auth/components';
@@ -12,7 +13,9 @@ const ForgotPasswordPage = () => {
return (
diff --git a/packages/frontend/src/app/auth/reset-password/page.tsx b/packages/frontend/src/app/auth/reset-password/page.tsx
index 3f9d2cc..636d988 100644
--- a/packages/frontend/src/app/auth/reset-password/page.tsx
+++ b/packages/frontend/src/app/auth/reset-password/page.tsx
@@ -28,7 +28,9 @@ const ResetPasswordContent = () => {
return (
diff --git a/packages/frontend/src/features/auth/api/auth-api.ts b/packages/frontend/src/features/auth/api/auth-api.ts
index 2d9b1d3..23e51ad 100644
--- a/packages/frontend/src/features/auth/api/auth-api.ts
+++ b/packages/frontend/src/features/auth/api/auth-api.ts
@@ -3,13 +3,14 @@ import {
LoginCredentials,
AuthResponse,
RegisterCredentials,
- ResetPasswordCredentials,
+ // ResetPasswordCredentials,
ForgotPasswordCredentials,
UpdatePasswordCredentials,
AccessToken,
RefreshToken,
ChangePasswordCredentials
} from '@/features/auth/types';
+import { ResetPasswordWithTokenFormValues } from '@/features/auth/validations/auth.schema';
export const authApi = {
login: (credentials: LoginCredentials) =>
@@ -27,8 +28,13 @@ export const authApi = {
forgotPassword: (credentials: ForgotPasswordCredentials) =>
apiMethods.post
('/auth/forgot-password', credentials),
- resetPassword: (credentials: ResetPasswordCredentials) =>
- apiMethods.post('/auth/reset-password', credentials),
+ resetPassword: (
+ credentials: ResetPasswordWithTokenFormValues & { token?: string }
+ ) =>
+ apiMethods.post(
+ '/auth/reset-password-with-token',
+ credentials
+ ),
changePassword: (credentials: ChangePasswordCredentials) =>
apiMethods.post('/auth/change-password', credentials),
diff --git a/packages/frontend/src/features/auth/components/forgot-password-form.tsx b/packages/frontend/src/features/auth/components/forgot-password-form.tsx
index 94069d5..9e2dd25 100644
--- a/packages/frontend/src/features/auth/components/forgot-password-form.tsx
+++ b/packages/frontend/src/features/auth/components/forgot-password-form.tsx
@@ -16,17 +16,22 @@ import { Icons } from '@/features/auth/components/icons';
import { useAuth } from '@/features/auth/hooks/use-auth';
import {
forgotPasswordSchema,
- resetPasswordSchema,
+ resetPasswordWithTokenSchema,
ForgotPasswordFormValues,
- ResetPasswordFormValues
+ ResetPasswordWithTokenFormValues
} from '@/features/auth/validations/auth.schema';
import Link from 'next/link';
import { IconInput } from '@/shared/components/ui/icon-input';
import { cn } from '@/shared/lib/utils';
import { AlertDialogUI } from '@/shared/components/notification/alert-dialog';
-type RetrievePasswordFormValues = ForgotPasswordFormValues &
- ResetPasswordFormValues;
+// Create a union type that includes all possible fields
+type FormValues = {
+ email?: string;
+ token?: string;
+ newPassword?: string;
+ confirmPassword?: string;
+};
const ForgotPasswordFormContent = () => {
const pathname = usePathname();
@@ -37,32 +42,51 @@ const ForgotPasswordFormContent = () => {
const isForgotPasswordPage = pathname === '/auth/forgot-password';
const token = searchParams.get('token') || '';
- const form = useForm({
- resolver: zodResolver(
- isForgotPasswordPage ? forgotPasswordSchema : resetPasswordSchema
- ),
+ // Use the appropriate schema based on the page
+ const schema = isForgotPasswordPage
+ ? forgotPasswordSchema
+ : resetPasswordWithTokenSchema;
+
+ const form = useForm({
+ resolver: zodResolver(schema),
defaultValues: {
email: '',
- token: token,
newPassword: '',
confirmPassword: ''
}
});
- const onSubmit = async (data: RetrievePasswordFormValues) => {
- console.log('rest password button clicked');
+ // Add token validation AFTER the hook
+ if (!isForgotPasswordPage && !token) {
+ return (
+
+
+
Invalid Reset Link
+
+ This password reset link is invalid or has expired.
+
+
+ Request a new reset link
+
+
+
+ );
+ }
+
+ const onSubmit = async (data: FormValues) => {
+ console.log('Form submitted with data:', data);
+ console.log('isForgotPasswordPage:', isForgotPasswordPage);
+ console.log('token:', token);
if (isForgotPasswordPage) {
- await forgotPassword(data as ForgotPasswordFormValues);
+ await forgotPassword({ email: data.email! });
} else {
- const resetPasswordData = {
- ...data,
+ // Include token in reset password call
+ await resetPassword({
+ newPassword: data.newPassword!,
+ confirmPassword: data.confirmPassword!,
token: token
- };
-
- console.log('reset password data', resetPasswordData);
-
- await resetPassword(resetPasswordData as ResetPasswordFormValues);
+ });
}
};
@@ -85,13 +109,6 @@ const ForgotPasswordFormContent = () => {