@fairmint/canton-fairmint-sdk 0.0.2
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.
- package/README.md +64 -0
- package/dist/clients/index.d.ts +5 -0
- package/dist/clients/index.d.ts.map +1 -0
- package/dist/clients/index.js +21 -0
- package/dist/clients/index.js.map +1 -0
- package/dist/clients/json-api/index.d.ts +2 -0
- package/dist/clients/json-api/index.d.ts.map +1 -0
- package/dist/clients/json-api/index.js +18 -0
- package/dist/clients/json-api/index.js.map +1 -0
- package/dist/clients/json-api/sdkHelper.d.ts +27 -0
- package/dist/clients/json-api/sdkHelper.d.ts.map +1 -0
- package/dist/clients/json-api/sdkHelper.js +73 -0
- package/dist/clients/json-api/sdkHelper.js.map +1 -0
- package/dist/clients/postgres-db-api/cantonDbClient.d.ts +335 -0
- package/dist/clients/postgres-db-api/cantonDbClient.d.ts.map +1 -0
- package/dist/clients/postgres-db-api/cantonDbClient.js +2703 -0
- package/dist/clients/postgres-db-api/cantonDbClient.js.map +1 -0
- package/dist/clients/postgres-db-api/fairmintDbClient.d.ts +241 -0
- package/dist/clients/postgres-db-api/fairmintDbClient.d.ts.map +1 -0
- package/dist/clients/postgres-db-api/fairmintDbClient.js +3078 -0
- package/dist/clients/postgres-db-api/fairmintDbClient.js.map +1 -0
- package/dist/clients/postgres-db-api/index.d.ts +5 -0
- package/dist/clients/postgres-db-api/index.d.ts.map +1 -0
- package/dist/clients/postgres-db-api/index.js +25 -0
- package/dist/clients/postgres-db-api/index.js.map +1 -0
- package/dist/clients/postgres-db-api/postgresDbClient.d.ts +118 -0
- package/dist/clients/postgres-db-api/postgresDbClient.d.ts.map +1 -0
- package/dist/clients/postgres-db-api/postgresDbClient.js +212 -0
- package/dist/clients/postgres-db-api/postgresDbClient.js.map +1 -0
- package/dist/clients/postgres-db-api/types.d.ts +330 -0
- package/dist/clients/postgres-db-api/types.d.ts.map +1 -0
- package/dist/clients/postgres-db-api/types.js +30 -0
- package/dist/clients/postgres-db-api/types.js.map +1 -0
- package/dist/clients/shared/config.d.ts +19 -0
- package/dist/clients/shared/config.d.ts.map +1 -0
- package/dist/clients/shared/config.js +208 -0
- package/dist/clients/shared/config.js.map +1 -0
- package/dist/clients/shared/index.d.ts +3 -0
- package/dist/clients/shared/index.d.ts.map +1 -0
- package/dist/clients/shared/index.js +19 -0
- package/dist/clients/shared/index.js.map +1 -0
- package/dist/clients/shared/types.d.ts +29 -0
- package/dist/clients/shared/types.d.ts.map +1 -0
- package/dist/clients/shared/types.js +3 -0
- package/dist/clients/shared/types.js.map +1 -0
- package/dist/clients/validator-api/index.d.ts +4 -0
- package/dist/clients/validator-api/index.d.ts.map +1 -0
- package/dist/clients/validator-api/index.js +61 -0
- package/dist/clients/validator-api/index.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,3078 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FairmintDbClient = void 0;
|
|
4
|
+
const pg_1 = require("pg");
|
|
5
|
+
const config_1 = require("../shared/config");
|
|
6
|
+
const types_1 = require("./types");
|
|
7
|
+
class FairmintDbClient {
|
|
8
|
+
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
|
+
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);
|
|
34
|
+
}
|
|
35
|
+
getNetwork() {
|
|
36
|
+
return this.network;
|
|
37
|
+
}
|
|
38
|
+
async connect() {
|
|
39
|
+
return this.pool.connect();
|
|
40
|
+
}
|
|
41
|
+
async close() {
|
|
42
|
+
await this.pool.end();
|
|
43
|
+
}
|
|
44
|
+
// Canton Transfers CRUD operations
|
|
45
|
+
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]);
|
|
73
|
+
}
|
|
74
|
+
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;
|
|
80
|
+
}
|
|
81
|
+
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;
|
|
87
|
+
}
|
|
88
|
+
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));
|
|
96
|
+
}
|
|
97
|
+
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
|
|
125
|
+
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]);
|
|
149
|
+
}
|
|
150
|
+
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 };
|
|
185
|
+
}
|
|
186
|
+
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]);
|
|
212
|
+
}
|
|
213
|
+
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;
|
|
219
|
+
}
|
|
220
|
+
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;
|
|
226
|
+
}
|
|
227
|
+
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));
|
|
240
|
+
}
|
|
241
|
+
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));
|
|
255
|
+
}
|
|
256
|
+
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;
|
|
263
|
+
}
|
|
264
|
+
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));
|
|
273
|
+
}
|
|
274
|
+
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));
|
|
290
|
+
}
|
|
291
|
+
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;
|
|
317
|
+
}
|
|
318
|
+
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));
|
|
349
|
+
}
|
|
350
|
+
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));
|
|
419
|
+
}
|
|
420
|
+
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;
|
|
491
|
+
}
|
|
492
|
+
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 = 'good') {
|
|
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
|
+
const statusCondition = couponStatus === 'good'
|
|
683
|
+
? ` AND status IN ('created', 'archived')`
|
|
684
|
+
: ` AND status = 'expired'`;
|
|
685
|
+
const query = `
|
|
686
|
+
WITH time_series AS (
|
|
687
|
+
SELECT generate_series(
|
|
688
|
+
date_trunc('${interval}', ${timeSeriesStart}),
|
|
689
|
+
date_trunc('${interval}', ${timeSeriesEnd}),
|
|
690
|
+
INTERVAL '1 ${interval}'
|
|
691
|
+
) AS timestamp
|
|
692
|
+
),
|
|
693
|
+
coupon_data AS (
|
|
694
|
+
SELECT
|
|
695
|
+
date_trunc('${interval}', tx_record_time) AS timestamp,
|
|
696
|
+
COUNT(*) as count,
|
|
697
|
+
COALESCE(SUM(app_reward_amount), 0) as amount,
|
|
698
|
+
COALESCE(SUM(coupon_amount), 0) as coupon_amount
|
|
699
|
+
FROM canton_app_reward_coupons
|
|
700
|
+
WHERE ${timeFilter}${partyCondition}${statusCondition}
|
|
701
|
+
GROUP BY date_trunc('${interval}', tx_record_time)
|
|
702
|
+
)
|
|
703
|
+
SELECT
|
|
704
|
+
ts.timestamp::text,
|
|
705
|
+
COALESCE(cd.count, 0) as count,
|
|
706
|
+
COALESCE(cd.amount, 0) as amount,
|
|
707
|
+
COALESCE(cd.coupon_amount, 0) as coupon_amount
|
|
708
|
+
FROM time_series ts
|
|
709
|
+
LEFT JOIN coupon_data cd ON ts.timestamp = cd.timestamp
|
|
710
|
+
ORDER BY ts.timestamp ASC
|
|
711
|
+
`;
|
|
712
|
+
const result = await this.pool.query(query);
|
|
713
|
+
return result.rows.map(row => ({
|
|
714
|
+
timestamp: row.timestamp,
|
|
715
|
+
count: parseInt(row.count, 10),
|
|
716
|
+
amount: parseFloat(row.amount),
|
|
717
|
+
couponAmount: parseFloat(row.coupon_amount),
|
|
718
|
+
}));
|
|
719
|
+
}
|
|
720
|
+
async getRewardCouponRoundSeriesData(timeRange, _metric = 'count', timePeriod, customStartDate, partyFilter, couponStatus = 'good') {
|
|
721
|
+
let timeRangeInterval;
|
|
722
|
+
let timeFilter;
|
|
723
|
+
switch (timeRange) {
|
|
724
|
+
case '15m':
|
|
725
|
+
timeRangeInterval = '15 minutes';
|
|
726
|
+
timeFilter = "tx_record_time >= NOW() - INTERVAL '15 minutes'";
|
|
727
|
+
break;
|
|
728
|
+
case '1h':
|
|
729
|
+
timeRangeInterval = '1 hour';
|
|
730
|
+
timeFilter = "tx_record_time >= NOW() - INTERVAL '1 hour'";
|
|
731
|
+
break;
|
|
732
|
+
case '6h':
|
|
733
|
+
timeRangeInterval = '6 hours';
|
|
734
|
+
timeFilter = "tx_record_time >= NOW() - INTERVAL '6 hours'";
|
|
735
|
+
break;
|
|
736
|
+
case '1d':
|
|
737
|
+
timeRangeInterval = '1 day';
|
|
738
|
+
timeFilter = "tx_record_time >= NOW() - INTERVAL '1 day'";
|
|
739
|
+
break;
|
|
740
|
+
case '7d':
|
|
741
|
+
timeRangeInterval = '7 days';
|
|
742
|
+
timeFilter = "tx_record_time >= NOW() - INTERVAL '7 days'";
|
|
743
|
+
break;
|
|
744
|
+
case '30d':
|
|
745
|
+
timeRangeInterval = '30 days';
|
|
746
|
+
timeFilter = "tx_record_time >= NOW() - INTERVAL '30 days'";
|
|
747
|
+
break;
|
|
748
|
+
case 'last-month':
|
|
749
|
+
timeRangeInterval = '1 month';
|
|
750
|
+
timeFilter =
|
|
751
|
+
"tx_record_time >= date_trunc('month', current_date - interval '1 month') AND tx_record_time < date_trunc('month', current_date)";
|
|
752
|
+
break;
|
|
753
|
+
case 'all':
|
|
754
|
+
default:
|
|
755
|
+
timeRangeInterval = '1 day';
|
|
756
|
+
timeFilter = "tx_record_time >= NOW() - INTERVAL '1 day'";
|
|
757
|
+
}
|
|
758
|
+
if (timePeriod === 'custom-start' && customStartDate) {
|
|
759
|
+
timeFilter = `tx_record_time >= '${customStartDate}:00' AND tx_record_time < ('${customStartDate}:00'::timestamp + INTERVAL '${timeRangeInterval}')`;
|
|
760
|
+
}
|
|
761
|
+
// Add party filter if provided
|
|
762
|
+
const partyCondition = partyFilter
|
|
763
|
+
? ` AND beneficiary_party_id = '${partyFilter}'`
|
|
764
|
+
: '';
|
|
765
|
+
// Add status filter based on couponStatus
|
|
766
|
+
const statusCondition = couponStatus === 'good'
|
|
767
|
+
? ` AND status IN ('created', 'archived')`
|
|
768
|
+
: ` AND status = 'expired'`;
|
|
769
|
+
const query = `
|
|
770
|
+
WITH time_filtered AS (
|
|
771
|
+
SELECT round_number
|
|
772
|
+
FROM canton_app_reward_coupons
|
|
773
|
+
WHERE ${timeFilter}${partyCondition}${statusCondition}
|
|
774
|
+
),
|
|
775
|
+
round_bounds AS (
|
|
776
|
+
SELECT
|
|
777
|
+
COALESCE(MIN(round_number), 0) AS min_round,
|
|
778
|
+
COALESCE(MAX(round_number), 0) AS max_round
|
|
779
|
+
FROM time_filtered
|
|
780
|
+
),
|
|
781
|
+
rounds AS (
|
|
782
|
+
SELECT generate_series(
|
|
783
|
+
GREATEST((SELECT min_round FROM round_bounds), 0),
|
|
784
|
+
GREATEST((SELECT max_round FROM round_bounds), 0)
|
|
785
|
+
) AS round
|
|
786
|
+
WHERE (SELECT max_round FROM round_bounds) > 0
|
|
787
|
+
),
|
|
788
|
+
coupon_data AS (
|
|
789
|
+
SELECT
|
|
790
|
+
round_number AS round,
|
|
791
|
+
MIN(tx_record_time) AS timestamp,
|
|
792
|
+
COUNT(*) AS count,
|
|
793
|
+
COALESCE(SUM(app_reward_amount), 0) AS amount,
|
|
794
|
+
COALESCE(SUM(coupon_amount), 0) AS coupon_amount
|
|
795
|
+
FROM canton_app_reward_coupons
|
|
796
|
+
WHERE round_number IN (SELECT round FROM rounds)${partyCondition}${statusCondition}
|
|
797
|
+
GROUP BY round_number
|
|
798
|
+
)
|
|
799
|
+
SELECT
|
|
800
|
+
r.round,
|
|
801
|
+
cd.timestamp,
|
|
802
|
+
COALESCE(cd.count, 0) AS count,
|
|
803
|
+
COALESCE(cd.amount, 0) AS amount,
|
|
804
|
+
COALESCE(cd.coupon_amount, 0) AS coupon_amount
|
|
805
|
+
FROM rounds r
|
|
806
|
+
LEFT JOIN coupon_data cd ON cd.round = r.round
|
|
807
|
+
ORDER BY r.round ASC
|
|
808
|
+
`;
|
|
809
|
+
const result = await this.pool.query(query);
|
|
810
|
+
return result.rows.map(row => ({
|
|
811
|
+
timestamp: row.timestamp
|
|
812
|
+
? new Date(row.timestamp).toISOString()
|
|
813
|
+
: new Date().toISOString(),
|
|
814
|
+
count: parseInt(row.count, 10),
|
|
815
|
+
amount: parseFloat(row.amount),
|
|
816
|
+
couponAmount: parseFloat(row.coupon_amount),
|
|
817
|
+
round: typeof row.round === 'number' ? row.round : parseInt(row.round, 10),
|
|
818
|
+
}));
|
|
819
|
+
}
|
|
820
|
+
async getLatestCouponAmounts() {
|
|
821
|
+
const query = `
|
|
822
|
+
WITH latest AS (
|
|
823
|
+
SELECT DISTINCT ON (featured)
|
|
824
|
+
featured,
|
|
825
|
+
coupon_amount
|
|
826
|
+
FROM canton_app_reward_coupons
|
|
827
|
+
WHERE coupon_amount IS NOT NULL
|
|
828
|
+
ORDER BY featured, tx_record_time DESC
|
|
829
|
+
)
|
|
830
|
+
SELECT
|
|
831
|
+
MAX(coupon_amount) FILTER (WHERE featured) AS featured_amount,
|
|
832
|
+
MAX(coupon_amount) FILTER (WHERE NOT featured) AS unfeatured_amount
|
|
833
|
+
FROM latest
|
|
834
|
+
`;
|
|
835
|
+
const result = await this.pool.query(query);
|
|
836
|
+
const row = result.rows[0] ?? {};
|
|
837
|
+
return {
|
|
838
|
+
featured: row.featured_amount !== null && row.featured_amount !== undefined
|
|
839
|
+
? parseFloat(row.featured_amount)
|
|
840
|
+
: null,
|
|
841
|
+
unfeatured: row.unfeatured_amount !== null && row.unfeatured_amount !== undefined
|
|
842
|
+
? parseFloat(row.unfeatured_amount)
|
|
843
|
+
: null,
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
async getLastAppRewardCouponTimestamp(partyFilter) {
|
|
847
|
+
const partyCondition = partyFilter
|
|
848
|
+
? ` AND beneficiary_party_id = '${partyFilter}'`
|
|
849
|
+
: '';
|
|
850
|
+
const query = `
|
|
851
|
+
SELECT MAX(tx_record_time) AS last_timestamp
|
|
852
|
+
FROM canton_app_reward_coupons
|
|
853
|
+
WHERE status IN ('created', 'archived')
|
|
854
|
+
AND app_reward_amount IS NOT NULL${partyCondition}
|
|
855
|
+
`;
|
|
856
|
+
const result = await this.pool.query(query);
|
|
857
|
+
const timestamp = result.rows[0]?.last_timestamp;
|
|
858
|
+
return timestamp ? new Date(timestamp).toISOString() : null;
|
|
859
|
+
}
|
|
860
|
+
async getLifetimeAppRewards() {
|
|
861
|
+
const query = `
|
|
862
|
+
SELECT COALESCE(SUM(app_reward_amount), 0) AS total
|
|
863
|
+
FROM canton_app_reward_coupons
|
|
864
|
+
WHERE status = 'archived'
|
|
865
|
+
`;
|
|
866
|
+
const result = await this.pool.query(query);
|
|
867
|
+
return Number(result.rows[0]?.total ?? 0);
|
|
868
|
+
}
|
|
869
|
+
async getUniqueCouponBeneficiaryParties() {
|
|
870
|
+
const query = `
|
|
871
|
+
SELECT DISTINCT
|
|
872
|
+
beneficiary_party_id as party_id,
|
|
873
|
+
beneficiary_party_id as display_name
|
|
874
|
+
FROM canton_app_reward_coupons
|
|
875
|
+
WHERE beneficiary_party_id IS NOT NULL
|
|
876
|
+
ORDER BY beneficiary_party_id ASC
|
|
877
|
+
`;
|
|
878
|
+
const result = await this.pool.query(query);
|
|
879
|
+
return result.rows;
|
|
880
|
+
}
|
|
881
|
+
async getAppMarkerTimeSeriesData(timeRange, _metric = 'count', timePeriod, customStartDate, partyFilter, _statusFilter) {
|
|
882
|
+
let interval;
|
|
883
|
+
let timeFilter;
|
|
884
|
+
let timeSeriesStart;
|
|
885
|
+
let timeSeriesEnd = 'NOW()';
|
|
886
|
+
let timeRangeInterval;
|
|
887
|
+
switch (timeRange) {
|
|
888
|
+
case '15m':
|
|
889
|
+
interval = 'minute';
|
|
890
|
+
timeFilter = "tx_record_time >= NOW() - INTERVAL '15 minutes'";
|
|
891
|
+
timeRangeInterval = '15 minutes';
|
|
892
|
+
timeSeriesStart = "NOW() - INTERVAL '15 minutes'";
|
|
893
|
+
break;
|
|
894
|
+
case '1h':
|
|
895
|
+
interval = 'minute';
|
|
896
|
+
timeFilter = "tx_record_time >= NOW() - INTERVAL '1 hour'";
|
|
897
|
+
timeRangeInterval = '1 hour';
|
|
898
|
+
timeSeriesStart = "NOW() - INTERVAL '1 hour'";
|
|
899
|
+
break;
|
|
900
|
+
case '6h':
|
|
901
|
+
interval = 'minute';
|
|
902
|
+
timeFilter = "tx_record_time >= NOW() - INTERVAL '6 hours'";
|
|
903
|
+
timeRangeInterval = '6 hours';
|
|
904
|
+
timeSeriesStart = "NOW() - INTERVAL '6 hours'";
|
|
905
|
+
break;
|
|
906
|
+
case '1d':
|
|
907
|
+
interval = 'hour';
|
|
908
|
+
timeFilter = "tx_record_time >= NOW() - INTERVAL '1 day'";
|
|
909
|
+
timeRangeInterval = '1 day';
|
|
910
|
+
timeSeriesStart = "NOW() - INTERVAL '1 day'";
|
|
911
|
+
break;
|
|
912
|
+
case '7d':
|
|
913
|
+
interval = 'hour';
|
|
914
|
+
timeFilter = "tx_record_time >= NOW() - INTERVAL '7 days'";
|
|
915
|
+
timeRangeInterval = '7 days';
|
|
916
|
+
timeSeriesStart = "NOW() - INTERVAL '7 days'";
|
|
917
|
+
break;
|
|
918
|
+
case '30d':
|
|
919
|
+
interval = 'day';
|
|
920
|
+
timeFilter = "tx_record_time >= NOW() - INTERVAL '30 days'";
|
|
921
|
+
timeRangeInterval = '30 days';
|
|
922
|
+
timeSeriesStart = "NOW() - INTERVAL '30 days'";
|
|
923
|
+
break;
|
|
924
|
+
case 'last-month':
|
|
925
|
+
interval = 'day';
|
|
926
|
+
timeFilter =
|
|
927
|
+
"tx_record_time >= date_trunc('month', current_date - interval '1 month') AND tx_record_time < date_trunc('month', current_date)";
|
|
928
|
+
timeRangeInterval = '1 month';
|
|
929
|
+
timeSeriesStart =
|
|
930
|
+
"date_trunc('month', current_date - interval '1 month')";
|
|
931
|
+
timeSeriesEnd = "date_trunc('month', current_date)";
|
|
932
|
+
break;
|
|
933
|
+
case 'all':
|
|
934
|
+
interval = 'day';
|
|
935
|
+
timeFilter = '1=1';
|
|
936
|
+
timeRangeInterval = '100 years';
|
|
937
|
+
timeSeriesStart = "'2023-01-01'::timestamp";
|
|
938
|
+
break;
|
|
939
|
+
default:
|
|
940
|
+
interval = 'hour';
|
|
941
|
+
timeFilter = "tx_record_time >= NOW() - INTERVAL '1 day'";
|
|
942
|
+
timeRangeInterval = '1 day';
|
|
943
|
+
timeSeriesStart = "NOW() - INTERVAL '1 day'";
|
|
944
|
+
}
|
|
945
|
+
// If custom start date is provided, override the timeFilter and time series start/end
|
|
946
|
+
if (timePeriod === 'custom-start' && customStartDate) {
|
|
947
|
+
timeFilter = `tx_record_time >= '${customStartDate}:00' AND tx_record_time < ('${customStartDate}:00'::timestamp + INTERVAL '${timeRangeInterval}')`;
|
|
948
|
+
timeSeriesStart = `'${customStartDate}:00'::timestamp`;
|
|
949
|
+
timeSeriesEnd = `('${customStartDate}:00'::timestamp + INTERVAL '${timeRangeInterval}')`;
|
|
950
|
+
}
|
|
951
|
+
// Add party filter if provided
|
|
952
|
+
const partyCondition = partyFilter
|
|
953
|
+
? ` AND beneficiary_party_id = '${partyFilter}'`
|
|
954
|
+
: '';
|
|
955
|
+
const query = `
|
|
956
|
+
WITH time_series AS (
|
|
957
|
+
SELECT generate_series(
|
|
958
|
+
date_trunc('${interval}', ${timeSeriesStart}),
|
|
959
|
+
date_trunc('${interval}', ${timeSeriesEnd}),
|
|
960
|
+
INTERVAL '1 ${interval}'
|
|
961
|
+
) AS timestamp
|
|
962
|
+
),
|
|
963
|
+
marker_data AS (
|
|
964
|
+
SELECT
|
|
965
|
+
date_trunc('${interval}', tx_record_time) AS timestamp,
|
|
966
|
+
COUNT(*) FILTER (WHERE status = 'archived') as archived_count,
|
|
967
|
+
COUNT(*) FILTER (WHERE status = 'created') as created_count,
|
|
968
|
+
COUNT(*) FILTER (WHERE status = 'archived') as archived_weight,
|
|
969
|
+
COUNT(*) FILTER (WHERE status = 'created') as created_weight
|
|
970
|
+
FROM canton_app_markers
|
|
971
|
+
WHERE ${timeFilter}${partyCondition}
|
|
972
|
+
GROUP BY date_trunc('${interval}', tx_record_time)
|
|
973
|
+
)
|
|
974
|
+
SELECT
|
|
975
|
+
ts.timestamp::text,
|
|
976
|
+
COALESCE(md.archived_count, 0) as archived_count,
|
|
977
|
+
COALESCE(md.created_count, 0) as created_count,
|
|
978
|
+
COALESCE(md.archived_count, 0) + COALESCE(md.created_count, 0) as count,
|
|
979
|
+
COALESCE(md.archived_weight, 0) as archived_weight,
|
|
980
|
+
COALESCE(md.created_weight, 0) as created_weight,
|
|
981
|
+
0 as amount,
|
|
982
|
+
0 as coupon_amount
|
|
983
|
+
FROM time_series ts
|
|
984
|
+
LEFT JOIN marker_data md ON ts.timestamp = md.timestamp
|
|
985
|
+
ORDER BY ts.timestamp ASC
|
|
986
|
+
`;
|
|
987
|
+
const result = await this.pool.query(query);
|
|
988
|
+
return result.rows.map(row => ({
|
|
989
|
+
timestamp: row.timestamp,
|
|
990
|
+
count: parseInt(row.count, 10),
|
|
991
|
+
archivedCount: parseInt(row.archived_count, 10),
|
|
992
|
+
createdCount: parseInt(row.created_count, 10),
|
|
993
|
+
archivedWeight: parseFloat(row.archived_weight),
|
|
994
|
+
createdWeight: parseFloat(row.created_weight),
|
|
995
|
+
amount: 0,
|
|
996
|
+
couponAmount: 0,
|
|
997
|
+
}));
|
|
998
|
+
}
|
|
999
|
+
async getAppMarkerRoundSeriesData(timeRange, _metric = 'count', timePeriod, customStartDate, partyFilter, _statusFilter) {
|
|
1000
|
+
let timeRangeInterval;
|
|
1001
|
+
switch (timeRange) {
|
|
1002
|
+
case '15m':
|
|
1003
|
+
timeRangeInterval = '15 minutes';
|
|
1004
|
+
break;
|
|
1005
|
+
case '1h':
|
|
1006
|
+
timeRangeInterval = '1 hour';
|
|
1007
|
+
break;
|
|
1008
|
+
case '6h':
|
|
1009
|
+
timeRangeInterval = '6 hours';
|
|
1010
|
+
break;
|
|
1011
|
+
case '1d':
|
|
1012
|
+
timeRangeInterval = '1 day';
|
|
1013
|
+
break;
|
|
1014
|
+
case '7d':
|
|
1015
|
+
timeRangeInterval = '7 days';
|
|
1016
|
+
break;
|
|
1017
|
+
case '30d':
|
|
1018
|
+
timeRangeInterval = '30 days';
|
|
1019
|
+
break;
|
|
1020
|
+
default:
|
|
1021
|
+
timeRangeInterval = '1 day';
|
|
1022
|
+
}
|
|
1023
|
+
if (timePeriod === 'custom-start' && customStartDate) {
|
|
1024
|
+
const _timeFilter = `tx_record_time >= '${customStartDate}:00' AND tx_record_time < ('${customStartDate}:00'::timestamp + INTERVAL '${timeRangeInterval}')`;
|
|
1025
|
+
void _timeFilter;
|
|
1026
|
+
}
|
|
1027
|
+
// Add party filter if provided
|
|
1028
|
+
const _partyCondition = partyFilter
|
|
1029
|
+
? ` AND beneficiary_party_id = '${partyFilter}'`
|
|
1030
|
+
: '';
|
|
1031
|
+
void _partyCondition;
|
|
1032
|
+
// Note: Markers don't have round numbers, so we just return time-based groupings
|
|
1033
|
+
// This method shouldn't be used for markers - use getAppMarkerTimeSeriesData instead
|
|
1034
|
+
// Returning empty result set as markers don't have rounds
|
|
1035
|
+
const query = `
|
|
1036
|
+
SELECT
|
|
1037
|
+
0::bigint as round,
|
|
1038
|
+
NOW()::text as timestamp,
|
|
1039
|
+
0::bigint as archived_count,
|
|
1040
|
+
0::bigint as created_count,
|
|
1041
|
+
0::bigint as count,
|
|
1042
|
+
0::numeric as archived_weight,
|
|
1043
|
+
0::numeric as created_weight,
|
|
1044
|
+
0::numeric as amount,
|
|
1045
|
+
0::numeric as coupon_amount
|
|
1046
|
+
WHERE false
|
|
1047
|
+
`;
|
|
1048
|
+
const result = await this.pool.query(query);
|
|
1049
|
+
return result.rows.map(row => ({
|
|
1050
|
+
timestamp: row.timestamp
|
|
1051
|
+
? new Date(row.timestamp).toISOString()
|
|
1052
|
+
: new Date().toISOString(),
|
|
1053
|
+
count: parseInt(row.count, 10),
|
|
1054
|
+
archivedCount: parseInt(row.archived_count, 10),
|
|
1055
|
+
createdCount: parseInt(row.created_count, 10),
|
|
1056
|
+
archivedWeight: parseFloat(row.archived_weight),
|
|
1057
|
+
createdWeight: parseFloat(row.created_weight),
|
|
1058
|
+
amount: 0,
|
|
1059
|
+
couponAmount: 0,
|
|
1060
|
+
round: typeof row.round === 'number' ? row.round : parseInt(row.round, 10),
|
|
1061
|
+
}));
|
|
1062
|
+
}
|
|
1063
|
+
async getUniqueMarkerBeneficiaryParties() {
|
|
1064
|
+
const query = `
|
|
1065
|
+
SELECT DISTINCT
|
|
1066
|
+
beneficiary_party_id as party_id,
|
|
1067
|
+
beneficiary_party_id as display_name
|
|
1068
|
+
FROM canton_app_markers
|
|
1069
|
+
WHERE beneficiary_party_id IS NOT NULL
|
|
1070
|
+
ORDER BY beneficiary_party_id ASC
|
|
1071
|
+
`;
|
|
1072
|
+
const result = await this.pool.query(query);
|
|
1073
|
+
return result.rows;
|
|
1074
|
+
}
|
|
1075
|
+
async getMonthlyPaymentTotalForParty(partyId, monthStart, monthEnd) {
|
|
1076
|
+
// Check for transfers using created_at since tx_record_time might be null
|
|
1077
|
+
const query = `
|
|
1078
|
+
SELECT COALESCE(SUM(transfer_amount), 0) as total_amount
|
|
1079
|
+
FROM canton_transfers
|
|
1080
|
+
WHERE transfer_sender_party_id = $1
|
|
1081
|
+
AND created_at >= $2
|
|
1082
|
+
AND created_at <= $3
|
|
1083
|
+
AND status = $4
|
|
1084
|
+
`;
|
|
1085
|
+
const result = await this.pool.query(query, [
|
|
1086
|
+
partyId,
|
|
1087
|
+
monthStart,
|
|
1088
|
+
monthEnd,
|
|
1089
|
+
types_1.TransferStatus.CONFIRMED,
|
|
1090
|
+
]);
|
|
1091
|
+
return parseFloat(result.rows[0].total_amount);
|
|
1092
|
+
}
|
|
1093
|
+
getRemainingRoundsInMonth(roundDurationMinutes = 10) {
|
|
1094
|
+
if (roundDurationMinutes <= 0) {
|
|
1095
|
+
throw new Error('roundDurationMinutes must be greater than 0');
|
|
1096
|
+
}
|
|
1097
|
+
const now = new Date();
|
|
1098
|
+
const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1);
|
|
1099
|
+
const monthEnd = new Date(nextMonth.getTime() - 1); // Last moment of current month
|
|
1100
|
+
// Calculate how many intervals are remaining in the current month
|
|
1101
|
+
const roundDurationInMs = roundDurationMinutes * 60 * 1000;
|
|
1102
|
+
const remainingTimeInMs = monthEnd.getTime() - now.getTime();
|
|
1103
|
+
const remainingRounds = Math.max(0, Math.floor(remainingTimeInMs / roundDurationInMs));
|
|
1104
|
+
return remainingRounds;
|
|
1105
|
+
}
|
|
1106
|
+
async getMonthlyPaymentTotalsForParties(partyIds, monthStart, monthEnd) {
|
|
1107
|
+
if (partyIds.length === 0) {
|
|
1108
|
+
return new Map();
|
|
1109
|
+
}
|
|
1110
|
+
// Create placeholders for the IN clause
|
|
1111
|
+
const placeholders = partyIds.map((_, index) => `$${index + 4}`).join(',');
|
|
1112
|
+
const query = `
|
|
1113
|
+
SELECT transfer_sender_party_id, COALESCE(SUM(transfer_amount), 0) as total_amount
|
|
1114
|
+
FROM canton_transfers
|
|
1115
|
+
WHERE transfer_sender_party_id IN (${placeholders})
|
|
1116
|
+
AND created_at >= $1
|
|
1117
|
+
AND created_at <= $2
|
|
1118
|
+
AND status = $3
|
|
1119
|
+
GROUP BY transfer_sender_party_id
|
|
1120
|
+
`;
|
|
1121
|
+
const params = [
|
|
1122
|
+
monthStart,
|
|
1123
|
+
monthEnd,
|
|
1124
|
+
types_1.TransferStatus.CONFIRMED,
|
|
1125
|
+
...partyIds,
|
|
1126
|
+
];
|
|
1127
|
+
const result = await this.pool.query(query, params);
|
|
1128
|
+
const totals = new Map();
|
|
1129
|
+
result.rows.forEach(row => {
|
|
1130
|
+
totals.set(row.transfer_sender_party_id, parseFloat(row.total_amount));
|
|
1131
|
+
});
|
|
1132
|
+
// Ensure all party IDs are in the map (with 0 if no transfers found)
|
|
1133
|
+
partyIds.forEach(partyId => {
|
|
1134
|
+
if (!totals.has(partyId)) {
|
|
1135
|
+
totals.set(partyId, 0);
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
return totals;
|
|
1139
|
+
}
|
|
1140
|
+
async getLifetimePaymentStatisticsForParties(partyIds) {
|
|
1141
|
+
if (partyIds.length === 0) {
|
|
1142
|
+
return new Map();
|
|
1143
|
+
}
|
|
1144
|
+
// Create placeholders for the IN clause
|
|
1145
|
+
const placeholders = partyIds.map((_, index) => `$${index + 1}`).join(',');
|
|
1146
|
+
const query = `
|
|
1147
|
+
SELECT
|
|
1148
|
+
transfer_sender_party_id,
|
|
1149
|
+
COUNT(*) as payment_count,
|
|
1150
|
+
COALESCE(SUM(transfer_amount), 0) as total_value_sent
|
|
1151
|
+
FROM canton_transfers
|
|
1152
|
+
WHERE transfer_sender_party_id IN (${placeholders})
|
|
1153
|
+
AND status = $${partyIds.length + 1}
|
|
1154
|
+
GROUP BY transfer_sender_party_id
|
|
1155
|
+
`;
|
|
1156
|
+
const params = [...partyIds, types_1.TransferStatus.CONFIRMED];
|
|
1157
|
+
const result = await this.pool.query(query, params);
|
|
1158
|
+
const statistics = new Map();
|
|
1159
|
+
result.rows.forEach(row => {
|
|
1160
|
+
const paymentCount = parseInt(row.payment_count, 10);
|
|
1161
|
+
const totalValueSent = parseFloat(row.total_value_sent);
|
|
1162
|
+
const totalFeesPaid = paymentCount * 0.6; // 0.6 * count as specified
|
|
1163
|
+
statistics.set(row.transfer_sender_party_id, {
|
|
1164
|
+
paymentCount,
|
|
1165
|
+
totalValueSent,
|
|
1166
|
+
totalFeesPaid,
|
|
1167
|
+
});
|
|
1168
|
+
});
|
|
1169
|
+
// Ensure all party IDs are in the map (with 0 if no transfers found)
|
|
1170
|
+
partyIds.forEach(partyId => {
|
|
1171
|
+
if (!statistics.has(partyId)) {
|
|
1172
|
+
statistics.set(partyId, {
|
|
1173
|
+
paymentCount: 0,
|
|
1174
|
+
totalValueSent: 0,
|
|
1175
|
+
totalFeesPaid: 0,
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
return statistics;
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Get portals that have enable_canton_rewards=true but don't have canton parties yet
|
|
1183
|
+
*
|
|
1184
|
+
* @returns Array of portal info that need canton parties created
|
|
1185
|
+
*/
|
|
1186
|
+
async getPortalsNeedingCantonParties() {
|
|
1187
|
+
const query = `
|
|
1188
|
+
SELECT p.id, p.company->>'name' as company_name
|
|
1189
|
+
FROM portal p
|
|
1190
|
+
WHERE p.enable_canton_rewards = true
|
|
1191
|
+
AND NOT EXISTS (
|
|
1192
|
+
SELECT 1 FROM canton_parties cp
|
|
1193
|
+
WHERE cp.portal_id = p.id
|
|
1194
|
+
)
|
|
1195
|
+
ORDER BY p.created_at ASC
|
|
1196
|
+
`;
|
|
1197
|
+
const result = await this.pool.query(query);
|
|
1198
|
+
return result.rows.map(row => ({
|
|
1199
|
+
id: row.id,
|
|
1200
|
+
companyName: row.company_name ?? undefined,
|
|
1201
|
+
}));
|
|
1202
|
+
}
|
|
1203
|
+
// Transaction helper - runs a callback within a database transaction
|
|
1204
|
+
async runInTransaction(callback) {
|
|
1205
|
+
const client = await this.pool.connect();
|
|
1206
|
+
try {
|
|
1207
|
+
await client.query('BEGIN');
|
|
1208
|
+
const result = await callback(client);
|
|
1209
|
+
await client.query('COMMIT');
|
|
1210
|
+
return result;
|
|
1211
|
+
}
|
|
1212
|
+
catch (error) {
|
|
1213
|
+
await client.query('ROLLBACK');
|
|
1214
|
+
throw error;
|
|
1215
|
+
}
|
|
1216
|
+
finally {
|
|
1217
|
+
client.release();
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
// Canton App Markers CRUD operations
|
|
1221
|
+
async insertCantonAppMarker(marker) {
|
|
1222
|
+
const query = `
|
|
1223
|
+
INSERT INTO canton_app_markers (
|
|
1224
|
+
status, contract_id, provider_party_id, beneficiary_party_id, tx_record_time, weight
|
|
1225
|
+
) VALUES ($1, $2, $3, $4, $5, $6)
|
|
1226
|
+
RETURNING *
|
|
1227
|
+
`;
|
|
1228
|
+
const values = [
|
|
1229
|
+
marker.status,
|
|
1230
|
+
marker.contract_id,
|
|
1231
|
+
marker.provider_party_id,
|
|
1232
|
+
marker.beneficiary_party_id,
|
|
1233
|
+
marker.tx_record_time,
|
|
1234
|
+
marker.weight,
|
|
1235
|
+
];
|
|
1236
|
+
const result = await this.pool.query(query, values);
|
|
1237
|
+
return this.mapAppMarkerFromDb(result.rows[0]);
|
|
1238
|
+
}
|
|
1239
|
+
async batchInsertCantonAppMarkers(markers) {
|
|
1240
|
+
if (markers.length === 0) {
|
|
1241
|
+
return { insertedCount: 0, skippedCount: 0 };
|
|
1242
|
+
}
|
|
1243
|
+
// Build the VALUES clause with placeholders
|
|
1244
|
+
const valuesPlaceholders = markers
|
|
1245
|
+
.map((_, idx) => `($${idx * 6 + 1}, $${idx * 6 + 2}, $${idx * 6 + 3}, $${idx * 6 + 4}, $${idx * 6 + 5}, $${idx * 6 + 6})`)
|
|
1246
|
+
.join(', ');
|
|
1247
|
+
const query = `
|
|
1248
|
+
INSERT INTO canton_app_markers (
|
|
1249
|
+
status, contract_id, provider_party_id, beneficiary_party_id, tx_record_time, weight
|
|
1250
|
+
) VALUES ${valuesPlaceholders}
|
|
1251
|
+
ON CONFLICT (contract_id) DO NOTHING
|
|
1252
|
+
RETURNING *
|
|
1253
|
+
`;
|
|
1254
|
+
// Flatten all marker values into a single array
|
|
1255
|
+
const values = markers.flatMap(marker => [
|
|
1256
|
+
marker.status,
|
|
1257
|
+
marker.contract_id,
|
|
1258
|
+
marker.provider_party_id,
|
|
1259
|
+
marker.beneficiary_party_id,
|
|
1260
|
+
marker.tx_record_time,
|
|
1261
|
+
marker.weight,
|
|
1262
|
+
]);
|
|
1263
|
+
const result = await this.pool.query(query, values);
|
|
1264
|
+
const insertedCount = result.rowCount ?? 0;
|
|
1265
|
+
const skippedCount = markers.length - insertedCount;
|
|
1266
|
+
return { insertedCount, skippedCount };
|
|
1267
|
+
}
|
|
1268
|
+
async upsertCantonAppMarkerAsArchived(marker) {
|
|
1269
|
+
const query = `
|
|
1270
|
+
INSERT INTO canton_app_markers (
|
|
1271
|
+
status, contract_id, provider_party_id, beneficiary_party_id, tx_record_time, weight
|
|
1272
|
+
) VALUES ($1, $2, $3, $4, $5, $6)
|
|
1273
|
+
ON CONFLICT (contract_id) DO UPDATE
|
|
1274
|
+
SET status = EXCLUDED.status, updated_at = NOW()
|
|
1275
|
+
RETURNING *
|
|
1276
|
+
`;
|
|
1277
|
+
const values = [
|
|
1278
|
+
marker.status,
|
|
1279
|
+
marker.contract_id,
|
|
1280
|
+
marker.provider_party_id,
|
|
1281
|
+
marker.beneficiary_party_id,
|
|
1282
|
+
marker.tx_record_time,
|
|
1283
|
+
marker.weight,
|
|
1284
|
+
];
|
|
1285
|
+
const result = await this.pool.query(query, values);
|
|
1286
|
+
return this.mapAppMarkerFromDb(result.rows[0]);
|
|
1287
|
+
}
|
|
1288
|
+
async getCantonAppMarker(id) {
|
|
1289
|
+
const query = 'SELECT * FROM canton_app_markers WHERE id = $1';
|
|
1290
|
+
const result = await this.pool.query(query, [id]);
|
|
1291
|
+
return result.rows.length > 0
|
|
1292
|
+
? this.mapAppMarkerFromDb(result.rows[0])
|
|
1293
|
+
: null;
|
|
1294
|
+
}
|
|
1295
|
+
async getCantonAppMarkerByContractId(contractId) {
|
|
1296
|
+
const query = 'SELECT * FROM canton_app_markers WHERE contract_id = $1';
|
|
1297
|
+
const result = await this.pool.query(query, [contractId]);
|
|
1298
|
+
return result.rows.length > 0
|
|
1299
|
+
? this.mapAppMarkerFromDb(result.rows[0])
|
|
1300
|
+
: null;
|
|
1301
|
+
}
|
|
1302
|
+
async getCantonAppMarkersByContractIds(contractIds) {
|
|
1303
|
+
if (contractIds.length === 0) {
|
|
1304
|
+
return [];
|
|
1305
|
+
}
|
|
1306
|
+
const placeholders = contractIds
|
|
1307
|
+
.map((_, index) => `$${index + 1}`)
|
|
1308
|
+
.join(', ');
|
|
1309
|
+
const query = `
|
|
1310
|
+
SELECT * FROM canton_app_markers
|
|
1311
|
+
WHERE contract_id IN (${placeholders})
|
|
1312
|
+
`;
|
|
1313
|
+
const result = await this.pool.query(query, contractIds);
|
|
1314
|
+
return result.rows.map(row => this.mapAppMarkerFromDb(row));
|
|
1315
|
+
}
|
|
1316
|
+
async batchUpdateCantonAppMarkersByContractIds(contractIds, updates) {
|
|
1317
|
+
if (contractIds.length === 0) {
|
|
1318
|
+
return [];
|
|
1319
|
+
}
|
|
1320
|
+
const setClauses = [];
|
|
1321
|
+
const values = [];
|
|
1322
|
+
let paramIndex = 1;
|
|
1323
|
+
// Build dynamic update query
|
|
1324
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
1325
|
+
if (key !== 'id' && key !== 'created_at') {
|
|
1326
|
+
setClauses.push(`${this.toSnakeCase(key)} = $${paramIndex}`);
|
|
1327
|
+
values.push(value);
|
|
1328
|
+
paramIndex++;
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1331
|
+
if (setClauses.length === 0) {
|
|
1332
|
+
throw new Error('No valid fields to update');
|
|
1333
|
+
}
|
|
1334
|
+
// Build the WHERE clause for contract IDs
|
|
1335
|
+
const contractIdPlaceholders = contractIds
|
|
1336
|
+
.map((_, index) => `$${paramIndex + index}`)
|
|
1337
|
+
.join(', ');
|
|
1338
|
+
values.push(...contractIds);
|
|
1339
|
+
const query = `
|
|
1340
|
+
UPDATE canton_app_markers
|
|
1341
|
+
SET ${setClauses.join(', ')}, updated_at = NOW()
|
|
1342
|
+
WHERE contract_id IN (${contractIdPlaceholders})
|
|
1343
|
+
RETURNING *
|
|
1344
|
+
`;
|
|
1345
|
+
const result = await this.pool.query(query, values);
|
|
1346
|
+
return result.rows.map(row => this.mapAppMarkerFromDb(row));
|
|
1347
|
+
}
|
|
1348
|
+
async getCantonAppMarkersByStatus(status, limit = 100, sortOrder = 'ASC') {
|
|
1349
|
+
const query = `
|
|
1350
|
+
SELECT * FROM canton_app_markers
|
|
1351
|
+
WHERE status = $1
|
|
1352
|
+
ORDER BY tx_record_time ${sortOrder}
|
|
1353
|
+
LIMIT $2
|
|
1354
|
+
`;
|
|
1355
|
+
const result = await this.pool.query(query, [status, limit]);
|
|
1356
|
+
return result.rows.map(row => this.mapAppMarkerFromDb(row));
|
|
1357
|
+
}
|
|
1358
|
+
async getLatestCantonAppMarkers(limit = 3, beneficiaryPartyId) {
|
|
1359
|
+
let query = `
|
|
1360
|
+
SELECT * FROM canton_app_markers
|
|
1361
|
+
`;
|
|
1362
|
+
const params = [];
|
|
1363
|
+
if (beneficiaryPartyId) {
|
|
1364
|
+
query += ` WHERE beneficiary_party_id = $1`;
|
|
1365
|
+
params.push(beneficiaryPartyId);
|
|
1366
|
+
}
|
|
1367
|
+
query += ` ORDER BY tx_record_time DESC LIMIT $${params.length + 1}`;
|
|
1368
|
+
params.push(limit);
|
|
1369
|
+
const result = await this.pool.query(query, params);
|
|
1370
|
+
return result.rows.map(row => this.mapAppMarkerFromDb(row));
|
|
1371
|
+
}
|
|
1372
|
+
async getOldestUnarchivedCantonAppMarkers(limit = 3, beneficiaryPartyId) {
|
|
1373
|
+
let query = `
|
|
1374
|
+
SELECT * FROM canton_app_markers
|
|
1375
|
+
WHERE status = 'created'
|
|
1376
|
+
`;
|
|
1377
|
+
const params = [];
|
|
1378
|
+
if (beneficiaryPartyId) {
|
|
1379
|
+
query += ` AND beneficiary_party_id = $${params.length + 1}`;
|
|
1380
|
+
params.push(beneficiaryPartyId);
|
|
1381
|
+
}
|
|
1382
|
+
query += ` ORDER BY tx_record_time ASC LIMIT $${params.length + 1}`;
|
|
1383
|
+
params.push(limit);
|
|
1384
|
+
const result = await this.pool.query(query, params);
|
|
1385
|
+
return result.rows.map(row => this.mapAppMarkerFromDb(row));
|
|
1386
|
+
}
|
|
1387
|
+
async updateCantonAppMarker(id, updates) {
|
|
1388
|
+
const setClauses = [];
|
|
1389
|
+
const values = [];
|
|
1390
|
+
let paramIndex = 1;
|
|
1391
|
+
// Build dynamic update query
|
|
1392
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
1393
|
+
if (key !== 'id' && key !== 'created_at') {
|
|
1394
|
+
setClauses.push(`${this.toSnakeCase(key)} = $${paramIndex}`);
|
|
1395
|
+
values.push(value);
|
|
1396
|
+
paramIndex++;
|
|
1397
|
+
}
|
|
1398
|
+
});
|
|
1399
|
+
if (setClauses.length === 0) {
|
|
1400
|
+
throw new Error('No valid fields to update');
|
|
1401
|
+
}
|
|
1402
|
+
values.push(id);
|
|
1403
|
+
const query = `
|
|
1404
|
+
UPDATE canton_app_markers
|
|
1405
|
+
SET ${setClauses.join(', ')}, updated_at = NOW()
|
|
1406
|
+
WHERE id = $${paramIndex}
|
|
1407
|
+
RETURNING *
|
|
1408
|
+
`;
|
|
1409
|
+
const result = await this.pool.query(query, values);
|
|
1410
|
+
return result.rows.length > 0
|
|
1411
|
+
? this.mapAppMarkerFromDb(result.rows[0])
|
|
1412
|
+
: null;
|
|
1413
|
+
}
|
|
1414
|
+
// Canton Parties CRUD operations
|
|
1415
|
+
async insertCantonParty(party) {
|
|
1416
|
+
const query = `
|
|
1417
|
+
INSERT INTO canton_parties (
|
|
1418
|
+
party_id, portal_id, provider, last_payment_until, is_active_customer_since
|
|
1419
|
+
) VALUES ($1, $2, $3, $4, $5)
|
|
1420
|
+
RETURNING *
|
|
1421
|
+
`;
|
|
1422
|
+
const values = [
|
|
1423
|
+
party.party_id,
|
|
1424
|
+
party.portal_id,
|
|
1425
|
+
party.provider,
|
|
1426
|
+
party.last_payment_until,
|
|
1427
|
+
party.is_active_customer_since,
|
|
1428
|
+
];
|
|
1429
|
+
const result = await this.pool.query(query, values);
|
|
1430
|
+
return this.mapPartyFromDb(result.rows[0]);
|
|
1431
|
+
}
|
|
1432
|
+
async getCantonParty(id) {
|
|
1433
|
+
const query = `
|
|
1434
|
+
SELECT
|
|
1435
|
+
cp.*,
|
|
1436
|
+
p.company
|
|
1437
|
+
FROM canton_parties cp
|
|
1438
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
1439
|
+
WHERE cp.id = $1
|
|
1440
|
+
`;
|
|
1441
|
+
const result = await this.pool.query(query, [id]);
|
|
1442
|
+
return result.rows.length > 0 ? this.mapPartyFromDb(result.rows[0]) : null;
|
|
1443
|
+
}
|
|
1444
|
+
async getCantonPartyByPartyId(partyId) {
|
|
1445
|
+
const query = `
|
|
1446
|
+
SELECT
|
|
1447
|
+
cp.*,
|
|
1448
|
+
p.company
|
|
1449
|
+
FROM canton_parties cp
|
|
1450
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
1451
|
+
WHERE cp.party_id = $1
|
|
1452
|
+
`;
|
|
1453
|
+
const result = await this.pool.query(query, [partyId]);
|
|
1454
|
+
return result.rows.length > 0 ? this.mapPartyFromDb(result.rows[0]) : null;
|
|
1455
|
+
}
|
|
1456
|
+
async updateCantonParty(id, updates) {
|
|
1457
|
+
const setClauses = [];
|
|
1458
|
+
const values = [];
|
|
1459
|
+
let paramIndex = 1;
|
|
1460
|
+
// Build dynamic update query - exclude amulets field
|
|
1461
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
1462
|
+
if (key !== 'id' && key !== 'created_at' && key !== 'amulets') {
|
|
1463
|
+
setClauses.push(`${this.toSnakeCase(key)} = $${paramIndex}`);
|
|
1464
|
+
values.push(value);
|
|
1465
|
+
paramIndex++;
|
|
1466
|
+
}
|
|
1467
|
+
});
|
|
1468
|
+
if (setClauses.length === 0) {
|
|
1469
|
+
throw new Error('No valid fields to update');
|
|
1470
|
+
}
|
|
1471
|
+
values.push(id);
|
|
1472
|
+
const query = `
|
|
1473
|
+
UPDATE canton_parties
|
|
1474
|
+
SET ${setClauses.join(', ')}, updated_at = NOW()
|
|
1475
|
+
WHERE id = $${paramIndex}
|
|
1476
|
+
RETURNING id
|
|
1477
|
+
`;
|
|
1478
|
+
const result = await this.pool.query(query, values);
|
|
1479
|
+
if (result.rows.length > 0) {
|
|
1480
|
+
return this.getCantonParty(id);
|
|
1481
|
+
}
|
|
1482
|
+
return null;
|
|
1483
|
+
}
|
|
1484
|
+
async updateCantonPartyByPartyId(partyId, updates) {
|
|
1485
|
+
const setClauses = [];
|
|
1486
|
+
const values = [];
|
|
1487
|
+
let paramIndex = 1;
|
|
1488
|
+
// Build dynamic update query - exclude amulets field
|
|
1489
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
1490
|
+
if (key !== 'id' && key !== 'created_at' && key !== 'amulets') {
|
|
1491
|
+
setClauses.push(`${this.toSnakeCase(key)} = $${paramIndex}`);
|
|
1492
|
+
values.push(value);
|
|
1493
|
+
paramIndex++;
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
if (setClauses.length === 0) {
|
|
1497
|
+
throw new Error('No valid fields to update');
|
|
1498
|
+
}
|
|
1499
|
+
values.push(partyId);
|
|
1500
|
+
const query = `
|
|
1501
|
+
UPDATE canton_parties
|
|
1502
|
+
SET ${setClauses.join(', ')}, updated_at = NOW()
|
|
1503
|
+
WHERE party_id = $${paramIndex}
|
|
1504
|
+
RETURNING party_id
|
|
1505
|
+
`;
|
|
1506
|
+
const result = await this.pool.query(query, values);
|
|
1507
|
+
if (result.rows.length > 0) {
|
|
1508
|
+
return this.getCantonPartyByPartyId(partyId);
|
|
1509
|
+
}
|
|
1510
|
+
return null;
|
|
1511
|
+
}
|
|
1512
|
+
// Amulet operations removed - use getActiveContracts instead
|
|
1513
|
+
// async addAmuletToParty() - REMOVED
|
|
1514
|
+
// async removeAmuletFromParty() - REMOVED
|
|
1515
|
+
// async replaceAmuletsForParty() - REMOVED
|
|
1516
|
+
async getActiveCustomersWithExpiredPayment() {
|
|
1517
|
+
const query = `
|
|
1518
|
+
SELECT
|
|
1519
|
+
cp.*,
|
|
1520
|
+
p.company
|
|
1521
|
+
FROM canton_parties cp
|
|
1522
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
1523
|
+
WHERE cp.is_active_customer_since IS NOT NULL
|
|
1524
|
+
AND (cp.last_payment_until IS NULL OR cp.last_payment_until < NOW())
|
|
1525
|
+
ORDER BY cp.last_payment_until ASC NULLS FIRST
|
|
1526
|
+
`;
|
|
1527
|
+
const result = await this.pool.query(query);
|
|
1528
|
+
return result.rows.map(row => this.mapPartyFromDb(row));
|
|
1529
|
+
}
|
|
1530
|
+
async getAllParties(provider) {
|
|
1531
|
+
let query = `
|
|
1532
|
+
SELECT
|
|
1533
|
+
cp.*,
|
|
1534
|
+
p.company
|
|
1535
|
+
FROM canton_parties cp
|
|
1536
|
+
INNER JOIN portal p ON cp.portal_id = p.id
|
|
1537
|
+
`;
|
|
1538
|
+
const values = [];
|
|
1539
|
+
if (provider) {
|
|
1540
|
+
// Handle 5n-broker case since database enum doesn't support it yet
|
|
1541
|
+
if (provider === '5n-broker') {
|
|
1542
|
+
// For now, return empty array since 5n-broker parties aren't in the database yet
|
|
1543
|
+
console.log('⚠️ 5n-broker provider requested - returning empty array (not in database yet)');
|
|
1544
|
+
return [];
|
|
1545
|
+
}
|
|
1546
|
+
query += ` WHERE cp.provider = $1`;
|
|
1547
|
+
values.push(provider);
|
|
1548
|
+
}
|
|
1549
|
+
query += ` ORDER BY cp.created_at ASC`;
|
|
1550
|
+
console.log(`🔍 Database query for network=${this.network}, provider=${provider}:`, { query, values });
|
|
1551
|
+
const result = await this.pool.query(query, values);
|
|
1552
|
+
console.log(`📋 Database returned ${result.rows.length} parties for network=${this.network}, provider=${provider}`);
|
|
1553
|
+
if (result.rows.length > 0) {
|
|
1554
|
+
console.log('📝 Sample database parties:', result.rows
|
|
1555
|
+
.slice(0, 3)
|
|
1556
|
+
.map(row => ({ party_id: row.party_id, provider: row.provider })));
|
|
1557
|
+
}
|
|
1558
|
+
const parties = result.rows.map(row => this.mapPartyFromDb(row));
|
|
1559
|
+
// Get payment statistics for all parties
|
|
1560
|
+
const partyIds = parties.map(party => party.party_id);
|
|
1561
|
+
const paymentStats = await this.getLifetimePaymentStatisticsForParties(partyIds);
|
|
1562
|
+
// Merge payment statistics with party data
|
|
1563
|
+
return parties.map(party => {
|
|
1564
|
+
const stats = paymentStats.get(party.party_id);
|
|
1565
|
+
return {
|
|
1566
|
+
...party,
|
|
1567
|
+
payment_count: stats?.paymentCount ?? 0,
|
|
1568
|
+
total_value_sent: stats?.totalValueSent ?? 0,
|
|
1569
|
+
total_fees_paid: stats?.totalFeesPaid ?? 0,
|
|
1570
|
+
};
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1573
|
+
// OCF Deployments CRUD operations
|
|
1574
|
+
async insertOcfDeployment(deployment) {
|
|
1575
|
+
const query = `
|
|
1576
|
+
INSERT INTO ocf_deployments (
|
|
1577
|
+
ocf_object_id, version, chain_id, status, tx_hash, contract_id, party_id, wallet_address
|
|
1578
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
1579
|
+
RETURNING id, ocf_object_id, version, chain_id, status, tx_hash, contract_id, party_id, wallet_address, created_at, updated_at
|
|
1580
|
+
`;
|
|
1581
|
+
const values = [
|
|
1582
|
+
deployment.ocf_object_id,
|
|
1583
|
+
deployment.version,
|
|
1584
|
+
deployment.chain_id,
|
|
1585
|
+
deployment.status,
|
|
1586
|
+
deployment.tx_hash,
|
|
1587
|
+
deployment.contract_id,
|
|
1588
|
+
deployment.party_id,
|
|
1589
|
+
deployment.wallet_address,
|
|
1590
|
+
];
|
|
1591
|
+
const result = await this.pool.query(query, values);
|
|
1592
|
+
const row = result.rows[0];
|
|
1593
|
+
return {
|
|
1594
|
+
id: row.id,
|
|
1595
|
+
ocf_object_id: row.ocf_object_id,
|
|
1596
|
+
version: row.version,
|
|
1597
|
+
chain_id: row.chain_id,
|
|
1598
|
+
status: row.status,
|
|
1599
|
+
tx_hash: row.tx_hash,
|
|
1600
|
+
contract_id: row.contract_id,
|
|
1601
|
+
party_id: row.party_id,
|
|
1602
|
+
wallet_address: row.wallet_address,
|
|
1603
|
+
created_at: row.created_at,
|
|
1604
|
+
updated_at: row.updated_at,
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
async getLatestOcfDeploymentByPartyId(partyId) {
|
|
1608
|
+
const query = `
|
|
1609
|
+
SELECT id, ocf_object_id, version, chain_id, status, tx_hash, contract_id, party_id, wallet_address, created_at, updated_at
|
|
1610
|
+
FROM ocf_deployments
|
|
1611
|
+
WHERE party_id = $1
|
|
1612
|
+
ORDER BY version DESC
|
|
1613
|
+
LIMIT 1
|
|
1614
|
+
`;
|
|
1615
|
+
const result = await this.pool.query(query, [partyId]);
|
|
1616
|
+
if (result.rows.length === 0) {
|
|
1617
|
+
return null;
|
|
1618
|
+
}
|
|
1619
|
+
const row = result.rows[0];
|
|
1620
|
+
return {
|
|
1621
|
+
id: row.id,
|
|
1622
|
+
ocf_object_id: row.ocf_object_id,
|
|
1623
|
+
version: row.version,
|
|
1624
|
+
chain_id: row.chain_id,
|
|
1625
|
+
status: row.status,
|
|
1626
|
+
tx_hash: row.tx_hash,
|
|
1627
|
+
contract_id: row.contract_id,
|
|
1628
|
+
party_id: row.party_id,
|
|
1629
|
+
wallet_address: row.wallet_address,
|
|
1630
|
+
created_at: row.created_at,
|
|
1631
|
+
updated_at: row.updated_at,
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
async getLatestOcfObjectByPortalId(portalId) {
|
|
1635
|
+
const query = `
|
|
1636
|
+
SELECT o.id as ocf_object_id, o.version
|
|
1637
|
+
FROM latest_ocf_objects o
|
|
1638
|
+
WHERE o.portal_id = $1
|
|
1639
|
+
AND o.type = 'ISSUER'
|
|
1640
|
+
ORDER BY o.version DESC
|
|
1641
|
+
LIMIT 1
|
|
1642
|
+
`;
|
|
1643
|
+
const result = await this.pool.query(query, [portalId]);
|
|
1644
|
+
if (result.rows.length === 0) {
|
|
1645
|
+
return null;
|
|
1646
|
+
}
|
|
1647
|
+
const row = result.rows[0];
|
|
1648
|
+
return {
|
|
1649
|
+
ocf_object_id: row.ocf_object_id,
|
|
1650
|
+
version: row.version,
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
/** Retrieve a specific OCF object row by id and version */
|
|
1654
|
+
async getOcfObjectDataByIdAndVersion(ocfObjectId, version) {
|
|
1655
|
+
const query = `
|
|
1656
|
+
SELECT type, subtype, ocf_data
|
|
1657
|
+
FROM ocf_objects
|
|
1658
|
+
WHERE id = $1 AND version = $2
|
|
1659
|
+
LIMIT 1
|
|
1660
|
+
`;
|
|
1661
|
+
const result = await this.pool.query(query, [ocfObjectId, version]);
|
|
1662
|
+
if (result.rows.length === 0)
|
|
1663
|
+
return null;
|
|
1664
|
+
const row = result.rows[0];
|
|
1665
|
+
return {
|
|
1666
|
+
type: row.type,
|
|
1667
|
+
subtype: row.subtype ?? null,
|
|
1668
|
+
ocf_data: row.ocf_data,
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
/** List OCF deployments, optionally filtered */
|
|
1672
|
+
async listOcfDeployments(params) {
|
|
1673
|
+
const conditions = [];
|
|
1674
|
+
const values = [];
|
|
1675
|
+
let idx = 1;
|
|
1676
|
+
if (params?.chainId) {
|
|
1677
|
+
conditions.push(`chain_id = $${idx++}`);
|
|
1678
|
+
values.push(params.chainId);
|
|
1679
|
+
}
|
|
1680
|
+
if (params?.status) {
|
|
1681
|
+
conditions.push(`status = $${idx++}`);
|
|
1682
|
+
values.push(params.status);
|
|
1683
|
+
}
|
|
1684
|
+
if (params?.partyId) {
|
|
1685
|
+
conditions.push(`party_id = $${idx++}`);
|
|
1686
|
+
values.push(params.partyId);
|
|
1687
|
+
}
|
|
1688
|
+
const whereClause = conditions.length
|
|
1689
|
+
? `WHERE ${conditions.join(' AND ')}`
|
|
1690
|
+
: '';
|
|
1691
|
+
const limitClause = typeof params?.limit === 'number' ? `LIMIT ${params.limit}` : '';
|
|
1692
|
+
const offsetClause = typeof params?.offset === 'number' ? `OFFSET ${params.offset}` : '';
|
|
1693
|
+
const query = `
|
|
1694
|
+
SELECT id, ocf_object_id, version, chain_id, status, tx_hash, contract_id, party_id, wallet_address, created_at, updated_at
|
|
1695
|
+
FROM ocf_deployments
|
|
1696
|
+
${whereClause}
|
|
1697
|
+
ORDER BY created_at DESC
|
|
1698
|
+
${limitClause}
|
|
1699
|
+
${offsetClause}
|
|
1700
|
+
`;
|
|
1701
|
+
const result = await this.pool.query(query, values);
|
|
1702
|
+
return result.rows.map(row => ({
|
|
1703
|
+
id: row.id,
|
|
1704
|
+
ocf_object_id: row.ocf_object_id,
|
|
1705
|
+
version: row.version,
|
|
1706
|
+
chain_id: row.chain_id,
|
|
1707
|
+
status: row.status,
|
|
1708
|
+
tx_hash: row.tx_hash,
|
|
1709
|
+
contract_id: row.contract_id,
|
|
1710
|
+
party_id: row.party_id,
|
|
1711
|
+
wallet_address: row.wallet_address,
|
|
1712
|
+
created_at: row.created_at,
|
|
1713
|
+
updated_at: row.updated_at,
|
|
1714
|
+
}));
|
|
1715
|
+
}
|
|
1716
|
+
/** Count total rows in latest_ocf_objects for progress stats */
|
|
1717
|
+
async countLatestOcfObjects() {
|
|
1718
|
+
const query = `SELECT COUNT(*) AS cnt FROM latest_ocf_objects`;
|
|
1719
|
+
const result = await this.pool.query(query);
|
|
1720
|
+
return Number(result.rows[0]?.cnt ?? 0);
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Count rows in latest_ocf_objects that belong to portals with sync_captable_onchain=true Used for actionable
|
|
1724
|
+
* progress stats (objects that should be synced onchain)
|
|
1725
|
+
*/
|
|
1726
|
+
async countLatestOcfObjectsForOnchainSync() {
|
|
1727
|
+
const query = `
|
|
1728
|
+
SELECT COUNT(*) AS cnt
|
|
1729
|
+
FROM latest_ocf_objects o
|
|
1730
|
+
JOIN portal p ON p.id = o.portal_id
|
|
1731
|
+
WHERE p.enable_canton_rewards = true
|
|
1732
|
+
`;
|
|
1733
|
+
const result = await this.pool.query(query);
|
|
1734
|
+
return Number(result.rows[0]?.cnt ?? 0);
|
|
1735
|
+
}
|
|
1736
|
+
async getLatestOcfObjectDataByPortalId(portalId) {
|
|
1737
|
+
const query = `
|
|
1738
|
+
SELECT o.id as ocf_object_id, o.version, o.ocf_data, o.type
|
|
1739
|
+
FROM latest_ocf_objects o
|
|
1740
|
+
WHERE o.portal_id = $1
|
|
1741
|
+
AND o.type = 'ISSUER'
|
|
1742
|
+
ORDER BY o.version DESC
|
|
1743
|
+
LIMIT 1
|
|
1744
|
+
`;
|
|
1745
|
+
const result = await this.pool.query(query, [portalId]);
|
|
1746
|
+
if (result.rows.length === 0) {
|
|
1747
|
+
return null;
|
|
1748
|
+
}
|
|
1749
|
+
const row = result.rows[0];
|
|
1750
|
+
return {
|
|
1751
|
+
ocf_object_id: row.ocf_object_id,
|
|
1752
|
+
version: row.version,
|
|
1753
|
+
ocf_data: row.ocf_data,
|
|
1754
|
+
type: row.type,
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
async getPartiesWithoutOcfObjects() {
|
|
1758
|
+
const query = `
|
|
1759
|
+
SELECT
|
|
1760
|
+
cp.party_id,
|
|
1761
|
+
cp.portal_id,
|
|
1762
|
+
cp.provider,
|
|
1763
|
+
p.company
|
|
1764
|
+
FROM canton_parties cp
|
|
1765
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
1766
|
+
WHERE NOT EXISTS (
|
|
1767
|
+
SELECT 1 FROM latest_ocf_objects o
|
|
1768
|
+
WHERE o.portal_id = cp.portal_id
|
|
1769
|
+
AND o.type = 'ISSUER'
|
|
1770
|
+
)
|
|
1771
|
+
ORDER BY cp.created_at ASC
|
|
1772
|
+
`;
|
|
1773
|
+
const result = await this.pool.query(query);
|
|
1774
|
+
return result.rows;
|
|
1775
|
+
}
|
|
1776
|
+
async getPartiesWithOcfObjectsButNoIssuerDeployments() {
|
|
1777
|
+
const query = `
|
|
1778
|
+
SELECT
|
|
1779
|
+
cp.party_id,
|
|
1780
|
+
cp.portal_id,
|
|
1781
|
+
cp.provider,
|
|
1782
|
+
p.company,
|
|
1783
|
+
o.id as ocf_object_id,
|
|
1784
|
+
o.version as ocf_version,
|
|
1785
|
+
o.ocf_data,
|
|
1786
|
+
o.type as ocf_type
|
|
1787
|
+
FROM canton_parties cp
|
|
1788
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
1789
|
+
INNER JOIN latest_ocf_objects o ON o.portal_id = cp.portal_id AND o.type = 'ISSUER'
|
|
1790
|
+
WHERE NOT EXISTS (
|
|
1791
|
+
SELECT 1 FROM ocf_deployments od
|
|
1792
|
+
WHERE od.party_id = cp.party_id
|
|
1793
|
+
AND od.ocf_object_id = o.id
|
|
1794
|
+
)
|
|
1795
|
+
ORDER BY cp.created_at ASC
|
|
1796
|
+
`;
|
|
1797
|
+
const result = await this.pool.query(query);
|
|
1798
|
+
return result.rows;
|
|
1799
|
+
}
|
|
1800
|
+
async getPartiesWithOutdatedIssuerDeployments() {
|
|
1801
|
+
const query = `
|
|
1802
|
+
SELECT
|
|
1803
|
+
cp.party_id,
|
|
1804
|
+
cp.portal_id,
|
|
1805
|
+
cp.provider,
|
|
1806
|
+
p.company,
|
|
1807
|
+
latest_ocf.version as latest_ocf_version,
|
|
1808
|
+
latest_deployment.version as deployed_version,
|
|
1809
|
+
latest_ocf.id as latest_ocf_object_id,
|
|
1810
|
+
latest_deployment.contract_id as current_contract_id,
|
|
1811
|
+
latest_ocf.ocf_data,
|
|
1812
|
+
latest_ocf.type as ocf_type
|
|
1813
|
+
FROM canton_parties cp
|
|
1814
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
1815
|
+
INNER JOIN (
|
|
1816
|
+
SELECT o.portal_id, o.id, o.version, o.ocf_data, o.type
|
|
1817
|
+
FROM latest_ocf_objects o
|
|
1818
|
+
WHERE o.type = 'ISSUER'
|
|
1819
|
+
) latest_ocf ON latest_ocf.portal_id = cp.portal_id
|
|
1820
|
+
INNER JOIN (
|
|
1821
|
+
SELECT od.party_id, od.ocf_object_id, od.version, od.contract_id,
|
|
1822
|
+
ROW_NUMBER() OVER (PARTITION BY od.party_id ORDER BY od.version DESC) as rn
|
|
1823
|
+
FROM ocf_deployments od
|
|
1824
|
+
WHERE od.status = 'deployed'
|
|
1825
|
+
) latest_deployment ON latest_deployment.party_id = cp.party_id
|
|
1826
|
+
AND latest_deployment.rn = 1
|
|
1827
|
+
WHERE latest_ocf.version > latest_deployment.version
|
|
1828
|
+
ORDER BY cp.created_at ASC
|
|
1829
|
+
`;
|
|
1830
|
+
const result = await this.pool.query(query);
|
|
1831
|
+
return result.rows;
|
|
1832
|
+
}
|
|
1833
|
+
async getOnchainEquityValuations() {
|
|
1834
|
+
const query = `
|
|
1835
|
+
SELECT p.id AS portal_id,
|
|
1836
|
+
p.id AS company_id,
|
|
1837
|
+
p.company->>'name' AS company_name,
|
|
1838
|
+
CASE
|
|
1839
|
+
WHEN COALESCE((pp.company_data ->> 'company_custom_valuation')::numeric, 0) > COALESCE((pp.company_data ->> 'company_computed_valuation')::numeric, 0)
|
|
1840
|
+
THEN COALESCE((pp.company_data ->> 'company_custom_valuation')::numeric, 0)
|
|
1841
|
+
ELSE COALESCE((pp.company_data ->> 'company_computed_valuation')::numeric, 0)
|
|
1842
|
+
END AS company_valuation
|
|
1843
|
+
FROM portal p
|
|
1844
|
+
JOIN portal_private pp ON pp.portal_id = p.id
|
|
1845
|
+
WHERE (p.company ->> 'name') !~* 'Fairbnb'
|
|
1846
|
+
AND (
|
|
1847
|
+
p.captable_minted
|
|
1848
|
+
OR (
|
|
1849
|
+
p.domain !~* 'staging'
|
|
1850
|
+
AND p.domain !~* 'websitecf'
|
|
1851
|
+
AND p.domain !~* '.cafe'
|
|
1852
|
+
)
|
|
1853
|
+
)
|
|
1854
|
+
ORDER BY p.captable_minted,
|
|
1855
|
+
CASE
|
|
1856
|
+
WHEN COALESCE((pp.company_data ->> 'company_custom_valuation')::numeric, 0) > COALESCE((pp.company_data ->> 'company_computed_valuation')::numeric, 0)
|
|
1857
|
+
THEN COALESCE((pp.company_data ->> 'company_custom_valuation')::numeric, 0)
|
|
1858
|
+
ELSE COALESCE((pp.company_data ->> 'company_computed_valuation')::numeric, 0)
|
|
1859
|
+
END DESC
|
|
1860
|
+
`;
|
|
1861
|
+
const result = await this.pool.query(query);
|
|
1862
|
+
return result.rows.map(row => ({
|
|
1863
|
+
portal_id: row.portal_id,
|
|
1864
|
+
company_id: row.company_id,
|
|
1865
|
+
company_name: row.company_name ?? null,
|
|
1866
|
+
company_valuation: Number(row.company_valuation ?? 0),
|
|
1867
|
+
}));
|
|
1868
|
+
}
|
|
1869
|
+
async getPortalsNeedingStockClassDeployments() {
|
|
1870
|
+
const query = `
|
|
1871
|
+
SELECT DISTINCT
|
|
1872
|
+
cp.portal_id,
|
|
1873
|
+
cp.party_id,
|
|
1874
|
+
cp.provider,
|
|
1875
|
+
cp.created_at,
|
|
1876
|
+
p.company,
|
|
1877
|
+
loo.ocf_data,
|
|
1878
|
+
loo.id AS ocf_object_id,
|
|
1879
|
+
loo.version AS ocf_version,
|
|
1880
|
+
(
|
|
1881
|
+
SELECT od_issuer.contract_id
|
|
1882
|
+
FROM ocf_deployments od_issuer
|
|
1883
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
1884
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
1885
|
+
AND od_issuer.status = 'deployed'
|
|
1886
|
+
AND loo_issuer.type = 'ISSUER'
|
|
1887
|
+
ORDER BY od_issuer.created_at DESC
|
|
1888
|
+
LIMIT 1
|
|
1889
|
+
) as issuer_contract_id
|
|
1890
|
+
FROM canton_parties cp
|
|
1891
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
1892
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
1893
|
+
WHERE
|
|
1894
|
+
loo.type = 'STOCK_CLASS'
|
|
1895
|
+
AND EXISTS (
|
|
1896
|
+
SELECT 1 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
|
+
)
|
|
1902
|
+
AND NOT EXISTS (
|
|
1903
|
+
SELECT 1 FROM ocf_deployments od_sc
|
|
1904
|
+
WHERE od_sc.ocf_object_id = loo.id
|
|
1905
|
+
AND od_sc.party_id = cp.party_id
|
|
1906
|
+
AND od_sc.status = 'deployed'
|
|
1907
|
+
)
|
|
1908
|
+
ORDER BY cp.created_at ASC
|
|
1909
|
+
`;
|
|
1910
|
+
const result = await this.pool.query(query);
|
|
1911
|
+
return result.rows;
|
|
1912
|
+
}
|
|
1913
|
+
async getPortalsNeedingStockClassUpdates() {
|
|
1914
|
+
const query = `
|
|
1915
|
+
SELECT DISTINCT
|
|
1916
|
+
cp.portal_id,
|
|
1917
|
+
cp.party_id,
|
|
1918
|
+
cp.provider,
|
|
1919
|
+
cp.created_at,
|
|
1920
|
+
p.company,
|
|
1921
|
+
loo.ocf_data,
|
|
1922
|
+
loo.id AS ocf_object_id,
|
|
1923
|
+
loo.version as latest_ocf_version,
|
|
1924
|
+
od.version as deployed_version,
|
|
1925
|
+
od.contract_id as current_contract_id,
|
|
1926
|
+
(loo.version - od.version) AS version_diff,
|
|
1927
|
+
(
|
|
1928
|
+
SELECT od_issuer.contract_id
|
|
1929
|
+
FROM ocf_deployments od_issuer
|
|
1930
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
1931
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
1932
|
+
AND od_issuer.status = 'deployed'
|
|
1933
|
+
AND loo_issuer.type = 'ISSUER'
|
|
1934
|
+
ORDER BY od_issuer.created_at DESC
|
|
1935
|
+
LIMIT 1
|
|
1936
|
+
) as issuer_contract_id
|
|
1937
|
+
FROM canton_parties cp
|
|
1938
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
1939
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
1940
|
+
JOIN ocf_deployments od ON loo.id = od.ocf_object_id AND cp.party_id = od.party_id
|
|
1941
|
+
WHERE
|
|
1942
|
+
loo.type = 'STOCK_CLASS'
|
|
1943
|
+
AND EXISTS (
|
|
1944
|
+
SELECT 1 FROM ocf_deployments od_issuer
|
|
1945
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
1946
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
1947
|
+
AND od_issuer.status = 'deployed'
|
|
1948
|
+
AND loo_issuer.type = 'ISSUER'
|
|
1949
|
+
)
|
|
1950
|
+
AND od.version < loo.version
|
|
1951
|
+
AND od.status = 'deployed'
|
|
1952
|
+
ORDER BY version_diff DESC, cp.created_at ASC
|
|
1953
|
+
`;
|
|
1954
|
+
const result = await this.pool.query(query);
|
|
1955
|
+
return result.rows;
|
|
1956
|
+
}
|
|
1957
|
+
async getPortalsNeedingStakeholderDeployments() {
|
|
1958
|
+
const query = `
|
|
1959
|
+
SELECT DISTINCT
|
|
1960
|
+
cp.portal_id,
|
|
1961
|
+
cp.party_id,
|
|
1962
|
+
cp.provider,
|
|
1963
|
+
cp.created_at,
|
|
1964
|
+
p.company,
|
|
1965
|
+
loo.ocf_data,
|
|
1966
|
+
loo.id AS ocf_object_id,
|
|
1967
|
+
loo.version AS ocf_version,
|
|
1968
|
+
(
|
|
1969
|
+
SELECT od_issuer.contract_id
|
|
1970
|
+
FROM ocf_deployments od_issuer
|
|
1971
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
1972
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
1973
|
+
AND od_issuer.status = 'deployed'
|
|
1974
|
+
AND loo_issuer.type = 'ISSUER'
|
|
1975
|
+
ORDER BY od_issuer.created_at DESC
|
|
1976
|
+
LIMIT 1
|
|
1977
|
+
) as issuer_contract_id
|
|
1978
|
+
FROM canton_parties cp
|
|
1979
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
1980
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
1981
|
+
WHERE
|
|
1982
|
+
loo.type = 'STAKEHOLDER'
|
|
1983
|
+
AND EXISTS (
|
|
1984
|
+
SELECT 1 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
|
+
)
|
|
1990
|
+
AND NOT EXISTS (
|
|
1991
|
+
SELECT 1 FROM ocf_deployments od_sh
|
|
1992
|
+
WHERE od_sh.ocf_object_id = loo.id
|
|
1993
|
+
AND od_sh.party_id = cp.party_id
|
|
1994
|
+
AND od_sh.status = 'deployed'
|
|
1995
|
+
)
|
|
1996
|
+
ORDER BY cp.created_at ASC
|
|
1997
|
+
`;
|
|
1998
|
+
const result = await this.pool.query(query);
|
|
1999
|
+
return result.rows;
|
|
2000
|
+
}
|
|
2001
|
+
async getPortalsNeedingStakeholderReDeployments() {
|
|
2002
|
+
const query = `
|
|
2003
|
+
SELECT DISTINCT
|
|
2004
|
+
cp.portal_id,
|
|
2005
|
+
cp.party_id,
|
|
2006
|
+
cp.provider,
|
|
2007
|
+
cp.created_at,
|
|
2008
|
+
p.company,
|
|
2009
|
+
loo.ocf_data,
|
|
2010
|
+
loo.id AS ocf_object_id,
|
|
2011
|
+
loo.version as latest_ocf_version,
|
|
2012
|
+
od.version as deployed_version,
|
|
2013
|
+
od.contract_id as current_contract_id,
|
|
2014
|
+
(loo.version - od.version) AS version_diff,
|
|
2015
|
+
(
|
|
2016
|
+
SELECT od_issuer.contract_id
|
|
2017
|
+
FROM ocf_deployments od_issuer
|
|
2018
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2019
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2020
|
+
AND od_issuer.status = 'deployed'
|
|
2021
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2022
|
+
ORDER BY od_issuer.created_at DESC
|
|
2023
|
+
LIMIT 1
|
|
2024
|
+
) as issuer_contract_id
|
|
2025
|
+
FROM canton_parties cp
|
|
2026
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2027
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2028
|
+
JOIN ocf_deployments od ON loo.id = od.ocf_object_id AND cp.party_id = od.party_id
|
|
2029
|
+
WHERE
|
|
2030
|
+
loo.type = 'STAKEHOLDER'
|
|
2031
|
+
AND EXISTS (
|
|
2032
|
+
SELECT 1 FROM ocf_deployments od_issuer
|
|
2033
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2034
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2035
|
+
AND od_issuer.status = 'deployed'
|
|
2036
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2037
|
+
)
|
|
2038
|
+
AND od.version < loo.version
|
|
2039
|
+
AND od.status = 'deployed'
|
|
2040
|
+
ORDER BY version_diff DESC, cp.created_at ASC
|
|
2041
|
+
`;
|
|
2042
|
+
const result = await this.pool.query(query);
|
|
2043
|
+
return result.rows;
|
|
2044
|
+
}
|
|
2045
|
+
async getPortalsNeedingStockPlanDeployments() {
|
|
2046
|
+
const query = `
|
|
2047
|
+
SELECT DISTINCT
|
|
2048
|
+
cp.portal_id,
|
|
2049
|
+
cp.party_id,
|
|
2050
|
+
cp.provider,
|
|
2051
|
+
cp.created_at,
|
|
2052
|
+
p.company,
|
|
2053
|
+
loo.ocf_data,
|
|
2054
|
+
loo.id AS ocf_object_id,
|
|
2055
|
+
loo.version AS ocf_version,
|
|
2056
|
+
(
|
|
2057
|
+
SELECT od_issuer.contract_id
|
|
2058
|
+
FROM ocf_deployments od_issuer
|
|
2059
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2060
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2061
|
+
AND od_issuer.status = 'deployed'
|
|
2062
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2063
|
+
ORDER BY od_issuer.created_at DESC
|
|
2064
|
+
LIMIT 1
|
|
2065
|
+
) as issuer_contract_id
|
|
2066
|
+
FROM canton_parties cp
|
|
2067
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2068
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2069
|
+
WHERE
|
|
2070
|
+
loo.type = 'STOCK_PLAN'
|
|
2071
|
+
AND EXISTS (
|
|
2072
|
+
SELECT 1 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
|
+
)
|
|
2078
|
+
AND NOT EXISTS (
|
|
2079
|
+
SELECT 1 FROM ocf_deployments od_sp
|
|
2080
|
+
WHERE od_sp.ocf_object_id = loo.id
|
|
2081
|
+
AND od_sp.party_id = cp.party_id
|
|
2082
|
+
AND od_sp.status = 'deployed'
|
|
2083
|
+
)
|
|
2084
|
+
ORDER BY cp.created_at ASC
|
|
2085
|
+
`;
|
|
2086
|
+
const result = await this.pool.query(query);
|
|
2087
|
+
return result.rows;
|
|
2088
|
+
}
|
|
2089
|
+
async getPortalsNeedingStockPlanReDeployments() {
|
|
2090
|
+
const query = `
|
|
2091
|
+
SELECT DISTINCT
|
|
2092
|
+
cp.portal_id,
|
|
2093
|
+
cp.party_id,
|
|
2094
|
+
cp.provider,
|
|
2095
|
+
cp.created_at,
|
|
2096
|
+
p.company,
|
|
2097
|
+
loo.ocf_data,
|
|
2098
|
+
loo.id AS ocf_object_id,
|
|
2099
|
+
loo.version as latest_ocf_version,
|
|
2100
|
+
od.version as deployed_version,
|
|
2101
|
+
od.contract_id as current_contract_id,
|
|
2102
|
+
(loo.version - od.version) AS version_diff,
|
|
2103
|
+
(
|
|
2104
|
+
SELECT od_issuer.contract_id
|
|
2105
|
+
FROM ocf_deployments od_issuer
|
|
2106
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2107
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2108
|
+
AND od_issuer.status = 'deployed'
|
|
2109
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2110
|
+
ORDER BY od_issuer.created_at DESC
|
|
2111
|
+
LIMIT 1
|
|
2112
|
+
) as issuer_contract_id
|
|
2113
|
+
FROM canton_parties cp
|
|
2114
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2115
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2116
|
+
JOIN ocf_deployments od ON loo.id = od.ocf_object_id AND cp.party_id = od.party_id
|
|
2117
|
+
WHERE
|
|
2118
|
+
loo.type = 'STOCK_PLAN'
|
|
2119
|
+
AND EXISTS (
|
|
2120
|
+
SELECT 1 FROM ocf_deployments od_issuer
|
|
2121
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2122
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2123
|
+
AND od_issuer.status = 'deployed'
|
|
2124
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2125
|
+
)
|
|
2126
|
+
AND od.version < loo.version
|
|
2127
|
+
AND od.status = 'deployed'
|
|
2128
|
+
ORDER BY version_diff DESC, cp.created_at ASC
|
|
2129
|
+
`;
|
|
2130
|
+
const result = await this.pool.query(query);
|
|
2131
|
+
return result.rows;
|
|
2132
|
+
}
|
|
2133
|
+
async getPortalsNeedingStockLegendTemplateDeployments() {
|
|
2134
|
+
const query = `
|
|
2135
|
+
SELECT DISTINCT
|
|
2136
|
+
cp.portal_id,
|
|
2137
|
+
cp.party_id,
|
|
2138
|
+
cp.provider,
|
|
2139
|
+
cp.created_at,
|
|
2140
|
+
p.company,
|
|
2141
|
+
loo.ocf_data,
|
|
2142
|
+
loo.id AS ocf_object_id,
|
|
2143
|
+
loo.version AS ocf_version,
|
|
2144
|
+
(
|
|
2145
|
+
SELECT od_issuer.contract_id
|
|
2146
|
+
FROM ocf_deployments od_issuer
|
|
2147
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2148
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2149
|
+
AND od_issuer.status = 'deployed'
|
|
2150
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2151
|
+
ORDER BY od_issuer.created_at DESC
|
|
2152
|
+
LIMIT 1
|
|
2153
|
+
) as issuer_contract_id
|
|
2154
|
+
FROM canton_parties cp
|
|
2155
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2156
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2157
|
+
WHERE
|
|
2158
|
+
(loo.type = 'OBJECT' AND loo.subtype = 'STOCK_LEGEND_TEMPLATE')
|
|
2159
|
+
AND EXISTS (
|
|
2160
|
+
SELECT 1 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
|
+
)
|
|
2166
|
+
AND NOT EXISTS (
|
|
2167
|
+
SELECT 1 FROM ocf_deployments od
|
|
2168
|
+
WHERE od.ocf_object_id = loo.id
|
|
2169
|
+
AND od.party_id = cp.party_id
|
|
2170
|
+
AND od.status = 'deployed'
|
|
2171
|
+
)
|
|
2172
|
+
ORDER BY cp.created_at ASC
|
|
2173
|
+
`;
|
|
2174
|
+
const result = await this.pool.query(query);
|
|
2175
|
+
return result.rows;
|
|
2176
|
+
}
|
|
2177
|
+
async getPortalsNeedingDocumentDeployments() {
|
|
2178
|
+
const query = `
|
|
2179
|
+
SELECT DISTINCT
|
|
2180
|
+
cp.portal_id,
|
|
2181
|
+
cp.party_id,
|
|
2182
|
+
cp.provider,
|
|
2183
|
+
cp.created_at,
|
|
2184
|
+
p.company,
|
|
2185
|
+
loo.ocf_data,
|
|
2186
|
+
loo.id AS ocf_object_id,
|
|
2187
|
+
loo.version AS ocf_version,
|
|
2188
|
+
(
|
|
2189
|
+
SELECT od_issuer.contract_id
|
|
2190
|
+
FROM ocf_deployments od_issuer
|
|
2191
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2192
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2193
|
+
AND od_issuer.status = 'deployed'
|
|
2194
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2195
|
+
ORDER BY od_issuer.created_at DESC
|
|
2196
|
+
LIMIT 1
|
|
2197
|
+
) as issuer_contract_id
|
|
2198
|
+
FROM canton_parties cp
|
|
2199
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2200
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2201
|
+
WHERE
|
|
2202
|
+
(loo.type = 'OBJECT' AND loo.subtype = 'DOCUMENT')
|
|
2203
|
+
AND EXISTS (
|
|
2204
|
+
SELECT 1 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
|
+
)
|
|
2210
|
+
AND NOT EXISTS (
|
|
2211
|
+
SELECT 1 FROM ocf_deployments od
|
|
2212
|
+
WHERE od.ocf_object_id = loo.id
|
|
2213
|
+
AND od.party_id = cp.party_id
|
|
2214
|
+
AND od.status = 'deployed'
|
|
2215
|
+
)
|
|
2216
|
+
ORDER BY cp.created_at ASC
|
|
2217
|
+
`;
|
|
2218
|
+
const result = await this.pool.query(query);
|
|
2219
|
+
return result.rows;
|
|
2220
|
+
}
|
|
2221
|
+
async getPortalsNeedingVestingTermsDeployments() {
|
|
2222
|
+
const query = `
|
|
2223
|
+
SELECT DISTINCT
|
|
2224
|
+
cp.portal_id,
|
|
2225
|
+
cp.party_id,
|
|
2226
|
+
cp.provider,
|
|
2227
|
+
cp.created_at,
|
|
2228
|
+
p.company,
|
|
2229
|
+
loo.ocf_data,
|
|
2230
|
+
loo.id AS ocf_object_id,
|
|
2231
|
+
loo.version AS ocf_version,
|
|
2232
|
+
(
|
|
2233
|
+
SELECT od_issuer.contract_id
|
|
2234
|
+
FROM ocf_deployments od_issuer
|
|
2235
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2236
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2237
|
+
AND od_issuer.status = 'deployed'
|
|
2238
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2239
|
+
ORDER BY od_issuer.created_at DESC
|
|
2240
|
+
LIMIT 1
|
|
2241
|
+
) as issuer_contract_id
|
|
2242
|
+
FROM canton_parties cp
|
|
2243
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2244
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2245
|
+
WHERE
|
|
2246
|
+
(loo.type = 'OBJECT' AND loo.subtype = 'VESTING_TERMS')
|
|
2247
|
+
AND EXISTS (
|
|
2248
|
+
SELECT 1 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
|
+
)
|
|
2254
|
+
AND NOT EXISTS (
|
|
2255
|
+
SELECT 1 FROM ocf_deployments od
|
|
2256
|
+
WHERE od.ocf_object_id = loo.id
|
|
2257
|
+
AND od.party_id = cp.party_id
|
|
2258
|
+
AND od.status = 'deployed'
|
|
2259
|
+
)
|
|
2260
|
+
ORDER BY cp.created_at ASC
|
|
2261
|
+
`;
|
|
2262
|
+
const result = await this.pool.query(query);
|
|
2263
|
+
return result.rows;
|
|
2264
|
+
}
|
|
2265
|
+
async getPortalsNeedingStockIssuanceDeployments() {
|
|
2266
|
+
const query = `
|
|
2267
|
+
SELECT DISTINCT
|
|
2268
|
+
cp.portal_id,
|
|
2269
|
+
cp.party_id,
|
|
2270
|
+
cp.provider,
|
|
2271
|
+
cp.created_at,
|
|
2272
|
+
p.company,
|
|
2273
|
+
loo.ocf_data,
|
|
2274
|
+
loo.id AS ocf_object_id,
|
|
2275
|
+
loo.version AS ocf_version,
|
|
2276
|
+
(
|
|
2277
|
+
SELECT od_issuer.contract_id
|
|
2278
|
+
FROM ocf_deployments od_issuer
|
|
2279
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2280
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2281
|
+
AND od_issuer.status = 'deployed'
|
|
2282
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2283
|
+
ORDER BY od_issuer.created_at DESC
|
|
2284
|
+
LIMIT 1
|
|
2285
|
+
) as issuer_contract_id
|
|
2286
|
+
FROM canton_parties cp
|
|
2287
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2288
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2289
|
+
WHERE
|
|
2290
|
+
(loo.type = 'TRANSACTION' AND loo.subtype = 'TX_STOCK_ISSUANCE')
|
|
2291
|
+
AND EXISTS (
|
|
2292
|
+
SELECT 1 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
|
+
)
|
|
2298
|
+
AND NOT EXISTS (
|
|
2299
|
+
SELECT 1 FROM ocf_deployments od
|
|
2300
|
+
WHERE od.ocf_object_id = loo.id
|
|
2301
|
+
AND od.party_id = cp.party_id
|
|
2302
|
+
AND od.status = 'deployed'
|
|
2303
|
+
)
|
|
2304
|
+
ORDER BY cp.created_at ASC
|
|
2305
|
+
`;
|
|
2306
|
+
const result = await this.pool.query(query);
|
|
2307
|
+
return result.rows;
|
|
2308
|
+
}
|
|
2309
|
+
async getPortalsNeedingWarrantIssuanceDeployments() {
|
|
2310
|
+
const query = `
|
|
2311
|
+
SELECT DISTINCT
|
|
2312
|
+
cp.portal_id,
|
|
2313
|
+
cp.party_id,
|
|
2314
|
+
cp.provider,
|
|
2315
|
+
cp.created_at,
|
|
2316
|
+
p.company,
|
|
2317
|
+
loo.ocf_data,
|
|
2318
|
+
loo.id AS ocf_object_id,
|
|
2319
|
+
loo.version AS ocf_version,
|
|
2320
|
+
(
|
|
2321
|
+
SELECT od_issuer.contract_id
|
|
2322
|
+
FROM ocf_deployments od_issuer
|
|
2323
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2324
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2325
|
+
AND od_issuer.status = 'deployed'
|
|
2326
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2327
|
+
ORDER BY od_issuer.created_at DESC
|
|
2328
|
+
LIMIT 1
|
|
2329
|
+
) as issuer_contract_id
|
|
2330
|
+
FROM canton_parties cp
|
|
2331
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2332
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2333
|
+
WHERE
|
|
2334
|
+
(loo.type = 'TRANSACTION' AND loo.subtype = 'TX_WARRANT_ISSUANCE')
|
|
2335
|
+
AND EXISTS (
|
|
2336
|
+
SELECT 1 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
|
+
)
|
|
2342
|
+
AND NOT EXISTS (
|
|
2343
|
+
SELECT 1 FROM ocf_deployments od
|
|
2344
|
+
WHERE od.ocf_object_id = loo.id
|
|
2345
|
+
AND od.party_id = cp.party_id
|
|
2346
|
+
AND od.status = 'deployed'
|
|
2347
|
+
)
|
|
2348
|
+
ORDER BY cp.created_at ASC
|
|
2349
|
+
`;
|
|
2350
|
+
const result = await this.pool.query(query);
|
|
2351
|
+
return result.rows;
|
|
2352
|
+
}
|
|
2353
|
+
async getPortalsNeedingStockPlanPoolAdjustmentDeployments() {
|
|
2354
|
+
const query = `
|
|
2355
|
+
SELECT DISTINCT
|
|
2356
|
+
cp.portal_id,
|
|
2357
|
+
cp.party_id,
|
|
2358
|
+
cp.provider,
|
|
2359
|
+
cp.created_at,
|
|
2360
|
+
p.company,
|
|
2361
|
+
loo.ocf_data,
|
|
2362
|
+
loo.id AS ocf_object_id,
|
|
2363
|
+
loo.version AS ocf_version,
|
|
2364
|
+
(
|
|
2365
|
+
SELECT od_issuer.contract_id
|
|
2366
|
+
FROM ocf_deployments od_issuer
|
|
2367
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2368
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2369
|
+
AND od_issuer.status = 'deployed'
|
|
2370
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2371
|
+
ORDER BY od_issuer.created_at DESC
|
|
2372
|
+
LIMIT 1
|
|
2373
|
+
) as issuer_contract_id
|
|
2374
|
+
FROM canton_parties cp
|
|
2375
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2376
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2377
|
+
WHERE
|
|
2378
|
+
(loo.type = 'TRANSACTION' AND loo.subtype = 'TX_STOCK_PLAN_POOL_ADJUSTMENT')
|
|
2379
|
+
AND EXISTS (
|
|
2380
|
+
SELECT 1 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
|
+
)
|
|
2386
|
+
AND NOT EXISTS (
|
|
2387
|
+
SELECT 1 FROM ocf_deployments od
|
|
2388
|
+
WHERE od.ocf_object_id = loo.id
|
|
2389
|
+
AND od.party_id = cp.party_id
|
|
2390
|
+
AND od.status = 'deployed'
|
|
2391
|
+
)
|
|
2392
|
+
ORDER BY cp.created_at ASC
|
|
2393
|
+
`;
|
|
2394
|
+
const result = await this.pool.query(query);
|
|
2395
|
+
return result.rows;
|
|
2396
|
+
}
|
|
2397
|
+
async getPortalsNeedingStockClassAuthorizedSharesAdjustmentDeployments() {
|
|
2398
|
+
const query = `
|
|
2399
|
+
SELECT DISTINCT
|
|
2400
|
+
cp.portal_id,
|
|
2401
|
+
cp.party_id,
|
|
2402
|
+
cp.provider,
|
|
2403
|
+
cp.created_at,
|
|
2404
|
+
p.company,
|
|
2405
|
+
loo.ocf_data,
|
|
2406
|
+
loo.id AS ocf_object_id,
|
|
2407
|
+
loo.version AS ocf_version,
|
|
2408
|
+
(
|
|
2409
|
+
SELECT od_issuer.contract_id
|
|
2410
|
+
FROM ocf_deployments od_issuer
|
|
2411
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2412
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2413
|
+
AND od_issuer.status = 'deployed'
|
|
2414
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2415
|
+
ORDER BY od_issuer.created_at DESC
|
|
2416
|
+
LIMIT 1
|
|
2417
|
+
) as issuer_contract_id
|
|
2418
|
+
FROM canton_parties cp
|
|
2419
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2420
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2421
|
+
WHERE
|
|
2422
|
+
(loo.type = 'TRANSACTION' AND loo.subtype = 'TX_STOCK_CLASS_AUTHORIZED_SHARES_ADJUSTMENT')
|
|
2423
|
+
AND EXISTS (
|
|
2424
|
+
SELECT 1 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
|
+
)
|
|
2430
|
+
AND NOT EXISTS (
|
|
2431
|
+
SELECT 1 FROM ocf_deployments od
|
|
2432
|
+
WHERE od.ocf_object_id = loo.id
|
|
2433
|
+
AND od.party_id = cp.party_id
|
|
2434
|
+
AND od.status = 'deployed'
|
|
2435
|
+
)
|
|
2436
|
+
ORDER BY cp.created_at ASC
|
|
2437
|
+
`;
|
|
2438
|
+
const result = await this.pool.query(query);
|
|
2439
|
+
return result.rows;
|
|
2440
|
+
}
|
|
2441
|
+
async getPortalsNeedingStockCancellationDeployments() {
|
|
2442
|
+
const query = `
|
|
2443
|
+
SELECT DISTINCT
|
|
2444
|
+
cp.portal_id,
|
|
2445
|
+
cp.party_id,
|
|
2446
|
+
cp.provider,
|
|
2447
|
+
cp.created_at,
|
|
2448
|
+
p.company,
|
|
2449
|
+
loo.ocf_data,
|
|
2450
|
+
loo.id AS ocf_object_id,
|
|
2451
|
+
loo.version AS ocf_version,
|
|
2452
|
+
(
|
|
2453
|
+
SELECT od_issuer.contract_id
|
|
2454
|
+
FROM ocf_deployments od_issuer
|
|
2455
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2456
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2457
|
+
AND od_issuer.status = 'deployed'
|
|
2458
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2459
|
+
ORDER BY od_issuer.created_at DESC
|
|
2460
|
+
LIMIT 1
|
|
2461
|
+
) as issuer_contract_id
|
|
2462
|
+
FROM canton_parties cp
|
|
2463
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2464
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2465
|
+
WHERE
|
|
2466
|
+
(loo.type = 'TRANSACTION' AND loo.subtype = 'TX_STOCK_CANCELLATION')
|
|
2467
|
+
AND EXISTS (
|
|
2468
|
+
SELECT 1 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
|
+
)
|
|
2474
|
+
AND NOT EXISTS (
|
|
2475
|
+
SELECT 1 FROM ocf_deployments od
|
|
2476
|
+
WHERE od.ocf_object_id = loo.id
|
|
2477
|
+
AND od.party_id = cp.party_id
|
|
2478
|
+
AND od.status = 'deployed'
|
|
2479
|
+
)
|
|
2480
|
+
ORDER BY cp.created_at ASC
|
|
2481
|
+
`;
|
|
2482
|
+
const result = await this.pool.query(query);
|
|
2483
|
+
return result.rows;
|
|
2484
|
+
}
|
|
2485
|
+
async getPortalsNeedingIssuerAuthorizedSharesAdjustmentDeployments() {
|
|
2486
|
+
const query = `
|
|
2487
|
+
SELECT DISTINCT
|
|
2488
|
+
cp.portal_id,
|
|
2489
|
+
cp.party_id,
|
|
2490
|
+
cp.provider,
|
|
2491
|
+
cp.created_at,
|
|
2492
|
+
p.company,
|
|
2493
|
+
loo.ocf_data,
|
|
2494
|
+
loo.id AS ocf_object_id,
|
|
2495
|
+
loo.version AS ocf_version,
|
|
2496
|
+
(
|
|
2497
|
+
SELECT od_issuer.contract_id
|
|
2498
|
+
FROM ocf_deployments od_issuer
|
|
2499
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2500
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2501
|
+
AND od_issuer.status = 'deployed'
|
|
2502
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2503
|
+
ORDER BY od_issuer.created_at DESC
|
|
2504
|
+
LIMIT 1
|
|
2505
|
+
) as issuer_contract_id
|
|
2506
|
+
FROM canton_parties cp
|
|
2507
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2508
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2509
|
+
WHERE
|
|
2510
|
+
(loo.type = 'TRANSACTION' AND loo.subtype = 'TX_ISSUER_AUTHORIZED_SHARES_ADJUSTMENT')
|
|
2511
|
+
AND EXISTS (
|
|
2512
|
+
SELECT 1 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
|
+
)
|
|
2518
|
+
AND NOT EXISTS (
|
|
2519
|
+
SELECT 1 FROM ocf_deployments od
|
|
2520
|
+
WHERE od.ocf_object_id = loo.id
|
|
2521
|
+
AND od.party_id = cp.party_id
|
|
2522
|
+
AND od.status = 'deployed'
|
|
2523
|
+
)
|
|
2524
|
+
ORDER BY cp.created_at ASC
|
|
2525
|
+
`;
|
|
2526
|
+
const result = await this.pool.query(query);
|
|
2527
|
+
return result.rows;
|
|
2528
|
+
}
|
|
2529
|
+
async getPortalsNeedingEquityCompensationIssuanceDeployments() {
|
|
2530
|
+
const query = `
|
|
2531
|
+
SELECT DISTINCT
|
|
2532
|
+
cp.portal_id,
|
|
2533
|
+
cp.party_id,
|
|
2534
|
+
cp.provider,
|
|
2535
|
+
cp.created_at,
|
|
2536
|
+
p.company,
|
|
2537
|
+
loo.ocf_data,
|
|
2538
|
+
loo.id AS ocf_object_id,
|
|
2539
|
+
loo.version AS ocf_version,
|
|
2540
|
+
(
|
|
2541
|
+
SELECT od_issuer.contract_id
|
|
2542
|
+
FROM ocf_deployments od_issuer
|
|
2543
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2544
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2545
|
+
AND od_issuer.status = 'deployed'
|
|
2546
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2547
|
+
ORDER BY od_issuer.created_at DESC
|
|
2548
|
+
LIMIT 1
|
|
2549
|
+
) as issuer_contract_id
|
|
2550
|
+
FROM canton_parties cp
|
|
2551
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2552
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2553
|
+
WHERE
|
|
2554
|
+
(loo.type = 'TRANSACTION' AND loo.subtype = 'TX_EQUITY_COMPENSATION_ISSUANCE')
|
|
2555
|
+
AND EXISTS (
|
|
2556
|
+
SELECT 1 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
|
+
)
|
|
2562
|
+
AND NOT EXISTS (
|
|
2563
|
+
SELECT 1 FROM ocf_deployments od
|
|
2564
|
+
WHERE od.ocf_object_id = loo.id
|
|
2565
|
+
AND od.party_id = cp.party_id
|
|
2566
|
+
AND od.status = 'deployed'
|
|
2567
|
+
)
|
|
2568
|
+
ORDER BY cp.created_at ASC
|
|
2569
|
+
`;
|
|
2570
|
+
const result = await this.pool.query(query);
|
|
2571
|
+
return result.rows;
|
|
2572
|
+
}
|
|
2573
|
+
async getPortalsNeedingEquityCompensationExerciseDeployments() {
|
|
2574
|
+
const query = `
|
|
2575
|
+
SELECT DISTINCT
|
|
2576
|
+
cp.portal_id,
|
|
2577
|
+
cp.party_id,
|
|
2578
|
+
cp.provider,
|
|
2579
|
+
cp.created_at,
|
|
2580
|
+
p.company,
|
|
2581
|
+
loo.ocf_data,
|
|
2582
|
+
loo.id AS ocf_object_id,
|
|
2583
|
+
loo.version AS ocf_version,
|
|
2584
|
+
(
|
|
2585
|
+
SELECT od_issuer.contract_id
|
|
2586
|
+
FROM ocf_deployments od_issuer
|
|
2587
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2588
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2589
|
+
AND od_issuer.status = 'deployed'
|
|
2590
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2591
|
+
ORDER BY od_issuer.created_at DESC
|
|
2592
|
+
LIMIT 1
|
|
2593
|
+
) as issuer_contract_id
|
|
2594
|
+
FROM canton_parties cp
|
|
2595
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2596
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2597
|
+
WHERE
|
|
2598
|
+
(loo.type = 'TRANSACTION' AND loo.subtype = 'TX_EQUITY_COMPENSATION_EXERCISE')
|
|
2599
|
+
AND EXISTS (
|
|
2600
|
+
SELECT 1 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
|
+
)
|
|
2606
|
+
AND NOT EXISTS (
|
|
2607
|
+
SELECT 1 FROM ocf_deployments od
|
|
2608
|
+
WHERE od.ocf_object_id = loo.id
|
|
2609
|
+
AND od.party_id = cp.party_id
|
|
2610
|
+
AND od.status = 'deployed'
|
|
2611
|
+
)
|
|
2612
|
+
ORDER BY cp.created_at ASC
|
|
2613
|
+
`;
|
|
2614
|
+
const result = await this.pool.query(query);
|
|
2615
|
+
return result.rows;
|
|
2616
|
+
}
|
|
2617
|
+
async getPortalsNeedingConvertibleIssuanceDeployments() {
|
|
2618
|
+
const query = `
|
|
2619
|
+
SELECT DISTINCT
|
|
2620
|
+
cp.portal_id,
|
|
2621
|
+
cp.party_id,
|
|
2622
|
+
cp.provider,
|
|
2623
|
+
cp.created_at,
|
|
2624
|
+
p.company,
|
|
2625
|
+
loo.ocf_data,
|
|
2626
|
+
loo.id AS ocf_object_id,
|
|
2627
|
+
loo.version AS ocf_version,
|
|
2628
|
+
(
|
|
2629
|
+
SELECT od_issuer.contract_id
|
|
2630
|
+
FROM ocf_deployments od_issuer
|
|
2631
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2632
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2633
|
+
AND od_issuer.status = 'deployed'
|
|
2634
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2635
|
+
ORDER BY od_issuer.created_at DESC
|
|
2636
|
+
LIMIT 1
|
|
2637
|
+
) as issuer_contract_id
|
|
2638
|
+
FROM canton_parties cp
|
|
2639
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2640
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2641
|
+
WHERE
|
|
2642
|
+
(loo.type = 'TRANSACTION' AND loo.subtype = 'TX_CONVERTIBLE_ISSUANCE')
|
|
2643
|
+
AND EXISTS (
|
|
2644
|
+
SELECT 1 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
|
+
)
|
|
2650
|
+
AND NOT EXISTS (
|
|
2651
|
+
SELECT 1 FROM ocf_deployments od
|
|
2652
|
+
WHERE od.ocf_object_id = loo.id
|
|
2653
|
+
AND od.party_id = cp.party_id
|
|
2654
|
+
AND od.status = 'deployed'
|
|
2655
|
+
)
|
|
2656
|
+
ORDER BY cp.created_at ASC
|
|
2657
|
+
`;
|
|
2658
|
+
const result = await this.pool.query(query);
|
|
2659
|
+
return result.rows;
|
|
2660
|
+
}
|
|
2661
|
+
/**
|
|
2662
|
+
* Returns the next party_id that has any pending OCF work (missing deployments or updates), ordered by the associated
|
|
2663
|
+
* party creation time.
|
|
2664
|
+
*/
|
|
2665
|
+
async getNextPartyIdWithPendingOcfWork() {
|
|
2666
|
+
const query = `
|
|
2667
|
+
WITH eligible_party AS (
|
|
2668
|
+
SELECT DISTINCT cp.party_id, cp.created_at
|
|
2669
|
+
FROM canton_parties cp
|
|
2670
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2671
|
+
WHERE
|
|
2672
|
+
-- Issuer must be deployed for this party
|
|
2673
|
+
EXISTS (
|
|
2674
|
+
SELECT 1 FROM ocf_deployments od_issuer
|
|
2675
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2676
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2677
|
+
AND od_issuer.status = 'deployed'
|
|
2678
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2679
|
+
)
|
|
2680
|
+
AND (
|
|
2681
|
+
-- Missing for any object/transaction/record type (excluding ISSUER)
|
|
2682
|
+
(loo.type <> 'ISSUER' AND NOT EXISTS (
|
|
2683
|
+
SELECT 1 FROM ocf_deployments od
|
|
2684
|
+
WHERE od.ocf_object_id = loo.id
|
|
2685
|
+
AND od.party_id = cp.party_id
|
|
2686
|
+
AND od.status = 'deployed'
|
|
2687
|
+
))
|
|
2688
|
+
OR
|
|
2689
|
+
-- Updates for any type/subtype except ISSUER
|
|
2690
|
+
(
|
|
2691
|
+
loo.type <> 'ISSUER'
|
|
2692
|
+
AND EXISTS (
|
|
2693
|
+
SELECT 1 FROM ocf_deployments od2
|
|
2694
|
+
WHERE od2.ocf_object_id = loo.id
|
|
2695
|
+
AND od2.party_id = cp.party_id
|
|
2696
|
+
AND od2.status = 'deployed'
|
|
2697
|
+
AND od2.version < loo.version
|
|
2698
|
+
)
|
|
2699
|
+
)
|
|
2700
|
+
)
|
|
2701
|
+
)
|
|
2702
|
+
SELECT party_id
|
|
2703
|
+
FROM eligible_party
|
|
2704
|
+
ORDER BY created_at ASC
|
|
2705
|
+
LIMIT 1
|
|
2706
|
+
`;
|
|
2707
|
+
const result = await this.pool.query(query);
|
|
2708
|
+
if (result.rows.length === 0)
|
|
2709
|
+
return null;
|
|
2710
|
+
return result.rows[0].party_id;
|
|
2711
|
+
}
|
|
2712
|
+
/**
|
|
2713
|
+
* Returns all pending OCF items (missing or updates) for a given party in a single query. This unifies
|
|
2714
|
+
* objects/transactions and core records, and includes metadata needed by the script.
|
|
2715
|
+
*/
|
|
2716
|
+
async getPendingOcfItemsForParty(partyId) {
|
|
2717
|
+
const query = `
|
|
2718
|
+
(
|
|
2719
|
+
-- Missing deployments across all OCF types/subtypes
|
|
2720
|
+
SELECT DISTINCT
|
|
2721
|
+
cp.portal_id,
|
|
2722
|
+
cp.party_id,
|
|
2723
|
+
cp.provider,
|
|
2724
|
+
p.company,
|
|
2725
|
+
loo.id AS ocf_object_id,
|
|
2726
|
+
loo.ocf_data,
|
|
2727
|
+
loo.version AS ocf_version,
|
|
2728
|
+
NULL::int AS latest_ocf_version,
|
|
2729
|
+
NULL::int AS deployed_version,
|
|
2730
|
+
NULL::text AS current_contract_id,
|
|
2731
|
+
loo.type AS ocf_type,
|
|
2732
|
+
loo.subtype AS ocf_subtype,
|
|
2733
|
+
(
|
|
2734
|
+
SELECT od_issuer.contract_id
|
|
2735
|
+
FROM ocf_deployments od_issuer
|
|
2736
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2737
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2738
|
+
AND od_issuer.status = 'deployed'
|
|
2739
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2740
|
+
ORDER BY od_issuer.created_at DESC
|
|
2741
|
+
LIMIT 1
|
|
2742
|
+
) AS issuer_contract_id,
|
|
2743
|
+
cp.created_at
|
|
2744
|
+
FROM canton_parties cp
|
|
2745
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2746
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2747
|
+
WHERE cp.party_id = $1
|
|
2748
|
+
AND EXISTS (
|
|
2749
|
+
SELECT 1 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
|
+
)
|
|
2755
|
+
AND NOT EXISTS (
|
|
2756
|
+
SELECT 1 FROM ocf_deployments od
|
|
2757
|
+
WHERE od.ocf_object_id = loo.id
|
|
2758
|
+
AND od.party_id = cp.party_id
|
|
2759
|
+
AND od.status = 'deployed'
|
|
2760
|
+
)
|
|
2761
|
+
)
|
|
2762
|
+
UNION ALL
|
|
2763
|
+
(
|
|
2764
|
+
-- Updates for core record types
|
|
2765
|
+
SELECT DISTINCT
|
|
2766
|
+
cp.portal_id,
|
|
2767
|
+
cp.party_id,
|
|
2768
|
+
cp.provider,
|
|
2769
|
+
p.company,
|
|
2770
|
+
loo.id AS ocf_object_id,
|
|
2771
|
+
loo.ocf_data,
|
|
2772
|
+
NULL::int AS ocf_version,
|
|
2773
|
+
loo.version AS latest_ocf_version,
|
|
2774
|
+
od.version AS deployed_version,
|
|
2775
|
+
od.contract_id AS current_contract_id,
|
|
2776
|
+
loo.type AS ocf_type,
|
|
2777
|
+
loo.subtype AS ocf_subtype,
|
|
2778
|
+
(
|
|
2779
|
+
SELECT od_issuer.contract_id
|
|
2780
|
+
FROM ocf_deployments od_issuer
|
|
2781
|
+
JOIN latest_ocf_objects loo_issuer ON od_issuer.ocf_object_id = loo_issuer.id
|
|
2782
|
+
WHERE od_issuer.party_id = cp.party_id
|
|
2783
|
+
AND od_issuer.status = 'deployed'
|
|
2784
|
+
AND loo_issuer.type = 'ISSUER'
|
|
2785
|
+
ORDER BY od_issuer.created_at DESC
|
|
2786
|
+
LIMIT 1
|
|
2787
|
+
) AS issuer_contract_id,
|
|
2788
|
+
cp.created_at
|
|
2789
|
+
FROM canton_parties cp
|
|
2790
|
+
LEFT JOIN portal p ON cp.portal_id = p.id
|
|
2791
|
+
JOIN latest_ocf_objects loo ON cp.portal_id = loo.portal_id
|
|
2792
|
+
JOIN ocf_deployments od ON loo.id = od.ocf_object_id AND cp.party_id = od.party_id
|
|
2793
|
+
WHERE cp.party_id = $1
|
|
2794
|
+
AND loo.type <> 'ISSUER'
|
|
2795
|
+
AND od.status = 'deployed'
|
|
2796
|
+
AND od.version < loo.version
|
|
2797
|
+
)
|
|
2798
|
+
ORDER BY created_at ASC
|
|
2799
|
+
`;
|
|
2800
|
+
const result = await this.pool.query(query, [partyId]);
|
|
2801
|
+
return result.rows;
|
|
2802
|
+
}
|
|
2803
|
+
async getLatestValuationReportByPortalId(portalId) {
|
|
2804
|
+
const query = `
|
|
2805
|
+
SELECT *
|
|
2806
|
+
FROM canton_valuation_reports
|
|
2807
|
+
WHERE portal_id = $1
|
|
2808
|
+
ORDER BY version DESC
|
|
2809
|
+
LIMIT 1
|
|
2810
|
+
`;
|
|
2811
|
+
const result = await this.pool.query(query, [portalId]);
|
|
2812
|
+
if (result.rows.length === 0)
|
|
2813
|
+
return null;
|
|
2814
|
+
return this.mapValuationReportFromDb(result.rows[0]);
|
|
2815
|
+
}
|
|
2816
|
+
/** Get the latest valuation report row for each distinct company_id */
|
|
2817
|
+
async getLatestValuationReportsGroupedByCompany() {
|
|
2818
|
+
const query = `
|
|
2819
|
+
SELECT DISTINCT ON (company_id)
|
|
2820
|
+
id, portal_id, company_id, version, contract_id, company_valuation,
|
|
2821
|
+
tx_update_id, last_valuation_until, created_at, updated_at
|
|
2822
|
+
FROM canton_valuation_reports
|
|
2823
|
+
ORDER BY company_id, version DESC
|
|
2824
|
+
`;
|
|
2825
|
+
const result = await this.pool.query(query);
|
|
2826
|
+
return result.rows.map(row => this.mapValuationReportFromDb(row));
|
|
2827
|
+
}
|
|
2828
|
+
async insertValuationReport(report) {
|
|
2829
|
+
const query = `
|
|
2830
|
+
INSERT INTO canton_valuation_reports (
|
|
2831
|
+
portal_id, company_id, version, contract_id, company_valuation, tx_update_id, last_valuation_until
|
|
2832
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
2833
|
+
RETURNING *
|
|
2834
|
+
`;
|
|
2835
|
+
const values = [
|
|
2836
|
+
report.portal_id,
|
|
2837
|
+
report.company_id,
|
|
2838
|
+
report.version,
|
|
2839
|
+
report.contract_id,
|
|
2840
|
+
report.company_valuation,
|
|
2841
|
+
report.tx_update_id,
|
|
2842
|
+
report.last_valuation_until,
|
|
2843
|
+
];
|
|
2844
|
+
const result = await this.pool.query(query, values);
|
|
2845
|
+
return this.mapValuationReportFromDb(result.rows[0]);
|
|
2846
|
+
}
|
|
2847
|
+
// Helper methods for mapping database results
|
|
2848
|
+
mapTransferFromDb(row) {
|
|
2849
|
+
return {
|
|
2850
|
+
id: row.id,
|
|
2851
|
+
api_tracking_id: row.api_tracking_id,
|
|
2852
|
+
api_expires_at: row.api_expires_at,
|
|
2853
|
+
transfer_sender_party_id: row.transfer_sender_party_id,
|
|
2854
|
+
transfer_receiver_party_id: row.transfer_receiver_party_id,
|
|
2855
|
+
transfer_amount: parseFloat(row.transfer_amount),
|
|
2856
|
+
transfer_description: row.transfer_description,
|
|
2857
|
+
transfer_burned_amount: parseFloat(row.transfer_burned_amount),
|
|
2858
|
+
receiver_holding_cids: row.receiver_holding_cids,
|
|
2859
|
+
sender_change_cids: row.sender_change_cids,
|
|
2860
|
+
tx_update_id: row.tx_update_id,
|
|
2861
|
+
tx_record_time: row.tx_record_time,
|
|
2862
|
+
status: row.status,
|
|
2863
|
+
app_reward_coupon_id: row.app_reward_coupon_id,
|
|
2864
|
+
created_at: row.created_at,
|
|
2865
|
+
updated_at: row.updated_at,
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
mapRewardCouponFromDb(row) {
|
|
2869
|
+
return {
|
|
2870
|
+
id: row.id,
|
|
2871
|
+
status: row.status,
|
|
2872
|
+
tx_update_id: row.tx_update_id,
|
|
2873
|
+
tx_record_time: row.tx_record_time,
|
|
2874
|
+
contract_id: row.contract_id,
|
|
2875
|
+
template_id: row.template_id,
|
|
2876
|
+
package_name: row.package_name,
|
|
2877
|
+
dso_party_id: row.dso_party_id,
|
|
2878
|
+
provider_party_id: row.provider_party_id,
|
|
2879
|
+
featured: row.featured,
|
|
2880
|
+
round_number: row.round_number,
|
|
2881
|
+
beneficiary_party_id: row.beneficiary_party_id,
|
|
2882
|
+
coupon_amount: parseFloat(row.coupon_amount),
|
|
2883
|
+
tx_archive_update_id: row.tx_archive_update_id,
|
|
2884
|
+
tx_archive_record_time: row.tx_archive_record_time,
|
|
2885
|
+
app_reward_amount: row.app_reward_amount
|
|
2886
|
+
? parseFloat(row.app_reward_amount)
|
|
2887
|
+
: null,
|
|
2888
|
+
created_at: row.created_at,
|
|
2889
|
+
updated_at: row.updated_at,
|
|
2890
|
+
};
|
|
2891
|
+
}
|
|
2892
|
+
mapAppMarkerFromDb(row) {
|
|
2893
|
+
return {
|
|
2894
|
+
id: row.id,
|
|
2895
|
+
status: row.status,
|
|
2896
|
+
contract_id: row.contract_id,
|
|
2897
|
+
provider_party_id: row.provider_party_id,
|
|
2898
|
+
beneficiary_party_id: row.beneficiary_party_id,
|
|
2899
|
+
tx_record_time: row.tx_record_time,
|
|
2900
|
+
weight: parseFloat(row.weight),
|
|
2901
|
+
created_at: row.created_at,
|
|
2902
|
+
updated_at: row.updated_at,
|
|
2903
|
+
};
|
|
2904
|
+
}
|
|
2905
|
+
mapRedemptionFromDb(row) {
|
|
2906
|
+
return {
|
|
2907
|
+
id: row.id,
|
|
2908
|
+
transfer_id: row.transfer_id,
|
|
2909
|
+
app_reward_coupon_ids: row.app_reward_coupon_ids,
|
|
2910
|
+
tx_update_id: row.tx_update_id,
|
|
2911
|
+
tx_record_time: row.tx_record_time,
|
|
2912
|
+
tx_synchronizer_id: row.tx_synchronizer_id,
|
|
2913
|
+
tx_effective_at: row.tx_effective_at,
|
|
2914
|
+
total_app_rewards: parseFloat(row.total_app_rewards),
|
|
2915
|
+
created_at: row.created_at,
|
|
2916
|
+
updated_at: row.updated_at,
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
mapRewardCouponWithTransferFromDb(row) {
|
|
2920
|
+
const coupon = {
|
|
2921
|
+
id: row.coupon_id,
|
|
2922
|
+
status: row.coupon_status,
|
|
2923
|
+
tx_update_id: row.coupon_tx_update_id,
|
|
2924
|
+
tx_record_time: row.coupon_tx_record_time,
|
|
2925
|
+
contract_id: row.coupon_contract_id,
|
|
2926
|
+
template_id: row.coupon_template_id,
|
|
2927
|
+
package_name: row.coupon_package_name,
|
|
2928
|
+
dso_party_id: row.coupon_dso_party_id,
|
|
2929
|
+
provider_party_id: row.coupon_provider_party_id,
|
|
2930
|
+
featured: row.coupon_featured,
|
|
2931
|
+
round_number: row.coupon_round_number,
|
|
2932
|
+
beneficiary_party_id: row.coupon_beneficiary_party_id,
|
|
2933
|
+
coupon_amount: parseFloat(row.coupon_coupon_amount),
|
|
2934
|
+
tx_archive_update_id: row.coupon_tx_archive_update_id,
|
|
2935
|
+
tx_archive_record_time: row.coupon_tx_archive_record_time,
|
|
2936
|
+
app_reward_amount: row.coupon_app_reward_amount
|
|
2937
|
+
? parseFloat(row.coupon_app_reward_amount)
|
|
2938
|
+
: null,
|
|
2939
|
+
created_at: row.coupon_created_at,
|
|
2940
|
+
updated_at: row.coupon_updated_at,
|
|
2941
|
+
};
|
|
2942
|
+
const transfer = {
|
|
2943
|
+
id: row.transfer_id,
|
|
2944
|
+
api_tracking_id: row.transfer_api_tracking_id,
|
|
2945
|
+
api_expires_at: row.transfer_api_expires_at,
|
|
2946
|
+
transfer_sender_party_id: row.transfer_transfer_sender_party_id,
|
|
2947
|
+
transfer_receiver_party_id: row.transfer_transfer_receiver_party_id,
|
|
2948
|
+
transfer_amount: parseFloat(row.transfer_transfer_amount),
|
|
2949
|
+
transfer_description: row.transfer_transfer_description,
|
|
2950
|
+
transfer_burned_amount: parseFloat(row.transfer_transfer_burned_amount),
|
|
2951
|
+
receiver_holding_cids: row.transfer_receiver_holding_cids,
|
|
2952
|
+
sender_change_cids: row.transfer_sender_change_cids,
|
|
2953
|
+
tx_update_id: row.transfer_tx_update_id,
|
|
2954
|
+
tx_record_time: row.transfer_tx_record_time,
|
|
2955
|
+
status: row.transfer_status,
|
|
2956
|
+
app_reward_coupon_id: row.transfer_app_reward_coupon_id,
|
|
2957
|
+
created_at: row.transfer_created_at,
|
|
2958
|
+
updated_at: row.transfer_updated_at,
|
|
2959
|
+
};
|
|
2960
|
+
const redemption = row.redemption_id
|
|
2961
|
+
? {
|
|
2962
|
+
id: row.redemption_id,
|
|
2963
|
+
transfer_id: row.transfer_id,
|
|
2964
|
+
app_reward_coupon_ids: [], // This would need to be fetched separately if needed
|
|
2965
|
+
tx_update_id: row.redemption_tx_update_id,
|
|
2966
|
+
tx_record_time: row.redemption_tx_record_time,
|
|
2967
|
+
tx_synchronizer_id: row.redemption_tx_synchronizer_id,
|
|
2968
|
+
tx_effective_at: row.redemption_tx_effective_at,
|
|
2969
|
+
total_app_rewards: parseFloat(row.total_app_rewards),
|
|
2970
|
+
created_at: row.redemption_created_at,
|
|
2971
|
+
updated_at: row.redemption_updated_at,
|
|
2972
|
+
}
|
|
2973
|
+
: null;
|
|
2974
|
+
return {
|
|
2975
|
+
...coupon,
|
|
2976
|
+
transfer,
|
|
2977
|
+
redemption,
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2980
|
+
mapPartyFromDb(row) {
|
|
2981
|
+
return {
|
|
2982
|
+
id: row.id,
|
|
2983
|
+
party_id: row.party_id,
|
|
2984
|
+
portal_id: row.portal_id,
|
|
2985
|
+
provider: row.provider,
|
|
2986
|
+
last_payment_until: row.last_payment_until,
|
|
2987
|
+
is_active_customer_since: row.is_active_customer_since,
|
|
2988
|
+
created_at: row.created_at,
|
|
2989
|
+
updated_at: row.updated_at,
|
|
2990
|
+
portal: row.company
|
|
2991
|
+
? {
|
|
2992
|
+
id: row.portal_id,
|
|
2993
|
+
company: row.company,
|
|
2994
|
+
}
|
|
2995
|
+
: null,
|
|
2996
|
+
};
|
|
2997
|
+
}
|
|
2998
|
+
toSnakeCase(str) {
|
|
2999
|
+
return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
|
|
3000
|
+
}
|
|
3001
|
+
/**
|
|
3002
|
+
* Get historical issuance rates for specific rounds from the scan_acs_store table This queries ClosedMiningRound
|
|
3003
|
+
* contracts to get issuance rates for old rounds
|
|
3004
|
+
*
|
|
3005
|
+
* @param roundNumbers - Array of round numbers to get issuance rates for
|
|
3006
|
+
* @returns Array of issuance rates for the specified rounds
|
|
3007
|
+
*/
|
|
3008
|
+
async getHistoricalIssuanceRates(roundNumbers) {
|
|
3009
|
+
if (roundNumbers.length === 0) {
|
|
3010
|
+
return [];
|
|
3011
|
+
}
|
|
3012
|
+
// Query the scan_acs_store table for ClosedMiningRound contracts
|
|
3013
|
+
// The template_id_qualified_name should match the ClosedMiningRound template
|
|
3014
|
+
const query = `
|
|
3015
|
+
SELECT
|
|
3016
|
+
round,
|
|
3017
|
+
create_arguments
|
|
3018
|
+
FROM scan_acs_store
|
|
3019
|
+
WHERE template_id_qualified_name LIKE '%:Splice.Round:ClosedMiningRound'
|
|
3020
|
+
AND round = ANY($1)
|
|
3021
|
+
AND create_arguments IS NOT NULL
|
|
3022
|
+
ORDER BY round ASC
|
|
3023
|
+
`;
|
|
3024
|
+
try {
|
|
3025
|
+
const result = await this.pool.query(query, [roundNumbers]);
|
|
3026
|
+
return result.rows.map(row => {
|
|
3027
|
+
const payload = row.create_arguments;
|
|
3028
|
+
const roundNumber = parseInt(row.round, 10);
|
|
3029
|
+
// Extract issuance rates from the contract payload
|
|
3030
|
+
// The payload is stored as JSON and contains the issuance rate fields
|
|
3031
|
+
const issuancePerFeaturedAppRewardCoupon = parseFloat(payload.issuancePerFeaturedAppRewardCoupon ?? '0');
|
|
3032
|
+
const issuancePerUnfeaturedAppRewardCoupon = parseFloat(payload.issuancePerUnfeaturedAppRewardCoupon ?? '0');
|
|
3033
|
+
return {
|
|
3034
|
+
roundNumber,
|
|
3035
|
+
issuancePerFeaturedAppRewardCoupon,
|
|
3036
|
+
issuancePerUnfeaturedAppRewardCoupon,
|
|
3037
|
+
};
|
|
3038
|
+
});
|
|
3039
|
+
}
|
|
3040
|
+
catch (error) {
|
|
3041
|
+
console.error('Error querying historical issuance rates:', error);
|
|
3042
|
+
throw new Error(`Failed to get historical issuance rates: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
mapValuationReportFromDb(row) {
|
|
3046
|
+
return {
|
|
3047
|
+
id: row.id,
|
|
3048
|
+
portal_id: row.portal_id,
|
|
3049
|
+
company_id: row.company_id,
|
|
3050
|
+
version: row.version,
|
|
3051
|
+
contract_id: row.contract_id,
|
|
3052
|
+
company_valuation: Number(row.company_valuation),
|
|
3053
|
+
tx_update_id: row.tx_update_id,
|
|
3054
|
+
last_valuation_until: row.last_valuation_until,
|
|
3055
|
+
created_at: row.created_at,
|
|
3056
|
+
updated_at: row.updated_at,
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
3059
|
+
/**
|
|
3060
|
+
* Debug method to execute custom queries for investigation
|
|
3061
|
+
*
|
|
3062
|
+
* @param query - SQL query to execute
|
|
3063
|
+
* @param params - Query parameters
|
|
3064
|
+
* @returns Query result
|
|
3065
|
+
*/
|
|
3066
|
+
async debugQuery(query, params = []) {
|
|
3067
|
+
try {
|
|
3068
|
+
const result = await this.pool.query(query, params);
|
|
3069
|
+
return result.rows;
|
|
3070
|
+
}
|
|
3071
|
+
catch (error) {
|
|
3072
|
+
console.error('Error executing debug query:', error);
|
|
3073
|
+
throw new Error(`Failed to execute debug query: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
exports.FairmintDbClient = FairmintDbClient;
|
|
3078
|
+
//# sourceMappingURL=fairmintDbClient.js.map
|