From 43fb482004fc7eadc69d09eb0224d05f15432f24 Mon Sep 17 00:00:00 2001 From: Matt Smithies Date: Fri, 8 Dec 2023 16:09:46 +0000 Subject: [PATCH] [DOVU Alpha V2] Guardian Middleware API rewrite (#20) * Hide tsc scan errror * deprecating currents into into tests for alpha version of v2 * update an API endpoints for guardian v2 re-write * update in tags with reference to query routes and state queries * Update roles.ts * Update index.ts * Add password confirmation for guardian auth register * Update tsc interfaces * Update acc register for DTO for guardian * Alpha V2: scan JSON (500) error response for unprocessible entities 422 * Updated openAPI spec for valid endpoints * Rewrite/re-arch of API usage against policies. * Format been able to return valid values based off of enum for validation (used in ensure query role) * Dynamic ability for particular roles to reference block data for particular stages of a workflow * Query handler for role based data view -- allow for filtering for any downstream credential subject value * standalone utility to enable the ingestion of a credential, subject and filterable values to enable filtering * Chore prettier --- .../e2e/fullAgrecalcPolicy.test.ts | 6 +- .../e2e/fullCoolFarmPolicy.test.ts | 6 +- ...llGeneralSupplyDocumentationPolicy.test.ts | 14 +- .../mrv/[did].ts => approval/claims/[id].ts} | 4 +- .../[did].ts => approval/projects/[id].ts} | 4 +- .../[did].ts => approval/sites/[id].ts} | 4 +- .../[id]/sites/index.ts} | 6 +- .../{register => projects}/index.ts | 6 +- .../{project => sites/[id]/claims}/index.ts | 9 +- .../api/policies/[policyId]/state/[entity].ts | 13 + src/config/guardianTags.ts | 66 +- src/config/roles.ts | 2 +- src/constants/language/index.ts | 15 +- src/guardian/account/index.ts | 1 + src/guardian/policies/index.ts | 5 +- src/handler/accounts/createAccountHandler.ts | 3 +- .../approveEcologicalProjectHandler.ts | 45 -- .../approveClaimHandler.ts} | 16 +- .../policies/claims/createClaimHandler.ts | 51 ++ .../policies/ecologicalProjectHandler.ts | 55 -- src/handler/policies/mrvSubmissionHandler.ts | 56 -- .../approveProjectHandler.ts} | 22 +- .../createProjectHandler.ts} | 15 +- .../registerAccountToPolicyHandler.ts | 2 +- .../policies/sites/approveSiteHandler.ts | 43 ++ .../policies/sites/createSiteHandler.ts | 44 ++ src/handler/policies/trustChainsHandler.ts | 1 + src/handler/state/queryEntityHandler.ts | 38 ++ src/middleware/ensureQueryDataRole.ts | 32 + src/middleware/exceptionFilter.ts | 34 +- src/spec/openapi.json | 588 +++++++++++++++++- src/spec/openapi.ts | 209 ++++++- src/utils/format.ts | 48 ++ src/utils/query.ts | 38 ++ 34 files changed, 1216 insertions(+), 285 deletions(-) rename __tests__/{ => deprecated}/e2e/fullAgrecalcPolicy.test.ts (97%) rename __tests__/{ => deprecated}/e2e/fullCoolFarmPolicy.test.ts (97%) rename __tests__/{ => deprecated}/e2e/fullGeneralSupplyDocumentationPolicy.test.ts (96%) rename pages/api/policies/[policyId]/{approve/mrv/[did].ts => approval/claims/[id].ts} (80%) rename pages/api/policies/[policyId]/{approve/application/[did].ts => approval/projects/[id].ts} (80%) rename pages/api/policies/[policyId]/{approve/project/[did].ts => approval/sites/[id].ts} (78%) rename pages/api/policies/[policyId]/{mrv/[mrvType].ts => projects/[id]/sites/index.ts} (77%) rename pages/api/policies/[policyId]/{register => projects}/index.ts (76%) rename pages/api/policies/[policyId]/{project => sites/[id]/claims}/index.ts (70%) create mode 100644 pages/api/policies/[policyId]/state/[entity].ts delete mode 100644 src/handler/policies/approveEcologicalProjectHandler.ts rename src/handler/policies/{approveMrvRequestHandler.ts => claims/approveClaimHandler.ts} (63%) create mode 100644 src/handler/policies/claims/createClaimHandler.ts delete mode 100644 src/handler/policies/ecologicalProjectHandler.ts delete mode 100644 src/handler/policies/mrvSubmissionHandler.ts rename src/handler/policies/{approveApplicationHandler.ts => projects/approveProjectHandler.ts} (53%) rename src/handler/policies/{registerProjectHandler.ts => projects/createProjectHandler.ts} (61%) create mode 100644 src/handler/policies/sites/approveSiteHandler.ts create mode 100644 src/handler/policies/sites/createSiteHandler.ts create mode 100644 src/handler/state/queryEntityHandler.ts create mode 100644 src/middleware/ensureQueryDataRole.ts create mode 100644 src/utils/format.ts create mode 100644 src/utils/query.ts diff --git a/__tests__/e2e/fullAgrecalcPolicy.test.ts b/__tests__/deprecated/e2e/fullAgrecalcPolicy.test.ts similarity index 97% rename from __tests__/e2e/fullAgrecalcPolicy.test.ts rename to __tests__/deprecated/e2e/fullAgrecalcPolicy.test.ts index 5c60b3b..c61c1d5 100644 --- a/__tests__/e2e/fullAgrecalcPolicy.test.ts +++ b/__tests__/deprecated/e2e/fullAgrecalcPolicy.test.ts @@ -1,6 +1,6 @@ -import StatusCode from 'src/constants/status' -import hmacAxios from 'src/apiClient/hmacApiClient' -import config from 'src/config' +import StatusCode from '../../../src/constants/status' +import hmacAxios from '../../../src/apiClient/hmacApiClient' +import config from '../../../src/config' const SECONDS = 1000 const TEN_SECONDS = 10 * SECONDS diff --git a/__tests__/e2e/fullCoolFarmPolicy.test.ts b/__tests__/deprecated/e2e/fullCoolFarmPolicy.test.ts similarity index 97% rename from __tests__/e2e/fullCoolFarmPolicy.test.ts rename to __tests__/deprecated/e2e/fullCoolFarmPolicy.test.ts index 5ae25f0..34d341e 100644 --- a/__tests__/e2e/fullCoolFarmPolicy.test.ts +++ b/__tests__/deprecated/e2e/fullCoolFarmPolicy.test.ts @@ -1,6 +1,6 @@ -import StatusCode from 'src/constants/status' -import hmacAxios from 'src/apiClient/hmacApiClient' -import config from 'src/config' +import StatusCode from '../../../src/constants/status' +import hmacAxios from '../../../src/apiClient/hmacApiClient' +import config from '../../../src/config' const SECONDS = 1000 const TEN_SECONDS = 10 * SECONDS diff --git a/__tests__/e2e/fullGeneralSupplyDocumentationPolicy.test.ts b/__tests__/deprecated/e2e/fullGeneralSupplyDocumentationPolicy.test.ts similarity index 96% rename from __tests__/e2e/fullGeneralSupplyDocumentationPolicy.test.ts rename to __tests__/deprecated/e2e/fullGeneralSupplyDocumentationPolicy.test.ts index ee0a9d0..d603ecf 100644 --- a/__tests__/e2e/fullGeneralSupplyDocumentationPolicy.test.ts +++ b/__tests__/deprecated/e2e/fullGeneralSupplyDocumentationPolicy.test.ts @@ -1,6 +1,6 @@ -import StatusCode from 'src/constants/status' -import hmacAxios from 'src/apiClient/hmacApiClient' -import config from 'src/config' +import StatusCode from '../../../src/constants/status' +import hmacAxios from '../../../src/apiClient/hmacApiClient' +import config from '../../../src/config' const SECONDS = 1000 const TEN_SECONDS = 10 * SECONDS @@ -116,7 +116,7 @@ describe('Test General Supply Documentation policy flow', () => { ONE_MINUTE ) it( - 'submits a new application', + 'submits a new project', async () => { const { accessToken } = await loginResponseData( registrant, @@ -149,7 +149,7 @@ describe('Test General Supply Documentation policy flow', () => { ONE_MINUTE ) it( - 'approves the application', + 'approves the project', async () => { await new Promise((r) => setTimeout(r, TEN_SECONDS)) const { did } = await loginResponseData(registrant, password) @@ -172,7 +172,7 @@ describe('Test General Supply Documentation policy flow', () => { ONE_MINUTE ) it( - 'submits an ecological project', + 'submits a site', async () => { await new Promise((r) => setTimeout(r, TEN_SECONDS)) const { accessToken } = await loginResponseData( @@ -210,7 +210,7 @@ describe('Test General Supply Documentation policy flow', () => { ONE_MINUTE ) it( - 'approves the ecological project', + 'approves the site', async () => { await new Promise((r) => setTimeout(r, TEN_SECONDS)) const { did } = await loginResponseData(registrant, password) diff --git a/pages/api/policies/[policyId]/approve/mrv/[did].ts b/pages/api/policies/[policyId]/approval/claims/[id].ts similarity index 80% rename from pages/api/policies/[policyId]/approve/mrv/[did].ts rename to pages/api/policies/[policyId]/approval/claims/[id].ts index d91d923..d3caf28 100644 --- a/pages/api/policies/[policyId]/approve/mrv/[did].ts +++ b/pages/api/policies/[policyId]/approval/claims/[id].ts @@ -1,7 +1,7 @@ import onlyPut from 'src/middleware/onlyPut' import prepare from 'src/utils/prepare' import useGuardianContext from 'src/context/useGuardianContext' -import approveMrvRequestHandler from 'src/handler/policies/approveMrvRequestHandler' +import approveClaimHandler from 'src/handler/policies/claims/approveClaimHandler' import withAuthentication from 'src/middleware/withAuthentication' import withHmac from 'src/middleware/withHmac' import ensureRole from 'src/middleware/ensureRole' @@ -13,4 +13,4 @@ export default prepare( useGuardianContext, withAuthentication, ensureRole(Role.VERIFIER) -)(approveMrvRequestHandler) +)(approveClaimHandler) diff --git a/pages/api/policies/[policyId]/approve/application/[did].ts b/pages/api/policies/[policyId]/approval/projects/[id].ts similarity index 80% rename from pages/api/policies/[policyId]/approve/application/[did].ts rename to pages/api/policies/[policyId]/approval/projects/[id].ts index 6d0900e..16c0ad8 100644 --- a/pages/api/policies/[policyId]/approve/application/[did].ts +++ b/pages/api/policies/[policyId]/approval/projects/[id].ts @@ -1,7 +1,7 @@ import onlyPut from 'src/middleware/onlyPut' import prepare from 'src/utils/prepare' import useGuardianContext from 'src/context/useGuardianContext' -import approveApplicationHandler from 'src/handler/policies/approveApplicationHandler' +import approveProjectHandler from 'src/handler/policies/projects/approveProjectHandler' import withAuthentication from 'src/middleware/withAuthentication' import withHmac from 'src/middleware/withHmac' import ensureRole from 'src/middleware/ensureRole' @@ -13,4 +13,4 @@ export default prepare( useGuardianContext, withAuthentication, ensureRole(Role.STANDARD_REGISTRY) -)(approveApplicationHandler) +)(approveProjectHandler) diff --git a/pages/api/policies/[policyId]/approve/project/[did].ts b/pages/api/policies/[policyId]/approval/sites/[id].ts similarity index 78% rename from pages/api/policies/[policyId]/approve/project/[did].ts rename to pages/api/policies/[policyId]/approval/sites/[id].ts index 0186032..ae621f5 100644 --- a/pages/api/policies/[policyId]/approve/project/[did].ts +++ b/pages/api/policies/[policyId]/approval/sites/[id].ts @@ -1,7 +1,7 @@ import onlyPut from 'src/middleware/onlyPut' import prepare from 'src/utils/prepare' import useGuardianContext from 'src/context/useGuardianContext' -import approveEcologicalProjectHandler from 'src/handler/policies/approveEcologicalProjectHandler' +import approveSiteHandler from 'src/handler/policies/sites/approveSiteHandler' import withAuthentication from 'src/middleware/withAuthentication' import withHmac from 'src/middleware/withHmac' import ensureRole from 'src/middleware/ensureRole' @@ -13,4 +13,4 @@ export default prepare( useGuardianContext, withAuthentication, ensureRole(Role.STANDARD_REGISTRY) -)(approveEcologicalProjectHandler) +)(approveSiteHandler) diff --git a/pages/api/policies/[policyId]/mrv/[mrvType].ts b/pages/api/policies/[policyId]/projects/[id]/sites/index.ts similarity index 77% rename from pages/api/policies/[policyId]/mrv/[mrvType].ts rename to pages/api/policies/[policyId]/projects/[id]/sites/index.ts index 4de8370..f9ec530 100644 --- a/pages/api/policies/[policyId]/mrv/[mrvType].ts +++ b/pages/api/policies/[policyId]/projects/[id]/sites/index.ts @@ -1,7 +1,7 @@ import onlyPost from 'src/middleware/onlyPost' import prepare from 'src/utils/prepare' import useGuardianContext from 'src/context/useGuardianContext' -import mrvSubmissionHandler from 'src/handler/policies/mrvSubmissionHandler' +import createSiteHandler from 'src/handler/policies/sites/createSiteHandler' import withAuthentication from 'src/middleware/withAuthentication' import withHmac from 'src/middleware/withHmac' import ensureRole from 'src/middleware/ensureRole' @@ -12,5 +12,5 @@ export default prepare( withHmac, useGuardianContext, withAuthentication, - ensureRole(Role.REGISTRANT) -)(mrvSubmissionHandler) + ensureRole(Role.SUPPLIER) +)(createSiteHandler) diff --git a/pages/api/policies/[policyId]/register/index.ts b/pages/api/policies/[policyId]/projects/index.ts similarity index 76% rename from pages/api/policies/[policyId]/register/index.ts rename to pages/api/policies/[policyId]/projects/index.ts index 55014fb..0ec6e16 100644 --- a/pages/api/policies/[policyId]/register/index.ts +++ b/pages/api/policies/[policyId]/projects/index.ts @@ -1,7 +1,7 @@ import onlyPost from 'src/middleware/onlyPost' import prepare from 'src/utils/prepare' import useGuardianContext from 'src/context/useGuardianContext' -import registerProjectHandler from 'src/handler/policies/registerProjectHandler' +import createProjectHandler from 'src/handler/policies/projects/createProjectHandler' import withAuthentication from 'src/middleware/withAuthentication' import withHmac from 'src/middleware/withHmac' import ensureRole from 'src/middleware/ensureRole' @@ -12,5 +12,5 @@ export default prepare( withHmac, useGuardianContext, withAuthentication, - ensureRole(Role.REGISTRANT) -)(registerProjectHandler) + ensureRole(Role.SUPPLIER) +)(createProjectHandler) diff --git a/pages/api/policies/[policyId]/project/index.ts b/pages/api/policies/[policyId]/sites/[id]/claims/index.ts similarity index 70% rename from pages/api/policies/[policyId]/project/index.ts rename to pages/api/policies/[policyId]/sites/[id]/claims/index.ts index 9ddf159..3e463ad 100644 --- a/pages/api/policies/[policyId]/project/index.ts +++ b/pages/api/policies/[policyId]/sites/[id]/claims/index.ts @@ -1,16 +1,19 @@ import onlyPost from 'src/middleware/onlyPost' import prepare from 'src/utils/prepare' import useGuardianContext from 'src/context/useGuardianContext' -import ecologicalProjectHandler from 'src/handler/policies/ecologicalProjectHandler' +import createClaimHandler from 'src/handler/policies/claims/createClaimHandler' import withAuthentication from 'src/middleware/withAuthentication' import withHmac from 'src/middleware/withHmac' import ensureRole from 'src/middleware/ensureRole' import { Role } from 'src/config/guardianTags' +/** + * 6 Month Deprecation notice: December 2023 - May 2024 + */ export default prepare( onlyPost, withHmac, useGuardianContext, withAuthentication, - ensureRole(Role.REGISTRANT) -)(ecologicalProjectHandler) + ensureRole(Role.SUPPLIER) +)(createClaimHandler) diff --git a/pages/api/policies/[policyId]/state/[entity].ts b/pages/api/policies/[policyId]/state/[entity].ts new file mode 100644 index 0000000..ecd3d82 --- /dev/null +++ b/pages/api/policies/[policyId]/state/[entity].ts @@ -0,0 +1,13 @@ +import onlyGet from 'src/middleware/onlyGet' +import prepare from 'src/utils/prepare' +import useGuardianContext from 'src/context/useGuardianContext' +import queryEntityHandler from 'src/handler/state/queryEntityHandler' +import withAuthentication from 'src/middleware/withAuthentication' +import ensureQueryDataRole from 'src/middleware/ensureQueryDataRole' + +export default prepare( + onlyGet, + useGuardianContext, + withAuthentication, + ensureQueryDataRole +)(queryEntityHandler) diff --git a/src/config/guardianTags.ts b/src/config/guardianTags.ts index 81476d3..dfca8c6 100644 --- a/src/config/guardianTags.ts +++ b/src/config/guardianTags.ts @@ -7,25 +7,32 @@ export enum Tag { chooseRole = 'choose_role', - initialApplicationSubmission = 'create_application', + initialProjectSubmission = 'create_ecological_project', // Approve an application - approveApplicationBlocks = 'registrants_grid', - approveApplicationBtn = 'approve_registrant_btn', + supplierGrid = 'supplier_grid', + // supplierGrid = 'save_copy_ecological_project', + approveProjectBtn = 'approve_supplier_btn', - // Submit document to make an ecological project - createEcologicalProject = 'create_farm_form', + // Submit document to make site + createSite = 'create_site_form', + + sitesForClaim = 'sites_grid', // Submit an MRV for an ecological project as an issue request - mrvSubmission = 'create_issue_request_form', + mrvSubmission = 'create_claim_request_form', + + // Submit document to make a claim + createClaim = 'create_claim_request_form', // Approve an ecological project - approveEcologicalProject = 'approve_farms_grid', - approveEcologicalProjectBtn = 'approve_farm_button', + approveSite = 'approve_sites_grid', + approveSiteBtn = 'approve_site_button', // Approve an MRV request - approveMrvRequest = 'issue_requests_grid(evident)', - approveMrvRequestBtn = 'approve_issue_requests_btn', + // approveMrvRequest = 'claim_requests_grid(evident)', + approveClaimRequest = 'claim_requests_grid(verifier)', + approveClaimRequestBtn = 'approve_claim_requests_btn', // Weirdness needed for all document approval approveBtn = 'Option_0', @@ -40,14 +47,14 @@ export enum Tag { // Path to mint token verifierWorkflow = 'verifier_workflow', - approveIssueRequestsPage = 'approve_issue_requests_page', - mintTokenParent = 'mit_token', + approveIssueRequestsPage = 'approve_claim_requests_page', + mintTokenParent = 'mint_token', } export enum Role { STANDARD_REGISTRY = 'ADMINISTRATOR', VERIFIER = 'VERIFIER', - REGISTRANT = 'REGISTRANT', + SUPPLIER = 'SUPPLIER', } export enum MRV { @@ -55,3 +62,36 @@ export enum MRV { COOL_FARM_TOOL = 'cool-farm-tool', GENERAL_SUPPLY_DOCUMENTATION = 'general-supply-documentation', } + +export enum EntityState { + APPROVED = 'Approved', + WAITING = 'Waiting for approval', +} + +/** + * This enables the (API) client to query the entity at a particular state of the workflow, + * guardian expects different users to query to submit documents. + */ +export enum QueryRoute { + PROJECTS = 'projects', // registry only + APPROVE_SITE = 'approve-site', // registry only + CREATE_SITE = 'create-site', // supplier only + CREATE_CLAIM = 'create-claim', // supplier only + APPROVE_CLAIM = 'approve-claim', // verifier only +} + +export const QueryBlockTag = { + [QueryRoute.PROJECTS]: Tag.supplierGrid, + [QueryRoute.CREATE_SITE]: Tag.createSite, + [QueryRoute.CREATE_CLAIM]: Tag.sitesForClaim, + [QueryRoute.APPROVE_SITE]: Tag.approveSite, + [QueryRoute.APPROVE_CLAIM]: Tag.approveClaimRequest, +} + +export const QueryRole = { + [QueryRoute.PROJECTS]: Role.STANDARD_REGISTRY, + [QueryRoute.CREATE_SITE]: Role.SUPPLIER, + [QueryRoute.CREATE_CLAIM]: Role.SUPPLIER, + [QueryRoute.APPROVE_SITE]: Role.STANDARD_REGISTRY, + [QueryRoute.APPROVE_CLAIM]: Role.VERIFIER, +} diff --git a/src/config/roles.ts b/src/config/roles.ts index 7c0326c..ebca47b 100644 --- a/src/config/roles.ts +++ b/src/config/roles.ts @@ -4,6 +4,6 @@ export default { user: 'USER', standardRegistry: 'STANDARD_REGISTRY', - registrant: 'REGISTRANT', + supplier: 'SUPPLIER', verifier: 'VERIFIER', } diff --git a/src/constants/language/index.ts b/src/constants/language/index.ts index 871f427..729fd9e 100644 --- a/src/constants/language/index.ts +++ b/src/constants/language/index.ts @@ -8,11 +8,11 @@ const language = { 'A role can only be enforced when connected to a policy', policyDoesNotExist: 'A policy cannot be found with that id', [Role.STANDARD_REGISTRY]: - 'Only a "STANDARD_REGISTRY" policy owner may approve an application', + 'Only a "STANDARD_REGISTRY" policy owner may approve a project, site or query related block data', [Role.VERIFIER]: - 'Only a user with the role "VERIFIER" make approve an MRV block', - [Role.REGISTRANT]: - 'Only a user with the role "REGISTRANT" may submit this document', + 'Only a user with the role "VERIFIER" make approve an claim (MRV) block or query related block data', + [Role.SUPPLIER]: + 'Only a user with the role "SUPPLIER" may submit this document or query related block data', }, withAuthenticationResponse: { noAccessToken: @@ -24,13 +24,16 @@ const language = { notAllowed: (method: string) => `Method ${method} is not allowed on this route`, }, + queryResponse: { + invalidType: (validValues: string) => + `The policy can only query types of ${validValues}. Please update your request.`, + }, ensureMrv: { unknownMrv: 'No MRV found with that id', }, validate: { message: 'Validation errors', - invalidRole: - "Invalid role type. Must be 'registrant' or 'verifier'", + invalidRole: "Invalid role type. Must be 'supplier' or 'verifier'", }, guardian: { serverError: diff --git a/src/guardian/account/index.ts b/src/guardian/account/index.ts index 32687b9..1785c48 100644 --- a/src/guardian/account/index.ts +++ b/src/guardian/account/index.ts @@ -70,6 +70,7 @@ interface LoginCredentialsDto { export interface CreateAccountDto { username: string password: string + password_confirmation: string role: 'USER' } export interface Accounts { diff --git a/src/guardian/policies/index.ts b/src/guardian/policies/index.ts index f7097bf..9d1f2db 100644 --- a/src/guardian/policies/index.ts +++ b/src/guardian/policies/index.ts @@ -1,7 +1,10 @@ import { AxiosInstance, AxiosResponse } from 'axios' export interface BlockData { - data?: BlockData[] // Feels naughty 👹 + find?: Function + reduce?: Function + push?: Function + data?: BlockData[] | BlockData // Feels naughty 👹 hash?: string id: string uiMetaData: { title: string; description: string } diff --git a/src/handler/accounts/createAccountHandler.ts b/src/handler/accounts/createAccountHandler.ts index 69a25e8..0793d87 100644 --- a/src/handler/accounts/createAccountHandler.ts +++ b/src/handler/accounts/createAccountHandler.ts @@ -28,8 +28,9 @@ async function CreateAccountHandler( const userData: CreateAccountDto = { ...userCredentials, - // This doesn't need to be a registrant (the API handles the registration) + // This doesn't need to be a supplier (the API handles the registration) role: 'USER', + password_confirmation: userCredentials.password, } await guardian.account.register(userData) diff --git a/src/handler/policies/approveEcologicalProjectHandler.ts b/src/handler/policies/approveEcologicalProjectHandler.ts deleted file mode 100644 index b902d70..0000000 --- a/src/handler/policies/approveEcologicalProjectHandler.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { GuardianMiddlewareRequest } from 'src/context/useGuardianContext' -import Response from 'src/response' -import { Tag } from 'src/config/guardianTags' -import { NextApiResponse } from 'next' - -async function ApproveEcologicalProjectHandler( - req: GuardianMiddlewareRequest, - res: NextApiResponse -) { - const { accessToken } = req - const { policyId, did } = req.query - const { engine } = req.context - - const submissions = await engine.fetchBlockSubmissions( - accessToken, - policyId as string, - Tag.approveEcologicalProject - ) - - const document = submissions.data.find( - // TODO: Temporary, we need a way of returning an identifier. - (submission) => - submission.owner === did && submission.option.status !== 'Approved' - ) - - if (!document) { - return Response.notFound() - } - - const submission = { - document, - tag: Tag.approveBtn, - } - - await engine.executeBlockViaTag( - accessToken, - policyId as string, - Tag.approveEcologicalProjectBtn, - submission - ) - - res.end() -} - -export default ApproveEcologicalProjectHandler diff --git a/src/handler/policies/approveMrvRequestHandler.ts b/src/handler/policies/claims/approveClaimHandler.ts similarity index 63% rename from src/handler/policies/approveMrvRequestHandler.ts rename to src/handler/policies/claims/approveClaimHandler.ts index d7aba78..e6d71cd 100644 --- a/src/handler/policies/approveMrvRequestHandler.ts +++ b/src/handler/policies/claims/approveClaimHandler.ts @@ -1,26 +1,24 @@ import { GuardianMiddlewareRequest } from 'src/context/useGuardianContext' import Response from 'src/response' -import { Tag } from 'src/config/guardianTags' +import { QueryBlockTag, QueryRoute, Tag } from 'src/config/guardianTags' import { NextApiResponse } from 'next' -async function ApproveMrvRequestHandler( +async function ApproveClaimHandler( req: GuardianMiddlewareRequest, res: NextApiResponse ) { const { accessToken } = req - const { policyId, did } = req.query + const { policyId, id } = req.query const { engine } = req.context const submissions = await engine.fetchBlockSubmissions( accessToken, policyId as string, - Tag.approveMrvRequest + QueryBlockTag[QueryRoute.APPROVE_CLAIM] ) const document = submissions.data?.find( - // TODO: Temporary, we need a way of returning an identifier. - (submission) => - submission.owner === did && submission.option.status !== 'Approved' + (submission) => submission?.id === id ) if (!document) { @@ -35,11 +33,11 @@ async function ApproveMrvRequestHandler( await engine.executeBlockViaTag( accessToken, policyId as string, - Tag.approveMrvRequestBtn, + Tag.approveClaimRequestBtn, submission ) res.end() } -export default ApproveMrvRequestHandler +export default ApproveClaimHandler diff --git a/src/handler/policies/claims/createClaimHandler.ts b/src/handler/policies/claims/createClaimHandler.ts new file mode 100644 index 0000000..f60a3a1 --- /dev/null +++ b/src/handler/policies/claims/createClaimHandler.ts @@ -0,0 +1,51 @@ +import { GuardianMiddlewareRequest } from 'src/context/useGuardianContext' +import Response from 'src/response' +import { NextApiResponse } from 'next' +import { components } from 'src/spec/openapi' +import { QueryBlockTag, QueryRoute, Tag } from 'src/config/guardianTags' + +type MeasurementReportingVerification = + components['schemas']['MeasurementReportingVerification'] + +interface MRVRequest extends GuardianMiddlewareRequest { + body: MeasurementReportingVerification +} + +async function CreateClaimHandler(req: MRVRequest, res: NextApiResponse) { + const { policyId, id } = req.query + const { engine } = req.context + const { body, accessToken } = req + + const tag = QueryBlockTag[QueryRoute.CREATE_CLAIM] + + const submissions = await engine.fetchBlockSubmissions( + accessToken, + policyId as string, + tag + ) + + // console.dir(submissions, { depth: null }) + + const submission = submissions.data?.find((elem) => elem?.id === id) + + if (!submission) { + // TODO: Context, this might not be the best error, as it is the entity that failed to resolve. + return Response.notFound() + } + + const data = { + document: body, + ref: submission, + } + + await engine.executeBlockViaTag( + accessToken, + policyId as string, + Tag.createClaim, + data + ) + + res.end() +} + +export default CreateClaimHandler diff --git a/src/handler/policies/ecologicalProjectHandler.ts b/src/handler/policies/ecologicalProjectHandler.ts deleted file mode 100644 index 5bcba10..0000000 --- a/src/handler/policies/ecologicalProjectHandler.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { GuardianMiddlewareRequest } from 'src/context/useGuardianContext' -import Response from 'src/response' -import { NextApiResponse } from 'next' -import { components } from 'src/spec/openapi' -import validateEcologicalProjectSubmission from 'src/validators/validateEcologicalProjectSubmission' -import { Tag } from 'src/config/guardianTags' - -type EcologicalProject = components['schemas']['EcologicalProject'] - -interface EcologicalProjectRequest extends GuardianMiddlewareRequest { - body: EcologicalProject -} - -async function EcologicalProjectHandler( - req: EcologicalProjectRequest, - res: NextApiResponse -) { - const { policyId } = req.query - const { engine } = req.context - const { body, accessToken } = req - - const validationErrors = validateEcologicalProjectSubmission(body) - - if (validationErrors) { - return Response.unprocessibleEntity(validationErrors) - } - - const did = await engine.getCurrentUserDid(accessToken) - - const previousDocument = await engine.retrievePreviousBlockContext( - policyId as string, - did as string, - Tag.approveApplicationBlocks - ) - - if (!previousDocument) { - return Response.notFound() - } - - const data = { - document: body, - ref: previousDocument, - } - - await engine.executeBlockViaTag( - accessToken, - policyId as string, - Tag.createEcologicalProject, - data - ) - - res.end() -} - -export default EcologicalProjectHandler diff --git a/src/handler/policies/mrvSubmissionHandler.ts b/src/handler/policies/mrvSubmissionHandler.ts deleted file mode 100644 index 5445a21..0000000 --- a/src/handler/policies/mrvSubmissionHandler.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { GuardianMiddlewareRequest } from 'src/context/useGuardianContext' -import Response from 'src/response' -import { NextApiResponse } from 'next' -import { components } from 'src/spec/openapi' -import validateMrvDocumentSubmission from 'src/validators/validateMrvDocumentSubmission' -import { Tag } from 'src/config/guardianTags' - -type MeasurementReportingVerification = - components['schemas']['MeasurementReportingVerification'] - -interface MRVRequest extends GuardianMiddlewareRequest { - body: MeasurementReportingVerification -} - -async function MrvSubmissionHandler(req: MRVRequest, res: NextApiResponse) { - const { policyId, mrvType } = req.query - const { engine } = req.context - const { body, accessToken } = req - - const validationErrors = validateMrvDocumentSubmission( - mrvType as string, - body - ) - - if (validationErrors) { - return Response.unprocessibleEntity(validationErrors) - } - - const did = await engine.getCurrentUserDid(accessToken) - - const previousDocument = await engine.retrievePreviousBlockContext( - policyId as string, - did as string, - Tag.approveEcologicalProject - ) - - if (!previousDocument) { - return Response.notFound() - } - - const data = { - document: body, - ref: previousDocument, - } - - await engine.executeBlockViaTag( - accessToken, - policyId as string, - Tag.mrvSubmission, - data - ) - - res.end() -} - -export default MrvSubmissionHandler diff --git a/src/handler/policies/approveApplicationHandler.ts b/src/handler/policies/projects/approveProjectHandler.ts similarity index 53% rename from src/handler/policies/approveApplicationHandler.ts rename to src/handler/policies/projects/approveProjectHandler.ts index 9c2407a..4892805 100644 --- a/src/handler/policies/approveApplicationHandler.ts +++ b/src/handler/policies/projects/approveProjectHandler.ts @@ -1,8 +1,8 @@ -import { GuardianMiddlewareRequest } from 'src/context/useGuardianContext' -import Response from 'src/response' +import { GuardianMiddlewareRequest } from '../../../context/useGuardianContext' +import Response from '../../../response' import { NextApiResponse } from 'next' -import { components } from 'src/spec/openapi' -import { Tag } from 'src/config/guardianTags' +import { components } from '../../../spec/openapi' +import { QueryBlockTag, QueryRoute, Tag } from '../../../config/guardianTags' type UserDid = components['schemas']['UserDid'] @@ -10,26 +10,26 @@ interface ApproveApplicationRequest extends GuardianMiddlewareRequest { body: UserDid } -async function ApproveApplicationHandler( +async function ApproveProjectHandler( req: ApproveApplicationRequest, res: NextApiResponse ) { const { accessToken } = req - const { policyId, did } = req.query + const { policyId, id } = req.query const { engine } = req.context - const tag = Tag.approveApplicationBlocks const submissions = await engine.fetchBlockSubmissions( accessToken, policyId as string, - tag + QueryBlockTag[QueryRoute.PROJECTS] ) const document = submissions.data?.find( - (submission) => submission.owner === did + (submission) => submission?.id === id ) if (!document) { + // TODO: Context, this might not be the best error, as it is the entity that failed to resolve. return Response.notFound() } @@ -41,11 +41,11 @@ async function ApproveApplicationHandler( await engine.executeBlockViaTag( accessToken, policyId as string, - Tag.approveApplicationBtn, + Tag.approveProjectBtn, submission ) res.end() } -export default ApproveApplicationHandler +export default ApproveProjectHandler diff --git a/src/handler/policies/registerProjectHandler.ts b/src/handler/policies/projects/createProjectHandler.ts similarity index 61% rename from src/handler/policies/registerProjectHandler.ts rename to src/handler/policies/projects/createProjectHandler.ts index 097c3ff..b468c3e 100644 --- a/src/handler/policies/registerProjectHandler.ts +++ b/src/handler/policies/projects/createProjectHandler.ts @@ -1,8 +1,6 @@ import { GuardianMiddlewareRequest } from 'src/context/useGuardianContext' -import Response from 'src/response' import { NextApiResponse } from 'next' import { components } from 'src/spec/openapi' -import validateProjectRegistrationApplication from 'src/validators/validateProjectRegistrationApplication' import { Tag } from 'src/config/guardianTags' type ProjectRegistration = components['schemas']['ProjectRegistration'] @@ -10,7 +8,7 @@ type ProjectRegistration = components['schemas']['ProjectRegistration'] interface RegisterProjectRequest extends GuardianMiddlewareRequest { body: ProjectRegistration } -async function RegisterProjectHandler( +async function CreateProjectHandler( req: RegisterProjectRequest, res: NextApiResponse ) { @@ -18,14 +16,7 @@ async function RegisterProjectHandler( const { engine } = req.context const { body, accessToken } = req - const validationErrors = validateProjectRegistrationApplication(body) - - if (validationErrors) { - Response.unprocessibleEntity(validationErrors) - return - } - - const tag = Tag.initialApplicationSubmission + const tag = Tag.initialProjectSubmission const data = { document: body, @@ -36,4 +27,4 @@ async function RegisterProjectHandler( res.end() } -export default RegisterProjectHandler +export default CreateProjectHandler diff --git a/src/handler/policies/registerAccountToPolicyHandler.ts b/src/handler/policies/registerAccountToPolicyHandler.ts index 4339ac3..eeda321 100644 --- a/src/handler/policies/registerAccountToPolicyHandler.ts +++ b/src/handler/policies/registerAccountToPolicyHandler.ts @@ -13,7 +13,7 @@ async function RegisterAccountToPolicyHandler( const role = (roleType as string)?.toUpperCase() - if (role !== Role.REGISTRANT && role !== Role.VERIFIER) { + if (role !== Role.SUPPLIER && role !== Role.VERIFIER) { return Response.unprocessibleEntity( language.middleware.validate.invalidRole ) diff --git a/src/handler/policies/sites/approveSiteHandler.ts b/src/handler/policies/sites/approveSiteHandler.ts new file mode 100644 index 0000000..4ccac06 --- /dev/null +++ b/src/handler/policies/sites/approveSiteHandler.ts @@ -0,0 +1,43 @@ +import { GuardianMiddlewareRequest } from '../../../context/useGuardianContext' +import Response from '../../../response' +import { QueryBlockTag, QueryRoute, Tag } from '../../../config/guardianTags' +import { NextApiResponse } from 'next' + +async function ApproveSiteHandler( + req: GuardianMiddlewareRequest, + res: NextApiResponse +) { + const { accessToken } = req + const { policyId, id } = req.query + const { engine } = req.context + + const submissions = await engine.fetchBlockSubmissions( + accessToken, + policyId as string, + QueryBlockTag[QueryRoute.APPROVE_SITE] + ) + + const document = submissions.data?.find( + (submission) => submission?.id === id + ) + + if (!document) { + return Response.notFound() + } + + const submission = { + document, + tag: Tag.approveBtn, + } + + await engine.executeBlockViaTag( + accessToken, + policyId as string, + Tag.approveSiteBtn, + submission + ) + + res.end() +} + +export default ApproveSiteHandler diff --git a/src/handler/policies/sites/createSiteHandler.ts b/src/handler/policies/sites/createSiteHandler.ts new file mode 100644 index 0000000..5b12b9d --- /dev/null +++ b/src/handler/policies/sites/createSiteHandler.ts @@ -0,0 +1,44 @@ +import { GuardianMiddlewareRequest } from 'src/context/useGuardianContext' +import Response from 'src/response' +import { NextApiResponse } from 'next' +import { components } from 'src/spec/openapi' +import { QueryBlockTag, QueryRoute } from 'src/config/guardianTags' + +type EcologicalProject = components['schemas']['EcologicalProject'] + +interface EcologicalProjectRequest extends GuardianMiddlewareRequest { + body: EcologicalProject +} + +async function CreateSiteHandler( + req: EcologicalProjectRequest, + res: NextApiResponse +) { + const { policyId, id } = req.query + const { engine } = req.context + const { body, accessToken } = req + const tag = QueryBlockTag[QueryRoute.CREATE_SITE] + + const submission = await engine.fetchBlockSubmissions( + accessToken, + policyId as string, + tag + ) + + // @ts-ignore + if (submission?.data?.id !== id) { + // TODO: Context, this might not be the best error, as it is the entity that failed to resolve. + return Response.notFound() + } + + const data = { + document: body, + ref: submission.data, + } + + await engine.executeBlockViaTag(accessToken, policyId as string, tag, data) + + res.end() +} + +export default CreateSiteHandler diff --git a/src/handler/policies/trustChainsHandler.ts b/src/handler/policies/trustChainsHandler.ts index 8ccc4b3..c6ac3ca 100644 --- a/src/handler/policies/trustChainsHandler.ts +++ b/src/handler/policies/trustChainsHandler.ts @@ -52,6 +52,7 @@ async function TrustChainsHandler( acc.push(block.hash) return acc }, + // @ts-ignore [] as string[] ) diff --git a/src/handler/state/queryEntityHandler.ts b/src/handler/state/queryEntityHandler.ts new file mode 100644 index 0000000..adfc307 --- /dev/null +++ b/src/handler/state/queryEntityHandler.ts @@ -0,0 +1,38 @@ +import { GuardianMiddlewareRequest } from 'src/context/useGuardianContext' +import { NextApiResponse } from 'next' +import applyFilters from 'src/utils/query' +import { EntityState, QueryBlockTag } from 'src/config/guardianTags' + +async function QueryEntityHandler( + req: GuardianMiddlewareRequest, + res: NextApiResponse +) { + const { policyId, entity, status, ...filters } = req.query + const { engine } = req.context + const { accessToken } = req + + const queryData = await engine.fetchBlockSubmissions( + accessToken, + policyId as string, + QueryBlockTag[entity as string] + ) + + const entityStatus = + status && EntityState[(status as EntityState).toUpperCase()] + const ensureArray = (item) => (Array.isArray(item) ? item : [item]) + + const filteredData = applyFilters( + ensureArray(queryData.data), + filters, + entityStatus + ) + + const result = { + count: filteredData.length, + result: filteredData, + } + + res.json(result) +} + +export default QueryEntityHandler diff --git a/src/middleware/ensureQueryDataRole.ts b/src/middleware/ensureQueryDataRole.ts new file mode 100644 index 0000000..dcf5b82 --- /dev/null +++ b/src/middleware/ensureQueryDataRole.ts @@ -0,0 +1,32 @@ +import { NextApiHandler, NextApiResponse } from 'next' +import { GuardianMiddlewareRequest } from 'src/context/useGuardianContext' +import Response from 'src/response' +import { QueryRoute, QueryRole } from 'src/config/guardianTags' +import Format from 'src/utils/format' +import Language from 'src/constants/language' +import ensureRole from './ensureRole' + +const ensureQueryDataRole = + (handler: NextApiHandler) => + async (req: GuardianMiddlewareRequest, res: NextApiResponse) => { + const { entity } = req.query + const entityQuery = entity as QueryRoute + + const { queryResponse } = Language.middleware + + const isValid = Object.values(QueryRoute).includes(entityQuery) + + if (!isValid) { + const validValues = Format.joinWithComma(Object.values(QueryRoute)) + + return Response.unprocessibleEntity( + queryResponse.invalidType(validValues) + ) + } + + const role = QueryRole[entityQuery] + + return ensureRole(role)(handler)(req, res) + } + +export default ensureQueryDataRole diff --git a/src/middleware/exceptionFilter.ts b/src/middleware/exceptionFilter.ts index ab2c9ff..0b1b8a3 100644 --- a/src/middleware/exceptionFilter.ts +++ b/src/middleware/exceptionFilter.ts @@ -59,12 +59,33 @@ function getIsGuardianException(exception: AxiosError) { return requestBaseUrl.host === guardianBaseUrl.host } +function jsonValidationParse(exception: AxiosError) { + const message = exception?.response?.data?.message + + console.log(exception?.response?.data) + + if (!message) { + return null + } + + const JSON_VALID_ERROR = 'JSON_SCHEMA_VALIDATION_ERROR' + + // console.log(message) + if (message.includes(JSON_VALID_ERROR)) { + const error = JSON.parse(message.split('Error: ')[1]) + + return error.details + } + + return null +} + function exceptionFilter(handler: NextApiHandler) { return async (req: NextApiRequest, res: NextApiResponse): Promise => { try { await handler(req, res) } catch (exception) { - const { url, headers } = req + const { url, headers, body } = req const statusCode = getExceptionStatus(exception) const message = getExceptionMessage(exception) @@ -99,19 +120,26 @@ function exceptionFilter(handler: NextApiHandler) { console.debug(stack) } + // TODO: Remove Temporary JSON validation check + const jsonValidationErrors = jsonValidationParse(exception) + const timestamp = new Date().toISOString() + const updatedStatusCode = jsonValidationErrors ? 422 : statusCode + const responseBody: ErrorApiResponse = { error: { message, - statusCode, + // TODO: Temporary JSON validation error catch (to delete) + statusCode: updatedStatusCode, timestamp, + jsonValidationErrors, path: req.url, ...(errors ? { errors } : {}), }, } - res.status(statusCode).send(responseBody) + res.status(updatedStatusCode).send(responseBody) } } } diff --git a/src/spec/openapi.json b/src/spec/openapi.json index ac1931b..949e7ba 100644 --- a/src/spec/openapi.json +++ b/src/spec/openapi.json @@ -1,11 +1,11 @@ { "openapi": "3.0.2", "info": { - "title": "Guardian Middleware API", - "description": "A simplified API for interacting with the Guardian API - https://docs.hedera.com/guardian", - "version": "0.0.1-alpha", + "title": "DOVU Digital Credit Middleware: Simplified Ecological Asset Integration", + "description": "This API serves as a pivotal middleware platform, interfacing seamlessly with the Hedera Hashgraph Guardian to empower the efficient management and standardization of ecological credit lifecycles. By converging the technical capabilities of the Guardian with DOVU's ecological digital credit standards, we offer an expedited, cost-effective route to green asset development. Sidestepping the high investment typically required for custom solutions, DOVU's operating system ensures rapid deployment and adherence to consistent standards, freeing users to concentrate on environmental impact without infrastructural overhead. Streamlining the complexities of ecological credit transactions, DOVU's middleware API is the key to unlocking a sustainable future with ease and precision.", + "version": "0.1.0", "contact": { - "name": "DOVU", + "name": "DOVU Support", "url": "https://www.dovu.earth", "email": "support@dovu.earth" } @@ -627,10 +627,20 @@ ], "tags": [ { - "name": "accounts" + "name": "accounts", + "description": "This section of the API deals with account management. It allows users to create accounts, authenticate, and manage their details. Endpoints under this tag are essential for ensuring that only authorized users can access the system and perform actions according to their roles." }, { - "name": "policies" + "name": "policies", + "description": "This tag addresses the management of policies related to ecological and carbon credit lifecycle. It includes creating policies, assigning roles, and managing submissions for sites and claims. Each submission must reference back to the originating document or entity, ensuring a traceable and verifiable workflow. This reference mechanism is facilitated through the state API endpoints, which allow users to query and manage the status of different components within a policy's lifecycle. These endpoints are key to maintaining a coherent and auditable trail from project inception through to credit issuance." + }, + { + "name": "approval", + "description": "The approval endpoints facilitate the progression of entities through their lifecycle within a policy's scope. These endpoints are pivotal for transitioning projects, sites, and claims to their subsequent stages by authorized roles. When a site or claim is approved, the ID provided in the endpoint is a direct reference to the state tracked by the system, ensuring that all actions are aligned with the entity's current status. This allows for a streamlined, traceable, and consistent approval process, simplifying the integration of various lifecycle stages within the policy framework." + }, + { + "name": "state", + "description": "The state tag facilitates a focused overview of a policy's lifecycle, tailored to the roles interacting with it. By segmenting access to lifecycle stages, the tag ensures that suppliers, verifiers, and other stakeholders can securely access only the data relevant to their function. This role-based access empowers stakeholders to make informed decisions and take appropriate actions within the policy workflow. It simplifies the complex ecosystem, providing a clear path through the policy's progress while upholding the integrity of the trust chain. Use role-specific endpoints like /policies/{policyId}/state/{entityState} to seamlessly navigate and manage the states pertinent to your role in the workflow." } ], "paths": { @@ -716,7 +726,7 @@ "/policies/{policyId}/role/{roleType}": { "post": { "tags": ["policies"], - "summary": "Assigns account to policy with a given role", + "summary": "The standard registry to assign an account to a policy with a given role", "parameters": [ { "name": "policyId", @@ -732,7 +742,7 @@ "in": "path", "schema": { "type": "string", - "enum": ["registrant", "verifier"] + "enum": ["supplier", "verifier"] }, "required": true, "example": "registrant" @@ -782,15 +792,15 @@ } } }, - "/policies/{policyId}/register": { + "/policies/{policyId}/projects": { "post": { "tags": ["policies"], - "summary": "Registers a new project application from DOVU", + "summary": "A Supplier can register a new ecological project, this is considered the 'root' level definition", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ProjectRegistration" + "$ref": "#/components/schemas/EcologicalProject" } } } @@ -846,19 +856,22 @@ }, "422": { "$ref": "#/components/responses/422" + }, + "401": { + "$ref": "#/components/responses/401" } } } }, - "/policies/{policyId}/project": { + "/policies/{policyId}/projects/{id}/sites": { "post": { "tags": ["policies"], - "summary": "Registers a new Ecological Project from DOVU", + "summary": "A supplier can register a new site that is connected to an approved project for a policy", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EcologicalProject" + "$ref": "#/components/schemas/ProjectRegistration" } } } @@ -914,17 +927,23 @@ }, "422": { "$ref": "#/components/responses/422" - }, - "401": { - "$ref": "#/components/responses/401" } } } }, - "/policies/{policyId}/token": { - "get": { + "/policies/{policyId}/sites/{id}/claims": { + "post": { "tags": ["policies"], - "summary": "Get the token id connected to a policy", + "summary": "A supplier can submit a new claim with MRV data points as evidence to prove alignment to a methodology.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MeasurementReportingVerification" + } + } + } + }, "parameters": [ { "name": "policyId", @@ -934,6 +953,25 @@ }, "required": true, "example": "62d98142a8075c6027dfcf90" + }, + { + "name": "mrv_type", + "in": "path", + "schema": { + "enum": [ + "agrecalc", + "cool-farm-tool", + "general-supply-documentation" + ] + }, + "required": true, + "example": "agrecalc" + }, + { + "name": "id", + "in": "path", + "required": true, + "example": "6543d0149494a2ae74af310a" } ], "responses": { @@ -983,10 +1021,11 @@ } } }, - "/policies/{policyId}/mrv/{mrv_type}": { + "/policies/{policyId}/sites/{id}/claim/{mrv_type}": { "post": { "tags": ["policies"], - "summary": "Submits a new MRV document to a given EP", + "description": "to be deprecated.", + "summary": "[DEPRECATED] A supplier can submit a new claim with MRV data points as evidence to prove alignment to a methodology.", "requestBody": { "content": { "application/json": { @@ -1018,6 +1057,74 @@ }, "required": true, "example": "agrecalc" + }, + { + "name": "id", + "in": "path", + "required": true, + "example": "6543d0149494a2ae74af310a" + } + ], + "responses": { + "200": { + "description": "OK", + "headers": { + "Date": { + "schema": { + "type": "string", + "example": "Mon, 25 Jul 2022 16:08:13 GMT" + } + }, + "Connection": { + "schema": { + "type": "string", + "example": "keep-alive" + } + }, + "Keep-Alive": { + "schema": { + "type": "string", + "example": "timeout=5" + } + }, + "Transfer-Encoding": { + "schema": { + "type": "string", + "example": "chunked" + } + } + }, + "content": { + "text/plain": { + "schema": { + "type": "string" + }, + "example": null + } + } + }, + "422": { + "$ref": "#/components/responses/422" + }, + "401": { + "$ref": "#/components/responses/401" + } + } + } + }, + "/policies/{policyId}/token": { + "get": { + "tags": ["policies"], + "summary": "Get the token id connected to a policy", + "parameters": [ + { + "name": "policyId", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "62d98142a8075c6027dfcf90" } ], "responses": { @@ -1309,10 +1416,10 @@ } } }, - "/policies/{policyId}/approve/application/{did}": { + "/policies/{policyId}/approval/projects/{id}": { "put": { "tags": ["approval"], - "summary": "Approves an application through a Standard Registry", + "summary": "A standard registry can approve a new supplier created project with all details.", "parameters": [ { "name": "policyId", @@ -1324,13 +1431,13 @@ "example": "62d98142a8075c6027dfcf90" }, { - "name": "did", + "name": "id", "in": "path", "schema": { "type": "string" }, "required": true, - "example": "did:hedera:testnet:DNenc37ybL93AmGhEhNrujUvLGQ2cC93JcZgFXVt5iYE;hedera:testnet:tid=0.0.47859693" + "example": "6543d0149494a2ae74af310a" } ], "responses": { @@ -1380,10 +1487,10 @@ } } }, - "/policies/{policyId}/approve/project/{did}": { + "/policies/{policyId}/approval/sites/{id}": { "put": { "tags": ["approval"], - "summary": "Approves an Ecological Project through a Standard Registry", + "summary": "A standard registry can approve a site that is connected to an approved project for a supplier", "parameters": [ { "name": "policyId", @@ -1451,10 +1558,10 @@ } } }, - "/policies/{policyId}/approve/mrv/{did}": { + "/policies/{policyId}/approval/claims/{id}": { "put": { "tags": ["approval"], - "summary": "Approves an MRV submission connected to a EP through a Verifier", + "summary": " A verifier can approve a claim (MRV) submission connected to a particular site for a project, the registry cannot approve a claim.", "parameters": [ { "name": "policyId", @@ -1521,6 +1628,427 @@ } } } + }, + "/policies/{policyId}/state/projects": { + "get": { + "tags": ["state"], + "summary": "Allow the standard registry to get the current status of any, and all projects that have been submitted to a DOVU standard policy", + "parameters": [ + { + "name": "policyId", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "62d98142a8075c6027dfcf90" + }, + { + "name": "status", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "approved" + }, + { + "name": "filter0", + "description": "One or more specific fields that refer to the previous/current document submission for the trustchain", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "uuid" + } + ], + "responses": { + "200": { + "description": "OK", + "headers": { + "Date": { + "schema": { + "type": "string", + "example": "Mon, 25 Jul 2022 16:08:13 GMT" + } + }, + "Connection": { + "schema": { + "type": "string", + "example": "keep-alive" + } + }, + "Keep-Alive": { + "schema": { + "type": "string", + "example": "timeout=5" + } + }, + "Transfer-Encoding": { + "schema": { + "type": "string", + "example": "chunked" + } + } + }, + "content": { + "text/plain": { + "schema": { + "type": "string" + }, + "example": null + } + } + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "404": { + "$ref": "#/components/responses/404" + } + } + } + }, + "/policies/{policyId}/state/approve-site": { + "get": { + "tags": ["state"], + "summary": "Allow the standard registry to get the current projects to connect to a related site to be approved.", + "parameters": [ + { + "name": "policyId", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "62d98142a8075c6027dfcf90" + }, + { + "name": "status", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "approved" + }, + { + "name": "filter0", + "description": "One or more specific fields that refer to the previous/current document submission for the trustchain", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "uuid" + } + ], + "responses": { + "200": { + "description": "OK", + "headers": { + "Date": { + "schema": { + "type": "string", + "example": "Mon, 25 Jul 2022 16:08:13 GMT" + } + }, + "Connection": { + "schema": { + "type": "string", + "example": "keep-alive" + } + }, + "Keep-Alive": { + "schema": { + "type": "string", + "example": "timeout=5" + } + }, + "Transfer-Encoding": { + "schema": { + "type": "string", + "example": "chunked" + } + } + }, + "content": { + "text/plain": { + "schema": { + "type": "string" + }, + "example": null + } + } + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "404": { + "$ref": "#/components/responses/404" + } + } + } + }, + "/policies/{policyId}/state/create-site": { + "get": { + "tags": ["state"], + "summary": "Allow the supplier role to get access to all projects before the create site submission process, to connect a project to a brand-new site.", + "parameters": [ + { + "name": "policyId", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "62d98142a8075c6027dfcf90" + }, + { + "name": "status", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "approved" + }, + { + "name": "filter0", + "description": "One or more specific fields that refer to the previous/current document submission for the trustchain", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "uuid" + } + ], + "responses": { + "200": { + "description": "OK", + "headers": { + "Date": { + "schema": { + "type": "string", + "example": "Mon, 25 Jul 2022 16:08:13 GMT" + } + }, + "Connection": { + "schema": { + "type": "string", + "example": "keep-alive" + } + }, + "Keep-Alive": { + "schema": { + "type": "string", + "example": "timeout=5" + } + }, + "Transfer-Encoding": { + "schema": { + "type": "string", + "example": "chunked" + } + } + }, + "content": { + "text/plain": { + "schema": { + "type": "string" + }, + "example": null + } + } + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "404": { + "$ref": "#/components/responses/404" + } + } + } + }, + "/policies/{policyId}/state/create-claim": { + "get": { + "tags": ["state"], + "summary": "Allow the Supplier role to get access to all sites that have been approved before creating a new claim to formulate the flow between a project, site, and a (MRV) claim.", + "parameters": [ + { + "name": "policyId", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "62d98142a8075c6027dfcf90" + }, + { + "name": "status", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "approved" + }, + { + "name": "filter0", + "description": "One or more specific fields that refer to the previous/current document submission for the trustchain", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "uuid" + } + ], + "responses": { + "200": { + "description": "OK", + "headers": { + "Date": { + "schema": { + "type": "string", + "example": "Mon, 25 Jul 2022 16:08:13 GMT" + } + }, + "Connection": { + "schema": { + "type": "string", + "example": "keep-alive" + } + }, + "Keep-Alive": { + "schema": { + "type": "string", + "example": "timeout=5" + } + }, + "Transfer-Encoding": { + "schema": { + "type": "string", + "example": "chunked" + } + } + }, + "content": { + "text/plain": { + "schema": { + "type": "string" + }, + "example": null + } + } + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "404": { + "$ref": "#/components/responses/404" + } + } + } + }, + "/policies/{policyId}/state/approve-claim": { + "get": { + "tags": ["state"], + "summary": "Allow the verfier Role in the system, the ability to check all of the claims that have been submitted through a supplier to approve before the minting of new digitial ecological tokens.", + "parameters": [ + { + "name": "policyId", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "62d98142a8075c6027dfcf90" + }, + { + "name": "status", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "approved" + }, + { + "name": "filter0", + "description": "One or more specific fields that refer to the previous/current document submission for the trustchain", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "uuid" + } + ], + "responses": { + "200": { + "description": "OK", + "headers": { + "Date": { + "schema": { + "type": "string", + "example": "Mon, 25 Jul 2022 16:08:13 GMT" + } + }, + "Connection": { + "schema": { + "type": "string", + "example": "keep-alive" + } + }, + "Keep-Alive": { + "schema": { + "type": "string", + "example": "timeout=5" + } + }, + "Transfer-Encoding": { + "schema": { + "type": "string", + "example": "chunked" + } + } + }, + "content": { + "text/plain": { + "schema": { + "type": "string" + }, + "example": null + } + } + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "404": { + "$ref": "#/components/responses/404" + } + } + } } + } } diff --git a/src/spec/openapi.ts b/src/spec/openapi.ts index 8bbddbf..e4edaa0 100644 --- a/src/spec/openapi.ts +++ b/src/spec/openapi.ts @@ -33,7 +33,7 @@ export interface paths { parameters: { path: { policyId: string - roleType: 'registrant' | 'verifier' + roleType: 'supplier' | 'verifier' } } responses: { @@ -53,7 +53,7 @@ export interface paths { } } } - '/policies/{policyId}/register': { + '/policies/{policyId}/projects': { post: { parameters: { path: { @@ -73,16 +73,17 @@ export interface paths { 'text/plain': string } } + 401: components['responses']['401'] 422: components['responses']['422'] } requestBody: { content: { - 'application/json': components['schemas']['ProjectRegistration'] + 'application/json': components['schemas']['EcologicalProject'] } } } } - '/policies/{policyId}/project': { + '/policies/{policyId}/projects/{id}/sites': { post: { parameters: { path: { @@ -102,21 +103,25 @@ export interface paths { 'text/plain': string } } - 401: components['responses']['401'] 422: components['responses']['422'] } requestBody: { content: { - 'application/json': components['schemas']['EcologicalProject'] + 'application/json': components['schemas']['ProjectRegistration'] } } } } - '/policies/{policyId}/token': { - get: { + '/policies/{policyId}/sites/{id}/claims': { + post: { parameters: { path: { policyId: string + mrv_type: + | 'agrecalc' + | 'cool-farm-tool' + | 'general-supply-documentation' + id: unknown } } responses: { @@ -135,9 +140,15 @@ export interface paths { 401: components['responses']['401'] 422: components['responses']['422'] } + requestBody: { + content: { + 'application/json': components['schemas']['MeasurementReportingVerification'] + } + } } } - '/policies/{policyId}/mrv/{mrv_type}': { + '/policies/{policyId}/sites/{id}/claim/{mrv_type}': { + /** to be deprecated. */ post: { parameters: { path: { @@ -146,6 +157,7 @@ export interface paths { | 'agrecalc' | 'cool-farm-tool' | 'general-supply-documentation' + id: unknown } } responses: { @@ -171,6 +183,31 @@ export interface paths { } } } + '/policies/{policyId}/token': { + get: { + parameters: { + path: { + policyId: string + } + } + responses: { + /** OK */ + 200: { + headers: { + Date?: string + Connection?: string + 'Keep-Alive'?: string + 'Transfer-Encoding'?: string + } + content: { + 'text/plain': string + } + } + 401: components['responses']['401'] + 422: components['responses']['422'] + } + } + } '/policies/{policyId}/trustchains': { get: { parameters: { @@ -194,12 +231,12 @@ export interface paths { } } } - '/policies/{policyId}/approve/application/{did}': { + '/policies/{policyId}/approval/projects/{id}': { put: { parameters: { path: { policyId: string - did: string + id: string } } responses: { @@ -220,7 +257,7 @@ export interface paths { } } } - '/policies/{policyId}/approve/project/{did}': { + '/policies/{policyId}/approval/sites/{id}': { put: { parameters: { path: { @@ -246,7 +283,7 @@ export interface paths { } } } - '/policies/{policyId}/approve/mrv/{did}': { + '/policies/{policyId}/approval/claims/{id}': { put: { parameters: { path: { @@ -272,6 +309,151 @@ export interface paths { } } } + '/policies/{policyId}/state/projects': { + get: { + parameters: { + path: { + policyId: string + status: string + /** One or more specific fields that refer to the previous/current document submission for the trustchain */ + filter0: string + } + } + responses: { + /** OK */ + 200: { + headers: { + Date?: string + Connection?: string + 'Keep-Alive'?: string + 'Transfer-Encoding'?: string + } + content: { + 'text/plain': string + } + } + 401: components['responses']['401'] + 404: components['responses']['404'] + 422: components['responses']['422'] + } + } + } + '/policies/{policyId}/state/approve-site': { + get: { + parameters: { + path: { + policyId: string + status: string + /** One or more specific fields that refer to the previous/current document submission for the trustchain */ + filter0: string + } + } + responses: { + /** OK */ + 200: { + headers: { + Date?: string + Connection?: string + 'Keep-Alive'?: string + 'Transfer-Encoding'?: string + } + content: { + 'text/plain': string + } + } + 401: components['responses']['401'] + 404: components['responses']['404'] + 422: components['responses']['422'] + } + } + } + '/policies/{policyId}/state/create-site': { + get: { + parameters: { + path: { + policyId: string + status: string + /** One or more specific fields that refer to the previous/current document submission for the trustchain */ + filter0: string + } + } + responses: { + /** OK */ + 200: { + headers: { + Date?: string + Connection?: string + 'Keep-Alive'?: string + 'Transfer-Encoding'?: string + } + content: { + 'text/plain': string + } + } + 401: components['responses']['401'] + 404: components['responses']['404'] + 422: components['responses']['422'] + } + } + } + '/policies/{policyId}/state/create-claim': { + get: { + parameters: { + path: { + policyId: string + status: string + /** One or more specific fields that refer to the previous/current document submission for the trustchain */ + filter0: string + } + } + responses: { + /** OK */ + 200: { + headers: { + Date?: string + Connection?: string + 'Keep-Alive'?: string + 'Transfer-Encoding'?: string + } + content: { + 'text/plain': string + } + } + 401: components['responses']['401'] + 404: components['responses']['404'] + 422: components['responses']['422'] + } + } + } + '/policies/{policyId}/state/approve-claim': { + get: { + parameters: { + path: { + policyId: string + status: string + /** One or more specific fields that refer to the previous/current document submission for the trustchain */ + filter0: string + } + } + responses: { + /** OK */ + 200: { + headers: { + Date?: string + Connection?: string + 'Keep-Alive'?: string + 'Transfer-Encoding'?: string + } + content: { + 'text/plain': string + } + } + 401: components['responses']['401'] + 404: components['responses']['404'] + 422: components['responses']['422'] + } + } + } } export interface components { @@ -318,6 +500,7 @@ export interface components { statusCode: number timestamp: string path: string + jsonValidationErrors?: string[] errors?: string[] } } diff --git a/src/utils/format.ts b/src/utils/format.ts new file mode 100644 index 0000000..62e38a9 --- /dev/null +++ b/src/utils/format.ts @@ -0,0 +1,48 @@ +/** + * Joins elements of an array into a string with a custom delimiter, and a + * different final delimiter before the last element. This function is useful + * for creating human-readable lists from array elements. + * + * @param {string[]} array - The array of strings to be joined. + * @param {string} delimiter - The delimiter to use between all elements + * except the last two. + * @param {string} finalDelimiter - The delimiter to use between the second-to-last + * and last element. + * @returns {string} A string formed by joining the array elements, using the specified + * delimiters between elements, and especially between the last two. + * + * @example + * // returns 'apple, orange, or banana' + * joinWithFinalDelimiter(['apple', 'orange', 'banana'], ', ', ', or '); + */ +function joinWithFinalDelimiter( + array: string[], + delimiter: string, + finalDelimiter: string +): string { + if (array.length <= 1) return array.join('') + const last = array.pop() + return `${array.join(delimiter)}${finalDelimiter}${last}` +} + +/** + * Joins elements of an array into a human-readable list separated by commas, + * with 'or' before the last element. This function is a specialized wrapper + * around 'joinWithFinalDelimiter', specifically tailored for lists where + * elements are typically separated by commas, and 'or' is used before the last element. + * + * @param {string[]} array - The array of strings to be joined into a list. + * @returns {string} A string representing a comma-separated list with 'or' + * before the last element. + * + * @example + * // returns 'apple, orange, or banana' + * joinWithComma(['apple', 'orange', 'banana']); + */ +function joinWithComma(array: string[]): string { + return joinWithFinalDelimiter(array, ', ', ', or ') +} + +export default { + joinWithComma, +} diff --git a/src/utils/query.ts b/src/utils/query.ts new file mode 100644 index 0000000..9bbfba2 --- /dev/null +++ b/src/utils/query.ts @@ -0,0 +1,38 @@ +import { EntityState } from 'src/config/guardianTags' + +/** + * Filters an array of objects based on specified criteria and an optional entity state. + * If the entity state is provided, only items matching the state are included. + * If the entity state is null or undefined, all items are included regardless of their state. + * + * @param {any[]} data - The array of objects to be filtered. + * @param {{ [key: string]: any }} filters - An object representing the filter criteria. + * Each key-value pair corresponds to a property + * and its desired value within the nested objects + * of the 'credentialSubject' array. + * @param {EntityState | null} [entityState=falsely] - An optional parameter to filter items + * based on their entity state. If null or + * undefined, items are not filtered by state. + * @returns {any[]} An array containing only those objects from the input 'data' array that meet + * all the filter criteria and, if specified, match the entity state. + * + * @example + * // Example usage + * const filteredData = applyFilters(data, yourFilters, EntityState.APPROVED); + */ +const applyFilters = ( + data: any[], + filters: { [key: string]: any }, + status: EntityState | null +): any[] => + data.filter( + (item) => + (!status || item?.option?.status === status) && + item?.document?.credentialSubject.some((nestedItem) => + Object.keys(filters).every( + (filterKey) => nestedItem[filterKey] === filters[filterKey] + ) + ) + ) + +export default applyFilters