server/packages/kit/db/role.ts

226 lines
6.2 KiB
TypeScript
Raw Normal View History

2025-05-01 16:27:34 +02:00
import type {
RolePermission,
Role as RoleSchema,
} from "@versia/client/schemas";
import { config, ProxiableUrl } from "@versia-server/config";
2024-06-08 05:31:17 +02:00
import {
and,
desc,
eq,
2025-04-10 19:15:31 +02:00
type InferInsertModel,
type InferSelectModel,
2024-06-08 05:31:17 +02:00
inArray,
2025-04-10 19:15:31 +02:00
type SQL,
2024-06-08 05:31:17 +02:00
} from "drizzle-orm";
import type { z } from "zod/v4";
import { db } from "../tables/db.ts";
import { Roles, RoleToUsers } from "../tables/schema.ts";
import { BaseInterface } from "./base.ts";
2025-04-10 19:15:31 +02:00
type RoleType = InferSelectModel<typeof Roles>;
export class Role extends BaseInterface<typeof Roles> {
public static $type: RoleType;
public static defaultRole = new Role({
id: "default",
name: "Default",
permissions: config.permissions.default,
priority: 0,
description: "Default role for all users",
visible: false,
icon: null,
});
public static adminRole = new Role({
id: "admin",
name: "Admin",
permissions: config.permissions.admin,
priority: 2 ** 31 - 1,
description: "Default role for all administrators",
visible: false,
icon: null,
});
public async reload(): Promise<void> {
const reloaded = await Role.fromId(this.data.id);
if (!reloaded) {
throw new Error("Failed to reload role");
}
this.data = reloaded.data;
}
2024-06-08 05:31:17 +02:00
public static async fromId(id: string | null): Promise<Role | null> {
if (!id) {
return null;
}
2024-06-08 05:31:17 +02:00
return await Role.fromSql(eq(Roles.id, id));
}
public static async fromIds(ids: string[]): Promise<Role[]> {
return await Role.manyFromSql(inArray(Roles.id, ids));
}
public static async fromSql(
sql: SQL<unknown> | undefined,
orderBy: SQL<unknown> | undefined = desc(Roles.id),
): Promise<Role | null> {
2024-06-08 05:31:17 +02:00
const found = await db.query.Roles.findFirst({
where: sql,
orderBy,
});
if (!found) {
return null;
}
2024-06-08 05:31:17 +02:00
return new Role(found);
}
public static async getAll(): Promise<Role[]> {
return (await Role.manyFromSql(undefined)).concat(
Role.defaultRole,
Role.adminRole,
);
}
public static async getUserRoles(
userId: string,
isAdmin: boolean,
): Promise<Role[]> {
2024-06-08 05:31:17 +02:00
return (
await db.query.RoleToUsers.findMany({
where: (role): SQL | undefined => eq(role.userId, userId),
2024-06-08 05:31:17 +02:00
with: {
role: true,
user: {
columns: {
isAdmin: true,
},
},
2024-06-08 05:31:17 +02:00
},
})
)
.map((r) => new Role(r.role))
.concat(
new Role({
id: "default",
name: "Default",
permissions: config.permissions.default,
priority: 0,
description: "Default role for all users",
visible: false,
icon: null,
}),
)
.concat(
isAdmin
? [
new Role({
id: "admin",
name: "Admin",
permissions: config.permissions.admin,
priority: 2 ** 31 - 1,
description:
"Default role for all administrators",
visible: false,
icon: null,
}),
]
: [],
);
2024-06-08 05:31:17 +02:00
}
public static async manyFromSql(
sql: SQL<unknown> | undefined,
orderBy: SQL<unknown> | undefined = desc(Roles.id),
limit?: number,
offset?: number,
extra?: Parameters<typeof db.query.Roles.findMany>[0],
): Promise<Role[]> {
2024-06-08 05:31:17 +02:00
const found = await db.query.Roles.findMany({
where: sql,
orderBy,
limit,
offset,
with: extra?.with,
});
return found.map((s) => new Role(s));
}
public async update(newRole: Partial<RoleType>): Promise<RoleType> {
await db.update(Roles).set(newRole).where(eq(Roles.id, this.id));
const updated = await Role.fromId(this.data.id);
if (!updated) {
throw new Error("Failed to update role");
}
return updated.data;
2024-06-08 05:31:17 +02:00
}
public save(): Promise<RoleType> {
return this.update(this.data);
2024-06-08 05:31:17 +02:00
}
public async delete(ids?: string[]): Promise<void> {
if (Array.isArray(ids)) {
await db.delete(Roles).where(inArray(Roles.id, ids));
} else {
await db.delete(Roles).where(eq(Roles.id, this.id));
}
}
public static async insert(
data: InferInsertModel<typeof Roles>,
): Promise<Role> {
const inserted = (await db.insert(Roles).values(data).returning())[0];
const role = await Role.fromId(inserted.id);
if (!role) {
throw new Error("Failed to insert role");
}
return role;
2024-06-08 05:31:17 +02:00
}
public async linkUser(userId: string): Promise<void> {
2024-06-08 05:31:17 +02:00
await db.insert(RoleToUsers).values({
userId,
roleId: this.id,
});
}
public async unlinkUser(userId: string): Promise<void> {
2024-06-08 05:31:17 +02:00
await db
.delete(RoleToUsers)
.where(
and(
eq(RoleToUsers.roleId, this.id),
eq(RoleToUsers.userId, userId),
),
);
}
public get id(): string {
return this.data.id;
2024-06-08 05:31:17 +02:00
}
public toApi(): z.infer<typeof RoleSchema> {
2024-06-08 05:31:17 +02:00
return {
id: this.id,
name: this.data.name,
permissions: this.data.permissions as unknown as RolePermission[],
priority: this.data.priority,
description: this.data.description ?? undefined,
visible: this.data.visible,
icon: this.data.icon
? new ProxiableUrl(this.data.icon).proxied
: undefined,
2024-06-08 05:31:17 +02:00
};
}
}