diff --git a/components/gitpod-db/src/container-module.ts b/components/gitpod-db/src/container-module.ts index e63a7e6dd4dc3c..87bc10af010b83 100644 --- a/components/gitpod-db/src/container-module.ts +++ b/components/gitpod-db/src/container-module.ts @@ -67,6 +67,8 @@ import { TypeORMBlockedRepositoryDBImpl } from "./typeorm/blocked-repository-db- import { BlockedRepositoryDB } from "./blocked-repository-db"; import { WebhookEventDB } from "./webhook-event-db"; import { WebhookEventDBImpl } from "./typeorm/webhook-event-db-impl"; +import { CostCenterDB } from "./cost-center-db"; +import { CostCenterDBImpl } from "./typeorm/cost-center-db-impl"; // THE DB container module that contains all DB implementations export const dbContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => { @@ -144,6 +146,9 @@ export const dbContainerModule = new ContainerModule((bind, unbind, isBound, reb bind(WebhookEventDBImpl).toSelf().inSingletonScope(); bind(WebhookEventDB).toService(WebhookEventDBImpl); + bind(CostCenterDBImpl).toSelf().inSingletonScope(); + bind(CostCenterDB).toService(CostCenterDBImpl); + // com concerns bind(AccountingDB).to(TypeORMAccountingDBImpl).inSingletonScope(); bind(TransactionalAccountingDBFactory).toFactory((ctx) => { diff --git a/components/gitpod-db/src/cost-center-db.ts b/components/gitpod-db/src/cost-center-db.ts new file mode 100644 index 00000000000000..419ba00f530e64 --- /dev/null +++ b/components/gitpod-db/src/cost-center-db.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2022 Gitpod GmbH. All rights reserved. + * Licensed under the Gitpod Enterprise Source Code License, + * See License.enterprise.txt in the project root folder. + */ + +import { CostCenter } from "@gitpod/gitpod-protocol"; + +export const CostCenterDB = Symbol("CostCenterDB"); +export interface CostCenterDB { + storeEntry(ts: CostCenter): Promise; + findById(id: string): Promise; +} diff --git a/components/gitpod-db/src/index.ts b/components/gitpod-db/src/index.ts index a19b888e7e5652..785de419ccb6d9 100644 --- a/components/gitpod-db/src/index.ts +++ b/components/gitpod-db/src/index.ts @@ -39,3 +39,4 @@ export * from "./project-db"; export * from "./team-db"; export * from "./installation-admin-db"; export * from "./webhook-event-db"; +export * from "./cost-center-db"; diff --git a/components/gitpod-db/src/tables.ts b/components/gitpod-db/src/tables.ts index 065dbe3a0e692d..b6795442068c1f 100644 --- a/components/gitpod-db/src/tables.ts +++ b/components/gitpod-db/src/tables.ts @@ -280,6 +280,12 @@ export class GitpodTableDescriptionProvider implements TableDescriptionProvider deletionColumn: "deleted", timeColumn: "_lastModified", }, + { + name: "d_b_cost_center", + primaryKeys: ["id"], + deletionColumn: "deleted", + timeColumn: "_lastModified", + }, ]; public getSortedTables(): TableDescription[] { diff --git a/components/gitpod-db/src/typeorm/cost-center-db-impl.ts b/components/gitpod-db/src/typeorm/cost-center-db-impl.ts new file mode 100644 index 00000000000000..9c7df6b1421b01 --- /dev/null +++ b/components/gitpod-db/src/typeorm/cost-center-db-impl.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2022 Gitpod GmbH. All rights reserved. + * Licensed under the Gitpod Enterprise Source Code License, + * See License.enterprise.txt in the project root folder. + */ + +import { injectable, inject } from "inversify"; +import { Repository } from "typeorm"; + +import { CostCenter } from "@gitpod/gitpod-protocol"; + +import { CostCenterDB } from "../cost-center-db"; +import { DBCostCenter } from "./entity/db-cost-center"; +import { TypeORM } from "./typeorm"; + +@injectable() +export class CostCenterDBImpl implements CostCenterDB { + @inject(TypeORM) protected readonly typeORM: TypeORM; + + protected async getEntityManager() { + return (await this.typeORM.getConnection()).manager; + } + + protected async getRepo(): Promise> { + return (await this.getEntityManager()).getRepository(DBCostCenter); + } + + async storeEntry(ts: CostCenter): Promise { + const repo = await this.getRepo(); + await repo.save(ts); + } + + async findById(id: string): Promise { + const repo = await this.getRepo(); + return repo.findOne(id); + } +} diff --git a/components/gitpod-db/src/typeorm/entity/db-cost-center.ts b/components/gitpod-db/src/typeorm/entity/db-cost-center.ts new file mode 100644 index 00000000000000..9457972eb9c879 --- /dev/null +++ b/components/gitpod-db/src/typeorm/entity/db-cost-center.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2022 Gitpod GmbH. All rights reserved. + * Licensed under the Gitpod Enterprise Source Code License, + * See License.enterprise.txt in the project root folder. + */ + +import { Entity, Column, PrimaryColumn } from "typeorm"; +import { CostCenter } from "@gitpod/gitpod-protocol"; + +@Entity() +// on DB but not Typeorm: @Index("ind_lastModified", ["_lastModified"]) // DBSync +export class DBCostCenter implements CostCenter { + @PrimaryColumn() + id: string; + + @Column() + spendingLimit: number; + + // This column triggers the db-sync deletion mechanism. It's not intended for public consumption. + @Column() + deleted: boolean; +} diff --git a/components/gitpod-db/src/typeorm/migration/1658241900000-CostCenter.ts b/components/gitpod-db/src/typeorm/migration/1658241900000-CostCenter.ts new file mode 100644 index 00000000000000..32369b4eb7ea75 --- /dev/null +++ b/components/gitpod-db/src/typeorm/migration/1658241900000-CostCenter.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2022 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CostCenter1658241900000 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + "CREATE TABLE IF NOT EXISTS `d_b_cost_center` (`id` char(255) NOT NULL, `spendingLimit` int(11) NOT NULL DEFAULT '0', `deleted` tinyint(4) NOT NULL DEFAULT '0', `_lastModified` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), PRIMARY KEY (`id`), KEY `ind_dbsync` (`_lastModified`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;", + ); + } + + public async down(queryRunner: QueryRunner): Promise {} +} diff --git a/components/gitpod-protocol/src/protocol.ts b/components/gitpod-protocol/src/protocol.ts index 2da4d97b5c1127..785f9a1f9aeafe 100644 --- a/components/gitpod-protocol/src/protocol.ts +++ b/components/gitpod-protocol/src/protocol.ts @@ -1481,3 +1481,11 @@ export interface Terms { readonly content: string; readonly formElements?: object; } + +export interface CostCenter { + readonly id: string; + /** + * Unit: credits + */ + spendingLimit: number; +} diff --git a/components/server/src/user/user-service.ts b/components/server/src/user/user-service.ts index 9dfe7b79e28b03..8359261eb0b666 100644 --- a/components/server/src/user/user-service.ts +++ b/components/server/src/user/user-service.ts @@ -16,7 +16,7 @@ import { WORKSPACE_TIMEOUT_EXTENDED, WORKSPACE_TIMEOUT_EXTENDED_ALT, } from "@gitpod/gitpod-protocol"; -import { ProjectDB, TermsAcceptanceDB, UserDB } from "@gitpod/gitpod-db/lib"; +import { CostCenterDB, ProjectDB, TermsAcceptanceDB, UserDB } from "@gitpod/gitpod-db/lib"; import { HostContextProvider } from "../auth/host-context-provider"; import { log } from "@gitpod/gitpod-protocol/lib/util/logging"; import { Config } from "../config"; @@ -65,6 +65,7 @@ export class UserService { @inject(TermsAcceptanceDB) protected readonly termsAcceptanceDb: TermsAcceptanceDB; @inject(TermsProvider) protected readonly termsProvider: TermsProvider; @inject(ProjectDB) protected readonly projectDb: ProjectDB; + @inject(CostCenterDB) protected readonly costCenterDb: CostCenterDB; /** * Takes strings in the form of / and returns the matching User