From a171fe454668e0456ef2d91aa2762e048a8d6ca5 Mon Sep 17 00:00:00 2001 From: akm99 Date: Sun, 14 Apr 2024 23:41:48 -0400 Subject: [PATCH 1/7] Soft Delete + Blocking refactor pt 1 --- src/api/controllers/UserController.ts | 7 +++++- src/migrations/1713139721037-softdelete.ts | 18 +++++++++++++++ src/models/UserModel.ts | 4 ++++ src/repositories/UserRepository.ts | 22 +++++++++++++++--- src/services/AuthService.ts | 6 +++-- src/services/UserService.ts | 27 ++++++++++++++++------ src/tests/UserSessionTest.test.ts | 1 + src/tests/UserTest.test.ts | 22 +++++++++++++----- src/tests/data/UserFactory.ts | 3 +++ src/types/ApiResponses.ts | 1 + 10 files changed, 92 insertions(+), 19 deletions(-) create mode 100644 src/migrations/1713139721037-softdelete.ts diff --git a/src/api/controllers/UserController.ts b/src/api/controllers/UserController.ts index 23dbdc7..bc17d7f 100644 --- a/src/api/controllers/UserController.ts +++ b/src/api/controllers/UserController.ts @@ -59,7 +59,7 @@ export class UserController { return { user: await this.userService.unblockUser(user, unblockUserRequest) } } - @Post('id/:id/blocked/') + @Get('id/:id/blocked/') async getBlockedUsersById(@Params() params: UuidParam): Promise { return { users: await this.userService.getBlockedUsersById(params) }; } @@ -68,4 +68,9 @@ export class UserController { async deleteUser(@Params() params: UuidParam, @CurrentUser() user: UserModel): Promise { return { user: await this.userService.deleteUser(user, params) }; } + + @Post('softDelete/id/:id/') + async softDeleteUser(@Params() params: UuidParam): Promise { + return { user: await this.userService.softDeleteUser(params) }; + } } \ No newline at end of file diff --git a/src/migrations/1713139721037-softdelete.ts b/src/migrations/1713139721037-softdelete.ts new file mode 100644 index 0000000..aaed929 --- /dev/null +++ b/src/migrations/1713139721037-softdelete.ts @@ -0,0 +1,18 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class softdelete1713139721037 implements MigrationInterface { + name = 'softdelete1713139721037' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "User" ADD "is_active" boolean NOT NULL DEFAULT true`); + await queryRunner.query(`ALTER TABLE "Post" ALTER COLUMN "original_price" TYPE numeric`); + await queryRunner.query(`ALTER TABLE "Post" ALTER COLUMN "altered_price" TYPE numeric`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "Post" ALTER COLUMN "altered_price" TYPE numeric`); + await queryRunner.query(`ALTER TABLE "Post" ALTER COLUMN "original_price" TYPE numeric`); + await queryRunner.query(`ALTER TABLE "User" DROP COLUMN "is_active"`); + } + +} diff --git a/src/models/UserModel.ts b/src/models/UserModel.ts index b794578..b26b1c0 100644 --- a/src/models/UserModel.ts +++ b/src/models/UserModel.ts @@ -28,6 +28,9 @@ export class UserModel { @Column() admin: boolean; + @Column({ default: true }) + is_active: boolean; + @Column({ type: "numeric", default: 0 }) stars: number; @@ -102,6 +105,7 @@ export class UserModel { email: this.email, googleId: this.googleId, bio: this.bio, + is_active: this.is_active, blocking: this.blocking, blockers: this.blockers, posts: this.posts, diff --git a/src/repositories/UserRepository.ts b/src/repositories/UserRepository.ts index 261bc9c..7436b93 100644 --- a/src/repositories/UserRepository.ts +++ b/src/repositories/UserRepository.ts @@ -19,10 +19,11 @@ export class UserRepository extends AbstractRepository { .getOne(); } - public async getBlockedUsersById(id: Uuid): Promise { + public async getUserWithBlockedInfo(id: Uuid): Promise { return await this.repository .createQueryBuilder("user") .leftJoinAndSelect("user.blocking", "user_blocking_users.blocking") + .leftJoinAndSelect("user.blockers", "user_blocking_users.blockers") .where("user.id = :id", { id }) .getOne(); } @@ -80,6 +81,16 @@ export class UserRepository extends AbstractRepository { googleId: string, ): Promise { let existingUser = this.repository + .createQueryBuilder("user") + .where("user.username = :username", { username }) + .getOne(); + if (await existingUser) throw new ConflictError('UserModel with same username already exists!'); + existingUser = this.repository + .createQueryBuilder("user") + .where("user.netid = :netid", { netid }) + .getOne(); + if (await existingUser) throw new ConflictError('UserModel with same netid already exists!'); + existingUser = this.repository .createQueryBuilder("user") .where("user.email = :email", { email }) .getOne(); @@ -159,9 +170,14 @@ export class UserRepository extends AbstractRepository { if (!blocker.blocking.find((user) => user.id === blocked.id)) { throw new NotFoundError("User has not been blocked!") } - blocker.blocking.splice(blocker.blocking.indexOf(blocked), 1); - if (blocker.blocking.length === 0) { blocker.blocking = undefined; } + // remove blocked user from blocking list + blocker.blocking = blocker.blocking.filter((user) => user.id !== blocked.id); } return this.repository.save(blocker); } + + public async softDeleteUser(user: UserModel): Promise { + user.is_active = false; + return this.repository.save(user); + } } \ No newline at end of file diff --git a/src/services/AuthService.ts b/src/services/AuthService.ts index e269b1f..a571fc3 100644 --- a/src/services/AuthService.ts +++ b/src/services/AuthService.ts @@ -39,13 +39,11 @@ export class AuthService { if (emailIndex === -1 && !adminEmails?.includes(authRequest.user.email)) { throw new UnauthorizedError('Non-Cornell email used!'); } - if (process.env.OAUTH_ANDROID_CLIENT && process.env.OAUTH_IOS_ID) { // verifies info using id token const ticket = await client.verifyIdToken({ idToken: authRequest.idToken, }); - const payload = ticket.getPayload(); if (payload) { @@ -68,6 +66,10 @@ export class AuthService { user = await userRepository.createUser(netid, netid, newUser.givenName, newUser.familyName, newUser.photoUrl, newUser.email, userId); } + //check if the user is inactive/soft deleted + if (!user.is_active) { + throw new ForbiddenError("User is soft deleted"); + } //add device token const session = await sessionsRepository.createSession(user); sessionsRepository.updateSessionDeviceToken(session, authRequest.deviceToken) diff --git a/src/services/UserService.ts b/src/services/UserService.ts index e84fa3c..ab00019 100644 --- a/src/services/UserService.ts +++ b/src/services/UserService.ts @@ -93,31 +93,35 @@ export class UserService { if (user.id === blockUserRequest.blocked) { throw new UnauthorizedError('User cannot block themselves!'); } - if (user.blocking?.find((blockedUser) => blockedUser.id === blockUserRequest.blocked)) { + const joinedUser = await userRepository.getUserWithBlockedInfo(user.id); + if (joinedUser?.blocking?.find((blockedUser) => blockedUser.id === blockUserRequest.blocked)) { throw new UnauthorizedError('User is already blocked!'); } const blocked = await userRepository.getUserById(blockUserRequest.blocked); if (!blocked) throw new NotFoundError('Blocked user not found!'); - return userRepository.blockUser(user, blocked); + if (!joinedUser) throw new NotFoundError('Joined user not found!'); + return userRepository.blockUser(joinedUser, blocked); }); } - public async unblockUser(user: UserModel, blockUserRequest: UnblockUserRequest): Promise { + public async unblockUser(user: UserModel, unblockUserRequest: UnblockUserRequest): Promise { return this.transactions.readWrite(async (transactionalEntityManager) => { const userRepository = Repositories.user(transactionalEntityManager); - const blocked = await userRepository.getUserById(blockUserRequest.unblocked); + const blocked = await userRepository.getUserById(unblockUserRequest.unblocked); if (!blocked) throw new NotFoundError('Blocked user not found!'); - if (!user.blocking?.find((blockedUser) => blockedUser.id === blockUserRequest.unblocked)) { + const joinedUser = await userRepository.getUserWithBlockedInfo(user.id); + if (!joinedUser) throw new NotFoundError('Joined user not found!'); + if (!joinedUser.blocking?.find((blockedUser) => blockedUser.id === unblockUserRequest.unblocked)) { throw new UnauthorizedError('User is not blocked!'); } - return userRepository.unblockUser(user, blocked); + return userRepository.unblockUser(joinedUser, blocked); }); } public async getBlockedUsersById(params: UuidParam): Promise { return this.transactions.readOnly(async (transactionalEntityManager) => { const userRepository = Repositories.user(transactionalEntityManager); - const user = await userRepository.getBlockedUsersById(params.id); + const user = await userRepository.getUserWithBlockedInfo(params.id); if (!user) throw new NotFoundError('User not found!'); return user.blocking ?? []; }); @@ -134,4 +138,13 @@ export class UserService { return userRepository.deleteUser(userToDelete); }); } + + public async softDeleteUser(params: UuidParam): Promise { + return this.transactions.readWrite(async (transactionalEntityManager) => { + const userRepository = Repositories.user(transactionalEntityManager); + const user = await userRepository.getUserById(params.id); + if (!user) throw new NotFoundError('User not found!'); + return userRepository.softDeleteUser(user); + }); + } } \ No newline at end of file diff --git a/src/tests/UserSessionTest.test.ts b/src/tests/UserSessionTest.test.ts index dd42ec2..4a3e312 100644 --- a/src/tests/UserSessionTest.test.ts +++ b/src/tests/UserSessionTest.test.ts @@ -30,6 +30,7 @@ beforeEach(async () => { expectedUser.username = 'snajima'; expectedUser.netid = 'sn999'; expectedUser.admin = false; + expectedUser.is_active = true; expectedUser.numReviews = 0; expectedUser.stars = 0; expectedUser.photoUrl = 'https://media-exp1.licdn.com/dms/image/C5603AQGmvQtdub6nAQ/profile-displayphoto-shrink_400_400/0/1635358826496?e=1668643200&v=beta&t=ncqjrFUqgqipctcmaSwPzSPrkj0RIQHiCINup_55NNs'; diff --git a/src/tests/UserTest.test.ts b/src/tests/UserTest.test.ts index d0d8a4e..85b47fc 100644 --- a/src/tests/UserTest.test.ts +++ b/src/tests/UserTest.test.ts @@ -32,6 +32,7 @@ beforeEach(async () => { expectedUser.username = 'snajima'; expectedUser.netid = 'sn999'; expectedUser.admin = false; + expectedUser.is_active = true; expectedUser.stars = 0; expectedUser.numReviews = 0; expectedUser.photoUrl = 'https://media-exp1.licdn.com/dms/image/C5603AQGmvQtdub6nAQ/profile-displayphoto-shrink_400_400/0/1635358826496?e=1668643200&v=beta&t=ncqjrFUqgqipctcmaSwPzSPrkj0RIQHiCINup_55NNs'; @@ -199,8 +200,7 @@ describe('user tests', () => { } } expect(blockUserResponse.user?.blocking).toHaveLength(1); - expect(blockUserResponse.user?.blockers).toBeUndefined(); - expect(user1.blocking).toHaveLength(1); + expect(blockUserResponse.user?.blockers).toHaveLength(0); }); test('block users - user cannot block themselves', async () => { @@ -248,12 +248,11 @@ describe('user tests', () => { } } expect(blockUserResponse.user?.blocking).toHaveLength(1); - expect(blockUserResponse.user?.blockers).toBeUndefined(); - expect(user1.blocking).toHaveLength(1); + expect(blockUserResponse.user?.blockers).toHaveLength(0); const unblockUserResponse = await userController.unblockUser({unblocked: user2.id}, user1); - expect(unblockUserResponse.user?.blocking).toBeUndefined(); - expect(unblockUserResponse.user?.blockers).toBeUndefined(); + expect(unblockUserResponse.user?.blocking).toHaveLength(0); + expect(unblockUserResponse.user?.blockers).toHaveLength(0); }); test('unblock users - user is not blocked', async () => { @@ -355,4 +354,15 @@ describe('user tests', () => { const getBlockedUsersResponse = await userController.getBlockedUsersById(user1Uuid); expect(getBlockedUsersResponse.users).toHaveLength(1); }); + + test('soft delete user', async () => { + const user = UserFactory.fakeTemplate(); + + await new DataFactory() + .createUsers(user) + .write(); + + const deleteUserResponse = await userController.softDeleteUser(uuidParam); + expect(deleteUserResponse.user?.is_active === false); + }); }); \ No newline at end of file diff --git a/src/tests/data/UserFactory.ts b/src/tests/data/UserFactory.ts index f81f654..6fd3811 100644 --- a/src/tests/data/UserFactory.ts +++ b/src/tests/data/UserFactory.ts @@ -33,6 +33,7 @@ export class UserFactory { fakeUser.email = fakeUser.netid + '@cornell.edu'; fakeUser.googleId = 'shungoGoogleID'; fakeUser.venmoHandle = "@Shungo-Najima"; + fakeUser.is_active = true; return fakeUser; } @@ -56,6 +57,7 @@ export class UserFactory { fakeUser.email = fakeUser.netid + '@cornell.edu'; fakeUser.googleId = 'tonyGoogleID'; fakeUser.venmoHandle = "@Tony-Matchev"; + fakeUser.is_active = true; return fakeUser; } @@ -79,6 +81,7 @@ export class UserFactory { fakeUser.photoUrl = faker.internet.url(); fakeUser.email = fakeUser.netid + '@cornell.edu'; fakeUser.googleId = faker.datatype.uuid(); + fakeUser.is_active = true; return fakeUser; } diff --git a/src/types/ApiResponses.ts b/src/types/ApiResponses.ts index 9a9480f..dcbae5a 100644 --- a/src/types/ApiResponses.ts +++ b/src/types/ApiResponses.ts @@ -32,6 +32,7 @@ export interface PrivateProfile extends PublicProfile { admin: boolean, email: string, googleId: string, + is_active: boolean, feedbacks: FeedbackModel[], blockers: UserModel[] | undefined, blocking: UserModel[] | undefined, From 5a85523591687979cffe09bf5c355473561f1352 Mon Sep 17 00:00:00 2001 From: akm99 Date: Sun, 14 Apr 2024 23:41:48 -0400 Subject: [PATCH 2/7] Soft Delete + Blocking refactor pt 1 --- src/api/controllers/UserController.ts | 7 +++++- src/migrations/1713139721037-softdelete.ts | 18 +++++++++++++++ src/models/UserModel.ts | 4 ++++ src/repositories/UserRepository.ts | 22 +++++++++++++++--- src/services/AuthService.ts | 6 +++-- src/services/UserService.ts | 27 ++++++++++++++++------ src/tests/UserSessionTest.test.ts | 1 + src/tests/UserTest.test.ts | 22 +++++++++++++----- src/tests/data/UserFactory.ts | 3 +++ src/types/ApiResponses.ts | 1 + 10 files changed, 92 insertions(+), 19 deletions(-) create mode 100644 src/migrations/1713139721037-softdelete.ts diff --git a/src/api/controllers/UserController.ts b/src/api/controllers/UserController.ts index 23dbdc7..bc17d7f 100644 --- a/src/api/controllers/UserController.ts +++ b/src/api/controllers/UserController.ts @@ -59,7 +59,7 @@ export class UserController { return { user: await this.userService.unblockUser(user, unblockUserRequest) } } - @Post('id/:id/blocked/') + @Get('id/:id/blocked/') async getBlockedUsersById(@Params() params: UuidParam): Promise { return { users: await this.userService.getBlockedUsersById(params) }; } @@ -68,4 +68,9 @@ export class UserController { async deleteUser(@Params() params: UuidParam, @CurrentUser() user: UserModel): Promise { return { user: await this.userService.deleteUser(user, params) }; } + + @Post('softDelete/id/:id/') + async softDeleteUser(@Params() params: UuidParam): Promise { + return { user: await this.userService.softDeleteUser(params) }; + } } \ No newline at end of file diff --git a/src/migrations/1713139721037-softdelete.ts b/src/migrations/1713139721037-softdelete.ts new file mode 100644 index 0000000..aaed929 --- /dev/null +++ b/src/migrations/1713139721037-softdelete.ts @@ -0,0 +1,18 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class softdelete1713139721037 implements MigrationInterface { + name = 'softdelete1713139721037' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "User" ADD "is_active" boolean NOT NULL DEFAULT true`); + await queryRunner.query(`ALTER TABLE "Post" ALTER COLUMN "original_price" TYPE numeric`); + await queryRunner.query(`ALTER TABLE "Post" ALTER COLUMN "altered_price" TYPE numeric`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "Post" ALTER COLUMN "altered_price" TYPE numeric`); + await queryRunner.query(`ALTER TABLE "Post" ALTER COLUMN "original_price" TYPE numeric`); + await queryRunner.query(`ALTER TABLE "User" DROP COLUMN "is_active"`); + } + +} diff --git a/src/models/UserModel.ts b/src/models/UserModel.ts index b794578..b26b1c0 100644 --- a/src/models/UserModel.ts +++ b/src/models/UserModel.ts @@ -28,6 +28,9 @@ export class UserModel { @Column() admin: boolean; + @Column({ default: true }) + is_active: boolean; + @Column({ type: "numeric", default: 0 }) stars: number; @@ -102,6 +105,7 @@ export class UserModel { email: this.email, googleId: this.googleId, bio: this.bio, + is_active: this.is_active, blocking: this.blocking, blockers: this.blockers, posts: this.posts, diff --git a/src/repositories/UserRepository.ts b/src/repositories/UserRepository.ts index 261bc9c..7436b93 100644 --- a/src/repositories/UserRepository.ts +++ b/src/repositories/UserRepository.ts @@ -19,10 +19,11 @@ export class UserRepository extends AbstractRepository { .getOne(); } - public async getBlockedUsersById(id: Uuid): Promise { + public async getUserWithBlockedInfo(id: Uuid): Promise { return await this.repository .createQueryBuilder("user") .leftJoinAndSelect("user.blocking", "user_blocking_users.blocking") + .leftJoinAndSelect("user.blockers", "user_blocking_users.blockers") .where("user.id = :id", { id }) .getOne(); } @@ -80,6 +81,16 @@ export class UserRepository extends AbstractRepository { googleId: string, ): Promise { let existingUser = this.repository + .createQueryBuilder("user") + .where("user.username = :username", { username }) + .getOne(); + if (await existingUser) throw new ConflictError('UserModel with same username already exists!'); + existingUser = this.repository + .createQueryBuilder("user") + .where("user.netid = :netid", { netid }) + .getOne(); + if (await existingUser) throw new ConflictError('UserModel with same netid already exists!'); + existingUser = this.repository .createQueryBuilder("user") .where("user.email = :email", { email }) .getOne(); @@ -159,9 +170,14 @@ export class UserRepository extends AbstractRepository { if (!blocker.blocking.find((user) => user.id === blocked.id)) { throw new NotFoundError("User has not been blocked!") } - blocker.blocking.splice(blocker.blocking.indexOf(blocked), 1); - if (blocker.blocking.length === 0) { blocker.blocking = undefined; } + // remove blocked user from blocking list + blocker.blocking = blocker.blocking.filter((user) => user.id !== blocked.id); } return this.repository.save(blocker); } + + public async softDeleteUser(user: UserModel): Promise { + user.is_active = false; + return this.repository.save(user); + } } \ No newline at end of file diff --git a/src/services/AuthService.ts b/src/services/AuthService.ts index e269b1f..a571fc3 100644 --- a/src/services/AuthService.ts +++ b/src/services/AuthService.ts @@ -39,13 +39,11 @@ export class AuthService { if (emailIndex === -1 && !adminEmails?.includes(authRequest.user.email)) { throw new UnauthorizedError('Non-Cornell email used!'); } - if (process.env.OAUTH_ANDROID_CLIENT && process.env.OAUTH_IOS_ID) { // verifies info using id token const ticket = await client.verifyIdToken({ idToken: authRequest.idToken, }); - const payload = ticket.getPayload(); if (payload) { @@ -68,6 +66,10 @@ export class AuthService { user = await userRepository.createUser(netid, netid, newUser.givenName, newUser.familyName, newUser.photoUrl, newUser.email, userId); } + //check if the user is inactive/soft deleted + if (!user.is_active) { + throw new ForbiddenError("User is soft deleted"); + } //add device token const session = await sessionsRepository.createSession(user); sessionsRepository.updateSessionDeviceToken(session, authRequest.deviceToken) diff --git a/src/services/UserService.ts b/src/services/UserService.ts index e84fa3c..ab00019 100644 --- a/src/services/UserService.ts +++ b/src/services/UserService.ts @@ -93,31 +93,35 @@ export class UserService { if (user.id === blockUserRequest.blocked) { throw new UnauthorizedError('User cannot block themselves!'); } - if (user.blocking?.find((blockedUser) => blockedUser.id === blockUserRequest.blocked)) { + const joinedUser = await userRepository.getUserWithBlockedInfo(user.id); + if (joinedUser?.blocking?.find((blockedUser) => blockedUser.id === blockUserRequest.blocked)) { throw new UnauthorizedError('User is already blocked!'); } const blocked = await userRepository.getUserById(blockUserRequest.blocked); if (!blocked) throw new NotFoundError('Blocked user not found!'); - return userRepository.blockUser(user, blocked); + if (!joinedUser) throw new NotFoundError('Joined user not found!'); + return userRepository.blockUser(joinedUser, blocked); }); } - public async unblockUser(user: UserModel, blockUserRequest: UnblockUserRequest): Promise { + public async unblockUser(user: UserModel, unblockUserRequest: UnblockUserRequest): Promise { return this.transactions.readWrite(async (transactionalEntityManager) => { const userRepository = Repositories.user(transactionalEntityManager); - const blocked = await userRepository.getUserById(blockUserRequest.unblocked); + const blocked = await userRepository.getUserById(unblockUserRequest.unblocked); if (!blocked) throw new NotFoundError('Blocked user not found!'); - if (!user.blocking?.find((blockedUser) => blockedUser.id === blockUserRequest.unblocked)) { + const joinedUser = await userRepository.getUserWithBlockedInfo(user.id); + if (!joinedUser) throw new NotFoundError('Joined user not found!'); + if (!joinedUser.blocking?.find((blockedUser) => blockedUser.id === unblockUserRequest.unblocked)) { throw new UnauthorizedError('User is not blocked!'); } - return userRepository.unblockUser(user, blocked); + return userRepository.unblockUser(joinedUser, blocked); }); } public async getBlockedUsersById(params: UuidParam): Promise { return this.transactions.readOnly(async (transactionalEntityManager) => { const userRepository = Repositories.user(transactionalEntityManager); - const user = await userRepository.getBlockedUsersById(params.id); + const user = await userRepository.getUserWithBlockedInfo(params.id); if (!user) throw new NotFoundError('User not found!'); return user.blocking ?? []; }); @@ -134,4 +138,13 @@ export class UserService { return userRepository.deleteUser(userToDelete); }); } + + public async softDeleteUser(params: UuidParam): Promise { + return this.transactions.readWrite(async (transactionalEntityManager) => { + const userRepository = Repositories.user(transactionalEntityManager); + const user = await userRepository.getUserById(params.id); + if (!user) throw new NotFoundError('User not found!'); + return userRepository.softDeleteUser(user); + }); + } } \ No newline at end of file diff --git a/src/tests/UserSessionTest.test.ts b/src/tests/UserSessionTest.test.ts index dd42ec2..4a3e312 100644 --- a/src/tests/UserSessionTest.test.ts +++ b/src/tests/UserSessionTest.test.ts @@ -30,6 +30,7 @@ beforeEach(async () => { expectedUser.username = 'snajima'; expectedUser.netid = 'sn999'; expectedUser.admin = false; + expectedUser.is_active = true; expectedUser.numReviews = 0; expectedUser.stars = 0; expectedUser.photoUrl = 'https://media-exp1.licdn.com/dms/image/C5603AQGmvQtdub6nAQ/profile-displayphoto-shrink_400_400/0/1635358826496?e=1668643200&v=beta&t=ncqjrFUqgqipctcmaSwPzSPrkj0RIQHiCINup_55NNs'; diff --git a/src/tests/UserTest.test.ts b/src/tests/UserTest.test.ts index d0d8a4e..85b47fc 100644 --- a/src/tests/UserTest.test.ts +++ b/src/tests/UserTest.test.ts @@ -32,6 +32,7 @@ beforeEach(async () => { expectedUser.username = 'snajima'; expectedUser.netid = 'sn999'; expectedUser.admin = false; + expectedUser.is_active = true; expectedUser.stars = 0; expectedUser.numReviews = 0; expectedUser.photoUrl = 'https://media-exp1.licdn.com/dms/image/C5603AQGmvQtdub6nAQ/profile-displayphoto-shrink_400_400/0/1635358826496?e=1668643200&v=beta&t=ncqjrFUqgqipctcmaSwPzSPrkj0RIQHiCINup_55NNs'; @@ -199,8 +200,7 @@ describe('user tests', () => { } } expect(blockUserResponse.user?.blocking).toHaveLength(1); - expect(blockUserResponse.user?.blockers).toBeUndefined(); - expect(user1.blocking).toHaveLength(1); + expect(blockUserResponse.user?.blockers).toHaveLength(0); }); test('block users - user cannot block themselves', async () => { @@ -248,12 +248,11 @@ describe('user tests', () => { } } expect(blockUserResponse.user?.blocking).toHaveLength(1); - expect(blockUserResponse.user?.blockers).toBeUndefined(); - expect(user1.blocking).toHaveLength(1); + expect(blockUserResponse.user?.blockers).toHaveLength(0); const unblockUserResponse = await userController.unblockUser({unblocked: user2.id}, user1); - expect(unblockUserResponse.user?.blocking).toBeUndefined(); - expect(unblockUserResponse.user?.blockers).toBeUndefined(); + expect(unblockUserResponse.user?.blocking).toHaveLength(0); + expect(unblockUserResponse.user?.blockers).toHaveLength(0); }); test('unblock users - user is not blocked', async () => { @@ -355,4 +354,15 @@ describe('user tests', () => { const getBlockedUsersResponse = await userController.getBlockedUsersById(user1Uuid); expect(getBlockedUsersResponse.users).toHaveLength(1); }); + + test('soft delete user', async () => { + const user = UserFactory.fakeTemplate(); + + await new DataFactory() + .createUsers(user) + .write(); + + const deleteUserResponse = await userController.softDeleteUser(uuidParam); + expect(deleteUserResponse.user?.is_active === false); + }); }); \ No newline at end of file diff --git a/src/tests/data/UserFactory.ts b/src/tests/data/UserFactory.ts index f81f654..6fd3811 100644 --- a/src/tests/data/UserFactory.ts +++ b/src/tests/data/UserFactory.ts @@ -33,6 +33,7 @@ export class UserFactory { fakeUser.email = fakeUser.netid + '@cornell.edu'; fakeUser.googleId = 'shungoGoogleID'; fakeUser.venmoHandle = "@Shungo-Najima"; + fakeUser.is_active = true; return fakeUser; } @@ -56,6 +57,7 @@ export class UserFactory { fakeUser.email = fakeUser.netid + '@cornell.edu'; fakeUser.googleId = 'tonyGoogleID'; fakeUser.venmoHandle = "@Tony-Matchev"; + fakeUser.is_active = true; return fakeUser; } @@ -79,6 +81,7 @@ export class UserFactory { fakeUser.photoUrl = faker.internet.url(); fakeUser.email = fakeUser.netid + '@cornell.edu'; fakeUser.googleId = faker.datatype.uuid(); + fakeUser.is_active = true; return fakeUser; } diff --git a/src/types/ApiResponses.ts b/src/types/ApiResponses.ts index 9a9480f..dcbae5a 100644 --- a/src/types/ApiResponses.ts +++ b/src/types/ApiResponses.ts @@ -32,6 +32,7 @@ export interface PrivateProfile extends PublicProfile { admin: boolean, email: string, googleId: string, + is_active: boolean, feedbacks: FeedbackModel[], blockers: UserModel[] | undefined, blocking: UserModel[] | undefined, From e038eb20e5364ab848953c518ced0c40e459075d Mon Sep 17 00:00:00 2001 From: akm99 Date: Mon, 15 Apr 2024 16:31:43 -0400 Subject: [PATCH 3/7] Requested changes --- src/api/controllers/UserController.ts | 2 +- src/migrations/1713139721037-softdelete.ts | 4 +- src/models/UserModel.ts | 4 +- src/repositories/UserRepository.ts | 48 +++++++++++----------- src/services/AuthService.ts | 2 +- src/tests/UserSessionTest.test.ts | 2 +- src/tests/UserTest.test.ts | 4 +- src/tests/data/UserFactory.ts | 6 +-- src/types/ApiResponses.ts | 2 +- 9 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/api/controllers/UserController.ts b/src/api/controllers/UserController.ts index bc17d7f..a38bc65 100644 --- a/src/api/controllers/UserController.ts +++ b/src/api/controllers/UserController.ts @@ -59,7 +59,7 @@ export class UserController { return { user: await this.userService.unblockUser(user, unblockUserRequest) } } - @Get('id/:id/blocked/') + @Get('blocked/id/:id/') async getBlockedUsersById(@Params() params: UuidParam): Promise { return { users: await this.userService.getBlockedUsersById(params) }; } diff --git a/src/migrations/1713139721037-softdelete.ts b/src/migrations/1713139721037-softdelete.ts index aaed929..0c7541f 100644 --- a/src/migrations/1713139721037-softdelete.ts +++ b/src/migrations/1713139721037-softdelete.ts @@ -4,7 +4,7 @@ export class softdelete1713139721037 implements MigrationInterface { name = 'softdelete1713139721037' public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "User" ADD "is_active" boolean NOT NULL DEFAULT true`); + await queryRunner.query(`ALTER TABLE "User" ADD "isActive" boolean NOT NULL DEFAULT true`); await queryRunner.query(`ALTER TABLE "Post" ALTER COLUMN "original_price" TYPE numeric`); await queryRunner.query(`ALTER TABLE "Post" ALTER COLUMN "altered_price" TYPE numeric`); } @@ -12,7 +12,7 @@ export class softdelete1713139721037 implements MigrationInterface { public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "Post" ALTER COLUMN "altered_price" TYPE numeric`); await queryRunner.query(`ALTER TABLE "Post" ALTER COLUMN "original_price" TYPE numeric`); - await queryRunner.query(`ALTER TABLE "User" DROP COLUMN "is_active"`); + await queryRunner.query(`ALTER TABLE "User" DROP COLUMN "isActive"`); } } diff --git a/src/models/UserModel.ts b/src/models/UserModel.ts index b26b1c0..35247e6 100644 --- a/src/models/UserModel.ts +++ b/src/models/UserModel.ts @@ -29,7 +29,7 @@ export class UserModel { admin: boolean; @Column({ default: true }) - is_active: boolean; + isActive: boolean; @Column({ type: "numeric", default: 0 }) stars: number; @@ -105,7 +105,7 @@ export class UserModel { email: this.email, googleId: this.googleId, bio: this.bio, - is_active: this.is_active, + isActive: this.isActive, blocking: this.blocking, blockers: this.blockers, posts: this.posts, diff --git a/src/repositories/UserRepository.ts b/src/repositories/UserRepository.ts index 7436b93..4fbe00f 100644 --- a/src/repositories/UserRepository.ts +++ b/src/repositories/UserRepository.ts @@ -20,7 +20,7 @@ export class UserRepository extends AbstractRepository { } public async getUserWithBlockedInfo(id: Uuid): Promise { - return await this.repository + return this.repository .createQueryBuilder("user") .leftJoinAndSelect("user.blocking", "user_blocking_users.blocking") .leftJoinAndSelect("user.blockers", "user_blocking_users.blockers") @@ -80,28 +80,28 @@ export class UserRepository extends AbstractRepository { email: string, googleId: string, ): Promise { - let existingUser = this.repository - .createQueryBuilder("user") - .where("user.username = :username", { username }) - .getOne(); - if (await existingUser) throw new ConflictError('UserModel with same username already exists!'); - existingUser = this.repository - .createQueryBuilder("user") - .where("user.netid = :netid", { netid }) - .getOne(); - if (await existingUser) throw new ConflictError('UserModel with same netid already exists!'); - existingUser = this.repository - .createQueryBuilder("user") - .where("user.email = :email", { email }) - .getOne(); - if (await existingUser) throw new ConflictError('UserModel with same email already exists!'); - - existingUser = this.repository - .createQueryBuilder("user") - .where("user.googleId = :googleId", { googleId }) - .getOne(); - if (await existingUser) throw new ConflictError('UserModel with same google ID already exists!'); - + let existingUser = await this.repository + .createQueryBuilder("user") + .where("user.username = :username", { username }) + .orWhere("user.netid = :netid", { netid }) + .orWhere("user.email = :email", { email }) + .orWhere("user.googleId = :googleId", { googleId }) + .getOne(); + if (existingUser) { + if (existingUser.username === username) { + throw new ConflictError('UserModel with same username already exists!'); + } + else if (existingUser.netid === netid) + { + throw new ConflictError('UserModel with same netid already exists!'); + } + else if (existingUser.email === email) { + throw new ConflictError('UserModel with same email already exists!'); + } + else { + throw new ConflictError('UserModel with same google ID already exists!'); + } + } const adminEmails = process.env.ADMIN_EMAILS?.split(","); const adminStatus = adminEmails?.includes(email); @@ -177,7 +177,7 @@ export class UserRepository extends AbstractRepository { } public async softDeleteUser(user: UserModel): Promise { - user.is_active = false; + user.isActive = false; return this.repository.save(user); } } \ No newline at end of file diff --git a/src/services/AuthService.ts b/src/services/AuthService.ts index a571fc3..daf76a1 100644 --- a/src/services/AuthService.ts +++ b/src/services/AuthService.ts @@ -67,7 +67,7 @@ export class AuthService { newUser.photoUrl, newUser.email, userId); } //check if the user is inactive/soft deleted - if (!user.is_active) { + if (!user.isActive) { throw new ForbiddenError("User is soft deleted"); } //add device token diff --git a/src/tests/UserSessionTest.test.ts b/src/tests/UserSessionTest.test.ts index 4a3e312..3d5c6cf 100644 --- a/src/tests/UserSessionTest.test.ts +++ b/src/tests/UserSessionTest.test.ts @@ -30,7 +30,7 @@ beforeEach(async () => { expectedUser.username = 'snajima'; expectedUser.netid = 'sn999'; expectedUser.admin = false; - expectedUser.is_active = true; + expectedUser.isActive = true; expectedUser.numReviews = 0; expectedUser.stars = 0; expectedUser.photoUrl = 'https://media-exp1.licdn.com/dms/image/C5603AQGmvQtdub6nAQ/profile-displayphoto-shrink_400_400/0/1635358826496?e=1668643200&v=beta&t=ncqjrFUqgqipctcmaSwPzSPrkj0RIQHiCINup_55NNs'; diff --git a/src/tests/UserTest.test.ts b/src/tests/UserTest.test.ts index 85b47fc..afa0c7c 100644 --- a/src/tests/UserTest.test.ts +++ b/src/tests/UserTest.test.ts @@ -32,7 +32,7 @@ beforeEach(async () => { expectedUser.username = 'snajima'; expectedUser.netid = 'sn999'; expectedUser.admin = false; - expectedUser.is_active = true; + expectedUser.isActive = true; expectedUser.stars = 0; expectedUser.numReviews = 0; expectedUser.photoUrl = 'https://media-exp1.licdn.com/dms/image/C5603AQGmvQtdub6nAQ/profile-displayphoto-shrink_400_400/0/1635358826496?e=1668643200&v=beta&t=ncqjrFUqgqipctcmaSwPzSPrkj0RIQHiCINup_55NNs'; @@ -363,6 +363,6 @@ describe('user tests', () => { .write(); const deleteUserResponse = await userController.softDeleteUser(uuidParam); - expect(deleteUserResponse.user?.is_active === false); + expect(deleteUserResponse.user?.isActive === false); }); }); \ No newline at end of file diff --git a/src/tests/data/UserFactory.ts b/src/tests/data/UserFactory.ts index 6fd3811..2ee284d 100644 --- a/src/tests/data/UserFactory.ts +++ b/src/tests/data/UserFactory.ts @@ -33,7 +33,7 @@ export class UserFactory { fakeUser.email = fakeUser.netid + '@cornell.edu'; fakeUser.googleId = 'shungoGoogleID'; fakeUser.venmoHandle = "@Shungo-Najima"; - fakeUser.is_active = true; + fakeUser.isActive = true; return fakeUser; } @@ -57,7 +57,7 @@ export class UserFactory { fakeUser.email = fakeUser.netid + '@cornell.edu'; fakeUser.googleId = 'tonyGoogleID'; fakeUser.venmoHandle = "@Tony-Matchev"; - fakeUser.is_active = true; + fakeUser.isActive = true; return fakeUser; } @@ -81,7 +81,7 @@ export class UserFactory { fakeUser.photoUrl = faker.internet.url(); fakeUser.email = fakeUser.netid + '@cornell.edu'; fakeUser.googleId = faker.datatype.uuid(); - fakeUser.is_active = true; + fakeUser.isActive = true; return fakeUser; } diff --git a/src/types/ApiResponses.ts b/src/types/ApiResponses.ts index dcbae5a..8d73a8a 100644 --- a/src/types/ApiResponses.ts +++ b/src/types/ApiResponses.ts @@ -32,7 +32,7 @@ export interface PrivateProfile extends PublicProfile { admin: boolean, email: string, googleId: string, - is_active: boolean, + isActive: boolean, feedbacks: FeedbackModel[], blockers: UserModel[] | undefined, blocking: UserModel[] | undefined, From 5bf9421ed5ac775686786618b86acfa630c606df Mon Sep 17 00:00:00 2001 From: akm99 Date: Mon, 15 Apr 2024 17:12:30 -0400 Subject: [PATCH 4/7] modify all user and post routes to include soft deletion --- src/services/PostService.ts | 20 ++++++++++----- src/services/UserService.ts | 14 +++++++++-- src/tests/PostTest.test.ts | 26 +++++++++++++++++++- src/tests/UserTest.test.ts | 49 +++++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 9 deletions(-) diff --git a/src/services/PostService.ts b/src/services/PostService.ts index ec232e7..3863048 100644 --- a/src/services/PostService.ts +++ b/src/services/PostService.ts @@ -24,7 +24,8 @@ export class PostService { public async getAllPosts(): Promise { return this.transactions.readOnly(async (transactionalEntityManager) => { const postRepository = Repositories.post(transactionalEntityManager); - return await postRepository.getAllPosts(); + // filter out posts from inactive users + return (await postRepository.getAllPosts()).filter((post) => post.user?.isActive); }); } @@ -33,6 +34,7 @@ export class PostService { const postRepository = Repositories.post(transactionalEntityManager); const post = await postRepository.getPostById(params.id); if (!post) throw new NotFoundError('Post not found!'); + if (!post.user?.isActive) throw new NotFoundError('User is not active!'); return post; }); } @@ -42,6 +44,7 @@ export class PostService { const userRepository = Repositories.user(transactionalEntityManager); const user = await userRepository.getUserById(params.id) if (!user) throw new NotFoundError('User not found!'); + if (!user.isActive) throw new NotFoundError('User is not active!'); const postRepository = Repositories.post(transactionalEntityManager); const posts = await postRepository.getPostsByUserId(params.id); return posts; @@ -53,6 +56,7 @@ export class PostService { const userRepository = Repositories.user(transactionalEntityManager); const user = await userRepository.getUserById(post.userId); if (!user) throw new NotFoundError('User not found!'); + if (!user.isActive) throw new NotFoundError('User is not active!'); const postRepository = Repositories.post(transactionalEntityManager); const images: string[] = []; for (const imageBase64 of post.imagesBase64) { @@ -112,7 +116,7 @@ export class PostService { posts.push(pd); } }); - return posts; + return posts.filter((post) => post.user?.isActive); }); } @@ -120,7 +124,7 @@ export class PostService { return this.transactions.readOnly(async (transactionalEntityManager) => { const postRepository = Repositories.post(transactionalEntityManager); const posts = await postRepository.filterPosts(filterPostsRequest.category); - return posts; + return posts.filter((post) => post.user?.isActive); }); } @@ -128,14 +132,14 @@ export class PostService { return this.transactions.readOnly(async (transactionalEntityManager) => { const postRepository = Repositories.post(transactionalEntityManager); const posts = await postRepository.filterPostsByPrice(filterPostsByPriceRequest.lowerBound, filterPostsByPriceRequest.upperBound) - return posts; + return posts.filter((post) => post.user?.isActive); }) } public async getArchivedPosts(): Promise { return this.transactions.readOnly(async (transactionalEntityManager) => { const postRepository = Repositories.post(transactionalEntityManager); - return await postRepository.getArchivedPosts(); + return (await postRepository.getArchivedPosts()).filter((post) => post.user?.isActive); }); } @@ -144,6 +148,7 @@ export class PostService { const userRepository = Repositories.user(transactionalEntityManager); const user = await userRepository.getUserById(params.id) if (!user) throw new NotFoundError('User not found!'); + if (!user.isActive) throw new NotFoundError('User is not active!'); const postRepository = Repositories.post(transactionalEntityManager); const posts = await postRepository.getArchivedPostsByUserId(params.id); return posts; @@ -155,6 +160,7 @@ export class PostService { const postRepository = Repositories.post(transactionalEntityManager); const post = await postRepository.getPostById(params.id); if (!post) throw new NotFoundError('Post not found!'); + if (post.user.isActive == false) throw new NotFoundError('User is not active!'); if (user.id != post.user?.id) throw new ForbiddenError('User is not poster!'); return await postRepository.archivePost(post); }); @@ -174,6 +180,7 @@ export class PostService { const postRepository = Repositories.post(transactionalEntityManager); const post = await postRepository.getPostById(params.id); if (!post) throw new NotFoundError('Post not found!'); + if (post.user.isActive == false) throw new NotFoundError('User is not active!'); const userRepository = Repositories.user(transactionalEntityManager); return await userRepository.savePost(user, post); }); @@ -184,6 +191,7 @@ export class PostService { const postRepository = Repositories.post(transactionalEntityManager); const post = await postRepository.getPostById(params.id); if (!post) throw new NotFoundError('Post not found!'); + if (post.user.isActive == false) throw new NotFoundError('User is not active!'); const userRepository = Repositories.user(transactionalEntityManager); return await userRepository.unsavePost(user, post); }); @@ -248,7 +256,7 @@ export class PostService { }); } } - return posts + return posts.filter((post) => post.user?.isActive); }); } } diff --git a/src/services/UserService.ts b/src/services/UserService.ts index ab00019..38055be 100644 --- a/src/services/UserService.ts +++ b/src/services/UserService.ts @@ -21,7 +21,7 @@ export class UserService { if (!user.admin) throw new UnauthorizedError('User does not have permission to get all users') return this.transactions.readOnly(async (transactionalEntityManager) => { const userRepository = Repositories.user(transactionalEntityManager); - return userRepository.getAllUsers(); + return (await userRepository.getAllUsers()).filter((user) => user.isActive); }); } @@ -30,6 +30,7 @@ export class UserService { const userRepository = Repositories.user(transactionalEntityManager); const user = await userRepository.getUserById(params.id); if (!user) throw new NotFoundError('User not found!'); + if (!user.isActive) throw new NotFoundError('User is not active!'); return user; }); } @@ -39,6 +40,7 @@ export class UserService { const userRepository = Repositories.user(transactionalEntityManager); const user = await userRepository.getUserByGoogleId(id); if (!user) throw new NotFoundError('User not found!'); + if (!user.isActive) throw new NotFoundError('User is not active!'); return user; }); } @@ -48,6 +50,7 @@ export class UserService { const postRepository = Repositories.post(transactionalEntityManager); const user = await postRepository.getUserByPostId(params.id); if (!user) throw new NotFoundError('Post not found!'); + if (!user.isActive) throw new NotFoundError('User is not active!'); return user; }); } @@ -57,6 +60,7 @@ export class UserService { const userRepository = Repositories.user(transactionalEntityManager); const user = await userRepository.getUserByEmail(email); if (!user) throw new NotFoundError('User not found!'); + if (!user.isActive) throw new NotFoundError('User is not active!'); return user; }); } @@ -93,6 +97,7 @@ export class UserService { if (user.id === blockUserRequest.blocked) { throw new UnauthorizedError('User cannot block themselves!'); } + if (!user.isActive) throw new UnauthorizedError('User is not active!'); const joinedUser = await userRepository.getUserWithBlockedInfo(user.id); if (joinedUser?.blocking?.find((blockedUser) => blockedUser.id === blockUserRequest.blocked)) { throw new UnauthorizedError('User is already blocked!'); @@ -109,6 +114,10 @@ export class UserService { const userRepository = Repositories.user(transactionalEntityManager); const blocked = await userRepository.getUserById(unblockUserRequest.unblocked); if (!blocked) throw new NotFoundError('Blocked user not found!'); + if (user.id === unblockUserRequest.unblocked) { + throw new UnauthorizedError('User cannot unblock themselves!'); + } + if (!user.isActive) throw new UnauthorizedError('User is not active!'); const joinedUser = await userRepository.getUserWithBlockedInfo(user.id); if (!joinedUser) throw new NotFoundError('Joined user not found!'); if (!joinedUser.blocking?.find((blockedUser) => blockedUser.id === unblockUserRequest.unblocked)) { @@ -123,7 +132,8 @@ export class UserService { const userRepository = Repositories.user(transactionalEntityManager); const user = await userRepository.getUserWithBlockedInfo(params.id); if (!user) throw new NotFoundError('User not found!'); - return user.blocking ?? []; + // get user.blocking and filter out inactive users, else return empty array + return user.blocking?.filter((blockedUser) => blockedUser.isActive) ?? []; }); } diff --git a/src/tests/PostTest.test.ts b/src/tests/PostTest.test.ts index 0667128..1c7183a 100644 --- a/src/tests/PostTest.test.ts +++ b/src/tests/PostTest.test.ts @@ -1,5 +1,5 @@ import { PostController } from 'src/api/controllers/PostController'; -import { Connection } from 'typeorm'; +import { Connection, Not } from 'typeorm'; import { UuidParam } from '../api/validators/GenericRequests'; import { PostModel } from '../models/PostModel'; @@ -551,4 +551,28 @@ describe('post tests', () => { console.log(post.altered_price) expect(Number(post.altered_price)).toEqual(Number(getPostsResponse.new_price)); }) + + test('get all posts/post by id with a user who is soft deleted', async () => { + const post = PostFactory.fakeTemplate(); + const user = UserFactory.fakeTemplate(); + user.isActive = false; + post.user = user; + + await new DataFactory() + .createPosts(post) + .createUsers(user) + .write(); + + const getPostsResponse = await postController.getPosts(); + + expect(getPostsResponse.posts).toHaveLength(0); + + try { + await postController.getPostById(uuidParam); + } catch (error) { + expect(error.message).toEqual('User is not active!'); + } + + + }); }); \ No newline at end of file diff --git a/src/tests/UserTest.test.ts b/src/tests/UserTest.test.ts index afa0c7c..650dcfa 100644 --- a/src/tests/UserTest.test.ts +++ b/src/tests/UserTest.test.ts @@ -365,4 +365,53 @@ describe('user tests', () => { const deleteUserResponse = await userController.softDeleteUser(uuidParam); expect(deleteUserResponse.user?.isActive === false); }); + + test('soft delete user, try to get the profile of a soft deleted user', async () => { + const user = UserFactory.fakeTemplate(); + user.admin = true; + + await new DataFactory() + .createUsers(user) + .write(); + + const deleteUserResponse = await userController.softDeleteUser(uuidParam); + expect(deleteUserResponse.user?.isActive === false); + + try { + await userController.getUserById(uuidParam); + } catch (error) { + expect(error.message).toBe('User is not active!'); + } + + try { + await userController.getUsers(user); + } catch (error) { + expect(error.message).toBe('User is not active!'); + } + + try { + await userController.getUserByEmail({ email: user.email }); + } catch (error) { + expect(error.message).toBe('User is not active!'); + } + + try { + await userController.getUserByGoogleId(user.googleId); + } catch (error) { + expect(error.message).toBe('User is not active!'); + } + }); + + test('soft delete, get all users with some users active, some inactive', async () => { + const [user1, user2, user3] = UserFactory.create(3); + user3.admin = true; + user1.isActive = false; + + await new DataFactory() + .createUsers(user1, user2, user3) + .write(); + + const getUsersResponse = await userController.getUsers(user3); + expect(getUsersResponse.users).toHaveLength(2); + }); }); \ No newline at end of file From 31bebf24438d3c77851465e70be68bfcc174314b Mon Sep 17 00:00:00 2001 From: akm99 Date: Mon, 15 Apr 2024 17:50:04 -0400 Subject: [PATCH 5/7] merge --- src/models/UserModel.ts | 6 ++++++ src/repositories/UserRepository.ts | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/models/UserModel.ts b/src/models/UserModel.ts index 35247e6..8ba799c 100644 --- a/src/models/UserModel.ts +++ b/src/models/UserModel.ts @@ -52,6 +52,12 @@ export class UserModel { @Column({ type: "text", default: "" }) bio: string; + @Column({ unique: true }) + referralCode: string; + + @Column({ nullable: true }) + referredBy: Uuid; + @ManyToMany(() => UserModel, (user) => user.blockers) @JoinTable({ name: "user_blocking_users", diff --git a/src/repositories/UserRepository.ts b/src/repositories/UserRepository.ts index 4fbe00f..a6a58cc 100644 --- a/src/repositories/UserRepository.ts +++ b/src/repositories/UserRepository.ts @@ -104,6 +104,9 @@ export class UserRepository extends AbstractRepository { } const adminEmails = process.env.ADMIN_EMAILS?.split(","); const adminStatus = adminEmails?.includes(email); + // Generate a random uuid referral code + const referralCode = require('uuid').v4(); + console.log(referralCode); const user = new UserModel(); user.username = username; From 59f8f1ce70ef6ccfeb11f90adc9d67dd4d760916 Mon Sep 17 00:00:00 2001 From: akm99 Date: Mon, 15 Apr 2024 17:55:07 -0400 Subject: [PATCH 6/7] merge stashed changes --- src/models/UserModel.ts | 6 ------ src/repositories/UserRepository.ts | 3 --- 2 files changed, 9 deletions(-) diff --git a/src/models/UserModel.ts b/src/models/UserModel.ts index 8ba799c..35247e6 100644 --- a/src/models/UserModel.ts +++ b/src/models/UserModel.ts @@ -52,12 +52,6 @@ export class UserModel { @Column({ type: "text", default: "" }) bio: string; - @Column({ unique: true }) - referralCode: string; - - @Column({ nullable: true }) - referredBy: Uuid; - @ManyToMany(() => UserModel, (user) => user.blockers) @JoinTable({ name: "user_blocking_users", diff --git a/src/repositories/UserRepository.ts b/src/repositories/UserRepository.ts index a6a58cc..4fbe00f 100644 --- a/src/repositories/UserRepository.ts +++ b/src/repositories/UserRepository.ts @@ -104,9 +104,6 @@ export class UserRepository extends AbstractRepository { } const adminEmails = process.env.ADMIN_EMAILS?.split(","); const adminStatus = adminEmails?.includes(email); - // Generate a random uuid referral code - const referralCode = require('uuid').v4(); - console.log(referralCode); const user = new UserModel(); user.username = username; From 1d7eda2d09b4cd7b145a069bacc58838b8c7f73c Mon Sep 17 00:00:00 2001 From: akm99 Date: Mon, 15 Apr 2024 18:04:31 -0400 Subject: [PATCH 7/7] Make netid nullable --- src/migrations/1713218553306-makenetidnullable.ts | 14 ++++++++++++++ src/models/UserModel.ts | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 src/migrations/1713218553306-makenetidnullable.ts diff --git a/src/migrations/1713218553306-makenetidnullable.ts b/src/migrations/1713218553306-makenetidnullable.ts new file mode 100644 index 0000000..3ab4844 --- /dev/null +++ b/src/migrations/1713218553306-makenetidnullable.ts @@ -0,0 +1,14 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class makenetidnullable1713218553306 implements MigrationInterface { + name = 'makenetidnullable1713218553306' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "User" ALTER COLUMN "netid" DROP NOT NULL`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "User" ALTER COLUMN "netid" SET NOT NULL`); + } + +} diff --git a/src/models/UserModel.ts b/src/models/UserModel.ts index 35247e6..e7f169b 100644 --- a/src/models/UserModel.ts +++ b/src/models/UserModel.ts @@ -16,7 +16,7 @@ export class UserModel { @Column({ unique: true }) username: string; - @Column({ unique: true }) + @Column({ unique: true, nullable: true }) netid: string; @Column()