Skip to content

Commit

Permalink
Add endpoint for querying all inductee points. (#153)
Browse files Browse the repository at this point in the history
  • Loading branch information
godwinpang authored Nov 16, 2020
1 parent fa19164 commit b73eb40
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 1 deletion.
39 changes: 39 additions & 0 deletions src/controllers/PointsController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { JsonController, Get, UseBefore } from 'routing-controllers';
import { ResponseSchema, OpenAPI } from 'routing-controllers-openapi';

import { AppUserService, AppUserServiceImpl } from '@Services';
import { OfficerAuthMiddleware } from '@Middlewares';
import { MultipleInducteePointsResponse, InducteePointsResponse, AppUserResponse } from '@Payloads';
import { InducteePointsView } from '@Entities';

@JsonController('/api/points')
export class PointsController {
constructor(private appUserService: AppUserService) {}

@Get('/inductees')
@ResponseSchema(MultipleInducteePointsResponse)
@UseBefore(OfficerAuthMiddleware)
@OpenAPI({ security: [{ TokenAuth: [] }] })
async getAllInducteePoints(): Promise<MultipleInducteePointsResponse> {
const points: InducteePointsView[] = await this.appUserService.getAllInducteePoints();

const pointResponses = points.map((point: InducteePointsView) => {
const res = new InducteePointsResponse();
res.points = point.points;

// man i hate this
res.user = point.user;
res.email = point.email;
res.hasMentorshipRequirement = point.hasMentorshipRequirement;
res.hasProfessionalRequirement = point.hasProfessionalRequirement;
return res;
});

const multiplePointResponse = new MultipleInducteePointsResponse();
multiplePointResponse.inducteePoints = pointResponses;

return multiplePointResponse;
}
}

export const PointsControllerImpl = new PointsController(AppUserServiceImpl);
10 changes: 9 additions & 1 deletion src/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@ import { EventController, EventControllerImpl } from './EventController';
import { UserController, UserControllerImpl } from './UserController';
import { AuthController, AuthControllerImpl } from './AuthController';
import { TeapotController, TeapotControllerImpl } from './TeapotController';
import { PointsController, PointsControllerImpl } from './PointsController';

export const controllers = [EventController, UserController, AuthController, TeapotController];
export const controllers = [
EventController,
UserController,
AuthController,
TeapotController,
PointsController,
];

// map from name of controller to an instance
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -12,6 +19,7 @@ controllerMap.set(EventController.name, EventControllerImpl);
controllerMap.set(UserController.name, UserControllerImpl);
controllerMap.set(AuthController.name, AuthControllerImpl);
controllerMap.set(TeapotController.name, TeapotControllerImpl);
controllerMap.set(PointsController.name, PointsControllerImpl);

export const ControllerContainer = {
// controller here is just a class and all classes have names in ts
Expand Down
4 changes: 4 additions & 0 deletions src/entities/InducteePointsView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Event } from './Event';
connection
.createQueryBuilder()
.select('appUser.id', 'user')
.addSelect('appUser.email', 'email')
.addSelect('SUM(attendance.points)', 'points')
.addSelect(
"SUM(CASE WHEN event.type = 'professional' THEN 1 ELSE 0 END)::int::bool",
Expand All @@ -28,6 +29,9 @@ export class InducteePointsView {
@ViewColumn()
user: number;

@ViewColumn()
email: string;

@ViewColumn()
points: number;

Expand Down
45 changes: 45 additions & 0 deletions src/migrations/1605509162887-AddEmailToInducteePointsView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddEmailToInducteePointsView1605509162887 implements MigrationInterface {
name = 'AddEmailToInducteePointsView1605509162887';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DELETE FROM "typeorm_metadata" WHERE "type" = 'VIEW' AND "schema" = $1 AND "name" = $2`,
['public', 'inductee_points_view']
);
await queryRunner.query(`DROP VIEW "inductee_points_view"`);
await queryRunner.query(
`CREATE VIEW "inductee_points_view" AS SELECT "appUser"."id" AS "user", "appUser"."email" AS "email", SUM("attendance"."points") AS "points", SUM(CASE WHEN "event"."type" = 'professional' THEN 1 ELSE 0 END)::int::bool AS "hasProfessionalRequirement", SUM(CASE WHEN "event"."type" = 'mentorship' THEN 1 ELSE 0 END)::int::bool AS "hasMentorshipRequirement" FROM "app_user" "appUser" INNER JOIN "attendance" "attendance" ON "appUser"."id" = "attendance"."attendeeId" INNER JOIN "event" "event" ON "event"."id" = "attendance"."eventId" WHERE "attendance"."isInductee" GROUP BY "appUser"."id"`
);
await queryRunner.query(
`INSERT INTO "typeorm_metadata"("type", "schema", "name", "value") VALUES ($1, $2, $3, $4)`,
[
'VIEW',
'public',
'inductee_points_view',
'SELECT "appUser"."id" AS "user", "appUser"."email" AS "email", SUM("attendance"."points") AS "points", SUM(CASE WHEN "event"."type" = \'professional\' THEN 1 ELSE 0 END)::int::bool AS "hasProfessionalRequirement", SUM(CASE WHEN "event"."type" = \'mentorship\' THEN 1 ELSE 0 END)::int::bool AS "hasMentorshipRequirement" FROM "app_user" "appUser" INNER JOIN "attendance" "attendance" ON "appUser"."id" = "attendance"."attendeeId" INNER JOIN "event" "event" ON "event"."id" = "attendance"."eventId" WHERE "attendance"."isInductee" GROUP BY "appUser"."id"',
]
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DELETE FROM "typeorm_metadata" WHERE "type" = 'VIEW' AND "schema" = $1 AND "name" = $2`,
['public', 'inductee_points_view']
);
await queryRunner.query(`DROP VIEW "inductee_points_view"`);
await queryRunner.query(
`CREATE VIEW "inductee_points_view" AS SELECT "appUser"."email" AS "email", SUM("attendance"."points") AS "points", SUM(CASE WHEN "event"."type" = 'professional' THEN 1 ELSE 0 END)::int::bool AS "hasProfessionalRequirement", SUM(CASE WHEN "event"."type" = 'mentorship' THEN 1 ELSE 0 END)::int::bool AS "hasMentorshipRequirement" FROM "app_user" "appUser" INNER JOIN "attendance" "attendance" ON "appUser"."id" = "attendance"."attendeeId" INNER JOIN "event" "event" ON "event"."id" = "attendance"."eventId" WHERE "attendance"."isInductee" GROUP BY "appUser"."id"`
);
await queryRunner.query(
`INSERT INTO "typeorm_metadata"("type", "schema", "name", "value") VALUES ($1, $2, $3, $4)`,
[
'VIEW',
'public',
'inductee_points_view',
'SELECT "appUser"."email" AS "email", SUM("attendance"."points") AS "points", SUM(CASE WHEN "event"."type" = \'professional\' THEN 1 ELSE 0 END)::int::bool AS "hasProfessionalRequirement", SUM(CASE WHEN "event"."type" = \'mentorship\' THEN 1 ELSE 0 END)::int::bool AS "hasMentorshipRequirement" FROM "app_user" "appUser" INNER JOIN "attendance" "attendance" ON "appUser"."id" = "attendance"."attendeeId" INNER JOIN "event" "event" ON "event"."id" = "attendance"."eventId" WHERE "attendance"."isInductee" GROUP BY "appUser"."id"',
]
);
}
}
25 changes: 25 additions & 0 deletions src/payloads/Points.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { IsBoolean, IsInt, ValidateNested, IsEmail } from 'class-validator';
import { Type } from 'class-transformer';

export class InducteePointsResponse {
@IsInt()
user: number;

@IsEmail()
email: string;

@IsInt()
points: number;

@IsBoolean()
hasProfessionalRequirement: boolean;

@IsBoolean()
hasMentorshipRequirement: boolean;
}

export class MultipleInducteePointsResponse {
@ValidateNested({ each: true })
@Type(() => InducteePointsResponse)
inducteePoints: InducteePointsResponse[];
}
1 change: 1 addition & 0 deletions src/payloads/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ export {
MultipleAttendanceQuery,
} from './Attendance';
export { RSVPResponse } from './RSVP';
export { InducteePointsResponse, MultipleInducteePointsResponse } from './Points';
5 changes: 5 additions & 0 deletions src/services/AppUserService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ export class AppUserService {
return appUser.role === AppUserRole.GUEST;
}

async getAllInducteePoints(): Promise<InducteePointsView[] | undefined> {
const inducteePointsRepo = getRepository(InducteePointsView);
return await inducteePointsRepo.find({});
}

/**
* Gets inductee points for user
* @param {number} appUserID ID of AppUser to get points for.
Expand Down

0 comments on commit b73eb40

Please sign in to comment.