create endpoint for deleting account with all associated data

This commit is contained in:
Michi 2025-10-31 16:45:31 +01:00
parent 279845628c
commit 65f34d4d45
3 changed files with 54 additions and 8 deletions

View file

@ -6,24 +6,24 @@ export const attraction = sqliteTable('attraction', {
id: integer().primaryKey({ autoIncrement: true }), id: integer().primaryKey({ autoIncrement: true }),
name: text().notNull(), name: text().notNull(),
apiCode: text('api_code').notNull().unique(), apiCode: text('api_code').notNull().unique(),
themeparkId: integer('themepark_id').notNull().references(() => themepark.id) themeparkId: integer('themepark_id').notNull().references(() => themepark.id, {onDelete: 'cascade'})
}, (t) => [ }, (t) => [
unique().on(t.apiCode, t.themeparkId) unique().on(t.apiCode, t.themeparkId)
]) ])
export const attractionNotification = sqliteTable('attraction_notification', { export const attractionNotification = sqliteTable('attraction_notification', {
id: integer().primaryKey({ autoIncrement: true}), id: integer().primaryKey({ autoIncrement: true}),
userId: integer('user_id').notNull().references(() => user.id), userId: integer('user_id').notNull().references(() => user.id, {onDelete: 'cascade'}),
attractionId: integer('attraction_id').notNull().references(() => attraction.id), attractionId: integer('attraction_id').notNull().references(() => attraction.id, {onDelete: 'cascade'}),
notificationMethodId: integer('notification_method_id').notNull().references(() => notificationMethod.id) notificationMethodId: integer('notification_method_id').notNull().references(() => notificationMethod.id, {onDelete: 'cascade'})
}, (t) => [ }, (t) => [
unique().on(t.userId, t.attractionId, t.notificationMethodId) unique().on(t.userId, t.attractionId, t.notificationMethodId)
]) ])
export const logbook = sqliteTable('logbook', { export const logbook = sqliteTable('logbook', {
id: integer().primaryKey({ autoIncrement: true }), id: integer().primaryKey({ autoIncrement: true }),
userId: integer('user_id').notNull().references(() => user.id), userId: integer('user_id').notNull().references(() => user.id, {onDelete: 'cascade'}),
attractionId: integer('attraction_id').notNull().references(() => attraction.id), attractionId: integer('attraction_id').notNull().references(() => attraction.id, {onDelete: 'cascade'}),
timestamp: integer().notNull(), // unix timecode timestamp: integer().notNull(), // unix timecode
expectedWaittime: integer(), expectedWaittime: integer(),
realWaittime: integer() realWaittime: integer()
@ -35,8 +35,8 @@ export const notificationMethod = sqliteTable('notification_method', {
id: integer().primaryKey({ autoIncrement: true }), id: integer().primaryKey({ autoIncrement: true }),
webhookUrl: text('webhook_url').notNull(), webhookUrl: text('webhook_url').notNull(),
shownName: text().notNull(), shownName: text().notNull(),
userId: integer('user_id').notNull().references(() => user.id), userId: integer('user_id').notNull().references(() => user.id, {onDelete: 'cascade'}),
notificationProviderId: integer('notification_provider_id').notNull().references(() => notificationProvider.id), notificationProviderId: integer('notification_provider_id').notNull().references(() => notificationProvider.id, {onDelete: 'cascade'}),
}, (t) => [ }, (t) => [
unique().on(t.webhookUrl, t.userId, t.notificationProviderId) unique().on(t.webhookUrl, t.userId, t.notificationProviderId)
]) ])

View file

@ -6,6 +6,7 @@ import attraction from './routes/attraction'
import notification from './routes/notification-method' import notification from './routes/notification-method'
import logbook from './routes/logbook' import logbook from './routes/logbook'
import themepark from './routes/themepark' import themepark from './routes/themepark'
import user from './routes/user'
import cronRouter from './jobs/cron' import cronRouter from './jobs/cron'
// create app // create app
@ -37,6 +38,7 @@ app.route('/attraction', attraction)
app.route('/notification-method', notification) app.route('/notification-method', notification)
app.route('/logbook', logbook) app.route('/logbook', logbook)
app.route('/themepark', themepark) app.route('/themepark', themepark)
app.route('/user', user)
export default { export default {
fetch: app.fetch, fetch: app.fetch,
scheduled: cronRouter, scheduled: cronRouter,

44
api/src/routes/user.ts Normal file
View file

@ -0,0 +1,44 @@
import { Hono } from 'hono'
import { DatabaseError } from '../errors'
import { getDbContext } from '../db/client'
import { getUser } from '../lib/user-auth'
import { user } from '../db/schema'
import { eq } from 'drizzle-orm'
import { Message } from '../types/response'
import * as z from 'zod'
import httpZValidator from '../lib/http-z-validator'
const app = new Hono()
/**
* Deletes user account with all associated data (requires parameter 'confirm' to be true)
* Be careful when using (once your data is delete it cannot be restored)
*/
app.delete('delete-account', httpZValidator('query', z.strictObject({
confirm: z.literal('true') // to prevent unwanted account deletion when hitting the endpoint unintentionally
})),
async (c) => {
const db = getDbContext(c);
const currentUser = await getUser(c);
const params = c.req.valid('query');
try{
const res = await db.delete(user).where(
eq(user.id, currentUser.id)
).returning();
const deletedUser = res[0];
const message = res.length > 0
? `User account ${deletedUser.mail} with all associated data deleted.`
: 'User account does not exist. No changes made.';
return c.json(new Message(message));
}
catch(e){
console.error(e);
throw new DatabaseError();
}
})
export default app