Skip to content

Commit

Permalink
Merge pull request #83 from logion-network/feature/expose-coll-item-t…
Browse files Browse the repository at this point in the history
…imestamp

Collection Timestamp Synchronization: Part 2 - Expose timestamps via REST api
  • Loading branch information
benoitdevos authored Feb 17, 2022
2 parents 0f27366 + 1047bf6 commit 8d6e605
Show file tree
Hide file tree
Showing 16 changed files with 282 additions and 66 deletions.
20 changes: 20 additions & 0 deletions resources/schemas.json
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,26 @@
},
"title": "VoidLocView",
"description": "The parameters of LOC voiding"
},
"CollectionItemView": {
"type": "object",
"properties": {
"collectionLocId": {
"type": "string",
"description": "The id of the collection loc",
"example": "5e4ef4bb-8657-444c-9880-d89e9403fc85"
},
"itemId": {
"type": "string",
"description": "The id of the collection item",
"example": "0x818f1c9cd44ed4ca11f2ede8e865c02a82f9f8a158d8d17368a6818346899705"
},
"addedOn": {
"type": "string",
"format": "date-time",
"description": "The creation timestamp"
}
}
}
}
}
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 @@ -20,6 +20,7 @@ import { ApplicationErrorController } from "./controllers/application.error.cont
import { JsonResponse } from "./middlewares/json.response";
import { Container } from "inversify";
import { AppContainer } from "./container/app.container";
import { fillInSpec as fillInSpecForCollection, CollectionController } from "./controllers/collection.controller";

export function predefinedSpec(spec: OpenAPIV3.Document): OpenAPIV3.Document {
setOpenApi3(spec);
Expand Down Expand Up @@ -47,6 +48,7 @@ export function predefinedSpec(spec: OpenAPIV3.Document): OpenAPIV3.Document {
fillInSpecForAuthentication(spec);
fillInSpecForLoc(spec);
fillInSpecForHealth(spec);
fillInSpecForCollection(spec);

return spec;
}
Expand All @@ -68,6 +70,7 @@ export function setupApp(app: Express) {
dino.registerController(TransactionController);
dino.registerController(LocRequestController);
dino.registerController(HealthController);
dino.registerController(CollectionController);
dino.registerApplicationError(ApplicationErrorController);
dino.requestEnd(JsonResponse);

Expand Down
4 changes: 2 additions & 2 deletions src/logion/controllers/authentication.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import moment from "moment";
import { requireDefined } from "../lib/assertions";
import { SignatureService } from "../services/signature.service";
import { OpenAPIV3 } from "express-oas-generator";
import { getRequestBody, getBodyContent, getDefaultResponses, addPathParameter, addTag, setControllerTag } from "./doc";
import { getRequestBody, getBodyContent, getDefaultResponses, setPathParameters, addTag, setControllerTag } from "./doc";

export function fillInSpec(spec: OpenAPIV3.Document): void {
const tagName = 'Authentication';
Expand Down Expand Up @@ -82,7 +82,7 @@ export class AuthenticationController extends ApiController {
view: "AuthenticateRequestView",
});
operationObject.responses = getDefaultResponses("AuthenticateResponseView");
addPathParameter(operationObject, 'sessionId', "The ID of the session to authenticate");
setPathParameters(operationObject, { 'sessionId': "The ID of the session to authenticate" });
}

@HttpPost('/:sessionId/authenticate')
Expand Down
61 changes: 61 additions & 0 deletions src/logion/controllers/collection.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { injectable } from "inversify";
import { Controller, ApiController, Async, HttpGet } from "dinoloop";
import { CollectionRepository, CollectionItemAggregateRoot } from "../model/collection.model";
import { components } from "./components";
import { requireDefined } from "../lib/assertions";
import { OpenAPIV3 } from "express-oas-generator";
import { addTag, setControllerTag, getPublicResponses, setPathParameters } from "./doc";
import { badRequest } from "./errors";

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

export function fillInSpec(spec: OpenAPIV3.Document): void {
const tagName = 'Collections';
addTag(spec, {
name: tagName,
description: "Handling of Collections"
});
setControllerTag(spec, /^\/api\/collection.*/, tagName);

CollectionController.getCollectionItem(spec)
}

@injectable()
@Controller('/collection')
export class CollectionController extends ApiController {

constructor(
private collectionRepository: CollectionRepository
) {
super();
}

static getCollectionItem(spec: OpenAPIV3.Document) {
const operationObject = spec.paths["/api/collection/{collectionLocId}/{itemId}"].get!;
operationObject.summary = "Gets the info of a published Collection Item";
operationObject.description = "No authentication required.";
operationObject.responses = getPublicResponses("CollectionItemView");
setPathParameters(operationObject, {
'collectionLocId': "The id of the collection loc",
'itemId': "The id of the collection item"
});
}

@HttpGet('/:collectionLocId/:itemId')
@Async()
async getCollectionItem(_body: any, collectionLocId: string, itemId: string): Promise<CollectionItemView> {
const collectionItem = requireDefined(
await this.collectionRepository.findBy(collectionLocId, itemId),
() => badRequest(`Collection item ${ collectionLocId }/${ itemId } not found`));
return this.toView(collectionItem)
}

private toView(collectionItem: CollectionItemAggregateRoot): CollectionItemView {
const { collectionLocId, itemId, addedOn } = collectionItem.getDescription();
return {
collectionLocId,
itemId,
addedOn: addedOn.toISOString()
}
}
}
31 changes: 23 additions & 8 deletions src/logion/controllers/doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ export function getDefaultResponses(view?: string): OpenAPIV3.ResponsesObject {
};
}

export function getPublicResponses(view?: string): OpenAPIV3.ResponsesObject {
return {
"200": {
description: "OK",
content: view !== undefined ? getBodyContent(view) : undefined,
},
"400": {
description: "Bad Request"
}
};
}

export function getDefaultResponsesNoContent(): OpenAPIV3.ResponsesObject {
return {
"204": {
Expand All @@ -113,17 +125,20 @@ export function getDefaultResponsesNoContent(): OpenAPIV3.ResponsesObject {
};
}

export function addPathParameter(operationObject: OpenAPIV3.OperationObject, parameterName: string, description: string) {
export function setPathParameters(operationObject: OpenAPIV3.OperationObject, params: Record<string, string>) {
if(operationObject.parameters !== undefined) {
operationObject.parameters = [];
}
operationObject.parameters!.push({
name: parameterName,
in: "path",
description,
required: true,
schema: {type: "string"}
});
Object.keys(params).forEach(parameterName => {
const description = params[parameterName]
operationObject.parameters!.push({
name: parameterName,
in: "path",
description,
required: true,
schema: { type: "string" }
});
})
}

export function getDefaultResponsesWithAnyBody(): OpenAPIV3.ResponsesObject {
Expand Down
5 changes: 5 additions & 0 deletions src/logion/controllers/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { BadRequestException } from "dinoloop/modules/builtin/exceptions/exceptions";

export function badRequest(error: string): Error {
return new BadRequestException({ error: error })
}
60 changes: 38 additions & 22 deletions src/logion/controllers/locrequest.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
addTag,
setControllerTag,
getDefaultResponsesNoContent,
addPathParameter,
setPathParameters,
getDefaultResponsesWithAnyBody
} from "./doc";
import { AuthenticationService } from "../services/authentication.service";
Expand Down Expand Up @@ -269,6 +269,7 @@ export class LocRequestController extends ApiController {
operationObject.summary = "Gets a single LOC Request";
operationObject.description = "The authenticated user must be either expected requester or expected owner.";
operationObject.responses = getDefaultResponses("LocRequestView");
setPathParameters(operationObject, { 'requestId': "The ID of the LOC request" })
}

@HttpGet('/:requestId')
Expand All @@ -291,6 +292,7 @@ export class LocRequestController extends ApiController {
operationObject.summary = "Gets the published attributes of a single LOC";
operationObject.description = "No authentication required.";
operationObject.responses = getDefaultResponses("LocPublicView");
setPathParameters(operationObject, { 'requestId': "The ID of the LOC request" })
}

@HttpGet('/:requestId/public')
Expand Down Expand Up @@ -347,7 +349,7 @@ export class LocRequestController extends ApiController {
view: "RejectLocRequestView",
});
operationObject.responses = getDefaultResponsesNoContent();
addPathParameter(operationObject, 'requestId', "The ID of the LOC request to reject");
setPathParameters(operationObject, { 'requestId': "The ID of the LOC request to reject" });
}

@HttpPost('/:requestId/reject')
Expand All @@ -365,7 +367,7 @@ export class LocRequestController extends ApiController {
operationObject.summary = "Accepts a LOC Request";
operationObject.description = "The authenticated user must be the owner of the LOC.";
operationObject.responses = getDefaultResponsesNoContent();
addPathParameter(operationObject, 'requestId', "The ID of the LOC request to reject");
setPathParameters(operationObject, { 'requestId': "The ID of the LOC request to reject" });
}

@HttpPost('/:requestId/accept')
Expand All @@ -383,7 +385,7 @@ export class LocRequestController extends ApiController {
operationObject.summary = "Adds a file to the LOC";
operationObject.description = "The authenticated user must be the owner of the LOC.";
operationObject.responses = getDefaultResponses("AddFileResultView");
addPathParameter(operationObject, 'requestId', "The ID of the LOC");
setPathParameters(operationObject, { 'requestId': "The ID of the LOC" });
}

@HttpPost('/:requestId/files')
Expand Down Expand Up @@ -430,8 +432,10 @@ export class LocRequestController extends ApiController {
operationObject.summary = "Downloads a file of the LOC";
operationObject.description = "The authenticated user must be the owner of the LOC.";
operationObject.responses = getDefaultResponsesWithAnyBody();
addPathParameter(operationObject, 'requestId', "The ID of the LOC");
addPathParameter(operationObject, 'hash', "The hash of the file to download");
setPathParameters(operationObject, {
'requestId': "The ID of the LOC",
'hash': "The hash of the file to download"
});
}

@HttpGet('/:requestId/files/:hash')
Expand All @@ -458,8 +462,10 @@ export class LocRequestController extends ApiController {
operationObject.summary = "Deletes a file of the LOC";
operationObject.description = "The authenticated user must be the owner of the LOC. The file's hash must not yet have been published in the blockchain.";
operationObject.responses = getDefaultResponsesWithAnyBody();
addPathParameter(operationObject, 'requestId', "The ID of the LOC");
addPathParameter(operationObject, 'hash', "The hash of the file to download");
setPathParameters(operationObject, {
'requestId': "The ID of the LOC",
'hash': "The hash of the file to download"
});
}

@HttpDelete('/:requestId/files/:hash')
Expand All @@ -480,8 +486,10 @@ export class LocRequestController extends ApiController {
operationObject.summary = "Confirms a file of the LOC";
operationObject.description = "The authenticated user must be the owner of the LOC. Once a file is confirmed, it cannot be deleted anymore.";
operationObject.responses = getDefaultResponsesNoContent();
addPathParameter(operationObject, 'requestId', "The ID of the LOC");
addPathParameter(operationObject, 'hash', "The hash of the file to download");
setPathParameters(operationObject, {
'requestId': "The ID of the LOC",
'hash': "The hash of the file to download"
});
}

@HttpPut('/:requestId/files/:hash/confirm')
Expand All @@ -503,7 +511,7 @@ export class LocRequestController extends ApiController {
operationObject.summary = "Closes a LOC";
operationObject.description = "The authenticated user must be the owner of the LOC.";
operationObject.responses = getDefaultResponsesNoContent();
addPathParameter(operationObject, 'requestId', "The ID of the LOC");
setPathParameters(operationObject, { 'requestId': "The ID of the LOC" });
}

@HttpPost('/:requestId/close')
Expand All @@ -529,7 +537,7 @@ export class LocRequestController extends ApiController {
view: "VoidLocView",
});
operationObject.responses = getDefaultResponsesNoContent();
addPathParameter(operationObject, 'requestId', "The ID of the LOC");
setPathParameters(operationObject, { 'requestId': "The ID of the LOC" });
}

@HttpPost('/:requestId/void')
Expand All @@ -551,7 +559,7 @@ export class LocRequestController extends ApiController {
operationObject.summary = "Adds a link to the LOC";
operationObject.description = "The authenticated user must be the owner of the LOC.";
operationObject.responses = getDefaultResponsesNoContent();
addPathParameter(operationObject, 'requestId', "The ID of the LOC");
setPathParameters(operationObject, { 'requestId': "The ID of the LOC" });
}

@HttpPost('/:requestId/links')
Expand All @@ -575,8 +583,10 @@ export class LocRequestController extends ApiController {
operationObject.summary = "Deletes a link of the LOC";
operationObject.description = "The authenticated user must be the owner of the LOC. The link must not yet have been published in the blockchain.";
operationObject.responses = getDefaultResponsesWithAnyBody();
addPathParameter(operationObject, 'requestId', "The ID of the LOC");
addPathParameter(operationObject, 'target', "The ID of the linked LOC");
setPathParameters(operationObject, {
'requestId': "The ID of the LOC",
'target': "The ID of the linked LOC"
});
}

@HttpDelete('/:requestId/links/:target')
Expand All @@ -595,8 +605,10 @@ export class LocRequestController extends ApiController {
operationObject.summary = "Confirms a link of the LOC";
operationObject.description = "The authenticated user must be the owner of the LOC. Once a link is confirmed, it cannot be deleted anymore.";
operationObject.responses = getDefaultResponsesNoContent();
addPathParameter(operationObject, 'requestId', "The ID of the LOC");
addPathParameter(operationObject, 'target', "The target of the link");
setPathParameters(operationObject, {
'requestId': "The ID of the LOC",
'target': "The target of the link"
});
}

@HttpPut('/:requestId/links/:target/confirm')
Expand All @@ -618,7 +630,7 @@ export class LocRequestController extends ApiController {
operationObject.summary = "Adds a Metadata item to the LOC";
operationObject.description = "The authenticated user must be the owner of the LOC.";
operationObject.responses = getDefaultResponsesNoContent();
addPathParameter(operationObject, 'requestId', "The ID of the LOC");
setPathParameters(operationObject, { 'requestId': "The ID of the LOC" });
}

@HttpPost('/:requestId/metadata')
Expand All @@ -641,8 +653,10 @@ export class LocRequestController extends ApiController {
operationObject.summary = "Deletes a metadata item of the LOC";
operationObject.description = "The authenticated user must be the owner of the LOC. The metadata item must not yet have been published in the blockchain.";
operationObject.responses = getDefaultResponsesWithAnyBody();
addPathParameter(operationObject, 'requestId', "The ID of the LOC");
addPathParameter(operationObject, 'name', "The name of the metadata item");
setPathParameters(operationObject, {
'requestId': "The ID of the LOC",
'name': "The name of the metadata item"
});
}

@HttpDelete('/:requestId/metadata/:name')
Expand All @@ -663,8 +677,10 @@ export class LocRequestController extends ApiController {
operationObject.summary = "Confirms a metadata item of the LOC";
operationObject.description = "The authenticated user must be the owner of the LOC. Once a metadata item is confirmed, it cannot be deleted anymore.";
operationObject.responses = getDefaultResponsesNoContent();
addPathParameter(operationObject, 'requestId', "The ID of the LOC");
addPathParameter(operationObject, 'name', "The name of the metadata");
setPathParameters(operationObject, {
'requestId': "The ID of the LOC",
'name': "The name of the metadata"
});
}

@HttpPut('/:requestId/metadata/:name/confirm')
Expand Down
8 changes: 4 additions & 4 deletions src/logion/controllers/protectionrequest.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {

import { components } from './components';

import { addTag, setControllerTag, getRequestBody, getDefaultResponses, addPathParameter } from './doc';
import { addTag, setControllerTag, getRequestBody, getDefaultResponses, setPathParameters } from './doc';
import { requireDefined } from '../lib/assertions';
import { AuthenticationService } from "../services/authentication.service";

Expand Down Expand Up @@ -163,7 +163,7 @@ export class ProtectionRequestController extends ApiController {
view: "RejectProtectionRequestView",
});
operationObject.responses = getDefaultResponses("ProtectionRequestView");
addPathParameter(operationObject, 'id', "The ID of the request to reject");
setPathParameters(operationObject, { 'id': "The ID of the request to reject" });
}

@Async()
Expand All @@ -186,7 +186,7 @@ export class ProtectionRequestController extends ApiController {
view: "AcceptProtectionRequestView",
});
operationObject.responses = getDefaultResponses("ProtectionRequestView");
addPathParameter(operationObject, 'id', "The ID of the request to accept");
setPathParameters(operationObject, { 'id': "The ID of the request to accept" });
}

@Async()
Expand All @@ -208,7 +208,7 @@ export class ProtectionRequestController extends ApiController {
operationObject.summary = "Fetch all info necessary for the legal officer to accept or reject recovery.";
operationObject.description = "The authentication user must be either the protection requester, the recovery requester, or one of the legal officers";
operationObject.responses = getDefaultResponses("RecoveryInfoView");
addPathParameter(operationObject, 'id', "The ID of the recovery request");
setPathParameters(operationObject, { 'id': "The ID of the recovery request" });
}

@Async()
Expand Down
Loading

0 comments on commit 8d6e605

Please sign in to comment.