diff --git a/api/src/db/schema.ts b/api/src/db/schema.ts index 922d49f..5907d41 100644 --- a/api/src/db/schema.ts +++ b/api/src/db/schema.ts @@ -6,24 +6,24 @@ export const attraction = sqliteTable('attraction', { id: integer().primaryKey({ autoIncrement: true }), name: text().notNull(), 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) => [ unique().on(t.apiCode, t.themeparkId) ]) export const attractionNotification = sqliteTable('attraction_notification', { id: integer().primaryKey({ autoIncrement: true}), - userId: integer('user_id').notNull().references(() => user.id), - attractionId: integer('attraction_id').notNull().references(() => attraction.id), - notificationMethodId: integer('notification_method_id').notNull().references(() => notificationMethod.id) + userId: integer('user_id').notNull().references(() => user.id, {onDelete: 'cascade'}), + attractionId: integer('attraction_id').notNull().references(() => attraction.id, {onDelete: 'cascade'}), + notificationMethodId: integer('notification_method_id').notNull().references(() => notificationMethod.id, {onDelete: 'cascade'}) }, (t) => [ unique().on(t.userId, t.attractionId, t.notificationMethodId) ]) export const logbook = sqliteTable('logbook', { id: integer().primaryKey({ autoIncrement: true }), - userId: integer('user_id').notNull().references(() => user.id), - attractionId: integer('attraction_id').notNull().references(() => attraction.id), + userId: integer('user_id').notNull().references(() => user.id, {onDelete: 'cascade'}), + attractionId: integer('attraction_id').notNull().references(() => attraction.id, {onDelete: 'cascade'}), timestamp: integer().notNull(), // unix timecode expectedWaittime: integer(), realWaittime: integer() @@ -35,8 +35,8 @@ export const notificationMethod = sqliteTable('notification_method', { id: integer().primaryKey({ autoIncrement: true }), webhookUrl: text('webhook_url').notNull(), shownName: text().notNull(), - userId: integer('user_id').notNull().references(() => user.id), - notificationProviderId: integer('notification_provider_id').notNull().references(() => notificationProvider.id), + userId: integer('user_id').notNull().references(() => user.id, {onDelete: 'cascade'}), + notificationProviderId: integer('notification_provider_id').notNull().references(() => notificationProvider.id, {onDelete: 'cascade'}), }, (t) => [ unique().on(t.webhookUrl, t.userId, t.notificationProviderId) ]) diff --git a/api/src/index.ts b/api/src/index.ts index b284adf..5d5a858 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -6,6 +6,7 @@ import attraction from './routes/attraction' import notification from './routes/notification-method' import logbook from './routes/logbook' import themepark from './routes/themepark' +import user from './routes/user' import cronRouter from './jobs/cron' // create app @@ -37,6 +38,7 @@ app.route('/attraction', attraction) app.route('/notification-method', notification) app.route('/logbook', logbook) app.route('/themepark', themepark) +app.route('/user', user) export default { fetch: app.fetch, scheduled: cronRouter, diff --git a/api/src/routes/user.ts b/api/src/routes/user.ts new file mode 100644 index 0000000..19e0f15 --- /dev/null +++ b/api/src/routes/user.ts @@ -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 \ No newline at end of file