From 140c1228dd9e2140942f2a9cd788d0bf344f3cac Mon Sep 17 00:00:00 2001 From: nigiri <168690269+0xnigir1@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:02:56 -0300 Subject: [PATCH] feat: application repository kisely impl --- packages/repository/src/db/connection.ts | 13 +- packages/repository/src/external.ts | 8 +- .../applicationRepository.interface.ts | 72 +++++++++++ packages/repository/src/interfaces/index.ts | 1 + .../interfaces/roundRepository.interface.ts | 5 +- .../kysely/application.repository.ts | 121 ++++++++++++++++++ .../src/repositories/kysely/index.ts | 1 + .../repositories/kysely/round.repository.ts | 18 ++- .../repository/src/types/application.types.ts | 2 - .../repository/src/types/changeset.types.ts | 28 ++-- packages/repository/src/types/index.ts | 1 + packages/shared/src/external.ts | 2 + packages/shared/src/internal.ts | 1 + 13 files changed, 245 insertions(+), 28 deletions(-) create mode 100644 packages/repository/src/interfaces/applicationRepository.interface.ts create mode 100644 packages/repository/src/repositories/kysely/application.repository.ts diff --git a/packages/repository/src/db/connection.ts b/packages/repository/src/db/connection.ts index c415edb..6f2cd51 100644 --- a/packages/repository/src/db/connection.ts +++ b/packages/repository/src/db/connection.ts @@ -1,19 +1,29 @@ -import { CamelCasePlugin, Kysely, PostgresDialect } from "kysely"; +import { CamelCasePlugin, ColumnType, Kysely, PostgresDialect } from "kysely"; import { Pool, PoolConfig } from "pg"; import { + Application, PendingProjectRole as PendingProjectRoleTable, PendingRoundRole as PendingRoundRoleTable, ProjectRole as ProjectRoleTable, Project as ProjectTable, RoundRole as RoundRoleTable, Round as RoundTable, + StatusSnapshot, } from "../internal.js"; export interface DatabaseConfig extends PoolConfig { connectionString: string; } +type ApplicationTable = Omit & { + statusSnapshots: ColumnType< + StatusSnapshot[], + StatusSnapshot[] | string, + StatusSnapshot[] | string + >; +}; + export interface Database { rounds: RoundTable; pendingRoundRoles: PendingRoundRoleTable; @@ -21,6 +31,7 @@ export interface Database { projects: ProjectTable; pendingProjectRoles: PendingProjectRoleTable; projectRoles: ProjectRoleTable; + applications: ApplicationTable; } /** diff --git a/packages/repository/src/external.ts b/packages/repository/src/external.ts index 5a6ea55..8dbf22a 100644 --- a/packages/repository/src/external.ts +++ b/packages/repository/src/external.ts @@ -4,6 +4,8 @@ export type { IRoundReadRepository, IProjectRepository, IProjectReadRepository, + IApplicationRepository, + IApplicationReadRepository, DatabaseConfig, } from "./internal.js"; @@ -35,6 +37,10 @@ export type { export type { Changeset } from "./types/index.js"; -export { KyselyRoundRepository, KyselyProjectRepository } from "./repositories/kysely/index.js"; +export { + KyselyRoundRepository, + KyselyProjectRepository, + KyselyApplicationRepository, +} from "./repositories/kysely/index.js"; export { createKyselyPostgresDb as createKyselyDatabase } from "./internal.js"; diff --git a/packages/repository/src/interfaces/applicationRepository.interface.ts b/packages/repository/src/interfaces/applicationRepository.interface.ts new file mode 100644 index 0000000..627fe8c --- /dev/null +++ b/packages/repository/src/interfaces/applicationRepository.interface.ts @@ -0,0 +1,72 @@ +import { Address, ChainId } from "@grants-stack-indexer/shared"; + +import { Application, NewApplication, PartialApplication } from "../types/application.types.js"; + +export interface IApplicationReadRepository { + /** + * Retrieves a specific application by its ID, chain ID, and round ID. + * @param id The ID of the application. + * @param chainId The chain ID of the application. + * @param roundId The round ID of the application. + * @returns A promise that resolves to an Application object if found, or undefined if not found. + */ + getApplicationById( + id: string, + chainId: ChainId, + roundId: string, + ): Promise; + + /** + * Retrieves a specific application by its chain ID, round ID, and project ID. + * @param chainId The chain ID of the application. + * @param roundId The round ID of the application. + * @param projectId The project ID of the application. + * @returns A promise that resolves to an Application object if found, or undefined if not found. + */ + getApplicationByProjectId( + chainId: ChainId, + roundId: string, + projectId: string, + ): Promise; + + /** + * Retrieves a specific application by its chain ID, round ID, and anchor address. + * @param chainId The chain ID of the application. + * @param roundId The round ID of the application. + * @param anchorAddress The anchor address of the application. + * @returns A promise that resolves to an Application object if found, or undefined if not found. + */ + getApplicationByAnchorAddress( + chainId: ChainId, + roundId: string, + anchorAddress: Address, + ): Promise; + + /** + * Retrieves all applications for a given chain ID and round ID. + * @param chainId The chain ID of the applications. + * @param roundId The round ID of the applications. + * @returns A promise that resolves to an array of Application objects. + */ + getApplicationsByRoundId(chainId: ChainId, roundId: string): Promise; +} + +export interface IApplicationRepository extends IApplicationReadRepository { + /** + * Inserts a new application into the repository. + * @param application The new application to insert. + * @returns A promise that resolves when the insertion is complete. + */ + insertApplication(application: NewApplication): Promise; + + /** + * Updates an existing application in the repository. + * @param where An object containing the (id, chainId, and roundId) of the application to update. + * @param application The partial application data to update. + * @returns A promise that resolves when the update is complete. + */ + updateApplication( + where: { id: string; chainId: ChainId; roundId: string }, + application: PartialApplication, + ): Promise; +} diff --git a/packages/repository/src/interfaces/index.ts b/packages/repository/src/interfaces/index.ts index f9900d3..a40ec10 100644 --- a/packages/repository/src/interfaces/index.ts +++ b/packages/repository/src/interfaces/index.ts @@ -1,2 +1,3 @@ export * from "./projectRepository.interface.js"; export * from "./roundRepository.interface.js"; +export * from "./applicationRepository.interface.js"; diff --git a/packages/repository/src/interfaces/roundRepository.interface.ts b/packages/repository/src/interfaces/roundRepository.interface.ts index 8cc2792..f937ee7 100644 --- a/packages/repository/src/interfaces/roundRepository.interface.ts +++ b/packages/repository/src/interfaces/roundRepository.interface.ts @@ -91,7 +91,10 @@ export interface IRoundRepository extends IRoundReadRepository { * @param round The partial round data to update. * @returns A promise that resolves when the update is complete. */ - updateRound(where: { id: string; chainId: ChainId }, round: PartialRound): Promise; + updateRound( + where: { id: string; chainId: ChainId } | { chainId: ChainId; strategyAddress: Address }, + round: PartialRound, + ): Promise; /** * Increments the funds for a specific round. diff --git a/packages/repository/src/repositories/kysely/application.repository.ts b/packages/repository/src/repositories/kysely/application.repository.ts new file mode 100644 index 0000000..05a3c21 --- /dev/null +++ b/packages/repository/src/repositories/kysely/application.repository.ts @@ -0,0 +1,121 @@ +import { Kysely } from "kysely"; + +import { Address, ChainId, stringify } from "@grants-stack-indexer/shared"; + +import { + Application, + Database, + IApplicationRepository, + NewApplication, + PartialApplication, +} from "../../internal.js"; + +export class KyselyApplicationRepository implements IApplicationRepository { + constructor( + private readonly db: Kysely, + private readonly schemaName: string, + ) {} + + /* @inheritdoc */ + async getApplicationById( + id: string, + chainId: ChainId, + roundId: string, + ): Promise { + return this.db + .withSchema(this.schemaName) + .selectFrom("applications") + .where("id", "=", id) + .where("chainId", "=", chainId) + .where("roundId", "=", roundId) + .selectAll() + .executeTakeFirst(); + } + + /* @inheritdoc */ + async getApplicationByProjectId( + chainId: ChainId, + roundId: string, + projectId: string, + ): Promise { + return this.db + .withSchema(this.schemaName) + .selectFrom("applications") + .where("chainId", "=", chainId) + .where("roundId", "=", roundId) + .where("projectId", "=", projectId) + .selectAll() + .executeTakeFirst(); + } + + /* @inheritdoc */ + async getApplicationByAnchorAddress( + chainId: ChainId, + roundId: string, + anchorAddress: Address, + ): Promise { + return this.db + .withSchema(this.schemaName) + .selectFrom("applications") + .where("chainId", "=", chainId) + .where("roundId", "=", roundId) + .where("anchorAddress", "=", anchorAddress) + .selectAll() + .executeTakeFirst(); + } + + /* @inheritdoc */ + async getApplicationsByRoundId(chainId: ChainId, roundId: string): Promise { + return this.db + .withSchema(this.schemaName) + .selectFrom("applications") + .where("chainId", "=", chainId) + .where("roundId", "=", roundId) + .selectAll() + .execute(); + } + + /* @inheritdoc */ + async insertApplication(application: NewApplication): Promise { + const _application = this.formatApplication(application); + + await this.db + .withSchema(this.schemaName) + .insertInto("applications") + .values(_application) + .execute(); + } + + /* @inheritdoc */ + async updateApplication( + where: { id: string; chainId: ChainId; roundId: string }, + application: PartialApplication, + ): Promise { + const _application = this.formatApplication(application); + + await this.db + .withSchema(this.schemaName) + .updateTable("applications") + .set(_application) + .where("id", "=", where.id) + .where("chainId", "=", where.chainId) + .where("roundId", "=", where.roundId) + .execute(); + } + + /** + * Formats the application to ensure that the statusSnapshots are stored as a JSON string. + * Also, properly handles BigInt stringification. + * @param application - The application to format. + * @returns The formatted application. + */ + private formatApplication(application: T): T { + if (application?.statusSnapshots) { + application = { + ...application, + statusSnapshots: stringify(application.statusSnapshots), + }; + } + return application; + } +} diff --git a/packages/repository/src/repositories/kysely/index.ts b/packages/repository/src/repositories/kysely/index.ts index 092ff8d..75d6410 100644 --- a/packages/repository/src/repositories/kysely/index.ts +++ b/packages/repository/src/repositories/kysely/index.ts @@ -1,2 +1,3 @@ export * from "./project.repository.js"; export * from "./round.repository.js"; +export * from "./application.repository.js"; diff --git a/packages/repository/src/repositories/kysely/round.repository.ts b/packages/repository/src/repositories/kysely/round.repository.ts index 66d409b..d3103c2 100644 --- a/packages/repository/src/repositories/kysely/round.repository.ts +++ b/packages/repository/src/repositories/kysely/round.repository.ts @@ -94,14 +94,22 @@ export class KyselyRoundRepository implements IRoundRepository { await this.db.withSchema(this.schemaName).insertInto("rounds").values(round).execute(); } - async updateRound(where: { id: string; chainId: ChainId }, round: PartialRound): Promise { - await this.db + /* @inheritdoc */ + async updateRound( + where: { id: string; chainId: ChainId } | { chainId: ChainId; strategyAddress: Address }, + round: PartialRound, + ): Promise { + const query = this.db .withSchema(this.schemaName) .updateTable("rounds") .set(round) - .where("id", "=", where.id) - .where("chainId", "=", where.chainId) - .execute(); + .where("chainId", "=", where.chainId); + + if ("id" in where) { + await query.where("id", "=", where.id).execute(); + } else { + await query.where("strategyAddress", "=", where.strategyAddress).execute(); + } } /* @inheritdoc */ diff --git a/packages/repository/src/types/application.types.ts b/packages/repository/src/types/application.types.ts index 62453d1..02df074 100644 --- a/packages/repository/src/types/application.types.ts +++ b/packages/repository/src/types/application.types.ts @@ -31,5 +31,3 @@ export type Application = { export type NewApplication = Application; export type PartialApplication = Partial; - -//TODO: create the corresponding repository implementation diff --git a/packages/repository/src/types/changeset.types.ts b/packages/repository/src/types/changeset.types.ts index 3704fde..b2d305c 100644 --- a/packages/repository/src/types/changeset.types.ts +++ b/packages/repository/src/types/changeset.types.ts @@ -1,6 +1,6 @@ import type { Address, ChainId } from "@grants-stack-indexer/shared"; -import { NewApplication } from "./application.types.js"; +import { NewApplication, PartialApplication } from "./application.types.js"; import { NewPendingProjectRole, NewProject, @@ -92,14 +92,6 @@ export type Changeset = fundedAmountInUsd: string; }; } - | { - type: "IncrementRoundDonationStats"; - args: { - chainId: ChainId; - roundId: Address; - amountInUsd: string; - }; - } | { type: "IncrementRoundTotalDistributed"; args: { @@ -108,15 +100,6 @@ export type Changeset = amount: bigint; }; } - | { - type: "IncrementApplicationDonationStats"; - args: { - chainId: ChainId; - roundId: Address; - applicationId: string; - amountInUsd: number; - }; - } | { type: "InsertPendingRoundRole"; args: { @@ -144,4 +127,13 @@ export type Changeset = | { type: "InsertApplication"; args: NewApplication; + } + | { + type: "UpdateApplication"; + args: { + chainId: ChainId; + roundId: string; + applicationId: string; + application: PartialApplication; + }; }; diff --git a/packages/repository/src/types/index.ts b/packages/repository/src/types/index.ts index b183e3c..5eaee5f 100644 --- a/packages/repository/src/types/index.ts +++ b/packages/repository/src/types/index.ts @@ -1,3 +1,4 @@ export * from "./project.types.js"; export * from "./round.types.js"; +export * from "./application.types.js"; export * from "./changeset.types.js"; diff --git a/packages/shared/src/external.ts b/packages/shared/src/external.ts index 0216b8e..0c449d0 100644 --- a/packages/shared/src/external.ts +++ b/packages/shared/src/external.ts @@ -17,3 +17,5 @@ export type { BigNumberType } from "./internal.js"; export type { TokenCode, Token } from "./internal.js"; export { TOKENS, getToken } from "./tokens/tokens.js"; + +export { stringify } from "./internal.js"; diff --git a/packages/shared/src/internal.ts b/packages/shared/src/internal.ts index 8a37a26..fae9241 100644 --- a/packages/shared/src/internal.ts +++ b/packages/shared/src/internal.ts @@ -1,4 +1,5 @@ export type { Address } from "viem"; +export { stringify } from "viem/utils"; export * from "./math/bignumber.js"; export * from "./types/index.js"; export * from "./constants/index.js";