diff --git a/app/lib/rbac.ts b/app/lib/rbac.ts index 1d3c5a3..254553e 100644 --- a/app/lib/rbac.ts +++ b/app/lib/rbac.ts @@ -29,8 +29,10 @@ export const {can} = canCant<'guest' | 'user' | 'manager' | 'admin'>({ 'entry:*', 'asset-manager:*', 'field-manager:*', + 'user-manager:*', 'dashboard', - 'search' + 'search', + 'logout' ] } }) as { diff --git a/app/lib/utils/ensure-user.tsx b/app/lib/utils/ensure-user.tsx index 41708ea..535054f 100644 --- a/app/lib/utils/ensure-user.tsx +++ b/app/lib/utils/ensure-user.tsx @@ -19,7 +19,7 @@ export const ensureUser = async ( const cookieSession = await prisma.session.findFirst({ where: {id: cookie}, - include: {user: true} + include: {user: {select: {id: true, name: true, role: true}}} }) if (!cookieSession) { @@ -39,5 +39,5 @@ export const ensureUser = async ( }) } - return cookieSession.user + return {...cookieSession.user, sessionId: cookieSession.id} } diff --git a/app/routes/api.me.tsx b/app/routes/api.me.tsx new file mode 100644 index 0000000..0eb683c --- /dev/null +++ b/app/routes/api.me.tsx @@ -0,0 +1,11 @@ +import {type LoaderFunctionArgs, json} from '@remix-run/node' + +import {ensureUser} from '~/lib/utils/ensure-user' + +export const loader = async ({request}: LoaderFunctionArgs) => { + const user = await ensureUser(request, 'dashboard', {}) + + console.log('HIYA') + + return json({user}) +} diff --git a/app/routes/app.logout.tsx b/app/routes/app.logout.tsx new file mode 100644 index 0000000..e7e3006 --- /dev/null +++ b/app/routes/app.logout.tsx @@ -0,0 +1,21 @@ +import {redirect, type LoaderFunctionArgs} from '@remix-run/node' + +import {ensureUser} from '~/lib/utils/ensure-user' +import {session} from '~/lib/cookies' +import {getPrisma} from '~/lib/prisma.server' + +export const loader = async ({request}: LoaderFunctionArgs) => { + const {sessionId} = await ensureUser(request, 'logout', {}) + + const prisma = getPrisma() + + await prisma.session.delete({where: {id: sessionId}}) + + return redirect(`/app/login`, { + headers: { + 'Set-Cookie': await session.serialize('', { + maxAge: 1 + }) + } + }) +} diff --git a/app/routes/app.tsx b/app/routes/app.tsx index f045e48..038bd46 100644 --- a/app/routes/app.tsx +++ b/app/routes/app.tsx @@ -21,6 +21,8 @@ export const loader = async ({request}: LoaderFunctionArgs) => { return json({user, assets}) } +export type AppLoader = {user: {name: string; id: string}} + const Dashboard = () => { const {assets} = useLoaderData() @@ -45,8 +47,13 @@ const Dashboard = () => {

System

- Asset Manager - Field Manager + 📦 Asset Manager + 🚜 Field Manager + 👤 User Manager +
+

User

+
+ 👋 Logout
diff --git a/app/routes/app.user-manager.$user.tsx b/app/routes/app.user-manager.$user.tsx new file mode 100644 index 0000000..4cc2bf8 --- /dev/null +++ b/app/routes/app.user-manager.$user.tsx @@ -0,0 +1,93 @@ +import { + type LoaderFunctionArgs, + type ActionFunctionArgs, + json, + redirect +} from '@remix-run/node' +import {useLoaderData} from '@remix-run/react' +import {invariant} from '@arcath/utils' + +import {ensureUser} from '~/lib/utils/ensure-user' +import {getPrisma} from '~/lib/prisma.server' +import {Button} from '~/lib/components/button' +import {Label, Input, Select} from '~/lib/components/input' +import {hashPassword} from '~/lib/user.server' + +export const loader = async ({request, params}: LoaderFunctionArgs) => { + const currentUser = await ensureUser(request, 'user-manager:edit', { + userId: params.user + }) + + const prisma = getPrisma() + + const user = await prisma.user.findFirstOrThrow({where: {id: params.user}}) + + return json({currentUser, user}) +} + +export const action = async ({request, params}: ActionFunctionArgs) => { + const currentUser = await ensureUser(request, 'user-manager:edit', { + userId: params.user + }) + + const prisma = getPrisma() + + const formData = await request.formData() + + const name = formData.get('name') as string | undefined + const email = formData.get('email') as string | undefined + const password = formData.get('password') as string | undefined + const role = formData.get('role') as string | undefined + + invariant(name) + invariant(email) + invariant(role) + + let newPassword: string | undefined = undefined + + if (password) { + newPassword = await hashPassword(password) + } + + const user = await prisma.user.update({ + where: {id: params.user}, + data: {name, email, passwordHash: newPassword, role} + }) + + return redirect(`/app/user-manager/${user.id}`) +} + +const UserManagerUser = () => { + const {user} = useLoaderData() + + return ( +
+

{user.name}

+
+ + + + + +
+
+ ) +} + +export default UserManagerUser diff --git a/app/routes/app.user-manager._index.tsx b/app/routes/app.user-manager._index.tsx new file mode 100644 index 0000000..5483454 --- /dev/null +++ b/app/routes/app.user-manager._index.tsx @@ -0,0 +1,53 @@ +import {type LoaderFunctionArgs, json} from '@remix-run/node' +import {useLoaderData} from '@remix-run/react' + +import {ensureUser} from '~/lib/utils/ensure-user' +import {getPrisma} from '~/lib/prisma.server' +import {AButton} from '~/lib/components/button' + +export const loader = async ({request}: LoaderFunctionArgs) => { + const user = await ensureUser(request, 'user-manager:list', {}) + + const prisma = getPrisma() + + const users = await prisma.user.findMany({ + select: {id: true, name: true, email: true}, + orderBy: {name: 'asc'} + }) + + return json({user, users}) +} + +const UserManagerList = () => { + const {users} = useLoaderData() + + return ( +
+ + Add User + + + + + + + + + + {users.map(({id, name, email}) => { + return ( + + + + + ) + })} + +
NameEmail
+ {name} + {email}
+
+ ) +} + +export default UserManagerList diff --git a/app/routes/app.user-manager.add.tsx b/app/routes/app.user-manager.add.tsx new file mode 100644 index 0000000..357b350 --- /dev/null +++ b/app/routes/app.user-manager.add.tsx @@ -0,0 +1,81 @@ +import { + type LoaderFunctionArgs, + type ActionFunctionArgs, + json, + redirect +} from '@remix-run/node' +import {useLoaderData} from '@remix-run/react' +import {invariant} from '@arcath/utils' + +import {ensureUser} from '~/lib/utils/ensure-user' +import {getPrisma} from '~/lib/prisma.server' +import {Button} from '~/lib/components/button' +import {Label, Input, Select} from '~/lib/components/input' +import {hashPassword} from '~/lib/user.server' + +export const loader = async ({request, params}: LoaderFunctionArgs) => { + const currentUser = await ensureUser(request, 'user-manager:edit', { + userId: params.user + }) + + return json({currentUser}) +} + +export const action = async ({request, params}: ActionFunctionArgs) => { + await ensureUser(request, 'user-manager:add', { + userId: params.user + }) + + const prisma = getPrisma() + + const formData = await request.formData() + + const name = formData.get('name') as string | undefined + const email = formData.get('email') as string | undefined + const password = formData.get('password') as string | undefined + const role = formData.get('role') as string | undefined + + invariant(name) + invariant(email) + invariant(password) + invariant(role) + + const user = await prisma.user.create({ + data: {name, email, passwordHash: await hashPassword(password), role} + }) + + return redirect(`/app/user-manager/${user.id}`) +} + +const UserManagerAdd = () => { + return ( +
+

Add User

+
+ + + + + +
+
+ ) +} + +export default UserManagerAdd diff --git a/app/routes/app.user-manager.tsx b/app/routes/app.user-manager.tsx new file mode 100644 index 0000000..7729339 --- /dev/null +++ b/app/routes/app.user-manager.tsx @@ -0,0 +1,14 @@ +import {Outlet} from '@remix-run/react' + +import {Header} from '~/lib/components/header' + +const UserManager = () => { + return ( +
+
+ +
+ ) +} + +export default UserManager