feat: add roleEdit and userEdit Zod models for validation

This commit is contained in:
biersoeckli
2025-02-26 18:23:05 +00:00
parent 5a2223f23d
commit 21decda0fe
32 changed files with 654 additions and 24 deletions

View File

@@ -0,0 +1,50 @@
-- CreateTable
CREATE TABLE "Role" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"description" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
-- CreateTable
CREATE TABLE "RoleAppPermission" (
"id" TEXT NOT NULL PRIMARY KEY,
"roleId" TEXT NOT NULL,
"appId" TEXT NOT NULL,
"permission" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "RoleAppPermission_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "Role" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "RoleAppPermission_appId_fkey" FOREIGN KEY ("appId") REFERENCES "App" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_User" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT,
"email" TEXT NOT NULL,
"emailVerified" DATETIME,
"password" TEXT NOT NULL,
"twoFaSecret" TEXT,
"twoFaEnabled" BOOLEAN NOT NULL DEFAULT false,
"image" TEXT,
"roleId" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "User_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "Role" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_User" ("createdAt", "email", "emailVerified", "id", "image", "name", "password", "twoFaEnabled", "twoFaSecret", "updatedAt") SELECT "createdAt", "email", "emailVerified", "id", "image", "name", "password", "twoFaEnabled", "twoFaSecret", "updatedAt" FROM "User";
DROP TABLE "User";
ALTER TABLE "new_User" RENAME TO "User";
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
-- CreateIndex
CREATE UNIQUE INDEX "Role_name_key" ON "Role"("name");
-- CreateIndex
CREATE UNIQUE INDEX "RoleAppPermission_roleId_appId_key" ON "RoleAppPermission"("roleId", "appId");

View File

@@ -42,7 +42,6 @@ export default function S3TargetsTable({ targets }: {
["updatedAt", "Updated At", false, (item) => formatDateTime(item.updatedAt)],
]}
data={targets}
onItemClickLink={(item) => `/project/${item.id}`}
actionCol={(item) =>
<>
<div className="flex">

View File

@@ -0,0 +1,54 @@
'use server'
import { SuccessActionResult } from "@/shared/model/server-action-error-return.model";
import { getAuthUserSession, saveFormAction, simpleAction } from "@/server/utils/action-wrapper.utils";
import { ServiceException } from "@/shared/model/service.exception.model";
import userService from "@/server/services/user.service";
import { UserEditModel, userEditZodModel } from "@/shared/model/user-edit.model";
import roleService from "@/server/services/role.service";
import { RoleEditModel, roleEditZodModel } from "@/shared/model/role-edit.model";
export const saveUser = async (prevState: any, inputData: UserEditModel) =>
saveFormAction(inputData, userEditZodModel, async (validatedData) => {
const { email } = await getAuthUserSession(); // check admin permission
if (validatedData.email === email) {
throw new ServiceException('Please edit your profile in the profile settings');
}
if (validatedData.id) {
if (!!validatedData.newPassword) {
await userService.changePasswordImediately(validatedData.email, validatedData.newPassword);
}
await userService.updateUser({
roleId: validatedData.roleId,
email: validatedData.email
});
} else {
if (!validatedData.newPassword || validatedData.newPassword.split(' ').join('').length === 0) {
throw new ServiceException('The password is required');
}
await userService.registerUser(validatedData.email, validatedData.newPassword, validatedData.roleId);
}
return new SuccessActionResult();
});
export const saveRole = async (prevState: any, inputData: RoleEditModel) =>
saveFormAction(inputData, roleEditZodModel, async (validatedData) => {
const { email } = await getAuthUserSession(); // check admin permission
await roleService.save(validatedData);
return new SuccessActionResult();
});
export const deleteUser = async (userId: string) =>
simpleAction(async () => {
await getAuthUserSession(); // todo check admin permission
await userService.deleteUserById(userId);
return new SuccessActionResult();
});
export const deleteRole = async (roleId: string) =>
simpleAction(async () => {
await getAuthUserSession(); // todo check admin permission
await roleService.deleteById(roleId);
return new SuccessActionResult();
});

View File

@@ -0,0 +1,50 @@
'use server'
import { getAuthUserSession } from "@/server/utils/action-wrapper.utils";
import PageTitle from "@/components/custom/page-title";
import S3TargetEditOverlay from "./user-edit-overlay";
import { Button } from "@/components/ui/button";
import BreadcrumbSetter from "@/components/breadcrumbs-setter";
import UsersTable from "./users-table";
import userService from "@/server/services/user.service";
import roleService from "@/server/services/role.service";
import UserEditOverlay from "./user-edit-overlay";
import { CircleUser, Plus, User, UserRoundCog } from "lucide-react";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs"
import RolesTable from "./roles-table";
export default async function S3TargetsPage() {
await getAuthUserSession(); // todo only admins
const users = await userService.getAllUsers();
const roles = await roleService.getAll();
return (
<div className="flex-1 space-y-4 pt-6">
<PageTitle
title={'Users & Roles'} >
</PageTitle>
<BreadcrumbSetter items={[
{ name: "Settings", url: "/settings/profile" },
{ name: "Users & Roles" },
]} />
<Tabs defaultValue="users" >
<TabsList className="">
<TabsTrigger className="px-8 gap-1.5" value="users"><CircleUser className="w-3.5 h-3.5" /> Users</TabsTrigger>
<TabsTrigger className="px-8 gap-1.5" value="roles"><UserRoundCog className="w-3.5 h-3.5"/> Roles</TabsTrigger>
</TabsList>
<TabsContent value="users">
<UsersTable users={users} roles={roles} />
</TabsContent>
<TabsContent value="roles">
<RolesTable roles={roles} />
</TabsContent>
</Tabs>
</div>
)
}

View File

@@ -0,0 +1,107 @@
'use client'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { useFormState } from 'react-dom'
import { useEffect, useState } from "react";
import { FormUtils } from "@/frontend/utils/form.utilts";
import { SubmitButton } from "@/components/custom/submit-button";
import { ServerActionResult } from "@/shared/model/server-action-error-return.model"
import { toast } from "sonner"
import { ScrollArea } from "@/components/ui/scroll-area"
import { saveRole } from "./actions"
import { RoleExtended } from "@/shared/model/role-extended.model.ts"
import { RoleEditModel, roleEditZodModel } from "@/shared/model/role-edit.model"
export default function RoleEditOverlay({ children, role }: {
children: React.ReactNode;
role?: RoleExtended;
}) {
const [isOpen, setIsOpen] = useState<boolean>(false);
const form = useForm<RoleEditModel>({
resolver: zodResolver(roleEditZodModel),
defaultValues: role
});
const [state, formAction] = useFormState((state: ServerActionResult<any, any>,
payload: RoleEditModel) =>
saveRole(state, {
...payload,
id: role?.id
}), FormUtils.getInitialFormState<typeof roleEditZodModel>());
useEffect(() => {
if (state.status === 'success') {
form.reset();
toast.success('Role saved successfully');
setIsOpen(false);
}
FormUtils.mapValidationErrorsToForm<typeof roleEditZodModel>(state, form);
}, [state]);
useEffect(() => {
if (role) {
form.reset(role);
}
}, [role]);
return (
<>
<div onClick={() => setIsOpen(true)}>
{children}
</div>
<Dialog open={!!isOpen} onOpenChange={(isOpened) => setIsOpen(false)}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>{role?.id ? 'Edit' : 'Create'} Role</DialogTitle>
</DialogHeader>
<ScrollArea className="max-h-[70vh]">
<div className="px-2">
<Form {...form}>
<form action={(e) => form.handleSubmit((data) => {
return formAction(data);
})()}>
<div className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<p className="text-red-500">{state.message}</p>
<SubmitButton>Save</SubmitButton>
</div>
</form>
</Form >
</div>
</ScrollArea>
</DialogContent>
</Dialog>
</>
)
}

View File

@@ -0,0 +1,58 @@
'use client';
import { Button } from "@/components/ui/button";
import { EditIcon, Plus, TrashIcon } from "lucide-react";
import { Toast } from "@/frontend/utils/toast.utils";
import { useConfirmDialog } from "@/frontend/states/zustand.states";
import React from "react";
import { SimpleDataTable } from "@/components/custom/simple-data-table";
import { formatDateTime } from "@/frontend/utils/format.utils";
import { deleteRole } from "./actions";
import { RoleExtended } from "@/shared/model/role-extended.model.ts";
import RoleEditOverlay from "./role-edit-overlay";
export default function RolesTable({ roles }: {
roles: RoleExtended[];
}) {
const { openConfirmDialog: openDialog } = useConfirmDialog();
const asyncDeleteItem = async (id: string) => {
const confirm = await openDialog({
title: "Delete Role",
description: "Do you really want to delete this role? Users with this role will be assigned to no role afterwards. They will not be able to use QuickStack until you reassign a new role to them.",
okButton: "Delete",
});
if (confirm) {
await Toast.fromAction(() => deleteRole(id), 'Deleting Role...', 'Role deleted successfully');
}
};
return <>
<SimpleDataTable columns={[
['id', 'ID', false],
['name', 'Name', true],
['roleReadPermissions', 'Read Permissions', true],
['roleWritePermissions', 'Write Permissions', true],
["createdAt", "Created At", true, (item) => formatDateTime(item.createdAt)],
["updatedAt", "Updated At", false, (item) => formatDateTime(item.updatedAt)],
]}
data={roles}
actionCol={(item) =>
<>
<div className="flex">
<div className="flex-1"></div>
<RoleEditOverlay role={item} >
<Button variant="ghost"><EditIcon /></Button>
</RoleEditOverlay>
<Button variant="ghost" onClick={() => asyncDeleteItem(item.id)}>
<TrashIcon />
</Button>
</div>
</>}
/>
<RoleEditOverlay >
<Button variant="secondary"><Plus /> Create Role</Button>
</RoleEditOverlay>
</>;
}

View File

@@ -0,0 +1,140 @@
'use client'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { useFormState } from 'react-dom'
import { useEffect, useState } from "react";
import { FormUtils } from "@/frontend/utils/form.utilts";
import { SubmitButton } from "@/components/custom/submit-button";
import { S3Target, User } from "@prisma/client"
import { ServerActionResult } from "@/shared/model/server-action-error-return.model"
import { toast } from "sonner"
import { ScrollArea } from "@/components/ui/scroll-area"
import { UserEditModel, userEditZodModel } from "@/shared/model/user-edit.model"
import { UserExtended } from "@/shared/model/user-extended.model"
import { saveUser } from "./actions"
import SelectFormField from "@/components/custom/select-form-field"
import { RoleExtended } from "@/shared/model/role-extended.model.ts"
export default function UserEditOverlay({ children, user, roles }: {
children: React.ReactNode;
roles: RoleExtended[];
user?: UserExtended;
}) {
const [isOpen, setIsOpen] = useState<boolean>(false);
const form = useForm<UserEditModel>({
resolver: zodResolver(userEditZodModel),
defaultValues: user
});
const [state, formAction] = useFormState((state: ServerActionResult<any, any>,
payload: UserEditModel) =>
saveUser(state, {
...payload,
id: user?.id
}), FormUtils.getInitialFormState<typeof userEditZodModel>());
useEffect(() => {
if (state.status === 'success') {
form.reset();
toast.success('User saved successfully');
setIsOpen(false);
}
FormUtils.mapValidationErrorsToForm<typeof userEditZodModel>(state, form);
}, [state]);
useEffect(() => {
if (user) {
form.reset(user);
}
}, [user]);
return (
<>
<div onClick={() => setIsOpen(true)}>
{children}
</div>
<Dialog open={!!isOpen} onOpenChange={(isOpened) => setIsOpen(false)}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>{user?.id ? 'Edit' : 'Create'} User</DialogTitle>
</DialogHeader>
<ScrollArea className="max-h-[70vh]">
<div className="px-2">
<Form {...form}>
<form action={(e) => form.handleSubmit((data) => {
return formAction(data);
})()}>
<div className="space-y-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>E-Mail</FormLabel>
<FormControl>
<Input placeholder="" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<SelectFormField
form={form}
name="roleId"
label="Role"
formDescription={<>
Choose a preconfigured role or create your own in the settings.
</>}
values={roles.map((role) =>
[role.id, `${role.name}`])}
/>
<FormField
control={form.control}
name="newPassword"
render={({ field }) => (
<FormItem>
<FormLabel>New Password {user?.id && <>(optional)</>}</FormLabel>
<FormControl>
<Input type="password" {...field} />
</FormControl>
<FormDescription>
{user?.id && <>Leave empty to keep the old password.</>}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<p className="text-red-500">{state.message}</p>
<SubmitButton>Save</SubmitButton>
</div>
</form>
</Form >
</div>
</ScrollArea>
</DialogContent>
</Dialog>
</>
)
}

View File

@@ -0,0 +1,60 @@
'use client';
import { Button } from "@/components/ui/button";
import { EditIcon, Plus, TrashIcon } from "lucide-react";
import { Toast } from "@/frontend/utils/toast.utils";
import { useConfirmDialog } from "@/frontend/states/zustand.states";
import { User } from "@prisma/client";
import React from "react";
import { SimpleDataTable } from "@/components/custom/simple-data-table";
import { formatDateTime } from "@/frontend/utils/format.utils";
import { UserExtended } from "@/shared/model/user-extended.model";
import UserEditOverlay from "./user-edit-overlay";
import { deleteUser } from "./actions";
import { RoleExtended } from "@/shared/model/role-extended.model.ts";
export default function UsersTable({ users, roles }: {
users: UserExtended[];
roles: RoleExtended[];
}) {
const { openConfirmDialog: openDialog } = useConfirmDialog();
const asyncDeleteItem = async (id: string) => {
const confirm = await openDialog({
title: "Delete User",
description: "Do you really want to delete this user?",
okButton: "Delete",
});
if (confirm) {
await Toast.fromAction(() => deleteUser(id), 'Deleting User...', 'User deleted successfully');
}
};
return <>
<SimpleDataTable columns={[
['id', 'ID', false],
['email', 'Mail', true],
['role.name', 'Role', true],
["createdAt", "Created At", true, (item) => formatDateTime(item.createdAt)],
["updatedAt", "Updated At", false, (item) => formatDateTime(item.updatedAt)],
]}
data={users}
actionCol={(item) =>
<>
<div className="flex">
<div className="flex-1"></div>
<UserEditOverlay user={item} roles={roles}>
<Button variant="ghost"><EditIcon /></Button>
</UserEditOverlay>
<Button variant="ghost" onClick={() => asyncDeleteItem(item.id)}>
<TrashIcon />
</Button>
</div>
</>}
/>
<UserEditOverlay roles={roles}>
<Button variant="secondary"><Plus /> Create User</Button>
</UserEditOverlay>
</>;
}

View File

@@ -17,7 +17,7 @@ import {
SidebarMenuAction,
useSidebar
} from "@/components/ui/sidebar"
import { BookOpen, Boxes, ChartNoAxesCombined, ChevronDown, ChevronRight, ChevronUp, Dot, FolderClosed, History, Info, Plus, Server, Settings, Settings2, User } from "lucide-react"
import { BookOpen, Boxes, ChartNoAxesCombined, ChevronDown, ChevronRight, ChevronUp, Dot, FolderClosed, History, Info, Plus, Server, Settings, Settings2, User, User2 } from "lucide-react"
import Link from "next/link"
import { EditProjectDialog } from "./projects/edit-project-dialog"
import { SidebarLogoutButton } from "./sidebar-logout-button"
@@ -39,6 +39,11 @@ const settingsMenu = [
url: "/settings/profile",
icon: User,
},
{
title: "Users & Roles",
url: "/settings/users",
icon: User2,
},
{
title: "S3 Targets",
url: "/settings/s3-targets",

View File

@@ -23,6 +23,7 @@ export class RoleService {
}
} finally {
revalidateTag(Tags.roles());
revalidateTag(Tags.users());
}
}
@@ -43,6 +44,7 @@ export class RoleService {
});
} finally {
revalidateTag(Tags.roles());
revalidateTag(Tags.users());
}
}
@@ -85,6 +87,19 @@ export class RoleService {
revalidateTag(Tags.users());
}
}
async deleteById(id: string) {
try {
await dataAccess.client.role.delete({
where: {
id
}
});
} finally {
revalidateTag(Tags.roles());
revalidateTag(Tags.users());
}
}
}
const roleService = new RoleService();

View File

@@ -1,4 +1,4 @@
import { User } from "@prisma/client";
import { Prisma, User } from "@prisma/client";
import dataAccess from "../adapter/db.client";
import { revalidateTag, unstable_cache } from "next/cache";
import { Tags } from "../utils/cache-tag-generator.utils";
@@ -6,6 +6,7 @@ import bcrypt from "bcrypt";
import { ServiceException } from "@/shared/model/service.exception.model";
import QRCode from "qrcode";
import * as OTPAuth from "otpauth";
import { UserExtended } from "@/shared/model/user-extended.model";
const saltRounds = 10;
@@ -39,6 +40,36 @@ export class UserService {
}
}
async changePasswordImediately(userMail: string, newPassword: string) {
try {
const hashedPassword = await bcrypt.hash(newPassword, saltRounds);
await dataAccess.client.user.update({
where: {
email: userMail
},
data: {
password: hashedPassword
}
});
} finally {
revalidateTag(Tags.users());
}
}
async updateUser(user: Prisma.UserUncheckedUpdateInput) {
try {
delete (user as any).password;
await dataAccess.client.user.update({
where: {
email: user.email as string
},
data: user
});
} finally {
revalidateTag(Tags.users());
}
}
async maptoDtoUser(user: User) {
return {
email: user.email,
@@ -69,13 +100,14 @@ export class UserService {
}
}
async registerUser(email: string, password: string) {
async registerUser(email: string, password: string, roleId: string | null) {
try {
const hashedPassword = await bcrypt.hash(password, saltRounds);
const user = await dataAccess.client.user.create({
data: {
email,
password: hashedPassword
password: hashedPassword,
roleId
}
});
return user;
@@ -84,8 +116,17 @@ export class UserService {
}
}
async getAllUsers() {
return await unstable_cache(async () => await dataAccess.client.user.findMany(),
async getAllUsers(): Promise<UserExtended[]> {
return await unstable_cache(async () => await dataAccess.client.user.findMany({
select: {
id: true,
email: true,
roleId: true,
createdAt: true,
updatedAt: true,
role: true
}
}),
[Tags.users()], {
tags: [Tags.users()]
})();
@@ -192,6 +233,22 @@ export class UserService {
revalidateTag(Tags.users());
}
}
async deleteUserById(id: string) {
try {
const allUsers = await this.getAllUsers();
if (allUsers.length <= 1) {
throw new ServiceException("You cannot delete the last user");
}
await dataAccess.client.user.delete({
where: {
id
}
});
} finally {
revalidateTag(Tags.users());
}
}
}
const userService = new UserService();

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
import { CompleteUser, RelatedUserModel } from "./index"
export const AccountModel = z.object({

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
import { CompleteProject, RelatedProjectModel, CompleteAppDomain, RelatedAppDomainModel, CompleteAppPort, RelatedAppPortModel, CompleteAppVolume, RelatedAppVolumeModel, CompleteAppFileMount, RelatedAppFileMountModel, CompleteAppBasicAuth, RelatedAppBasicAuthModel, CompleteRoleAppPermission, RelatedRoleAppPermissionModel } from "./index"
export const AppModel = z.object({

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
import { CompleteApp, RelatedAppModel } from "./index"
export const AppBasicAuthModel = z.object({

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
import { CompleteApp, RelatedAppModel } from "./index"
export const AppDomainModel = z.object({

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
import { CompleteApp, RelatedAppModel } from "./index"
export const AppFileMountModel = z.object({

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
import { CompleteApp, RelatedAppModel } from "./index"
export const AppPortModel = z.object({

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
import { CompleteApp, RelatedAppModel, CompleteVolumeBackup, RelatedVolumeBackupModel } from "./index"
export const AppVolumeModel = z.object({

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
import { CompleteUser, RelatedUserModel } from "./index"
export const AuthenticatorModel = z.object({

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
export const ParameterModel = z.object({
name: z.string(),

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
import { CompleteApp, RelatedAppModel } from "./index"
export const ProjectModel = z.object({

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
import { CompleteUser, RelatedUserModel, CompleteRoleAppPermission, RelatedRoleAppPermissionModel } from "./index"
export const RoleModel = z.object({

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
import { CompleteRole, RelatedRoleModel, CompleteApp, RelatedAppModel } from "./index"
export const RoleAppPermissionModel = z.object({

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
import { CompleteVolumeBackup, RelatedVolumeBackupModel } from "./index"
export const S3TargetModel = z.object({

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
import { CompleteUser, RelatedUserModel } from "./index"
export const SessionModel = z.object({

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
import { CompleteRole, RelatedRoleModel, CompleteAccount, RelatedAccountModel, CompleteSession, RelatedSessionModel, CompleteAuthenticator, RelatedAuthenticatorModel } from "./index"
export const UserModel = z.object({

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
export const VerificationTokenModel = z.object({
identifier: z.string(),

View File

@@ -1,5 +1,5 @@
import * as z from "zod"
import * as imports from "../../../../prisma/null"
import { CompleteAppVolume, RelatedAppVolumeModel, CompleteS3Target, RelatedS3TargetModel } from "./index"
export const VolumeBackupModel = z.object({

View File

@@ -0,0 +1,9 @@
import { stringToNumber } from "@/shared/utils/zod.utils";
import { z } from "zod";
export const roleEditZodModel = z.object({
id: z.string().trim().optional(),
name: z.string().trim().min(1),
})
export type RoleEditModel = z.infer<typeof roleEditZodModel>;

View File

@@ -0,0 +1,5 @@
import { Role, RoleAppPermission, User } from "@prisma/client";
export type RoleExtended = Role & {
roleAppPermissions: RoleAppPermission[];
}

View File

@@ -0,0 +1,11 @@
import { stringToNumber } from "@/shared/utils/zod.utils";
import { z } from "zod";
export const userEditZodModel = z.object({
id: z.string().trim().optional(),
email: z.string().trim().min(1),
newPassword: z.string().optional(),
roleId: z.string().trim().nullable(),
})
export type UserEditModel = z.infer<typeof userEditZodModel>;

View File

@@ -0,0 +1,10 @@
import { Role, User } from "@prisma/client";
export type UserExtended = {
id: string;
role: Role | null;
roleId: string | null;
email: string;
createdAt: Date;
updatedAt: Date;
};