Skip to content

Commit

Permalink
feat: finish off in-person training, revoking it and infractions
Browse files Browse the repository at this point in the history
  • Loading branch information
Gobot1234 committed Apr 11, 2024
1 parent 746aee7 commit 10bbe06
Show file tree
Hide file tree
Showing 10 changed files with 411 additions and 232 deletions.
20 changes: 16 additions & 4 deletions apps/anvil/src/users/dto/users.dto.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import {
CreateUserSchema,
UpdateUserSchema,
} from "@dbschema/edgedb-zod/modules/users";
import { 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) {}
export class CreateInfractionDto extends createZodDto(CreateInfractionSchema) {}

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

export class AddInPersonTrainingDto extends createZodDto(AddInPersonTrainingSchema) {}

export const RevokeTrainingSchema = z.object({
reason: z.string(),
});

export class RevokeTrainingDto extends createZodDto(RevokeTrainingSchema) {}
49 changes: 39 additions & 10 deletions apps/anvil/src/users/users.controller.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import { CheckAbilities } from "@/auth/authorization/decorators/check-abilities-decorator";
import { IsAdmin } from "@/auth/authorization/decorators/check-roles-decorator";
import { CaslAbilityGuard } from "@/auth/authorization/guards/casl-ability.guard";
import { TrainingService } from "@/training/training.service";
import type { UpdateUserSchema } from "@dbschema/edgedb-zod/modules/users";
import { users } from "@ignis/types";
import type { Training, User } from "@ignis/types/users";
import { Body, Controller, Delete, Get, NotFoundException, Param, Patch, Post, UseGuards } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import type { z } from "zod";
import { User as GetUser } from "../shared/decorators/user.decorator";
import type { CreateUserDto, UpdateUserDto } from "./dto/users.dto";
import type {
AddInPersonTrainingDto,
CreateInfractionDto,
CreateUserDto,
RevokeTrainingDto,
UpdateUserDto,
} from "./dto/users.dto";
import { UsersService } from "./users.service";
import { users } from "@ignis/types";

@Controller("users")
@UseGuards(AuthGuard("jwt"), CaslAbilityGuard)
export class UsersController {
constructor(
private readonly usersService: UsersService,
private readonly trainingService: TrainingService,
) {}
constructor(private readonly usersService: UsersService) {}

@Post()
@CheckAbilities(["CREATE"], "USER")
Expand All @@ -32,7 +35,7 @@ export class UsersController {
}

@Get("me") // Get own user data
// @CheckAbilities(["READ"], "SELF")
@CheckAbilities(["READ"], "SELF")
async findSelf(@GetUser() user: User) {
return user;
}
Expand All @@ -54,7 +57,7 @@ export class UsersController {
}

@Get(":id") // Get any user's data (admin/higher permission)
// @CheckAbilities(['READ'], 'USER')
@CheckAbilities(["READ"], "USER")
async findOne(@Param("id") id: string) {
const user = await this.usersService.findOne(id);
if (!user) {
Expand Down Expand Up @@ -87,8 +90,34 @@ export class UsersController {
return this.usersService.getUserTrainingInPersonTrainingRemaining(id);
}

@Post(":id/training/:training_id")
@IsAdmin()
async addTraining(
@Param("id") id: string,
@Param("training_id") training_id: string,
@Body() data: AddInPersonTrainingDto,
) {
return this.usersService.addInPersonTraining(id, training_id, data);
}

@Delete(":id/training/:training_id")
@CheckAbilities(["READ"], "USER")
async revokeTraining(
@Param("id") id: string,
@Param("training_id") training_id: string,
@Body() data: RevokeTrainingDto,
) {
return this.usersService.revokeTraining(id, training_id, data);
}

@Post(":id/infractions")
@IsAdmin()
async addInfraction(@Param("id") id: string, @Body() data: CreateInfractionDto) {
return this.usersService.addInfraction(id, data);
}

@Patch(":id/promote/:teamid")
@CheckAbilities(["UPDATE"], "USER")
@IsAdmin()
async promoteUser(@Param("id") id: string, @Param("teamid") teamid: string) {
return this.usersService.promoteUserToRep(id, teamid);
}
Expand Down
50 changes: 40 additions & 10 deletions apps/anvil/src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ import { GoogleUser } from "@/auth/interfaces/google-user.interface";
import { LdapUser } from "@/auth/interfaces/ldap-user.interface";
import { EdgeDBService } from "@/edgedb/edgedb.service";
import { LdapService } from "@/ldap/ldap.service";
import { CreateUserSchema, UpdateUserSchema } from "@dbschema/edgedb-zod/modules/users";
import e from "@dbschema/edgeql-js";
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, InvalidValueError } from "edgedb";
import { z } from "zod";
import { CardinalityViolationError, ConstraintViolationError, Duration, InvalidValueError } from "edgedb";
import {
AddInPersonTrainingDto,
CreateInfractionDto,
CreateUserDto,
RevokeTrainingDto,
UpdateUserDto,
} from "./dto/users.dto";

export const PartialUserProps = e.shape(e.users.User, () => ({
// Fairly minimal, useful for templating
Expand Down Expand Up @@ -91,7 +96,7 @@ export class UsersService {

/** Insert a user into the database. Take extra care that the ucard_number is a valid thing to insert */
async create(
createUserDto: Omit<z.infer<typeof CreateUserSchema>, "ucard_number"> & {
createUserDto: Omit<CreateUserDto, "ucard_number"> & {
ucard_number: any;
},
): Promise<User> {
Expand Down Expand Up @@ -156,7 +161,7 @@ export class UsersService {
);
}

async update(id: string, updateUserDto: z.infer<typeof UpdateUserSchema>): Promise<void> {
async update(id: string, updateUserDto: UpdateUserDto): Promise<void> {
try {
await this.dbService.query(
e.assert_exists(
Expand Down Expand Up @@ -343,7 +348,7 @@ export class UsersService {
);
}

async addInPersonTraining(id: string, training_id: string, rep_id: string, created_at: Date) {
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(
Expand All @@ -353,16 +358,16 @@ export class UsersService {
training: e.select(u.training, (t) => ({
filter_single: e.op(t.id, "=", training_id),
"@created_at": t["@created_at"],
"@in_person_created_at": created_at,
"@in_person_signed_off_by": rep_id,
"@in_person_created_at": data.created_at,
"@in_person_signed_off_by": data.rep_id,
})),
},
})),
),
);
}

async revokeTraining(id: string, training_id: string) {
async revokeTraining(id: string, training_id: string, data: RevokeTrainingDto) {
await this.dbService.query(
e.delete(e.training.UserTrainingSession, (session) => ({
filter_single: e.all(
Expand All @@ -371,11 +376,19 @@ export class UsersService {
e.op(
session.user,
"=",
e.update(e.users.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 },
})),
Expand All @@ -386,6 +399,23 @@ export class UsersService {
);
}

async addInfraction(id: string, data: CreateInfractionDto) {
await this.dbService.query(
e.update(e.users.User, (user) => ({
set: {
infractions: {
"+=": e.insert(e.users.Infraction, {
user,
...data,
duration: Duration.from(data.duration!),
}),
},
},
filter_single: { id },
})),
);
}

async promoteUserToRep(id: string, teamId: string, status: RepStatus = "ACTIVE") {
// Assuming you have a method to check if a user is already a Rep
const isAlreadyRep = await this.isRep(id);
Expand Down
1 change: 1 addition & 0 deletions apps/forge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"postcss": "^8.4.38",
"react": "^18.2.0",
"react-cookie": "^6.1.3",
"react-day-picker": "^8.10.0",
"react-dom": "^18.2.0",
"react-helmet-async": "^2.0.4",
"react-hook-form": "^7.51.2",
Expand Down
Loading

0 comments on commit 10bbe06

Please sign in to comment.