Personal no-bs blogging platform.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123import { drizzle } from 'drizzle-orm/bun-sqlite';import { mkdir } from "node:fs/promises";import path from "node:path";import { eq, lte } from 'drizzle-orm';
import { SESSION_TOKEN_DURATION_MS } from '@/lib/constants.ts';import * as schema from './schema/schema.ts';import type { User, Session, Blog } from '@/lib/types.ts';import { UserEmailExistsError, InvalidPasswordError, UserNotFoundError, SubdomainAlreadyUsedError, InvalidSessionError } from '@/lib/errors.ts';
await mkdir(path.dirname(Bun.env.DB_URL), { recursive: true });export const db = drizzle(process.env.DB_URL, schema);
/** * Creates a new user, PASSWORD UNHASHED. Will error with `UserEmailExistsError()` if email is already taken. * @param input */export async function new_user(input: User) { input.password = await Bun.password.hash(input.password); const result = db.insert(schema.users).values(input).onConflictDoNothing().returning().get(); // if it works, great!
if (!result) throw new UserEmailExistsError(); else return result;}
/** * Deletes an user. * @param {number} id */export async function delete_user(id: number) { const result = db.delete(schema.users).where(eq(schema.users.id, id)).returning()}
/** * Retrive a user by it's ID. Will error with `UserNotFoundError` if the id doesn't exist. * @param id User's ID. * @returns User. */export async function get_user(id: number) { const [user] = await db.select().from(schema.users).where(eq(schema.users.id, id)); if (!user) throw new UserNotFoundError(); return user;}
/** * Creates a new blog. Will error with `SubdomainAlreadyUsedError` if the subdomain's taken. * @param blog Blog without id. * @returns Blog with id. */export async function new_blog(blog: Blog) { const result = db.insert(schema.blogs).values(blog).onConflictDoNothing().returning().get(); // if it works, great!
if (!result) throw new SubdomainAlreadyUsedError(); return result as Blog;}
/** * Takes in the email and alleged RAW PASSWORD, returns User. Might error with `UserNotFoundError` or `InvalidPasswordError` * @param {string} email email. * @param {string} password * @returns User. */export async function validate_login(email: string, password: string): Promise<User> { const user = db.select().from(schema.users).where(eq(schema.users.email, email)).get(); if (!user) throw new UserNotFoundError(); if (await Bun.password.verify(password, user.password)) { return user as User; } else { throw new InvalidPasswordError() }}
/** * Creates a long-lived session token for a user to store in HTTP-ONLY COOKIES. * @param user_id User's ID. * @returns Session object. */export async function create_session(user_id: number): Promise<Session> { const new_refresh_token = { user_id, token: Bun.randomUUIDv7() // issued_at is handled by the database's default `unixepoch()` }; try { await db.insert(schema.sessions).values(new_refresh_token).returning(); } catch (e) { console.error(e); } return new_refresh_token as Session;};
/** * Checks if a session is valid. * @param session_token UUID. * @returns {Session} Session or throws `InvalidSessionError` */export async function validate_session(session_token: string): Promise<Session> { const [session] = await db.select().from(schema.sessions).where(eq(schema.sessions.token, session_token)); if (!session) throw new InvalidSessionError(); if (session.issued_at < new Date(Date.now() - SESSION_TOKEN_DURATION_MS)) { await delete_session(session.token); throw new InvalidSessionError(); }; return session;}
/** * Deletes a given session. * @param session Session token/UUID. * @returns {boolean} True if deleted, false if there wasn't anything like it. */export async function delete_session(session: string): Promise<boolean> { const result = await db.delete(schema.sessions).where(eq(schema.sessions.token, session)).returning(); if (result === undefined) return true; else return false;}
/** * Gets rid of all expired sessions. * @returns A number with the amount of expired sessions removed. */export async function cleanup_sessions(): Promise<number> { const result = await db.delete(schema.sessions) .where(lte(schema.sessions.issued_at, new Date(Date.now() - SESSION_TOKEN_DURATION_MS))).returning() // dates lower (older) than current date - expiry date are removed return result.length;}