@better-giving/endowment 2.0.6 → 3.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/src/media.mts ADDED
@@ -0,0 +1,67 @@
1
+ import type { TRecord } from "@better-giving/db";
2
+ import type { IMedia, TBinFlag } from "./interfaces.mjs";
3
+ import type { IMediaSearchObj, TMediaType } from "./schema.mjs";
4
+
5
+ export const med_sk = (ksuid: string, type: TMediaType, featured: TBinFlag) =>
6
+ `MediaList#${featured}#${ksuid}#${type}`;
7
+
8
+ export const med_sk_attr = (sk: string) => {
9
+ const [featured, ksuid, type] = sk.split("#").slice(1) as [
10
+ TBinFlag,
11
+ string,
12
+ TMediaType,
13
+ ];
14
+ return { featured, ksuid, type };
15
+ };
16
+
17
+ export function med_key_filter(
18
+ PK: string,
19
+ params: IMediaSearchObj
20
+ ): [string, Record<string, string>] {
21
+ /** KSUID string is base62, length:27 */
22
+ const min_ksuid = "0".repeat(27);
23
+ const max_ksuid = "z".repeat(27);
24
+
25
+ //media categories: get all media per type, all featured - sorted by date
26
+ if (params.featured && params.type) {
27
+ return [
28
+ `gsi1PK = :pk AND gsi1SK BETWEEN :startSK and :endSK`,
29
+ {
30
+ ":pk": PK,
31
+ ":startSK": med_sk(min_ksuid, params.type, "0"),
32
+ ":endSK": med_sk(max_ksuid, params.type, "0"),
33
+ },
34
+ ];
35
+ }
36
+
37
+ //media type page: get all media per type - sorted by featured and date
38
+ if (params.type) {
39
+ return [
40
+ `gsi1PK = :pk AND gsi1SK BETWEEN :startSK and :endSK`,
41
+ {
42
+ ":pk": PK,
43
+ ":startSK": med_sk(min_ksuid, params.type, "0"),
44
+ ":endSK": med_sk(max_ksuid, params.type, "1"),
45
+ },
46
+ ];
47
+ }
48
+
49
+ //endow-profile: get all media, all featured - sorted by date
50
+ if (params.featured) {
51
+ return [
52
+ `gsi1PK = :pk AND begins_with(gsi1SK, :sk)`,
53
+ { ":pk": PK, ":sk": `MediaList#0` },
54
+ ];
55
+ }
56
+
57
+ return [
58
+ `gsi1PK = :pk AND begins_with(gsi1SK, :sk)`,
59
+ { ":pk": PK, ":sk": `MediaList#` },
60
+ ];
61
+ }
62
+
63
+ export const to_imedia = (d: TRecord): IMedia => {
64
+ const { gsi1SK, gsi1PK, PK, SK, ...rest } = d;
65
+ const x = med_sk_attr(gsi1SK);
66
+ return { ...rest, featured: x.featured === "1" } as any;
67
+ };
package/src/npo.mts ADDED
@@ -0,0 +1,47 @@
1
+ import type { INpo } from "./schema.mjs";
2
+
3
+ const npo_fields: { [K in keyof Required<INpo>]: "" } = {
4
+ id: "",
5
+ active_in_countries: "",
6
+ endow_designation: "",
7
+ fiscal_sponsored: "",
8
+ hide_bg_tip: "",
9
+ hq_country: "",
10
+ image: "",
11
+ kyc_donors_only: "",
12
+ logo: "",
13
+ name: "",
14
+ overview: "",
15
+ published: "",
16
+ registration_number: "",
17
+ social_media_urls: "",
18
+ sdgs: "",
19
+ street_address: "",
20
+ tagline: "",
21
+ url: "",
22
+ slug: "",
23
+ claimed: "",
24
+ card_img: "",
25
+ progDonationsAllowed: "",
26
+ allocation: "",
27
+ donateMethods: "",
28
+ increments: "",
29
+ receiptMsg: "",
30
+ fund_opt_in: "",
31
+ env: "",
32
+ target: "",
33
+ referrer: "",
34
+ referrer_expiry: "",
35
+ referral_id: "",
36
+ w_form: "",
37
+ payout_minimum: "",
38
+ };
39
+
40
+ export function projection(fields: string[] = Object.keys(npo_fields)) {
41
+ const expression = fields.map((n) => `#${n}`).join(",");
42
+ const names = fields.reduce(
43
+ (prev, curr) => ({ ...prev, [`#${curr}`]: curr }),
44
+ {} as Record<string, string>
45
+ );
46
+ return { expression, names };
47
+ }
package/src/schema.mts CHANGED
@@ -11,13 +11,6 @@ import {
11
11
  org_designation,
12
12
  unsdg_num,
13
13
  } from "@better-giving/schemas";
14
- export {
15
- type OrgDesignation as EndowDesignation,
16
- type Environment,
17
- type UnSdgNum,
18
- type DonateMethodId,
19
- https_url,
20
- } from "@better-giving/schemas";
21
14
  export {
22
15
  org_designation as endow_designation,
23
16
  env,
@@ -54,25 +47,7 @@ export const allocation = v.pipe(
54
47
  v.check((x) => x.cash + x.liq + x.lock === 100, "must total to 100")
55
48
  );
56
49
 
57
- export interface Allocation extends v.InferOutput<typeof allocation> {}
58
-
59
- export const segmentMaxChars = 30;
60
- export const segment = v.pipe(
61
- $,
62
- v.maxLength(
63
- segmentMaxChars,
64
- ({ requirement: r }) => `cannot exceed ${r} chars`
65
- ),
66
- //must not be id-like
67
- v.regex(/^(?!^\d+$)/, "should not be an id"),
68
- //valid characters
69
- v.regex(/^[a-zA-Z0-9-._~]+$/, "allowed: numbers | letters | - | . | _ | ~"),
70
- v.excludes("..", "should not contain double periods"),
71
- v.custom((x) => !(x as string).startsWith("."), "should not start with dot"),
72
- v.custom((x) => !(x as string).endsWith("."), "should not end with dot")
73
- );
74
-
75
- export const slug = v.lazy((x) => (x ? segment : $));
50
+ export interface IAllocation extends v.InferOutput<typeof allocation> {}
76
51
 
77
52
  export const reg_number = v.pipe(
78
53
  $,
@@ -91,37 +66,37 @@ export const social_media_urls = v.object({
91
66
  tiktok: v.optional(https_url(false)),
92
67
  });
93
68
 
94
- export interface SocialMediaURLs
69
+ export interface ISocialMediaURLs
95
70
  extends v.InferOutput<typeof social_media_urls> {}
96
71
 
97
72
  export const MAX_RECEIPT_MSG_CHAR = 500;
98
73
 
99
- export const incrementVal = v.pipe(
74
+ export const increment_val = v.pipe(
100
75
  $req_num_gt0,
101
76
  //parsed output
102
77
  v.transform((x) => x.toString())
103
78
  );
104
79
 
105
80
  export const MAX_NUM_INCREMENTS = 4;
106
- export const incrementLabelMaxChars = 60;
107
- export const taglineMaxChars = 140;
81
+ export const increment_label_max_chars = 60;
82
+ export const tagline_max_chars = 140;
108
83
 
109
- export const incrementLabel = v.pipe(
84
+ export const increment_label = v.pipe(
110
85
  $,
111
86
  v.maxLength(
112
- incrementLabelMaxChars,
87
+ increment_label_max_chars,
113
88
  ({ requirement: r }) => `cannot exceed ${r} characters`
114
89
  )
115
90
  );
116
91
 
117
92
  export const increment = v.object({
118
- value: incrementVal,
119
- label: incrementLabel,
93
+ value: increment_val,
94
+ label: increment_label,
120
95
  });
121
96
 
122
- export interface Increment extends v.InferOutput<typeof increment> {}
97
+ export interface IIncrement extends v.InferOutput<typeof increment> {}
123
98
 
124
- export const endowment = v.object({
99
+ export const npo = v.object({
125
100
  id: int_gte1,
126
101
  env,
127
102
  slug: v.optional(slug),
@@ -129,7 +104,7 @@ export const endowment = v.object({
129
104
  name: $req,
130
105
  endow_designation: org_designation,
131
106
  overview: v.optional($),
132
- tagline: v.optional(v.pipe($, v.maxLength(taglineMaxChars))),
107
+ tagline: v.optional(v.pipe($, v.maxLength(tagline_max_chars))),
133
108
  image: v.optional(_url),
134
109
  logo: v.optional(_url),
135
110
  card_img: v.optional(_url),
@@ -177,93 +152,88 @@ export const endowment = v.object({
177
152
  payout_minimum: v.optional(v.pipe(v.number(), v.minValue(min_payout_amount))),
178
153
  });
179
154
 
180
- export const endowUpdate = v.partial(
181
- v.omit(endowment, [
182
- "id",
183
- "claimed",
184
- "kyc_donors_only",
185
- "env",
186
- "fiscal_sponsored",
187
- ])
155
+ export const npo_update = v.partial(
156
+ v.omit(npo, ["id", "claimed", "kyc_donors_only", "env", "fiscal_sponsored"])
188
157
  );
189
158
 
190
- export const endowFields = v.keyof(endowment);
191
- export interface Endowment extends v.InferOutput<typeof endowment> {}
192
- export interface EndowUpdate extends v.InferOutput<typeof endowUpdate> {}
193
- export type EndowFields = v.InferOutput<typeof endowFields>;
159
+ export const npo_fields = v.keyof(npo);
160
+ export interface INpo extends v.InferOutput<typeof npo> {}
161
+ export interface INpoUpdate extends v.InferOutput<typeof npo_update> {}
162
+ export type INpoFields = v.InferOutput<typeof npo_fields>;
194
163
 
195
164
  /** for ein path, only fields in reg-num/env gsi is available */
196
- export const endowQueryParams = v.object({
197
- fields: v.optional(v.pipe(csvStrs, v.array(endowFields))),
165
+ export const npo_search = v.object({
166
+ fields: v.optional(v.pipe(csvStrs, v.array(npo_fields))),
198
167
  });
199
168
 
200
- export interface EndowQueryParams
201
- extends v.InferInput<typeof endowQueryParams> {}
169
+ export interface INposSearch extends v.InferInput<typeof npo_search> {}
202
170
 
203
171
  const amnt = v.pipe(v.number(), v.minValue(0));
204
- export const programId = v.pipe($, v.uuid());
205
- export type ProgramId = v.InferOutput<typeof programId>;
206
- export const milestoneId = v.pipe($, v.uuid());
207
- export type MilestoneId = v.InferOutput<typeof milestoneId>;
172
+ export const program_id = v.pipe($, v.uuid());
173
+ export const milestone_id = v.pipe($, v.uuid());
208
174
 
209
- export const newMilestone = v.object({
175
+ export const milestone_new = v.object({
210
176
  date: v.pipe($, v.isoTimestamp()),
211
177
  title: $,
212
178
  description: $,
213
179
  media: v.optional(_url),
214
180
  });
215
181
 
216
- export const milestoneUpdate = v.partial(newMilestone, ["date"]);
217
- export interface MilestoneUpdate
218
- extends v.InferOutput<typeof milestoneUpdate> {}
182
+ export const milestone_update = v.partial(milestone_new, ["date"]);
183
+ export interface IMilestoneUpdate
184
+ extends v.InferOutput<typeof milestone_update> {}
219
185
 
220
- export interface NewMilestone extends v.InferOutput<typeof newMilestone> {}
221
- export const milestone = v.object({ ...newMilestone.entries, id: milestoneId });
222
- export interface Milestone extends v.InferOutput<typeof milestone> {}
186
+ export interface IMilestoneNew extends v.InferOutput<typeof milestone_new> {}
187
+ export const milestone = v.object({
188
+ ...milestone_new.entries,
189
+ id: milestone_id,
190
+ });
191
+ export interface IMilestone extends v.InferOutput<typeof milestone> {}
223
192
 
224
- export const newProgram = v.object({
193
+ export const program_new = v.object({
225
194
  title: $,
226
195
  description: $,
227
196
  banner: v.optional(_url),
228
197
  /** null unsets target */
229
198
  targetRaise: v.nullish(amnt),
230
- milestones: v.pipe(v.array(newMilestone), v.maxLength(24)),
199
+ milestones: v.pipe(v.array(milestone_new), v.maxLength(24)),
231
200
  });
232
201
 
233
202
  export const program = v.object({
234
- ...v.omit(newProgram, ["milestones"]).entries,
203
+ ...v.omit(program_new, ["milestones"]).entries,
235
204
  /** in USD */
236
205
  totalDonations: v.optional(v.number()),
237
- id: programId,
206
+ id: program_id,
238
207
  });
239
208
 
240
- export const programUpdate = v.partial(v.omit(newProgram, ["milestones"]));
209
+ export const program_update = v.partial(v.omit(program_new, ["milestones"]));
241
210
 
242
- export interface NewProgram extends v.InferOutput<typeof newProgram> {}
243
- export interface Program extends v.InferOutput<typeof program> {}
244
- export interface ProgramUpdate extends v.InferOutput<typeof programUpdate> {}
211
+ export interface IProgramNew extends v.InferOutput<typeof program_new> {}
212
+ export interface IProgramDb extends v.InferOutput<typeof program> {}
213
+ export interface IProgram extends v.InferOutput<typeof program> {
214
+ milestones: IMilestone[];
215
+ }
216
+ export interface IProgramUpdate extends v.InferOutput<typeof program_update> {}
245
217
 
246
- export const mediaUrl = v.pipe($, v.url());
218
+ export const media_url = v.pipe($, v.url());
247
219
  /**
248
220
  * so that media is automatically sorted by date
249
221
  * @see https://github.com/segmentio/ksuid
250
222
  * */
251
- export const mediaKsuid = $req; // base62;
252
- export const mediaTypes = ["album", "article", "video"] as const;
253
- export const mediaType = v.picklist(mediaTypes);
254
- export type MediaType = v.InferOutput<typeof mediaType>;
223
+ export const media_ksuid = $req; // base62;
224
+ export const media_types = ["album", "article", "video"] as const;
225
+ export const media_type = v.picklist(media_types);
226
+ export type TMediaType = v.InferOutput<typeof media_type>;
255
227
 
256
- export const mediaUpdate = v.object({
257
- url: v.optional(mediaUrl),
228
+ export const media_update = v.object({
229
+ url: v.optional(media_url),
258
230
  featured: v.optional(v.boolean()),
259
231
  });
260
- export interface MediaUpdate extends v.InferOutput<typeof mediaUpdate> {}
261
-
262
- export type MediaId = v.InferOutput<typeof mediaKsuid>;
232
+ export interface IMediaUpdate extends v.InferOutput<typeof media_update> {}
263
233
 
264
- export const mediaQueryParams = v.object({
265
- type: v.optional(mediaType),
266
- nextPageKey: v.optional(v.pipe($, v.base64())),
234
+ export const media_search = v.object({
235
+ type: v.optional(media_type),
236
+ next: v.optional(v.pipe($, v.base64())),
267
237
  featured: v.optional(
268
238
  v.pipe(
269
239
  $,
@@ -279,7 +249,5 @@ export const mediaQueryParams = v.object({
279
249
  )
280
250
  ),
281
251
  });
282
- export interface MediaQueryParams
283
- extends v.InferInput<typeof mediaQueryParams> {}
284
- export interface MediaQueryParamsObj
285
- extends v.InferOutput<typeof mediaQueryParams> {}
252
+ export interface IMediaSearch extends v.InferInput<typeof media_search> {}
253
+ export interface IMediaSearchObj extends v.InferOutput<typeof media_search> {}