Skip to content

Commit

Permalink
Merge pull request #293 from logion-network/feature/workload-api
Browse files Browse the repository at this point in the history
Expose workload of legal officer
  • Loading branch information
gdethier authored Feb 9, 2024
2 parents 5ee198b + f51b80c commit ffa624f
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 0 deletions.
9 changes: 9 additions & 0 deletions resources/schemas.json
Original file line number Diff line number Diff line change
Expand Up @@ -1849,6 +1849,15 @@
"description": "Accepted items should be published automatically on opening"
}
}
},
"WorkloadView": {
"type": "object",
"properties": {
"workload": {
"type": "number",
"description": "The number of pending tasks"
}
}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/logion/app.support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { fillInSpec as fillInSpecConfig, ConfigController } from "./controllers/
import { fillInSpec as fillInSpecIdenfy, IdenfyController } from "./controllers/idenfy.controller.js";
import { fillInSpec as fillInSpecVote, VoteController } from "./controllers/vote.controller.js";
import { fillInSpec as fillInSpecTokensRecord, TokensRecordController } from "./controllers/records.controller.js";
import { fillInSpec as fillInSpecWorkload, WorkloadController } from "./controllers/workload.controller.js";

const { logger } = Log;

Expand Down Expand Up @@ -60,6 +61,7 @@ export function predefinedSpec(spec: OpenAPIV3.Document): OpenAPIV3.Document {
fillInSpecIdenfy(spec);
fillInSpecVote(spec);
fillInSpecTokensRecord(spec);
fillInSpecWorkload(spec);

return spec;
}
Expand Down Expand Up @@ -121,6 +123,7 @@ export function setupApp(expressConfig?: ExpressConfig): Express {
dino.registerController(IdenfyController);
dino.registerController(VoteController);
dino.registerController(TokensRecordController);
dino.registerController(WorkloadController);

dino.dependencyResolver<Container>(AppContainer,
(injector, type) => {
Expand Down
4 changes: 4 additions & 0 deletions src/logion/container/app.container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ import { LocAuthorizationService } from "../services/locauthorization.service.js
import { SponsorshipService } from "../services/sponsorship.service.js";
import { MultiversxService } from "../services/ownership/multiversx.service.js";
import { AstarService, ConnectedAstarService } from "../services/ownership/astar.service.js";
import { WorkloadService } from "../services/workload.service.js";
import { WorkloadController } from "../controllers/workload.controller.js";

const container = new Container({ defaultScope: "Singleton", skipBaseClassChecks: true });
configureContainer(container);
Expand Down Expand Up @@ -148,6 +150,7 @@ container.bind(LocAuthorizationService).toSelf();
container.bind(SponsorshipService).toSelf();
container.bind(ConnectedAstarService).toSelf();
container.bind(AstarService).toService(ConnectedAstarService);
container.bind(WorkloadService).toSelf();

// Controllers are stateful so they must not be injected with singleton scope
container.bind(LocRequestController).toSelf().inTransientScope();
Expand All @@ -160,5 +163,6 @@ container.bind(ConfigController).toSelf().inTransientScope();
container.bind(IdenfyController).toSelf().inTransientScope();
container.bind(VoteController).toSelf().inTransientScope();
container.bind(TokensRecordController).toSelf().inTransientScope();
container.bind(WorkloadController).toSelf().inTransientScope();

export { container as AppContainer };
4 changes: 4 additions & 0 deletions src/logion/controllers/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,10 @@ export interface components {
/** @description Accepted items should be published automatically on opening */
autoPublish?: boolean;
};
WorkloadView: {
/** @description The number of pending tasks */
workload?: number;
};
};
responses: never;
parameters: never;
Expand Down
52 changes: 52 additions & 0 deletions src/logion/controllers/workload.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { injectable } from "inversify";
import { Controller, ApiController, Async, HttpGet } from "dinoloop";
import { components } from "./components.js";
import { OpenAPIV3 } from "express-oas-generator";
import {
addTag,
setControllerTag,
AuthenticationService,
getDefaultResponses,
} from "@logion/rest-api-core";
import { WorkloadService } from "../services/workload.service.js";

export function fillInSpec(spec: OpenAPIV3.Document): void {
const tagName = 'Workload';
addTag(spec, {
name: tagName,
description: "Legal-officer workload"
});
setControllerTag(spec, /^\/api\/workload.*/, tagName);

WorkloadController.getWorkload(spec);
}

type WorkloadView = components["schemas"]["WorkloadView"];

@injectable()
@Controller('/workload')
export class WorkloadController extends ApiController {

constructor(
private authenticationService: AuthenticationService,
private workloadService: WorkloadService,
) {
super();
}

static getWorkload(spec: OpenAPIV3.Document) {
const operationObject = spec.paths["/api/workload/{legalOfficerAddress}"].get!;
operationObject.summary = "Provides the workload of a given legal officer";
operationObject.description = "Requires authentication.";
operationObject.responses = getDefaultResponses("WorkloadView");
}

@HttpGet('/:legalOfficerAddress')
@Async()
async getWorkload(legalOfficerAddress: string): Promise<WorkloadView> {
await this.authenticationService.authenticatedUser(this.request);
return {
workload: await this.workloadService.workloadOf(legalOfficerAddress),
}
}
}
31 changes: 31 additions & 0 deletions src/logion/services/workload.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { injectable } from "inversify";
import { LocRequestRepository } from "../model/locrequest.model.js";
import { FetchVaultTransferRequestsSpecification, VaultTransferRequestRepository } from "../model/vaulttransferrequest.model.js";
import { FetchProtectionRequestsSpecification, ProtectionRequestRepository } from "../model/protectionrequest.model.js";

@injectable()
export class WorkloadService {

constructor(
private locRequestRepository: LocRequestRepository,
private vaultTransferRequestRepository: VaultTransferRequestRepository,
private protectionRequestRepository: ProtectionRequestRepository,
) {
}

async workloadOf(address: string): Promise<number> {
const pendingLocRequests = await this.locRequestRepository.findBy({
expectedOwnerAddress: address,
expectedStatuses: [ "REVIEW_PENDING" ],
});
const pendingVaultTransferRequests = await this.vaultTransferRequestRepository.findBy(new FetchVaultTransferRequestsSpecification({
expectedLegalOfficerAddress: address,
expectedStatuses: [ "PENDING" ],
}));
const pendingProtectionRequests = await this.protectionRequestRepository.findBy(new FetchProtectionRequestsSpecification({
expectedLegalOfficerAddress: address,
expectedStatuses: [ "PENDING" ],
}));
return pendingLocRequests.length + pendingVaultTransferRequests.length + pendingProtectionRequests.length;
}
}
30 changes: 30 additions & 0 deletions test/unit/controllers/workload.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { TestApp } from "@logion/rest-api-core";
import { Container } from "inversify";
import { Mock } from "moq.ts";
import request from "supertest";
import { ALICE } from "../../helpers/addresses.js";
import { WorkloadController } from "../../../src/logion/controllers/workload.controller.js";
import { WorkloadService } from "../../../src/logion/services/workload.service.js";

const { setupApp } = TestApp;

describe("WorkloadController", () => {

it("provides expected workload", async () => {
const app = setupApp(WorkloadController, mockForFetch)
await request(app)
.get(`/api/workload/${ ALICE }`)
.send()
.expect(200)
.expect('Content-Type', /application\/json/)
.expect(response => {
expect(response.body.workload).toEqual(42);
});
});
});

function mockForFetch(container: Container) {
const service = new Mock<WorkloadService>();
service.setup(instance => instance.workloadOf(ALICE)).returnsAsync(42);
container.bind(WorkloadService).toConstantValue(service.object());
}
77 changes: 77 additions & 0 deletions test/unit/services/workload.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { It, Mock } from "moq.ts";
import { FetchLocRequestsSpecification, LocRequestAggregateRoot, LocRequestRepository } from "../../../src/logion/model/locrequest.model.js";
import { FetchProtectionRequestsSpecification, ProtectionRequestAggregateRoot, ProtectionRequestRepository } from "../../../src/logion/model/protectionrequest.model.js";
import { FetchVaultTransferRequestsSpecification, VaultTransferRequestAggregateRoot, VaultTransferRequestRepository } from "../../../src/logion/model/vaulttransferrequest.model.js";
import { WorkloadService } from "../../../src/logion/services/workload.service.js";
import { ALICE } from "../../helpers/addresses.js";

describe("WorkloadService", () => {

it("provides expected workload", async () => {
const legalOfficerAddress = ALICE;
const service = buildService({
legalOfficerAddress,
locRequests: 10,
vaultTransferRequests: 20,
protectionRequests: 12,
});

const workload = await service.workloadOf(legalOfficerAddress);

expect(workload).toBe(42);
});

it("provides 0 workload if nothing pending", async () => {
const legalOfficerAddress = ALICE;
const service = buildService({
legalOfficerAddress,
locRequests: 0,
vaultTransferRequests: 0,
protectionRequests: 0,
});

const workload = await service.workloadOf(legalOfficerAddress);

expect(workload).toBe(0);
});
});

function buildService(args: {
legalOfficerAddress: string,
locRequests: number,
vaultTransferRequests: number,
protectionRequests: number,
}): WorkloadService {
const locRequestRepository = new Mock<LocRequestRepository>();
locRequestRepository.setup(instance => instance.findBy(
It.Is<FetchLocRequestsSpecification>(spec => spec.expectedOwnerAddress === args.legalOfficerAddress)
)).returnsAsync(pendingLocRequests(args.locRequests));

const vaultTransferRequestRepository = new Mock<VaultTransferRequestRepository>();
vaultTransferRequestRepository.setup(instance => instance.findBy(
It.Is<FetchVaultTransferRequestsSpecification>(spec => spec.expectedLegalOfficerAddress === args.legalOfficerAddress)
)).returnsAsync(pendingVaultTransferRequests(args.vaultTransferRequests));

const protectionRequestRepository = new Mock<ProtectionRequestRepository>();
protectionRequestRepository.setup(instance => instance.findBy(
It.Is<FetchProtectionRequestsSpecification>(spec => spec.expectedLegalOfficerAddress === args.legalOfficerAddress)
)).returnsAsync(pendingProtectionRequests(args.protectionRequests));

return new WorkloadService(
locRequestRepository.object(),
vaultTransferRequestRepository.object(),
protectionRequestRepository.object(),
);
}

function pendingLocRequests(length: number): LocRequestAggregateRoot[] {
return new Array(length);
}

function pendingVaultTransferRequests(length: number): VaultTransferRequestAggregateRoot[] {
return new Array(length);
}

function pendingProtectionRequests(length: number): ProtectionRequestAggregateRoot[] {
return new Array(length);
}

0 comments on commit ffa624f

Please sign in to comment.