mirror of
https://github.com/versia-pub/server.git
synced 2026-03-13 05:49:16 +01:00
refactor(database): ♻️ Move Token to its own ORM abstraction, optimize familiar_followers route
This commit is contained in:
parent
962c159ddd
commit
845041e4db
55 changed files with 694 additions and 504 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import type { Application as APIApplication } from "@versia/client/types";
|
||||
import { db } from "@versia/kit/db";
|
||||
import { Token, db } from "@versia/kit/db";
|
||||
import { Applications } from "@versia/kit/tables";
|
||||
import {
|
||||
type InferInsertModel,
|
||||
|
|
@ -81,15 +81,11 @@ export class Application extends BaseInterface<typeof Applications> {
|
|||
public static async getFromToken(
|
||||
token: string,
|
||||
): Promise<Application | null> {
|
||||
const result = await db.query.Tokens.findFirst({
|
||||
where: (tokens, { eq }): SQL | undefined =>
|
||||
eq(tokens.accessToken, token),
|
||||
with: {
|
||||
application: true,
|
||||
},
|
||||
});
|
||||
const result = await Token.fromAccessToken(token);
|
||||
|
||||
return result?.application ? new Application(result.application) : null;
|
||||
return result?.data.application
|
||||
? new Application(result.data.application)
|
||||
: null;
|
||||
}
|
||||
|
||||
public static fromClientId(clientId: string): Promise<Application | null> {
|
||||
|
|
|
|||
171
classes/database/token.ts
Normal file
171
classes/database/token.ts
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
import type { Token as ApiToken } from "@versia/client/types";
|
||||
import { User, db } from "@versia/kit/db";
|
||||
import { type Applications, 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";
|
||||
|
||||
export type TokenType = InferSelectModel<typeof Tokens> & {
|
||||
application: InferSelectModel<typeof Applications> | null;
|
||||
};
|
||||
|
||||
export class Token extends BaseInterface<typeof Tokens, TokenType> {
|
||||
public static schema: z.ZodType<ApiToken> = z.object({
|
||||
access_token: z.string(),
|
||||
token_type: z.enum(["bearer"]),
|
||||
scope: z.string(),
|
||||
created_at: z.number(),
|
||||
});
|
||||
|
||||
public async reload(): Promise<void> {
|
||||
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<Token | null> {
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await Token.fromSql(eq(Tokens.id, id));
|
||||
}
|
||||
|
||||
public static async fromIds(ids: string[]): Promise<Token[]> {
|
||||
return await Token.manyFromSql(inArray(Tokens.id, ids));
|
||||
}
|
||||
|
||||
public static async fromSql(
|
||||
sql: SQL<unknown> | undefined,
|
||||
orderBy: SQL<unknown> | undefined = desc(Tokens.id),
|
||||
): Promise<Token | null> {
|
||||
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<unknown> | undefined,
|
||||
orderBy: SQL<unknown> | undefined = desc(Tokens.id),
|
||||
limit?: number,
|
||||
offset?: number,
|
||||
extra?: Parameters<typeof db.query.Tokens.findMany>[0],
|
||||
): Promise<Token[]> {
|
||||
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<TokenType>): Promise<TokenType> {
|
||||
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<TokenType> {
|
||||
return this.update(this.data);
|
||||
}
|
||||
|
||||
public async delete(ids?: string[]): Promise<void> {
|
||||
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<typeof Tokens>,
|
||||
): Promise<Token> {
|
||||
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<typeof Tokens>[],
|
||||
): Promise<Token[]> {
|
||||
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<Token | null> {
|
||||
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<User | null> {
|
||||
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,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import type { Tokens } from "@versia/kit/tables";
|
||||
import type { InferSelectModel } from "drizzle-orm";
|
||||
|
||||
/**
|
||||
* The type of token.
|
||||
*/
|
||||
export enum TokenType {
|
||||
Bearer = "Bearer",
|
||||
}
|
||||
|
||||
export type Token = InferSelectModel<typeof Tokens>;
|
||||
|
|
@ -3,18 +3,10 @@ import type {
|
|||
FollowAccept,
|
||||
FollowReject,
|
||||
} from "@versia/federation/types";
|
||||
import { User, db } from "@versia/kit/db";
|
||||
import {
|
||||
Applications,
|
||||
type Instances,
|
||||
type Roles,
|
||||
Tokens,
|
||||
type Users,
|
||||
} from "@versia/kit/tables";
|
||||
import { type InferSelectModel, type SQL, eq, sql } from "drizzle-orm";
|
||||
import type { ApplicationType } from "~/classes/database/application.ts";
|
||||
import { type Application, type Token, type User, db } from "@versia/kit/db";
|
||||
import type { Instances, Roles, Users } from "@versia/kit/tables";
|
||||
import { type InferSelectModel, type SQL, sql } from "drizzle-orm";
|
||||
import type { EmojiWithInstance } from "~/classes/database/emoji.ts";
|
||||
import type { Token } from "./token.ts";
|
||||
|
||||
export type UserType = InferSelectModel<typeof Users>;
|
||||
|
||||
|
|
@ -31,23 +23,7 @@ export type UserWithRelations = UserType & {
|
|||
roles: InferSelectModel<typeof Roles>[];
|
||||
};
|
||||
|
||||
export const userRelations: {
|
||||
instance: true;
|
||||
emojis: {
|
||||
with: {
|
||||
emoji: {
|
||||
with: {
|
||||
instance: true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
roles: {
|
||||
with: {
|
||||
role: true;
|
||||
};
|
||||
};
|
||||
} = {
|
||||
export const userRelations = {
|
||||
instance: true,
|
||||
emojis: {
|
||||
with: {
|
||||
|
|
@ -63,7 +39,7 @@ export const userRelations: {
|
|||
role: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
} as const;
|
||||
|
||||
export const userExtras = {
|
||||
followerCount:
|
||||
|
|
@ -103,19 +79,10 @@ export const userExtrasTemplate = (
|
|||
|
||||
export interface AuthData {
|
||||
user: User | null;
|
||||
token: string;
|
||||
application: ApplicationType | null;
|
||||
token: Token | null;
|
||||
application: Application | null;
|
||||
}
|
||||
|
||||
export const getFromHeader = async (value: string): Promise<AuthData> => {
|
||||
const token = value.split(" ")[1];
|
||||
|
||||
const { user, application } =
|
||||
await retrieveUserAndApplicationFromToken(token);
|
||||
|
||||
return { user, token, application };
|
||||
};
|
||||
|
||||
export const transformOutputToUserWithRelations = (
|
||||
user: Omit<UserType, "endpoints"> & {
|
||||
followerCount: unknown;
|
||||
|
|
@ -180,52 +147,6 @@ export const findManyUsers = async (
|
|||
return output.map((user) => transformOutputToUserWithRelations(user));
|
||||
};
|
||||
|
||||
export const retrieveUserAndApplicationFromToken = async (
|
||||
accessToken: string,
|
||||
): Promise<{
|
||||
user: User | null;
|
||||
application: ApplicationType | null;
|
||||
}> => {
|
||||
if (!accessToken) {
|
||||
return { user: null, application: null };
|
||||
}
|
||||
|
||||
const output = (
|
||||
await db
|
||||
.select({
|
||||
token: Tokens,
|
||||
application: Applications,
|
||||
})
|
||||
.from(Tokens)
|
||||
.leftJoin(Applications, eq(Tokens.applicationId, Applications.id))
|
||||
.where(eq(Tokens.accessToken, accessToken))
|
||||
.limit(1)
|
||||
)[0];
|
||||
|
||||
if (!output?.token.userId) {
|
||||
return { user: null, application: null };
|
||||
}
|
||||
|
||||
const user = await User.fromId(output.token.userId);
|
||||
|
||||
return { user, application: output.application ?? null };
|
||||
};
|
||||
|
||||
export const retrieveToken = async (
|
||||
accessToken: string,
|
||||
): Promise<Token | null> => {
|
||||
if (!accessToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
(await db.query.Tokens.findFirst({
|
||||
where: (tokens, { eq }): SQL | undefined =>
|
||||
eq(tokens.accessToken, accessToken),
|
||||
})) ?? null
|
||||
);
|
||||
};
|
||||
|
||||
export const followRequestToVersia = (
|
||||
follower: User,
|
||||
followee: User,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue