Skip to content

Commit

Permalink
Merge pull request #296 from logion-network/feature/collection-params
Browse files Browse the repository at this point in the history
Add collection params to model.
  • Loading branch information
benoitdevos authored Mar 20, 2024
2 parents f4f22e2 + b67dd86 commit 70dc3ce
Show file tree
Hide file tree
Showing 13 changed files with 226 additions and 11 deletions.
27 changes: 27 additions & 0 deletions resources/schemas.json
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,24 @@
}
}
},
"CollectionParamsView": {
"type": "object",
"properties": {
"lastBlockSubmission": {
"type": "string",
"description": "The last block that will accept an item submission"
},
"maxSize": {
"type": "number",
"description": "The maximum size of the collection"
},
"canUpload": {
"type": "boolean",
"description": "true if items can contain files",
"required": true
}
}
},
"BaseLocView": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -700,6 +718,9 @@
},
"fees": {
"$ref": "#/components/schemas/LocFeesView"
},
"collectionParams": {
"$ref": "#/components/schemas/CollectionParamsView"
}
},
"title": "BaseLocView",
Expand Down Expand Up @@ -1012,6 +1033,9 @@
},
"fees": {
"$ref": "#/components/schemas/LocFeesView"
},
"collectionParams": {
"$ref": "#/components/schemas/CollectionParamsView"
}
},
"title": "LocRequestView",
Expand Down Expand Up @@ -1152,6 +1176,9 @@
"template": {
"type": "string",
"description": "The LOC's template or undefined"
},
"collectionParams": {
"$ref": "#/components/schemas/CollectionParamsView"
}
},
"title": "LocPublicView",
Expand Down
22 changes: 21 additions & 1 deletion src/logion/controllers/adapters/locrequestadapter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { requireDefined } from "@logion/rest-api-core";
import { injectable } from "inversify";
import { IdenfyService } from "../../services/idenfy/idenfy.service.js";
import { LocRequestAggregateRoot, LocRequestRepository, ItemLifecycle, LocFees } from "../../model/locrequest.model.js";
import {
LocRequestAggregateRoot,
LocRequestRepository,
ItemLifecycle,
LocFees,
CollectionParams
} from "../../model/locrequest.model.js";
import { PostalAddress } from "../../model/postaladdress.js";
import { UserIdentity } from "../../model/useridentity.js";
import { components } from "../components.js";
Expand All @@ -22,6 +28,7 @@ type PostalAddressView = components["schemas"]["PostalAddressView"];
type VerifiedIssuerIdentity = components["schemas"]["VerifiedIssuerIdentity"];
type FeesView = components["schemas"]["FeesView"];
type LocFeesView = components["schemas"]["LocFeesView"];
type CollectionParamsView = components["schemas"]["CollectionParamsView"];

@injectable()
export class LocRequestAdapter {
Expand Down Expand Up @@ -114,6 +121,7 @@ export class LocRequestAdapter {
template: locDescription.template,
sponsorshipId: locDescription.sponsorshipId?.toString(),
fees: this.toLocFeesView(locDescription.fees),
collectionParams: this.toCollectionParamsView(locDescription.collectionParams),
};
const voidInfo = request.getVoidInfo();
if(voidInfo !== null) {
Expand Down Expand Up @@ -172,6 +180,18 @@ export class LocRequestAdapter {
tokensRecordFee: fees.tokensRecordFee?.toString(),
}
}

toCollectionParamsView(collectionParams: CollectionParams | undefined): CollectionParamsView | undefined {
if (!collectionParams) {
return undefined;
}
const { lastBlockSubmission, maxSize, canUpload } = collectionParams;
return {
lastBlockSubmission: lastBlockSubmission?.toString(),
maxSize,
canUpload,
}
}
}

export function toUserIdentityView(userIdentity: UserIdentity | undefined): UserIdentityView | undefined {
Expand Down
15 changes: 15 additions & 0 deletions src/logion/controllers/collection.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,21 @@ export class CollectionController extends ApiController {
throw badRequest("Cannot replace existing item, you may try to cancel it first");
}

const collection = requireDefined(await this.locRequestRepository.findById(collectionLocId));
if (collection.collectionCanUpload !== undefined
&& !collection.collectionCanUpload
&& body.files !== undefined
&& body.files.length > 0
) {
throw badRequest("Collection does not accept item files");
}
if (collection.collectionMaxSize !== undefined) {
const size = await this.collectionRepository.countBy(collectionLocId);
if (size >= collection.collectionMaxSize) {
throw badRequest("Collection has too many items");
}
}

const description = requireDefined(body.description);
const item = this.collectionFactory.newItem({
collectionLocId,
Expand Down
11 changes: 11 additions & 0 deletions src/logion/controllers/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,14 @@ export interface components {
/** @description The fee to be charged on tokens record creation */
tokensRecordFee?: string;
};
CollectionParamsView: {
/** @description The last block that will accept an item submission */
lastBlockSubmission?: string;
/** @description The maximum size of the collection */
maxSize?: number;
/** @description true if items can contain files */
canUpload?: boolean;
};
/**
* BaseLocView
* @description Base LOC attributes
Expand All @@ -396,6 +404,7 @@ export interface components {
/** @description The LOC's template or undefined */
template?: string;
fees?: components["schemas"]["LocFeesView"];
collectionParams?: components["schemas"]["CollectionParamsView"];
};
OpenLocView: {
/** @description The metadata attached to this LOC */
Expand Down Expand Up @@ -545,6 +554,7 @@ export interface components {
/** @description The ID of the sponsorship to use */
sponsorshipId?: string;
fees?: components["schemas"]["LocFeesView"];
collectionParams?: components["schemas"]["CollectionParamsView"];
};
/**
* LocPublicView
Expand Down Expand Up @@ -633,6 +643,7 @@ export interface components {
};
/** @description The LOC's template or undefined */
template?: string;
collectionParams?: components["schemas"]["CollectionParamsView"];
};
/**
* FetchLocRequestsSpecificationView
Expand Down
18 changes: 17 additions & 1 deletion src/logion/controllers/locrequest.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
StoredFile,
LinkParams,
SubmissionType,
LocFees
LocFees,
CollectionParams
} from "../model/locrequest.model.js";
import {
getRequestBody,
Expand Down Expand Up @@ -130,6 +131,7 @@ type OpenLocView = components["schemas"]["OpenLocView"];
type CloseView = components["schemas"]["CloseView"];
type OpenView = components["schemas"]["OpenView"];
type LocFeesView = components["schemas"]["LocFeesView"];
type CollectionParamsView = components["schemas"]["CollectionParamsView"];

@injectable()
@Controller('/loc-request')
Expand Down Expand Up @@ -208,6 +210,7 @@ export class LocRequestController extends ApiController {
template: createLocRequestView.template,
sponsorshipId,
fees: this.toLocFees(createLocRequestView.fees, locType),
collectionParams: this.toCollectionParams(createLocRequestView.collectionParams),
}
let request: LocRequestAggregateRoot;
if (accountEquals(authenticatedUser, owner)) {
Expand Down Expand Up @@ -252,6 +255,18 @@ export class LocRequestController extends ApiController {
};
}

private toCollectionParams(view: CollectionParamsView | undefined): CollectionParams | undefined {
if (view === undefined) {
return undefined;
} else {
return {
lastBlockSubmission: view.lastBlockSubmission === undefined ? undefined : BigInt(view.lastBlockSubmission),
maxSize: view.maxSize,
canUpload: view.canUpload !== undefined && view.canUpload
}
}
}

static createOpenLoc(spec: OpenAPIV3.Document) {
const operationObject = spec.paths["/api/loc-request/open"].post!;
operationObject.summary = "Creates a new LOC";
Expand Down Expand Up @@ -289,6 +304,7 @@ export class LocRequestController extends ApiController {
company: openLocView.company,
template: openLocView.template,
fees: this.toLocFees(openLocView.fees, locType),
collectionParams: this.toCollectionParams(openLocView.collectionParams),
}
const metadata = openLocView.metadata?.map(item => this.toMetadata(item, requesterAddress));
const links = openLocView.links ?
Expand Down
18 changes: 18 additions & 0 deletions src/logion/migration/1710862720387-AddCollectionParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class AddCollectionParams1710862720387 implements MigrationInterface {
name = 'AddCollectionParams1710862720387'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "loc_request" ADD "collection_last_block_submission" bigint`);
await queryRunner.query(`ALTER TABLE "loc_request" ADD "collection_max_size" integer`);
await queryRunner.query(`ALTER TABLE "loc_request" ADD "collection_can_upload" boolean`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "loc_request" DROP COLUMN "collection_can_upload"`);
await queryRunner.query(`ALTER TABLE "loc_request" DROP COLUMN "collection_max_size"`);
await queryRunner.query(`ALTER TABLE "loc_request" DROP COLUMN "collection_last_block_submission"`);
}

}
4 changes: 4 additions & 0 deletions src/logion/model/collection.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,10 @@ export class CollectionRepository {
return this.repository.findOneBy({ collectionLocId, itemId: itemId.toHex() });
}

public async countBy(collectionLocId: string): Promise<number> {
return this.repository.countBy({ collectionLocId })
}

public async findAllBy(collectionLocId: string): Promise<CollectionItemAggregateRoot[]> {
const builder = this.repository.createQueryBuilder("item")
.leftJoinAndSelect("item.files", "file")
Expand Down
38 changes: 38 additions & 0 deletions src/logion/model/locrequest.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ export interface LocFees {
readonly tokensRecordFee?: bigint;
}

export interface CollectionParams {
lastBlockSubmission: bigint | undefined;
maxSize: number | undefined;
canUpload: boolean;
}

export interface LocRequestDescription {
readonly requesterAddress?: SupportedAccountId;
readonly requesterIdentityLoc?: string;
Expand All @@ -57,6 +63,7 @@ export interface LocRequestDescription {
readonly template?: string;
readonly sponsorshipId?: UUID;
readonly fees: LocFees;
readonly collectionParams?: CollectionParams;
}

export interface LocRequestDecision {
Expand Down Expand Up @@ -524,6 +531,11 @@ export class LocRequestAggregateRoot {
template: this.template,
sponsorshipId: this.sponsorshipId ? new UUID(this.sponsorshipId) : undefined,
fees: this.fees ? this.fees.to() : {},
collectionParams: this.locType === "Collection" ? {
lastBlockSubmission: this.collectionLastBlockSubmission ? BigInt(this.collectionLastBlockSubmission) : undefined,
maxSize: this.collectionMaxSize,
canUpload: this.collectionCanUpload || false,
} : undefined,
};
}

Expand Down Expand Up @@ -1468,6 +1480,15 @@ export class LocRequestAggregateRoot {
@Column(() => EmbeddableLocFees, { prefix: "" })
fees?: EmbeddableLocFees;

@Column({ type: "bigint", name: "collection_last_block_submission", nullable: true })
collectionLastBlockSubmission?: string;

@Column({ type: "integer", name: "collection_max_size", nullable: true })
collectionMaxSize?: number;

@Column({ type: "boolean", name: "collection_can_upload", nullable: true })
collectionCanUpload?: boolean;

_filesToDelete: LocFile[] = [];
_linksToDelete: LocLink[] = [];
_metadataToDelete: LocMetadataItem[] = [];
Expand Down Expand Up @@ -2021,6 +2042,7 @@ export class LocRequestFactory {
private async createLocRequest(params: NewUserLocRequestParameters, submissionType: SubmissionType): Promise<LocRequestAggregateRoot> {
const { description } = params;

this.ensureCorrectCollectionParams(description);
const request = new LocRequestAggregateRoot();
request.id = params.id;

Expand Down Expand Up @@ -2057,6 +2079,9 @@ export class LocRequestFactory {
requireDefined(description.fees.tokensRecordFee, () => new Error("Collection LOC must have a tokens record fee"));
}
request.fees = EmbeddableLocFees.from(description.fees);
request.collectionLastBlockSubmission = description.collectionParams?.lastBlockSubmission?.toString();
request.collectionMaxSize = description.collectionParams?.maxSize;
request.collectionCanUpload = description.collectionParams?.canUpload;
return request;
}

Expand Down Expand Up @@ -2088,4 +2113,17 @@ export class LocRequestFactory {
throw new Error("Logion Identity LOC request must contain first name, last name, email and phone number.")
}
}

private ensureCorrectCollectionParams(description: LocRequestDescription) {
const { locType } = description;
if (locType !== 'Collection' && description.collectionParams !== undefined) {
throw badRequest("Collection Params are for collections only.");
}
if (locType === 'Collection') {
const collectionParams = requireDefined(description.collectionParams, () => Error("Missing Collection Params."));
if (collectionParams.lastBlockSubmission === undefined) {
requireDefined(collectionParams.maxSize, () => Error("Missing Collection upper bound."));
}
}
}
}
6 changes: 5 additions & 1 deletion test/integration/model/collection.model.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe("CollectionRepository", () => {

const deliveredList = collectionItem?.getFile(Hash.fromHex("0x979ff1da4670561bf3f521a1a1d4aad097d617d2fa2c0e75d52efe90e7b7ce83")).delivered!;
expect(deliveredList.length).toBe(2)

const delivered1 = deliveredList.find(delivered => delivered.deliveredFileHash === "0x38c79034a97d8827559f883790d52a1527f6e7d37e66ac8e70bafda216fda6d7");
expect(delivered1?.generatedOn).toBeDefined()
expect(delivered1?.owner).toBe("0x900edc98db53508e6742723988B872dd08cd09c2")
Expand Down Expand Up @@ -181,4 +181,8 @@ describe("CollectionRepository", () => {
expect(items[0].itemId).toBe("0xf35e4bcbc1b0ce85af90914e04350cce472a2f01f00c0f7f8bc5c7ba04da2bf2");
expect(items[1].itemId).toBe("0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee");
})

it("counts", async () => {
expect(await repository.countBy("296d3d8f-057f-445c-b4c8-59aa7d2d21de")).toEqual(2);
})
})
4 changes: 2 additions & 2 deletions test/integration/model/loc_requests.sql
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ VALUES (md5(random()::text || clock_timestamp()::text)::uuid, '7eb5b359-088b-405
INSERT INTO loc_request (id, requester_identity_loc, owner_address, description, status, loc_type, legal_fee)
VALUES (md5(random()::text || clock_timestamp()::text)::uuid, '518536e4-71e6-4c4f-82db-b16cbfb495ed', '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty', 'loc-19', 'OPEN', 'Transaction', 42);
-- Requested Collection locs
INSERT INTO loc_request (id, owner_address, requester_address, requester_address_type, description, status, loc_type, legal_fee)
VALUES (md5(random()::text || clock_timestamp()::text)::uuid, '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', '5CXLTF2PFBE89tTYsrofGPkSfGTdmW4ciw4vAfgcKhjggRgZ', 'Polkadot', 'loc-21', 'REVIEW_PENDING', 'Collection', 42);
INSERT INTO loc_request (id, owner_address, requester_address, requester_address_type, description, status, loc_type, legal_fee, collection_last_block_submission, collection_max_size, collection_can_upload)
VALUES ('d0460773-5b63-4fba-be29-283f3cd5fe8f', '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', '5CXLTF2PFBE89tTYsrofGPkSfGTdmW4ciw4vAfgcKhjggRgZ', 'Polkadot', 'loc-21', 'REVIEW_PENDING', 'Collection', 42, 10000000, 9999, true);
INSERT INTO loc_request (id, owner_address, requester_address, requester_address_type, description, status, loc_type, legal_fee)
VALUES (md5(random()::text || clock_timestamp()::text)::uuid, '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', '5Ew3MyB15VprZrjQVkpQFj8okmc9xLDSEdNhqMMS5cXsqxoW', 'Polkadot', 'loc-22', 'REVIEW_PENDING', 'Collection', 42);
INSERT INTO loc_request (id, owner_address, requester_address, requester_address_type, description, status, loc_type, legal_fee)
Expand Down
9 changes: 9 additions & 0 deletions test/integration/model/locrequest.model.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ describe('LocRequestRepository - read accesses', () => {
expect(await repository.existsBy(query)).toBeTrue();
})

it("finds collection with params", async() => {
const request = await repository.findById(COLLECTION_WITH_PARAMS);
const { collectionParams } = request!.getDescription();
expect(collectionParams?.lastBlockSubmission).toEqual(10000000n);
expect(collectionParams?.maxSize).toEqual(9999);
expect(collectionParams?.canUpload).toBeTrue();
})

it("finds loc with files, metadata and links", async () => {
const request = await repository.findById(LOC_WITH_FILES);
checkDescription([request!], "Transaction", "loc-10");
Expand Down Expand Up @@ -462,3 +470,4 @@ function checkDescription(requests: LocRequestAggregateRoot[], expectedLocType:

const LOC_WITH_FILES = "2b287596-f9d5-8030-b606-d1da538cb37f";
const LOGION_TRANSACTION_LOC_ID = "f93bc0d2-f443-49ff-a9de-a6331167b267";
const COLLECTION_WITH_PARAMS = "d0460773-5b63-4fba-be29-283f3cd5fe8f";
Loading

0 comments on commit 70dc3ce

Please sign in to comment.