-
Notifications
You must be signed in to change notification settings - Fork 20
feat(indexer/distributions): define distributions database schema #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| -- Migration: 0002_create_distributions_schema | ||
| -- Creates distribution_batch and claim_action tables for the distributions indexer domain. | ||
|
|
||
| CREATE TABLE IF NOT EXISTS schema_migrations ( | ||
| version TEXT NOT NULL PRIMARY KEY, | ||
| applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW() | ||
| ); | ||
|
|
||
| DO $$ | ||
| BEGIN | ||
| IF EXISTS ( | ||
| SELECT 1 FROM schema_migrations WHERE version = '0002_create_distributions_schema' | ||
| ) THEN | ||
| RAISE NOTICE 'Migration 0002_create_distributions_schema already applied, skipping.'; | ||
| RETURN; | ||
| END IF; | ||
|
|
||
| CREATE TABLE distribution_batch ( | ||
| id TEXT NOT NULL, | ||
| contract_id TEXT NOT NULL, | ||
| distributor TEXT NOT NULL, | ||
| token TEXT NOT NULL, | ||
| total_amount NUMERIC(78, 0) NOT NULL, | ||
| claimed_amount NUMERIC(78, 0) NOT NULL DEFAULT 0, | ||
| recipient_count INTEGER NOT NULL DEFAULT 0, | ||
| status TEXT NOT NULL DEFAULT 'active', | ||
| paused_at BIGINT, | ||
| resumed_at BIGINT, | ||
| unique_ref TEXT NOT NULL, | ||
| ledger_number BIGINT NOT NULL, | ||
| tx_hash TEXT NOT NULL, | ||
| created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | ||
| updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | ||
|
|
||
| CONSTRAINT distribution_batch_pkey PRIMARY KEY (id), | ||
| CONSTRAINT distribution_batch_status_chk | ||
| CHECK (status IN ('active', 'paused', 'completed', 'cancelled')), | ||
| CONSTRAINT distribution_batch_total_amount_nonneg CHECK (total_amount >= 0), | ||
| CONSTRAINT distribution_batch_claimed_amount_nonneg CHECK (claimed_amount >= 0), | ||
| CONSTRAINT distribution_batch_recipient_count_nonneg CHECK (recipient_count >= 0) | ||
| ); | ||
|
|
||
| CREATE INDEX distribution_batch_contract_id_idx | ||
| ON distribution_batch (contract_id); | ||
| CREATE INDEX distribution_batch_distributor_idx | ||
| ON distribution_batch (distributor); | ||
| CREATE INDEX distribution_batch_status_idx | ||
| ON distribution_batch (status); | ||
| CREATE INDEX distribution_batch_created_at_idx | ||
| ON distribution_batch (created_at); | ||
| CREATE INDEX distribution_batch_contract_status_idx | ||
| ON distribution_batch (contract_id, status); | ||
|
|
||
| CREATE TABLE claim_action ( | ||
| id UUID NOT NULL DEFAULT gen_random_uuid(), | ||
| batch_id TEXT NOT NULL, | ||
| claimant TEXT NOT NULL, | ||
| amount NUMERIC(78, 0) NOT NULL, | ||
| tx_hash TEXT NOT NULL, | ||
| ledger_number BIGINT NOT NULL, | ||
| event_timestamp BIGINT NOT NULL, | ||
| created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | ||
|
|
||
| CONSTRAINT claim_action_pkey PRIMARY KEY (id), | ||
| CONSTRAINT claim_action_batch_fk | ||
| FOREIGN KEY (batch_id) REFERENCES distribution_batch (id) ON DELETE CASCADE, | ||
| CONSTRAINT claim_action_amount_nonneg CHECK (amount >= 0) | ||
| ); | ||
|
|
||
| CREATE INDEX claim_action_batch_id_idx ON claim_action (batch_id); | ||
| CREATE INDEX claim_action_claimant_idx ON claim_action (claimant); | ||
| CREATE INDEX claim_action_tx_hash_idx ON claim_action (tx_hash); | ||
| CREATE INDEX claim_action_batch_claimant_idx ON claim_action (batch_id, claimant); | ||
|
|
||
| INSERT INTO schema_migrations (version) VALUES ('0002_create_distributions_schema'); | ||
| END; | ||
| $$; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,55 @@ | ||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||
| Column, | ||||||||||||||||||||||||||||||||||||||||
| CreateDateColumn, | ||||||||||||||||||||||||||||||||||||||||
| Entity, | ||||||||||||||||||||||||||||||||||||||||
| Index, | ||||||||||||||||||||||||||||||||||||||||
| JoinColumn, | ||||||||||||||||||||||||||||||||||||||||
| ManyToOne, | ||||||||||||||||||||||||||||||||||||||||
| PrimaryGeneratedColumn, | ||||||||||||||||||||||||||||||||||||||||
| } from "typeorm"; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| import { DistributionBatch } from "./DistributionBatch.js"; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Entity("claim_action") | ||||||||||||||||||||||||||||||||||||||||
| @Index("claim_action_batch_id_idx", ["batchId"]) | ||||||||||||||||||||||||||||||||||||||||
| @Index("claim_action_claimant_idx", ["claimant"]) | ||||||||||||||||||||||||||||||||||||||||
| @Index("claim_action_tx_hash_idx", ["txHash"]) | ||||||||||||||||||||||||||||||||||||||||
| @Index("claim_action_batch_claimant_idx", ["batchId", "claimant"]) | ||||||||||||||||||||||||||||||||||||||||
| export class ClaimAction { | ||||||||||||||||||||||||||||||||||||||||
| @PrimaryGeneratedColumn("uuid") | ||||||||||||||||||||||||||||||||||||||||
| id!: string; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Column({ type: "varchar", comment: "Distribution batch this claim belongs to" }) | ||||||||||||||||||||||||||||||||||||||||
| batchId!: string; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @ManyToOne( | ||||||||||||||||||||||||||||||||||||||||
| () => DistributionBatch, | ||||||||||||||||||||||||||||||||||||||||
| (batch) => batch.claims, | ||||||||||||||||||||||||||||||||||||||||
| { onDelete: "CASCADE" }, | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
| @JoinColumn({ name: "batchId" }) | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+22
to
+30
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ποΈ Data Integrity & Integration | π Major π§© Analysis chainπ Script executed: fd --extension ts --glob '*migration*' --type f | grep -i claimRepository: Fundable-Protocol/Backend Length of output: 163 π Script executed: fd --extension ts --type f "src/db/migrations" | head -20Repository: Fundable-Protocol/Backend Length of output: 163 π Script executed: find . -type f -name "*migration*.ts" -path "*/src/*" | head -20Repository: Fundable-Protocol/Backend Length of output: 199 π Script executed: find . -type d -name "migrations" | head -5Repository: Fundable-Protocol/Backend Length of output: 215 π Script executed: grep -r "batch_id\|batchId" ./src/migrations --include="*.ts" | head -20Repository: Fundable-Protocol/Backend Length of output: 163 π Script executed: grep -r "batch_id\|batchId" ./indexer/common/src/db/migrations --include="*.ts" | head -20Repository: Fundable-Protocol/Backend Length of output: 163 π Script executed: ls -la ./src/migrations/Repository: Fundable-Protocol/Backend Length of output: 438 π Script executed: grep -r "batch_id\|batchId" ./src/migrations ./indexer/common/src/db/migrations --include="*.js" --include="*.ts" 2>/dev/null | head -20Repository: Fundable-Protocol/Backend Length of output: 163 π Script executed: cat ./indexer/distributions/src/db/entity/ClaimAction.tsRepository: Fundable-Protocol/Backend Length of output: 1542 π Script executed: find ./indexer -type f \( -name "*.js" -o -name "*.ts" \) | xargs grep -l "claim_action" | grep -i migration | head -10Repository: Fundable-Protocol/Backend Length of output: 163 π Script executed: find ./indexer -type f \( -name "*migration*" -o -name "*Migrate*" \) | head -10Repository: Fundable-Protocol/Backend Length of output: 163 π Script executed: grep -r "claim_action" --include="*.js" --include="*.ts" . | grep -i "createTable\|addColumn" | head -10Repository: Fundable-Protocol/Backend Length of output: 163 Align the The current code explicitly sets Also update the scalar Suggested fix: Detailed Change `@Index`("claim_action_batch_id_idx", ["batchId"])
`@Index`("claim_action_claimant_idx", ["claimant"])
`@Index`("claim_action_tx_hash_idx", ["txHash"])
`@Index`("claim_action_batch_claimant_idx", ["batchId", "claimant"])
export class ClaimAction {
`@PrimaryGeneratedColumn`("uuid")
id!: string;
- `@Column`({ type: "varchar", comment: "Distribution batch this claim belongs to" })
+ `@Column`({ name: "batch_id", type: "varchar", comment: "Distribution batch this claim belongs to" })
batchId!: string;
`@ManyToOne`(
() => DistributionBatch,
(batch) => batch.claims,
{ onDelete: "CASCADE" },
)
- `@JoinColumn`({ name: "batchId" })
+ `@JoinColumn`({ name: "batch_id" })
batch!: DistributionBatch;π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| batch!: DistributionBatch; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Column({ type: "varchar", comment: "Address that claimed tokens" }) | ||||||||||||||||||||||||||||||||||||||||
| claimant!: string; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Column({ | ||||||||||||||||||||||||||||||||||||||||
| type: "numeric", | ||||||||||||||||||||||||||||||||||||||||
| precision: 78, | ||||||||||||||||||||||||||||||||||||||||
| scale: 0, | ||||||||||||||||||||||||||||||||||||||||
| comment: "Claimed amount in the token's smallest unit", | ||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||
| amount!: string; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Column({ type: "varchar", comment: "Transaction hash where the claim occurred" }) | ||||||||||||||||||||||||||||||||||||||||
| txHash!: string; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Column({ type: "bigint", comment: "Ledger where the claim was recorded" }) | ||||||||||||||||||||||||||||||||||||||||
| ledgerNumber!: string; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Column({ type: "bigint", comment: "On-chain timestamp of the claim" }) | ||||||||||||||||||||||||||||||||||||||||
| eventTimestamp!: string; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @CreateDateColumn() | ||||||||||||||||||||||||||||||||||||||||
| createdAt!: Date; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| import { | ||
| Column, | ||
| CreateDateColumn, | ||
| Entity, | ||
| Index, | ||
| OneToMany, | ||
| PrimaryColumn, | ||
| UpdateDateColumn, | ||
| } from "typeorm"; | ||
|
|
||
| import type { DistributionBatchStatus } from "../status.js"; | ||
| import { ClaimAction } from "./ClaimAction.js"; | ||
|
|
||
| @Entity("distribution_batch") | ||
| @Index("distribution_batch_contract_id_idx", ["contractId"]) | ||
| @Index("distribution_batch_distributor_idx", ["distributor"]) | ||
| @Index("distribution_batch_status_idx", ["status"]) | ||
| @Index("distribution_batch_created_at_idx", ["createdAt"]) | ||
| @Index("distribution_batch_contract_status_idx", ["contractId", "status"]) | ||
| export class DistributionBatch { | ||
| @PrimaryColumn({ | ||
| type: "varchar", | ||
| comment: "Deterministic batch ID from the Soroban contract", | ||
| }) | ||
| id!: string; | ||
|
|
||
| @Column({ type: "varchar", comment: "Soroban contract that emitted the batch" }) | ||
| contractId!: string; | ||
|
|
||
| @Column({ type: "varchar", comment: "Address that created the distribution batch" }) | ||
| distributor!: string; | ||
|
|
||
| @Column({ type: "varchar", comment: "Token asset contract address" }) | ||
| token!: string; | ||
|
|
||
| @Column({ | ||
| type: "numeric", | ||
| precision: 78, | ||
| scale: 0, | ||
| comment: "Total allocated amount in the token's smallest unit", | ||
| }) | ||
| totalAmount!: string; | ||
|
|
||
| @Column({ | ||
| type: "numeric", | ||
| precision: 78, | ||
| scale: 0, | ||
| default: "0", | ||
| comment: "Total amount claimed so far in the token's smallest unit", | ||
| }) | ||
| claimedAmount!: string; | ||
|
|
||
| @Column({ type: "integer", default: 0, comment: "Number of intended recipients" }) | ||
| recipientCount!: number; | ||
|
|
||
| @Column({ | ||
| type: "varchar", | ||
| default: "active", | ||
| comment: "Batch lifecycle status for pause/resume behavior", | ||
| }) | ||
| status!: DistributionBatchStatus; | ||
|
|
||
| @Column({ | ||
| type: "bigint", | ||
| nullable: true, | ||
| comment: "Unix timestamp when the batch was paused", | ||
| }) | ||
| pausedAt!: string | null; | ||
|
|
||
| @Column({ | ||
| type: "bigint", | ||
| nullable: true, | ||
| comment: "Unix timestamp when the batch was last resumed", | ||
| }) | ||
| resumedAt!: string | null; | ||
|
|
||
| @Column({ type: "varchar", comment: "Contract-unique reference for the batch" }) | ||
| uniqueRef!: string; | ||
|
|
||
| @Column({ type: "bigint", comment: "Ledger where the batch was created" }) | ||
| ledgerNumber!: string; | ||
|
|
||
| @Column({ type: "varchar", comment: "Transaction hash that created the batch" }) | ||
| txHash!: string; | ||
|
|
||
| @OneToMany( | ||
| () => ClaimAction, | ||
| (claim) => claim.batch, | ||
| ) | ||
| claims!: ClaimAction[]; | ||
|
|
||
| @CreateDateColumn() | ||
| createdAt!: Date; | ||
|
|
||
| @UpdateDateColumn() | ||
| updatedAt!: Date; | ||
|
Comment on lines
+27
to
+96
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ποΈ Data Integrity & Integration | π΄ Critical π§© Analysis chainπ Script executed: #!/bin/bash
# Inspect datasource / TypeORM config for an explicit snake_case naming strategy.
fd -i 'data-source|datasource|ormconfig' .
rg -n -C2 'namingStrategy|SnakeNamingStrategy|snake' .Repository: Fundable-Protocol/Backend Length of output: 203 π Script executed: cat -n src/config/persistence/data-source.tsRepository: Fundable-Protocol/Backend Length of output: 3452 Critical mismatch between TypeORM config and database schema: Missing The The datasource configuration in Change required in import { SnakeNamingStrategy } from 'typeorm';
// ... inside AppDataSource options:
namingStrategy: new SnakeNamingStrategy(),π€ Prompt for AI Agents |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π©Ί Stability & Availability | π Major
π§© Analysis chain
π Script executed:
Repository: Fundable-Protocol/Backend
Length of output: 260
Missing
reflect-metadatadependency for decorator-based TypeORM entities.TypeORM 0.3.x entities using decorators require
reflect-metadatato be installed and imported once at startup. Verification confirms it is missing from allpackage.jsonfiles in theindexer/workspace and is not imported anywhere.π οΈ Proposed dependency addition
"`@fundable-indexer/common`": "workspace:*", + "reflect-metadata": "^0.2.2", "typeorm": "^0.3.20"Add
import "reflect-metadata";at the application entrypoint before loading any entity metadata to prevent runtime failures in metadata resolution.π Committable suggestion
π€ Prompt for AI Agents