Skip to content

Commit a567edb

Browse files
committed
feat: more role based access work
1 parent fd61fb5 commit a567edb

File tree

11 files changed

+767
-55
lines changed

11 files changed

+767
-55
lines changed

apps/api/src/controllers/roles.ts

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
2+
import { track } from "../lib/hog";
3+
import { requirePermission } from "../lib/roles";
4+
import { checkSession } from "../lib/session";
5+
import { prisma } from "../prisma";
6+
7+
export function roleRoutes(fastify: FastifyInstance) {
8+
// Create a new role
9+
fastify.post(
10+
"/api/v1/role/create",
11+
{
12+
preHandler: requirePermission(['role::create']),
13+
},
14+
async (request: FastifyRequest, reply: FastifyReply) => {
15+
const user = await checkSession(request);
16+
const { name, description, permissions, isDefault }: any = request.body;
17+
18+
const existingRole = await prisma.role.findUnique({
19+
where: { name },
20+
});
21+
22+
if (existingRole) {
23+
return reply.status(400).send({
24+
message: "Role already exists",
25+
success: false
26+
});
27+
}
28+
29+
await prisma.role.create({
30+
data: {
31+
name,
32+
description,
33+
permissions,
34+
isDefault: isDefault || false,
35+
},
36+
});
37+
38+
const client = track();
39+
client.capture({
40+
event: "role_created",
41+
distinctId: "uuid",
42+
});
43+
client.shutdownAsync();
44+
45+
reply.status(200).send({ message: "Role created!", success: true });
46+
}
47+
);
48+
49+
// Get all roles
50+
fastify.get(
51+
"/api/v1/roles/all",
52+
{
53+
preHandler: requirePermission(['role::read']),
54+
},
55+
async (request: FastifyRequest, reply: FastifyReply) => {
56+
const roles = await prisma.role.findMany({
57+
include: {
58+
users: true,
59+
},
60+
});
61+
62+
reply.status(200).send({ roles, success: true });
63+
}
64+
);
65+
66+
// Get role by ID
67+
fastify.get(
68+
"/api/v1/role/:id",
69+
{
70+
preHandler: requirePermission(['role::read']),
71+
},
72+
async (request: FastifyRequest, reply: FastifyReply) => {
73+
const { id }: any = request.params;
74+
75+
const role = await prisma.role.findUnique({
76+
where: { id },
77+
include: {
78+
users: true,
79+
},
80+
});
81+
82+
if (!role) {
83+
return reply.status(404).send({
84+
message: "Role not found",
85+
success: false
86+
});
87+
}
88+
89+
reply.status(200).send({ role, success: true });
90+
}
91+
);
92+
93+
// Update role
94+
fastify.put(
95+
"/api/v1/role/:id/update",
96+
{
97+
preHandler: requirePermission(['role::update']),
98+
},
99+
async (request: FastifyRequest, reply: FastifyReply) => {
100+
const { id }: any = request.params;
101+
const { name, description, permissions, isDefault }: any = request.body;
102+
103+
try {
104+
const updatedRole = await prisma.role.update({
105+
where: { id },
106+
data: {
107+
name,
108+
description,
109+
permissions,
110+
isDefault,
111+
updatedAt: new Date(),
112+
},
113+
});
114+
115+
reply.status(200).send({ role: updatedRole, success: true });
116+
} catch (error: any) {
117+
if (error.code === 'P2025') {
118+
return reply.status(404).send({
119+
message: "Role not found",
120+
success: false
121+
});
122+
}
123+
throw error;
124+
}
125+
}
126+
);
127+
128+
// Delete role
129+
fastify.delete(
130+
"/api/v1/role/:id/delete",
131+
{
132+
preHandler: requirePermission(['role::delete']),
133+
},
134+
async (request: FastifyRequest, reply: FastifyReply) => {
135+
const { id }: any = request.params;
136+
137+
try {
138+
await prisma.role.delete({
139+
where: { id },
140+
});
141+
142+
reply.status(200).send({ success: true });
143+
} catch (error: any) {
144+
if (error.code === 'P2025') {
145+
return reply.status(404).send({
146+
message: "Role not found",
147+
success: false
148+
});
149+
}
150+
throw error;
151+
}
152+
}
153+
);
154+
155+
// Assign role to user
156+
fastify.post(
157+
"/api/v1/role/assign",
158+
{
159+
// preHandler: requirePermission(['role::assign']),
160+
},
161+
async (request: FastifyRequest, reply: FastifyReply) => {
162+
const { userId, roleId }: any = request.body;
163+
164+
try {
165+
const updatedUser = await prisma.user.update({
166+
where: { id: userId },
167+
data: {
168+
roles: {
169+
connect: { id: roleId },
170+
},
171+
},
172+
include: {
173+
roles: true,
174+
},
175+
});
176+
177+
reply.status(200).send({ user: updatedUser, success: true });
178+
} catch (error: any) {
179+
if (error.code === 'P2025') {
180+
return reply.status(404).send({
181+
message: "User or Role not found",
182+
success: false
183+
});
184+
}
185+
throw error;
186+
}
187+
}
188+
);
189+
190+
// Remove role from user
191+
fastify.post(
192+
"/api/v1/role/remove",
193+
{
194+
// preHandler: requirePermission(['role::remove']),
195+
},
196+
async (request: FastifyRequest, reply: FastifyReply) => {
197+
const { userId, roleId }: any = request.body;
198+
199+
try {
200+
const updatedUser = await prisma.user.update({
201+
where: { id: userId },
202+
data: {
203+
roles: {
204+
disconnect: { id: roleId },
205+
},
206+
},
207+
include: {
208+
roles: true,
209+
},
210+
});
211+
212+
reply.status(200).send({ user: updatedUser, success: true });
213+
} catch (error: any) {
214+
if (error.code === 'P2025') {
215+
return reply.status(404).send({
216+
message: "User or Role not found",
217+
success: false
218+
});
219+
}
220+
throw error;
221+
}
222+
}
223+
);
224+
}

apps/api/src/controllers/webhooks.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export function webhookRoutes(fastify: FastifyInstance) {
99
fastify.post(
1010
"/api/v1/webhook/create",
1111
{
12-
preHandler: requirePermission(['webhook:create']),
12+
preHandler: requirePermission(["webhook::create"]),
1313
},
1414
async (request: FastifyRequest, reply: FastifyReply) => {
1515
const user = await checkSession(request);
@@ -42,7 +42,7 @@ export function webhookRoutes(fastify: FastifyInstance) {
4242
fastify.get(
4343
"/api/v1/webhooks/all",
4444
{
45-
preHandler: requirePermission(['webhook:read']),
45+
preHandler: requirePermission(["webhook::read"]),
4646
},
4747
async (request: FastifyRequest, reply: FastifyReply) => {
4848
const webhooks = await prisma.webhooks.findMany({});
@@ -55,7 +55,7 @@ export function webhookRoutes(fastify: FastifyInstance) {
5555
fastify.delete(
5656
"/api/v1/admin/webhook/:id/delete",
5757
{
58-
preHandler: requirePermission(['webhook:delete']),
58+
preHandler: requirePermission(["webhook::delete"]),
5959
},
6060
async (request: FastifyRequest, reply: FastifyReply) => {
6161
const { id }: any = request.params;

apps/api/src/lib/roles.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ export function hasPermission(
3636
// Add permissions from default role if it exists
3737
const defaultRole = user.roles.find((role) => role.isDefault);
3838
if (defaultRole) {
39-
defaultRole.permissions.forEach((perm) => userPermissions.add(perm as Permission));
39+
defaultRole.permissions.forEach((perm) =>
40+
userPermissions.add(perm as Permission)
41+
);
4042
}
4143

4244
// Add permissions from additional roles
@@ -80,6 +82,11 @@ export function requirePermission(
8082
})
8183
: null;
8284

85+
// Admins have all permissions
86+
if (user?.isAdmin) {
87+
next();
88+
}
89+
8390
if (!userWithRoles) {
8491
throw new Error("User not authenticated");
8592
}

0 commit comments

Comments
 (0)