diff --git a/packages/api/src/resolvers/user/index.ts b/packages/api/src/resolvers/user/index.ts index 38b0111e2..c15000a66 100644 --- a/packages/api/src/resolvers/user/index.ts +++ b/packages/api/src/resolvers/user/index.ts @@ -42,7 +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 { softDeleteUser } from '../../services/user' import { authorized, userDataToUser } from '../../utils/helpers' import { validateUsername } from '../../utils/usernamePolicy' import { WithDataSourcesContext } from '../types' @@ -320,10 +320,8 @@ export const deleteAccountResolver = authorized< DeleteAccountError, MutationDeleteAccountArgs >(async (_, { userID }, { log }) => { - // soft delete user - const result = await updateUser(userID, { - status: StatusType.Deleted, - }) + // soft delete user and change email address for user to sign up again + const result = await softDeleteUser(userID) 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 d7f9fa0d3..1a4dc58d1 100644 --- a/packages/api/src/services/user.ts +++ b/packages/api/src/services/user.ts @@ -1,4 +1,5 @@ import { DeepPartial, FindOptionsWhere, In } from 'typeorm' +import { Profile } from '../entity/profile' import { StatusType, User } from '../entity/user' import { authTrx, getRepository, queryBuilderToRawSql } from '../repository' import { userRepository } from '../repository/user' @@ -22,6 +23,32 @@ export const updateUser = async (userId: string, update: Partial) => { ) } +export const softDeleteUser = async (userId: string) => { + return authTrx( + async (t) => { + // change email address and username for user to sign up again + await t.getRepository(Profile).update( + { + user: { + id: userId, + }, + }, + { + username: `deleted_user_${userId}`, + } + ) + + return t.getRepository(User).update(userId, { + status: StatusType.Deleted, + email: `deleted_user_${userId}@omnivore.app`, + sourceUserId: `deleted_user_${userId}`, + }) + }, + undefined, + userId + ) +} + export const findActiveUser = async (id: string): Promise => { return userRepository.findOneBy({ id, status: StatusType.Active }) } diff --git a/packages/api/test/resolvers/user.test.ts b/packages/api/test/resolvers/user.test.ts index d3ac7ea4f..5e19f714a 100644 --- a/packages/api/test/resolvers/user.test.ts +++ b/packages/api/test/resolvers/user.test.ts @@ -1,10 +1,11 @@ import { expect } from 'chai' import 'mocha' -import { User } from '../../src/entity/user' +import { StatusType, User } from '../../src/entity/user' import { UpdateUserErrorCode, UpdateUserProfileErrorCode, } from '../../src/generated/graphql' +import { userRepository } from '../../src/repository/user' import { findProfile } from '../../src/services/profile' import { deleteUser, findActiveUser } from '../../src/services/user' import { hashPassword } from '../../src/utils/auth' @@ -272,11 +273,15 @@ describe('User API', () => { }) context('when user id is valid', () => { - it('deletes user and responds with 200', async () => { + it('deletes user and changes email address', async () => { const response = await graphqlRequest(query(userId), authToken).expect( 200 ) expect(response.body.data.deleteAccount.userID).to.eql(userId) + + const user = await userRepository.findOneBy({ id: userId }) + expect(user?.status).to.eql(StatusType.Deleted) + expect(user?.email).to.eql(`deleted_user_${userId}@omnivore.app`) }) })