Skip to content
This repository was archived by the owner on Nov 29, 2025. It is now read-only.

Commit a130e4d

Browse files
authored
Merge pull request #15 from MicroPyramid/dev
Refactor user management pages: consolidate user addition and organiz…
2 parents 7ac4c99 + 2ae59c0 commit a130e4d

File tree

8 files changed

+472
-691
lines changed

8 files changed

+472
-691
lines changed

src/app.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
declare global {
44
namespace App {
55
// interface Error {}
6-
// interface Locals {}
6+
interface Locals {
7+
user?: any; // You might want to replace 'any' with a more specific type for user
8+
org?: any; // You might want to replace 'any' with a more specific type for org
9+
org_name?: string;
10+
}
711
// interface PageData {}
812
// interface PageState {}
913
// interface Platform {}

src/hooks.server.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,13 @@ export async function handle({ event, resolve }) {
4646
event.locals.org_name = userOrg.organization.name;
4747
}
4848
} else {
49-
// User doesn't have access to this organization, redirect to logout
50-
throw redirect(307, '/logout');
49+
// User doesn't have access to this organization or orgId is stale.
50+
// Clear the invalid org cookies.
51+
event.cookies.delete('org', { path: '/' });
52+
event.cookies.delete('org_name', { path: '/' });
53+
// Redirect to the organization selection page.
54+
// The user is still authenticated.
55+
throw redirect(307, '/org');
5156
}
5257
}
5358
}
Lines changed: 221 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,222 @@
1+
import prisma from '$lib/prisma'
2+
import { fail, redirect } from '@sveltejs/kit';
3+
14
/** @type {import('./$types').PageServerLoad} */
2-
export async function load() {
3-
return {};
4-
};
5+
export async function load({ params, locals }) {
6+
const org_id = locals.org.id; // Changed from params.org_id
7+
const user = locals.user;
8+
9+
// Check if user is admin of the organization
10+
const userOrg = await prisma.userOrganization.findFirst({
11+
where: {
12+
userId: user.id,
13+
organizationId: org_id,
14+
role: 'ADMIN'
15+
}
16+
});
17+
if (!userOrg) {
18+
return {
19+
error: {
20+
name: 'You do not have permission to access this organization'
21+
}
22+
};
23+
}
24+
// Fetch organization details
25+
const organization = await prisma.organization.findUnique({
26+
where: {
27+
id: org_id // Changed from params.org_id
28+
}
29+
});
30+
31+
// fetch all users in the organization
32+
const users = await prisma.userOrganization.findMany({
33+
where: {
34+
organizationId: org_id
35+
},
36+
include: {
37+
user: true
38+
}
39+
});
40+
// Pass logged-in user id to page for UI logic
41+
return { organization, users, user: { id: user.id } };
42+
};
43+
44+
/** @type {import('./$types').Actions} */
45+
export const actions = {
46+
update: async ({ request, params, locals }) => {
47+
const org_id = locals.org.id; // Changed from params.org_id
48+
const user = locals.user;
49+
if (!user) return fail(401, { error: 'Unauthorized' });
50+
51+
// Only ADMIN can update
52+
const userOrg = await prisma.userOrganization.findFirst({
53+
where: {
54+
userId: user.id,
55+
organizationId: org_id,
56+
role: 'ADMIN'
57+
}
58+
});
59+
if (!userOrg) return fail(403, { error: 'Forbidden' });
60+
61+
const formData = await request.formData();
62+
const name = formData.get('name')?.toString().trim();
63+
const domain = formData.get('domain')?.toString().trim();
64+
const description = formData.get('description')?.toString().trim();
65+
66+
if (!name) return fail(400, { error: 'Name is required' });
67+
68+
try {
69+
await prisma.organization.update({
70+
where: { id: org_id },
71+
data: {
72+
name,
73+
domain,
74+
description
75+
}
76+
});
77+
// Update locals for the current request so layout reloads with new name
78+
if (name) {
79+
if (locals.org) {
80+
locals.org.name = name;
81+
}
82+
locals.org_name = name;
83+
}
84+
return { success: true };
85+
} catch (err) {
86+
return fail(500, { error: 'Failed to update organization' });
87+
}
88+
},
89+
90+
add_user: async ({ request, params, locals }) => {
91+
const org_id = locals.org.id; // Changed from params.org_id
92+
const user = locals.user;
93+
if (!user) return fail(401, { error: 'Unauthorized' });
94+
95+
// Only ADMIN can add
96+
const userOrg = await prisma.userOrganization.findFirst({
97+
where: {
98+
userId: user.id,
99+
organizationId: org_id,
100+
role: 'ADMIN'
101+
}
102+
});
103+
if (!userOrg) return fail(403, { error: 'Forbidden' });
104+
105+
const formData = await request.formData();
106+
const email = formData.get('email')?.toString().trim().toLowerCase();
107+
const role = formData.get('role')?.toString();
108+
if (!email || !role) return fail(400, { error: 'Email and role are required' });
109+
110+
// Find user by email
111+
const foundUser = await prisma.user.findUnique({ where: { email } });
112+
if (!foundUser) return fail(404, { error: 'No user found with that email' });
113+
114+
// Check if already in org
115+
const already = await prisma.userOrganization.findFirst({
116+
where: { userId: foundUser.id, organizationId: org_id }
117+
});
118+
if (already) return fail(400, { error: 'User already in organization' });
119+
120+
// Add user to org
121+
await prisma.userOrganization.create({
122+
data: {
123+
userId: foundUser.id,
124+
organizationId: org_id,
125+
role
126+
}
127+
});
128+
return { success: true };
129+
},
130+
131+
edit_role: async ({ request, params, locals }) => {
132+
const org_id = locals.org.id; // Changed from params.org_id
133+
const user = locals.user;
134+
if (!user) return fail(401, { error: 'Unauthorized' });
135+
136+
// Only ADMIN can edit
137+
const userOrg = await prisma.userOrganization.findFirst({
138+
where: {
139+
userId: user.id,
140+
organizationId: org_id,
141+
role: 'ADMIN'
142+
}
143+
});
144+
if (!userOrg) return fail(403, { error: 'Forbidden' });
145+
146+
const formData = await request.formData();
147+
const user_id = formData.get('user_id')?.toString();
148+
const role = formData.get('role')?.toString();
149+
if (!user_id || !role) return fail(400, { error: 'User and role are required' });
150+
151+
// Don't allow editing own role (prevent lockout)
152+
if (user_id === user.id) return fail(400, { error: 'You cannot change your own role' });
153+
154+
// Don't allow editing role of the only remaining admin
155+
if (role !== 'ADMIN') {
156+
// Count number of admins in org
157+
const adminCount = await prisma.userOrganization.count({
158+
where: {
159+
organizationId: org_id,
160+
role: 'ADMIN'
161+
}
162+
});
163+
// If target user is admin and only one admin left, prevent demotion
164+
const target = await prisma.userOrganization.findUnique({
165+
where: { userId_organizationId: { userId: user_id, organizationId: org_id } }
166+
});
167+
if (target && target.role === 'ADMIN' && adminCount === 1) {
168+
return fail(400, { error: 'Organization must have at least one admin' });
169+
}
170+
}
171+
172+
await prisma.userOrganization.update({
173+
where: { userId_organizationId: { userId: user_id, organizationId: org_id } },
174+
data: { role }
175+
});
176+
return { success: true };
177+
},
178+
179+
remove_user: async ({ request, params, locals }) => {
180+
const org_id = locals.org.id; // Changed from params.org_id
181+
const user = locals.user;
182+
if (!user) return fail(401, { error: 'Unauthorized' });
183+
184+
// Only ADMIN can remove
185+
const userOrg = await prisma.userOrganization.findFirst({
186+
where: {
187+
userId: user.id,
188+
organizationId: org_id,
189+
role: 'ADMIN'
190+
}
191+
});
192+
if (!userOrg) return fail(403, { error: 'Forbidden' });
193+
194+
const formData = await request.formData();
195+
const user_id = formData.get('user_id')?.toString();
196+
if (!user_id) return fail(400, { error: 'User is required' });
197+
198+
// Don't allow removing self (prevent lockout)
199+
if (user_id === user.id) return fail(400, { error: 'You cannot remove yourself' });
200+
201+
// Don't allow removing the only remaining admin
202+
const target = await prisma.userOrganization.findUnique({
203+
where: { userId_organizationId: { userId: user_id, organizationId: org_id } }
204+
});
205+
if (target && target.role === 'ADMIN') {
206+
const adminCount = await prisma.userOrganization.count({
207+
where: {
208+
organizationId: org_id,
209+
role: 'ADMIN'
210+
}
211+
});
212+
if (adminCount === 1) {
213+
return fail(400, { error: 'Organization must have at least one admin' });
214+
}
215+
}
216+
217+
await prisma.userOrganization.delete({
218+
where: { userId_organizationId: { userId: user_id, organizationId: org_id } }
219+
});
220+
return { success: true };
221+
}
222+
};

0 commit comments

Comments
 (0)