2025-03-22 18:04:47 +01:00
|
|
|
import type { Role as RoleSchema } from "@versia/client/schemas";
|
|
|
|
|
import type { RolePermission } from "@versia/client/schemas";
|
2024-11-01 21:05:54 +01:00
|
|
|
import { db } from "@versia/kit/db";
|
2025-04-10 19:15:31 +02:00
|
|
|
import { Roles, RoleToUsers } from "@versia/kit/tables";
|
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";
|
2025-03-29 03:30:06 +01:00
|
|
|
import type { z } from "zod";
|
2025-02-15 02:47:29 +01:00
|
|
|
import { config } from "~/config.ts";
|
2025-03-30 23:44:50 +02:00
|
|
|
import { ProxiableUrl } from "../media/url.ts";
|
2024-10-04 15:22:48 +02:00
|
|
|
import { BaseInterface } from "./base.ts";
|
2025-04-10 19:15:31 +02:00
|
|
|
|
2024-11-04 14:58:17 +01:00
|
|
|
type RoleType = InferSelectModel<typeof Roles>;
|
2024-06-13 02:45:07 +02:00
|
|
|
|
|
|
|
|
export class Role extends BaseInterface<typeof Roles> {
|
2024-11-04 14:58:17 +01:00
|
|
|
public static $type: RoleType;
|
2025-03-30 23:44:50 +02:00
|
|
|
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,
|
|
|
|
|
});
|
2024-11-04 14:58:17 +01:00
|
|
|
|
2024-11-01 21:20:12 +01:00
|
|
|
public async reload(): Promise<void> {
|
2024-06-13 02:45:07 +02:00
|
|
|
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> {
|
2024-06-13 04:26:43 +02:00
|
|
|
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),
|
2024-11-02 00:43:33 +01:00
|
|
|
): Promise<Role | null> {
|
2024-06-08 05:31:17 +02:00
|
|
|
const found = await db.query.Roles.findFirst({
|
|
|
|
|
where: sql,
|
|
|
|
|
orderBy,
|
|
|
|
|
});
|
|
|
|
|
|
2024-06-13 04:26:43 +02:00
|
|
|
if (!found) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2024-06-08 05:31:17 +02:00
|
|
|
return new Role(found);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-26 15:27:39 +01:00
|
|
|
public static async getAll(): Promise<Role[]> {
|
|
|
|
|
return (await Role.manyFromSql(undefined)).concat(
|
2025-03-30 23:44:50 +02:00
|
|
|
Role.defaultRole,
|
|
|
|
|
Role.adminRole,
|
2024-11-26 15:27:39 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-02 00:43:33 +01:00
|
|
|
public static async getUserRoles(
|
|
|
|
|
userId: string,
|
|
|
|
|
isAdmin: boolean,
|
|
|
|
|
): Promise<Role[]> {
|
2024-06-08 05:31:17 +02:00
|
|
|
return (
|
|
|
|
|
await db.query.RoleToUsers.findMany({
|
2024-11-02 00:43:33 +01:00
|
|
|
where: (role, { eq }): SQL | undefined =>
|
|
|
|
|
eq(role.userId, userId),
|
2024-06-08 05:31:17 +02:00
|
|
|
with: {
|
|
|
|
|
role: true,
|
2024-06-10 04:14:36 +02:00
|
|
|
user: {
|
|
|
|
|
columns: {
|
|
|
|
|
isAdmin: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-06-08 05:31:17 +02:00
|
|
|
},
|
|
|
|
|
})
|
2024-06-10 04:14:36 +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,
|
2024-06-08 06:57:29 +02:00
|
|
|
extra?: Parameters<typeof db.query.Roles.findMany>[0],
|
2024-11-02 00:43:33 +01:00
|
|
|
): 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));
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-01 21:20:12 +01:00
|
|
|
public async update(newRole: Partial<RoleType>): Promise<RoleType> {
|
2024-06-13 02:45:07 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2024-11-01 21:20:12 +01:00
|
|
|
public save(): Promise<RoleType> {
|
2024-06-13 02:45:07 +02:00
|
|
|
return this.update(this.data);
|
2024-06-08 05:31:17 +02:00
|
|
|
}
|
|
|
|
|
|
2024-11-01 21:20:12 +01:00
|
|
|
public async delete(ids?: string[]): Promise<void> {
|
2024-06-13 02:45:07 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2024-11-02 00:43:33 +01: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,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-02 00:43:33 +01:00
|
|
|
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),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-02 00:43:33 +01:00
|
|
|
public get id(): string {
|
2024-06-13 02:45:07 +02:00
|
|
|
return this.data.id;
|
2024-06-08 05:31:17 +02:00
|
|
|
}
|
|
|
|
|
|
2025-03-22 18:04:47 +01:00
|
|
|
public toApi(): z.infer<typeof RoleSchema> {
|
2024-06-08 05:31:17 +02:00
|
|
|
return {
|
|
|
|
|
id: this.id,
|
2024-06-13 02:45:07 +02:00
|
|
|
name: this.data.name,
|
2024-06-29 08:36:15 +02:00
|
|
|
permissions: this.data.permissions as unknown as RolePermission[],
|
2024-06-13 02:45:07 +02:00
|
|
|
priority: this.data.priority,
|
2024-11-28 10:54:44 +01:00
|
|
|
description: this.data.description ?? undefined,
|
2024-06-13 02:45:07 +02:00
|
|
|
visible: this.data.visible,
|
2025-02-01 16:32:18 +01:00
|
|
|
icon: this.data.icon
|
2025-03-30 23:44:50 +02:00
|
|
|
? new ProxiableUrl(this.data.icon).proxied
|
2025-02-01 16:32:18 +01:00
|
|
|
: undefined,
|
2024-06-08 05:31:17 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|