@better-giving/endowment 2.0.6 → 3.0.0

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/src/db.mts CHANGED
@@ -1,151 +1,441 @@
1
- import type { Ensure } from "@better-giving/types/utils";
1
+ import {
2
+ DeleteCommand,
3
+ GetCommand,
4
+ PutCommand,
5
+ QueryCommand,
6
+ TransactWriteCommand,
7
+ UpdateCommand,
8
+ } from "@aws-sdk/lib-dynamodb";
9
+ import { Db, Txs, UpdateBuilder } from "@better-giving/db";
10
+ import KSUID from "ksuid";
2
11
  import type {
3
- Endowment as EndowmentShape,
4
- Environment,
5
- MediaId,
6
- MediaType,
7
- MilestoneId,
8
- Milestone as MilestoneShape,
9
- ProgramId,
10
- Program as ProgramShape,
11
- } from "./schema.mjs";
12
- export type {
13
- SocialMediaURLs,
14
- Allocation,
15
- Increment,
16
- EndowDesignation,
17
- Environment,
18
- ProgramId,
12
+ IMedia,
13
+ IMediaDb,
14
+ IMediaPage,
15
+ INpoReferredBy,
16
+ INpoWithRegNum,
17
+ INpoWithRid,
18
+ TBinFlag,
19
+ TNpoDbKeys,
20
+ TNpoDbProjectedTo,
21
+ } from "./interfaces.mjs";
22
+ import { med_key_filter, med_sk, to_imedia } from "./media.mjs";
23
+ import { projection } from "./npo.mjs";
24
+ import type {
25
+ IMediaSearchObj,
26
+ IMilestone,
27
+ IMilestoneUpdate,
28
+ INpo,
29
+ INpoUpdate,
30
+ IProgram,
31
+ IProgramDb,
32
+ IProgramNew,
33
+ IProgramUpdate,
34
+ TMediaType,
19
35
  } from "./schema.mjs";
20
36
 
21
- export const endowGsi = {
22
- regnumEnv: "regnum-env-gsi",
23
- slugEnv: "slug-env-gsi",
24
- } as const;
37
+ export class NpoDb extends Db {
38
+ static override readonly name = "endowments_v3";
39
+ static readonly slug_env_gsi = "slug-env-gsi" as const;
40
+ static readonly regnum_env_gsi = "regnum-env-gsi" as const;
41
+ key_npo(id: number) {
42
+ return {
43
+ PK: `Endow#${id}`,
44
+ SK: this.env,
45
+ };
46
+ }
47
+ get key_count() {
48
+ return {
49
+ PK: "Count",
50
+ SK: this.env,
51
+ };
52
+ }
53
+ key_npo_program(id: string, npo: number) {
54
+ return {
55
+ PK: `Endow#${npo}#${this.env}`,
56
+ SK: `Prog#${id}`,
57
+ };
58
+ }
25
59
 
26
- export namespace EndowCount {
27
- export interface Keys {
28
- PK: "Count";
29
- SK: Environment;
60
+ key_prog_milestone(id: string, prog: string) {
61
+ return {
62
+ PK: `Prog#${prog}#${this.env}`,
63
+ SK: `Mile#${id}`,
64
+ };
30
65
  }
31
- export interface NonKeyAttributes {
32
- count: number;
66
+
67
+ key_npo_med(ksuid: string, npo: number) {
68
+ return {
69
+ PK: `Endow#${npo}#${this.env}`,
70
+ SK: `Media#${ksuid}`,
71
+ };
33
72
  }
34
- }
35
73
 
36
- export namespace Endow {
37
- export type Keys = {
38
- PK: `Endow#${number}`;
39
- SK: Environment;
40
- };
41
-
42
- export type NonKeyAttributes = EndowmentShape & {
43
- /** referral id */
44
- gsi1PK?: `ReferredBy#${string}`;
45
- /** expiry / date onboarded */
46
- gsi1SK?: string;
47
- /** Rid#referral_id */
48
- gsi2PK?: `Rid#${string}`;
49
- gsi2SK?: `Rid#${string}`;
50
-
51
- /** will only be present for unclaimed NPOs */
52
- updated_at_auto?: string;
53
- /** in USD @deprecated */
54
- payout_minimum?: number;
55
- /** @deprecated */
56
- splitLiqPct?: number;
57
- /** @deprecated */
58
- splitFixed?: boolean;
59
- /** @deprecated */
60
- sfCompounded?: boolean;
61
- };
62
-
63
- export interface DbRecord extends Keys, NonKeyAttributes {}
64
- }
74
+ gsi1_npo(id: string) {
75
+ return {
76
+ gsi1PK: `ReferredBy#${id}`,
77
+ gsi1SK: this.env,
78
+ };
79
+ }
65
80
 
66
- export namespace RegNumEnvGsi {
67
- export type Name = typeof endowGsi.regnumEnv;
68
- export interface Keys
69
- extends Pick<Endow.NonKeyAttributes, "registration_number" | "env"> {}
70
- export interface DbRecord
71
- extends Keys,
72
- Endow.Keys,
73
- Pick<Endow.NonKeyAttributes, "claimed" | "name" | "hq_country" | "id"> {}
74
- }
81
+ gsi2_npo(id: string) {
82
+ return {
83
+ gsi2PK: `Rid#${id}`,
84
+ gsi2SK: `Rid#${id}`,
85
+ };
86
+ }
75
87
 
76
- export namespace SlugEnvGsi {
77
- export type Name = typeof endowGsi.slugEnv;
78
- export interface Keys extends Pick<Endow.NonKeyAttributes, "slug" | "env"> {}
79
- export interface DbRecord extends Endow.DbRecord {} //all attributes are copied to this index
80
- }
88
+ gsi1_npo_med(id: string, npo: number, featured: TBinFlag, type: TMediaType) {
89
+ return {
90
+ gsi1PK: this.key_npo_med(id, npo).PK,
91
+ gsi1SK: med_sk(id, type, featured),
92
+ };
93
+ }
94
+ async npo_media(npo: number, opts: IMediaSearchObj): Promise<IMediaPage> {
95
+ const PK: string = this.key_npo_med("ksuid-sk", npo).PK;
96
+ const [expression, values] = med_key_filter(PK, opts);
97
+
98
+ const cmd = new QueryCommand({
99
+ TableName: NpoDb.name,
100
+ IndexName: "gsi1",
101
+ Limit: opts.limit,
102
+ KeyConditionExpression: expression,
103
+ ExpressionAttributeValues: values,
104
+ ExclusiveStartKey: this.key_to_obj(opts.next),
105
+ });
81
106
 
82
- export namespace Program {
83
- export interface Keys {
84
- PK: `Endow#${number}#${Environment}`;
85
- SK: `Prog#${ProgramId}`;
107
+ const res = await this.client.send(cmd);
108
+ const page = this.to_page(res, to_imedia);
109
+ return page;
86
110
  }
87
111
 
88
- export interface NonKeyAttributes extends ProgramShape {}
89
- export interface DbRecord extends Keys, NonKeyAttributes {}
90
- }
112
+ async npo_referred_by(id: string): Promise<INpoReferredBy[]> {
113
+ const cmd = new QueryCommand({
114
+ TableName: NpoDb.name,
115
+ IndexName: "gsi1",
116
+ KeyConditionExpression: "#pk = :pk",
117
+ ExpressionAttributeValues: {
118
+ ":pk": this.gsi1_npo(id).gsi1PK,
119
+ },
120
+ ExpressionAttributeNames: { "#pk": "gsi1PK" },
121
+ });
122
+ const { Items = [] } = await this.client.send(cmd);
123
+ return Items.map((x) => this.sans_keys(x));
124
+ }
91
125
 
92
- export namespace Milestone {
93
- export interface Keys {
94
- PK: `Prog#${string}#${Environment}`;
95
- SK: `Mile#${MilestoneId}`;
126
+ async npo_with_rid(id: string): Promise<INpoWithRid | undefined> {
127
+ const cmd = new QueryCommand({
128
+ TableName: NpoDb.name,
129
+ IndexName: "gsi2",
130
+ KeyConditionExpression: "gsi2PK = :pk",
131
+ ExpressionAttributeValues: {
132
+ ":pk": this.gsi2_npo(id).gsi2PK,
133
+ },
134
+ Limit: 1,
135
+ });
136
+ const { Items = [] } = await this.client.send(cmd);
137
+ const i = Items[0];
138
+ return i && this.sans_keys(i);
96
139
  }
97
- export interface DbRecord extends Keys, MilestoneShape {}
98
- }
99
140
 
100
- export namespace Media {
101
- export interface Keys {
102
- PK: `Endow#${number}#${Environment}`;
103
- SK: `Media#${MediaId}`;
141
+ async npo_with_regnum(
142
+ regnum: string,
143
+ country = "United States"
144
+ ): Promise<INpoWithRegNum | undefined> {
145
+ const cmd = new QueryCommand({
146
+ TableName: NpoDb.name,
147
+ IndexName: NpoDb.regnum_env_gsi,
148
+ Limit: 1,
149
+ KeyConditionExpression: "#rn = :rn AND #env = :env",
150
+
151
+ FilterExpression: "#country = :country",
152
+ ExpressionAttributeNames: {
153
+ "#rn": "registration_number" satisfies TNpoDbKeys,
154
+ "#env": "env" satisfies TNpoDbKeys,
155
+ "#country": "hq_country" satisfies TNpoDbKeys,
156
+ },
157
+ ExpressionAttributeValues: {
158
+ ":rn": regnum,
159
+ ":env": this.env,
160
+ ":country": country,
161
+ },
162
+ });
163
+
164
+ const { Items = [] } = await this.client.send(cmd);
165
+ const i = Items[0];
166
+ return i && this.sans_keys(i);
104
167
  }
105
168
 
106
- /** "0" - featured (goes before 1), "1" - not-featured */
107
- export type FeaturedFlag = "0" | "1";
108
- export interface NonKeyAttributes {
109
- id: MediaId;
110
- gsi1PK: Keys["PK"];
111
- gsi1SK: `MediaList#${FeaturedFlag}#${MediaId}#${MediaType}`;
112
- /** iso string */
113
- dateCreated: string;
169
+ npo_record(data: INpo) {
170
+ return {
171
+ ...this.key_npo(data.id),
172
+ ...data,
173
+ ...(data.referrer ? this.gsi1_npo(data.referrer) : {}),
174
+ ...(data.referral_id ? this.gsi2_npo(data.referral_id) : {}),
175
+ };
114
176
  }
115
177
 
116
- export interface VideoAttributes extends NonKeyAttributes {
117
- type: Extract<MediaType, "video">;
118
- url: string;
178
+ npo_prog_record(npo: number, data: IProgramDb) {
179
+ return {
180
+ ...this.key_npo_program(data.id, npo),
181
+ ...data,
182
+ };
119
183
  }
120
184
 
121
- export interface VideoDbRecord extends Keys, VideoAttributes {}
122
- export type DbRecord = VideoDbRecord; // | album | article
123
- }
185
+ prog_milestone_record(prog: string, data: IMilestone) {
186
+ return {
187
+ ...this.key_prog_milestone(data.id, prog),
188
+ ...data,
189
+ };
190
+ }
124
191
 
125
- export namespace Gsi1 {
126
- export namespace MediaList {
127
- export interface Keys
128
- extends Pick<Media.NonKeyAttributes, "gsi1PK" | "gsi1SK"> {}
129
- export interface DbRecord extends Media.DbRecord {}
192
+ npo_med_record(
193
+ npo_id: number,
194
+ { featured /** not saved as attribute */, ...d }: IMedia
195
+ ) {
196
+ return {
197
+ ...this.key_npo_med(d.id, npo_id),
198
+ ...this.gsi1_npo_med(d.id, npo_id, featured ? "1" : "0", d.type),
199
+ ...(d satisfies IMediaDb),
200
+ };
130
201
  }
131
202
 
132
- export namespace ReferredNpos {
133
- export interface Keys
134
- extends Required<Pick<Endow.NonKeyAttributes, "gsi1PK" | "gsi1SK">> {}
135
- export interface DbRecord
136
- extends Ensure<
137
- Endow.DbRecord,
138
- "gsi1PK" | "gsi1SK" | "referrer" | "referrer_expiry"
139
- > {}
203
+ async npo_med_put(npo: number, url: string) {
204
+ const ksuid = KSUID.randomSync();
205
+ const mid = ksuid.string;
206
+ const item = this.npo_med_record(npo, {
207
+ id: mid,
208
+ url,
209
+ type: "video",
210
+ dateCreated: ksuid.date.toISOString(),
211
+ featured: false,
212
+ });
213
+ const command = new PutCommand({
214
+ TableName: NpoDb.name,
215
+ Item: item,
216
+ });
217
+ await this.client.send(command);
218
+ return mid;
140
219
  }
141
- }
142
- export namespace Gsi2 {
143
- export namespace Rid {
144
- export interface Keys
145
- extends Required<Pick<Endow.NonKeyAttributes, "gsi1PK" | "gsi1SK">> {}
146
- export interface DbRecord
147
- extends Ensure<Endow.DbRecord, "gsi1PK" | "gsi1SK" | "referral_id"> {}
220
+
221
+ async npo_med(npo: number, mid: string): Promise<IMedia | undefined> {
222
+ const cmd = new GetCommand({
223
+ TableName: NpoDb.name,
224
+ Key: this.key_npo_med(mid, npo),
225
+ });
226
+ const { Item: i } = await this.client.send(cmd);
227
+ return i && to_imedia(i);
148
228
  }
149
- }
150
229
 
151
- export type DbRecord = Endow.DbRecord | Program.DbRecord;
230
+ async npo_med_delete(npo: number, mid: string) {
231
+ const cmd = new DeleteCommand({
232
+ TableName: NpoDb.name,
233
+ Key: this.key_npo_med(mid, npo),
234
+ });
235
+ return this.client.send(cmd);
236
+ }
237
+
238
+ async npo<T extends TNpoDbKeys[]>(
239
+ id: string | number,
240
+ fields?: T
241
+ ): Promise<TNpoDbProjectedTo<T> | undefined> {
242
+ const { names, expression } = projection(fields);
243
+
244
+ if (typeof id === "string") {
245
+ const cmd = new QueryCommand({
246
+ TableName: NpoDb.name,
247
+ IndexName: NpoDb.slug_env_gsi,
248
+ KeyConditionExpression: "#slug = :slug and #env = :env",
249
+ ExpressionAttributeValues: {
250
+ ":slug": id,
251
+ ":env": this.env,
252
+ },
253
+ ProjectionExpression: expression,
254
+ ExpressionAttributeNames: {
255
+ ...names,
256
+ "#env": "env",
257
+ "#slug": "slug",
258
+ },
259
+ });
260
+ const [x] = await this.client.send(cmd).then(({ Items: x = [] }) => x);
261
+ return x ? this.sans_keys(x) : undefined;
262
+ }
263
+
264
+ const cmd = new GetCommand({
265
+ TableName: NpoDb.name,
266
+ Key: this.key_npo(id),
267
+ ProjectionExpression: expression,
268
+ ExpressionAttributeNames: names,
269
+ });
270
+ const { Item: i } = await this.client.send(cmd);
271
+ return i ? this.sans_keys(i) : undefined;
272
+ }
273
+
274
+ async npo_update(
275
+ id: number,
276
+ { target, slug, social_media_urls, ...update }: INpoUpdate
277
+ ) {
278
+ const updates = new UpdateBuilder();
279
+
280
+ if (slug) updates.set("slug", slug);
281
+ if (slug === "") updates.remove("slug");
282
+
283
+ if (target || target === "0") {
284
+ updates.set("target", target);
285
+ }
286
+
287
+ if (social_media_urls) {
288
+ for (const [k, v] of Object.entries(social_media_urls)) {
289
+ if (v === undefined) continue;
290
+ updates.set(`social_media_urls.${k}`, v);
291
+ }
292
+ }
293
+
294
+ for (const [k, v] of Object.entries(update)) {
295
+ if (v === undefined) continue;
296
+ updates.set(k, v);
297
+ }
298
+
299
+ const cmd = new UpdateCommand({
300
+ TableName: NpoDb.name,
301
+ Key: this.key_npo(id),
302
+ ReturnValues: "ALL_NEW",
303
+ ...updates.collect(),
304
+ });
305
+
306
+ return this.client.send(cmd);
307
+ }
308
+ async prog_milestones(id: string): Promise<IMilestone[]> {
309
+ const command = new QueryCommand({
310
+ TableName: NpoDb.name,
311
+ KeyConditionExpression: `PK = :PK`,
312
+ ExpressionAttributeValues: {
313
+ ":PK": this.key_prog_milestone("not-used", id).PK,
314
+ },
315
+ });
316
+ return this.client
317
+ .send(command)
318
+ .then(({ Items: x = [] }) => x.map((i) => this.sans_keys(i)));
319
+ }
320
+ async prog_milestone_delete(pid: string, mid: string) {
321
+ const cmd = new DeleteCommand({
322
+ TableName: NpoDb.name,
323
+ Key: this.key_prog_milestone(mid, pid),
324
+ });
325
+ return this.client.send(cmd);
326
+ }
327
+ async prog_milestone_update(
328
+ pid: string,
329
+ mid: string,
330
+ update: IMilestoneUpdate
331
+ ) {
332
+ const upd8 = new UpdateBuilder();
333
+ for (const [key, value] of Object.entries(update)) {
334
+ upd8.set(key, value);
335
+ }
336
+
337
+ const cmd = new UpdateCommand({
338
+ TableName: NpoDb.name,
339
+ Key: this.key_prog_milestone(mid, pid),
340
+ ...upd8.collect(),
341
+ ReturnValues: "ALL_NEW",
342
+ });
343
+ return this.client.send(cmd).then((res) => res.Attributes);
344
+ }
345
+
346
+ async npo_program(id: string, npo_id: number): Promise<IProgram | undefined> {
347
+ const cmd = new GetCommand({
348
+ TableName: NpoDb.name,
349
+ Key: this.key_npo_program(id, npo_id),
350
+ });
351
+ const { Item: p } = await this.client.send(cmd);
352
+ if (!p) return undefined;
353
+
354
+ const milestones = await this.prog_milestones(id);
355
+
356
+ return {
357
+ ...this.sans_keys<IProgramDb>(p),
358
+ milestones: milestones.toSorted((a, b) => a.date.localeCompare(b.date)),
359
+ };
360
+ }
361
+ async npo_programs(id: number): Promise<IProgramDb[]> {
362
+ const cmd = new QueryCommand({
363
+ TableName: NpoDb.name,
364
+ KeyConditionExpression: `PK = :PK and begins_with(SK, :SK)`,
365
+ ExpressionAttributeValues: {
366
+ ":PK": this.key_npo_program("not-used", id).PK,
367
+ ":SK": "Prog#",
368
+ },
369
+ });
370
+
371
+ const { Items: x = [] } = await this.client.send(cmd);
372
+ return x.map((i) => this.sans_keys(i));
373
+ }
374
+
375
+ async npo_program_put(npo: number, content: IProgramNew): Promise<string> {
376
+ const pid = crypto.randomUUID();
377
+
378
+ const { milestones, ...prog } = content;
379
+ const txs = new Txs();
380
+ const db_prog = this.npo_prog_record(npo, {
381
+ ...prog,
382
+ id: pid,
383
+ totalDonations: 0,
384
+ });
385
+ txs.put({
386
+ TableName: NpoDb.name,
387
+ Item: db_prog,
388
+ });
389
+
390
+ for (const m of milestones || []) {
391
+ const mid = crypto.randomUUID();
392
+ txs.put({
393
+ TableName: NpoDb.name,
394
+ Item: this.prog_milestone_record(pid, { ...m, id: mid }),
395
+ });
396
+ }
397
+
398
+ const cmd = new TransactWriteCommand({
399
+ TransactItems: txs.all,
400
+ });
401
+
402
+ await this.client.send(cmd);
403
+ return pid;
404
+ }
405
+
406
+ async npo_prog_del(npo: number, prog: string) {
407
+ const milestones = await this.prog_milestones(prog);
408
+ const txs = new Txs();
409
+ txs.del({
410
+ TableName: NpoDb.name,
411
+ Key: this.key_npo_program(prog, npo),
412
+ });
413
+
414
+ for (const m of milestones) {
415
+ txs.del({
416
+ TableName: NpoDb.name,
417
+ Key: this.key_prog_milestone(m.id, prog),
418
+ });
419
+ }
420
+
421
+ const cmd = new TransactWriteCommand({
422
+ TransactItems: txs.all,
423
+ });
424
+ return this.client.send(cmd);
425
+ }
426
+
427
+ async npo_prog_update(npo: number, prog: string, update: IProgramUpdate) {
428
+ const upd8 = new UpdateBuilder();
429
+ for (const [key, value] of Object.entries(update)) {
430
+ upd8.set(key, value);
431
+ }
432
+
433
+ const cmd = new UpdateCommand({
434
+ TableName: NpoDb.name,
435
+ Key: this.key_npo_program(prog, npo),
436
+ ...upd8.collect(),
437
+ ReturnValues: "ALL_NEW",
438
+ });
439
+ return this.client.send(cmd).then((res) => res.Attributes);
440
+ }
441
+ }
package/src/index.mts CHANGED
@@ -1,50 +1,7 @@
1
- import type { IPageKeyed } from "@better-giving/types/api";
2
- import type { CloudsearchEndow as EndowItem } from "./cloudsearch.mjs";
3
- import type { Media } from "./db.mjs";
4
- import type { Milestone, Program as ProgramShape } from "./schema.mjs";
5
- export type {
6
- Allocation,
7
- DonateMethodId,
8
- EndowDesignation,
9
- Endowment as Endow,
10
- EndowFields,
11
- EndowQueryParams,
12
- EndowUpdate,
13
- Environment,
14
- Increment,
15
- MediaQueryParams,
16
- MediaQueryParamsObj,
17
- MediaType,
18
- MediaUpdate,
19
- Milestone,
20
- MilestoneUpdate,
21
- NewMilestone,
22
- NewProgram,
23
- ProgramUpdate,
24
- SocialMediaURLs,
25
- UnSdgNum,
26
- } from "./schema.mjs";
1
+ export * from "./interfaces.mjs";
2
+ export { NpoDb } from "./db.mjs";
27
3
  export type {
28
4
  CloudsearchEndow as EndowItem,
29
5
  CloudsearchEndowsQueryParams as EndowsQueryParams,
30
6
  CloudsearchEndowsQueryParamsParsed as EndowsQueryParamsParsed,
31
7
  } from "./cloudsearch.mjs";
32
-
33
- /** client responsible on T depending on keys projected */
34
- export interface EndowsPage<T extends keyof EndowItem = keyof EndowItem> {
35
- items: Pick<EndowItem, T>[];
36
- page: number;
37
- pages: number;
38
- }
39
-
40
- export interface Program extends ProgramShape {
41
- milestones: Milestone[];
42
- }
43
-
44
- /** web-app format */
45
- export interface IMedia
46
- extends Omit<Media.DbRecord, "PK" | "SK" | "gsi1PK" | "gsi1SK"> {
47
- featured: boolean;
48
- }
49
-
50
- export interface MediaPage extends IPageKeyed<IMedia> {}
@@ -0,0 +1,73 @@
1
+ import type { IPageKeyed } from "@better-giving/types/api";
2
+ import type { Ensure } from "@better-giving/types/utils";
3
+ import type { IMediaUpdate, INpo, TMediaType } from "./schema.mjs";
4
+
5
+ export type {
6
+ IAllocation,
7
+ INpo,
8
+ INpoFields,
9
+ INposSearch,
10
+ INpoUpdate,
11
+ IIncrement,
12
+ IMediaSearch,
13
+ IMediaSearchObj,
14
+ TMediaType,
15
+ IMediaUpdate,
16
+ IMilestone,
17
+ IMilestoneUpdate,
18
+ IMilestoneNew,
19
+ IProgramNew,
20
+ IProgramUpdate,
21
+ ISocialMediaURLs,
22
+ } from "./schema.mjs";
23
+
24
+ export {
25
+ type OrgDesignation as EndowDesignation,
26
+ type Environment,
27
+ type UnSdgNum,
28
+ type DonateMethodId,
29
+ https_url,
30
+ } from "@better-giving/schemas";
31
+
32
+ export interface INpoDb extends INpo {
33
+ /** will only be present for unclaimed NPOs */
34
+ updated_at_auto?: string;
35
+ /** in USD @deprecated */
36
+ payout_minimum?: number;
37
+ /** @deprecated */
38
+ splitLiqPct?: number;
39
+ /** @deprecated */
40
+ splitFixed?: boolean;
41
+ /** @deprecated */
42
+ sfCompounded?: boolean;
43
+ }
44
+
45
+ export interface INpoReferredBy
46
+ extends Ensure<INpo, "referrer" | "referrer_expiry"> {}
47
+
48
+ export interface INpoWithRid extends Ensure<INpo, "referral_id"> {}
49
+
50
+ export interface INpoWithRegNum
51
+ extends Pick<INpo, "claimed" | "name" | "hq_country" | "id"> {}
52
+
53
+ export interface IMedia extends Required<IMediaUpdate> {
54
+ id: string;
55
+ type: Extract<TMediaType, "video">; // only video for now
56
+ dateCreated: string;
57
+ }
58
+ export interface IMediaDb extends Omit<IMedia, "featured"> {}
59
+ export interface IMediaPage extends IPageKeyed<IMedia> {}
60
+
61
+ export type TNpoDbKeys = keyof INpoDb;
62
+ export type TArrayValues<T extends readonly unknown[]> = T[number];
63
+ export type TNpoDbProjectedTo<T> = T extends TNpoDbKeys[]
64
+ ? Pick<INpoDb, TArrayValues<T>>
65
+ : INpoDb;
66
+
67
+ export type TBinFlag = "0" | "1";
68
+
69
+ export interface INposPage<T extends keyof INpoDb = keyof INpoDb> {
70
+ items: Pick<INpoDb, T>[];
71
+ page: number;
72
+ pages: number;
73
+ }