delete account

main
alex 2024-01-28 00:14:08 +01:00
parent c8ba965329
commit 7ede8eda45
8 changed files with 164 additions and 0 deletions

View File

@ -8,6 +8,7 @@ import {
isUsernameValid,
} from "../validator/validator";
import {
ACCOUNT_STATE,
CALENDAR_MAX_FUTURE_BOOKING_DAYS,
CALENDAR_MAX_SERVICE_DURATION,
CALENDAR_MIN_EARLIEST_BOOKING_TIME,
@ -19,12 +20,14 @@ import {
getUserSession,
hashPassword,
matchPassword,
newFeedbackId,
newStoreId,
newUserId,
saveSession,
} from "../utils/utils";
import Store from "../models/store";
import Session from "../models/session";
import Feedback from "../models/feedback";
export async function SignUp(req: Request, res: Response) {
try {
@ -180,6 +183,23 @@ export async function Login(req: Request, res: Response) {
return res.status(400).send({ err: "invalid request" });
}
// check user state
if (user.state === ACCOUNT_STATE.DELETED) {
// update user state back to active
User.update(
{
state: ACCOUNT_STATE.ACTIVE,
},
{
where: {
user_id: user.user_id,
},
}
);
}
// create session
saveSession(req, res, user.user_id, user.username);
} catch (error) {
@ -553,3 +573,72 @@ export async function DeleteUserProfileSession(req: Request, res: Response) {
res.status(500).send({ err: "invalid request" });
}
}
export async function DeleteUserProfile(req: Request, res: Response) {
try {
const { reason, feedback, password } = req.body;
if (!reason || !feedback || !password) {
return res.status(400).send({ err: "invalid request" });
}
const session = await getUserSession(req);
if (!session) {
return res.status(401).send({ err: "unauthorized" });
}
const user = await User.findOne({
where: {
user_id: session.user_id,
},
attributes: ["password"],
});
if (!user) {
return res.status(401).send({ err: "unauthorized" });
}
const decodedPassword = decodeBase64(password);
const match = await matchPassword(decodedPassword, user.password);
if (!match) {
return res.status(400).send({ err: "invalid request" });
}
// set user state to deleted
await User.update(
{
state: ACCOUNT_STATE.DELETED,
},
{
where: {
user_id: session.user_id,
},
}
);
// delete all sessions of this user by deleting all sessions with this user_id
await Session.destroy({
where: {
user_id: session.user_id,
},
});
// send feedback
Feedback.create({
feedback_id: newFeedbackId(),
user_id: session.user_id,
feedback: `${reason} - ${feedback}`,
});
res.status(200).send({ msg: "user deleted" });
} catch (error) {
logger.error(error);
res.status(500).send({ err: "invalid request" });
}
}

41
src/models/feedback.ts Normal file
View File

@ -0,0 +1,41 @@
import { DataTypes, Model } from "sequelize";
import sequelize from "../database/database";
interface FeedbackAttributes {
feedback_id: string;
user_id: string;
feedback: string;
}
class Feedback extends Model<FeedbackAttributes> implements FeedbackAttributes {
declare feedback_id: string;
declare user_id: string;
declare feedback: string;
}
Feedback.init(
{
// Model attributes are defined here
feedback_id: {
primaryKey: true,
type: DataTypes.STRING,
allowNull: false,
},
user_id: {
type: DataTypes.STRING,
allowNull: false,
},
feedback: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
tableName: "feedback",
sequelize, // passing the `sequelize` instance is required
createdAt: "created_at",
updatedAt: "updated_at",
}
);
export default Feedback;

View File

@ -1,3 +1,4 @@
import Feedback from "./feedback";
import Session from "./session";
import Store from "./store";
import StoreService from "./storeService";
@ -14,6 +15,7 @@ function syncModels() {
StoreServiceActivity.sync();
StoreServiceActivityUsers.sync();
Website.sync();
Feedback.sync();
// UserGoogleTokens.sync(); not needed as it is created by the calendar backend
}

View File

@ -13,6 +13,7 @@ interface UserAttributes {
calendar_min_earliest_booking_time?: number;
calendar_using_primary_calendar?: boolean;
language: string;
state?: number; // like active, deleted, etc
analytics_enabled: boolean;
}
@ -27,6 +28,7 @@ class User extends Model<UserAttributes> implements UserAttributes {
declare calendar_min_earliest_booking_time: number;
declare calendar_using_primary_calendar: boolean;
declare language: string;
declare state: number;
declare analytics_enabled: boolean;
}
@ -74,6 +76,10 @@ User.init(
type: DataTypes.STRING,
allowNull: false,
},
state: {
type: DataTypes.INTEGER,
// allowNull defaults to true
},
analytics_enabled: {
type: DataTypes.BOOLEAN,
allowNull: false,

View File

@ -34,5 +34,10 @@ router.delete(
sessionProtection,
userController.DeleteUserProfileSession
);
router.delete(
"/profile/delete",
sessionProtection,
userController.DeleteUserProfile
);
export default router;

View File

@ -42,6 +42,14 @@ export const USER_ANALYTICS_ENABLED_DEFAULT = true;
export const VALID_LANGUAGE_CODES = ["en", "de"];
export enum ACCOUNT_STATE {
ACTIVE = 0, // everything is fine
DELETED = 1, // account still exists but is marked as deleted, can be restored or will be deleted after a certain time
}
export const FEEDBACK_MIN_LENGTH = 3;
export const FEEDBACK_MAX_LENGTH = 1024;
// TODO: outdated
export const Roles = {
// admin of the whole system independent of stores

View File

@ -45,6 +45,10 @@ export function newSessionId() {
return uuidv4();
}
export function newFeedbackId() {
return uuidv4();
}
export async function saveSession(
req: Request,
res: Response,

View File

@ -17,6 +17,8 @@ import {
STORE_SERVICE_ACTIVITY_DURATION_MAX,
STORE_SERVICE_ACTIVITY_DURATION_MIN,
VALID_LANGUAGE_CODES,
FEEDBACK_MAX_LENGTH,
FEEDBACK_MIN_LENGTH,
} from "../utils/constants";
import User from "../models/user";
@ -115,3 +117,10 @@ export function isStoreServiceActivityDurationValid(
export function isLanguageCodeValid(languageCode: string) {
return VALID_LANGUAGE_CODES.includes(languageCode);
}
export function isFeedbackValid(feedback: string) {
return (
feedback.length >= FEEDBACK_MIN_LENGTH &&
feedback.length <= FEEDBACK_MAX_LENGTH
);
}