From 6e5691218331c39abebfef6c242281ec2b9967a8 Mon Sep 17 00:00:00 2001 From: Bijin Krishn Date: Wed, 29 Apr 2026 12:39:04 +0530 Subject: [PATCH] refactor: modify prisma schema file --- .../migration.sql | 30 +++++++++++++++++ prisma/schema.prisma | 33 ++++++++++--------- src/auth/auth.service.spec.ts | 8 ++--- src/auth/auth.service.ts | 4 +-- src/auth/guard/auth.guard.spec.ts | 8 ++--- src/auth/guard/auth.guard.ts | 12 +++++-- src/token/token.service.spec.ts | 8 ++--- src/token/token.service.ts | 5 +-- src/user/user-cleanup.service.ts | 1 - src/user/user.service.spec.ts | 18 +++++----- src/user/user.service.ts | 7 ++-- test/user.e2e-spec.ts | 5 ++- 12 files changed, 88 insertions(+), 51 deletions(-) create mode 100644 prisma/migrations/20260429062607_modify_schemas/migration.sql diff --git a/prisma/migrations/20260429062607_modify_schemas/migration.sql b/prisma/migrations/20260429062607_modify_schemas/migration.sql new file mode 100644 index 0000000..ed702a6 --- /dev/null +++ b/prisma/migrations/20260429062607_modify_schemas/migration.sql @@ -0,0 +1,30 @@ +/* + Warnings: + + - You are about to drop the column `createdAt` on the `users` table. All the data in the column will be lost. + - You are about to drop the column `deleted` on the `users` table. All the data in the column will be lost. + - You are about to drop the column `deletedAt` on the `users` table. All the data in the column will be lost. + - You are about to drop the column `firstName` on the `users` table. All the data in the column will be lost. + - You are about to drop the column `lastName` on the `users` table. All the data in the column will be lost. + - You are about to drop the column `tokenVersion` on the `users` table. All the data in the column will be lost. + - You are about to drop the column `updatedAt` on the `users` table. All the data in the column will be lost. + - Added the required column `updated_at` to the `users` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "refresh_tokens" ALTER COLUMN "expires_at" DROP DEFAULT; + +-- AlterTable +ALTER TABLE "users" DROP COLUMN "createdAt", +DROP COLUMN "deleted", +DROP COLUMN "deletedAt", +DROP COLUMN "firstName", +DROP COLUMN "lastName", +DROP COLUMN "tokenVersion", +DROP COLUMN "updatedAt", +ADD COLUMN "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "deleted_at" TIMESTAMP(3), +ADD COLUMN "first_name" TEXT, +ADD COLUMN "last_name" TEXT, +ADD COLUMN "token_version" INTEGER NOT NULL DEFAULT 1, +ADD COLUMN "updated_at" TIMESTAMP NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5876e0a..9a3769f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -15,19 +15,18 @@ datasource db { } model User { - id Int @id @default(autoincrement()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deleted Boolean @default(false) - deletedAt DateTime? - role Role @default(USER) - tokenVersion Int @default(1) - - email String @unique - password String - - firstName String? - lastName String? + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) @map("created_at") @db.Timestamp() + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamp() + deletedAt DateTime? @map("deleted_at") + + email String @unique + password String + firstName String? @map("first_name") + lastName String? @map("last_name") + role Role @default(USER) + tokenVersion Int @default(1) @map("token_version") + refreshTokens RefreshToken[] @@map("users") @@ -43,10 +42,12 @@ enum Role { model RefreshToken { id String @id @db.Uuid userId Int @map("user_id") - token String @map("token") createdAt DateTime @default(now()) @map("created_at") @db.Timestamp() - expiresAt DateTime @default(now()) @map("expires_at") @db.Timestamp() - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + token String + expiresAt DateTime @map("expires_at") @db.Timestamp() + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("refresh_tokens") } diff --git a/src/auth/auth.service.spec.ts b/src/auth/auth.service.spec.ts index 3ac204a..b0f78e9 100644 --- a/src/auth/auth.service.spec.ts +++ b/src/auth/auth.service.spec.ts @@ -96,8 +96,8 @@ describe('AuthService', () => { ).rejects.toThrow(NotFoundException) }) - it('should throw ForbiddenException if user is deleted', async () => { - mockPrisma.user.findUnique.mockResolvedValue({ deleted: true }) + it('should throw ForbiddenException if user is soft-deleted', async () => { + mockPrisma.user.findUnique.mockResolvedValue({ deletedAt: new Date() }) await expect( service.login({ email: 't@t.com', password: 'p' }), ).rejects.toThrow(ForbiddenException) @@ -105,7 +105,7 @@ describe('AuthService', () => { it('should throw ForbiddenException if password mismsatch', async () => { mockPrisma.user.findUnique.mockResolvedValue({ - deleted: false, + deletedAt: null, password: 'hashed', }) ;(argon.verify as jest.Mock).mockResolvedValue(false) @@ -115,7 +115,7 @@ describe('AuthService', () => { }) it('should generate token if successful', async () => { - const mockUser = { id: 1, deleted: false, password: 'hashed' } + const mockUser = { id: 1, deletedAt: null, password: 'hashed' } mockPrisma.user.findUnique.mockResolvedValue(mockUser) ;(argon.verify as jest.Mock).mockResolvedValue(true) mockTokenService.generateToken.mockResolvedValue({ accessToken: 'a' }) diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index e4d8a26..7843292 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -51,8 +51,8 @@ export class AuthService { }) // if user does not exist, throw exception if (!user) throw new NotFoundException('User not found') - // check user is not active (deleted) - if (user.deleted) throw new ForbiddenException('User account inactive') + // check user is not active (soft-deleted) + if (user.deletedAt) throw new ForbiddenException('User account inactive') // compare password const pwMatches = await argon.verify(user.password, loginDto.password) // if the password incorrect, throw exception diff --git a/src/auth/guard/auth.guard.spec.ts b/src/auth/guard/auth.guard.spec.ts index f51b914..44163ca 100644 --- a/src/auth/guard/auth.guard.spec.ts +++ b/src/auth/guard/auth.guard.spec.ts @@ -108,11 +108,11 @@ describe('AuthGuard', () => { ) }) - it('should throw ForbiddenException if user is inactive (deleted)', async () => { + it('should throw ForbiddenException if user is inactive (soft-deleted)', async () => { mockReflector.getAllAndOverride.mockReturnValue(false) const context = mockContext({ authorization: 'Bearer valid_token' }) mockJwtService.verifyAsync.mockResolvedValue({ sub: 1, version: 1 }) - mockPrisma.user.findUnique.mockResolvedValue({ deleted: true }) + mockPrisma.user.findUnique.mockResolvedValue({ deletedAt: new Date() }) await expect(guard.canActivate(context)).rejects.toThrow( new ForbiddenException('Your account is inactive'), @@ -124,7 +124,7 @@ describe('AuthGuard', () => { const context = mockContext({ authorization: 'Bearer valid_token' }) mockJwtService.verifyAsync.mockResolvedValue({ sub: 1, version: 1 }) // payload version 1 mockPrisma.user.findUnique.mockResolvedValue({ - deleted: false, + deletedAt: null, tokenVersion: 2, }) // user version 2 @@ -149,7 +149,7 @@ describe('AuthGuard', () => { email: 'test@test.com', role: 'USER', tokenVersion: 1, - deleted: false, + deletedAt: null, }) const result = await guard.canActivate(context) diff --git a/src/auth/guard/auth.guard.ts b/src/auth/guard/auth.guard.ts index 1166102..352d214 100644 --- a/src/auth/guard/auth.guard.ts +++ b/src/auth/guard/auth.guard.ts @@ -49,11 +49,17 @@ export class AuthGuard implements CanActivate { const user = await this.prisma.user.findUnique({ where: { id: payload.sub }, - select: { email: true, role: true, tokenVersion: true, deleted: true }, + select: { + email: true, + role: true, + tokenVersion: true, + deletedAt: true, + }, }) if (!user) throw new NotFoundException('Your account not exists') - // check user is not active (deleted) - if (user.deleted) throw new ForbiddenException('Your account is inactive') + // check user is not active (soft-deleted) + if (user.deletedAt) + throw new ForbiddenException('Your account is inactive') if (user.tokenVersion !== payload.version) { throw new UnauthorizedException('Access token revoked') diff --git a/src/token/token.service.spec.ts b/src/token/token.service.spec.ts index 9161139..6f76e54 100644 --- a/src/token/token.service.spec.ts +++ b/src/token/token.service.spec.ts @@ -130,10 +130,10 @@ describe('TokenService', () => { ) }) - it('should throw ForbiddenException if user is deleted', async () => { + it('should throw ForbiddenException if user is soft-deleted', async () => { mockPrismaService.user.findUnique.mockResolvedValue({ id: 1, - deleted: true, + deletedAt: new Date(), }) await expect(service.refreshToken(1, 'mockTokenId')).rejects.toThrow( @@ -144,7 +144,7 @@ describe('TokenService', () => { it('should throw UnauthorizedException on JsonWebTokenError', async () => { mockPrismaService.user.findUnique.mockResolvedValue({ id: 1, - deleted: false, + deletedAt: null, }) mockPrismaService.refreshToken.delete.mockRejectedValue( new JsonWebTokenError('Invalid token'), @@ -160,7 +160,7 @@ describe('TokenService', () => { id: 1, email: 'test@test.com', tokenVersion: 1, - deleted: false, + deletedAt: null, } as User mockPrismaService.user.findUnique.mockResolvedValue(mockUser) mockPrismaService.refreshToken.delete.mockResolvedValue({}) diff --git a/src/token/token.service.ts b/src/token/token.service.ts index 0d25508..e505aec 100644 --- a/src/token/token.service.ts +++ b/src/token/token.service.ts @@ -82,8 +82,9 @@ export class TokenService { where: { id: userId }, }) if (!user) throw new NotFoundException('User not exists') - // check user is not active (deleted) - if (user.deleted) throw new ForbiddenException('User account is inactive') + // check user is not active (soft-deleted) + if (user.deletedAt) + throw new ForbiddenException('User account is inactive') // delete used refresh token from db await this.prisma.refreshToken.delete({ where: { id: tokenId } }) // generate access token and refresh token diff --git a/src/user/user-cleanup.service.ts b/src/user/user-cleanup.service.ts index 46c5d22..4e46875 100644 --- a/src/user/user-cleanup.service.ts +++ b/src/user/user-cleanup.service.ts @@ -15,7 +15,6 @@ export class UserCleanupService { const usersToDelete = await this.prisma.user.findMany({ where: { - deleted: true, deletedAt: { lte: threshold, }, diff --git a/src/user/user.service.spec.ts b/src/user/user.service.spec.ts index 31c6c09..03353ac 100644 --- a/src/user/user.service.spec.ts +++ b/src/user/user.service.spec.ts @@ -144,7 +144,10 @@ describe('UserService', () => { describe('softDeleteUser', () => { it('should soft delete user and revoke token', async () => { mockOtpService.verifyCode.mockResolvedValue(true) - mockPrisma.user.update.mockResolvedValue({ id: 1, deleted: true }) + mockPrisma.user.update.mockResolvedValue({ + id: 1, + deletedAt: new Date(), + }) mockTokenService.revokeAllToken.mockResolvedValue({}) const result = await service.softDeleteUser('t@t.com', { @@ -153,7 +156,6 @@ describe('UserService', () => { expect(result).toEqual({ id: 1, status: true, - deleted: true, message: 'Soft deleted user', }) expect(mockTokenService.revokeAllToken).toHaveBeenCalledWith(1) @@ -190,17 +192,17 @@ describe('UserService', () => { }) describe('reactivateUser', () => { - it('should reactivate deleted user and return tokens', async () => { + it('should reactivate soft-deleted user and return tokens', async () => { mockPrisma.user.findUnique.mockResolvedValue({ id: 1, email: 'deleted@t.com', - deleted: true, + deletedAt: new Date(), }) mockOtpService.verifyCode.mockResolvedValue(true) mockPrisma.user.update.mockResolvedValue({ id: 1, email: 'deleted@t.com', - deleted: false, + deletedAt: null, }) mockTokenService.generateToken.mockResolvedValue({ accessToken: 'access-token', @@ -219,13 +221,13 @@ describe('UserService', () => { ) expect(mockPrisma.user.update).toHaveBeenCalledWith({ where: { email: 'deleted@t.com' }, - data: { deleted: false, deletedAt: null }, + data: { deletedAt: null }, omit: { password: true }, }) expect(mockTokenService.generateToken).toHaveBeenCalledWith({ id: 1, email: 'deleted@t.com', - deleted: false, + deletedAt: null, }) expect(result).toEqual({ id: 1, @@ -251,7 +253,7 @@ describe('UserService', () => { mockPrisma.user.findUnique.mockResolvedValue({ id: 1, email: 'active@t.com', - deleted: false, + deletedAt: null, }) await expect( diff --git a/src/user/user.service.ts b/src/user/user.service.ts index b5359d2..8b0ab60 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -96,14 +96,13 @@ export class UserService { await this.otpService.verifyCode(email, dto.emailVerifiedCode, true) const deletedUser = await this.prisma.user.update({ where: { email }, - data: { deleted: true, deletedAt: new Date() }, + data: { deletedAt: new Date() }, omit: { password: true }, }) await this.tokenService.revokeAllToken(deletedUser.id) return { id: deletedUser.id, status: true, - deleted: deletedUser.deleted, message: 'Soft deleted user', } } catch (error) { @@ -122,11 +121,11 @@ export class UserService { omit: { password: true }, }) if (!foundUser) throw new NotFoundException('User not found') - if (!foundUser.deleted) throw new ConflictException('User already active') + if (!foundUser.deletedAt) throw new ConflictException('User already active') await this.otpService.verifyCode(dto.email, dto.emailVerifiedCode) const reactivatedUser = await this.prisma.user.update({ where: { email: dto.email }, - data: { deleted: false, deletedAt: null }, + data: { deletedAt: null }, omit: { password: true }, }) const tokens = await this.tokenService.generateToken(reactivatedUser) diff --git a/test/user.e2e-spec.ts b/test/user.e2e-spec.ts index 9290b46..1434a09 100644 --- a/test/user.e2e-spec.ts +++ b/test/user.e2e-spec.ts @@ -94,7 +94,7 @@ describe('UserController (e2e)', () => { // Soft delete the user await prisma.user.update({ where: { email }, - data: { deleted: true }, + data: { deletedAt: new Date() }, }) await request(app.getHttpServer()) @@ -179,7 +179,7 @@ describe('UserController (e2e)', () => { .expect(200) const user = await prisma.user.findUnique({ where: { email } }) - expect(user?.deleted).toBe(true) + expect(user?.deletedAt).not.toBeNull() }) }) @@ -262,7 +262,6 @@ describe('UserController (e2e)', () => { expect(res.body.refreshToken).toBeDefined() const user = await prisma.user.findUnique({ where: { email } }) - expect(user?.deleted).toBe(false) expect(user?.deletedAt).toBeNull() })