Skip to content

Commit

Permalink
🎉 manage users
Browse files Browse the repository at this point in the history
  • Loading branch information
casperiv0 committed Oct 10, 2021
1 parent d3e69ff commit 1aafeec
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 14 deletions.
37 changes: 30 additions & 7 deletions packages/api/src/controllers/admin/manage/Users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { PathParams, BodyParams, Context } from "@tsed/common";
import { Controller } from "@tsed/di";
import { BadRequest, NotFound } from "@tsed/exceptions";
import { UseBeforeEach } from "@tsed/platform-middlewares";
import { Get, JsonRequestBody, Post } from "@tsed/schema";
import { Get, JsonRequestBody, Post, Put } from "@tsed/schema";
import { userProperties } from "../../../lib/auth";
import { prisma } from "../../../lib/prisma";
import { IsAuth, IsAdmin } from "../../../middlewares";
import { BAN_SCHEMA, validate } from "@snailycad/schemas";
import { BAN_SCHEMA, UPDATE_USER_SCHEMA, validate } from "@snailycad/schemas";

@UseBeforeEach(IsAuth, IsAdmin)
@Controller("/users")
Expand All @@ -26,17 +26,41 @@ export class ManageUsersController {
return user;
}

@Post("/:id")
@Put("/:id")
async updateUserById(@PathParams("id") userId: string, @BodyParams() body: JsonRequestBody) {
const error = validate(UPDATE_USER_SCHEMA, body.toJSON(), true);

if (error) {
throw new BadRequest(error);
}

const user = await prisma.user.findUnique({ where: { id: userId } });
body;

if (!user) {
throw new NotFound("notFound");
}

return "TODO";
if (user.rank === Rank.OWNER && body.get("rank") !== Rank.OWNER) {
throw new BadRequest("cannotUpdateOwnerRank");
}

const updated = await prisma.user.update({
where: {
id: user.id,
},
data: {
isLeo: body.get("isLeo"),
isSupervisor: body.get("isSupervisor"),
isDispatch: body.get("isDispatch"),
isEmsFd: body.get("isEmsFd"),
isTow: body.get("isTow"),
steamId: body.get("steamId"),
rank: user.rank === Rank.OWNER ? Rank.OWNER : Rank[body.get("rank") as Rank],
},
select: userProperties,
});

// return user;
return updated;
}

@Post("/:id/:type")
Expand All @@ -58,7 +82,6 @@ export class ManageUsersController {
}

const user = await prisma.user.findUnique({ where: { id: userId } });

if (!user) {
throw new NotFound("notFound");
}
Expand Down
4 changes: 4 additions & 0 deletions packages/api/src/controllers/auth/Auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export class AuthController {
throw new BadRequest("whitelistDeclined");
}

if (user.banned) {
throw new BadRequest("userBanned");
}

const isPasswordCorrect = compareSync(body.get("password"), user.password);
if (!isPasswordCorrect) {
throw new BadRequest("passwordIncorrect");
Expand Down
3 changes: 2 additions & 1 deletion packages/client/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"userAlreadyExists": "A user with that username already exists.",
"notFound": "The requested item was not found.",
"plateAlreadyInUse": "That plate is already being used on a vehicle.",
"invalidImageType": "Only types image/png, image/jpeg, image/jpg and image/jpg are supported"
"invalidImageType": "Only types image/png, image/jpeg, image/jpg and image/jpg are supported",
"userBanned": "This account is banned from this CAD."
}
}
2 changes: 1 addition & 1 deletion packages/client/src/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ const NavDropdown = () => {

return (
<>
<Menu as="div" className="relative inline-block text-left">
<Menu as="div" className="relative inline-block text-left z-50">
<Menu.Button className="inline-flex justify-center w-full px-1 py-2 text-sm font-medium text-white bg-transparent rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
<PersonCircle fill="#2f2f2f" width={20} height={20} />
</Menu.Button>
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/components/admin/AdminLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const AdminLayout = ({ className, children }: Props) => {
<>
<Nav />

<main className={`relative mt-5 px-4 container max-w-6xl mx-auto flex ${className}`}>
<main className={`relative z-10 mt-5 px-4 container max-w-6xl mx-auto flex ${className}`}>
<div className="w-60">
<AdminSidebar />
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/components/form/FormRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const FormRow = ({
...rest
}: Props) => {
const cols = Array.isArray(children)
? `grid grid-cols-1 sm:grid-cols-${children.length / 2} md:grid-cols-${children.length}`
? `grid grid-cols-1 sm:grid-cols-2 md:grid-cols-${children.length}`
: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4";

return (
Expand Down
28 changes: 25 additions & 3 deletions packages/client/src/pages/admin/manage/users/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { useTranslations } from "use-intl";
import Link from "next/link";
import { useRouter } from "next/router";
import Head from "next/head";
import { Formik } from "formik";
import { UPDATE_USER_SCHEMA } from "@snailycad/schemas";
import { getSessionUser } from "lib/auth";
import { handleRequest } from "lib/fetch";
import { getTranslations } from "lib/getTranslation";
import { GetServerSideProps } from "next";
import type { User } from "types/prisma";
import { AdminLayout } from "components/admin/AdminLayout";
import { Formik } from "formik";
import { FormField } from "components/form/FormField";
import { Select } from "components/form/Select";
import { Error } from "components/form/Error";
Expand All @@ -20,6 +21,8 @@ import useFetch from "lib/useFetch";
import { Toggle } from "components/form/Toggle";
import { FormRow } from "components/form/FormRow";
import { BanArea } from "components/admin/manage/BanArea";
import { handleValidate } from "lib/handleValidate";
import { Input } from "components/form/Input";

interface Props {
user: User | null;
Expand Down Expand Up @@ -58,11 +61,14 @@ export default function ManageCitizens(props: Props) {
rank: user.rank,
isDispatch: user.isDispatch,
isLeo: user.isLeo,
isSupervisor: user.isSupervisor,
isEmsFd: user.isEmsFd,
isTow: user.isTow,
steamId: user.steamId ?? "",
};

const isRankDisabled = user.rank === "OWNER" || user.id === session?.id;
const validate = handleValidate(UPDATE_USER_SCHEMA);

return (
<AdminLayout>
Expand All @@ -73,7 +79,7 @@ export default function ManageCitizens(props: Props) {
<h1 className="text-3xl font-semibold">{user?.username}</h1>

<div className="mt-5">
<Formik onSubmit={onSubmit} initialValues={INITIAL_VALUES}>
<Formik validate={validate} onSubmit={onSubmit} initialValues={INITIAL_VALUES}>
{({ handleChange, handleSubmit, isValid, values, errors }) => (
<form onSubmit={handleSubmit}>
<FormField label="Rank">
Expand All @@ -95,13 +101,23 @@ export default function ManageCitizens(props: Props) {
<Error>{errors.rank}</Error>
</FormField>

<FormRow className="mt-5">
<FormRow flexLike className="mt-5">
<FormField label="Leo Access">
<Toggle name="isLeo" onClick={handleChange} toggled={values.isLeo} />

<Error>{errors.isLeo}</Error>
</FormField>

<FormField label="LEO Supervisor">
<Toggle
name="isSupervisor"
onClick={handleChange}
toggled={values.isSupervisor}
/>

<Error>{errors.isSupervisor}</Error>
</FormField>

<FormField label="Dispatch Access">
<Toggle name="isDispatch" onClick={handleChange} toggled={values.isDispatch} />

Expand All @@ -121,6 +137,12 @@ export default function ManageCitizens(props: Props) {
</FormField>
</FormRow>

<FormField label="Steam ID">
<Input name="steamId" onChange={handleChange} value={values.steamId} />

<Error>{errors.steamId}</Error>
</FormField>

<div className="flex justify-end">
<Link href="/admin/manage/users">
<a>
Expand Down
12 changes: 12 additions & 0 deletions packages/schemas/src/admin/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { z } from "zod";

const RANK_REGEX = /OWNER|ADMIN|MODERATOR|USER/;

export const CAD_SETTINGS_SCHEMA = z.object({
name: z.string().min(2).max(255),
areaOfPlay: z.string().max(255),
Expand All @@ -12,3 +14,13 @@ export const CAD_SETTINGS_SCHEMA = z.object({
export const BAN_SCHEMA = z.object({
reason: z.string().min(2).max(255),
});

export const UPDATE_USER_SCHEMA = z.object({
rank: z.string().min(2).max(255).regex(RANK_REGEX),
isLeo: z.boolean(),
isEmsFd: z.boolean(),
isDispatch: z.boolean(),
isTow: z.boolean(),
isSupervisor: z.boolean(),
steamId: z.string().max(255),
});
62 changes: 62 additions & 0 deletions production.docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
version: "3.9"

services:
postgres:
container_name: "snaily-cad-postgres"
image: postgres
env_file:
- .env
ports:
- "5433:5432"
networks:
- cad_web
volumes:
- ./.data:/var/lib/postgresql/data

api:
container_name: "snaily-cad-api"

user: node
image: node:16
command: "yarn workspace @snailycad/api start"
ports:
- "8080:8080"
expose:
- 8080
volumes:
- .:/srv/api
env_file:
- .env
working_dir: /srv/api
depends_on:
- postgres
networks:
- cad_web

client:
container_name: "snaily-cad-client"

user: node
image: node:16
command: "yarn workspace @snailycad/client start"
ports:
- "3000:3000"
expose:
- 3000
volumes:
- .:/srv/client
env_file:
- .env
working_dir: /srv/client
depends_on:
- postgres
networks:
- cad_web

volumes:
redis-data:
redis-conf:

networks:
cad_web:
external: true

0 comments on commit 1aafeec

Please sign in to comment.