diff --git a/webserver/server/app/controllers/GovernanceVotesByActionIdsController.ts b/webserver/server/app/controllers/GovernanceVotesByActionIdsController.ts new file mode 100644 index 00000000..19a4b773 --- /dev/null +++ b/webserver/server/app/controllers/GovernanceVotesByActionIdsController.ts @@ -0,0 +1,88 @@ +import { Body, Controller, TsoaResponse, Res, Post, Route, SuccessResponse } from 'tsoa'; +import { StatusCodes } from 'http-status-codes'; +import tx from 'pg-tx'; +import pool from '../services/PgPoolSingleton'; +import type { ErrorShape } from '../../../shared/errors'; +import { genErrorMessage } from '../../../shared/errors'; +import { Errors } from '../../../shared/errors'; +import type { EndpointTypes } from '../../../shared/routes'; +import { Routes } from '../../../shared/routes'; +import { resolveUntilTransaction } from '../services/PaginationService'; +import { expectType } from 'tsd'; +import { governanceCredentialDidVote } from '../services/GovernanceCredentialVotesByActionIds'; +import { GOVERNANCE_VOTES_BY_GOV_IDS_LIMIT } from '../../../shared/constants'; + +const route = Routes.governanceCredentialVotesByGovActionId; + +@Route('governance/credential/votesByGovId') +export class GovernanceCredentialVotesByGovId extends Controller { + /** + * Gets votes cast for a set of governance action ids. + */ + @SuccessResponse(`${StatusCodes.OK}`) + @Post() + public async governanceCredentialDidVote( + @Body() + requestBody: EndpointTypes[typeof route]['input'], + @Res() + errorResponse: TsoaResponse< + StatusCodes.BAD_REQUEST | StatusCodes.CONFLICT | StatusCodes.UNPROCESSABLE_ENTITY, + ErrorShape + > + ): Promise { + + if (requestBody.actionIds.length > GOVERNANCE_VOTES_BY_GOV_IDS_LIMIT.MAX_ACTION_IDS) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return errorResponse( + StatusCodes.BAD_REQUEST, + genErrorMessage(Errors.GovActionIdsLimitExceeded, { + limit: GOVERNANCE_VOTES_BY_GOV_IDS_LIMIT.MAX_ACTION_IDS, + found: requestBody.actionIds.length, + }) + ); + } + + let credential = Buffer.from(requestBody.credential, 'hex'); + + const response = await tx( + pool, + async dbTx => { + const until = await resolveUntilTransaction({ + block_hash: Buffer.from(requestBody.untilBlock, 'hex'), + dbTx, + }); + + if (until == null) { + return genErrorMessage(Errors.BlockHashNotFound, { + untilBlock: requestBody.untilBlock, + }); + } + + if(requestBody.actionIds.length === 0) { + return []; + } + + const data = await governanceCredentialDidVote({ + credential, + govActionIds: requestBody.actionIds.map(actionId => Buffer.from(actionId, 'hex')), + until: until.tx_id, + dbTx, + }); + + return data.map(vote => ({ + actionId: vote.govActionId.toString('hex'), + txId: vote.txId, + payload: vote.vote.toString('hex'), + })); + } + ); + + if ('code' in response) { + expectType>(true); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return errorResponse(StatusCodes.CONFLICT, response); + } + + return response; + } +} diff --git a/webserver/server/app/controllers/GovernanceVotesForAddressController.ts b/webserver/server/app/controllers/GovernanceVotesForAddressController.ts index 3b391ecc..b18c2e19 100644 --- a/webserver/server/app/controllers/GovernanceVotesForAddressController.ts +++ b/webserver/server/app/controllers/GovernanceVotesForAddressController.ts @@ -15,7 +15,7 @@ import { GovernanceVotesForAddressResponse } from '../../../shared/models/Govern const route = Routes.governanceVotesForAddress; -@Route('governance/votes/address') +@Route('governance/credential/votes') export class GovernanceVotesForAddress extends Controller { /** * Returns the drep of the last delegation for this address. diff --git a/webserver/server/app/models/governance/votesForAddress.queries.ts b/webserver/server/app/models/governance/votesForAddress.queries.ts index 935788e0..c85d00a2 100644 --- a/webserver/server/app/models/governance/votesForAddress.queries.ts +++ b/webserver/server/app/models/governance/votesForAddress.queries.ts @@ -1,8 +1,6 @@ /** Types generated for queries found in "app/models/governance/votesForAddress.sql" */ import { PreparedQuery } from '@pgtyped/runtime'; -export type BufferArray = (Buffer)[]; - export type Json = null | boolean | number | string | Json[] | { [key: string]: Json }; export type NumberOrString = number | string; @@ -59,16 +57,16 @@ export const votesForAddress = new PreparedQuery(didVoteIR); diff --git a/webserver/server/app/models/governance/votesForAddress.sql b/webserver/server/app/models/governance/votesForAddress.sql index cfcb7fe2..c40d4be7 100644 --- a/webserver/server/app/models/governance/votesForAddress.sql +++ b/webserver/server/app/models/governance/votesForAddress.sql @@ -24,12 +24,11 @@ LIMIT :limit!; @name didVote @param gov_action_ids -> (...) */ -SELECT gov_action_id, vote +SELECT gov_action_id as "govActionId!", vote as "vote!", "Transaction".id as "txId!" FROM "GovernanceVote" JOIN "Transaction" ON "GovernanceVote".tx_id = "Transaction".id WHERE "Transaction".id <= :until_tx_id! AND voter = :voter! AND - gov_action_id = ANY(:gov_action_ids!) -ORDER BY "Transaction".id -LIMIT :limit!; \ No newline at end of file + gov_action_id IN :gov_action_ids! +ORDER BY "Transaction".id; \ No newline at end of file diff --git a/webserver/server/app/services/GovernanceCredentialVotesByActionIds.ts b/webserver/server/app/services/GovernanceCredentialVotesByActionIds.ts new file mode 100644 index 00000000..a83be3a3 --- /dev/null +++ b/webserver/server/app/services/GovernanceCredentialVotesByActionIds.ts @@ -0,0 +1,24 @@ +import type { PoolClient } from 'pg'; +import { + didVote, + IDidVoteResult, +} from '../models/governance/votesForAddress.queries'; + +export async function governanceCredentialDidVote(request: { + credential: Buffer; + govActionIds: Buffer[]; + dbTx: PoolClient; + until: number; +}): Promise { + return ( + await didVote.run( + { + voter: request.credential, + gov_action_ids: request.govActionIds, + until_tx_id: request.until, + }, + request.dbTx + ) + ); +} + diff --git a/webserver/shared/models/Governance.ts b/webserver/shared/models/Governance.ts index 702fbb62..6941267d 100644 --- a/webserver/shared/models/Governance.ts +++ b/webserver/shared/models/Governance.ts @@ -4,10 +4,22 @@ import { AfterBlockPagination, UntilBlockPagination } from "./common"; export type GovernanceVotesForAddressRequest = { credential: CredentialHex; limit?: number | undefined; -} & UntilBlockPagination & AfterBlockPagination; +} & UntilBlockPagination & + AfterBlockPagination; export type GovernanceVotesForAddressResponse = { votes: { govActionId: string; vote: string }[]; txId: string; block: string; }[]; + +export type GovernanceCredentialDidVoteRequest = { + credential: CredentialHex; + actionIds: string[]; +} & UntilBlockPagination; + +export type GovernanceCredentialDidVoteResponse = { + actionId: string; + txId: string; + payload: string; +}[];