From 4d269b480714dbd17fc6584bfb5b4418d6dd8405 Mon Sep 17 00:00:00 2001 From: juliecoust Date: Tue, 30 Jan 2024 14:19:25 +0100 Subject: [PATCH] DEV - Delete user --- .../sqlite/sqlite-user-data-source.ts | 12 ++----- .../data-sources/user-data-source.ts | 1 - src/domain/entities/user.ts | 5 +-- .../repositories/user-repository.ts | 2 ++ .../interfaces/use-cases/user/delete-user.ts | 4 +++ src/domain/repositories/user-repository.ts | 30 ++++++++++++++++ src/domain/use-cases/auth/change-password.ts | 3 ++ src/domain/use-cases/auth/refresh-token.ts | 3 ++ .../use-cases/auth/reset-password-request.ts | 3 ++ src/domain/use-cases/auth/reset-password.ts | 2 ++ src/domain/use-cases/user/create-user.ts | 3 ++ src/domain/use-cases/user/delete-user.ts | 36 +++++++++++++++++++ src/domain/use-cases/user/get-all-users.ts | 4 +++ src/domain/use-cases/user/update-user.ts | 3 ++ src/domain/use-cases/user/valid-user.ts | 3 ++ src/main.ts | 2 ++ src/presentation/routers/user-router.ts | 18 +++++++++- 17 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 src/domain/interfaces/use-cases/user/delete-user.ts create mode 100644 src/domain/use-cases/user/delete-user.ts diff --git a/src/data/data-sources/sqlite/sqlite-user-data-source.ts b/src/data/data-sources/sqlite/sqlite-user-data-source.ts index 36db248..c52a506 100644 --- a/src/data/data-sources/sqlite/sqlite-user-data-source.ts +++ b/src/data/data-sources/sqlite/sqlite-user-data-source.ts @@ -13,7 +13,7 @@ export class SQLiteUserDataSource implements UserDataSource { } init_user_db() { - const sql_create = "CREATE TABLE IF NOT EXISTS 'user' (user_id INTEGER PRIMARY KEY AUTOINCREMENT, first_name TEXT NOT NULL, last_name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, password_hash CHAR(60) NOT NULL, valid_email BOOLEAN CHECK (valid_email IN (0, 1)) DEFAULT 0, confirmation_code TEXT , reset_password_code TEXT ,is_admin BOOLEAN CHECK (is_admin IN (0, 1)) DEFAULT 0, organisation TEXT NOT NULL, country TEXT NOT NULL, user_planned_usage TEXT NOT NULL, user_creation_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP);" + const sql_create = "CREATE TABLE IF NOT EXISTS 'user' (user_id INTEGER PRIMARY KEY AUTOINCREMENT, first_name TEXT NOT NULL, last_name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, password_hash CHAR(60) NOT NULL, valid_email BOOLEAN CHECK (valid_email IN (0, 1)) DEFAULT 0, confirmation_code TEXT , reset_password_code TEXT ,is_admin BOOLEAN CHECK (is_admin IN (0, 1)) DEFAULT 0, organisation TEXT NOT NULL, country TEXT NOT NULL, user_planned_usage TEXT NOT NULL, user_creation_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, deleted TIMESTAMP DEFAULT NULL);" this.db.run(sql_create, []) } @@ -53,20 +53,13 @@ export class SQLiteUserDataSource implements UserDataSource { country: row.country, user_planned_usage: row.user_planned_usage, user_creation_date: row.user_creation_date, + deleted: row.deleted })); resolve(result); } }); }) } - //TODO - // async deleteOne(user_id: String) { - // await this.db.run(`delete ${DB_TABLE} where user_id = $1`, [user_id]) - // } - deleteOne(user_id: string): void { - console.log(user_id) - throw new Error("Method not implemented."); - } // Returns the number of lines updates updateOne(user: UserUpdateModel): Promise { @@ -131,6 +124,7 @@ export class SQLiteUserDataSource implements UserDataSource { country: row.country, user_planned_usage: row.user_planned_usage, user_creation_date: row.user_creation_date, + deleted: row.deleted }; resolve(result); } diff --git a/src/data/interfaces/data-sources/user-data-source.ts b/src/data/interfaces/data-sources/user-data-source.ts index dea6d2d..2483eaa 100644 --- a/src/data/interfaces/data-sources/user-data-source.ts +++ b/src/data/interfaces/data-sources/user-data-source.ts @@ -4,7 +4,6 @@ import { AuthUserCredentialsModel } from "../../../domain/entities/auth"; export interface UserDataSource { create(user: UserRequesCreationtModel): Promise; getAll(): Promise; - deleteOne(user_id: string): void; updateOne(user: UserUpdateModel): Promise; getOne(user: UserRequestModel): Promise; getUserLogin(email: string): Promise; diff --git a/src/domain/entities/user.ts b/src/domain/entities/user.ts index 894383c..d1519c0 100644 --- a/src/domain/entities/user.ts +++ b/src/domain/entities/user.ts @@ -4,7 +4,6 @@ export enum UserStatus { Anonym = "ANONYM" } -// the user request model export interface UserRequesCreationtModel { password: string; first_name: string; @@ -28,6 +27,7 @@ export interface UserRequestModel { country?: string; user_planned_usage?: string; user_creation_date?: string; + deleted?: string; } export interface UserUpdateModel { [key: string]: any; @@ -44,10 +44,11 @@ export interface UserUpdateModel { country?: string; user_planned_usage?: string; user_creation_date?: string; + deleted?: string; } -// the user response model export interface UserResponseModel extends PublicUserModel { + deleted?: string; confirmation_code?: string | null; reset_password_code?: string | null; } diff --git a/src/domain/interfaces/repositories/user-repository.ts b/src/domain/interfaces/repositories/user-repository.ts index 845e7f7..b1a4375 100644 --- a/src/domain/interfaces/repositories/user-repository.ts +++ b/src/domain/interfaces/repositories/user-repository.ts @@ -17,4 +17,6 @@ export interface UserRepository { verifyResetPasswordToken(reset_password_token: string): DecodedToken | null; setResetPasswordCode(user: UserUpdateModel): Promise; toPublicUser(createdUser: PrivateUserModel): PublicUserModel; + deleteUser(user: UserUpdateModel): Promise; + isDeleted(user_id: number): Promise; } \ No newline at end of file diff --git a/src/domain/interfaces/use-cases/user/delete-user.ts b/src/domain/interfaces/use-cases/user/delete-user.ts new file mode 100644 index 0000000..8c06e27 --- /dev/null +++ b/src/domain/interfaces/use-cases/user/delete-user.ts @@ -0,0 +1,4 @@ +import { UserUpdateModel } from "../../../entities/user"; +export interface DeleteUserUseCase { + execute(current_user: UserUpdateModel, user_to_update: UserUpdateModel): Promise; +} \ No newline at end of file diff --git a/src/domain/repositories/user-repository.ts b/src/domain/repositories/user-repository.ts index be1499e..96845d7 100644 --- a/src/domain/repositories/user-repository.ts +++ b/src/domain/repositories/user-repository.ts @@ -161,4 +161,34 @@ export class UserRepositoryImpl implements UserRepository { return publicUser } + async isDeleted(user_id: number): Promise { + const user = await this.userDataSource.getOne({ user_id: user_id }) + if (!user) return false + return user.deleted ? true : false + } + + async deleteUser(user: UserUpdateModel): Promise { + const params = ["user_id", "first_name", "last_name", "email", "valid_email", "confirmation_code", "is_admin", "organisation", "country", "user_planned_usage", "password_hash", "reset_password_code", "deleted"] + const anonymized_user: UserUpdateModel = { + user_id: user.user_id, + first_name: "anonym_" + user.user_id, + last_name: "anonym_" + user.user_id, + email: "anonym_" + user.user_id, + valid_email: false, + confirmation_code: null, + is_admin: false, + organisation: "anonymized", + country: "anonymized", + user_planned_usage: "anonymized", + password_hash: "anonymized", + reset_password_code: null, + // Deleted : current date time + deleted: new Date().toISOString() + } + console.log(anonymized_user) + + const nb_of_updated_user = await this.updateUser(anonymized_user, params) + return nb_of_updated_user + + } } \ No newline at end of file diff --git a/src/domain/use-cases/auth/change-password.ts b/src/domain/use-cases/auth/change-password.ts index dcc4146..5c25033 100644 --- a/src/domain/use-cases/auth/change-password.ts +++ b/src/domain/use-cases/auth/change-password.ts @@ -12,6 +12,9 @@ export class ChangePassword implements ChangePasswordUseCase { async execute(current_user: DecodedToken, credentials: ChangeCredentialsModel): Promise { let nb_of_updated_user: number = 0 + // User should not be deleted + if (await this.userRepository.isDeleted(credentials.user_id)) throw new Error("User is deleted"); + // admin can update anyone password without old password if (await this.userRepository.isAdmin(current_user.user_id)) { nb_of_updated_user = await this.userRepository.changePassword(credentials) diff --git a/src/domain/use-cases/auth/refresh-token.ts b/src/domain/use-cases/auth/refresh-token.ts index da2cd17..2e37dbb 100644 --- a/src/domain/use-cases/auth/refresh-token.ts +++ b/src/domain/use-cases/auth/refresh-token.ts @@ -14,6 +14,9 @@ export class RefreshToken implements RefreshTokenUseCase { } async execute(userAuth: DecodedToken): Promise { + // User should not be deleted + if (await this.userRepository.isDeleted(userAuth.user_id)) throw new Error("User is deleted"); + // Get full user based on decoded token user's email const full_user = await this.userRepository.getUser({ email: userAuth.email }) diff --git a/src/domain/use-cases/auth/reset-password-request.ts b/src/domain/use-cases/auth/reset-password-request.ts index aece1cd..dd71bba 100644 --- a/src/domain/use-cases/auth/reset-password-request.ts +++ b/src/domain/use-cases/auth/reset-password-request.ts @@ -21,6 +21,9 @@ export class ResetPasswordRequest implements ResetPasswordRequestUseCase { const preexistant_user = await this.userRepository.getUser(user) if (!preexistant_user) throw new Error("User does not exist"); + // User should not be deleted + if (await this.userRepository.isDeleted(preexistant_user.user_id)) throw new Error("User is deleted"); + // is the user validated ? if (!preexistant_user.valid_email) throw new Error("User email is not validated"); diff --git a/src/domain/use-cases/auth/reset-password.ts b/src/domain/use-cases/auth/reset-password.ts index 786540a..5f63b6f 100644 --- a/src/domain/use-cases/auth/reset-password.ts +++ b/src/domain/use-cases/auth/reset-password.ts @@ -17,6 +17,8 @@ export class ResetPassword implements ResetPasswordUseCase { if (credentials.reset_password_token) { decoded_token = this.userRepository.verifyResetPasswordToken(credentials.reset_password_token) if (!decoded_token) throw new Error("Token is not valid"); + // User should not be deleted + if (await this.userRepository.isDeleted(decoded_token.user_id)) throw new Error("User is deleted"); } else throw new Error("No token provided"); // does the user bind to reset_password_code exist ? diff --git a/src/domain/use-cases/user/create-user.ts b/src/domain/use-cases/user/create-user.ts index f3b132b..ea83359 100644 --- a/src/domain/use-cases/user/create-user.ts +++ b/src/domain/use-cases/user/create-user.ts @@ -20,6 +20,9 @@ export class CreateUser implements CreateUserUseCase { // Check if a user with the given email already exists if (preexistentUser) { + // User should not be deleted + if (await this.userRepository.isDeleted(preexistentUser.user_id)) throw new Error("User is deleted"); + // If the user exists but hasn't validated their email if (!preexistentUser.valid_email) { // Update the preexisting user with new information diff --git a/src/domain/use-cases/user/delete-user.ts b/src/domain/use-cases/user/delete-user.ts new file mode 100644 index 0000000..f5920fb --- /dev/null +++ b/src/domain/use-cases/user/delete-user.ts @@ -0,0 +1,36 @@ +import { UserUpdateModel } from "../../entities/user"; +import { UserRepository } from "../../interfaces/repositories/user-repository"; +import { DeleteUserUseCase } from "../../interfaces/use-cases/user/delete-user"; + +export class DeleteUser implements DeleteUserUseCase { + userRepository: UserRepository + constructor(userRepository: UserRepository) { + this.userRepository = userRepository + } + + async execute(current_user: UserUpdateModel, user_to_update: UserUpdateModel): Promise { + let nb_of_deleted_user: number = 0 + //TODO LATER : check if user_to_update have no progects where he is manager or member + // const user_to_update_projects = await this.userRepository.getUserProjects(user_to_update.user_id) + // if (user_to_update_projects.length > 0) throw new Error("User have projects") + + // Check that user to upadate exist + const user_to_update_exist = await this.userRepository.getUser({ user_id: user_to_update.user_id }) + if (!user_to_update_exist) throw new Error("Can't find user to delete"); + // User should not be deleted + if (await this.userRepository.isDeleted(user_to_update_exist.user_id)) throw new Error("User is deleted"); + + // Check that current_user is admin or is the user to update + if (await this.userRepository.isAdmin(current_user.user_id) || current_user.user_id == user_to_update.user_id) { + nb_of_deleted_user = await this.userRepository.deleteUser(user_to_update) + if (nb_of_deleted_user == 0) throw new Error("Can't delete user"); + } else { + throw new Error("Logged user cannot delet this user"); + } + + const updated_user = await this.userRepository.getUser({ user_id: user_to_update.user_id }) + console.log(updated_user) + if (!updated_user) throw new Error("Can't find deleted user"); + + } +} \ No newline at end of file diff --git a/src/domain/use-cases/user/get-all-users.ts b/src/domain/use-cases/user/get-all-users.ts index 9ad47e0..ceb040b 100644 --- a/src/domain/use-cases/user/get-all-users.ts +++ b/src/domain/use-cases/user/get-all-users.ts @@ -9,6 +9,10 @@ export class GetAllUsers implements GetAllUsersUseCase { } async execute(): Promise { + // TODO + // User should not be deleted + //if (await this.userRepository.isDeleted(userAuth.user_id)) throw new Error("User is deleted"); + const result = await this.userRepository.getUsers() const publicUsers = result.map(user => this.userRepository.toPublicUser(user)) return publicUsers diff --git a/src/domain/use-cases/user/update-user.ts b/src/domain/use-cases/user/update-user.ts index 9db16fb..425f95a 100644 --- a/src/domain/use-cases/user/update-user.ts +++ b/src/domain/use-cases/user/update-user.ts @@ -11,6 +11,9 @@ export class UpdateUser implements UpdateUserUseCase { async execute(current_user: UserUpdateModel, user_to_update: UserUpdateModel): Promise { let nb_of_updated_user: number = 0 + // User should not be deleted + if (await this.userRepository.isDeleted(user_to_update.user_id)) throw new Error("User is deleted"); + // update admin can update anyone if (await this.userRepository.isAdmin(current_user.user_id)) { nb_of_updated_user = await this.userRepository.adminUpdateUser(user_to_update) diff --git a/src/domain/use-cases/user/valid-user.ts b/src/domain/use-cases/user/valid-user.ts index 1b52ab1..d5d2ebe 100644 --- a/src/domain/use-cases/user/valid-user.ts +++ b/src/domain/use-cases/user/valid-user.ts @@ -14,6 +14,9 @@ export class ValidUser implements ValidUserUseCase { if (!decoded_token) throw new Error("Invalid confirmation token"); // Check if user_id in token is the same as user_id in params if (decoded_token.user_id != user_id) throw new Error("User vallidation forbidden"); + // User should not be deleted + if (await this.userRepository.isDeleted(decoded_token.user_id)) throw new Error("User is deleted"); + // Find user with confirmation code and user_id const user_to_update = await this.userRepository.getUser({ user_id: user_id, confirmation_code: decoded_token.confirmation_code }) diff --git a/src/main.ts b/src/main.ts index 6d3bc95..c441414 100644 --- a/src/main.ts +++ b/src/main.ts @@ -14,6 +14,7 @@ import { ChangePassword } from './domain/use-cases/auth/change-password' import { ValidUser } from './domain/use-cases/user/valid-user' import { ResetPasswordRequest } from './domain/use-cases/auth/reset-password-request' import { ResetPassword } from './domain/use-cases/auth/reset-password' +import { DeleteUser } from './domain/use-cases/user/delete-user' import { UserRepositoryImpl } from './domain/repositories/user-repository' import { AuthRepositoryImpl } from './domain/repositories/auth-repository' @@ -86,6 +87,7 @@ async function getSQLiteDS() { new CreateUser(new UserRepositoryImpl(dataSource, bcryptAdapter, jwtAdapter, config.VALIDATION_TOKEN_SECRET, config.RESET_PASSWORD_TOKEN_SECRET), transporter, mailerAdapter), new UpdateUser(new UserRepositoryImpl(dataSource, bcryptAdapter, jwtAdapter, config.VALIDATION_TOKEN_SECRET, config.RESET_PASSWORD_TOKEN_SECRET)), new ValidUser(new UserRepositoryImpl(dataSource, bcryptAdapter, jwtAdapter, config.VALIDATION_TOKEN_SECRET, config.RESET_PASSWORD_TOKEN_SECRET)), + new DeleteUser(new UserRepositoryImpl(dataSource, bcryptAdapter, jwtAdapter, config.VALIDATION_TOKEN_SECRET, config.RESET_PASSWORD_TOKEN_SECRET)), ) const authMiddleWare = AuthRouter( new MiddlewareAuthCookie(jwtAdapter, config.ACCESS_TOKEN_SECRET, config.REFRESH_TOKEN_SECRET), diff --git a/src/presentation/routers/user-router.ts b/src/presentation/routers/user-router.ts index c6f44d5..a563b0e 100644 --- a/src/presentation/routers/user-router.ts +++ b/src/presentation/routers/user-router.ts @@ -7,6 +7,7 @@ import { CreateUserUseCase } from '../../domain/interfaces/use-cases/user/create import { GetAllUsersUseCase } from '../../domain/interfaces/use-cases/user/get-all-users' import { UpdateUserUseCase } from '../../domain/interfaces/use-cases/user/update-user' import { ValidUserUseCase } from '../../domain/interfaces/use-cases/user/valid-user' +import { DeleteUserUseCase } from '../../domain/interfaces/use-cases/user/delete-user' import { CustomRequest } from '../../domain/entities/auth' export default function UsersRouter( @@ -15,7 +16,8 @@ export default function UsersRouter( getAllUsersUseCase: GetAllUsersUseCase, createUserUseCase: CreateUserUseCase, updateUserUseCase: UpdateUserUseCase, - validUserUseCase: ValidUserUseCase + validUserUseCase: ValidUserUseCase, + deleteUserUseCase: DeleteUserUseCase ) { const router = express.Router() @@ -73,5 +75,19 @@ export default function UsersRouter( else res.status(500).send({ errors: ["Can't welcome user"] }) } }) + + router.delete('/:user_id/', middlewareAuth.auth, async (req: Request, res: Response) => { + try { + const deleted_user = await deleteUserUseCase.execute((req as CustomRequest).token, { ...req.body, user_id: req.params.user_id }) + res.status(200).send(deleted_user) + } catch (err) { + console.log(err) + if (err.message === "Logged user cannot delete this user") res.status(401).send({ errors: [err.message] }) + else if (err.message === "Can't find user to delete") res.status(404).send({ errors: [err.message] }) + else if (err.message === "Can't find deleted user") res.status(500).send({ errors: [err.message] }) + else res.status(500).send({ errors: ["Can't delete user"] }) + } + }) + return router } \ No newline at end of file