From e54c1c81a163ae503b73f69db0955724533884f6 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 16 Oct 2023 15:24:53 +0800 Subject: [PATCH] soft delete user by calling delete account api --- packages/api/src/entity/user.ts | 1 + packages/api/src/resolvers/user/index.ts | 14 +++-- packages/api/src/services/user.ts | 2 +- packages/api/test/resolvers/user.test.ts | 56 ++++++++++++++++++- .../0135.do.alter_user_status_type.sql | 9 +++ .../0135.undo.alter_user_status_type.sql | 9 +++ 6 files changed, 85 insertions(+), 6 deletions(-) create mode 100755 packages/db/migrations/0135.do.alter_user_status_type.sql create mode 100755 packages/db/migrations/0135.undo.alter_user_status_type.sql diff --git a/packages/api/src/entity/user.ts b/packages/api/src/entity/user.ts index a926b5a89..83526f946 100644 --- a/packages/api/src/entity/user.ts +++ b/packages/api/src/entity/user.ts @@ -22,6 +22,7 @@ export enum RegistrationType { export enum StatusType { Active = 'ACTIVE', Pending = 'PENDING', + Deleted = 'DELETED', } @Entity() diff --git a/packages/api/src/resolvers/user/index.ts b/packages/api/src/resolvers/user/index.ts index 8b80e1340..bf99a5c04 100644 --- a/packages/api/src/resolvers/user/index.ts +++ b/packages/api/src/resolvers/user/index.ts @@ -1,5 +1,9 @@ import * as jwt from 'jsonwebtoken' -import { RegistrationType, User as UserEntity } from '../../entity/user' +import { + RegistrationType, + StatusType, + User as UserEntity, +} from '../../entity/user' import { env } from '../../env' import { DeleteAccountError, @@ -38,6 +42,7 @@ import { import { userRepository } from '../../repository/user' import { createUser } from '../../services/create_user' import { sendVerificationEmail } from '../../services/send_emails' +import { updateUser } from '../../services/user' import { authorized, userDataToUser } from '../../utils/helpers' import { validateUsername } from '../../utils/usernamePolicy' import { WithDataSourcesContext } from '../types' @@ -313,9 +318,10 @@ export const deleteAccountResolver = authorized< DeleteAccountSuccess, DeleteAccountError, MutationDeleteAccountArgs ->(async (_, { userID }, { authTrx, log }) => { - const result = await authTrx(async (t) => { - return t.withRepository(userRepository).delete(userID) +>(async (_, { userID }, { log }) => { + // soft delete user + const result = await updateUser(userID, { + status: StatusType.Deleted, }) if (!result.affected) { log.error('Error deleting user account') diff --git a/packages/api/src/services/user.ts b/packages/api/src/services/user.ts index aed61b8f9..39babc270 100644 --- a/packages/api/src/services/user.ts +++ b/packages/api/src/services/user.ts @@ -13,7 +13,7 @@ export const deleteUser = async (userId: string) => { } export const updateUser = async (userId: string, update: Partial) => { - await authTrx( + return authTrx( async (t) => t.getRepository(User).update(userId, update), undefined, userId diff --git a/packages/api/test/resolvers/user.test.ts b/packages/api/test/resolvers/user.test.ts index 404f4a09d..82e0ceefc 100644 --- a/packages/api/test/resolvers/user.test.ts +++ b/packages/api/test/resolvers/user.test.ts @@ -9,7 +9,7 @@ import { findProfile } from '../../src/services/profile' import { deleteUser, findUser } from '../../src/services/user' import { hashPassword } from '../../src/utils/auth' import { createTestUser } from '../db' -import { graphqlRequest, request } from '../util' +import { generateFakeUuid, graphqlRequest, request } from '../util' describe('User API', () => { const correctPassword = 'fakePassword' @@ -238,4 +238,58 @@ describe('User API', () => { return graphqlRequest(query, invalidAuthToken).expect(500) }) }) + + describe('Delete account', () => { + const query = (userId: string) => ` + mutation { + deleteAccount( + userID: "${userId}" + ) { + ... on DeleteAccountSuccess { + userID + } + ... on DeleteAccountError { + errorCodes + } + } + } + ` + + let userId: string + let authToken: string + + before(async () => { + const user = await createTestUser('to_delete_user') + const res = await request + .post('/local/debug/fake-user-login') + .send({ fakeEmail: user.email }) + userId = user.id + authToken = res.body.authToken + }) + + after(async () => { + await deleteUser(userId) + }) + + context('when user id is valid', () => { + it('deletes user and responds with 200', async () => { + const response = await graphqlRequest(query(userId), authToken).expect( + 200 + ) + expect(response.body.data.deleteAccount.userID).to.eql(userId) + }) + }) + + context('when user not found', () => { + it('responds with error code UserNotFound', async () => { + const response = await graphqlRequest( + query(generateFakeUuid()), + authToken + ).expect(200) + expect(response.body.data.deleteAccount.errorCodes).to.eql([ + 'USER_NOT_FOUND', + ]) + }) + }) + }) }) diff --git a/packages/db/migrations/0135.do.alter_user_status_type.sql b/packages/db/migrations/0135.do.alter_user_status_type.sql new file mode 100755 index 000000000..da4f39eef --- /dev/null +++ b/packages/db/migrations/0135.do.alter_user_status_type.sql @@ -0,0 +1,9 @@ +-- Type: DO +-- Name: alter_user_status_type +-- Description: Add DELETED to the user_status_type enum + +BEGIN; + +ALTER TYPE user_status_type ADD VALUE 'DELETED'; + +COMMIT; diff --git a/packages/db/migrations/0135.undo.alter_user_status_type.sql b/packages/db/migrations/0135.undo.alter_user_status_type.sql new file mode 100755 index 000000000..a39f1f7ba --- /dev/null +++ b/packages/db/migrations/0135.undo.alter_user_status_type.sql @@ -0,0 +1,9 @@ +-- Type: UNDO +-- Name: alter_user_status_type +-- Description: Add DELETED to the user_status_type enum + +BEGIN; + +ALTER TYPE user_status_type DROP VALUE IF EXISTS 'DELETED'; + +COMMIT;