feat: add repo funcs

This commit is contained in:
Henry
2026-01-26 14:38:44 +00:00
parent 087f7fe216
commit 9415daecd3
3 changed files with 407 additions and 1 deletions

View File

@@ -55,6 +55,12 @@ export const getByPublicId = async (db: dbClient, publicId: string) => {
});
};
export const getById = async (db: dbClient, memberId: number) => {
return db.query.workspaceMembers.findFirst({
where: eq(workspaceMembers.id, memberId),
});
};
export const getByEmailAndStatus = async (
db: dbClient,
email: string,
@@ -133,3 +139,28 @@ export const pauseAllMembers = async (db: dbClient, workspaceId: number) => {
),
);
};
export const updateRole = async (
db: dbClient,
args: {
memberId: number;
role: MemberRole;
roleId: number | null;
},
) => {
const [result] = await db
.update(workspaceMembers)
.set({
role: args.role,
roleId: args.roleId,
updatedAt: new Date(),
})
.where(eq(workspaceMembers.id, args.memberId))
.returning({
id: workspaceMembers.id,
publicId: workspaceMembers.publicId,
role: workspaceMembers.role,
});
return result;
};

View File

@@ -0,0 +1,332 @@
import { and, eq, isNull } from "drizzle-orm";
import type { dbClient } from "@kan/db/client";
import {
workspaceMemberPermissions,
workspaceMembers,
workspaceRolePermissions,
workspaceRoles,
} from "@kan/db/schema";
import type { Permission, Role } from "@kan/shared";
import { generateUID, getDefaultPermissions } from "@kan/shared";
/**
* Get permissions by role ID
*/
export const getPermissionsByRoleId = async (
db: dbClient,
roleId: number,
): Promise<Permission[]> => {
const permissions = await db
.select({ permission: workspaceRolePermissions.permission })
.from(workspaceRolePermissions)
.where(
and(
eq(workspaceRolePermissions.workspaceRoleId, roleId),
eq(workspaceRolePermissions.granted, true),
),
);
return permissions.map((p) => p.permission as Permission);
};
/**
* Get role by workspace ID and name
*/
export const getRoleByWorkspaceIdAndName = async (
db: dbClient,
workspaceId: number,
name: string,
) => {
const [role] = await db
.select()
.from(workspaceRoles)
.where(
and(
eq(workspaceRoles.workspaceId, workspaceId),
eq(workspaceRoles.name, name),
),
)
.limit(1);
return role;
};
/**
* Get all custom permission overrides for a workspace member
*/
export const getMemberPermissionOverrides = async (
db: dbClient,
workspaceMemberId: number,
) => {
return db
.select()
.from(workspaceMemberPermissions)
.where(eq(workspaceMemberPermissions.workspaceMemberId, workspaceMemberId));
};
/**
* Get effective permissions for a workspace member
* Combines role template (from DB) with custom overrides
*/
export const getMemberEffectivePermissions = async (
db: dbClient,
workspaceMemberId: number,
roleId: number | null,
roleName: string,
): Promise<Permission[]> => {
let roleDefaults: Set<Permission>;
// Try to get role permissions from database first
if (roleId) {
const dbPermissions = await getPermissionsByRoleId(db, roleId);
roleDefaults = new Set<Permission>(dbPermissions);
} else {
// Fallback to code-based defaults if roleId not set
const codeDefaults = getDefaultPermissions(roleName as Role);
roleDefaults = new Set<Permission>([...codeDefaults]);
}
// Get custom overrides
const overrides = await getMemberPermissionOverrides(db, workspaceMemberId);
// Apply overrides
for (const override of overrides) {
if (override.granted) {
roleDefaults.add(override.permission as Permission);
} else {
roleDefaults.delete(override.permission as Permission);
}
}
return Array.from(roleDefaults);
};
/**
* Check if a member has a specific permission
*/
export const memberHasPermission = async (
db: dbClient,
workspaceMemberId: number,
roleId: number | null,
roleName: string,
permission: Permission,
): Promise<boolean> => {
let hasRoleDefault: boolean;
// Check role permission from database first
if (roleId) {
const dbPermissions = await getPermissionsByRoleId(db, roleId);
hasRoleDefault = dbPermissions.includes(permission);
} else {
// Fallback to code-based defaults
const codeDefaults = getDefaultPermissions(roleName as Role);
hasRoleDefault = codeDefaults.includes(permission);
}
// Check for override
const [override] = await db
.select()
.from(workspaceMemberPermissions)
.where(
and(
eq(workspaceMemberPermissions.workspaceMemberId, workspaceMemberId),
eq(workspaceMemberPermissions.permission, permission),
),
)
.limit(1);
// Override takes precedence
if (override) {
return override.granted;
}
return hasRoleDefault;
};
/**
* Grant a permission to a member
*/
export const grantPermission = async (
db: dbClient,
workspaceMemberId: number,
permission: Permission,
) => {
const [result] = await db
.insert(workspaceMemberPermissions)
.values({
workspaceMemberId,
permission,
granted: true,
})
.onConflictDoUpdate({
target: [
workspaceMemberPermissions.workspaceMemberId,
workspaceMemberPermissions.permission,
],
set: {
granted: true,
updatedAt: new Date(),
},
})
.returning();
return result;
};
/**
* Revoke a permission from a member
*/
export const revokePermission = async (
db: dbClient,
workspaceMemberId: number,
permission: Permission,
) => {
const [result] = await db
.insert(workspaceMemberPermissions)
.values({
workspaceMemberId,
permission,
granted: false,
})
.onConflictDoUpdate({
target: [
workspaceMemberPermissions.workspaceMemberId,
workspaceMemberPermissions.permission,
],
set: {
granted: false,
updatedAt: new Date(),
},
})
.returning();
return result;
};
/**
* Get member with their role by userId and workspaceId
*/
export const getMemberWithRole = async (
db: dbClient,
userId: string,
workspaceId: number,
) => {
const [member] = await db
.select({
id: workspaceMembers.id,
publicId: workspaceMembers.publicId,
role: workspaceMembers.role,
roleId: workspaceMembers.roleId,
})
.from(workspaceMembers)
.where(
and(
eq(workspaceMembers.userId, userId),
eq(workspaceMembers.workspaceId, workspaceId),
isNull(workspaceMembers.deletedAt),
),
)
.limit(1);
return member;
};
/**
* Check if user has permission in workspace
*/
export const userHasPermissionInWorkspace = async (
db: dbClient,
userId: string,
workspaceId: number,
permission: Permission,
): Promise<boolean> => {
const member = await getMemberWithRole(db, userId, workspaceId);
if (!member) {
return false;
}
return memberHasPermission(
db,
member.id,
member.roleId,
member.role,
permission,
);
};
/**
* Get all permissions for a user in a workspace
*/
export const getUserPermissionsInWorkspace = async (
db: dbClient,
userId: string,
workspaceId: number,
): Promise<{
permissions: Permission[];
role: string;
roleId: number | null;
} | null> => {
const member = await getMemberWithRole(db, userId, workspaceId);
if (!member) {
return null;
}
const permissions = await getMemberEffectivePermissions(
db,
member.id,
member.roleId,
member.role,
);
return {
permissions,
role: member.role,
roleId: member.roleId,
};
};
/**
* Create a role for a workspace
*/
export const createRole = async (
db: dbClient,
args: {
workspaceId: number;
name: string;
description: string;
hierarchyLevel: number;
isSystem: boolean;
permissions: Permission[];
},
) => {
const [role] = await db
.insert(workspaceRoles)
.values({
publicId: generateUID(),
workspaceId: args.workspaceId,
name: args.name,
description: args.description,
hierarchyLevel: args.hierarchyLevel,
isSystem: args.isSystem,
})
.returning({
id: workspaceRoles.id,
name: workspaceRoles.name,
publicId: workspaceRoles.publicId,
});
if (role && args.permissions.length > 0) {
await db.insert(workspaceRolePermissions).values(
args.permissions.map((perm) => ({
workspaceRoleId: role.id,
permission: perm,
granted: true,
})),
);
}
return role;
};

View File

@@ -18,7 +18,33 @@ import {
workspaceMembers,
workspaces,
} from "@kan/db/schema";
import { generateUID } from "@kan/shared/utils";
import type { Permission, Role } from "@kan/shared";
import { generateUID, getDefaultPermissions } from "@kan/shared";
import * as permissionRepo from "./permission.repo";
// System role definitions
const SYSTEM_ROLES: {
name: Role;
description: string;
hierarchyLevel: number;
}[] = [
{
name: "admin",
description: "Full access to all workspace features",
hierarchyLevel: 100,
},
{
name: "member",
description: "Standard member with create and edit permissions",
hierarchyLevel: 50,
},
{
name: "guest",
description: "View-only access",
hierarchyLevel: 10,
},
];
export const getCount = async (db: dbClient) => {
const result = await db
@@ -57,6 +83,22 @@ export const create = async (
});
if (workspace) {
// Create system roles for the workspace
let adminRoleId: number | null = null;
for (const roleData of SYSTEM_ROLES) {
const role = await permissionRepo.createRole(db, {
workspaceId: workspace.id,
name: roleData.name,
description: roleData.description,
hierarchyLevel: roleData.hierarchyLevel,
isSystem: true,
permissions: [...getDefaultPermissions(roleData.name)] as Permission[],
});
if (roleData.name === "admin" && role) {
adminRoleId = role.id;
}
}
await db.insert(workspaceMembers).values({
publicId: generateUID(),
userId: workspaceInput.createdBy,
@@ -64,6 +106,7 @@ export const create = async (
workspaceId: workspace.id,
createdBy: workspaceInput.createdBy,
role: "admin",
roleId: adminRoleId,
status: "active",
});
}