Fix 5 validator blockers in groups/users admin work

1. apps.ts getVisibleAppsForUser: use getEffectiveRoleIds() so admin
   gate honors group_roles inheritance (was direct user_roles only —
   security bypass for group-only admins). Exported helper from
   middlewares/auth.ts.

2. POST /users: switch from RegisterBody to new CreateUserBody schema
   that accepts optional groupIds[]. Validates ids exist; user creation
   + auto-Everyone + requested groups happen in single transaction.
   OpenAPI spec extended; api-zod and api-client-react regenerated.

3. DELETE /users/🆔 400 if req.session.userId === target (no
   self-delete).

4. scripts/src/seed.ts: removed `void inArray;` AI-slop and dropped
   unused inArray import.

5. Locales (ar.json + en.json): added admin.users.col.displayNameAr,
   displayNameEn, language and admin.groups.searchPlaceholder.

Typecheck clean. groups-crud + service-orders + users tests pass.
Pre-existing admin-app-opens-pagination flake unrelated.
Architect re-review: PASS.
This commit is contained in:
riyadhafraa
2026-04-22 08:47:35 +00:00
parent bf0768948e
commit 4595e792e4
10 changed files with 136 additions and 48 deletions
@@ -28,6 +28,19 @@ export interface RegisterBody {
preferredLanguage?: string;
}
export interface CreateUserBody {
username: string;
email: string;
password: string;
/** @nullable */
displayNameAr?: string | null;
/** @nullable */
displayNameEn?: string | null;
preferredLanguage?: string;
/** Initial group memberships in addition to the auto-assigned Everyone group. */
groupIds?: number[];
}
export interface LoginBody {
username: string;
password: string;
+9 -8
View File
@@ -32,6 +32,7 @@ import type {
CreateGroupBody,
CreateServiceBody,
CreateServiceOrderBody,
CreateUserBody,
ErrorResponse,
ForgotPasswordBody,
ForgotPasswordResponse,
@@ -4086,14 +4087,14 @@ export const getCreateUserUrl = () => {
};
export const createUser = async (
registerBody: RegisterBody,
createUserBody: CreateUserBody,
options?: RequestInit,
): Promise<UserProfile> => {
return customFetch<UserProfile>(getCreateUserUrl(), {
...options,
method: "POST",
headers: { "Content-Type": "application/json", ...options?.headers },
body: JSON.stringify(registerBody),
body: JSON.stringify(createUserBody),
});
};
@@ -4104,14 +4105,14 @@ export const getCreateUserMutationOptions = <
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createUser>>,
TError,
{ data: BodyType<RegisterBody> },
{ data: BodyType<CreateUserBody> },
TContext
>;
request?: SecondParameter<typeof customFetch>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createUser>>,
TError,
{ data: BodyType<RegisterBody> },
{ data: BodyType<CreateUserBody> },
TContext
> => {
const mutationKey = ["createUser"];
@@ -4125,7 +4126,7 @@ export const getCreateUserMutationOptions = <
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createUser>>,
{ data: BodyType<RegisterBody> }
{ data: BodyType<CreateUserBody> }
> = (props) => {
const { data } = props ?? {};
@@ -4138,7 +4139,7 @@ export const getCreateUserMutationOptions = <
export type CreateUserMutationResult = NonNullable<
Awaited<ReturnType<typeof createUser>>
>;
export type CreateUserMutationBody = BodyType<RegisterBody>;
export type CreateUserMutationBody = BodyType<CreateUserBody>;
export type CreateUserMutationError = ErrorType<void>;
/**
@@ -4151,14 +4152,14 @@ export const useCreateUser = <
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createUser>>,
TError,
{ data: BodyType<RegisterBody> },
{ data: BodyType<CreateUserBody> },
TContext
>;
request?: SecondParameter<typeof customFetch>;
}): UseMutationResult<
Awaited<ReturnType<typeof createUser>>,
TError,
{ data: BodyType<RegisterBody> },
{ data: BodyType<CreateUserBody> },
TContext
> => {
return useMutation(getCreateUserMutationOptions(options));