Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
Sampiiiii committed Apr 12, 2024
2 parents 9fc846d + 67fdf20 commit 7d3690c
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 70 deletions.
3 changes: 3 additions & 0 deletions apps/anvil/dbschema/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ Regeneration:
- `npx @edgedb/generate edgeql-js` - regenerate the query builder generator.
- `pnpm edgedb-zod` - regenerates the Zod schema for the DB.

**These Must Be Extended If You Use `z.date` (use `z.string().datetime()`)**

If you want to run all of these at once, `pnpm regen`.

## Some comments 🥁

Inline comments are why things were done, the `annotation description := "..."` comments are specifically on schema and representation.

19 changes: 19 additions & 0 deletions apps/anvil/dbschema/queries/addInPersonTraining.edgeql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
with rep := (
select users::Rep
filter .id = <uuid>$rep_id
),
user := (
update users::User
filter .id = <uuid>$id
set {
training := (
select .training {
@created_at := @created_at,
@in_person_created_at := <datetime>$created_at,
@in_person_signed_off_by := assert_exists(rep.id),
}
filter .id = <uuid>$training_id
)
}
)
select assert_exists(user);
38 changes: 38 additions & 0 deletions apps/anvil/dbschema/queries/addInPersonTraining.query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// GENERATED by @edgedb/generate v0.5.2

import type {Executor} from "edgedb";

export type AddInPersonTrainingArgs = {
readonly "rep_id": string;
readonly "training_id": string;
readonly "created_at": Date;
readonly "id": string;
};

export type AddInPersonTrainingReturns = {
"id": string;
};

export function addInPersonTraining(client: Executor, args: AddInPersonTrainingArgs): Promise<AddInPersonTrainingReturns> {
return client.queryRequiredSingle(`\
with rep := (
select users::Rep
filter .id = <uuid>$rep_id
),
user := (
update users::User
filter .id = <uuid>$id
set {
training := (
select .training {
@created_at := @created_at,
@in_person_created_at := <datetime>$created_at,
@in_person_signed_off_by := assert_exists(rep.id),
}
filter .id = <uuid>$training_id
)
}
)
select assert_exists(user);`, args);

}
12 changes: 10 additions & 2 deletions apps/anvil/src/users/dto/users.dto.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { CreateInfractionSchema, CreateUserSchema, UpdateUserSchema } from "@dbschema/edgedb-zod/modules/users";
import {
CreateInfractionSchema as CreateInfractionSchema_,
CreateUserSchema,
UpdateUserSchema,
} from "@dbschema/edgedb-zod/modules/users";
import { createZodDto } from "nestjs-zod";
import { z } from "zod";

export class CreateUserDto extends createZodDto(CreateUserSchema) {}
export class UpdateUserDto extends createZodDto(UpdateUserSchema) {}

const CreateInfractionSchema = CreateInfractionSchema_.omit({ duration: true }).extend({
duration: z.number().optional(),
});
export class CreateInfractionDto extends createZodDto(CreateInfractionSchema) {}

export const AddInPersonTrainingSchema = z.object({
rep_id: z.string(),
created_at: z.date(),
created_at: z.string().datetime(),
});

export class AddInPersonTrainingDto extends createZodDto(AddInPersonTrainingSchema) {}
Expand Down
86 changes: 42 additions & 44 deletions apps/anvil/src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@ import { LdapUser } from "@/auth/interfaces/ldap-user.interface";
import { EdgeDBService } from "@/edgedb/edgedb.service";
import { LdapService } from "@/ldap/ldap.service";
import e from "@dbschema/edgeql-js";
import { addInPersonTraining } from "@dbschema/queries/addInPersonTraining.query";
import { users } from "@ignis/types";
import { Location } from "@ignis/types/sign_in";
import { RepStatus, SignInStat, Training, User } from "@ignis/types/users";
import { ConflictException, Injectable, NotFoundException } from "@nestjs/common";
import { CardinalityViolationError, ConstraintViolationError, Duration, InvalidValueError } from "edgedb";
import {
CardinalityViolationError,
ConstraintViolationError,
Duration,
InvalidValueError,
RelativeDuration,
} from "edgedb";
import { parseHumanDurationString } from "edgedb/dist/datatypes/datetime";
import {
AddInPersonTrainingDto,
CreateInfractionDto,
Expand Down Expand Up @@ -350,68 +358,58 @@ export class UsersService {

async addInPersonTraining(id: string, training_id: string, data: AddInPersonTrainingDto) {
// pre-condition they must already have completed online training otherwise this is a no-op
await this.dbService.query(
e.assert_exists(
e.update(e.users.User, (u) => ({
filter_single: { id },
set: {
training: e.select(u.training, (t) => ({
filter_single: e.op(t.id, "=", training_id),
"@created_at": t["@created_at"],
"@in_person_created_at": data.created_at,
"@in_person_signed_off_by": data.rep_id,
})),
},
})),
),
);
await addInPersonTraining(this.dbService.client, {
id,
training_id,
...data,
created_at: new Date(data.created_at),
});
}

async revokeTraining(id: string, training_id: string, data: RevokeTrainingDto) {
const user = e.assert_exists(e.select(e.users.User, () => ({ filter_single: { id } })));
await this.dbService.query(
e.update(user, () => ({
set: {
training: {
"-=": e.assert_exists(e.select(e.users.User, UserTrainingEntry(id, training_id))).training,
},
infractions: {
"+=": e.insert(e.users.Infraction, {
user,
reason: data.reason,
resolved: true,
type: e.users.InfractionType.TRAINING_ISSUE,
}),
},
},
})),
);
await this.dbService.query(
e.delete(e.training.UserTrainingSession, (session) => ({
filter_single: e.all(
e.set(
e.op(session.training.id, "=", e.cast(e.uuid, training_id)),
e.op(
session.user,
"=",
e.update(e.users.User, (user) => ({
set: {
training: {
"-=": e.assert_exists(e.select(e.users.User, UserTrainingEntry(id, training_id))).training,
},
infractions: {
"+=": e.insert(e.users.Infraction, {
user,
reason: data.reason,
resolved: true,
type: e.users.InfractionType.TRAINING_ISSUE,
}),
},
},
filter_single: { id },
})),
),
),
e.set(e.op(session.training.id, "=", e.cast(e.uuid, training_id)), e.op(session.user, "=", user)),
),
})),
);
}

async addInfraction(id: string, data: CreateInfractionDto) {
await this.dbService.query(
e.update(e.users.User, (user) => ({
const user = e.assert_exists(e.select(e.users.User, () => ({ filter_single: { id } })));
return await this.dbService.query(
e.update(user, () => ({
set: {
infractions: {
"+=": e.insert(e.users.Infraction, {
user,
...data,
duration: Duration.from(data.duration!),
created_at: data.created_at,
reason: data.reason,
resolved: data.resolved,
type: data.type,
duration: data.duration ? new Duration(0, 0, 0, 0, data.duration) : undefined,
}),
},
},
filter_single: { id },
})),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { Separator } from "@ui/components/ui/separator";
import { Textarea } from "@ui/components/ui/textarea";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@ui/components/ui/tooltip";
import { cn } from "@ui/lib/utils";
import { addDays, format } from "date-fns";
import { addDays, addMonths, format, startOfDay } from "date-fns";
import { CalendarIcon, LogOut, Plus } from "lucide-react";
import * as React from "react";
import { DateRange } from "react-day-picker";
Expand Down Expand Up @@ -116,7 +116,14 @@ const TrainingSection: React.FC<AddToUserProps> = ({ user, location, onShiftReps
<div className="flex justify-center">
<Button
type="submit"
onClick={() => addInPersonTraining(user.id, training!, { rep_id: repSigningOff!, created_at: date! })}
onClick={() => {
try {
addInPersonTraining(user.id, training!, { rep_id: repSigningOff!, created_at: date! });
} catch (e) {
return toast.error(`Failed to submit contact the IT Team ${e}`);
}
toast.success("Successfully submitted");
}}
disabled={!(training && date && repSigningOff)}
>
Add
Expand All @@ -126,8 +133,9 @@ const TrainingSection: React.FC<AddToUserProps> = ({ user, location, onShiftReps
);
};

const InfractionSection: React.FC<Omit<AddToUserProps, "onShiftReps">> = ({ user, location }) => {
const [type, setInfractionType] = React.useState<InfractionType>("WARNING");
const InfractionSection: React.FC<AddToUserProps> = ({ user, location, onShiftReps }) => {
// TODO auto log onShiftReps
const [type, setType] = React.useState<InfractionType>("WARNING");
const [reason, setReason] = React.useState<string>("");
const [resolved, setResolved] = React.useState<boolean>(true);

Expand All @@ -136,16 +144,23 @@ const InfractionSection: React.FC<Omit<AddToUserProps, "onShiftReps">> = ({ user
queryKey: ["userTraining", user.id],
queryFn: () => getUserTraining(user.id),
});

const now = new Date();
const [date, setDate] = React.useState<DateRange | undefined>({
from: new Date(),
to: addDays(new Date(), 7),
from: now,
to: addDays(now, 7),
});

let extra_field = (
<div className="flex items-center space-x-2">
<Checkbox id={"resolved-checkbox"} onCheckedChange={() => setResolved((oldValue) => !oldValue)} />
<div className="m-2 flex items-center space-x-2">
<Checkbox
id={"resolved-checkbox"}
defaultChecked={true}
required
onCheckedChange={() => setResolved((oldValue) => !oldValue)}
/>
<Label htmlFor={"resolved-checkbox"} className="hover:cursor-pointer">
Is the issue resolved?
<text>Resolved? (untick if needs investigation)</text>
</Label>
</div>
);
Expand All @@ -155,22 +170,20 @@ const InfractionSection: React.FC<Omit<AddToUserProps, "onShiftReps">> = ({ user
type,
resolved,
reason,
created_at: date!.from!,
created_at: date?.from || new Date(),
duration:
date!.from && date!.to
? `${Math.round(date!.from!.getTime() - date!.to!.getTime() / 1000 / 60 / 60 / 24)}d`
: undefined,
type === "TEMP_BAN" ? Math.round((date!.to!.getTime() - date!.from!.getTime()) / 1000 / 60 / 60) : undefined,
});

switch (type) {
case "TEMP_BAN":
case "TEMP_BAN": // FIXME this insta break
buttonDisabled = !(type && date?.from && date?.to);
extra_field = (
<>
{extra_field}
<div className="m-2">
<Label>Duration</Label>
<DatePickerWithRange date={date} setDate={setDate} />
<DatePickerWithRange date={date} setDate={setDate} disabled={(date) => date < startOfDay(now)} />
</div>
</>
);
Expand Down Expand Up @@ -207,8 +220,15 @@ const InfractionSection: React.FC<Omit<AddToUserProps, "onShiftReps">> = ({ user
<>
<div className="m-2">
<Label>Type</Label>
{/* @ts-ignore: setInfractionType should always be safe */}
<Select required onValueChange={setInfractionType}>
<Select
required
onValueChange={(value) => {
setTrainingToRevoke(undefined);
setDate(undefined);
// @ts-ignore: setInfractionType should always be safe
setType(value);
}}
>
<SelectTrigger className="w-[280px]">
<SelectValue placeholder="Type of infraction" />
</SelectTrigger>
Expand All @@ -226,6 +246,7 @@ const InfractionSection: React.FC<Omit<AddToUserProps, "onShiftReps">> = ({ user
<Label htmlFor="message">Reason</Label>
<Textarea
required
className="w-[280px]"
placeholder="Please include a little summary of the issue."
id="message"
onChange={(e) => setReason(e.target.value)}
Expand Down Expand Up @@ -255,7 +276,9 @@ const sectionComponents: Record<Addable, (props: AddToUserProps) => React.ReactE
Training: ({ user, location, onShiftReps }) => (
<TrainingSection user={user} location={location} onShiftReps={onShiftReps} />
),
Infraction: ({ user, location }) => <InfractionSection user={user} location={location} />,
Infraction: ({ user, location, onShiftReps }) => (
<InfractionSection user={user} location={location} onShiftReps={onShiftReps} />
),
};

const AddToUser: React.FC<AddToUserProps> = ({ user, location, onShiftReps }) => {
Expand Down
2 changes: 1 addition & 1 deletion apps/forge/src/services/users/addInPersonTraining.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type AddInPersonTrainingDto = {

export default async function (id: string, training_id: string, data: AddInPersonTrainingDto): Promise<Training[]> {
try {
const { data: resp } = await axiosInstance.post(`/users/${id}/training/${training_id}`, { data });
const { data: resp } = await axiosInstance.post(`/users/${id}/training/${training_id}`, data);
return resp;
} catch (error) {
console.error(`Error fetching trainings for ${id}: ${error}`);
Expand Down
4 changes: 2 additions & 2 deletions apps/forge/src/services/users/addInfraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import axiosInstance from "@/api/axiosInstance";

type CreateInfractionDto = {
created_at: Date;
duration?: string;
duration?: number; // duration in days
reason: string;
resolved: boolean;
type: string;
};

export default async function addInfraction(id: string, data: CreateInfractionDto) {
try {
const { data: resp } = await axiosInstance.post(`/users/${id}/infractions`, { data });
const { data: resp } = await axiosInstance.post(`/users/${id}/infractions`, data);
return resp;
} catch (error) {
console.error(`Error adding infraction for ${id}: ${error}`);
Expand Down
Loading

0 comments on commit 7d3690c

Please sign in to comment.