diff --git a/README.md b/README.md index 5d0e7b9..ded9be1 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,11 @@ apply changes npx drizzle-kit push --config=drizzle-dev.config.ts ``` +export sql statements instead of running migration +```bash +npx drizzle-kit export --config=drizzle-dev.config.ts +``` + ## SQLite / D1 Delete view ```sql diff --git a/api/src/db/schema.ts b/api/src/db/schema.ts index bc56e07..cdb7942 100644 --- a/api/src/db/schema.ts +++ b/api/src/db/schema.ts @@ -1,4 +1,4 @@ -import { integer, text, sqliteTable, sqliteView } from "drizzle-orm/sqlite-core"; +import { check, integer, text, sqliteTable, sqliteView } from "drizzle-orm/sqlite-core"; import { eq, sql } from "drizzle-orm"; // Tables @@ -48,9 +48,15 @@ export const themepark = sqliteTable('themepark', { export const user = sqliteTable('user', { id: integer().primaryKey({ autoIncrement: true }), - username: text().notNull(), - isActive: integer({ mode: 'boolean' }).default(false) -}) + mail: text().notNull().unique(), + isActive: integer({ mode: 'boolean' }).notNull().default(false), + createdAt: integer().notNull(), + lastActive: integer().notNull() +}, +(table) => [ + check("mail_validation", sql`${table.mail} LIKE '%@%'`) +] +) // Views export const subscribedThemeparks = sqliteView('subscribed_themeparks').as((qb) => diff --git a/api/src/index.ts b/api/src/index.ts index 767b467..6547f1b 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -1,5 +1,6 @@ import { Hono } from 'hono' import { authHandler, initAuthConfig, verifyAuth } from '@hono/auth-js' +import { getUser } from './lib/user-auth' import GitHub from '@auth/core/providers/github' import notification from './routes/notification' import logbook from './routes/logbook' @@ -24,9 +25,9 @@ app.use('/auth/*', authHandler()) app.use('/*', verifyAuth()) // example endpoint -app.get('/protected', (c) => { - const auth = c.get('authUser') - return c.json(auth) +app.get('/protected', async (c) => { + const user = await getUser(c); + return c.json(user); }) // define routes & export app diff --git a/api/src/lib/user-auth.ts b/api/src/lib/user-auth.ts new file mode 100644 index 0000000..b5ebec8 --- /dev/null +++ b/api/src/lib/user-auth.ts @@ -0,0 +1,70 @@ +import { getDbContext } from "../db/client"; +import { Context } from "hono"; +import { UserSelect } from "../types/user"; +import { user } from "../db/schema"; +import { like } from "drizzle-orm"; +import { DrizzleD1Database } from "drizzle-orm/d1"; +import { MissingMailError, UserInactiveError } from "../types/error"; + +/** + * Returns the details of a user from the given context + * @param c Request context + * @returns Object of the user details as type UserSelect + */ +export async function getUser(c: Context): Promise{ + const db = getDbContext(c); + const auth = c.get('authUser'); + if(!auth.session.user || !auth.session.user.email) throw new MissingMailError(); + + const currentUser: UserSelect = c.get('currentUser'); + if(currentUser) return currentUser; + + const mail = auth.session.user.email; + + let userData: UserSelect[]; + try{ + userData = await db.selectDistinct().from(user).limit(1).where(like(user.mail, mail)); + + } + catch(e){ + throw new Error(`Database error: ${e}`); + } + + const dbResult = userData[0] ?? await createUser(db, mail); + if(!dbResult.isActive) throw new UserInactiveError(); + + c.set('currentUser', dbResult); + return dbResult; +} + +/** + * Creates a new user in the DB from the given context + * @param c Request context + * @returns The created user as Object of type UserSelect + */ +async function createUser(db: DrizzleD1Database, userMail: string): Promise{ + let userData: UserSelect[]; + + try{ + userData = await db.insert(user).values( + { + mail: userMail, + isActive: true, + createdAt: now(), + lastActive: now() + } + ).returning(); + + } + catch(e){ + throw new Error(`Database error: ${e}`); + } + + return userData[0]; +} + +/** + * Getting the current time + * @returns Current unix timestamp + */ +const now = () => Math.floor(Date.now() / 1000); diff --git a/api/src/types/error.ts b/api/src/types/error.ts new file mode 100644 index 0000000..fa072b6 --- /dev/null +++ b/api/src/types/error.ts @@ -0,0 +1,13 @@ +import { HTTPException } from "hono/http-exception"; + +export class UserInactiveError extends HTTPException{ + constructor(){ + super(403, { message: 'User is currently disabled.' }) + } +} + +export class MissingMailError extends HTTPException{ + constructor(){ + super(400, { message: 'Mail address is missing in authorizaton header.' }) + } +} \ No newline at end of file