Skip to content

Commit

Permalink
[enhance] CheckEvent controller with new query endpoints for user and…
Browse files Browse the repository at this point in the history
… activity check events
  • Loading branch information
Soecka committed Jan 3, 2025
1 parent 5b4a77b commit ec2cad5
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 60 deletions.
2 changes: 1 addition & 1 deletion ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ git push origin master --tags
[7]: https://typeorm.io/
[8]: https://swagger.io/
[9]: https://github.com/anttiviljami/openapi-backend
[10]: https://github.com/settings/tokens
[10]: https://github.com/settings/tokens/new?description=KYS-service&scopes=read:packages
[11]: https://www.postgresql.org/
[12]: https://azure.microsoft.com/en-us/products/storage/blobs
[13]: https://www.leancloud.cn/
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"web-utility": "^4.4.2"
},
"devDependencies": {
"@kaiyuanshe/kys-service": "1.0.0-rc.0",
"@types/jsonwebtoken": "^9.0.7",
"@types/koa": "^2.15.0",
"@types/koa-bodyparser": "^4.3.12",
Expand Down
19 changes: 19 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

93 changes: 72 additions & 21 deletions src/controller/CheckEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,27 @@ import {
ForbiddenError,
Get,
JsonController,
Param,
Post,
QueryParams
} from 'routing-controllers';
import { ResponseSchema } from 'routing-controllers-openapi';
QueryParams,
} from "routing-controllers";
import { ResponseSchema } from "routing-controllers-openapi";

import {
ActivityCheckInListChunk,
BaseFilter,
CheckEvent,
CheckEventChunk,
CheckEventFilter,
CheckEventInput,
dataSource,
User,
dataSource
} from '../model';
import { ActivityLogController } from './ActivityLog';
UserActivityCheckInListChunk,
} from "../model";
import { ActivityLogController } from "./ActivityLog";
import { FindOptionsWhere } from "typeorm";

@JsonController('/event/check')
@JsonController("/event/check")
export class CheckEventController {
store = dataSource.getRepository(CheckEvent);
userStore = dataSource.getRepository(User);
Expand All @@ -31,51 +36,97 @@ export class CheckEventController {
@ResponseSchema(CheckEvent)
async createOne(
@CurrentUser() createdBy: User,
@Body() { user: id, ...data }: CheckEventInput
@Body() { user: id, ...data }: CheckEventInput,
) {
if (createdBy.id === id) throw new ForbiddenError('No self-checking');
if (createdBy.id === id) throw new ForbiddenError("No self-checking");

const user = await this.userStore.findOne({ where: { id } });

if (!user) throw new BadRequestError('Invalid user: ' + id);
if (!user) throw new BadRequestError("Invalid user: " + id);

const checked = await this.store.findOne({
where: { ...data, user: { id } }
where: { ...data, user: { id } },
});

if (checked) throw new ForbiddenError('No duplicated check');
if (checked) throw new ForbiddenError("No duplicated check");

const saved = await this.store.save({ ...data, createdBy, user });

await ActivityLogController.logCreate(
createdBy,
'CheckEvent',
saved.id
"CheckEvent",
saved.id,
);
return saved;
}

@Get()
@ResponseSchema(CheckEventChunk)
async getSessionList(
@QueryParams()
{
@QueryParams() {
user: id,
activityId,
agendaId,
pageSize = 10,
pageIndex = 1
}: CheckEventFilter
pageIndex = 1,
}: CheckEventFilter,
) {
const [list, count] = await this.store.findAndCount({
where: {
...(id ? { user: { id } } : {}),
activityId,
agendaId
agendaId,
},
relations: ['user'],
relations: ["user"],
skip: pageSize * (pageIndex - 1),
take: pageSize
take: pageSize,
});
return { list, count };
}

@Get("/user/:id")
@ResponseSchema(UserActivityCheckInListChunk)
getCheckEventList(
@Param("id") id: number,
@QueryParams() filter: BaseFilter,
) {
return this.queryList(
{ user: { id } },
filter,
["user"],
);
}

@Get("/activity/:id")
@ResponseSchema(ActivityCheckInListChunk)
getActivityCheckEventList(
@Param("id") id: string,
@QueryParams() filter: BaseFilter,
) {
return this.queryList({ activityId: id }, filter);
}

@Get("/activity")
@ResponseSchema(ActivityCheckInListChunk)
getAgendaCheckEventList(
@Param("id") id: string,
@QueryParams() filter: BaseFilter,
) {
return this.queryList({ agendaId: id }, filter);
}

async queryList(
where: FindOptionsWhere<CheckEvent>,
{ pageSize, pageIndex, sort }: BaseFilter,
relations?: string[],
) {
const skip = pageSize * (pageIndex - 1);

const [list, count] = await this.store.findAndCount({
where,
relations,
skip,
take: pageSize,
});
return { list, count };
}
Expand Down
42 changes: 20 additions & 22 deletions src/model/ActivityLog.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
import { Type } from 'class-transformer';
import { Type } from "class-transformer";
import {
IsEnum,
IsInt,
IsObject,
IsOptional,
Min,
ValidateNested
} from 'class-validator';
import { Column, Entity, ViewColumn, ViewEntity } from 'typeorm';
ValidateNested,
} from "class-validator";
import { Column, Entity, ViewColumn, ViewEntity } from "typeorm";

import { Base, BaseFilter, InputData, ListChunk } from './Base';
import { CheckEvent } from './CheckEvent';
import { User, UserBase } from './User';
import { Base, BaseFilter, InputData, ListChunk } from "./Base";
import { CheckEvent } from "./CheckEvent";
import { User, UserBase } from "./User";

export enum Operation {
Create = 'create',
Update = 'update',
Delete = 'delete'
Create = "create",
Update = "update",
Delete = "delete",
}

export const LogableTable = { User, CheckEvent };

const LogableTableEnum = Object.fromEntries(
Object.entries(LogableTable).map(([key]) => [key, key])
Object.entries(LogableTable).map(([key]) => [key, key]),
);

@Entity()
export class ActivityLog extends UserBase {
@IsEnum(Operation)
@Column({ type: 'simple-enum', enum: Operation })
@Column({ type: "simple-enum", enum: Operation })
operation: Operation;

@IsEnum(LogableTableEnum)
@Column({ type: 'simple-enum', enum: LogableTableEnum })
@Column({ type: "simple-enum", enum: LogableTableEnum })
tableName: keyof typeof LogableTable;

@IsInt()
Expand All @@ -45,10 +45,8 @@ export class ActivityLog extends UserBase {
record?: Base;
}

export class ActivityLogFilter
extends BaseFilter
implements Partial<InputData<ActivityLog>>
{
export class ActivityLogFilter extends BaseFilter
implements Partial<InputData<ActivityLog>> {
@IsEnum(Operation)
@IsOptional()
operation?: Operation;
Expand All @@ -65,13 +63,13 @@ export class ActivityLogListChunk implements ListChunk<ActivityLog> {
}

@ViewEntity({
expression: connection =>
expression: (connection) =>
connection
.createQueryBuilder()
.from(ActivityLog, 'al')
.groupBy('al.createdBy')
.select('al.createdBy.id', 'userId')
.addSelect('COUNT(al.id)', 'score')
.from(ActivityLog, "al")
.groupBy("al.createdBy")
.select("al.createdBy.id", "userId")
.addSelect("COUNT(al.id)", "score"),
})
export class UserRank {
@IsInt()
Expand Down
22 changes: 16 additions & 6 deletions src/model/Base.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { Type } from 'class-transformer';
import { Type } from "class-transformer";
import {
IsDateString,
IsEnum,
IsInt,
IsOptional,
IsString,
Min
} from 'class-validator';
import { NewData } from 'mobx-restful';
Min,
} from "class-validator";
import { NewData } from "mobx-restful";
import {
CreateDateColumn,
PrimaryGeneratedColumn,
UpdateDateColumn
} from 'typeorm';
UpdateDateColumn,
} from "typeorm";

export enum Sort {
DESC = "DESC",
ASC = "ASC",
}

export abstract class Base {
constructor(id?: number) {
Expand Down Expand Up @@ -52,6 +58,10 @@ export class BaseFilter {
@IsString()
@IsOptional()
keywords?: string;

@IsEnum(Sort)
@IsOptional()
sort?: Sort;
}

export interface ListChunk<T> {
Expand Down
Loading

0 comments on commit ec2cad5

Please sign in to comment.