@fairmint/canton-fairmint-sdk 0.0.22 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/clients/postgres-db-api/fairmint-db/app-markers.d.ts +26 -0
  2. package/dist/clients/postgres-db-api/fairmint-db/app-markers.d.ts.map +1 -0
  3. package/dist/clients/postgres-db-api/fairmint-db/app-markers.js +219 -0
  4. package/dist/clients/postgres-db-api/fairmint-db/app-markers.js.map +1 -0
  5. package/dist/clients/postgres-db-api/fairmint-db/base.d.ts +30 -0
  6. package/dist/clients/postgres-db-api/fairmint-db/base.d.ts.map +1 -0
  7. package/dist/clients/postgres-db-api/fairmint-db/base.js +208 -0
  8. package/dist/clients/postgres-db-api/fairmint-db/base.js.map +1 -0
  9. package/dist/clients/postgres-db-api/fairmint-db/index.d.ts +9 -0
  10. package/dist/clients/postgres-db-api/fairmint-db/index.d.ts.map +1 -0
  11. package/dist/clients/postgres-db-api/fairmint-db/index.js +21 -0
  12. package/dist/clients/postgres-db-api/fairmint-db/index.js.map +1 -0
  13. package/dist/clients/postgres-db-api/fairmint-db/ocf-deployments.d.ts +95 -0
  14. package/dist/clients/postgres-db-api/fairmint-db/ocf-deployments.d.ts.map +1 -0
  15. package/dist/clients/postgres-db-api/fairmint-db/ocf-deployments.js +592 -0
  16. package/dist/clients/postgres-db-api/fairmint-db/ocf-deployments.js.map +1 -0
  17. package/dist/clients/postgres-db-api/fairmint-db/parties.d.ts +26 -0
  18. package/dist/clients/postgres-db-api/fairmint-db/parties.d.ts.map +1 -0
  19. package/dist/clients/postgres-db-api/fairmint-db/parties.js +190 -0
  20. package/dist/clients/postgres-db-api/fairmint-db/parties.js.map +1 -0
  21. package/dist/clients/postgres-db-api/fairmint-db/reward-coupons.d.ts +41 -0
  22. package/dist/clients/postgres-db-api/fairmint-db/reward-coupons.d.ts.map +1 -0
  23. package/dist/clients/postgres-db-api/fairmint-db/reward-coupons.js +467 -0
  24. package/dist/clients/postgres-db-api/fairmint-db/reward-coupons.js.map +1 -0
  25. package/dist/clients/postgres-db-api/fairmint-db/time-series.d.ts +48 -0
  26. package/dist/clients/postgres-db-api/fairmint-db/time-series.d.ts.map +1 -0
  27. package/dist/clients/postgres-db-api/fairmint-db/time-series.js +419 -0
  28. package/dist/clients/postgres-db-api/fairmint-db/time-series.js.map +1 -0
  29. package/dist/clients/postgres-db-api/fairmint-db/transfers.d.ts +25 -0
  30. package/dist/clients/postgres-db-api/fairmint-db/transfers.d.ts.map +1 -0
  31. package/dist/clients/postgres-db-api/fairmint-db/transfers.js +206 -0
  32. package/dist/clients/postgres-db-api/fairmint-db/transfers.js.map +1 -0
  33. package/dist/clients/postgres-db-api/fairmint-db/valuation-reports.d.ts +11 -0
  34. package/dist/clients/postgres-db-api/fairmint-db/valuation-reports.d.ts.map +1 -0
  35. package/dist/clients/postgres-db-api/fairmint-db/valuation-reports.js +54 -0
  36. package/dist/clients/postgres-db-api/fairmint-db/valuation-reports.js.map +1 -0
  37. package/dist/clients/postgres-db-api/fairmintDbClient.d.ts +91 -127
  38. package/dist/clients/postgres-db-api/fairmintDbClient.d.ts.map +1 -1
  39. package/dist/clients/postgres-db-api/fairmintDbClient.js +183 -2969
  40. package/dist/clients/postgres-db-api/fairmintDbClient.js.map +1 -1
  41. package/package.json +1 -1
@@ -1,36 +1,35 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FairmintDbClient = void 0;
4
- const pg_1 = require("pg");
5
- const config_1 = require("../shared/config");
6
- const types_1 = require("./types");
4
+ const base_1 = require("./fairmint-db/base");
5
+ const transfers_1 = require("./fairmint-db/transfers");
6
+ const reward_coupons_1 = require("./fairmint-db/reward-coupons");
7
+ const app_markers_1 = require("./fairmint-db/app-markers");
8
+ const parties_1 = require("./fairmint-db/parties");
9
+ const ocf_deployments_1 = require("./fairmint-db/ocf-deployments");
10
+ const time_series_1 = require("./fairmint-db/time-series");
11
+ const valuation_reports_1 = require("./fairmint-db/valuation-reports");
12
+ /**
13
+ * FairmintDbClient provides database access for the Fairmint application.
14
+ *
15
+ * This class uses a composition pattern, delegating to domain-specific
16
+ * repositories for better maintainability and code organization.
17
+ */
7
18
  class FairmintDbClient {
8
19
  constructor(network) {
9
- // Initialize the shared config which handles environment loading
10
- this.config = new config_1.ProviderConfig();
11
- // Use provided network or get from environment variable, defaulting to devnet
12
20
  this.network = network;
13
- const databaseUrl = this.getDatabaseUrl();
14
- if (databaseUrl) {
15
- // Extract database name from URL for debugging (without exposing credentials)
16
- const dbNameMatch = databaseUrl.match(/\/\/([^:]+:[^@]+@)?[^\/]+\/([^?]+)/);
17
- const _dbName = dbNameMatch ? dbNameMatch[2] : 'unknown';
18
- }
19
- if (!databaseUrl) {
20
- throw new Error(`Fairmint database URL for ${this.network} is not configured. Please set POSTGRES_DB_URL_${this.network.toUpperCase()} environment variable.`);
21
- }
22
- this.pool = new pg_1.Pool({
23
- connectionString: databaseUrl,
24
- ssl: { rejectUnauthorized: false },
25
- });
26
- // Test the connection
27
- this.pool.on('error', err => {
28
- console.error('Unexpected error on idle client', err);
29
- process.exit(-1);
30
- });
31
- }
32
- getDatabaseUrl() {
33
- return this.config.getDatabaseUrl(this.network);
21
+ const { pool, config } = (0, base_1.createFairmintDbPool)(network);
22
+ this.pool = pool;
23
+ this.config = config;
24
+ // Initialize repositories
25
+ this.transfers = new transfers_1.TransfersRepository(pool, config, network);
26
+ this.rewardCoupons = new reward_coupons_1.RewardCouponsRepository(pool, config, network);
27
+ this.appMarkers = new app_markers_1.AppMarkersRepository(pool, config, network);
28
+ this.ocfDeployments = new ocf_deployments_1.OcfDeploymentsRepository(pool, config, network);
29
+ this.timeSeries = new time_series_1.TimeSeriesRepository(pool, config, network);
30
+ this.valuationReports = new valuation_reports_1.ValuationReportsRepository(pool, config, network);
31
+ // Parties needs transfers for payment statistics
32
+ this.parties = new parties_1.PartiesRepository(pool, config, network, this.transfers);
34
33
  }
35
34
  getNetwork() {
36
35
  return this.network;
@@ -41,3042 +40,313 @@ class FairmintDbClient {
41
40
  async close() {
42
41
  await this.pool.end();
43
42
  }
44
- // Canton Transfers CRUD operations
43
+ // ========================================================================
44
+ // Transfer Operations
45
+ // ========================================================================
45
46
  async insertCantonTransfer(transfer) {
46
- const query = `
47
- INSERT INTO canton_transfers (
48
- api_tracking_id, api_expires_at,
49
- transfer_sender_party_id, transfer_receiver_party_id, transfer_amount,
50
- transfer_description, transfer_burned_amount, receiver_holding_cids,
51
- sender_change_cids, tx_update_id, tx_record_time,
52
- status, app_reward_coupon_id
53
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
54
- RETURNING *
55
- `;
56
- const values = [
57
- transfer.api_tracking_id,
58
- transfer.api_expires_at,
59
- transfer.transfer_sender_party_id,
60
- transfer.transfer_receiver_party_id,
61
- transfer.transfer_amount,
62
- transfer.transfer_description,
63
- transfer.transfer_burned_amount,
64
- transfer.receiver_holding_cids,
65
- transfer.sender_change_cids,
66
- transfer.tx_update_id,
67
- transfer.tx_record_time,
68
- transfer.status,
69
- transfer.app_reward_coupon_id,
70
- ];
71
- const result = await this.pool.query(query, values);
72
- return this.mapTransferFromDb(result.rows[0]);
47
+ return this.transfers.insertCantonTransfer(transfer);
73
48
  }
74
49
  async getCantonTransfer(id) {
75
- const query = 'SELECT * FROM canton_transfers WHERE id = $1';
76
- const result = await this.pool.query(query, [id]);
77
- return result.rows.length > 0
78
- ? this.mapTransferFromDb(result.rows[0])
79
- : null;
50
+ return this.transfers.getCantonTransfer(id);
80
51
  }
81
52
  async getCantonTransferByTrackingId(apiTrackingId) {
82
- const query = 'SELECT * FROM canton_transfers WHERE api_tracking_id = $1';
83
- const result = await this.pool.query(query, [apiTrackingId]);
84
- return result.rows.length > 0
85
- ? this.mapTransferFromDb(result.rows[0])
86
- : null;
53
+ return this.transfers.getCantonTransferByTrackingId(apiTrackingId);
87
54
  }
88
55
  async getSubmittedTransfers() {
89
- const query = `
90
- SELECT * FROM canton_transfers
91
- WHERE status = $1
92
- ORDER BY created_at ASC
93
- `;
94
- const result = await this.pool.query(query, [types_1.TransferStatus.SUBMITTED]);
95
- return result.rows.map(row => this.mapTransferFromDb(row));
56
+ return this.transfers.getSubmittedTransfers();
96
57
  }
97
58
  async updateCantonTransfer(id, updates) {
98
- const setClauses = [];
99
- const values = [];
100
- let paramIndex = 1;
101
- // Build dynamic update query
102
- Object.entries(updates).forEach(([key, value]) => {
103
- if (key !== 'id' && key !== 'created_at') {
104
- setClauses.push(`${this.toSnakeCase(key)} = $${paramIndex}`);
105
- values.push(value);
106
- paramIndex++;
107
- }
108
- });
109
- if (setClauses.length === 0) {
110
- throw new Error('No valid fields to update');
111
- }
112
- values.push(id);
113
- const query = `
114
- UPDATE canton_transfers
115
- SET ${setClauses.join(', ')}, updated_at = NOW()
116
- WHERE id = $${paramIndex}
117
- RETURNING *
118
- `;
119
- const result = await this.pool.query(query, values);
120
- return result.rows.length > 0
121
- ? this.mapTransferFromDb(result.rows[0])
122
- : null;
123
- }
124
- // Canton App Reward Coupons CRUD operations
59
+ return this.transfers.updateCantonTransfer(id, updates);
60
+ }
61
+ async getMonthlyPaymentTotalForParty(partyId, roundDurationMinutes = 10) {
62
+ return this.transfers.getMonthlyPaymentTotalForParty(partyId, roundDurationMinutes);
63
+ }
64
+ getRemainingRoundsInMonth(roundDurationMinutes = 10) {
65
+ return this.transfers.getRemainingRoundsInMonth(roundDurationMinutes);
66
+ }
67
+ async getMonthlyPaymentTotalsForParties(partyIds) {
68
+ return this.transfers.getMonthlyPaymentTotalsForParties(partyIds);
69
+ }
70
+ async getLifetimePaymentStatisticsForParties(partyIds) {
71
+ return this.transfers.getLifetimePaymentStatisticsForParties(partyIds);
72
+ }
73
+ // ========================================================================
74
+ // Reward Coupon Operations
75
+ // ========================================================================
125
76
  async insertCantonAppRewardCoupon(coupon) {
126
- const query = `
127
- INSERT INTO canton_app_reward_coupons (
128
- status, tx_update_id, tx_record_time, contract_id, template_id, package_name,
129
- dso_party_id, provider_party_id, featured, round_number, beneficiary_party_id, coupon_amount
130
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
131
- RETURNING *
132
- `;
133
- const values = [
134
- coupon.status,
135
- coupon.tx_update_id,
136
- coupon.tx_record_time,
137
- coupon.contract_id,
138
- coupon.template_id,
139
- coupon.package_name,
140
- coupon.dso_party_id,
141
- coupon.provider_party_id,
142
- coupon.featured,
143
- coupon.round_number,
144
- coupon.beneficiary_party_id,
145
- coupon.coupon_amount,
146
- ];
147
- const result = await this.pool.query(query, values);
148
- return this.mapRewardCouponFromDb(result.rows[0]);
77
+ return this.rewardCoupons.insertCantonAppRewardCoupon(coupon);
149
78
  }
150
79
  async batchInsertCantonAppRewardCoupons(coupons) {
151
- if (coupons.length === 0) {
152
- return { insertedCount: 0, skippedCount: 0 };
153
- }
154
- // Build the VALUES clause with placeholders
155
- const valuesPlaceholders = coupons
156
- .map((_, idx) => `($${idx * 12 + 1}, $${idx * 12 + 2}, $${idx * 12 + 3}, $${idx * 12 + 4}, $${idx * 12 + 5}, $${idx * 12 + 6}, $${idx * 12 + 7}, $${idx * 12 + 8}, $${idx * 12 + 9}, $${idx * 12 + 10}, $${idx * 12 + 11}, $${idx * 12 + 12})`)
157
- .join(', ');
158
- const query = `
159
- INSERT INTO canton_app_reward_coupons (
160
- status, tx_update_id, tx_record_time, contract_id, template_id, package_name,
161
- dso_party_id, provider_party_id, featured, round_number, beneficiary_party_id, coupon_amount
162
- ) VALUES ${valuesPlaceholders}
163
- ON CONFLICT (contract_id) DO NOTHING
164
- RETURNING *
165
- `;
166
- // Flatten all coupon values into a single array
167
- const values = coupons.flatMap(coupon => [
168
- coupon.status,
169
- coupon.tx_update_id,
170
- coupon.tx_record_time,
171
- coupon.contract_id,
172
- coupon.template_id,
173
- coupon.package_name,
174
- coupon.dso_party_id,
175
- coupon.provider_party_id,
176
- coupon.featured,
177
- coupon.round_number,
178
- coupon.beneficiary_party_id,
179
- coupon.coupon_amount,
180
- ]);
181
- const result = await this.pool.query(query, values);
182
- const insertedCount = result.rowCount ?? 0;
183
- const skippedCount = coupons.length - insertedCount;
184
- return { insertedCount, skippedCount };
80
+ return this.rewardCoupons.batchInsertCantonAppRewardCoupons(coupons);
185
81
  }
186
82
  async upsertCantonAppRewardCouponWithStatus(coupon) {
187
- const query = `
188
- INSERT INTO canton_app_reward_coupons (
189
- status, tx_update_id, tx_record_time, contract_id, template_id, package_name,
190
- dso_party_id, provider_party_id, featured, round_number, beneficiary_party_id, coupon_amount
191
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
192
- ON CONFLICT (contract_id) DO UPDATE
193
- SET status = EXCLUDED.status, updated_at = NOW()
194
- RETURNING *
195
- `;
196
- const values = [
197
- coupon.status,
198
- coupon.tx_update_id,
199
- coupon.tx_record_time,
200
- coupon.contract_id,
201
- coupon.template_id,
202
- coupon.package_name,
203
- coupon.dso_party_id,
204
- coupon.provider_party_id,
205
- coupon.featured,
206
- coupon.round_number,
207
- coupon.beneficiary_party_id,
208
- coupon.coupon_amount,
209
- ];
210
- const result = await this.pool.query(query, values);
211
- return this.mapRewardCouponFromDb(result.rows[0]);
83
+ return this.rewardCoupons.upsertCantonAppRewardCouponWithStatus(coupon);
212
84
  }
213
85
  async getCantonAppRewardCoupon(id) {
214
- const query = 'SELECT * FROM canton_app_reward_coupons WHERE id = $1';
215
- const result = await this.pool.query(query, [id]);
216
- return result.rows.length > 0
217
- ? this.mapRewardCouponFromDb(result.rows[0])
218
- : null;
86
+ return this.rewardCoupons.getCantonAppRewardCoupon(id);
219
87
  }
220
88
  async getCantonAppRewardCouponByContractId(contractId) {
221
- const query = 'SELECT * FROM canton_app_reward_coupons WHERE contract_id = $1';
222
- const result = await this.pool.query(query, [contractId]);
223
- return result.rows.length > 0
224
- ? this.mapRewardCouponFromDb(result.rows[0])
225
- : null;
89
+ return this.rewardCoupons.getCantonAppRewardCouponByContractId(contractId);
226
90
  }
227
91
  async getCantonAppRewardCouponsByContractIds(contractIds) {
228
- if (contractIds.length === 0) {
229
- return [];
230
- }
231
- const placeholders = contractIds
232
- .map((_, index) => `$${index + 1}`)
233
- .join(', ');
234
- const query = `
235
- SELECT * FROM canton_app_reward_coupons
236
- WHERE contract_id IN (${placeholders})
237
- `;
238
- const result = await this.pool.query(query, contractIds);
239
- return result.rows.map(row => this.mapRewardCouponFromDb(row));
92
+ return this.rewardCoupons.getCantonAppRewardCouponsByContractIds(contractIds);
240
93
  }
241
94
  async getCantonAppRewardCouponsByStatus(status, limit = 100, sortOrder = 'ASC', beneficiaryPartyId) {
242
- let query = `
243
- SELECT * FROM canton_app_reward_coupons
244
- WHERE status = $1
245
- `;
246
- const params = [status];
247
- if (beneficiaryPartyId) {
248
- query += ` AND beneficiary_party_id = $${params.length + 1}`;
249
- params.push(beneficiaryPartyId);
250
- }
251
- query += ` ORDER BY tx_record_time ${sortOrder} LIMIT $${params.length + 1}`;
252
- params.push(limit);
253
- const result = await this.pool.query(query, params);
254
- return result.rows.map(row => this.mapRewardCouponFromDb(row));
95
+ return this.rewardCoupons.getCantonAppRewardCouponsByStatus(status, limit, sortOrder, beneficiaryPartyId);
255
96
  }
256
97
  async countCantonAppRewardCouponsByStatus(status) {
257
- const query = `
258
- SELECT COUNT(*) FROM canton_app_reward_coupons
259
- WHERE status = $1
260
- `;
261
- const result = await this.pool.query(query, [status]);
262
- return result.rows[0].count;
98
+ return this.rewardCoupons.countCantonAppRewardCouponsByStatus(status);
263
99
  }
264
100
  async getOldestCreatedCantonAppRewardCoupons(limit = 100) {
265
- const query = `
266
- SELECT * FROM canton_app_reward_coupons
267
- WHERE status = 'created'
268
- ORDER BY tx_record_time ASC
269
- LIMIT $1
270
- `;
271
- const result = await this.pool.query(query, [limit]);
272
- return result.rows.map(row => this.mapRewardCouponFromDb(row));
101
+ return this.rewardCoupons.getOldestCreatedCantonAppRewardCoupons(limit);
273
102
  }
274
103
  async getCantonAppRewardCouponsByDateRange(startDate, endDate, status, limit = 1000, sortOrder = 'ASC') {
275
- let query = `
276
- SELECT * FROM canton_app_reward_coupons
277
- WHERE tx_archive_record_time >= $1 AND tx_archive_record_time <= $2
278
- `;
279
- const values = [startDate, endDate];
280
- let paramIndex = 3;
281
- if (status) {
282
- query += ` AND status = $${paramIndex}`;
283
- values.push(status);
284
- paramIndex++;
285
- }
286
- query += ` ORDER BY tx_archive_record_time ${sortOrder} LIMIT $${paramIndex}`;
287
- values.push(limit);
288
- const result = await this.pool.query(query, values);
289
- return result.rows.map(row => this.mapRewardCouponFromDb(row));
104
+ return this.rewardCoupons.getCantonAppRewardCouponsByDateRange(startDate, endDate, status, limit, sortOrder);
290
105
  }
291
106
  async updateCantonAppRewardCoupon(id, updates) {
292
- const setClauses = [];
293
- const values = [];
294
- let paramIndex = 1;
295
- // Build dynamic update query
296
- Object.entries(updates).forEach(([key, value]) => {
297
- if (key !== 'id' && key !== 'created_at') {
298
- setClauses.push(`${this.toSnakeCase(key)} = $${paramIndex}`);
299
- values.push(value);
300
- paramIndex++;
301
- }
302
- });
303
- if (setClauses.length === 0) {
304
- throw new Error('No valid fields to update');
305
- }
306
- values.push(id);
307
- const query = `
308
- UPDATE canton_app_reward_coupons
309
- SET ${setClauses.join(', ')}, updated_at = NOW()
310
- WHERE id = $${paramIndex}
311
- RETURNING *
312
- `;
313
- const result = await this.pool.query(query, values);
314
- return result.rows.length > 0
315
- ? this.mapRewardCouponFromDb(result.rows[0])
316
- : null;
107
+ return this.rewardCoupons.updateCantonAppRewardCoupon(id, updates);
317
108
  }
318
109
  async batchUpdateCantonAppRewardCouponsByContractIds(contractIds, updates) {
319
- if (contractIds.length === 0) {
320
- return [];
321
- }
322
- const setClauses = [];
323
- const values = [];
324
- let paramIndex = 1;
325
- // Build dynamic update query
326
- Object.entries(updates).forEach(([key, value]) => {
327
- if (key !== 'id' && key !== 'created_at') {
328
- setClauses.push(`${this.toSnakeCase(key)} = $${paramIndex}`);
329
- values.push(value);
330
- paramIndex++;
331
- }
332
- });
333
- if (setClauses.length === 0) {
334
- throw new Error('No valid fields to update');
335
- }
336
- // Build the WHERE clause for contract IDs
337
- const contractIdPlaceholders = contractIds
338
- .map((_, index) => `$${paramIndex + index}`)
339
- .join(', ');
340
- values.push(...contractIds);
341
- const query = `
342
- UPDATE canton_app_reward_coupons
343
- SET ${setClauses.join(', ')}, updated_at = NOW()
344
- WHERE contract_id IN (${contractIdPlaceholders})
345
- RETURNING *
346
- `;
347
- const result = await this.pool.query(query, values);
348
- return result.rows.map(row => this.mapRewardCouponFromDb(row));
110
+ return this.rewardCoupons.batchUpdateCantonAppRewardCouponsByContractIds(contractIds, updates);
349
111
  }
350
112
  async getRewardCoupons(options = {}) {
351
- const { limit = 100, order = 'oldest', redeemed = 'Both' } = options;
352
- let query = `
353
- SELECT
354
- c.id as coupon_id,
355
- c.status as coupon_status,
356
- c.tx_update_id as coupon_tx_update_id,
357
- c.tx_record_time as coupon_tx_record_time,
358
- c.contract_id as coupon_contract_id,
359
- c.template_id as coupon_template_id,
360
- c.package_name as coupon_package_name,
361
- c.dso_party_id as coupon_dso_party_id,
362
- c.provider_party_id as coupon_provider_party_id,
363
- c.featured as coupon_featured,
364
- c.round_number as coupon_round_number,
365
- c.beneficiary_party_id as coupon_beneficiary_party_id,
366
- c.coupon_amount as coupon_coupon_amount,
367
- c.tx_archive_update_id as coupon_tx_archive_update_id,
368
- c.tx_archive_record_time as coupon_tx_archive_record_time,
369
- c.app_reward_amount as coupon_app_reward_amount,
370
- c.created_at as coupon_created_at,
371
- c.updated_at as coupon_updated_at,
372
- t.id as transfer_id,
373
- t.status as transfer_status,
374
- t.api_tracking_id as transfer_api_tracking_id,
375
- t.api_expires_at as transfer_api_expires_at,
376
- t.transfer_sender_party_id as transfer_transfer_sender_party_id,
377
- t.transfer_receiver_party_id as transfer_transfer_receiver_party_id,
378
- t.transfer_amount as transfer_transfer_amount,
379
- t.transfer_description as transfer_transfer_description,
380
- t.transfer_burned_amount as transfer_transfer_burned_amount,
381
- t.receiver_holding_cids as transfer_receiver_holding_cids,
382
- t.sender_change_cids as transfer_sender_change_cids,
383
- t.tx_update_id as transfer_tx_update_id,
384
- t.tx_record_time as transfer_tx_record_time,
385
- t.app_reward_coupon_id as transfer_app_reward_coupon_id,
386
- t.created_at as transfer_created_at,
387
- t.updated_at as transfer_updated_at,
388
- r.id as redemption_id,
389
- r.tx_update_id as redemption_tx_update_id,
390
- r.tx_record_time as redemption_tx_record_time,
391
- r.tx_synchronizer_id as redemption_tx_synchronizer_id,
392
- r.tx_effective_at as redemption_tx_effective_at,
393
- r.total_app_rewards,
394
- r.created_at as redemption_created_at,
395
- r.updated_at as redemption_updated_at
396
- FROM canton_app_reward_coupons c
397
- LEFT JOIN canton_transfers t ON t.app_reward_coupon_id = c.id
398
- `;
399
- const conditions = [];
400
- const values = [];
401
- const paramIndex = 1;
402
- // Filter by redemption status
403
- if (redeemed === 'Unredeemed') {
404
- conditions.push('r.id IS NULL');
405
- }
406
- else if (redeemed === 'Redeemed') {
407
- conditions.push('r.id IS NOT NULL');
408
- }
409
- if (conditions.length > 0) {
410
- query += ` WHERE ${conditions.join(' AND ')}`;
411
- }
412
- // Order by
413
- query += ` ORDER BY c.tx_record_time ${order === 'oldest' ? 'ASC' : 'DESC'}`;
414
- // Limit
415
- query += ` LIMIT $${paramIndex}`;
416
- values.push(limit);
417
- const result = await this.pool.query(query, values);
418
- return result.rows.map(row => this.mapRewardCouponWithTransferFromDb(row));
113
+ return this.rewardCoupons.getRewardCoupons(options);
419
114
  }
420
115
  async getRewardCoupon(options) {
421
- const { contract_id } = options;
422
- const query = `
423
- SELECT
424
- c.id as coupon_id,
425
- c.status as coupon_status,
426
- c.tx_update_id as coupon_tx_update_id,
427
- c.tx_record_time as coupon_tx_record_time,
428
- c.contract_id as coupon_contract_id,
429
- c.template_id as coupon_template_id,
430
- c.package_name as coupon_package_name,
431
- c.dso_party_id as coupon_dso_party_id,
432
- c.provider_party_id as coupon_provider_party_id,
433
- c.featured as coupon_featured,
434
- c.round_number as coupon_round_number,
435
- c.beneficiary_party_id as coupon_beneficiary_party_id,
436
- c.coupon_amount as coupon_coupon_amount,
437
- c.tx_archive_update_id as coupon_tx_archive_update_id,
438
- c.tx_archive_record_time as coupon_tx_archive_record_time,
439
- c.app_reward_amount as coupon_app_reward_amount,
440
- c.created_at as coupon_created_at,
441
- c.updated_at as coupon_updated_at,
442
- t.id as transfer_id,
443
- t.status as transfer_status,
444
- t.api_tracking_id as transfer_api_tracking_id,
445
- t.api_expires_at as transfer_api_expires_at,
446
- t.transfer_sender_party_id as transfer_transfer_sender_party_id,
447
- t.transfer_receiver_party_id as transfer_transfer_receiver_party_id,
448
- t.transfer_amount as transfer_transfer_amount,
449
- t.transfer_description as transfer_transfer_description,
450
- t.transfer_burned_amount as transfer_transfer_burned_amount,
451
- t.receiver_holding_cids as transfer_receiver_holding_cids,
452
- t.sender_change_cids as transfer_sender_change_cids,
453
- t.tx_update_id as transfer_tx_update_id,
454
- t.tx_record_time as transfer_tx_record_time,
455
- t.app_reward_coupon_id as transfer_app_reward_coupon_id,
456
- t.created_at as transfer_created_at,
457
- t.updated_at as transfer_updated_at,
458
- r.id as redemption_id,
459
- r.tx_update_id as redemption_tx_update_id,
460
- r.tx_record_time as redemption_tx_record_time,
461
- r.tx_synchronizer_id as redemption_tx_synchronizer_id,
462
- r.tx_effective_at as redemption_tx_effective_at,
463
- r.total_app_rewards,
464
- r.created_at as redemption_created_at,
465
- r.updated_at as redemption_updated_at
466
- FROM canton_app_reward_coupons c
467
- LEFT JOIN canton_transfers t ON t.app_reward_coupon_id = c.id
468
- WHERE c.contract_id = $1
469
- `;
470
- const result = await this.pool.query(query, [contract_id]);
471
- return result.rows.length > 0
472
- ? this.mapRewardCouponWithTransferFromDb(result.rows[0])
473
- : null;
474
- }
475
- async getMonthlyTransferTotal(monthStart, monthEnd) {
476
- const query = `
477
- SELECT COALESCE(SUM(transfer_amount), 0) as total_amount
478
- FROM canton_transfers
479
- WHERE tx_record_time >= $1
480
- AND tx_record_time <= $2
481
- AND status IN ($3, $4)
482
- `;
483
- const result = await this.pool.query(query, [
484
- monthStart,
485
- monthEnd,
486
- types_1.TransferStatus.CONFIRMED,
487
- types_1.TransferStatus.PENDING,
488
- ]);
489
- const totalAmount = parseFloat(result.rows[0].total_amount);
490
- return totalAmount;
116
+ return this.rewardCoupons.getRewardCoupon(options);
491
117
  }
492
118
  async getMonthlyAppRewardAmountTotal(monthStart, monthEnd) {
493
- const query = `
494
- SELECT COALESCE(SUM(app_reward_amount), 0) as total_amount
495
- FROM canton_app_reward_coupons
496
- WHERE created_at >= $1
497
- AND created_at <= $2
498
- AND app_reward_amount IS NOT NULL
499
- `;
500
- const result = await this.pool.query(query, [monthStart, monthEnd]);
501
- const totalAmount = parseFloat(result.rows[0].total_amount);
502
- return totalAmount;
503
- }
504
- async getTransferTimeSeriesData(timeRange, _metric = 'count', timePeriod, customStartDate) {
505
- let interval;
506
- let timeFilter;
507
- let timeSeriesStart;
508
- let timeSeriesEnd = 'NOW()';
509
- let timeRangeInterval;
510
- switch (timeRange) {
511
- case '15m':
512
- interval = 'minute';
513
- timeFilter = "created_at >= NOW() - INTERVAL '15 minutes'";
514
- timeRangeInterval = '15 minutes';
515
- timeSeriesStart = "NOW() - INTERVAL '15 minutes'";
516
- break;
517
- case '1h':
518
- interval = 'minute';
519
- timeFilter = "created_at >= NOW() - INTERVAL '1 hour'";
520
- timeRangeInterval = '1 hour';
521
- timeSeriesStart = "NOW() - INTERVAL '1 hour'";
522
- break;
523
- case '6h':
524
- interval = 'minute';
525
- timeFilter = "created_at >= NOW() - INTERVAL '6 hours'";
526
- timeRangeInterval = '6 hours';
527
- timeSeriesStart = "NOW() - INTERVAL '6 hours'";
528
- break;
529
- case '1d':
530
- interval = 'hour';
531
- timeFilter = "created_at >= NOW() - INTERVAL '1 day'";
532
- timeRangeInterval = '1 day';
533
- timeSeriesStart = "NOW() - INTERVAL '1 day'";
534
- break;
535
- case '7d':
536
- interval = 'hour';
537
- timeFilter = "created_at >= NOW() - INTERVAL '7 days'";
538
- timeRangeInterval = '7 days';
539
- timeSeriesStart = "NOW() - INTERVAL '7 days'";
540
- break;
541
- case '30d':
542
- interval = 'day';
543
- timeFilter = "created_at >= NOW() - INTERVAL '30 days'";
544
- timeRangeInterval = '30 days';
545
- timeSeriesStart = "NOW() - INTERVAL '30 days'";
546
- break;
547
- case 'last-month':
548
- interval = 'day';
549
- timeFilter =
550
- "created_at >= date_trunc('month', current_date - interval '1 month') AND created_at < date_trunc('month', current_date)";
551
- timeRangeInterval = '1 month';
552
- timeSeriesStart =
553
- "date_trunc('month', current_date - interval '1 month')";
554
- timeSeriesEnd = "date_trunc('month', current_date)";
555
- break;
556
- case 'all':
557
- interval = 'day';
558
- timeFilter = '1=1';
559
- timeRangeInterval = '100 years';
560
- timeSeriesStart = "'2023-01-01'::timestamp";
561
- break;
562
- default:
563
- interval = 'hour';
564
- timeFilter = "created_at >= NOW() - INTERVAL '1 day'";
565
- timeRangeInterval = '1 day';
566
- timeSeriesStart = "NOW() - INTERVAL '1 day'";
567
- }
568
- // If custom start date is provided, override the timeFilter and time series start/end
569
- if (timePeriod === 'custom-start' && customStartDate) {
570
- timeFilter = `created_at >= '${customStartDate}:00' AND created_at < ('${customStartDate}:00'::timestamp + INTERVAL '${timeRangeInterval}')`;
571
- timeSeriesStart = `'${customStartDate}:00'::timestamp`;
572
- timeSeriesEnd = `('${customStartDate}:00'::timestamp + INTERVAL '${timeRangeInterval}')`;
573
- }
574
- const query = `
575
- WITH time_series AS (
576
- SELECT generate_series(
577
- date_trunc('${interval}', ${timeSeriesStart}),
578
- date_trunc('${interval}', ${timeSeriesEnd}),
579
- INTERVAL '1 ${interval}'
580
- ) AS timestamp
581
- ),
582
- transfer_data AS (
583
- SELECT
584
- date_trunc('${interval}', created_at) AS timestamp,
585
- COUNT(*) as count,
586
- COALESCE(SUM(transfer_amount), 0) as amount
587
- FROM canton_transfers
588
- WHERE ${timeFilter}
589
- AND status IN ('pending', 'submitted', 'confirmed')
590
- GROUP BY date_trunc('${interval}', created_at)
591
- )
592
- SELECT
593
- ts.timestamp::text,
594
- COALESCE(td.count, 0) as count,
595
- COALESCE(td.amount, 0) as amount
596
- FROM time_series ts
597
- LEFT JOIN transfer_data td ON ts.timestamp = td.timestamp
598
- ORDER BY ts.timestamp ASC
599
- `;
600
- const result = await this.pool.query(query);
601
- return result.rows.map(row => ({
602
- timestamp: row.timestamp,
603
- count: parseInt(row.count, 10),
604
- amount: parseFloat(row.amount),
605
- }));
606
- }
607
- async getRewardCouponTimeSeriesData(timeRange, _metric = 'count', timePeriod, customStartDate, partyFilter, couponStatus = 'all') {
608
- let interval;
609
- let timeFilter;
610
- let timeSeriesStart;
611
- let timeSeriesEnd = 'NOW()';
612
- let timeRangeInterval;
613
- switch (timeRange) {
614
- case '15m':
615
- interval = 'minute';
616
- timeFilter = "tx_record_time >= NOW() - INTERVAL '15 minutes'";
617
- timeRangeInterval = '15 minutes';
618
- timeSeriesStart = "NOW() - INTERVAL '15 minutes'";
619
- break;
620
- case '1h':
621
- interval = 'minute';
622
- timeFilter = "tx_record_time >= NOW() - INTERVAL '1 hour'";
623
- timeRangeInterval = '1 hour';
624
- timeSeriesStart = "NOW() - INTERVAL '1 hour'";
625
- break;
626
- case '6h':
627
- interval = 'minute';
628
- timeFilter = "tx_record_time >= NOW() - INTERVAL '6 hours'";
629
- timeRangeInterval = '6 hours';
630
- timeSeriesStart = "NOW() - INTERVAL '6 hours'";
631
- break;
632
- case '1d':
633
- interval = 'hour';
634
- timeFilter = "tx_record_time >= NOW() - INTERVAL '1 day'";
635
- timeRangeInterval = '1 day';
636
- timeSeriesStart = "NOW() - INTERVAL '1 day'";
637
- break;
638
- case '7d':
639
- interval = 'hour';
640
- timeFilter = "tx_record_time >= NOW() - INTERVAL '7 days'";
641
- timeRangeInterval = '7 days';
642
- timeSeriesStart = "NOW() - INTERVAL '7 days'";
643
- break;
644
- case '30d':
645
- interval = 'day';
646
- timeFilter = "tx_record_time >= NOW() - INTERVAL '30 days'";
647
- timeRangeInterval = '30 days';
648
- timeSeriesStart = "NOW() - INTERVAL '30 days'";
649
- break;
650
- case 'last-month':
651
- interval = 'day';
652
- timeFilter =
653
- "tx_record_time >= date_trunc('month', current_date - interval '1 month') AND tx_record_time < date_trunc('month', current_date)";
654
- timeRangeInterval = '1 month';
655
- timeSeriesStart =
656
- "date_trunc('month', current_date - interval '1 month')";
657
- timeSeriesEnd = "date_trunc('month', current_date)";
658
- break;
659
- case 'all':
660
- interval = 'day'; // Default to daily for 'all' to avoid too many points
661
- timeFilter = '1=1';
662
- timeRangeInterval = '100 years'; // Just for custom start calc
663
- timeSeriesStart = "'2023-01-01'::timestamp"; // Reasonable start date
664
- break;
665
- default:
666
- interval = 'hour';
667
- timeFilter = "tx_record_time >= NOW() - INTERVAL '1 day'";
668
- timeRangeInterval = '1 day';
669
- timeSeriesStart = "NOW() - INTERVAL '1 day'";
670
- }
671
- // If custom start date is provided, override the timeFilter and time series start/end
672
- if (timePeriod === 'custom-start' && customStartDate) {
673
- timeFilter = `tx_record_time >= '${customStartDate}:00' AND tx_record_time < ('${customStartDate}:00'::timestamp + INTERVAL '${timeRangeInterval}')`;
674
- timeSeriesStart = `'${customStartDate}:00'::timestamp`;
675
- timeSeriesEnd = `('${customStartDate}:00'::timestamp + INTERVAL '${timeRangeInterval}')`;
676
- }
677
- // Add party filter if provided
678
- const partyCondition = partyFilter
679
- ? ` AND beneficiary_party_id = '${partyFilter}'`
680
- : '';
681
- // Add status filter based on couponStatus
682
- // 'created' = not yet archived, 'archived' = has been collected
683
- // 'all' = all coupons regardless of archive status
684
- let statusCondition = '';
685
- if (couponStatus === 'created') {
686
- statusCondition = ` AND status = 'created'`;
687
- }
688
- else if (couponStatus === 'archived') {
689
- statusCondition = ` AND status = 'archived'`;
690
- }
691
- // When couponStatus === 'all', no filter is applied (statusCondition remains '')
692
- const query = `
693
- WITH time_series AS (
694
- SELECT generate_series(
695
- date_trunc('${interval}', ${timeSeriesStart}),
696
- date_trunc('${interval}', ${timeSeriesEnd}),
697
- INTERVAL '1 ${interval}'
698
- ) AS timestamp
699
- ),
700
- coupon_data AS (
701
- SELECT
702
- date_trunc('${interval}', tx_record_time) AS timestamp,
703
- COUNT(*) as count,
704
- COALESCE(SUM(app_reward_amount), 0) as amount,
705
- COALESCE(SUM(coupon_amount), 0) as coupon_amount
706
- FROM canton_app_reward_coupons
707
- WHERE ${timeFilter}${partyCondition}${statusCondition}
708
- GROUP BY date_trunc('${interval}', tx_record_time)
709
- )
710
- SELECT
711
- ts.timestamp::text,
712
- COALESCE(cd.count, 0) as count,
713
- COALESCE(cd.amount, 0) as amount,
714
- COALESCE(cd.coupon_amount, 0) as coupon_amount
715
- FROM time_series ts
716
- LEFT JOIN coupon_data cd ON ts.timestamp = cd.timestamp
717
- ORDER BY ts.timestamp ASC
718
- `;
719
- const result = await this.pool.query(query);
720
- return result.rows.map(row => ({
721
- timestamp: row.timestamp,
722
- count: parseInt(row.count, 10),
723
- amount: parseFloat(row.amount),
724
- couponAmount: parseFloat(row.coupon_amount),
725
- }));
726
- }
727
- async getRewardCouponRoundSeriesData(timeRange, _metric = 'count', timePeriod, customStartDate, partyFilter, couponStatus = 'all') {
728
- let timeRangeInterval;
729
- let timeFilter;
730
- switch (timeRange) {
731
- case '15m':
732
- timeRangeInterval = '15 minutes';
733
- timeFilter = "tx_record_time >= NOW() - INTERVAL '15 minutes'";
734
- break;
735
- case '1h':
736
- timeRangeInterval = '1 hour';
737
- timeFilter = "tx_record_time >= NOW() - INTERVAL '1 hour'";
738
- break;
739
- case '6h':
740
- timeRangeInterval = '6 hours';
741
- timeFilter = "tx_record_time >= NOW() - INTERVAL '6 hours'";
742
- break;
743
- case '1d':
744
- timeRangeInterval = '1 day';
745
- timeFilter = "tx_record_time >= NOW() - INTERVAL '1 day'";
746
- break;
747
- case '7d':
748
- timeRangeInterval = '7 days';
749
- timeFilter = "tx_record_time >= NOW() - INTERVAL '7 days'";
750
- break;
751
- case '30d':
752
- timeRangeInterval = '30 days';
753
- timeFilter = "tx_record_time >= NOW() - INTERVAL '30 days'";
754
- break;
755
- case 'last-month':
756
- timeRangeInterval = '1 month';
757
- timeFilter =
758
- "tx_record_time >= date_trunc('month', current_date - interval '1 month') AND tx_record_time < date_trunc('month', current_date)";
759
- break;
760
- case 'all':
761
- default:
762
- timeRangeInterval = '1 day';
763
- timeFilter = "tx_record_time >= NOW() - INTERVAL '1 day'";
764
- }
765
- if (timePeriod === 'custom-start' && customStartDate) {
766
- timeFilter = `tx_record_time >= '${customStartDate}:00' AND tx_record_time < ('${customStartDate}:00'::timestamp + INTERVAL '${timeRangeInterval}')`;
767
- }
768
- // Add party filter if provided
769
- const partyCondition = partyFilter
770
- ? ` AND beneficiary_party_id = '${partyFilter}'`
771
- : '';
772
- // Add status filter based on couponStatus
773
- // 'created' = not yet archived, 'archived' = has been collected
774
- // 'all' = all coupons regardless of archive status
775
- let statusCondition = '';
776
- if (couponStatus === 'created') {
777
- statusCondition = ` AND status = 'created'`;
778
- }
779
- else if (couponStatus === 'archived') {
780
- statusCondition = ` AND status = 'archived'`;
781
- }
782
- // When couponStatus === 'all', no filter is applied (statusCondition remains '')
783
- const query = `
784
- WITH time_filtered AS (
785
- SELECT round_number
786
- FROM canton_app_reward_coupons
787
- WHERE ${timeFilter}${partyCondition}${statusCondition}
788
- ),
789
- round_bounds AS (
790
- SELECT
791
- COALESCE(MIN(round_number), 0) AS min_round,
792
- COALESCE(MAX(round_number), 0) AS max_round
793
- FROM time_filtered
794
- ),
795
- rounds AS (
796
- SELECT generate_series(
797
- GREATEST((SELECT min_round FROM round_bounds), 0),
798
- GREATEST((SELECT max_round FROM round_bounds), 0)
799
- ) AS round
800
- WHERE (SELECT max_round FROM round_bounds) > 0
801
- ),
802
- coupon_data AS (
803
- SELECT
804
- round_number AS round,
805
- MIN(tx_record_time) AS timestamp,
806
- COUNT(*) AS count,
807
- COALESCE(SUM(app_reward_amount), 0) AS amount,
808
- COALESCE(SUM(coupon_amount), 0) AS coupon_amount
809
- FROM canton_app_reward_coupons
810
- WHERE round_number IN (SELECT round FROM rounds)${partyCondition}${statusCondition}
811
- GROUP BY round_number
812
- )
813
- SELECT
814
- r.round,
815
- cd.timestamp,
816
- COALESCE(cd.count, 0) AS count,
817
- COALESCE(cd.amount, 0) AS amount,
818
- COALESCE(cd.coupon_amount, 0) AS coupon_amount
819
- FROM rounds r
820
- LEFT JOIN coupon_data cd ON cd.round = r.round
821
- ORDER BY r.round ASC
822
- `;
823
- const result = await this.pool.query(query);
824
- return result.rows.map(row => ({
825
- timestamp: row.timestamp
826
- ? new Date(row.timestamp).toISOString()
827
- : new Date().toISOString(),
828
- count: parseInt(row.count, 10),
829
- amount: parseFloat(row.amount),
830
- couponAmount: parseFloat(row.coupon_amount),
831
- round: typeof row.round === 'number' ? row.round : parseInt(row.round, 10),
832
- }));
119
+ return this.rewardCoupons.getMonthlyAppRewardAmountTotal(monthStart, monthEnd);
833
120
  }
834
121
  async getLatestCouponAmounts() {
835
- const query = `
836
- WITH latest AS (
837
- SELECT DISTINCT ON (featured)
838
- featured,
839
- coupon_amount
840
- FROM canton_app_reward_coupons
841
- WHERE coupon_amount IS NOT NULL
842
- ORDER BY featured, tx_record_time DESC
843
- )
844
- SELECT
845
- MAX(coupon_amount) FILTER (WHERE featured) AS featured_amount,
846
- MAX(coupon_amount) FILTER (WHERE NOT featured) AS unfeatured_amount
847
- FROM latest
848
- `;
849
- const result = await this.pool.query(query);
850
- const row = result.rows[0] ?? {};
851
- return {
852
- featured: row.featured_amount !== null && row.featured_amount !== undefined
853
- ? parseFloat(row.featured_amount)
854
- : null,
855
- unfeatured: row.unfeatured_amount !== null && row.unfeatured_amount !== undefined
856
- ? parseFloat(row.unfeatured_amount)
857
- : null,
858
- };
122
+ return this.rewardCoupons.getLatestCouponAmounts();
859
123
  }
860
124
  async getLastAppRewardCouponTimestamp(partyFilter) {
861
- const partyCondition = partyFilter
862
- ? ` AND beneficiary_party_id = '${partyFilter}'`
863
- : '';
864
- const query = `
865
- SELECT MAX(tx_record_time) AS last_timestamp
866
- FROM canton_app_reward_coupons
867
- WHERE status IN ('created', 'archived')
868
- AND app_reward_amount IS NOT NULL${partyCondition}
869
- `;
870
- const result = await this.pool.query(query);
871
- const timestamp = result.rows[0]?.last_timestamp;
872
- return timestamp ? new Date(timestamp).toISOString() : null;
125
+ return this.rewardCoupons.getLastAppRewardCouponTimestamp(partyFilter);
873
126
  }
874
127
  async getLifetimeAppRewards() {
875
- const query = `
876
- SELECT COALESCE(SUM(app_reward_amount), 0) AS total
877
- FROM canton_app_reward_coupons
878
- WHERE status = 'archived'
879
- `;
880
- const result = await this.pool.query(query);
881
- return Number(result.rows[0]?.total ?? 0);
128
+ return this.rewardCoupons.getLifetimeAppRewards();
882
129
  }
883
130
  async getUniqueCouponBeneficiaryParties() {
884
- const query = `
885
- SELECT DISTINCT
886
- beneficiary_party_id as party_id,
887
- beneficiary_party_id as display_name
888
- FROM canton_app_reward_coupons
889
- WHERE beneficiary_party_id IS NOT NULL
890
- ORDER BY beneficiary_party_id ASC
891
- `;
892
- const result = await this.pool.query(query);
893
- return result.rows;
894
- }
895
- async getAppMarkerTimeSeriesData(timeRange, _metric = 'count', timePeriod, customStartDate, partyFilter, _statusFilter) {
896
- let interval;
897
- let timeFilter;
898
- let timeSeriesStart;
899
- let timeSeriesEnd = 'NOW()';
900
- let timeRangeInterval;
901
- switch (timeRange) {
902
- case '15m':
903
- interval = 'minute';
904
- timeFilter = "tx_record_time >= NOW() - INTERVAL '15 minutes'";
905
- timeRangeInterval = '15 minutes';
906
- timeSeriesStart = "NOW() - INTERVAL '15 minutes'";
907
- break;
908
- case '1h':
909
- interval = 'minute';
910
- timeFilter = "tx_record_time >= NOW() - INTERVAL '1 hour'";
911
- timeRangeInterval = '1 hour';
912
- timeSeriesStart = "NOW() - INTERVAL '1 hour'";
913
- break;
914
- case '6h':
915
- interval = 'minute';
916
- timeFilter = "tx_record_time >= NOW() - INTERVAL '6 hours'";
917
- timeRangeInterval = '6 hours';
918
- timeSeriesStart = "NOW() - INTERVAL '6 hours'";
919
- break;
920
- case '1d':
921
- interval = 'hour';
922
- timeFilter = "tx_record_time >= NOW() - INTERVAL '1 day'";
923
- timeRangeInterval = '1 day';
924
- timeSeriesStart = "NOW() - INTERVAL '1 day'";
925
- break;
926
- case '7d':
927
- interval = 'hour';
928
- timeFilter = "tx_record_time >= NOW() - INTERVAL '7 days'";
929
- timeRangeInterval = '7 days';
930
- timeSeriesStart = "NOW() - INTERVAL '7 days'";
931
- break;
932
- case '30d':
933
- interval = 'day';
934
- timeFilter = "tx_record_time >= NOW() - INTERVAL '30 days'";
935
- timeRangeInterval = '30 days';
936
- timeSeriesStart = "NOW() - INTERVAL '30 days'";
937
- break;
938
- case 'last-month':
939
- interval = 'day';
940
- timeFilter =
941
- "tx_record_time >= date_trunc('month', current_date - interval '1 month') AND tx_record_time < date_trunc('month', current_date)";
942
- timeRangeInterval = '1 month';
943
- timeSeriesStart =
944
- "date_trunc('month', current_date - interval '1 month')";
945
- timeSeriesEnd = "date_trunc('month', current_date)";
946
- break;
947
- case 'all':
948
- interval = 'day';
949
- timeFilter = '1=1';
950
- timeRangeInterval = '100 years';
951
- timeSeriesStart = "'2023-01-01'::timestamp";
952
- break;
953
- default:
954
- interval = 'hour';
955
- timeFilter = "tx_record_time >= NOW() - INTERVAL '1 day'";
956
- timeRangeInterval = '1 day';
957
- timeSeriesStart = "NOW() - INTERVAL '1 day'";
958
- }
959
- // If custom start date is provided, override the timeFilter and time series start/end
960
- if (timePeriod === 'custom-start' && customStartDate) {
961
- timeFilter = `tx_record_time >= '${customStartDate}:00' AND tx_record_time < ('${customStartDate}:00'::timestamp + INTERVAL '${timeRangeInterval}')`;
962
- timeSeriesStart = `'${customStartDate}:00'::timestamp`;
963
- timeSeriesEnd = `('${customStartDate}:00'::timestamp + INTERVAL '${timeRangeInterval}')`;
964
- }
965
- // Add party filter if provided
966
- const partyCondition = partyFilter
967
- ? ` AND beneficiary_party_id = '${partyFilter}'`
968
- : '';
969
- const query = `
970
- WITH time_series AS (
971
- SELECT generate_series(
972
- date_trunc('${interval}', ${timeSeriesStart}),
973
- date_trunc('${interval}', ${timeSeriesEnd}),
974
- INTERVAL '1 ${interval}'
975
- ) AS timestamp
976
- ),
977
- marker_data AS (
978
- SELECT
979
- date_trunc('${interval}', tx_record_time) AS timestamp,
980
- COUNT(*) FILTER (WHERE status = 'archived') as archived_count,
981
- COUNT(*) FILTER (WHERE status = 'created') as created_count,
982
- COUNT(*) FILTER (WHERE status = 'archived') as archived_weight,
983
- COUNT(*) FILTER (WHERE status = 'created') as created_weight
984
- FROM canton_app_markers
985
- WHERE ${timeFilter}${partyCondition}
986
- GROUP BY date_trunc('${interval}', tx_record_time)
987
- )
988
- SELECT
989
- ts.timestamp::text,
990
- COALESCE(md.archived_count, 0) as archived_count,
991
- COALESCE(md.created_count, 0) as created_count,
992
- COALESCE(md.archived_count, 0) + COALESCE(md.created_count, 0) as count,
993
- COALESCE(md.archived_weight, 0) as archived_weight,
994
- COALESCE(md.created_weight, 0) as created_weight,
995
- 0 as amount,
996
- 0 as coupon_amount
997
- FROM time_series ts
998
- LEFT JOIN marker_data md ON ts.timestamp = md.timestamp
999
- ORDER BY ts.timestamp ASC
1000
- `;
1001
- const result = await this.pool.query(query);
1002
- return result.rows.map(row => ({
1003
- timestamp: row.timestamp,
1004
- count: parseInt(row.count, 10),
1005
- archivedCount: parseInt(row.archived_count, 10),
1006
- createdCount: parseInt(row.created_count, 10),
1007
- archivedWeight: parseFloat(row.archived_weight),
1008
- createdWeight: parseFloat(row.created_weight),
1009
- amount: 0,
1010
- couponAmount: 0,
1011
- }));
1012
- }
1013
- async getAppMarkerRoundSeriesData(timeRange, _metric = 'count', timePeriod, customStartDate, partyFilter, _statusFilter) {
1014
- let timeRangeInterval;
1015
- switch (timeRange) {
1016
- case '15m':
1017
- timeRangeInterval = '15 minutes';
1018
- break;
1019
- case '1h':
1020
- timeRangeInterval = '1 hour';
1021
- break;
1022
- case '6h':
1023
- timeRangeInterval = '6 hours';
1024
- break;
1025
- case '1d':
1026
- timeRangeInterval = '1 day';
1027
- break;
1028
- case '7d':
1029
- timeRangeInterval = '7 days';
1030
- break;
1031
- case '30d':
1032
- timeRangeInterval = '30 days';
1033
- break;
1034
- default:
1035
- timeRangeInterval = '1 day';
1036
- }
1037
- if (timePeriod === 'custom-start' && customStartDate) {
1038
- const _timeFilter = `tx_record_time >= '${customStartDate}:00' AND tx_record_time < ('${customStartDate}:00'::timestamp + INTERVAL '${timeRangeInterval}')`;
1039
- void _timeFilter;
1040
- }
1041
- // Add party filter if provided
1042
- const _partyCondition = partyFilter
1043
- ? ` AND beneficiary_party_id = '${partyFilter}'`
1044
- : '';
1045
- void _partyCondition;
1046
- // Note: Markers don't have round numbers, so we just return time-based groupings
1047
- // This method shouldn't be used for markers - use getAppMarkerTimeSeriesData instead
1048
- // Returning empty result set as markers don't have rounds
1049
- const query = `
1050
- SELECT
1051
- 0::bigint as round,
1052
- NOW()::text as timestamp,
1053
- 0::bigint as archived_count,
1054
- 0::bigint as created_count,
1055
- 0::bigint as count,
1056
- 0::numeric as archived_weight,
1057
- 0::numeric as created_weight,
1058
- 0::numeric as amount,
1059
- 0::numeric as coupon_amount
1060
- WHERE false
1061
- `;
1062
- const result = await this.pool.query(query);
1063
- return result.rows.map(row => ({
1064
- timestamp: row.timestamp
1065
- ? new Date(row.timestamp).toISOString()
1066
- : new Date().toISOString(),
1067
- count: parseInt(row.count, 10),
1068
- archivedCount: parseInt(row.archived_count, 10),
1069
- createdCount: parseInt(row.created_count, 10),
1070
- archivedWeight: parseFloat(row.archived_weight),
1071
- createdWeight: parseFloat(row.created_weight),
1072
- amount: 0,
1073
- couponAmount: 0,
1074
- round: typeof row.round === 'number' ? row.round : parseInt(row.round, 10),
1075
- }));
1076
- }
1077
- async getUniqueMarkerBeneficiaryParties() {
1078
- const query = `
1079
- SELECT DISTINCT
1080
- beneficiary_party_id as party_id,
1081
- beneficiary_party_id as display_name
1082
- FROM canton_app_markers
1083
- WHERE beneficiary_party_id IS NOT NULL
1084
- ORDER BY beneficiary_party_id ASC
1085
- `;
1086
- const result = await this.pool.query(query);
1087
- return result.rows;
1088
- }
1089
- async getMonthlyPaymentTotalForParty(partyId, monthStart, monthEnd) {
1090
- // Check for transfers using created_at since tx_record_time might be null
1091
- const query = `
1092
- SELECT COALESCE(SUM(transfer_amount), 0) as total_amount
1093
- FROM canton_transfers
1094
- WHERE transfer_sender_party_id = $1
1095
- AND created_at >= $2
1096
- AND created_at <= $3
1097
- AND status = $4
1098
- `;
1099
- const result = await this.pool.query(query, [
1100
- partyId,
1101
- monthStart,
1102
- monthEnd,
1103
- types_1.TransferStatus.CONFIRMED,
1104
- ]);
1105
- return parseFloat(result.rows[0].total_amount);
1106
- }
1107
- getRemainingRoundsInMonth(roundDurationMinutes = 10) {
1108
- if (roundDurationMinutes <= 0) {
1109
- throw new Error('roundDurationMinutes must be greater than 0');
1110
- }
1111
- const now = new Date();
1112
- const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1);
1113
- const monthEnd = new Date(nextMonth.getTime() - 1); // Last moment of current month
1114
- // Calculate how many intervals are remaining in the current month
1115
- const roundDurationInMs = roundDurationMinutes * 60 * 1000;
1116
- const remainingTimeInMs = monthEnd.getTime() - now.getTime();
1117
- const remainingRounds = Math.max(0, Math.floor(remainingTimeInMs / roundDurationInMs));
1118
- return remainingRounds;
1119
- }
1120
- async getMonthlyPaymentTotalsForParties(partyIds, monthStart, monthEnd) {
1121
- if (partyIds.length === 0) {
1122
- return new Map();
1123
- }
1124
- // Create placeholders for the IN clause
1125
- const placeholders = partyIds.map((_, index) => `$${index + 4}`).join(',');
1126
- const query = `
1127
- SELECT transfer_sender_party_id, COALESCE(SUM(transfer_amount), 0) as total_amount
1128
- FROM canton_transfers
1129
- WHERE transfer_sender_party_id IN (${placeholders})
1130
- AND created_at >= $1
1131
- AND created_at <= $2
1132
- AND status = $3
1133
- GROUP BY transfer_sender_party_id
1134
- `;
1135
- const params = [
1136
- monthStart,
1137
- monthEnd,
1138
- types_1.TransferStatus.CONFIRMED,
1139
- ...partyIds,
1140
- ];
1141
- const result = await this.pool.query(query, params);
1142
- const totals = new Map();
1143
- result.rows.forEach(row => {
1144
- totals.set(row.transfer_sender_party_id, parseFloat(row.total_amount));
1145
- });
1146
- // Ensure all party IDs are in the map (with 0 if no transfers found)
1147
- partyIds.forEach(partyId => {
1148
- if (!totals.has(partyId)) {
1149
- totals.set(partyId, 0);
1150
- }
1151
- });
1152
- return totals;
1153
- }
1154
- async getLifetimePaymentStatisticsForParties(partyIds) {
1155
- if (partyIds.length === 0) {
1156
- return new Map();
1157
- }
1158
- // Create placeholders for the IN clause
1159
- const placeholders = partyIds.map((_, index) => `$${index + 1}`).join(',');
1160
- const query = `
1161
- SELECT
1162
- transfer_sender_party_id,
1163
- COUNT(*) as payment_count,
1164
- COALESCE(SUM(transfer_amount), 0) as total_value_sent
1165
- FROM canton_transfers
1166
- WHERE transfer_sender_party_id IN (${placeholders})
1167
- AND status = $${partyIds.length + 1}
1168
- GROUP BY transfer_sender_party_id
1169
- `;
1170
- const params = [...partyIds, types_1.TransferStatus.CONFIRMED];
1171
- const result = await this.pool.query(query, params);
1172
- const statistics = new Map();
1173
- result.rows.forEach(row => {
1174
- const paymentCount = parseInt(row.payment_count, 10);
1175
- const totalValueSent = parseFloat(row.total_value_sent);
1176
- const totalFeesPaid = paymentCount * 0.6; // 0.6 * count as specified
1177
- statistics.set(row.transfer_sender_party_id, {
1178
- paymentCount,
1179
- totalValueSent,
1180
- totalFeesPaid,
1181
- });
1182
- });
1183
- // Ensure all party IDs are in the map (with 0 if no transfers found)
1184
- partyIds.forEach(partyId => {
1185
- if (!statistics.has(partyId)) {
1186
- statistics.set(partyId, {
1187
- paymentCount: 0,
1188
- totalValueSent: 0,
1189
- totalFeesPaid: 0,
1190
- });
1191
- }
1192
- });
1193
- return statistics;
131
+ return this.rewardCoupons.getUniqueCouponBeneficiaryParties();
1194
132
  }
1195
- /**
1196
- * Get portals that have enable_canton_rewards=true but don't have canton parties yet
1197
- *
1198
- * @returns Array of portal info that need canton parties created
1199
- */
1200
- async getPortalsNeedingCantonParties() {
1201
- const query = `
1202
- SELECT p.id, p.company->>'name' as company_name
1203
- FROM portal p
1204
- WHERE p.enable_canton_rewards = true
1205
- AND NOT EXISTS (
1206
- SELECT 1 FROM canton_parties cp
1207
- WHERE cp.portal_id = p.id
1208
- )
1209
- ORDER BY p.created_at ASC
1210
- `;
1211
- const result = await this.pool.query(query);
1212
- return result.rows.map(row => ({
1213
- id: row.id,
1214
- companyName: row.company_name ?? undefined,
1215
- }));
1216
- }
1217
- // Transaction helper - runs a callback within a database transaction
1218
- async runInTransaction(callback) {
1219
- const client = await this.pool.connect();
1220
- try {
1221
- await client.query('BEGIN');
1222
- const result = await callback(client);
1223
- await client.query('COMMIT');
1224
- return result;
1225
- }
1226
- catch (error) {
1227
- await client.query('ROLLBACK');
1228
- throw error;
1229
- }
1230
- finally {
1231
- client.release();
1232
- }
133
+ async getHistoricalIssuanceRates(roundNumbers) {
134
+ return this.rewardCoupons.getHistoricalIssuanceRates(roundNumbers);
1233
135
  }
1234
- // Canton App Markers CRUD operations
136
+ // ========================================================================
137
+ // App Marker Operations
138
+ // ========================================================================
1235
139
  async insertCantonAppMarker(marker) {
1236
- const query = `
1237
- INSERT INTO canton_app_markers (
1238
- status, contract_id, provider_party_id, beneficiary_party_id, tx_record_time, weight
1239
- ) VALUES ($1, $2, $3, $4, $5, $6)
1240
- RETURNING *
1241
- `;
1242
- const values = [
1243
- marker.status,
1244
- marker.contract_id,
1245
- marker.provider_party_id,
1246
- marker.beneficiary_party_id,
1247
- marker.tx_record_time,
1248
- marker.weight,
1249
- ];
1250
- const result = await this.pool.query(query, values);
1251
- return this.mapAppMarkerFromDb(result.rows[0]);
140
+ return this.appMarkers.insertCantonAppMarker(marker);
1252
141
  }
1253
142
  async batchInsertCantonAppMarkers(markers) {
1254
- if (markers.length === 0) {
1255
- return { insertedCount: 0, skippedCount: 0 };
1256
- }
1257
- // Build the VALUES clause with placeholders
1258
- const valuesPlaceholders = markers
1259
- .map((_, idx) => `($${idx * 6 + 1}, $${idx * 6 + 2}, $${idx * 6 + 3}, $${idx * 6 + 4}, $${idx * 6 + 5}, $${idx * 6 + 6})`)
1260
- .join(', ');
1261
- const query = `
1262
- INSERT INTO canton_app_markers (
1263
- status, contract_id, provider_party_id, beneficiary_party_id, tx_record_time, weight
1264
- ) VALUES ${valuesPlaceholders}
1265
- ON CONFLICT (contract_id) DO NOTHING
1266
- RETURNING *
1267
- `;
1268
- // Flatten all marker values into a single array
1269
- const values = markers.flatMap(marker => [
1270
- marker.status,
1271
- marker.contract_id,
1272
- marker.provider_party_id,
1273
- marker.beneficiary_party_id,
1274
- marker.tx_record_time,
1275
- marker.weight,
1276
- ]);
1277
- const result = await this.pool.query(query, values);
1278
- const insertedCount = result.rowCount ?? 0;
1279
- const skippedCount = markers.length - insertedCount;
1280
- return { insertedCount, skippedCount };
143
+ return this.appMarkers.batchInsertCantonAppMarkers(markers);
1281
144
  }
1282
145
  async upsertCantonAppMarkerAsArchived(marker) {
1283
- const query = `
1284
- INSERT INTO canton_app_markers (
1285
- status, contract_id, provider_party_id, beneficiary_party_id, tx_record_time, weight
1286
- ) VALUES ($1, $2, $3, $4, $5, $6)
1287
- ON CONFLICT (contract_id) DO UPDATE
1288
- SET status = EXCLUDED.status, updated_at = NOW()
1289
- RETURNING *
1290
- `;
1291
- const values = [
1292
- marker.status,
1293
- marker.contract_id,
1294
- marker.provider_party_id,
1295
- marker.beneficiary_party_id,
1296
- marker.tx_record_time,
1297
- marker.weight,
1298
- ];
1299
- const result = await this.pool.query(query, values);
1300
- return this.mapAppMarkerFromDb(result.rows[0]);
146
+ return this.appMarkers.upsertCantonAppMarkerAsArchived(marker);
1301
147
  }
1302
148
  async getCantonAppMarker(id) {
1303
- const query = 'SELECT * FROM canton_app_markers WHERE id = $1';
1304
- const result = await this.pool.query(query, [id]);
1305
- return result.rows.length > 0
1306
- ? this.mapAppMarkerFromDb(result.rows[0])
1307
- : null;
149
+ return this.appMarkers.getCantonAppMarker(id);
1308
150
  }
1309
151
  async getCantonAppMarkerByContractId(contractId) {
1310
- const query = 'SELECT * FROM canton_app_markers WHERE contract_id = $1';
1311
- const result = await this.pool.query(query, [contractId]);
1312
- return result.rows.length > 0
1313
- ? this.mapAppMarkerFromDb(result.rows[0])
1314
- : null;
152
+ return this.appMarkers.getCantonAppMarkerByContractId(contractId);
1315
153
  }
1316
154
  async getCantonAppMarkersByContractIds(contractIds) {
1317
- if (contractIds.length === 0) {
1318
- return [];
1319
- }
1320
- const placeholders = contractIds
1321
- .map((_, index) => `$${index + 1}`)
1322
- .join(', ');
1323
- const query = `
1324
- SELECT * FROM canton_app_markers
1325
- WHERE contract_id IN (${placeholders})
1326
- `;
1327
- const result = await this.pool.query(query, contractIds);
1328
- return result.rows.map(row => this.mapAppMarkerFromDb(row));
155
+ return this.appMarkers.getCantonAppMarkersByContractIds(contractIds);
1329
156
  }
1330
157
  async batchUpdateCantonAppMarkersByContractIds(contractIds, updates) {
1331
- if (contractIds.length === 0) {
1332
- return [];
1333
- }
1334
- const setClauses = [];
1335
- const values = [];
1336
- let paramIndex = 1;
1337
- // Build dynamic update query
1338
- Object.entries(updates).forEach(([key, value]) => {
1339
- if (key !== 'id' && key !== 'created_at') {
1340
- setClauses.push(`${this.toSnakeCase(key)} = $${paramIndex}`);
1341
- values.push(value);
1342
- paramIndex++;
1343
- }
1344
- });
1345
- if (setClauses.length === 0) {
1346
- throw new Error('No valid fields to update');
1347
- }
1348
- // Build the WHERE clause for contract IDs
1349
- const contractIdPlaceholders = contractIds
1350
- .map((_, index) => `$${paramIndex + index}`)
1351
- .join(', ');
1352
- values.push(...contractIds);
1353
- const query = `
1354
- UPDATE canton_app_markers
1355
- SET ${setClauses.join(', ')}, updated_at = NOW()
1356
- WHERE contract_id IN (${contractIdPlaceholders})
1357
- RETURNING *
1358
- `;
1359
- const result = await this.pool.query(query, values);
1360
- return result.rows.map(row => this.mapAppMarkerFromDb(row));
158
+ return this.appMarkers.batchUpdateCantonAppMarkersByContractIds(contractIds, updates);
1361
159
  }
1362
160
  async getCantonAppMarkersByStatus(status, limit = 100, sortOrder = 'ASC') {
1363
- const query = `
1364
- SELECT * FROM canton_app_markers
1365
- WHERE status = $1
1366
- ORDER BY tx_record_time ${sortOrder}
1367
- LIMIT $2
1368
- `;
1369
- const result = await this.pool.query(query, [status, limit]);
1370
- return result.rows.map(row => this.mapAppMarkerFromDb(row));
161
+ return this.appMarkers.getCantonAppMarkersByStatus(status, limit, sortOrder);
1371
162
  }
1372
163
  async getLatestCantonAppMarkers(limit = 3, beneficiaryPartyId) {
1373
- let query = `
1374
- SELECT * FROM canton_app_markers
1375
- `;
1376
- const params = [];
1377
- if (beneficiaryPartyId) {
1378
- query += ` WHERE beneficiary_party_id = $1`;
1379
- params.push(beneficiaryPartyId);
1380
- }
1381
- query += ` ORDER BY tx_record_time DESC LIMIT $${params.length + 1}`;
1382
- params.push(limit);
1383
- const result = await this.pool.query(query, params);
1384
- return result.rows.map(row => this.mapAppMarkerFromDb(row));
164
+ return this.appMarkers.getLatestCantonAppMarkers(limit, beneficiaryPartyId);
1385
165
  }
1386
166
  async getOldestUnarchivedCantonAppMarkers(limit = 3, beneficiaryPartyId) {
1387
- let query = `
1388
- SELECT * FROM canton_app_markers
1389
- WHERE status = 'created'
1390
- `;
1391
- const params = [];
1392
- if (beneficiaryPartyId) {
1393
- query += ` AND beneficiary_party_id = $${params.length + 1}`;
1394
- params.push(beneficiaryPartyId);
1395
- }
1396
- query += ` ORDER BY tx_record_time ASC LIMIT $${params.length + 1}`;
1397
- params.push(limit);
1398
- const result = await this.pool.query(query, params);
1399
- return result.rows.map(row => this.mapAppMarkerFromDb(row));
167
+ return this.appMarkers.getOldestUnarchivedCantonAppMarkers(limit, beneficiaryPartyId);
1400
168
  }
1401
169
  async updateCantonAppMarker(id, updates) {
1402
- const setClauses = [];
1403
- const values = [];
1404
- let paramIndex = 1;
1405
- // Build dynamic update query
1406
- Object.entries(updates).forEach(([key, value]) => {
1407
- if (key !== 'id' && key !== 'created_at') {
1408
- setClauses.push(`${this.toSnakeCase(key)} = $${paramIndex}`);
1409
- values.push(value);
1410
- paramIndex++;
1411
- }
1412
- });
1413
- if (setClauses.length === 0) {
1414
- throw new Error('No valid fields to update');
1415
- }
1416
- values.push(id);
1417
- const query = `
1418
- UPDATE canton_app_markers
1419
- SET ${setClauses.join(', ')}, updated_at = NOW()
1420
- WHERE id = $${paramIndex}
1421
- RETURNING *
1422
- `;
1423
- const result = await this.pool.query(query, values);
1424
- return result.rows.length > 0
1425
- ? this.mapAppMarkerFromDb(result.rows[0])
1426
- : null;
1427
- }
1428
- // Canton Parties CRUD operations
170
+ return this.appMarkers.updateCantonAppMarker(id, updates);
171
+ }
172
+ async getUniqueMarkerBeneficiaryParties() {
173
+ return this.appMarkers.getUniqueMarkerBeneficiaryParties();
174
+ }
175
+ // ========================================================================
176
+ // Party Operations
177
+ // ========================================================================
1429
178
  async insertCantonParty(party) {
1430
- const query = `
1431
- INSERT INTO canton_parties (
1432
- party_id, portal_id, provider, last_payment_until, is_active_customer_since
1433
- ) VALUES ($1, $2, $3, $4, $5)
1434
- RETURNING *
1435
- `;
1436
- const values = [
1437
- party.party_id,
1438
- party.portal_id,
1439
- party.provider,
1440
- party.last_payment_until,
1441
- party.is_active_customer_since,
1442
- ];
1443
- const result = await this.pool.query(query, values);
1444
- return this.mapPartyFromDb(result.rows[0]);
179
+ return this.parties.insertCantonParty(party);
1445
180
  }
1446
181
  async getCantonParty(id) {
1447
- const query = `
1448
- SELECT
1449
- cp.*,
1450
- p.company
1451
- FROM canton_parties cp
1452
- LEFT JOIN portal p ON cp.portal_id = p.id
1453
- WHERE cp.id = $1
1454
- `;
1455
- const result = await this.pool.query(query, [id]);
1456
- return result.rows.length > 0 ? this.mapPartyFromDb(result.rows[0]) : null;
182
+ return this.parties.getCantonParty(id);
1457
183
  }
1458
184
  async getCantonPartyByPartyId(partyId) {
1459
- const query = `
1460
- SELECT
1461
- cp.*,
1462
- p.company
1463
- FROM canton_parties cp
1464
- LEFT JOIN portal p ON cp.portal_id = p.id
1465
- WHERE cp.party_id = $1
1466
- `;
1467
- const result = await this.pool.query(query, [partyId]);
1468
- return result.rows.length > 0 ? this.mapPartyFromDb(result.rows[0]) : null;
185
+ return this.parties.getCantonPartyByPartyId(partyId);
1469
186
  }
1470
187
  async updateCantonParty(id, updates) {
1471
- const setClauses = [];
1472
- const values = [];
1473
- let paramIndex = 1;
1474
- // Build dynamic update query - exclude amulets field
1475
- Object.entries(updates).forEach(([key, value]) => {
1476
- if (key !== 'id' && key !== 'created_at' && key !== 'amulets') {
1477
- setClauses.push(`${this.toSnakeCase(key)} = $${paramIndex}`);
1478
- values.push(value);
1479
- paramIndex++;
1480
- }
1481
- });
1482
- if (setClauses.length === 0) {
1483
- throw new Error('No valid fields to update');
1484
- }
1485
- values.push(id);
1486
- const query = `
1487
- UPDATE canton_parties
1488
- SET ${setClauses.join(', ')}, updated_at = NOW()
1489
- WHERE id = $${paramIndex}
1490
- RETURNING id
1491
- `;
1492
- const result = await this.pool.query(query, values);
1493
- if (result.rows.length > 0) {
1494
- return this.getCantonParty(id);
1495
- }
1496
- return null;
188
+ return this.parties.updateCantonParty(id, updates);
1497
189
  }
1498
190
  async updateCantonPartyByPartyId(partyId, updates) {
1499
- const setClauses = [];
1500
- const values = [];
1501
- let paramIndex = 1;
1502
- // Build dynamic update query - exclude amulets field
1503
- Object.entries(updates).forEach(([key, value]) => {
1504
- if (key !== 'id' && key !== 'created_at' && key !== 'amulets') {
1505
- setClauses.push(`${this.toSnakeCase(key)} = $${paramIndex}`);
1506
- values.push(value);
1507
- paramIndex++;
1508
- }
1509
- });
1510
- if (setClauses.length === 0) {
1511
- throw new Error('No valid fields to update');
1512
- }
1513
- values.push(partyId);
1514
- const query = `
1515
- UPDATE canton_parties
1516
- SET ${setClauses.join(', ')}, updated_at = NOW()
1517
- WHERE party_id = $${paramIndex}
1518
- RETURNING party_id
1519
- `;
1520
- const result = await this.pool.query(query, values);
1521
- if (result.rows.length > 0) {
1522
- return this.getCantonPartyByPartyId(partyId);
1523
- }
1524
- return null;
191
+ return this.parties.updateCantonPartyByPartyId(partyId, updates);
1525
192
  }
1526
- // Amulet operations removed - use getActiveContracts instead
1527
- // async addAmuletToParty() - REMOVED
1528
- // async removeAmuletFromParty() - REMOVED
1529
- // async replaceAmuletsForParty() - REMOVED
1530
193
  async getActiveCustomersWithExpiredPayment() {
1531
- const query = `
1532
- SELECT
1533
- cp.*,
1534
- p.company
1535
- FROM canton_parties cp
1536
- LEFT JOIN portal p ON cp.portal_id = p.id
1537
- WHERE cp.is_active_customer_since IS NOT NULL
1538
- AND (cp.last_payment_until IS NULL OR cp.last_payment_until < NOW())
1539
- ORDER BY cp.last_payment_until ASC NULLS FIRST
1540
- `;
1541
- const result = await this.pool.query(query);
1542
- return result.rows.map(row => this.mapPartyFromDb(row));
194
+ return this.parties.getActiveCustomersWithExpiredPayment();
1543
195
  }
1544
196
  async getAllParties(provider) {
1545
- let query = `
1546
- SELECT
1547
- cp.*,
1548
- p.company
1549
- FROM canton_parties cp
1550
- INNER JOIN portal p ON cp.portal_id = p.id
1551
- `;
1552
- const values = [];
1553
- if (provider) {
1554
- // Handle 5n-broker case since database enum doesn't support it yet
1555
- if (provider === '5n-broker') {
1556
- // For now, return empty array since 5n-broker parties aren't in the database yet
1557
- console.log('⚠️ 5n-broker provider requested - returning empty array (not in database yet)');
1558
- return [];
1559
- }
1560
- query += ` WHERE cp.provider = $1`;
1561
- values.push(provider);
1562
- }
1563
- query += ` ORDER BY cp.created_at ASC`;
1564
- console.log(`🔍 Database query for network=${this.network}, provider=${provider}:`, { query, values });
1565
- const result = await this.pool.query(query, values);
1566
- console.log(`📋 Database returned ${result.rows.length} parties for network=${this.network}, provider=${provider}`);
1567
- if (result.rows.length > 0) {
1568
- console.log('📝 Sample database parties:', result.rows
1569
- .slice(0, 3)
1570
- .map(row => ({ party_id: row.party_id, provider: row.provider })));
1571
- }
1572
- const parties = result.rows.map(row => this.mapPartyFromDb(row));
1573
- // Get payment statistics for all parties
1574
- const partyIds = parties.map(party => party.party_id);
1575
- const paymentStats = await this.getLifetimePaymentStatisticsForParties(partyIds);
1576
- // Merge payment statistics with party data
1577
- return parties.map(party => {
1578
- const stats = paymentStats.get(party.party_id);
1579
- return {
1580
- ...party,
1581
- payment_count: stats?.paymentCount ?? 0,
1582
- total_value_sent: stats?.totalValueSent ?? 0,
1583
- total_fees_paid: stats?.totalFeesPaid ?? 0,
1584
- };
1585
- });
1586
- }
1587
- // OCF Deployments CRUD operations
197
+ return this.parties.getAllParties(provider);
198
+ }
199
+ async getPortalsNeedingCantonParties() {
200
+ return this.parties.getPortalsNeedingCantonParties();
201
+ }
202
+ // ========================================================================
203
+ // Time Series Operations
204
+ // ========================================================================
205
+ async getTransferTimeSeriesData(timeRange, metric = 'count', timePeriod, customStartDate) {
206
+ return this.timeSeries.getTransferTimeSeriesData(timeRange, metric, timePeriod, customStartDate);
207
+ }
208
+ async getRewardCouponTimeSeriesData(timeRange, metric = 'count', timePeriod, customStartDate, partyFilter, couponStatus = 'all') {
209
+ return this.timeSeries.getRewardCouponTimeSeriesData(timeRange, metric, timePeriod, customStartDate, partyFilter, couponStatus);
210
+ }
211
+ async getRewardCouponRoundSeriesData(timeRange, metric = 'count', timePeriod, customStartDate, partyFilter, couponStatus = 'all') {
212
+ return this.timeSeries.getRewardCouponRoundSeriesData(timeRange, metric, timePeriod, customStartDate, partyFilter, couponStatus);
213
+ }
214
+ async getAppMarkerTimeSeriesData(timeRange, metric = 'count', timePeriod, customStartDate, partyFilter, statusFilter) {
215
+ return this.timeSeries.getAppMarkerTimeSeriesData(timeRange, metric, timePeriod, customStartDate, partyFilter, statusFilter);
216
+ }
217
+ async getAppMarkerRoundSeriesData(timeRange, metric = 'count', timePeriod, customStartDate, partyFilter, statusFilter) {
218
+ return this.timeSeries.getAppMarkerRoundSeriesData(timeRange, metric, timePeriod, customStartDate, partyFilter, statusFilter);
219
+ }
220
+ async getMonthlyTransferTotal(monthStart, monthEnd) {
221
+ return this.timeSeries.getMonthlyTransferTotal(monthStart, monthEnd);
222
+ }
223
+ // ========================================================================
224
+ // OCF Deployment Operations
225
+ // ========================================================================
1588
226
  async insertOcfDeployment(deployment) {
1589
- const query = `
1590
- INSERT INTO ocf_deployments (
1591
- ocf_object_id, version, chain_id, status, tx_hash, contract_id, party_id, wallet_address
1592
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
1593
- RETURNING id, ocf_object_id, version, chain_id, status, tx_hash, contract_id, party_id, wallet_address, created_at, updated_at
1594
- `;
1595
- const values = [
1596
- deployment.ocf_object_id,
1597
- deployment.version,
1598
- deployment.chain_id,
1599
- deployment.status,
1600
- deployment.tx_hash,
1601
- deployment.contract_id,
1602
- deployment.party_id,
1603
- deployment.wallet_address,
1604
- ];
1605
- const result = await this.pool.query(query, values);
1606
- const row = result.rows[0];
1607
- return {
1608
- id: row.id,
1609
- ocf_object_id: row.ocf_object_id,
1610
- version: row.version,
1611
- chain_id: row.chain_id,
1612
- status: row.status,
1613
- tx_hash: row.tx_hash,
1614
- contract_id: row.contract_id,
1615
- party_id: row.party_id,
1616
- wallet_address: row.wallet_address,
1617
- created_at: row.created_at,
1618
- updated_at: row.updated_at,
1619
- };
227
+ return this.ocfDeployments.insertOcfDeployment(deployment);
1620
228
  }
1621
229
  async getLatestOcfDeploymentByPartyId(partyId) {
1622
- const query = `
1623
- SELECT id, ocf_object_id, version, chain_id, status, tx_hash, contract_id, party_id, wallet_address, created_at, updated_at
1624
- FROM ocf_deployments
1625
- WHERE party_id = $1
1626
- ORDER BY version DESC
1627
- LIMIT 1
1628
- `;
1629
- const result = await this.pool.query(query, [partyId]);
1630
- if (result.rows.length === 0) {
1631
- return null;
1632
- }
1633
- const row = result.rows[0];
1634
- return {
1635
- id: row.id,
1636
- ocf_object_id: row.ocf_object_id,
1637
- version: row.version,
1638
- chain_id: row.chain_id,
1639
- status: row.status,
1640
- tx_hash: row.tx_hash,
1641
- contract_id: row.contract_id,
1642
- party_id: row.party_id,
1643
- wallet_address: row.wallet_address,
1644
- created_at: row.created_at,
1645
- updated_at: row.updated_at,
1646
- };
230
+ return this.ocfDeployments.getLatestOcfDeploymentByPartyId(partyId);
1647
231
  }
1648
232
  async getLatestOcfObjectByPortalId(portalId) {
1649
- const query = `
1650
- SELECT o.id as ocf_object_id, o.version
1651
- FROM latest_ocf_objects o
1652
- WHERE o.portal_id = $1
1653
- AND o.type = 'ISSUER'
1654
- ORDER BY o.version DESC
1655
- LIMIT 1
1656
- `;
1657
- const result = await this.pool.query(query, [portalId]);
1658
- if (result.rows.length === 0) {
1659
- return null;
1660
- }
1661
- const row = result.rows[0];
1662
- return {
1663
- ocf_object_id: row.ocf_object_id,
1664
- version: row.version,
1665
- };
233
+ return this.ocfDeployments.getLatestOcfObjectByPortalId(portalId);
1666
234
  }
1667
- /** Retrieve a specific OCF object row by id and version */
1668
235
  async getOcfObjectDataByIdAndVersion(ocfObjectId, version) {
1669
- const query = `
1670
- SELECT type, subtype, ocf_data
1671
- FROM ocf_objects
1672
- WHERE id = $1 AND version = $2
1673
- LIMIT 1
1674
- `;
1675
- const result = await this.pool.query(query, [ocfObjectId, version]);
1676
- if (result.rows.length === 0)
1677
- return null;
1678
- const row = result.rows[0];
1679
- return {
1680
- type: row.type,
1681
- subtype: row.subtype ?? null,
1682
- ocf_data: row.ocf_data,
1683
- };
1684
- }
1685
- /** List OCF deployments, optionally filtered */
236
+ return this.ocfDeployments.getOcfObjectDataByIdAndVersion(ocfObjectId, version);
237
+ }
1686
238
  async listOcfDeployments(params) {
1687
- const conditions = [];
1688
- const values = [];
1689
- let idx = 1;
1690
- if (params?.chainId) {
1691
- conditions.push(`chain_id = $${idx++}`);
1692
- values.push(params.chainId);
1693
- }
1694
- if (params?.status) {
1695
- conditions.push(`status = $${idx++}`);
1696
- values.push(params.status);
1697
- }
1698
- if (params?.partyId) {
1699
- conditions.push(`party_id = $${idx++}`);
1700
- values.push(params.partyId);
1701
- }
1702
- const whereClause = conditions.length
1703
- ? `WHERE ${conditions.join(' AND ')}`
1704
- : '';
1705
- const limitClause = typeof params?.limit === 'number' ? `LIMIT ${params.limit}` : '';
1706
- const offsetClause = typeof params?.offset === 'number' ? `OFFSET ${params.offset}` : '';
1707
- const query = `
1708
- SELECT id, ocf_object_id, version, chain_id, status, tx_hash, contract_id, party_id, wallet_address, created_at, updated_at
1709
- FROM ocf_deployments
1710
- ${whereClause}
1711
- ORDER BY created_at DESC
1712
- ${limitClause}
1713
- ${offsetClause}
1714
- `;
1715
- const result = await this.pool.query(query, values);
1716
- return result.rows.map(row => ({
1717
- id: row.id,
1718
- ocf_object_id: row.ocf_object_id,
1719
- version: row.version,
1720
- chain_id: row.chain_id,
1721
- status: row.status,
1722
- tx_hash: row.tx_hash,
1723
- contract_id: row.contract_id,
1724
- party_id: row.party_id,
1725
- wallet_address: row.wallet_address,
1726
- created_at: row.created_at,
1727
- updated_at: row.updated_at,
1728
- }));
1729
- }
1730
- /** Count total rows in latest_ocf_objects for progress stats */
239
+ return this.ocfDeployments.listOcfDeployments(params);
240
+ }
1731
241
  async countLatestOcfObjects() {
1732
- const query = `SELECT COUNT(*) AS cnt FROM latest_ocf_objects`;
1733
- const result = await this.pool.query(query);
1734
- return Number(result.rows[0]?.cnt ?? 0);
242
+ return this.ocfDeployments.countLatestOcfObjects();
1735
243
  }
1736
- /**
1737
- * Count rows in latest_ocf_objects that belong to portals with sync_captable_onchain=true Used for actionable
1738
- * progress stats (objects that should be synced onchain)
1739
- */
1740
244
  async countLatestOcfObjectsForOnchainSync() {
1741
- const query = `
1742
- SELECT COUNT(*) AS cnt
1743
- FROM latest_ocf_objects o
1744
- JOIN portal p ON p.id = o.portal_id
1745
- WHERE p.enable_canton_rewards = true
1746
- `;
1747
- const result = await this.pool.query(query);
1748
- return Number(result.rows[0]?.cnt ?? 0);
245
+ return this.ocfDeployments.countLatestOcfObjectsForOnchainSync();
1749
246
  }
1750
247
  async getLatestOcfObjectDataByPortalId(portalId) {
1751
- const query = `
1752
- SELECT o.id as ocf_object_id, o.version, o.ocf_data, o.type
1753
- FROM latest_ocf_objects o
1754
- WHERE o.portal_id = $1
1755
- AND o.type = 'ISSUER'
1756
- ORDER BY o.version DESC
1757
- LIMIT 1
1758
- `;
1759
- const result = await this.pool.query(query, [portalId]);
1760
- if (result.rows.length === 0) {
1761
- return null;
1762
- }
1763
- const row = result.rows[0];
1764
- return {
1765
- ocf_object_id: row.ocf_object_id,
1766
- version: row.version,
1767
- ocf_data: row.ocf_data,
1768
- type: row.type,
1769
- };
248
+ return this.ocfDeployments.getLatestOcfObjectDataByPortalId(portalId);
1770
249
  }
1771
250
  async getPartiesWithoutOcfObjects() {
1772
- const query = `
1773
- SELECT
1774
- cp.party_id,
1775
- cp.portal_id,
1776
- cp.provider,
1777
- p.company
1778
- FROM canton_parties cp
1779
- LEFT JOIN portal p ON cp.portal_id = p.id
1780
- WHERE NOT EXISTS (
1781
- SELECT 1 FROM latest_ocf_objects o
1782
- WHERE o.portal_id = cp.portal_id
1783
- AND o.type = 'ISSUER'
1784
- )
1785
- ORDER BY cp.created_at ASC
1786
- `;
1787
- const result = await this.pool.query(query);
1788
- return result.rows;
251
+ return this.ocfDeployments.getPartiesWithoutOcfObjects();
1789
252
  }
1790
253
  async getPartiesWithOcfObjectsButNoIssuerDeployments() {
1791
- const query = `
1792
- SELECT
1793
- cp.party_id,
1794
- cp.portal_id,
1795
- cp.provider,
1796
- p.company,
1797
- o.id as ocf_object_id,
1798
- o.version as ocf_version,
1799
- o.ocf_data,
1800
- o.type as ocf_type
1801
- FROM canton_parties cp
1802
- LEFT JOIN portal p ON cp.portal_id = p.id
1803
- INNER JOIN latest_ocf_objects o ON o.portal_id = cp.portal_id AND o.type = 'ISSUER'
1804
- WHERE NOT EXISTS (
1805
- SELECT 1 FROM ocf_deployments od
1806
- WHERE od.party_id = cp.party_id
1807
- AND od.ocf_object_id = o.id
1808
- )
1809
- ORDER BY cp.created_at ASC
1810
- `;
1811
- const result = await this.pool.query(query);
1812
- return result.rows;
254
+ return this.ocfDeployments.getPartiesWithOcfObjectsButNoIssuerDeployments();
1813
255
  }
1814
256
  async getPartiesWithOutdatedIssuerDeployments() {
1815
- const query = `
1816
- SELECT
1817
- cp.party_id,
1818
- cp.portal_id,
1819
- cp.provider,
1820
- p.company,
1821
- latest_ocf.version as latest_ocf_version,
1822
- latest_deployment.version as deployed_version,
1823
- latest_ocf.id as latest_ocf_object_id,
1824
- latest_deployment.contract_id as current_contract_id,
1825
- latest_ocf.ocf_data,
1826
- latest_ocf.type as ocf_type
1827
- FROM canton_parties cp
1828
- LEFT JOIN portal p ON cp.portal_id = p.id
1829
- INNER JOIN (
1830
- SELECT o.portal_id, o.id, o.version, o.ocf_data, o.type
1831
- FROM latest_ocf_objects o
1832
- WHERE o.type = 'ISSUER'
1833
- ) latest_ocf ON latest_ocf.portal_id = cp.portal_id
1834
- INNER JOIN (
1835
- SELECT od.party_id, od.ocf_object_id, od.version, od.contract_id,
1836
- ROW_NUMBER() OVER (PARTITION BY od.party_id ORDER BY od.version DESC) as rn
1837
- FROM ocf_deployments od
1838
- WHERE od.status = 'deployed'
1839
- ) latest_deployment ON latest_deployment.party_id = cp.party_id
1840
- AND latest_deployment.rn = 1
1841
- WHERE latest_ocf.version > latest_deployment.version
1842
- ORDER BY cp.created_at ASC
1843
- `;
1844
- const result = await this.pool.query(query);
1845
- return result.rows;
257
+ return this.ocfDeployments.getPartiesWithOutdatedIssuerDeployments();
1846
258
  }
1847
259
  async getOnchainEquityValuations() {
1848
- const query = `
1849
- SELECT p.id AS portal_id,
1850
- p.id AS company_id,
1851
- p.company->>'name' AS company_name,
1852
- CASE
1853
- WHEN COALESCE((pp.company_data ->> 'company_custom_valuation')::numeric, 0) > COALESCE((pp.company_data ->> 'company_computed_valuation')::numeric, 0)
1854
- THEN COALESCE((pp.company_data ->> 'company_custom_valuation')::numeric, 0)
1855
- ELSE COALESCE((pp.company_data ->> 'company_computed_valuation')::numeric, 0)
1856
- END AS company_valuation
1857
- FROM portal p
1858
- JOIN portal_private pp ON pp.portal_id = p.id
1859
- WHERE (p.company ->> 'name') !~* 'Fairbnb'
1860
- AND (
1861
- p.captable_minted
1862
- OR (
1863
- p.domain !~* 'staging'
1864
- AND p.domain !~* 'websitecf'
1865
- AND p.domain !~* '.cafe'
1866
- )
1867
- )
1868
- ORDER BY p.captable_minted,
1869
- CASE
1870
- WHEN COALESCE((pp.company_data ->> 'company_custom_valuation')::numeric, 0) > COALESCE((pp.company_data ->> 'company_computed_valuation')::numeric, 0)
1871
- THEN COALESCE((pp.company_data ->> 'company_custom_valuation')::numeric, 0)
1872
- ELSE COALESCE((pp.company_data ->> 'company_computed_valuation')::numeric, 0)
1873
- END DESC
1874
- `;
1875
- const result = await this.pool.query(query);
1876
- return result.rows.map(row => ({
1877
- portal_id: row.portal_id,
1878
- company_id: row.company_id,
1879
- company_name: row.company_name ?? null,
1880
- company_valuation: Number(row.company_valuation ?? 0),
1881
- }));
260
+ return this.ocfDeployments.getOnchainEquityValuations();
1882
261
  }
1883
262
  async getPortalsNeedingStockClassDeployments() {
1884
- const query = `
1885
- SELECT DISTINCT
1886
- cp.portal_id,
1887
- cp.party_id,
1888
- cp.provider,
1889
- cp.created_at,
1890
- p.company,
1891
- loo.ocf_data,
1892
- loo.id AS ocf_object_id,
1893
- loo.version AS ocf_version,
1894
- (
1895
- SELECT od_issuer.contract_id
1896
- FROM ocf_deployments od_issuer
1897
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
1898
- WHERE od_issuer.party_id = cp.party_id
1899
- AND od_issuer.status = 'deployed'
1900
- AND loo_issuer.type = 'ISSUER'
1901
- ORDER BY od_issuer.created_at DESC
1902
- LIMIT 1
1903
- ) as issuer_contract_id
1904
- FROM canton_parties cp
1905
- LEFT JOIN portal p ON cp.portal_id = p.id
1906
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
1907
- WHERE
1908
- loo.type = 'STOCK_CLASS'
1909
- AND EXISTS (
1910
- SELECT 1 FROM ocf_deployments od_issuer
1911
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
1912
- WHERE od_issuer.party_id = cp.party_id
1913
- AND od_issuer.status = 'deployed'
1914
- AND loo_issuer.type = 'ISSUER'
1915
- )
1916
- AND NOT EXISTS (
1917
- SELECT 1 FROM ocf_deployments od_sc
1918
- WHERE od_sc.ocf_object_id = loo.id
1919
- AND od_sc.party_id = cp.party_id
1920
- AND od_sc.status = 'deployed'
1921
- )
1922
- ORDER BY cp.created_at ASC
1923
- `;
1924
- const result = await this.pool.query(query);
1925
- return result.rows;
263
+ return this.ocfDeployments.getPortalsNeedingStockClassDeployments();
1926
264
  }
1927
265
  async getPortalsNeedingStockClassUpdates() {
1928
- const query = `
1929
- SELECT DISTINCT
1930
- cp.portal_id,
1931
- cp.party_id,
1932
- cp.provider,
1933
- cp.created_at,
1934
- p.company,
1935
- loo.ocf_data,
1936
- loo.id AS ocf_object_id,
1937
- loo.version as latest_ocf_version,
1938
- od.version as deployed_version,
1939
- od.contract_id as current_contract_id,
1940
- (loo.version - od.version) AS version_diff,
1941
- (
1942
- SELECT od_issuer.contract_id
1943
- FROM ocf_deployments od_issuer
1944
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
1945
- WHERE od_issuer.party_id = cp.party_id
1946
- AND od_issuer.status = 'deployed'
1947
- AND loo_issuer.type = 'ISSUER'
1948
- ORDER BY od_issuer.created_at DESC
1949
- LIMIT 1
1950
- ) as issuer_contract_id
1951
- FROM canton_parties cp
1952
- LEFT JOIN portal p ON cp.portal_id = p.id
1953
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
1954
- JOIN ocf_deployments od ON loo.id = od.ocf_object_id AND cp.party_id = od.party_id
1955
- WHERE
1956
- loo.type = 'STOCK_CLASS'
1957
- AND EXISTS (
1958
- SELECT 1 FROM ocf_deployments od_issuer
1959
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
1960
- WHERE od_issuer.party_id = cp.party_id
1961
- AND od_issuer.status = 'deployed'
1962
- AND loo_issuer.type = 'ISSUER'
1963
- )
1964
- AND od.version < loo.version
1965
- AND od.status = 'deployed'
1966
- ORDER BY version_diff DESC, cp.created_at ASC
1967
- `;
1968
- const result = await this.pool.query(query);
1969
- return result.rows;
266
+ return this.ocfDeployments.getPortalsNeedingStockClassUpdates();
1970
267
  }
1971
268
  async getPortalsNeedingStakeholderDeployments() {
1972
- const query = `
1973
- SELECT DISTINCT
1974
- cp.portal_id,
1975
- cp.party_id,
1976
- cp.provider,
1977
- cp.created_at,
1978
- p.company,
1979
- loo.ocf_data,
1980
- loo.id AS ocf_object_id,
1981
- loo.version AS ocf_version,
1982
- (
1983
- SELECT od_issuer.contract_id
1984
- FROM ocf_deployments od_issuer
1985
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
1986
- WHERE od_issuer.party_id = cp.party_id
1987
- AND od_issuer.status = 'deployed'
1988
- AND loo_issuer.type = 'ISSUER'
1989
- ORDER BY od_issuer.created_at DESC
1990
- LIMIT 1
1991
- ) as issuer_contract_id
1992
- FROM canton_parties cp
1993
- LEFT JOIN portal p ON cp.portal_id = p.id
1994
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
1995
- WHERE
1996
- loo.type = 'STAKEHOLDER'
1997
- AND EXISTS (
1998
- SELECT 1 FROM ocf_deployments od_issuer
1999
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2000
- WHERE od_issuer.party_id = cp.party_id
2001
- AND od_issuer.status = 'deployed'
2002
- AND loo_issuer.type = 'ISSUER'
2003
- )
2004
- AND NOT EXISTS (
2005
- SELECT 1 FROM ocf_deployments od_sh
2006
- WHERE od_sh.ocf_object_id = loo.id
2007
- AND od_sh.party_id = cp.party_id
2008
- AND od_sh.status = 'deployed'
2009
- )
2010
- ORDER BY cp.created_at ASC
2011
- `;
2012
- const result = await this.pool.query(query);
2013
- return result.rows;
269
+ return this.ocfDeployments.getPortalsNeedingStakeholderDeployments();
2014
270
  }
2015
271
  async getPortalsNeedingStakeholderReDeployments() {
2016
- const query = `
2017
- SELECT DISTINCT
2018
- cp.portal_id,
2019
- cp.party_id,
2020
- cp.provider,
2021
- cp.created_at,
2022
- p.company,
2023
- loo.ocf_data,
2024
- loo.id AS ocf_object_id,
2025
- loo.version as latest_ocf_version,
2026
- od.version as deployed_version,
2027
- od.contract_id as current_contract_id,
2028
- (loo.version - od.version) AS version_diff,
2029
- (
2030
- SELECT od_issuer.contract_id
2031
- FROM ocf_deployments od_issuer
2032
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2033
- WHERE od_issuer.party_id = cp.party_id
2034
- AND od_issuer.status = 'deployed'
2035
- AND loo_issuer.type = 'ISSUER'
2036
- ORDER BY od_issuer.created_at DESC
2037
- LIMIT 1
2038
- ) as issuer_contract_id
2039
- FROM canton_parties cp
2040
- LEFT JOIN portal p ON cp.portal_id = p.id
2041
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2042
- JOIN ocf_deployments od ON loo.id = od.ocf_object_id AND cp.party_id = od.party_id
2043
- WHERE
2044
- loo.type = 'STAKEHOLDER'
2045
- AND EXISTS (
2046
- SELECT 1 FROM ocf_deployments od_issuer
2047
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2048
- WHERE od_issuer.party_id = cp.party_id
2049
- AND od_issuer.status = 'deployed'
2050
- AND loo_issuer.type = 'ISSUER'
2051
- )
2052
- AND od.version < loo.version
2053
- AND od.status = 'deployed'
2054
- ORDER BY version_diff DESC, cp.created_at ASC
2055
- `;
2056
- const result = await this.pool.query(query);
2057
- return result.rows;
272
+ return this.ocfDeployments.getPortalsNeedingStakeholderReDeployments();
2058
273
  }
2059
274
  async getPortalsNeedingStockPlanDeployments() {
2060
- const query = `
2061
- SELECT DISTINCT
2062
- cp.portal_id,
2063
- cp.party_id,
2064
- cp.provider,
2065
- cp.created_at,
2066
- p.company,
2067
- loo.ocf_data,
2068
- loo.id AS ocf_object_id,
2069
- loo.version AS ocf_version,
2070
- (
2071
- SELECT od_issuer.contract_id
2072
- FROM ocf_deployments od_issuer
2073
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2074
- WHERE od_issuer.party_id = cp.party_id
2075
- AND od_issuer.status = 'deployed'
2076
- AND loo_issuer.type = 'ISSUER'
2077
- ORDER BY od_issuer.created_at DESC
2078
- LIMIT 1
2079
- ) as issuer_contract_id
2080
- FROM canton_parties cp
2081
- LEFT JOIN portal p ON cp.portal_id = p.id
2082
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2083
- WHERE
2084
- loo.type = 'STOCK_PLAN'
2085
- AND EXISTS (
2086
- SELECT 1 FROM ocf_deployments od_issuer
2087
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2088
- WHERE od_issuer.party_id = cp.party_id
2089
- AND od_issuer.status = 'deployed'
2090
- AND loo_issuer.type = 'ISSUER'
2091
- )
2092
- AND NOT EXISTS (
2093
- SELECT 1 FROM ocf_deployments od_sp
2094
- WHERE od_sp.ocf_object_id = loo.id
2095
- AND od_sp.party_id = cp.party_id
2096
- AND od_sp.status = 'deployed'
2097
- )
2098
- ORDER BY cp.created_at ASC
2099
- `;
2100
- const result = await this.pool.query(query);
2101
- return result.rows;
275
+ return this.ocfDeployments.getPortalsNeedingStockPlanDeployments();
2102
276
  }
2103
277
  async getPortalsNeedingStockPlanReDeployments() {
2104
- const query = `
2105
- SELECT DISTINCT
2106
- cp.portal_id,
2107
- cp.party_id,
2108
- cp.provider,
2109
- cp.created_at,
2110
- p.company,
2111
- loo.ocf_data,
2112
- loo.id AS ocf_object_id,
2113
- loo.version as latest_ocf_version,
2114
- od.version as deployed_version,
2115
- od.contract_id as current_contract_id,
2116
- (loo.version - od.version) AS version_diff,
2117
- (
2118
- SELECT od_issuer.contract_id
2119
- FROM ocf_deployments od_issuer
2120
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2121
- WHERE od_issuer.party_id = cp.party_id
2122
- AND od_issuer.status = 'deployed'
2123
- AND loo_issuer.type = 'ISSUER'
2124
- ORDER BY od_issuer.created_at DESC
2125
- LIMIT 1
2126
- ) as issuer_contract_id
2127
- FROM canton_parties cp
2128
- LEFT JOIN portal p ON cp.portal_id = p.id
2129
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2130
- JOIN ocf_deployments od ON loo.id = od.ocf_object_id AND cp.party_id = od.party_id
2131
- WHERE
2132
- loo.type = 'STOCK_PLAN'
2133
- AND EXISTS (
2134
- SELECT 1 FROM ocf_deployments od_issuer
2135
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2136
- WHERE od_issuer.party_id = cp.party_id
2137
- AND od_issuer.status = 'deployed'
2138
- AND loo_issuer.type = 'ISSUER'
2139
- )
2140
- AND od.version < loo.version
2141
- AND od.status = 'deployed'
2142
- ORDER BY version_diff DESC, cp.created_at ASC
2143
- `;
2144
- const result = await this.pool.query(query);
2145
- return result.rows;
278
+ return this.ocfDeployments.getPortalsNeedingStockPlanReDeployments();
2146
279
  }
2147
280
  async getPortalsNeedingStockLegendTemplateDeployments() {
2148
- const query = `
2149
- SELECT DISTINCT
2150
- cp.portal_id,
2151
- cp.party_id,
2152
- cp.provider,
2153
- cp.created_at,
2154
- p.company,
2155
- loo.ocf_data,
2156
- loo.id AS ocf_object_id,
2157
- loo.version AS ocf_version,
2158
- (
2159
- SELECT od_issuer.contract_id
2160
- FROM ocf_deployments od_issuer
2161
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2162
- WHERE od_issuer.party_id = cp.party_id
2163
- AND od_issuer.status = 'deployed'
2164
- AND loo_issuer.type = 'ISSUER'
2165
- ORDER BY od_issuer.created_at DESC
2166
- LIMIT 1
2167
- ) as issuer_contract_id
2168
- FROM canton_parties cp
2169
- LEFT JOIN portal p ON cp.portal_id = p.id
2170
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2171
- WHERE
2172
- (loo.type = 'OBJECT' AND loo.subtype = 'STOCK_LEGEND_TEMPLATE')
2173
- AND EXISTS (
2174
- SELECT 1 FROM ocf_deployments od_issuer
2175
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2176
- WHERE od_issuer.party_id = cp.party_id
2177
- AND od_issuer.status = 'deployed'
2178
- AND loo_issuer.type = 'ISSUER'
2179
- )
2180
- AND NOT EXISTS (
2181
- SELECT 1 FROM ocf_deployments od
2182
- WHERE od.ocf_object_id = loo.id
2183
- AND od.party_id = cp.party_id
2184
- AND od.status = 'deployed'
2185
- )
2186
- ORDER BY cp.created_at ASC
2187
- `;
2188
- const result = await this.pool.query(query);
2189
- return result.rows;
281
+ return this.ocfDeployments.getPortalsNeedingStockLegendTemplateDeployments();
2190
282
  }
2191
283
  async getPortalsNeedingDocumentDeployments() {
2192
- const query = `
2193
- SELECT DISTINCT
2194
- cp.portal_id,
2195
- cp.party_id,
2196
- cp.provider,
2197
- cp.created_at,
2198
- p.company,
2199
- loo.ocf_data,
2200
- loo.id AS ocf_object_id,
2201
- loo.version AS ocf_version,
2202
- (
2203
- SELECT od_issuer.contract_id
2204
- FROM ocf_deployments od_issuer
2205
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2206
- WHERE od_issuer.party_id = cp.party_id
2207
- AND od_issuer.status = 'deployed'
2208
- AND loo_issuer.type = 'ISSUER'
2209
- ORDER BY od_issuer.created_at DESC
2210
- LIMIT 1
2211
- ) as issuer_contract_id
2212
- FROM canton_parties cp
2213
- LEFT JOIN portal p ON cp.portal_id = p.id
2214
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2215
- WHERE
2216
- (loo.type = 'OBJECT' AND loo.subtype = 'DOCUMENT')
2217
- AND EXISTS (
2218
- SELECT 1 FROM ocf_deployments od_issuer
2219
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2220
- WHERE od_issuer.party_id = cp.party_id
2221
- AND od_issuer.status = 'deployed'
2222
- AND loo_issuer.type = 'ISSUER'
2223
- )
2224
- AND NOT EXISTS (
2225
- SELECT 1 FROM ocf_deployments od
2226
- WHERE od.ocf_object_id = loo.id
2227
- AND od.party_id = cp.party_id
2228
- AND od.status = 'deployed'
2229
- )
2230
- ORDER BY cp.created_at ASC
2231
- `;
2232
- const result = await this.pool.query(query);
2233
- return result.rows;
284
+ return this.ocfDeployments.getPortalsNeedingDocumentDeployments();
2234
285
  }
2235
286
  async getPortalsNeedingVestingTermsDeployments() {
2236
- const query = `
2237
- SELECT DISTINCT
2238
- cp.portal_id,
2239
- cp.party_id,
2240
- cp.provider,
2241
- cp.created_at,
2242
- p.company,
2243
- loo.ocf_data,
2244
- loo.id AS ocf_object_id,
2245
- loo.version AS ocf_version,
2246
- (
2247
- SELECT od_issuer.contract_id
2248
- FROM ocf_deployments od_issuer
2249
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2250
- WHERE od_issuer.party_id = cp.party_id
2251
- AND od_issuer.status = 'deployed'
2252
- AND loo_issuer.type = 'ISSUER'
2253
- ORDER BY od_issuer.created_at DESC
2254
- LIMIT 1
2255
- ) as issuer_contract_id
2256
- FROM canton_parties cp
2257
- LEFT JOIN portal p ON cp.portal_id = p.id
2258
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2259
- WHERE
2260
- (loo.type = 'OBJECT' AND loo.subtype = 'VESTING_TERMS')
2261
- AND EXISTS (
2262
- SELECT 1 FROM ocf_deployments od_issuer
2263
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2264
- WHERE od_issuer.party_id = cp.party_id
2265
- AND od_issuer.status = 'deployed'
2266
- AND loo_issuer.type = 'ISSUER'
2267
- )
2268
- AND NOT EXISTS (
2269
- SELECT 1 FROM ocf_deployments od
2270
- WHERE od.ocf_object_id = loo.id
2271
- AND od.party_id = cp.party_id
2272
- AND od.status = 'deployed'
2273
- )
2274
- ORDER BY cp.created_at ASC
2275
- `;
2276
- const result = await this.pool.query(query);
2277
- return result.rows;
287
+ return this.ocfDeployments.getPortalsNeedingVestingTermsDeployments();
2278
288
  }
2279
289
  async getPortalsNeedingStockIssuanceDeployments() {
2280
- const query = `
2281
- SELECT DISTINCT
2282
- cp.portal_id,
2283
- cp.party_id,
2284
- cp.provider,
2285
- cp.created_at,
2286
- p.company,
2287
- loo.ocf_data,
2288
- loo.id AS ocf_object_id,
2289
- loo.version AS ocf_version,
2290
- (
2291
- SELECT od_issuer.contract_id
2292
- FROM ocf_deployments od_issuer
2293
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2294
- WHERE od_issuer.party_id = cp.party_id
2295
- AND od_issuer.status = 'deployed'
2296
- AND loo_issuer.type = 'ISSUER'
2297
- ORDER BY od_issuer.created_at DESC
2298
- LIMIT 1
2299
- ) as issuer_contract_id
2300
- FROM canton_parties cp
2301
- LEFT JOIN portal p ON cp.portal_id = p.id
2302
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2303
- WHERE
2304
- (loo.type = 'TRANSACTION' AND loo.subtype = 'TX_STOCK_ISSUANCE')
2305
- AND EXISTS (
2306
- SELECT 1 FROM ocf_deployments od_issuer
2307
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2308
- WHERE od_issuer.party_id = cp.party_id
2309
- AND od_issuer.status = 'deployed'
2310
- AND loo_issuer.type = 'ISSUER'
2311
- )
2312
- AND NOT EXISTS (
2313
- SELECT 1 FROM ocf_deployments od
2314
- WHERE od.ocf_object_id = loo.id
2315
- AND od.party_id = cp.party_id
2316
- AND od.status = 'deployed'
2317
- )
2318
- ORDER BY cp.created_at ASC
2319
- `;
2320
- const result = await this.pool.query(query);
2321
- return result.rows;
290
+ return this.ocfDeployments.getPortalsNeedingStockIssuanceDeployments();
2322
291
  }
2323
292
  async getPortalsNeedingWarrantIssuanceDeployments() {
2324
- const query = `
2325
- SELECT DISTINCT
2326
- cp.portal_id,
2327
- cp.party_id,
2328
- cp.provider,
2329
- cp.created_at,
2330
- p.company,
2331
- loo.ocf_data,
2332
- loo.id AS ocf_object_id,
2333
- loo.version AS ocf_version,
2334
- (
2335
- SELECT od_issuer.contract_id
2336
- FROM ocf_deployments od_issuer
2337
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2338
- WHERE od_issuer.party_id = cp.party_id
2339
- AND od_issuer.status = 'deployed'
2340
- AND loo_issuer.type = 'ISSUER'
2341
- ORDER BY od_issuer.created_at DESC
2342
- LIMIT 1
2343
- ) as issuer_contract_id
2344
- FROM canton_parties cp
2345
- LEFT JOIN portal p ON cp.portal_id = p.id
2346
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2347
- WHERE
2348
- (loo.type = 'TRANSACTION' AND loo.subtype = 'TX_WARRANT_ISSUANCE')
2349
- AND EXISTS (
2350
- SELECT 1 FROM ocf_deployments od_issuer
2351
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2352
- WHERE od_issuer.party_id = cp.party_id
2353
- AND od_issuer.status = 'deployed'
2354
- AND loo_issuer.type = 'ISSUER'
2355
- )
2356
- AND NOT EXISTS (
2357
- SELECT 1 FROM ocf_deployments od
2358
- WHERE od.ocf_object_id = loo.id
2359
- AND od.party_id = cp.party_id
2360
- AND od.status = 'deployed'
2361
- )
2362
- ORDER BY cp.created_at ASC
2363
- `;
2364
- const result = await this.pool.query(query);
2365
- return result.rows;
293
+ return this.ocfDeployments.getPortalsNeedingWarrantIssuanceDeployments();
2366
294
  }
2367
295
  async getPortalsNeedingStockPlanPoolAdjustmentDeployments() {
2368
- const query = `
2369
- SELECT DISTINCT
2370
- cp.portal_id,
2371
- cp.party_id,
2372
- cp.provider,
2373
- cp.created_at,
2374
- p.company,
2375
- loo.ocf_data,
2376
- loo.id AS ocf_object_id,
2377
- loo.version AS ocf_version,
2378
- (
2379
- SELECT od_issuer.contract_id
2380
- FROM ocf_deployments od_issuer
2381
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2382
- WHERE od_issuer.party_id = cp.party_id
2383
- AND od_issuer.status = 'deployed'
2384
- AND loo_issuer.type = 'ISSUER'
2385
- ORDER BY od_issuer.created_at DESC
2386
- LIMIT 1
2387
- ) as issuer_contract_id
2388
- FROM canton_parties cp
2389
- LEFT JOIN portal p ON cp.portal_id = p.id
2390
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2391
- WHERE
2392
- (loo.type = 'TRANSACTION' AND loo.subtype = 'TX_STOCK_PLAN_POOL_ADJUSTMENT')
2393
- AND EXISTS (
2394
- SELECT 1 FROM ocf_deployments od_issuer
2395
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2396
- WHERE od_issuer.party_id = cp.party_id
2397
- AND od_issuer.status = 'deployed'
2398
- AND loo_issuer.type = 'ISSUER'
2399
- )
2400
- AND NOT EXISTS (
2401
- SELECT 1 FROM ocf_deployments od
2402
- WHERE od.ocf_object_id = loo.id
2403
- AND od.party_id = cp.party_id
2404
- AND od.status = 'deployed'
2405
- )
2406
- ORDER BY cp.created_at ASC
2407
- `;
2408
- const result = await this.pool.query(query);
2409
- return result.rows;
296
+ return this.ocfDeployments.getPortalsNeedingStockPlanPoolAdjustmentDeployments();
2410
297
  }
2411
298
  async getPortalsNeedingStockClassAuthorizedSharesAdjustmentDeployments() {
2412
- const query = `
2413
- SELECT DISTINCT
2414
- cp.portal_id,
2415
- cp.party_id,
2416
- cp.provider,
2417
- cp.created_at,
2418
- p.company,
2419
- loo.ocf_data,
2420
- loo.id AS ocf_object_id,
2421
- loo.version AS ocf_version,
2422
- (
2423
- SELECT od_issuer.contract_id
2424
- FROM ocf_deployments od_issuer
2425
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2426
- WHERE od_issuer.party_id = cp.party_id
2427
- AND od_issuer.status = 'deployed'
2428
- AND loo_issuer.type = 'ISSUER'
2429
- ORDER BY od_issuer.created_at DESC
2430
- LIMIT 1
2431
- ) as issuer_contract_id
2432
- FROM canton_parties cp
2433
- LEFT JOIN portal p ON cp.portal_id = p.id
2434
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2435
- WHERE
2436
- (loo.type = 'TRANSACTION' AND loo.subtype = 'TX_STOCK_CLASS_AUTHORIZED_SHARES_ADJUSTMENT')
2437
- AND EXISTS (
2438
- SELECT 1 FROM ocf_deployments od_issuer
2439
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2440
- WHERE od_issuer.party_id = cp.party_id
2441
- AND od_issuer.status = 'deployed'
2442
- AND loo_issuer.type = 'ISSUER'
2443
- )
2444
- AND NOT EXISTS (
2445
- SELECT 1 FROM ocf_deployments od
2446
- WHERE od.ocf_object_id = loo.id
2447
- AND od.party_id = cp.party_id
2448
- AND od.status = 'deployed'
2449
- )
2450
- ORDER BY cp.created_at ASC
2451
- `;
2452
- const result = await this.pool.query(query);
2453
- return result.rows;
299
+ return this.ocfDeployments.getPortalsNeedingStockClassAuthorizedSharesAdjustmentDeployments();
2454
300
  }
2455
301
  async getPortalsNeedingStockCancellationDeployments() {
2456
- const query = `
2457
- SELECT DISTINCT
2458
- cp.portal_id,
2459
- cp.party_id,
2460
- cp.provider,
2461
- cp.created_at,
2462
- p.company,
2463
- loo.ocf_data,
2464
- loo.id AS ocf_object_id,
2465
- loo.version AS ocf_version,
2466
- (
2467
- SELECT od_issuer.contract_id
2468
- FROM ocf_deployments od_issuer
2469
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2470
- WHERE od_issuer.party_id = cp.party_id
2471
- AND od_issuer.status = 'deployed'
2472
- AND loo_issuer.type = 'ISSUER'
2473
- ORDER BY od_issuer.created_at DESC
2474
- LIMIT 1
2475
- ) as issuer_contract_id
2476
- FROM canton_parties cp
2477
- LEFT JOIN portal p ON cp.portal_id = p.id
2478
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2479
- WHERE
2480
- (loo.type = 'TRANSACTION' AND loo.subtype = 'TX_STOCK_CANCELLATION')
2481
- AND EXISTS (
2482
- SELECT 1 FROM ocf_deployments od_issuer
2483
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2484
- WHERE od_issuer.party_id = cp.party_id
2485
- AND od_issuer.status = 'deployed'
2486
- AND loo_issuer.type = 'ISSUER'
2487
- )
2488
- AND NOT EXISTS (
2489
- SELECT 1 FROM ocf_deployments od
2490
- WHERE od.ocf_object_id = loo.id
2491
- AND od.party_id = cp.party_id
2492
- AND od.status = 'deployed'
2493
- )
2494
- ORDER BY cp.created_at ASC
2495
- `;
2496
- const result = await this.pool.query(query);
2497
- return result.rows;
302
+ return this.ocfDeployments.getPortalsNeedingStockCancellationDeployments();
2498
303
  }
2499
304
  async getPortalsNeedingIssuerAuthorizedSharesAdjustmentDeployments() {
2500
- const query = `
2501
- SELECT DISTINCT
2502
- cp.portal_id,
2503
- cp.party_id,
2504
- cp.provider,
2505
- cp.created_at,
2506
- p.company,
2507
- loo.ocf_data,
2508
- loo.id AS ocf_object_id,
2509
- loo.version AS ocf_version,
2510
- (
2511
- SELECT od_issuer.contract_id
2512
- FROM ocf_deployments od_issuer
2513
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2514
- WHERE od_issuer.party_id = cp.party_id
2515
- AND od_issuer.status = 'deployed'
2516
- AND loo_issuer.type = 'ISSUER'
2517
- ORDER BY od_issuer.created_at DESC
2518
- LIMIT 1
2519
- ) as issuer_contract_id
2520
- FROM canton_parties cp
2521
- LEFT JOIN portal p ON cp.portal_id = p.id
2522
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2523
- WHERE
2524
- (loo.type = 'TRANSACTION' AND loo.subtype = 'TX_ISSUER_AUTHORIZED_SHARES_ADJUSTMENT')
2525
- AND EXISTS (
2526
- SELECT 1 FROM ocf_deployments od_issuer
2527
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2528
- WHERE od_issuer.party_id = cp.party_id
2529
- AND od_issuer.status = 'deployed'
2530
- AND loo_issuer.type = 'ISSUER'
2531
- )
2532
- AND NOT EXISTS (
2533
- SELECT 1 FROM ocf_deployments od
2534
- WHERE od.ocf_object_id = loo.id
2535
- AND od.party_id = cp.party_id
2536
- AND od.status = 'deployed'
2537
- )
2538
- ORDER BY cp.created_at ASC
2539
- `;
2540
- const result = await this.pool.query(query);
2541
- return result.rows;
305
+ return this.ocfDeployments.getPortalsNeedingIssuerAuthorizedSharesAdjustmentDeployments();
2542
306
  }
2543
307
  async getPortalsNeedingEquityCompensationIssuanceDeployments() {
2544
- const query = `
2545
- SELECT DISTINCT
2546
- cp.portal_id,
2547
- cp.party_id,
2548
- cp.provider,
2549
- cp.created_at,
2550
- p.company,
2551
- loo.ocf_data,
2552
- loo.id AS ocf_object_id,
2553
- loo.version AS ocf_version,
2554
- (
2555
- SELECT od_issuer.contract_id
2556
- FROM ocf_deployments od_issuer
2557
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2558
- WHERE od_issuer.party_id = cp.party_id
2559
- AND od_issuer.status = 'deployed'
2560
- AND loo_issuer.type = 'ISSUER'
2561
- ORDER BY od_issuer.created_at DESC
2562
- LIMIT 1
2563
- ) as issuer_contract_id
2564
- FROM canton_parties cp
2565
- LEFT JOIN portal p ON cp.portal_id = p.id
2566
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2567
- WHERE
2568
- (loo.type = 'TRANSACTION' AND loo.subtype = 'TX_EQUITY_COMPENSATION_ISSUANCE')
2569
- AND EXISTS (
2570
- SELECT 1 FROM ocf_deployments od_issuer
2571
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2572
- WHERE od_issuer.party_id = cp.party_id
2573
- AND od_issuer.status = 'deployed'
2574
- AND loo_issuer.type = 'ISSUER'
2575
- )
2576
- AND NOT EXISTS (
2577
- SELECT 1 FROM ocf_deployments od
2578
- WHERE od.ocf_object_id = loo.id
2579
- AND od.party_id = cp.party_id
2580
- AND od.status = 'deployed'
2581
- )
2582
- ORDER BY cp.created_at ASC
2583
- `;
2584
- const result = await this.pool.query(query);
2585
- return result.rows;
308
+ return this.ocfDeployments.getPortalsNeedingEquityCompensationIssuanceDeployments();
2586
309
  }
2587
310
  async getPortalsNeedingEquityCompensationExerciseDeployments() {
2588
- const query = `
2589
- SELECT DISTINCT
2590
- cp.portal_id,
2591
- cp.party_id,
2592
- cp.provider,
2593
- cp.created_at,
2594
- p.company,
2595
- loo.ocf_data,
2596
- loo.id AS ocf_object_id,
2597
- loo.version AS ocf_version,
2598
- (
2599
- SELECT od_issuer.contract_id
2600
- FROM ocf_deployments od_issuer
2601
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2602
- WHERE od_issuer.party_id = cp.party_id
2603
- AND od_issuer.status = 'deployed'
2604
- AND loo_issuer.type = 'ISSUER'
2605
- ORDER BY od_issuer.created_at DESC
2606
- LIMIT 1
2607
- ) as issuer_contract_id
2608
- FROM canton_parties cp
2609
- LEFT JOIN portal p ON cp.portal_id = p.id
2610
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2611
- WHERE
2612
- (loo.type = 'TRANSACTION' AND loo.subtype = 'TX_EQUITY_COMPENSATION_EXERCISE')
2613
- AND EXISTS (
2614
- SELECT 1 FROM ocf_deployments od_issuer
2615
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2616
- WHERE od_issuer.party_id = cp.party_id
2617
- AND od_issuer.status = 'deployed'
2618
- AND loo_issuer.type = 'ISSUER'
2619
- )
2620
- AND NOT EXISTS (
2621
- SELECT 1 FROM ocf_deployments od
2622
- WHERE od.ocf_object_id = loo.id
2623
- AND od.party_id = cp.party_id
2624
- AND od.status = 'deployed'
2625
- )
2626
- ORDER BY cp.created_at ASC
2627
- `;
2628
- const result = await this.pool.query(query);
2629
- return result.rows;
311
+ return this.ocfDeployments.getPortalsNeedingEquityCompensationExerciseDeployments();
2630
312
  }
2631
313
  async getPortalsNeedingConvertibleIssuanceDeployments() {
2632
- const query = `
2633
- SELECT DISTINCT
2634
- cp.portal_id,
2635
- cp.party_id,
2636
- cp.provider,
2637
- cp.created_at,
2638
- p.company,
2639
- loo.ocf_data,
2640
- loo.id AS ocf_object_id,
2641
- loo.version AS ocf_version,
2642
- (
2643
- SELECT od_issuer.contract_id
2644
- FROM ocf_deployments od_issuer
2645
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2646
- WHERE od_issuer.party_id = cp.party_id
2647
- AND od_issuer.status = 'deployed'
2648
- AND loo_issuer.type = 'ISSUER'
2649
- ORDER BY od_issuer.created_at DESC
2650
- LIMIT 1
2651
- ) as issuer_contract_id
2652
- FROM canton_parties cp
2653
- LEFT JOIN portal p ON cp.portal_id = p.id
2654
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2655
- WHERE
2656
- (loo.type = 'TRANSACTION' AND loo.subtype = 'TX_CONVERTIBLE_ISSUANCE')
2657
- AND EXISTS (
2658
- SELECT 1 FROM ocf_deployments od_issuer
2659
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2660
- WHERE od_issuer.party_id = cp.party_id
2661
- AND od_issuer.status = 'deployed'
2662
- AND loo_issuer.type = 'ISSUER'
2663
- )
2664
- AND NOT EXISTS (
2665
- SELECT 1 FROM ocf_deployments od
2666
- WHERE od.ocf_object_id = loo.id
2667
- AND od.party_id = cp.party_id
2668
- AND od.status = 'deployed'
2669
- )
2670
- ORDER BY cp.created_at ASC
2671
- `;
2672
- const result = await this.pool.query(query);
2673
- return result.rows;
314
+ return this.ocfDeployments.getPortalsNeedingConvertibleIssuanceDeployments();
2674
315
  }
2675
- /**
2676
- * Returns the next party_id that has any pending OCF work (missing deployments or updates), ordered by the associated
2677
- * party creation time.
2678
- */
2679
316
  async getNextPartyIdWithPendingOcfWork() {
2680
- const query = `
2681
- WITH eligible_party AS (
2682
- SELECT DISTINCT cp.party_id, cp.created_at
2683
- FROM canton_parties cp
2684
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2685
- WHERE
2686
- -- Issuer must be deployed for this party
2687
- EXISTS (
2688
- SELECT 1 FROM ocf_deployments od_issuer
2689
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2690
- WHERE od_issuer.party_id = cp.party_id
2691
- AND od_issuer.status = 'deployed'
2692
- AND loo_issuer.type = 'ISSUER'
2693
- )
2694
- AND (
2695
- -- Missing for any object/transaction/record type (excluding ISSUER)
2696
- (loo.type <> 'ISSUER' AND NOT EXISTS (
2697
- SELECT 1 FROM ocf_deployments od
2698
- WHERE od.ocf_object_id = loo.id
2699
- AND od.party_id = cp.party_id
2700
- AND od.status = 'deployed'
2701
- ))
2702
- OR
2703
- -- Updates for any type/subtype except ISSUER
2704
- (
2705
- loo.type <> 'ISSUER'
2706
- AND EXISTS (
2707
- SELECT 1 FROM ocf_deployments od2
2708
- WHERE od2.ocf_object_id = loo.id
2709
- AND od2.party_id = cp.party_id
2710
- AND od2.status = 'deployed'
2711
- AND od2.version < loo.version
2712
- )
2713
- )
2714
- )
2715
- )
2716
- SELECT party_id
2717
- FROM eligible_party
2718
- ORDER BY created_at ASC
2719
- LIMIT 1
2720
- `;
2721
- const result = await this.pool.query(query);
2722
- if (result.rows.length === 0)
2723
- return null;
2724
- return result.rows[0].party_id;
317
+ return this.ocfDeployments.getNextPartyIdWithPendingOcfWork();
318
+ }
319
+ async getPendingOcfItemsForParty(partyId) {
320
+ return this.ocfDeployments.getPendingOcfItemsForParty(partyId);
321
+ }
322
+ // Cap Table Replication
323
+ async getAllIssuersToReplicate() {
324
+ return this.ocfDeployments.getAllIssuersToReplicate();
2725
325
  }
2726
326
  /**
2727
- * Returns all pending OCF items (missing or updates) for a given party in a single query. This unifies
2728
- * objects/transactions and core records, and includes metadata needed by the script.
327
+ * @deprecated Use `getAllIssuersToReplicate` instead.
2729
328
  */
2730
- async getPendingOcfItemsForParty(partyId) {
2731
- const query = `
2732
- (
2733
- -- Missing deployments across all OCF types/subtypes
2734
- SELECT DISTINCT
2735
- cp.portal_id,
2736
- cp.party_id,
2737
- cp.provider,
2738
- p.company,
2739
- loo.id AS ocf_object_id,
2740
- loo.ocf_data,
2741
- loo.version AS ocf_version,
2742
- NULL::int AS latest_ocf_version,
2743
- NULL::int AS deployed_version,
2744
- NULL::text AS current_contract_id,
2745
- loo.type AS ocf_type,
2746
- loo.subtype AS ocf_subtype,
2747
- (
2748
- SELECT od_issuer.contract_id
2749
- FROM ocf_deployments od_issuer
2750
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2751
- WHERE od_issuer.party_id = cp.party_id
2752
- AND od_issuer.status = 'deployed'
2753
- AND loo_issuer.type = 'ISSUER'
2754
- ORDER BY od_issuer.created_at DESC
2755
- LIMIT 1
2756
- ) AS issuer_contract_id,
2757
- cp.created_at
2758
- FROM canton_parties cp
2759
- LEFT JOIN portal p ON cp.portal_id = p.id
2760
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2761
- WHERE cp.party_id = $1
2762
- AND EXISTS (
2763
- SELECT 1 FROM ocf_deployments od_issuer
2764
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2765
- WHERE od_issuer.party_id = cp.party_id
2766
- AND od_issuer.status = 'deployed'
2767
- AND loo_issuer.type = 'ISSUER'
2768
- )
2769
- AND NOT EXISTS (
2770
- SELECT 1 FROM ocf_deployments od
2771
- WHERE od.ocf_object_id = loo.id
2772
- AND od.party_id = cp.party_id
2773
- AND od.status = 'deployed'
2774
- )
2775
- )
2776
- UNION ALL
2777
- (
2778
- -- Updates for core record types
2779
- SELECT DISTINCT
2780
- cp.portal_id,
2781
- cp.party_id,
2782
- cp.provider,
2783
- p.company,
2784
- loo.id AS ocf_object_id,
2785
- loo.ocf_data,
2786
- NULL::int AS ocf_version,
2787
- loo.version AS latest_ocf_version,
2788
- od.version AS deployed_version,
2789
- od.contract_id AS current_contract_id,
2790
- loo.type AS ocf_type,
2791
- loo.subtype AS ocf_subtype,
2792
- (
2793
- SELECT od_issuer.contract_id
2794
- FROM ocf_deployments od_issuer
2795
- JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
2796
- WHERE od_issuer.party_id = cp.party_id
2797
- AND od_issuer.status = 'deployed'
2798
- AND loo_issuer.type = 'ISSUER'
2799
- ORDER BY od_issuer.created_at DESC
2800
- LIMIT 1
2801
- ) AS issuer_contract_id,
2802
- cp.created_at
2803
- FROM canton_parties cp
2804
- LEFT JOIN portal p ON cp.portal_id = p.id
2805
- JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
2806
- JOIN ocf_deployments od ON loo.id = od.ocf_object_id AND cp.party_id = od.party_id
2807
- WHERE cp.party_id = $1
2808
- AND loo.type <> 'ISSUER'
2809
- AND od.status = 'deployed'
2810
- AND od.version < loo.version
2811
- )
2812
- ORDER BY created_at ASC
2813
- `;
2814
- const result = await this.pool.query(query, [partyId]);
2815
- return result.rows;
329
+ async getNextIssuerToReplicate() {
330
+ return this.ocfDeployments.getNextIssuerToReplicate();
2816
331
  }
332
+ async getAllOcfObjectsForIssuer(portalId) {
333
+ return this.ocfDeployments.getAllOcfObjectsForIssuer(portalId);
334
+ }
335
+ // ========================================================================
336
+ // Valuation Report Operations
337
+ // ========================================================================
2817
338
  async getLatestValuationReportByPortalId(portalId) {
2818
- const query = `
2819
- SELECT *
2820
- FROM canton_valuation_reports
2821
- WHERE portal_id = $1
2822
- ORDER BY version DESC
2823
- LIMIT 1
2824
- `;
2825
- const result = await this.pool.query(query, [portalId]);
2826
- if (result.rows.length === 0)
2827
- return null;
2828
- return this.mapValuationReportFromDb(result.rows[0]);
2829
- }
2830
- /** Get the latest valuation report row for each distinct company_id */
339
+ return this.valuationReports.getLatestValuationReportByPortalId(portalId);
340
+ }
2831
341
  async getLatestValuationReportsGroupedByCompany() {
2832
- const query = `
2833
- SELECT DISTINCT ON (company_id)
2834
- id, portal_id, company_id, version, contract_id, company_valuation,
2835
- tx_update_id, last_valuation_until, created_at, updated_at
2836
- FROM canton_valuation_reports
2837
- ORDER BY company_id, version DESC
2838
- `;
2839
- const result = await this.pool.query(query);
2840
- return result.rows.map(row => this.mapValuationReportFromDb(row));
342
+ return this.valuationReports.getLatestValuationReportsGroupedByCompany();
2841
343
  }
2842
344
  async insertValuationReport(report) {
2843
- const query = `
2844
- INSERT INTO canton_valuation_reports (
2845
- portal_id, company_id, version, contract_id, company_valuation, tx_update_id, last_valuation_until
2846
- ) VALUES ($1, $2, $3, $4, $5, $6, $7)
2847
- RETURNING *
2848
- `;
2849
- const values = [
2850
- report.portal_id,
2851
- report.company_id,
2852
- report.version,
2853
- report.contract_id,
2854
- report.company_valuation,
2855
- report.tx_update_id,
2856
- report.last_valuation_until,
2857
- ];
2858
- const result = await this.pool.query(query, values);
2859
- return this.mapValuationReportFromDb(result.rows[0]);
2860
- }
2861
- // Helper methods for mapping database results
2862
- mapTransferFromDb(row) {
2863
- return {
2864
- id: row.id,
2865
- api_tracking_id: row.api_tracking_id,
2866
- api_expires_at: row.api_expires_at,
2867
- transfer_sender_party_id: row.transfer_sender_party_id,
2868
- transfer_receiver_party_id: row.transfer_receiver_party_id,
2869
- transfer_amount: parseFloat(row.transfer_amount),
2870
- transfer_description: row.transfer_description,
2871
- transfer_burned_amount: parseFloat(row.transfer_burned_amount),
2872
- receiver_holding_cids: row.receiver_holding_cids,
2873
- sender_change_cids: row.sender_change_cids,
2874
- tx_update_id: row.tx_update_id,
2875
- tx_record_time: row.tx_record_time,
2876
- status: row.status,
2877
- app_reward_coupon_id: row.app_reward_coupon_id,
2878
- created_at: row.created_at,
2879
- updated_at: row.updated_at,
2880
- };
2881
- }
2882
- mapRewardCouponFromDb(row) {
2883
- return {
2884
- id: row.id,
2885
- status: row.status,
2886
- tx_update_id: row.tx_update_id,
2887
- tx_record_time: row.tx_record_time,
2888
- contract_id: row.contract_id,
2889
- template_id: row.template_id,
2890
- package_name: row.package_name,
2891
- dso_party_id: row.dso_party_id,
2892
- provider_party_id: row.provider_party_id,
2893
- featured: row.featured,
2894
- round_number: row.round_number,
2895
- beneficiary_party_id: row.beneficiary_party_id,
2896
- coupon_amount: parseFloat(row.coupon_amount),
2897
- tx_archive_update_id: row.tx_archive_update_id,
2898
- tx_archive_record_time: row.tx_archive_record_time,
2899
- app_reward_amount: row.app_reward_amount
2900
- ? parseFloat(row.app_reward_amount)
2901
- : null,
2902
- created_at: row.created_at,
2903
- updated_at: row.updated_at,
2904
- };
2905
- }
2906
- mapAppMarkerFromDb(row) {
2907
- return {
2908
- id: row.id,
2909
- status: row.status,
2910
- contract_id: row.contract_id,
2911
- provider_party_id: row.provider_party_id,
2912
- beneficiary_party_id: row.beneficiary_party_id,
2913
- tx_record_time: row.tx_record_time,
2914
- weight: parseFloat(row.weight),
2915
- created_at: row.created_at,
2916
- updated_at: row.updated_at,
2917
- };
2918
- }
2919
- mapRedemptionFromDb(row) {
2920
- return {
2921
- id: row.id,
2922
- transfer_id: row.transfer_id,
2923
- app_reward_coupon_ids: row.app_reward_coupon_ids,
2924
- tx_update_id: row.tx_update_id,
2925
- tx_record_time: row.tx_record_time,
2926
- tx_synchronizer_id: row.tx_synchronizer_id,
2927
- tx_effective_at: row.tx_effective_at,
2928
- total_app_rewards: parseFloat(row.total_app_rewards),
2929
- created_at: row.created_at,
2930
- updated_at: row.updated_at,
2931
- };
2932
- }
2933
- mapRewardCouponWithTransferFromDb(row) {
2934
- const coupon = {
2935
- id: row.coupon_id,
2936
- status: row.coupon_status,
2937
- tx_update_id: row.coupon_tx_update_id,
2938
- tx_record_time: row.coupon_tx_record_time,
2939
- contract_id: row.coupon_contract_id,
2940
- template_id: row.coupon_template_id,
2941
- package_name: row.coupon_package_name,
2942
- dso_party_id: row.coupon_dso_party_id,
2943
- provider_party_id: row.coupon_provider_party_id,
2944
- featured: row.coupon_featured,
2945
- round_number: row.coupon_round_number,
2946
- beneficiary_party_id: row.coupon_beneficiary_party_id,
2947
- coupon_amount: parseFloat(row.coupon_coupon_amount),
2948
- tx_archive_update_id: row.coupon_tx_archive_update_id,
2949
- tx_archive_record_time: row.coupon_tx_archive_record_time,
2950
- app_reward_amount: row.coupon_app_reward_amount
2951
- ? parseFloat(row.coupon_app_reward_amount)
2952
- : null,
2953
- created_at: row.coupon_created_at,
2954
- updated_at: row.coupon_updated_at,
2955
- };
2956
- const transfer = {
2957
- id: row.transfer_id,
2958
- api_tracking_id: row.transfer_api_tracking_id,
2959
- api_expires_at: row.transfer_api_expires_at,
2960
- transfer_sender_party_id: row.transfer_transfer_sender_party_id,
2961
- transfer_receiver_party_id: row.transfer_transfer_receiver_party_id,
2962
- transfer_amount: parseFloat(row.transfer_transfer_amount),
2963
- transfer_description: row.transfer_transfer_description,
2964
- transfer_burned_amount: parseFloat(row.transfer_transfer_burned_amount),
2965
- receiver_holding_cids: row.transfer_receiver_holding_cids,
2966
- sender_change_cids: row.transfer_sender_change_cids,
2967
- tx_update_id: row.transfer_tx_update_id,
2968
- tx_record_time: row.transfer_tx_record_time,
2969
- status: row.transfer_status,
2970
- app_reward_coupon_id: row.transfer_app_reward_coupon_id,
2971
- created_at: row.transfer_created_at,
2972
- updated_at: row.transfer_updated_at,
2973
- };
2974
- const redemption = row.redemption_id
2975
- ? {
2976
- id: row.redemption_id,
2977
- transfer_id: row.transfer_id,
2978
- app_reward_coupon_ids: [], // This would need to be fetched separately if needed
2979
- tx_update_id: row.redemption_tx_update_id,
2980
- tx_record_time: row.redemption_tx_record_time,
2981
- tx_synchronizer_id: row.redemption_tx_synchronizer_id,
2982
- tx_effective_at: row.redemption_tx_effective_at,
2983
- total_app_rewards: parseFloat(row.total_app_rewards),
2984
- created_at: row.redemption_created_at,
2985
- updated_at: row.redemption_updated_at,
2986
- }
2987
- : null;
2988
- return {
2989
- ...coupon,
2990
- transfer,
2991
- redemption,
2992
- };
2993
- }
2994
- mapPartyFromDb(row) {
2995
- return {
2996
- id: row.id,
2997
- party_id: row.party_id,
2998
- portal_id: row.portal_id,
2999
- provider: row.provider,
3000
- last_payment_until: row.last_payment_until,
3001
- is_active_customer_since: row.is_active_customer_since,
3002
- created_at: row.created_at,
3003
- updated_at: row.updated_at,
3004
- portal: row.company
3005
- ? {
3006
- id: row.portal_id,
3007
- company: row.company,
3008
- }
3009
- : null,
3010
- };
3011
- }
3012
- toSnakeCase(str) {
3013
- return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
3014
- }
3015
- /**
3016
- * Get historical issuance rates for specific rounds from the scan_acs_store table This queries ClosedMiningRound
3017
- * contracts to get issuance rates for old rounds
3018
- *
3019
- * @param roundNumbers - Array of round numbers to get issuance rates for
3020
- * @returns Array of issuance rates for the specified rounds
3021
- */
3022
- async getHistoricalIssuanceRates(roundNumbers) {
3023
- if (roundNumbers.length === 0) {
3024
- return [];
3025
- }
3026
- // Query the scan_acs_store table for ClosedMiningRound contracts
3027
- // The template_id_qualified_name should match the ClosedMiningRound template
3028
- const query = `
3029
- SELECT
3030
- round,
3031
- create_arguments
3032
- FROM scan_acs_store
3033
- WHERE template_id_qualified_name LIKE '%:Splice.Round:ClosedMiningRound'
3034
- AND round = ANY($1)
3035
- AND create_arguments IS NOT NULL
3036
- ORDER BY round ASC
3037
- `;
3038
- try {
3039
- const result = await this.pool.query(query, [roundNumbers]);
3040
- return result.rows.map(row => {
3041
- const payload = row.create_arguments;
3042
- const roundNumber = parseInt(row.round, 10);
3043
- // Extract issuance rates from the contract payload
3044
- // The payload is stored as JSON and contains the issuance rate fields
3045
- const issuancePerFeaturedAppRewardCoupon = parseFloat(payload.issuancePerFeaturedAppRewardCoupon ?? '0');
3046
- const issuancePerUnfeaturedAppRewardCoupon = parseFloat(payload.issuancePerUnfeaturedAppRewardCoupon ?? '0');
3047
- return {
3048
- roundNumber,
3049
- issuancePerFeaturedAppRewardCoupon,
3050
- issuancePerUnfeaturedAppRewardCoupon,
3051
- };
3052
- });
3053
- }
3054
- catch (error) {
3055
- console.error('Error querying historical issuance rates:', error);
3056
- throw new Error(`Failed to get historical issuance rates: ${error instanceof Error ? error.message : 'Unknown error'}`);
3057
- }
3058
- }
3059
- mapValuationReportFromDb(row) {
3060
- return {
3061
- id: row.id,
3062
- portal_id: row.portal_id,
3063
- company_id: row.company_id,
3064
- version: row.version,
3065
- contract_id: row.contract_id,
3066
- company_valuation: Number(row.company_valuation),
3067
- tx_update_id: row.tx_update_id,
3068
- last_valuation_until: row.last_valuation_until,
3069
- created_at: row.created_at,
3070
- updated_at: row.updated_at,
3071
- };
345
+ return this.valuationReports.insertValuationReport(report);
3072
346
  }
3073
- /**
3074
- * Debug method to execute custom queries for investigation
3075
- *
3076
- * @param query - SQL query to execute
3077
- * @param params - Query parameters
3078
- * @returns Query result
3079
- */
347
+ // ========================================================================
348
+ // Debug Operations
349
+ // ========================================================================
3080
350
  async debugQuery(query, params = []) {
3081
351
  try {
3082
352
  const result = await this.pool.query(query, params);
@@ -3087,62 +357,6 @@ class FairmintDbClient {
3087
357
  throw new Error(`Failed to execute debug query: ${error instanceof Error ? error.message : 'Unknown error'}`);
3088
358
  }
3089
359
  }
3090
- // ========================================================================
3091
- // Cap Table Replication Methods
3092
- // ========================================================================
3093
- /**
3094
- * Get the next issuer that needs cap table replication.
3095
- *
3096
- * Returns issuers that have OCF objects and a canton party configured.
3097
- * Ordered by portal_id for deterministic processing order.
3098
- *
3099
- * @returns Issuer info with party_id and portal_id, or null if none need sync
3100
- */
3101
- async getNextIssuerToReplicate() {
3102
- const query = `
3103
- SELECT DISTINCT
3104
- cp.party_id,
3105
- cp.portal_id,
3106
- p.company
3107
- FROM canton_parties cp
3108
- LEFT JOIN portal p ON cp.portal_id = p.id
3109
- WHERE EXISTS (
3110
- SELECT 1 FROM latest_ocf_objects loo
3111
- WHERE loo.portal_id = cp.portal_id
3112
- AND loo.type = 'ISSUER'
3113
- )
3114
- ORDER BY cp.portal_id
3115
- LIMIT 1
3116
- `;
3117
- const result = await this.pool.query(query);
3118
- if (result.rows.length === 0) {
3119
- return null;
3120
- }
3121
- return result.rows[0];
3122
- }
3123
- /**
3124
- * Get all OCF objects for an issuer.
3125
- *
3126
- * Returns all OCF objects from the latest_ocf_objects view for a given portal.
3127
- * Used by the replication script to determine desired state.
3128
- *
3129
- * @param portalId - Portal ID of the issuer
3130
- * @returns Array of OCF objects with type, subtype, and data
3131
- */
3132
- async getAllOcfObjectsForIssuer(portalId) {
3133
- const query = `
3134
- SELECT
3135
- loo.id as ocf_id,
3136
- loo.type,
3137
- loo.subtype,
3138
- loo.ocf_data as data
3139
- FROM latest_ocf_objects loo
3140
- WHERE loo.portal_id = $1
3141
- ORDER BY loo.type, loo.subtype, loo.id
3142
- `;
3143
- const result = await this.pool.query(query, [portalId]);
3144
- return result.rows;
3145
- }
3146
360
  }
3147
361
  exports.FairmintDbClient = FairmintDbClient;
3148
362
  //# sourceMappingURL=fairmintDbClient.js.map