import type { Token as ApiToken } from "@versia/client/types"; import { type Application, User, db } from "@versia/kit/db"; import { Tokens } from "@versia/kit/tables"; import { type InferInsertModel, type InferSelectModel, type SQL, desc, eq, inArray, } from "drizzle-orm"; import { z } from "zod"; import { BaseInterface } from "./base.ts"; type TokenType = InferSelectModel & { application: typeof Application.$type | null; }; export class Token extends BaseInterface { public static schema: z.ZodType = z.object({ access_token: z.string(), token_type: z.enum(["bearer"]), scope: z.string(), created_at: z.number(), }); public static $type: TokenType; public async reload(): Promise { const reloaded = await Token.fromId(this.data.id); if (!reloaded) { throw new Error("Failed to reload token"); } this.data = reloaded.data; } public static async fromId(id: string | null): Promise { if (!id) { return null; } return await Token.fromSql(eq(Tokens.id, id)); } public static async fromIds(ids: string[]): Promise { return await Token.manyFromSql(inArray(Tokens.id, ids)); } public static async fromSql( sql: SQL | undefined, orderBy: SQL | undefined = desc(Tokens.id), ): Promise { const found = await db.query.Tokens.findFirst({ where: sql, orderBy, with: { application: true, }, }); if (!found) { return null; } return new Token(found); } public static async manyFromSql( sql: SQL | undefined, orderBy: SQL | undefined = desc(Tokens.id), limit?: number, offset?: number, extra?: Parameters[0], ): Promise { const found = await db.query.Tokens.findMany({ where: sql, orderBy, limit, offset, with: { application: true, ...extra?.with, }, }); return found.map((s) => new Token(s)); } public async update(newAttachment: Partial): Promise { await db .update(Tokens) .set(newAttachment) .where(eq(Tokens.id, this.id)); const updated = await Token.fromId(this.data.id); if (!updated) { throw new Error("Failed to update token"); } this.data = updated.data; return updated.data; } public save(): Promise { return this.update(this.data); } public async delete(ids?: string[]): Promise { if (Array.isArray(ids)) { await db.delete(Tokens).where(inArray(Tokens.id, ids)); } else { await db.delete(Tokens).where(eq(Tokens.id, this.id)); } } public static async insert( data: InferInsertModel, ): Promise { const inserted = (await db.insert(Tokens).values(data).returning())[0]; const token = await Token.fromId(inserted.id); if (!token) { throw new Error("Failed to insert token"); } return token; } public static async insertMany( data: InferInsertModel[], ): Promise { const inserted = await db.insert(Tokens).values(data).returning(); return await Token.fromIds(inserted.map((i) => i.id)); } public get id(): string { return this.data.id; } public static async fromAccessToken( accessToken: string, ): Promise { return await Token.fromSql(eq(Tokens.accessToken, accessToken)); } /** * Retrieves the associated user from this token * * @returns The user associated with this token */ public async getUser(): Promise { if (!this.data.userId) { return null; } return await User.fromId(this.data.userId); } public toApi(): ApiToken { return { access_token: this.data.accessToken, token_type: "Bearer", scope: this.data.scope, created_at: Math.floor( new Date(this.data.createdAt).getTime() / 1000, ), }; } }