Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/agenstra/backend-agent-controller/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,12 @@
"executor": "nx:run-commands",
"outputs": ["{workspaceRoot}/dist/clients/agent-controller/js"],
"options": {
"command": "npx @openapitools/openapi-generator-cli generate -i libs/domains/agenstra/backend/feature-agent-controller/spec/openapi.yaml -g typescript-axios -o dist/clients/agent-controller/js --additional-properties=npmName=@forepath/agenstra-agent-controller-client,npmVersion=1.0.0,withInterfaces=true && cd dist/clients/agent-controller/js && npm install && npm run build"
"command": "npx @openapitools/openapi-generator-cli generate -i libs/domains/agenstra/backend/feature-agent-controller/spec/openapi.yaml -g typescript-axios -o dist/clients/agent-controller/js --additional-properties=npmName=@forepath/agenstra-agent-controller-client,npmVersion=1.0.0,withInterfaces=true,axiosVersion=1.18.1 && cd dist/clients/agent-controller/js && npm install && npm run build"
},
"configurations": {
"test": {},
"release": {
"command": "npx @openapitools/openapi-generator-cli generate -i libs/domains/agenstra/backend/feature-agent-controller/spec/openapi.yaml -g typescript-axios -o dist/clients/agent-controller/js --additional-properties=npmName=@forepath/agenstra-agent-controller-client,npmVersion=$VERSION,withInterfaces=true && cd dist/clients/agent-controller/js && node -e \"const fs=require('fs');const pkg=JSON.parse(fs.readFileSync('package.json'));pkg.publishConfig={registry:'https://npm.pkg.github.com'};fs.writeFileSync('package.json',JSON.stringify(pkg,null,2));\" && npm install && npm run build && npm publish"
"command": "npx @openapitools/openapi-generator-cli generate -i libs/domains/agenstra/backend/feature-agent-controller/spec/openapi.yaml -g typescript-axios -o dist/clients/agent-controller/js --additional-properties=npmName=@forepath/agenstra-agent-controller-client,npmVersion=$VERSION,withInterfaces=true,axiosVersion=1.18.1 && cd dist/clients/agent-controller/js && node -e \"const fs=require('fs');const pkg=JSON.parse(fs.readFileSync('package.json'));pkg.publishConfig={registry:'https://npm.pkg.github.com'};fs.writeFileSync('package.json',JSON.stringify(pkg,null,2));\" && npm install && npm run build && npm publish"
}
},
"defaultConfiguration": "test"
Expand Down
4 changes: 2 additions & 2 deletions apps/agenstra/backend-agent-manager/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,12 @@
"executor": "nx:run-commands",
"outputs": ["{workspaceRoot}/dist/clients/agent-manager/js"],
"options": {
"command": "npx @openapitools/openapi-generator-cli generate -i libs/domains/agenstra/backend/feature-agent-manager/spec/openapi.yaml -g typescript-axios -o dist/clients/agent-manager/js --additional-properties=npmName=@forepath/agenstra-agent-manager-client,npmVersion=1.0.0,withInterfaces=true && cd dist/clients/agent-manager/js && npm install && npm run build"
"command": "npx @openapitools/openapi-generator-cli generate -i libs/domains/agenstra/backend/feature-agent-manager/spec/openapi.yaml -g typescript-axios -o dist/clients/agent-manager/js --additional-properties=npmName=@forepath/agenstra-agent-manager-client,npmVersion=1.0.0,withInterfaces=true,axiosVersion=1.18.1 && cd dist/clients/agent-manager/js && npm install && npm run build"
},
"configurations": {
"test": {},
"release": {
"command": "npx @openapitools/openapi-generator-cli generate -i libs/domains/agenstra/backend/feature-agent-manager/spec/openapi.yaml -g typescript-axios -o dist/clients/agent-manager/js --additional-properties=npmName=@forepath/agenstra-agent-manager-client,npmVersion=$VERSION,withInterfaces=true && cd dist/clients/agent-manager/js && node -e \"const fs=require('fs');const pkg=JSON.parse(fs.readFileSync('package.json'));pkg.publishConfig={registry:'https://npm.pkg.github.com'};fs.writeFileSync('package.json',JSON.stringify(pkg,null,2));\" && npm install && npm run build && npm publish"
"command": "npx @openapitools/openapi-generator-cli generate -i libs/domains/agenstra/backend/feature-agent-manager/spec/openapi.yaml -g typescript-axios -o dist/clients/agent-manager/js --additional-properties=npmName=@forepath/agenstra-agent-manager-client,npmVersion=$VERSION,withInterfaces=true,axiosVersion=1.18.1 && cd dist/clients/agent-manager/js && node -e \"const fs=require('fs');const pkg=JSON.parse(fs.readFileSync('package.json'));pkg.publishConfig={registry:'https://npm.pkg.github.com'};fs.writeFileSync('package.json',JSON.stringify(pkg,null,2));\" && npm install && npm run build && npm publish"
}
},
"defaultConfiguration": "test"
Expand Down
4 changes: 2 additions & 2 deletions apps/decabill/backend-billing-manager/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,12 @@
"executor": "nx:run-commands",
"outputs": ["{workspaceRoot}/dist/clients/billing/js"],
"options": {
"command": "npx @openapitools/openapi-generator-cli generate -i libs/domains/decabill/backend/feature-billing-manager/spec/openapi.yaml -g typescript-axios -o dist/clients/billing-manager/js --additional-properties=npmName=@forepath/decabill-billing-manager-client,npmVersion=1.0.0,withInterfaces=true && cd dist/clients/billing-manager/js && npm install && npm run build"
"command": "npx @openapitools/openapi-generator-cli generate -i libs/domains/decabill/backend/feature-billing-manager/spec/openapi.yaml -g typescript-axios -o dist/clients/billing-manager/js --additional-properties=npmName=@forepath/decabill-billing-manager-client,npmVersion=1.0.0,withInterfaces=true,axiosVersion=1.18.1 && cd dist/clients/billing-manager/js && npm install && npm run build"
},
"configurations": {
"test": {},
"release": {
"command": "npx @openapitools/openapi-generator-cli generate -i libs/domains/decabill/backend/feature-billing-manager/spec/openapi.yaml -g typescript-axios -o dist/clients/billing-manager/js --additional-properties=npmName=@forepath/decabill-billing-manager-client,npmVersion=$VERSION,withInterfaces=true && cd dist/clients/billing-manager/js && node -e \"const fs=require('fs');const pkg=JSON.parse(fs.readFileSync('package.json'));pkg.publishConfig={registry:'https://npm.pkg.github.com'};fs.writeFileSync('package.json',JSON.stringify(pkg,null,2));\" && npm install && npm run build && npm publish"
"command": "npx @openapitools/openapi-generator-cli generate -i libs/domains/decabill/backend/feature-billing-manager/spec/openapi.yaml -g typescript-axios -o dist/clients/billing-manager/js --additional-properties=npmName=@forepath/decabill-billing-manager-client,npmVersion=$VERSION,withInterfaces=true,axiosVersion=1.18.1 && cd dist/clients/billing-manager/js && node -e \"const fs=require('fs');const pkg=JSON.parse(fs.readFileSync('package.json'));pkg.publishConfig={registry:'https://npm.pkg.github.com'};fs.writeFileSync('package.json',JSON.stringify(pkg,null,2));\" && npm install && npm run build && npm publish"
}
},
"defaultConfiguration": "test"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class CreateMeterTables1775900000000 implements MigrationInterface {
name = 'CreateMeterTables1775900000000';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE IF NOT EXISTS "billing_meters" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"tenant_id" character varying(64) NOT NULL DEFAULT 'default',
"key" character varying(100) NOT NULL,
"name" character varying(255) NOT NULL,
"description" text,
"unit_label" character varying(64),
"aggregator" character varying(16) NOT NULL,
"default_unit_price_net" numeric(12,4) NOT NULL DEFAULT 0,
"is_active" boolean NOT NULL DEFAULT true,
"created_at" TIMESTAMP NOT NULL DEFAULT now(),
"updated_at" TIMESTAMP NOT NULL DEFAULT now(),
CONSTRAINT "PK_billing_meters" PRIMARY KEY ("id"),
CONSTRAINT "uq_billing_meters_tenant_key" UNIQUE ("tenant_id", "key")
)
`);

await queryRunner.query(`
CREATE INDEX IF NOT EXISTS "IDX_billing_meters_tenant_active"
ON "billing_meters" ("tenant_id", "is_active")
`);

await queryRunner.query(`
CREATE TABLE IF NOT EXISTS "billing_service_plan_meters" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"service_plan_id" uuid NOT NULL,
"meter_id" uuid NOT NULL,
"unit_price_net" numeric(12,4),
"created_at" TIMESTAMP NOT NULL DEFAULT now(),
"updated_at" TIMESTAMP NOT NULL DEFAULT now(),
CONSTRAINT "PK_billing_service_plan_meters" PRIMARY KEY ("id"),
CONSTRAINT "uq_billing_service_plan_meters_plan_meter" UNIQUE ("service_plan_id", "meter_id"),
CONSTRAINT "FK_billing_service_plan_meters_plan"
FOREIGN KEY ("service_plan_id") REFERENCES "billing_service_plans"("id") ON DELETE CASCADE,
CONSTRAINT "FK_billing_service_plan_meters_meter"
FOREIGN KEY ("meter_id") REFERENCES "billing_meters"("id") ON DELETE RESTRICT
)
`);

await queryRunner.query(`
CREATE INDEX IF NOT EXISTS "IDX_billing_service_plan_meters_plan"
ON "billing_service_plan_meters" ("service_plan_id")
`);

await queryRunner.query(`
CREATE TABLE IF NOT EXISTS "billing_addon_meters" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"addon_id" uuid NOT NULL,
"meter_id" uuid NOT NULL,
"unit_price_net" numeric(12,4),
"created_at" TIMESTAMP NOT NULL DEFAULT now(),
"updated_at" TIMESTAMP NOT NULL DEFAULT now(),
CONSTRAINT "PK_billing_addon_meters" PRIMARY KEY ("id"),
CONSTRAINT "uq_billing_addon_meters_addon_meter" UNIQUE ("addon_id", "meter_id"),
CONSTRAINT "FK_billing_addon_meters_addon"
FOREIGN KEY ("addon_id") REFERENCES "billing_addons"("id") ON DELETE CASCADE,
CONSTRAINT "FK_billing_addon_meters_meter"
FOREIGN KEY ("meter_id") REFERENCES "billing_meters"("id") ON DELETE RESTRICT
)
`);

await queryRunner.query(`
CREATE INDEX IF NOT EXISTS "IDX_billing_addon_meters_addon"
ON "billing_addon_meters" ("addon_id")
`);

await queryRunner.query(`
ALTER TABLE "billing_usage_records"
ADD COLUMN IF NOT EXISTS "meter_id" uuid,
ADD COLUMN IF NOT EXISTS "value" numeric(18,6),
ADD COLUMN IF NOT EXISTS "attachment_type" character varying(16),
ADD COLUMN IF NOT EXISTS "addon_id" uuid
`);

await queryRunner.query(`
DO $$ BEGIN
ALTER TABLE "billing_usage_records"
ADD CONSTRAINT "FK_billing_usage_records_meter"
FOREIGN KEY ("meter_id") REFERENCES "billing_meters"("id") ON DELETE RESTRICT;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$
`);

await queryRunner.query(`
DO $$ BEGIN
ALTER TABLE "billing_usage_records"
ADD CONSTRAINT "FK_billing_usage_records_addon"
FOREIGN KEY ("addon_id") REFERENCES "billing_addons"("id") ON DELETE RESTRICT;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$
`);

await queryRunner.query(`
CREATE INDEX IF NOT EXISTS "IDX_billing_usage_records_subscription_meter"
ON "billing_usage_records" ("subscription_id", "meter_id", "attachment_type")
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_billing_usage_records_subscription_meter"`);
await queryRunner.query(`
ALTER TABLE "billing_usage_records" DROP CONSTRAINT IF EXISTS "FK_billing_usage_records_addon"
`);
await queryRunner.query(`
ALTER TABLE "billing_usage_records" DROP CONSTRAINT IF EXISTS "FK_billing_usage_records_meter"
`);
await queryRunner.query(`
ALTER TABLE "billing_usage_records"
DROP COLUMN IF EXISTS "addon_id",
DROP COLUMN IF EXISTS "attachment_type",
DROP COLUMN IF EXISTS "value",
DROP COLUMN IF EXISTS "meter_id"
`);
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_billing_addon_meters_addon"`);
await queryRunner.query(`DROP TABLE IF EXISTS "billing_addon_meters"`);
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_billing_service_plan_meters_plan"`);
await queryRunner.query(`DROP TABLE IF EXISTS "billing_service_plan_meters"`);
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_billing_meters_tenant_active"`);
await queryRunner.query(`DROP TABLE IF EXISTS "billing_meters"`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class DeclaredRequiredMeters1776000000000 implements MigrationInterface {
name = 'DeclaredRequiredMeters1776000000000';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "billing_service_plan_meters"
ADD COLUMN IF NOT EXISTS "source" character varying(16) NOT NULL DEFAULT 'manual',
ADD COLUMN IF NOT EXISTS "required" boolean NOT NULL DEFAULT false
`);

await queryRunner.query(`
ALTER TABLE "billing_addon_meters"
ADD COLUMN IF NOT EXISTS "source" character varying(16) NOT NULL DEFAULT 'manual',
ADD COLUMN IF NOT EXISTS "required" boolean NOT NULL DEFAULT false
`);

await queryRunner.query(`
CREATE TABLE IF NOT EXISTS "billing_service_type_meters" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"service_type_id" uuid NOT NULL,
"meter_id" uuid NOT NULL,
"unit_price_net" numeric(12,4),
"source" character varying(16) NOT NULL DEFAULT 'manual',
"required" boolean NOT NULL DEFAULT false,
"created_at" TIMESTAMP NOT NULL DEFAULT now(),
"updated_at" TIMESTAMP NOT NULL DEFAULT now(),
CONSTRAINT "PK_billing_service_type_meters" PRIMARY KEY ("id"),
CONSTRAINT "uq_billing_service_type_meters_type_meter" UNIQUE ("service_type_id", "meter_id"),
CONSTRAINT "FK_billing_service_type_meters_type"
FOREIGN KEY ("service_type_id") REFERENCES "billing_service_types"("id") ON DELETE CASCADE,
CONSTRAINT "FK_billing_service_type_meters_meter"
FOREIGN KEY ("meter_id") REFERENCES "billing_meters"("id") ON DELETE RESTRICT
)
`);

await queryRunner.query(`
CREATE INDEX IF NOT EXISTS "IDX_billing_service_type_meters_type"
ON "billing_service_type_meters" ("service_type_id")
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_billing_service_type_meters_type"`);
await queryRunner.query(`DROP TABLE IF EXISTS "billing_service_type_meters"`);
await queryRunner.query(`
ALTER TABLE "billing_addon_meters"
DROP COLUMN IF EXISTS "required",
DROP COLUMN IF EXISTS "source"
`);
await queryRunner.query(`
ALTER TABLE "billing_service_plan_meters"
DROP COLUMN IF EXISTS "required",
DROP COLUMN IF EXISTS "source"
`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class WidenMeterAggregatorColumn1776200000000 implements MigrationInterface {
name = 'WidenMeterAggregatorColumn1776200000000';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "billing_meters"
ALTER COLUMN "aggregator" TYPE character varying(32)
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "billing_meters"
ALTER COLUMN "aggregator" TYPE character varying(16)
`);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,28 @@ describe('billing job-registry', () => {
delete process.env.BILLING_PRICE_RECALC_ENABLED;
});

it('getBillingRepeatableJobs includes optional meter-collect coordinator every 60s', () => {
delete process.env.BILLING_METER_COLLECT_ENABLED;
delete process.env.BILLING_METER_COLLECT_INTERVAL;
const withMeterCollect = getBillingRepeatableJobs();
expect(withMeterCollect.map((job) => job.name)).toContain(BillingJobName.METER_COLLECT_COORDINATOR);

const meterCollectJob = withMeterCollect.find((job) => job.name === BillingJobName.METER_COLLECT_COORDINATOR);
expect(meterCollectJob?.everyMs).toBe(60_000);

process.env.BILLING_METER_COLLECT_INTERVAL = '120000';
const customInterval = getBillingRepeatableJobs().find(
(job) => job.name === BillingJobName.METER_COLLECT_COORDINATOR,
);
expect(customInterval?.everyMs).toBe(120_000);
delete process.env.BILLING_METER_COLLECT_INTERVAL;

process.env.BILLING_METER_COLLECT_ENABLED = 'false';
const withoutMeterCollect = getBillingRepeatableJobs();
expect(withoutMeterCollect.map((job) => job.name)).not.toContain(BillingJobName.METER_COLLECT_COORDINATOR);
delete process.env.BILLING_METER_COLLECT_ENABLED;
});

it('getBillingRepeatableJobs includes update check coordinator', () => {
const jobs = getBillingRepeatableJobs();
const updateCheckJob = jobs.find((job) => job.name === BillingJobName.UPDATE_CHECK);
Expand Down
10 changes: 10 additions & 0 deletions apps/decabill/backend-billing-manager/src/queue/job-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export const BillingJobName = {
PRICE_RECALC_COORDINATOR: 'price-recalc.coordinator',
PRICE_RECALC_UNIT: 'price-recalc.unit',
PLAN_PRICE_MIGRATE_UNIT: 'plan-price-migrate.unit',
METER_COLLECT_COORDINATOR: 'meter-collect.coordinator',
METER_COLLECT_UNIT: 'meter-collect.unit',
VAT_ID_VALIDATION_UNIT: VatIdValidationJobName.UNIT,
WEBHOOK_DELIVERY_RETENTION_COORDINATOR,
UPDATE_CHECK: UPDATE_CHECK_JOB_NAME,
Expand Down Expand Up @@ -172,6 +174,14 @@ export function getBillingRepeatableJobs(): BillingRepeatableJobDefinition[] {
});
}

if (parseBooleanEnv('BILLING_METER_COLLECT_ENABLED', true)) {
jobs.push({
name: BillingJobName.METER_COLLECT_COORDINATOR,
coordinatorJobId: buildCoordinatorJobId('meter-collect'),
everyMs: parseIntervalMs('BILLING_METER_COLLECT_INTERVAL', 60_000),
});
}

jobs.push({
name: BillingJobName.UPDATE_CHECK,
coordinatorJobId: buildCoordinatorJobId('update-check'),
Expand Down
Loading
Loading