jansathi-community-schema 0.1.0 → 0.3.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/dist/comment.d.ts CHANGED
@@ -19,6 +19,7 @@ export declare const communityCommentWireSchema: z.ZodObject<{
19
19
  post: "post";
20
20
  lost_found: "lost_found";
21
21
  voice_box: "voice_box";
22
+ donate_item: "donate_item";
22
23
  }>;
23
24
  contentId: z.ZodString;
24
25
  parentCommentId: z.ZodNullable<z.ZodString>;
@@ -1 +1 @@
1
- {"version":3,"file":"comment.d.ts","sourceRoot":"","sources":["../src/comment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkCrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI9E,eAAO,MAAM,uBAAuB;;;iBAMlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAIxE,eAAO,MAAM,uBAAuB;;iBAElC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
1
+ {"version":3,"file":"comment.d.ts","sourceRoot":"","sources":["../src/comment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkCrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI9E,eAAO,MAAM,uBAAuB;;;iBAMlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAIxE,eAAO,MAAM,uBAAuB;;iBAElC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
@@ -22,6 +22,7 @@ export declare const toggleUpvoteResponseSchema: z.ZodObject<{
22
22
  post: "post";
23
23
  lost_found: "lost_found";
24
24
  voice_box: "voice_box";
25
+ donate_item: "donate_item";
25
26
  }>;
26
27
  contentId: z.ZodString;
27
28
  upvoted: z.ZodBoolean;
@@ -52,6 +53,7 @@ export declare const setReactionResponseSchema: z.ZodObject<{
52
53
  post: "post";
53
54
  lost_found: "lost_found";
54
55
  voice_box: "voice_box";
56
+ donate_item: "donate_item";
55
57
  }>;
56
58
  contentId: z.ZodString;
57
59
  reaction: z.ZodNullable<z.ZodEnum<{
@@ -1 +1 @@
1
- {"version":3,"file":"engagement.d.ts","sourceRoot":"","sources":["../src/engagement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;GAIG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;iBAQrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI9E;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;iBAEhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;iBAOpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
1
+ {"version":3,"file":"engagement.d.ts","sourceRoot":"","sources":["../src/engagement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;GAIG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;iBAQrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI9E;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;iBAEhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;iBAOpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
package/dist/enums.d.ts CHANGED
@@ -40,17 +40,19 @@ export type VisibilityLevel = z.infer<typeof visibilityLevelSchema>;
40
40
  export declare const VISIBILITY_LEVEL_RANK: Record<VisibilityLevel, number>;
41
41
  /**
42
42
  * The kinds of items that show up in the feed. `post` is the new
43
- * community-native kind; `lost_found` and `voice_box` are existing
44
- * features surfaced into the feed via polymorphic union.
43
+ * community-native kind; `lost_found`, `voice_box`, and `donate_item`
44
+ * are existing legacy features surfaced into the feed via polymorphic
45
+ * union.
45
46
  *
46
- * Engagement (upvote / reaction / comment) is keyed by
47
+ * Engagement (upvote / reaction / comment / share) is keyed by
47
48
  * (contentKind, contentId) so every kind gets the same engagement bar.
48
49
  */
49
- export declare const CONTENT_KIND_VALUES: readonly ["post", "lost_found", "voice_box"];
50
+ export declare const CONTENT_KIND_VALUES: readonly ["post", "lost_found", "voice_box", "donate_item"];
50
51
  export declare const contentKindSchema: z.ZodEnum<{
51
52
  post: "post";
52
53
  lost_found: "lost_found";
53
54
  voice_box: "voice_box";
55
+ donate_item: "donate_item";
54
56
  }>;
55
57
  export type ContentKind = z.infer<typeof contentKindSchema>;
56
58
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../src/enums.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uBAAuB,+DAM1B,CAAC;AACX,eAAO,MAAM,qBAAqB;;;;;;EAAkC,CAAC;AACrE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAMjE,CAAC;AAIF;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,8CAA+C,CAAC;AAChF,eAAO,MAAM,iBAAiB;;;;EAA8B,CAAC;AAC7D,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAI5D;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,qEAQvB,CAAC;AACX,eAAO,MAAM,kBAAkB;;;;;;;;EAA+B,CAAC;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,yDAK3B,CAAC;AACX,eAAO,MAAM,sBAAsB;;;;;EAAmC,CAAC;AACvE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAItE;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,gCAAiC,CAAC;AACrE,eAAO,MAAM,oBAAoB;;;EAAiC,CAAC;AACnE,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,yIAUvB,CAAC;AACX,eAAO,MAAM,kBAAkB;;;;;;;;;;EAA+B,CAAC;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,6CAA6C;AAC7C,eAAO,MAAM,oBAAoB,4DAKvB,CAAC;AACX,eAAO,MAAM,kBAAkB;;;;;EAA+B,CAAC;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,gCAAiC,CAAC;AAC/D,eAAO,MAAM,cAAc;;;;EAA2B,CAAC;AACvD,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
1
+ {"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../src/enums.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uBAAuB,+DAM1B,CAAC;AACX,eAAO,MAAM,qBAAqB;;;;;;EAAkC,CAAC;AACrE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAMjE,CAAC;AAIF;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,6DAA8D,CAAC;AAC/F,eAAO,MAAM,iBAAiB;;;;;EAA8B,CAAC;AAC7D,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAI5D;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,qEAQvB,CAAC;AACX,eAAO,MAAM,kBAAkB;;;;;;;;EAA+B,CAAC;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,yDAA0D,CAAC;AAChG,eAAO,MAAM,sBAAsB;;;;;EAAmC,CAAC;AACvE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAItE;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,gCAAiC,CAAC;AACrE,eAAO,MAAM,oBAAoB;;;EAAiC,CAAC;AACnE,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,yIAUvB,CAAC;AACX,eAAO,MAAM,kBAAkB;;;;;;;;;;EAA+B,CAAC;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,6CAA6C;AAC7C,eAAO,MAAM,oBAAoB,4DAA6D,CAAC;AAC/F,eAAO,MAAM,kBAAkB;;;;;EAA+B,CAAC;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,gCAAiC,CAAC;AAC/D,eAAO,MAAM,cAAc;;;;EAA2B,CAAC;AACvD,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
package/dist/enums.js CHANGED
@@ -47,13 +47,14 @@ export const VISIBILITY_LEVEL_RANK = {
47
47
  // ─── Polymorphic content kind ───────────────────────────────────────────────
48
48
  /**
49
49
  * The kinds of items that show up in the feed. `post` is the new
50
- * community-native kind; `lost_found` and `voice_box` are existing
51
- * features surfaced into the feed via polymorphic union.
50
+ * community-native kind; `lost_found`, `voice_box`, and `donate_item`
51
+ * are existing legacy features surfaced into the feed via polymorphic
52
+ * union.
52
53
  *
53
- * Engagement (upvote / reaction / comment) is keyed by
54
+ * Engagement (upvote / reaction / comment / share) is keyed by
54
55
  * (contentKind, contentId) so every kind gets the same engagement bar.
55
56
  */
56
- export const CONTENT_KIND_VALUES = ['post', 'lost_found', 'voice_box'];
57
+ export const CONTENT_KIND_VALUES = ['post', 'lost_found', 'voice_box', 'donate_item'];
57
58
  export const contentKindSchema = z.enum(CONTENT_KIND_VALUES);
58
59
  // ─── Reactions ──────────────────────────────────────────────────────────────
59
60
  /**
@@ -82,12 +83,7 @@ export const reactionTypeSchema = z.enum(REACTION_TYPE_VALUES);
82
83
  * `declined` — recipient rejected; surfaces as "you can't reconnect for X days" on the requester side.
83
84
  * `blocked` — either side blocked; permanently prevents reconnection.
84
85
  */
85
- export const CONNECTION_STATUS_VALUES = [
86
- 'pending',
87
- 'accepted',
88
- 'declined',
89
- 'blocked',
90
- ];
86
+ export const CONNECTION_STATUS_VALUES = ['pending', 'accepted', 'declined', 'blocked'];
91
87
  export const connectionStatusSchema = z.enum(CONNECTION_STATUS_VALUES);
92
88
  // ─── Profile privacy ───────────────────────────────────────────────────────
93
89
  /**
@@ -120,12 +116,7 @@ export const REPORT_REASON_VALUES = [
120
116
  ];
121
117
  export const reportReasonSchema = z.enum(REPORT_REASON_VALUES);
122
118
  /** Moderator decision states on a report. */
123
- export const REPORT_STATUS_VALUES = [
124
- 'pending',
125
- 'reviewing',
126
- 'actioned',
127
- 'dismissed',
128
- ];
119
+ export const REPORT_STATUS_VALUES = ['pending', 'reviewing', 'actioned', 'dismissed'];
129
120
  export const reportStatusSchema = z.enum(REPORT_STATUS_VALUES);
130
121
  // ─── Feed sort ─────────────────────────────────────────────────────────────
131
122
  /**
package/dist/enums.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"enums.js","sourceRoot":"","sources":["../src/enums.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,+EAA+E;AAE/E;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,OAAO;IACP,UAAU;IACV,OAAO;IACP,UAAU;IACV,QAAQ;CACA,CAAC;AACX,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;AAGrE;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAoC;IACpE,KAAK,EAAE,CAAC;IACR,QAAQ,EAAE,CAAC;IACX,KAAK,EAAE,CAAC;IACR,QAAQ,EAAE,CAAC;IACX,MAAM,EAAE,CAAC;CACV,CAAC;AAEF,+EAA+E;AAE/E;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,WAAW,CAAU,CAAC;AAChF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;AAG7D,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,MAAM;IACN,MAAM;IACN,MAAM;IACN,KAAK;IACL,KAAK;IACL,OAAO;IACP,SAAS;CACD,CAAC;AACX,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AAG/D,8EAA8E;AAE9E;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,SAAS;IACT,UAAU;IACV,UAAU;IACV,SAAS;CACD,CAAC;AACX,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;AAGvE,8EAA8E;AAE9E;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAU,CAAC;AACrE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;AAGnE,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,MAAM;IACN,YAAY;IACZ,aAAa;IACb,gBAAgB;IAChB,gBAAgB;IAChB,UAAU;IACV,WAAW;IACX,iBAAiB;IACjB,OAAO;CACC,CAAC;AACX,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AAG/D,6CAA6C;AAC7C,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,SAAS;IACT,WAAW;IACX,UAAU;IACV,WAAW;CACH,CAAC;AACX,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AAG/D,8EAA8E;AAE9E;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAU,CAAC;AAC/D,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC","sourcesContent":["/**\n * Enum value arrays shared across the community feature.\n *\n * Each enum is exported as both a `*_VALUES` tuple and the corresponding\n * Zod schema so backend Mongoose models and frontend forms validate\n * against the exact same set.\n *\n * @module community-schema/enums\n */\n\nimport { z } from 'zod';\n\n// ─── Visibility scope ───────────────────────────────────────────────────────\n\n/**\n * The geographic level at which a piece of content is posted.\n *\n * Cascade-upward semantics: a viewer whose feed filter is set to L\n * sees every post at L and every BROADER level that matches their\n * area lineage. So:\n * - `local` → matches local + district + state + national + global within the viewer's locality / village / urbanBody / etc.\n * - `district` → matches district + state + national + global within the viewer's district\n * - `state` → matches state + national + global within the viewer's state\n * - `national` → matches national + global within the viewer's country\n * - `global` → matches global only\n *\n * The full meaning of \"local\" is \"anything below district\" — i.e., the\n * deepest area-lineage match (locality, village, urbanWard, …).\n */\nexport const VISIBILITY_LEVEL_VALUES = [\n 'local',\n 'district',\n 'state',\n 'national',\n 'global',\n] as const;\nexport const visibilityLevelSchema = z.enum(VISIBILITY_LEVEL_VALUES);\nexport type VisibilityLevel = z.infer<typeof visibilityLevelSchema>;\n\n/**\n * Numeric ranking used for the cascade-upward query and for sorting\n * filter chips. `local` is the deepest (most specific), `global` the\n * shallowest. Higher rank = broader scope.\n */\nexport const VISIBILITY_LEVEL_RANK: Record<VisibilityLevel, number> = {\n local: 0,\n district: 1,\n state: 2,\n national: 3,\n global: 4,\n};\n\n// ─── Polymorphic content kind ───────────────────────────────────────────────\n\n/**\n * The kinds of items that show up in the feed. `post` is the new\n * community-native kind; `lost_found` and `voice_box` are existing\n * features surfaced into the feed via polymorphic union.\n *\n * Engagement (upvote / reaction / comment) is keyed by\n * (contentKind, contentId) so every kind gets the same engagement bar.\n */\nexport const CONTENT_KIND_VALUES = ['post', 'lost_found', 'voice_box'] as const;\nexport const contentKindSchema = z.enum(CONTENT_KIND_VALUES);\nexport type ContentKind = z.infer<typeof contentKindSchema>;\n\n// ─── Reactions ──────────────────────────────────────────────────────────────\n\n/**\n * Reaction types available on every feed item.\n *\n * `support` replaces Facebook's \"Care\" — better-suited to a civic /\n * hyperlocal context where reacting to a complaint or lost-and-found\n * post deserves a distinct \"I support you\" signal.\n */\nexport const REACTION_TYPE_VALUES = [\n 'like',\n 'love',\n 'haha',\n 'wow',\n 'sad',\n 'angry',\n 'support',\n] as const;\nexport const reactionTypeSchema = z.enum(REACTION_TYPE_VALUES);\nexport type ReactionType = z.infer<typeof reactionTypeSchema>;\n\n// ─── Social graph ──────────────────────────────────────────────────────────\n\n/**\n * Connection (mutual friend) lifecycle.\n *\n * `pending` — requester sent it, recipient hasn't acted yet.\n * `accepted` — both sides confirmed; full visibility regardless of level.\n * `declined` — recipient rejected; surfaces as \"you can't reconnect for X days\" on the requester side.\n * `blocked` — either side blocked; permanently prevents reconnection.\n */\nexport const CONNECTION_STATUS_VALUES = [\n 'pending',\n 'accepted',\n 'declined',\n 'blocked',\n] as const;\nexport const connectionStatusSchema = z.enum(CONNECTION_STATUS_VALUES);\nexport type ConnectionStatus = z.infer<typeof connectionStatusSchema>;\n\n// ─── Profile privacy ───────────────────────────────────────────────────────\n\n/**\n * Profile-level privacy (single toggle in Settings).\n *\n * `public` — anyone in the matching geo scope sees this user's posts.\n * `private` — only accepted Connections see this user's posts. Visibility\n * level on each post still applies on top (a private post at\n * state level is visible only to Connections who are in the\n * same state).\n */\nexport const PROFILE_PRIVACY_VALUES = ['public', 'private'] as const;\nexport const profilePrivacySchema = z.enum(PROFILE_PRIVACY_VALUES);\nexport type ProfilePrivacy = z.infer<typeof profilePrivacySchema>;\n\n// ─── Moderation ────────────────────────────────────────────────────────────\n\n/**\n * Reasons a piece of content can be reported. The list is deliberately\n * short — finer categorization can come from the optional `details`\n * free-text field on a report.\n */\nexport const REPORT_REASON_VALUES = [\n 'spam',\n 'harassment',\n 'hate_speech',\n 'misinformation',\n 'sexual_content',\n 'violence',\n 'self_harm',\n 'illegal_content',\n 'other',\n] as const;\nexport const reportReasonSchema = z.enum(REPORT_REASON_VALUES);\nexport type ReportReason = z.infer<typeof reportReasonSchema>;\n\n/** Moderator decision states on a report. */\nexport const REPORT_STATUS_VALUES = [\n 'pending',\n 'reviewing',\n 'actioned',\n 'dismissed',\n] as const;\nexport const reportStatusSchema = z.enum(REPORT_STATUS_VALUES);\nexport type ReportStatus = z.infer<typeof reportStatusSchema>;\n\n// ─── Feed sort ─────────────────────────────────────────────────────────────\n\n/**\n * Sort options exposed in the feed UI.\n *\n * `hot` — the default. Reddit-style score blending upvotes, comment count,\n * and recency. Best surface for civic / community engagement.\n * `new` — strict reverse-chronological. Useful when the viewer wants to\n * catch up on everything fresh.\n * `top` — highest upvote count over a configurable window (defaults to\n * 24 hours on the client).\n */\nexport const FEED_SORT_VALUES = ['hot', 'new', 'top'] as const;\nexport const feedSortSchema = z.enum(FEED_SORT_VALUES);\nexport type FeedSort = z.infer<typeof feedSortSchema>;\n"]}
1
+ {"version":3,"file":"enums.js","sourceRoot":"","sources":["../src/enums.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,+EAA+E;AAE/E;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,OAAO;IACP,UAAU;IACV,OAAO;IACP,UAAU;IACV,QAAQ;CACA,CAAC;AACX,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;AAGrE;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAoC;IACpE,KAAK,EAAE,CAAC;IACR,QAAQ,EAAE,CAAC;IACX,KAAK,EAAE,CAAC;IACR,QAAQ,EAAE,CAAC;IACX,MAAM,EAAE,CAAC;CACV,CAAC;AAEF,+EAA+E;AAE/E;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,CAAU,CAAC;AAC/F,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;AAG7D,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,MAAM;IACN,MAAM;IACN,MAAM;IACN,KAAK;IACL,KAAK;IACL,OAAO;IACP,SAAS;CACD,CAAC;AACX,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AAG/D,8EAA8E;AAE9E;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,CAAU,CAAC;AAChG,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;AAGvE,8EAA8E;AAE9E;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAU,CAAC;AACrE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;AAGnE,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,MAAM;IACN,YAAY;IACZ,aAAa;IACb,gBAAgB;IAChB,gBAAgB;IAChB,UAAU;IACV,WAAW;IACX,iBAAiB;IACjB,OAAO;CACC,CAAC;AACX,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AAG/D,6CAA6C;AAC7C,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,CAAU,CAAC;AAC/F,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AAG/D,8EAA8E;AAE9E;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAU,CAAC;AAC/D,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC","sourcesContent":["/**\n * Enum value arrays shared across the community feature.\n *\n * Each enum is exported as both a `*_VALUES` tuple and the corresponding\n * Zod schema so backend Mongoose models and frontend forms validate\n * against the exact same set.\n *\n * @module community-schema/enums\n */\n\nimport { z } from 'zod';\n\n// ─── Visibility scope ───────────────────────────────────────────────────────\n\n/**\n * The geographic level at which a piece of content is posted.\n *\n * Cascade-upward semantics: a viewer whose feed filter is set to L\n * sees every post at L and every BROADER level that matches their\n * area lineage. So:\n * - `local` → matches local + district + state + national + global within the viewer's locality / village / urbanBody / etc.\n * - `district` → matches district + state + national + global within the viewer's district\n * - `state` → matches state + national + global within the viewer's state\n * - `national` → matches national + global within the viewer's country\n * - `global` → matches global only\n *\n * The full meaning of \"local\" is \"anything below district\" — i.e., the\n * deepest area-lineage match (locality, village, urbanWard, …).\n */\nexport const VISIBILITY_LEVEL_VALUES = [\n 'local',\n 'district',\n 'state',\n 'national',\n 'global',\n] as const;\nexport const visibilityLevelSchema = z.enum(VISIBILITY_LEVEL_VALUES);\nexport type VisibilityLevel = z.infer<typeof visibilityLevelSchema>;\n\n/**\n * Numeric ranking used for the cascade-upward query and for sorting\n * filter chips. `local` is the deepest (most specific), `global` the\n * shallowest. Higher rank = broader scope.\n */\nexport const VISIBILITY_LEVEL_RANK: Record<VisibilityLevel, number> = {\n local: 0,\n district: 1,\n state: 2,\n national: 3,\n global: 4,\n};\n\n// ─── Polymorphic content kind ───────────────────────────────────────────────\n\n/**\n * The kinds of items that show up in the feed. `post` is the new\n * community-native kind; `lost_found`, `voice_box`, and `donate_item`\n * are existing legacy features surfaced into the feed via polymorphic\n * union.\n *\n * Engagement (upvote / reaction / comment / share) is keyed by\n * (contentKind, contentId) so every kind gets the same engagement bar.\n */\nexport const CONTENT_KIND_VALUES = ['post', 'lost_found', 'voice_box', 'donate_item'] as const;\nexport const contentKindSchema = z.enum(CONTENT_KIND_VALUES);\nexport type ContentKind = z.infer<typeof contentKindSchema>;\n\n// ─── Reactions ──────────────────────────────────────────────────────────────\n\n/**\n * Reaction types available on every feed item.\n *\n * `support` replaces Facebook's \"Care\" — better-suited to a civic /\n * hyperlocal context where reacting to a complaint or lost-and-found\n * post deserves a distinct \"I support you\" signal.\n */\nexport const REACTION_TYPE_VALUES = [\n 'like',\n 'love',\n 'haha',\n 'wow',\n 'sad',\n 'angry',\n 'support',\n] as const;\nexport const reactionTypeSchema = z.enum(REACTION_TYPE_VALUES);\nexport type ReactionType = z.infer<typeof reactionTypeSchema>;\n\n// ─── Social graph ──────────────────────────────────────────────────────────\n\n/**\n * Connection (mutual friend) lifecycle.\n *\n * `pending` — requester sent it, recipient hasn't acted yet.\n * `accepted` — both sides confirmed; full visibility regardless of level.\n * `declined` — recipient rejected; surfaces as \"you can't reconnect for X days\" on the requester side.\n * `blocked` — either side blocked; permanently prevents reconnection.\n */\nexport const CONNECTION_STATUS_VALUES = ['pending', 'accepted', 'declined', 'blocked'] as const;\nexport const connectionStatusSchema = z.enum(CONNECTION_STATUS_VALUES);\nexport type ConnectionStatus = z.infer<typeof connectionStatusSchema>;\n\n// ─── Profile privacy ───────────────────────────────────────────────────────\n\n/**\n * Profile-level privacy (single toggle in Settings).\n *\n * `public` — anyone in the matching geo scope sees this user's posts.\n * `private` — only accepted Connections see this user's posts. Visibility\n * level on each post still applies on top (a private post at\n * state level is visible only to Connections who are in the\n * same state).\n */\nexport const PROFILE_PRIVACY_VALUES = ['public', 'private'] as const;\nexport const profilePrivacySchema = z.enum(PROFILE_PRIVACY_VALUES);\nexport type ProfilePrivacy = z.infer<typeof profilePrivacySchema>;\n\n// ─── Moderation ────────────────────────────────────────────────────────────\n\n/**\n * Reasons a piece of content can be reported. The list is deliberately\n * short — finer categorization can come from the optional `details`\n * free-text field on a report.\n */\nexport const REPORT_REASON_VALUES = [\n 'spam',\n 'harassment',\n 'hate_speech',\n 'misinformation',\n 'sexual_content',\n 'violence',\n 'self_harm',\n 'illegal_content',\n 'other',\n] as const;\nexport const reportReasonSchema = z.enum(REPORT_REASON_VALUES);\nexport type ReportReason = z.infer<typeof reportReasonSchema>;\n\n/** Moderator decision states on a report. */\nexport const REPORT_STATUS_VALUES = ['pending', 'reviewing', 'actioned', 'dismissed'] as const;\nexport const reportStatusSchema = z.enum(REPORT_STATUS_VALUES);\nexport type ReportStatus = z.infer<typeof reportStatusSchema>;\n\n// ─── Feed sort ─────────────────────────────────────────────────────────────\n\n/**\n * Sort options exposed in the feed UI.\n *\n * `hot` — the default. Reddit-style score blending upvotes, comment count,\n * and recency. Best surface for civic / community engagement.\n * `new` — strict reverse-chronological. Useful when the viewer wants to\n * catch up on everything fresh.\n * `top` — highest upvote count over a configurable window (defaults to\n * 24 hours on the client).\n */\nexport const FEED_SORT_VALUES = ['hot', 'new', 'top'] as const;\nexport const feedSortSchema = z.enum(FEED_SORT_VALUES);\nexport type FeedSort = z.infer<typeof feedSortSchema>;\n"]}
package/dist/feed.d.ts CHANGED
@@ -54,6 +54,37 @@ export declare const lostFoundFeedItemSchema: z.ZodObject<{
54
54
  }, z.core.$strip>>;
55
55
  }, z.core.$strip>;
56
56
  export type LostFoundFeedItem = z.infer<typeof lostFoundFeedItemSchema>;
57
+ /**
58
+ * Donated-item listing projected into the feed. Mirrors the engagement
59
+ * + author shape every feed card needs, plus the donate-item-specific
60
+ * fields the card renders (item name, category, condition, status).
61
+ */
62
+ export declare const donateItemFeedItemSchema: z.ZodObject<{
63
+ _id: z.ZodString;
64
+ contentKind: z.ZodLiteral<"donate_item">;
65
+ author: z.ZodObject<{
66
+ userId: z.ZodString;
67
+ displayName: z.ZodString;
68
+ avatarUrl: z.ZodOptional<z.ZodString>;
69
+ localName: z.ZodOptional<z.ZodString>;
70
+ }, z.core.$strip>;
71
+ itemName: z.ZodString;
72
+ description: z.ZodOptional<z.ZodString>;
73
+ category: z.ZodString;
74
+ condition: z.ZodOptional<z.ZodString>;
75
+ imageUrl: z.ZodOptional<z.ZodString>;
76
+ status: z.ZodString;
77
+ upvoteCount: z.ZodNumber;
78
+ commentCount: z.ZodNumber;
79
+ reactionCounts: z.ZodRecord<z.ZodString, z.ZodNumber>;
80
+ createdAt: z.ZodString;
81
+ viewer: z.ZodOptional<z.ZodObject<{
82
+ upvoted: z.ZodBoolean;
83
+ reaction: z.ZodNullable<z.ZodString>;
84
+ isAuthor: z.ZodBoolean;
85
+ }, z.core.$strip>>;
86
+ }, z.core.$strip>;
87
+ export type DonateItemFeedItem = z.infer<typeof donateItemFeedItemSchema>;
57
88
  /**
58
89
  * Voice-box (complaint or suggestion) item projected into the feed.
59
90
  */
@@ -183,6 +214,30 @@ export declare const feedItemSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
183
214
  reaction: z.ZodNullable<z.ZodString>;
184
215
  isAuthor: z.ZodBoolean;
185
216
  }, z.core.$strip>>;
217
+ }, z.core.$strip>, z.ZodObject<{
218
+ _id: z.ZodString;
219
+ contentKind: z.ZodLiteral<"donate_item">;
220
+ author: z.ZodObject<{
221
+ userId: z.ZodString;
222
+ displayName: z.ZodString;
223
+ avatarUrl: z.ZodOptional<z.ZodString>;
224
+ localName: z.ZodOptional<z.ZodString>;
225
+ }, z.core.$strip>;
226
+ itemName: z.ZodString;
227
+ description: z.ZodOptional<z.ZodString>;
228
+ category: z.ZodString;
229
+ condition: z.ZodOptional<z.ZodString>;
230
+ imageUrl: z.ZodOptional<z.ZodString>;
231
+ status: z.ZodString;
232
+ upvoteCount: z.ZodNumber;
233
+ commentCount: z.ZodNumber;
234
+ reactionCounts: z.ZodRecord<z.ZodString, z.ZodNumber>;
235
+ createdAt: z.ZodString;
236
+ viewer: z.ZodOptional<z.ZodObject<{
237
+ upvoted: z.ZodBoolean;
238
+ reaction: z.ZodNullable<z.ZodString>;
239
+ isAuthor: z.ZodBoolean;
240
+ }, z.core.$strip>>;
186
241
  }, z.core.$strip>], "contentKind">;
187
242
  export type FeedItem = z.infer<typeof feedItemSchema>;
188
243
  export declare const feedQueryParamsSchema: z.ZodObject<{
@@ -303,6 +358,30 @@ export declare const feedResponseSchema: z.ZodObject<{
303
358
  reaction: z.ZodNullable<z.ZodString>;
304
359
  isAuthor: z.ZodBoolean;
305
360
  }, z.core.$strip>>;
361
+ }, z.core.$strip>, z.ZodObject<{
362
+ _id: z.ZodString;
363
+ contentKind: z.ZodLiteral<"donate_item">;
364
+ author: z.ZodObject<{
365
+ userId: z.ZodString;
366
+ displayName: z.ZodString;
367
+ avatarUrl: z.ZodOptional<z.ZodString>;
368
+ localName: z.ZodOptional<z.ZodString>;
369
+ }, z.core.$strip>;
370
+ itemName: z.ZodString;
371
+ description: z.ZodOptional<z.ZodString>;
372
+ category: z.ZodString;
373
+ condition: z.ZodOptional<z.ZodString>;
374
+ imageUrl: z.ZodOptional<z.ZodString>;
375
+ status: z.ZodString;
376
+ upvoteCount: z.ZodNumber;
377
+ commentCount: z.ZodNumber;
378
+ reactionCounts: z.ZodRecord<z.ZodString, z.ZodNumber>;
379
+ createdAt: z.ZodString;
380
+ viewer: z.ZodOptional<z.ZodObject<{
381
+ upvoted: z.ZodBoolean;
382
+ reaction: z.ZodNullable<z.ZodString>;
383
+ isAuthor: z.ZodBoolean;
384
+ }, z.core.$strip>>;
306
385
  }, z.core.$strip>], "contentKind">>;
307
386
  nextCursor: z.ZodNullable<z.ZodString>;
308
387
  }, z.core.$strip>;
@@ -1 +1 @@
1
- {"version":3,"file":"feed.d.ts","sourceRoot":"","sources":["../src/feed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAMhD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;iBA0BlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE;;GAEG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;iBAuBjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAItE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAIzB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAItD,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;iBAShC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAG7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAG9D,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"feed.d.ts","sourceRoot":"","sources":["../src/feed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAMhD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;iBA0BlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;iBA+BnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E;;GAEG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;iBAuBjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAItE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAKzB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAItD,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;iBAShC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAG7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAG9D,OAAO,EAAE,cAAc,EAAE,CAAC"}
package/dist/feed.js CHANGED
@@ -53,6 +53,39 @@ export const lostFoundFeedItemSchema = z.object({
53
53
  })
54
54
  .optional(),
55
55
  });
56
+ /**
57
+ * Donated-item listing projected into the feed. Mirrors the engagement
58
+ * + author shape every feed card needs, plus the donate-item-specific
59
+ * fields the card renders (item name, category, condition, status).
60
+ */
61
+ export const donateItemFeedItemSchema = z.object({
62
+ _id: z.string(),
63
+ contentKind: z.literal('donate_item'),
64
+ author: postAuthorSnapshotSchema,
65
+ /** Display name of the item (maps to ReformItemDonation.itemName). */
66
+ itemName: z.string(),
67
+ description: z.string().optional(),
68
+ /** Free-text category (clothing / books / electronics / etc.). */
69
+ category: z.string(),
70
+ /** Free-text condition label (new / good / used / etc.). Optional. */
71
+ condition: z.string().optional(),
72
+ /** First photo if present — used as the card's hero image. */
73
+ imageUrl: z.string().url().optional(),
74
+ /** Donation lifecycle status (pending / verified / picked / etc.) so
75
+ * the card can show an appropriate badge. */
76
+ status: z.string(),
77
+ upvoteCount: z.number().int().nonnegative(),
78
+ commentCount: z.number().int().nonnegative(),
79
+ reactionCounts: z.record(z.string(), z.number().int().nonnegative()),
80
+ createdAt: z.string().datetime(),
81
+ viewer: z
82
+ .object({
83
+ upvoted: z.boolean(),
84
+ reaction: z.string().nullable(),
85
+ isAuthor: z.boolean(),
86
+ })
87
+ .optional(),
88
+ });
56
89
  /**
57
90
  * Voice-box (complaint or suggestion) item projected into the feed.
58
91
  */
@@ -81,6 +114,7 @@ export const feedItemSchema = z.discriminatedUnion('contentKind', [
81
114
  communityPostWireSchema,
82
115
  lostFoundFeedItemSchema,
83
116
  voiceBoxFeedItemSchema,
117
+ donateItemFeedItemSchema,
84
118
  ]);
85
119
  // ─── Query + response ──────────────────────────────────────────────────────
86
120
  export const feedQueryParamsSchema = z.object({
package/dist/feed.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"feed.js","sourceRoot":"","sources":["../src/feed.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Feed wire shapes — polymorphic union of community posts, lost-and-\n * found reports, and voice-box (complaints + suggestions).\n *\n * The feed is a SINGLE list typed as `FeedItem = Post | LostFound | VoiceBox`.\n * The client discriminates on `contentKind` and renders the matching\n * card component.\n *\n * Strategy notes (server-side):\n * - Read-time fanout. The server runs ONE parallel query per kind\n * filtered by the same area-lineage match and the same cascade-\n * upward visibility rule, then merges and sorts by `hot` score.\n * - Cursor-based pagination. The cursor is opaque (base64 of\n * `<sortKey>|<_id>`) so we can swap sort algorithms without a\n * breaking change.\n * - LostFound / VoiceBox shapes are pruned to the fields the feed\n * card needs — they keep their richer detail-page shape in their\n * own schemas (lost-found-schema, voice-box-schema if/when those\n * get factored out).\n *\n * @module community-schema/feed\n */\n\nimport { z } from 'zod';\nimport { FEED_PAGE_SIZE } from './constants.js';\nimport { feedSortSchema, visibilityLevelSchema } from './enums.js';\nimport { communityPostWireSchema, postAuthorSnapshotSchema } from './post.js';\n\n// ─── Per-kind feed envelopes ───────────────────────────────────────────────\n\n/**\n * Lost-and-found item projected into the feed. Mirrors the engagement\n * + author shape every feed card needs, plus a small handful of\n * lost-found-specific fields used by the card.\n */\nexport const lostFoundFeedItemSchema = z.object({\n _id: z.string(),\n contentKind: z.literal('lost_found'),\n author: postAuthorSnapshotSchema,\n\n /** `'lost'` or `'found'` — drives the card chip colour. */\n kind: z.enum(['lost', 'found']),\n title: z.string(),\n description: z.string(),\n /** Single primary image rendered on the card. */\n imageUrl: z.string().url().optional(),\n\n /** Inherited engagement axis — same shape as posts. */\n upvoteCount: z.number().int().nonnegative(),\n commentCount: z.number().int().nonnegative(),\n reactionCounts: z.record(z.string(), z.number().int().nonnegative()),\n\n createdAt: z.string().datetime(),\n\n viewer: z\n .object({\n upvoted: z.boolean(),\n reaction: z.string().nullable(),\n isAuthor: z.boolean(),\n })\n .optional(),\n});\nexport type LostFoundFeedItem = z.infer<typeof lostFoundFeedItemSchema>;\n\n/**\n * Voice-box (complaint or suggestion) item projected into the feed.\n */\nexport const voiceBoxFeedItemSchema = z.object({\n _id: z.string(),\n contentKind: z.literal('voice_box'),\n author: postAuthorSnapshotSchema,\n\n /** `'complaint'` or `'suggestion'` — drives the card chip colour. */\n kind: z.enum(['complaint', 'suggestion']),\n title: z.string(),\n description: z.string(),\n\n upvoteCount: z.number().int().nonnegative(),\n commentCount: z.number().int().nonnegative(),\n reactionCounts: z.record(z.string(), z.number().int().nonnegative()),\n\n createdAt: z.string().datetime(),\n\n viewer: z\n .object({\n upvoted: z.boolean(),\n reaction: z.string().nullable(),\n isAuthor: z.boolean(),\n })\n .optional(),\n});\nexport type VoiceBoxFeedItem = z.infer<typeof voiceBoxFeedItemSchema>;\n\n// ─── Polymorphic union ─────────────────────────────────────────────────────\n\nexport const feedItemSchema = z.discriminatedUnion('contentKind', [\n communityPostWireSchema,\n lostFoundFeedItemSchema,\n voiceBoxFeedItemSchema,\n]);\nexport type FeedItem = z.infer<typeof feedItemSchema>;\n\n// ─── Query + response ──────────────────────────────────────────────────────\n\nexport const feedQueryParamsSchema = z.object({\n /** The viewer's chosen feed level. Cascade-upward semantics apply. */\n level: visibilityLevelSchema,\n /** Sort algorithm. Defaults to `hot` server-side when omitted. */\n sort: feedSortSchema.optional(),\n /** Opaque pagination cursor returned by the previous page. */\n cursor: z.string().optional(),\n /** Page size; defaults to `FEED_PAGE_SIZE` server-side. */\n pageSize: z.number().int().positive().max(50).optional(),\n});\nexport type FeedQueryParams = z.infer<typeof feedQueryParamsSchema>;\n\nexport const feedResponseSchema = z.object({\n items: z.array(feedItemSchema),\n nextCursor: z.string().nullable(),\n});\nexport type FeedResponse = z.infer<typeof feedResponseSchema>;\n\n// Re-export so consumers don't have to also import constants.ts.\nexport { FEED_PAGE_SIZE };\n"]}
1
+ {"version":3,"file":"feed.js","sourceRoot":"","sources":["../src/feed.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Feed wire shapes — polymorphic union of community posts, lost-and-\n * found reports, and voice-box (complaints + suggestions).\n *\n * The feed is a SINGLE list typed as `FeedItem = Post | LostFound | VoiceBox`.\n * The client discriminates on `contentKind` and renders the matching\n * card component.\n *\n * Strategy notes (server-side):\n * - Read-time fanout. The server runs ONE parallel query per kind\n * filtered by the same area-lineage match and the same cascade-\n * upward visibility rule, then merges and sorts by `hot` score.\n * - Cursor-based pagination. The cursor is opaque (base64 of\n * `<sortKey>|<_id>`) so we can swap sort algorithms without a\n * breaking change.\n * - LostFound / VoiceBox shapes are pruned to the fields the feed\n * card needs — they keep their richer detail-page shape in their\n * own schemas (lost-found-schema, voice-box-schema if/when those\n * get factored out).\n *\n * @module community-schema/feed\n */\n\nimport { z } from 'zod';\nimport { FEED_PAGE_SIZE } from './constants.js';\nimport { feedSortSchema, visibilityLevelSchema } from './enums.js';\nimport { communityPostWireSchema, postAuthorSnapshotSchema } from './post.js';\n\n// ─── Per-kind feed envelopes ───────────────────────────────────────────────\n\n/**\n * Lost-and-found item projected into the feed. Mirrors the engagement\n * + author shape every feed card needs, plus a small handful of\n * lost-found-specific fields used by the card.\n */\nexport const lostFoundFeedItemSchema = z.object({\n _id: z.string(),\n contentKind: z.literal('lost_found'),\n author: postAuthorSnapshotSchema,\n\n /** `'lost'` or `'found'` — drives the card chip colour. */\n kind: z.enum(['lost', 'found']),\n title: z.string(),\n description: z.string(),\n /** Single primary image rendered on the card. */\n imageUrl: z.string().url().optional(),\n\n /** Inherited engagement axis — same shape as posts. */\n upvoteCount: z.number().int().nonnegative(),\n commentCount: z.number().int().nonnegative(),\n reactionCounts: z.record(z.string(), z.number().int().nonnegative()),\n\n createdAt: z.string().datetime(),\n\n viewer: z\n .object({\n upvoted: z.boolean(),\n reaction: z.string().nullable(),\n isAuthor: z.boolean(),\n })\n .optional(),\n});\nexport type LostFoundFeedItem = z.infer<typeof lostFoundFeedItemSchema>;\n\n/**\n * Donated-item listing projected into the feed. Mirrors the engagement\n * + author shape every feed card needs, plus the donate-item-specific\n * fields the card renders (item name, category, condition, status).\n */\nexport const donateItemFeedItemSchema = z.object({\n _id: z.string(),\n contentKind: z.literal('donate_item'),\n author: postAuthorSnapshotSchema,\n\n /** Display name of the item (maps to ReformItemDonation.itemName). */\n itemName: z.string(),\n description: z.string().optional(),\n /** Free-text category (clothing / books / electronics / etc.). */\n category: z.string(),\n /** Free-text condition label (new / good / used / etc.). Optional. */\n condition: z.string().optional(),\n /** First photo if present — used as the card's hero image. */\n imageUrl: z.string().url().optional(),\n /** Donation lifecycle status (pending / verified / picked / etc.) so\n * the card can show an appropriate badge. */\n status: z.string(),\n\n upvoteCount: z.number().int().nonnegative(),\n commentCount: z.number().int().nonnegative(),\n reactionCounts: z.record(z.string(), z.number().int().nonnegative()),\n\n createdAt: z.string().datetime(),\n\n viewer: z\n .object({\n upvoted: z.boolean(),\n reaction: z.string().nullable(),\n isAuthor: z.boolean(),\n })\n .optional(),\n});\nexport type DonateItemFeedItem = z.infer<typeof donateItemFeedItemSchema>;\n\n/**\n * Voice-box (complaint or suggestion) item projected into the feed.\n */\nexport const voiceBoxFeedItemSchema = z.object({\n _id: z.string(),\n contentKind: z.literal('voice_box'),\n author: postAuthorSnapshotSchema,\n\n /** `'complaint'` or `'suggestion'` — drives the card chip colour. */\n kind: z.enum(['complaint', 'suggestion']),\n title: z.string(),\n description: z.string(),\n\n upvoteCount: z.number().int().nonnegative(),\n commentCount: z.number().int().nonnegative(),\n reactionCounts: z.record(z.string(), z.number().int().nonnegative()),\n\n createdAt: z.string().datetime(),\n\n viewer: z\n .object({\n upvoted: z.boolean(),\n reaction: z.string().nullable(),\n isAuthor: z.boolean(),\n })\n .optional(),\n});\nexport type VoiceBoxFeedItem = z.infer<typeof voiceBoxFeedItemSchema>;\n\n// ─── Polymorphic union ─────────────────────────────────────────────────────\n\nexport const feedItemSchema = z.discriminatedUnion('contentKind', [\n communityPostWireSchema,\n lostFoundFeedItemSchema,\n voiceBoxFeedItemSchema,\n donateItemFeedItemSchema,\n]);\nexport type FeedItem = z.infer<typeof feedItemSchema>;\n\n// ─── Query + response ──────────────────────────────────────────────────────\n\nexport const feedQueryParamsSchema = z.object({\n /** The viewer's chosen feed level. Cascade-upward semantics apply. */\n level: visibilityLevelSchema,\n /** Sort algorithm. Defaults to `hot` server-side when omitted. */\n sort: feedSortSchema.optional(),\n /** Opaque pagination cursor returned by the previous page. */\n cursor: z.string().optional(),\n /** Page size; defaults to `FEED_PAGE_SIZE` server-side. */\n pageSize: z.number().int().positive().max(50).optional(),\n});\nexport type FeedQueryParams = z.infer<typeof feedQueryParamsSchema>;\n\nexport const feedResponseSchema = z.object({\n items: z.array(feedItemSchema),\n nextCursor: z.string().nullable(),\n});\nexport type FeedResponse = z.infer<typeof feedResponseSchema>;\n\n// Re-export so consumers don't have to also import constants.ts.\nexport { FEED_PAGE_SIZE };\n"]}
package/dist/group.d.ts CHANGED
@@ -41,8 +41,119 @@ export declare const createGroupBodySchema: z.ZodObject<{
41
41
  memberUserIds: z.ZodArray<z.ZodString>;
42
42
  }, z.core.$strip>;
43
43
  export type CreateGroupBody = z.infer<typeof createGroupBodySchema>;
44
+ /**
45
+ * PATCH body for `/groups/:groupId`. All fields optional; sending
46
+ * `null` for `description` or `imageUrl` clears that field. Sending
47
+ * `name` requires the new value to satisfy the min/max constraints.
48
+ */
49
+ export declare const updateGroupBodySchema: z.ZodObject<{
50
+ name: z.ZodOptional<z.ZodString>;
51
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
52
+ imageUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
53
+ }, z.core.$strip>;
54
+ export type UpdateGroupBody = z.infer<typeof updateGroupBodySchema>;
44
55
  export declare const addGroupMembersBodySchema: z.ZodObject<{
45
56
  userIds: z.ZodArray<z.ZodString>;
46
57
  }, z.core.$strip>;
47
58
  export type AddGroupMembersBody = z.infer<typeof addGroupMembersBodySchema>;
59
+ /**
60
+ * One row in `GET /groups/:groupId/members` or the inlined member list
61
+ * on `GET /groups/:groupId`. Carries display info so the roster
62
+ * renders without an N+1 profile fetch.
63
+ */
64
+ export declare const groupMemberWireSchema: z.ZodObject<{
65
+ userId: z.ZodString;
66
+ displayName: z.ZodString;
67
+ avatarUrl: z.ZodOptional<z.ZodString>;
68
+ localName: z.ZodOptional<z.ZodString>;
69
+ role: z.ZodEnum<{
70
+ owner: "owner";
71
+ admin: "admin";
72
+ member: "member";
73
+ }>;
74
+ joinedAt: z.ZodString;
75
+ }, z.core.$strip>;
76
+ export type GroupMemberWire = z.infer<typeof groupMemberWireSchema>;
77
+ /**
78
+ * `GET /groups/:groupId` returns the full group + the first page of
79
+ * members. Larger groups paginate the members via
80
+ * `GET /groups/:groupId/members?cursor=…`.
81
+ */
82
+ export declare const communityGroupWithMembersSchema: z.ZodObject<{
83
+ _id: z.ZodString;
84
+ conversationId: z.ZodString;
85
+ name: z.ZodString;
86
+ description: z.ZodOptional<z.ZodString>;
87
+ imageUrl: z.ZodOptional<z.ZodString>;
88
+ createdByUserId: z.ZodString;
89
+ memberCount: z.ZodNumber;
90
+ createdAt: z.ZodString;
91
+ viewer: z.ZodOptional<z.ZodObject<{
92
+ role: z.ZodEnum<{
93
+ owner: "owner";
94
+ admin: "admin";
95
+ member: "member";
96
+ }>;
97
+ joinedAt: z.ZodString;
98
+ }, z.core.$strip>>;
99
+ members: z.ZodArray<z.ZodObject<{
100
+ userId: z.ZodString;
101
+ displayName: z.ZodString;
102
+ avatarUrl: z.ZodOptional<z.ZodString>;
103
+ localName: z.ZodOptional<z.ZodString>;
104
+ role: z.ZodEnum<{
105
+ owner: "owner";
106
+ admin: "admin";
107
+ member: "member";
108
+ }>;
109
+ joinedAt: z.ZodString;
110
+ }, z.core.$strip>>;
111
+ membersNextCursor: z.ZodNullable<z.ZodString>;
112
+ }, z.core.$strip>;
113
+ export type CommunityGroupWithMembers = z.infer<typeof communityGroupWithMembersSchema>;
114
+ /**
115
+ * Response shape for `GET /groups` (groups the viewer is a member of)
116
+ * and `GET /groups/discover` (Phase 7+ — groups in viewer's geo scope).
117
+ *
118
+ * `nextCursor` is an opaque base64 string the client passes back to
119
+ * fetch the next page; null when the current page is the last.
120
+ */
121
+ export declare const groupsListResponseSchema: z.ZodObject<{
122
+ items: z.ZodArray<z.ZodObject<{
123
+ _id: z.ZodString;
124
+ conversationId: z.ZodString;
125
+ name: z.ZodString;
126
+ description: z.ZodOptional<z.ZodString>;
127
+ imageUrl: z.ZodOptional<z.ZodString>;
128
+ createdByUserId: z.ZodString;
129
+ memberCount: z.ZodNumber;
130
+ createdAt: z.ZodString;
131
+ viewer: z.ZodOptional<z.ZodObject<{
132
+ role: z.ZodEnum<{
133
+ owner: "owner";
134
+ admin: "admin";
135
+ member: "member";
136
+ }>;
137
+ joinedAt: z.ZodString;
138
+ }, z.core.$strip>>;
139
+ }, z.core.$strip>>;
140
+ nextCursor: z.ZodNullable<z.ZodString>;
141
+ }, z.core.$strip>;
142
+ export type GroupsListResponse = z.infer<typeof groupsListResponseSchema>;
143
+ export declare const groupMembersListResponseSchema: z.ZodObject<{
144
+ items: z.ZodArray<z.ZodObject<{
145
+ userId: z.ZodString;
146
+ displayName: z.ZodString;
147
+ avatarUrl: z.ZodOptional<z.ZodString>;
148
+ localName: z.ZodOptional<z.ZodString>;
149
+ role: z.ZodEnum<{
150
+ owner: "owner";
151
+ admin: "admin";
152
+ member: "member";
153
+ }>;
154
+ joinedAt: z.ZodString;
155
+ }, z.core.$strip>>;
156
+ nextCursor: z.ZodNullable<z.ZodString>;
157
+ }, z.core.$strip>;
158
+ export type GroupMembersListResponse = z.infer<typeof groupMembersListResponseSchema>;
48
159
  //# sourceMappingURL=group.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../src/group.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,eAAO,MAAM,eAAe;;;;EAAuC,CAAC;AACpE,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;iBAmBnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAI1E,eAAO,MAAM,qBAAqB;;;;iBAKhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAIpE,eAAO,MAAM,yBAAyB;;iBAEpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
1
+ {"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../src/group.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,eAAe;;;;EAAuC,CAAC;AACpE,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;iBAmBnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAI1E,eAAO,MAAM,qBAAqB;;;;iBAKhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAIpE;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;;iBAIhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAIpE,eAAO,MAAM,yBAAyB;;iBAEpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAI5E;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;iBAOhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;;GAIG;AACH,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAG1C,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAIxF;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;iBAGnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;iBAGzC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC"}
package/dist/group.js CHANGED
@@ -10,7 +10,7 @@
10
10
  * @module community-schema/group
11
11
  */
12
12
  import { z } from 'zod';
13
- import { GROUP_MAX_MEMBERS, GROUP_NAME_MAX_CHARS, GROUP_NAME_MIN_CHARS, } from './constants.js';
13
+ import { GROUP_MAX_MEMBERS, GROUP_NAME_MAX_CHARS, GROUP_NAME_MIN_CHARS } from './constants.js';
14
14
  export const groupRoleSchema = z.enum(['owner', 'admin', 'member']);
15
15
  export const communityGroupWireSchema = z.object({
16
16
  _id: z.string(),
@@ -38,8 +38,58 @@ export const createGroupBodySchema = z.object({
38
38
  /** Initial member user ids (excluding the creator who becomes owner). */
39
39
  memberUserIds: z.array(z.string()).max(GROUP_MAX_MEMBERS - 1),
40
40
  });
41
+ // ─── Update body (owner / admin only) ──────────────────────────────────────
42
+ /**
43
+ * PATCH body for `/groups/:groupId`. All fields optional; sending
44
+ * `null` for `description` or `imageUrl` clears that field. Sending
45
+ * `name` requires the new value to satisfy the min/max constraints.
46
+ */
47
+ export const updateGroupBodySchema = z.object({
48
+ name: z.string().min(GROUP_NAME_MIN_CHARS).max(GROUP_NAME_MAX_CHARS).optional(),
49
+ description: z.string().max(500).nullable().optional(),
50
+ imageUrl: z.string().url().nullable().optional(),
51
+ });
41
52
  // ─── Add / remove members ──────────────────────────────────────────────────
42
53
  export const addGroupMembersBodySchema = z.object({
43
54
  userIds: z.array(z.string()).min(1).max(50),
44
55
  });
56
+ // ─── Member roster row ─────────────────────────────────────────────────────
57
+ /**
58
+ * One row in `GET /groups/:groupId/members` or the inlined member list
59
+ * on `GET /groups/:groupId`. Carries display info so the roster
60
+ * renders without an N+1 profile fetch.
61
+ */
62
+ export const groupMemberWireSchema = z.object({
63
+ userId: z.string(),
64
+ displayName: z.string(),
65
+ avatarUrl: z.string().url().optional(),
66
+ localName: z.string().optional(),
67
+ role: groupRoleSchema,
68
+ joinedAt: z.string().datetime(),
69
+ });
70
+ /**
71
+ * `GET /groups/:groupId` returns the full group + the first page of
72
+ * members. Larger groups paginate the members via
73
+ * `GET /groups/:groupId/members?cursor=…`.
74
+ */
75
+ export const communityGroupWithMembersSchema = communityGroupWireSchema.extend({
76
+ members: z.array(groupMemberWireSchema),
77
+ membersNextCursor: z.string().nullable(),
78
+ });
79
+ // ─── List response ─────────────────────────────────────────────────────────
80
+ /**
81
+ * Response shape for `GET /groups` (groups the viewer is a member of)
82
+ * and `GET /groups/discover` (Phase 7+ — groups in viewer's geo scope).
83
+ *
84
+ * `nextCursor` is an opaque base64 string the client passes back to
85
+ * fetch the next page; null when the current page is the last.
86
+ */
87
+ export const groupsListResponseSchema = z.object({
88
+ items: z.array(communityGroupWireSchema),
89
+ nextCursor: z.string().nullable(),
90
+ });
91
+ export const groupMembersListResponseSchema = z.object({
92
+ items: z.array(groupMemberWireSchema),
93
+ nextCursor: z.string().nullable(),
94
+ });
45
95
  //# sourceMappingURL=group.js.map
package/dist/group.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"group.js","sourceRoot":"","sources":["../src/group.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AAExB,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;AAGpE,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,8DAA8D;IAC9D,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE;IAC1B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,GAAG,CAAC,oBAAoB,CAAC;IACpE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IAC3C,kCAAkC;IAClC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACrC,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE;IAC3B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAC3C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAEhC,+DAA+D;IAC/D,MAAM,EAAE,CAAC;SACN,MAAM,CAAC;QACN,IAAI,EAAE,eAAe;QACrB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAChC,CAAC;SACD,QAAQ,EAAE;CACd,CAAC,CAAC;AAGH,+EAA+E;AAE/E,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,GAAG,CAAC,oBAAoB,CAAC;IACpE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IAC3C,yEAAyE;IACzE,aAAa,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,iBAAiB,GAAG,CAAC,CAAC;CAC9D,CAAC,CAAC;AAGH,8EAA8E;AAE9E,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;CAC5C,CAAC,CAAC","sourcesContent":["/**\n * User-created group chat wire shapes for the Community → Chat tab.\n *\n * The community-scoped group chats are wire-compatible with the\n * generic `Conversation` shape on chat-server (so a community group\n * shows up in the inbox like any other conversation). What lives here\n * is the COMMUNITY-side metadata used to create + administer the group\n * before chat-server takes over.\n *\n * @module community-schema/group\n */\n\nimport { z } from 'zod';\nimport {\n GROUP_MAX_MEMBERS,\n GROUP_NAME_MAX_CHARS,\n GROUP_NAME_MIN_CHARS,\n} from './constants.js';\n\nexport const groupRoleSchema = z.enum(['owner', 'admin', 'member']);\nexport type GroupRole = z.infer<typeof groupRoleSchema>;\n\nexport const communityGroupWireSchema = z.object({\n _id: z.string(),\n /** The chat-server conversation id this group is bound to. */\n conversationId: z.string(),\n name: z.string().min(GROUP_NAME_MIN_CHARS).max(GROUP_NAME_MAX_CHARS),\n description: z.string().max(500).optional(),\n /** Group avatar / cover image. */\n imageUrl: z.string().url().optional(),\n createdByUserId: z.string(),\n memberCount: z.number().int().nonnegative(),\n createdAt: z.string().datetime(),\n\n /** Per-viewer state — included when the viewer is a member. */\n viewer: z\n .object({\n role: groupRoleSchema,\n joinedAt: z.string().datetime(),\n })\n .optional(),\n});\nexport type CommunityGroupWire = z.infer<typeof communityGroupWireSchema>;\n\n// ─── Create body ────────────────────────────────────────────────────────────\n\nexport const createGroupBodySchema = z.object({\n name: z.string().min(GROUP_NAME_MIN_CHARS).max(GROUP_NAME_MAX_CHARS),\n description: z.string().max(500).optional(),\n /** Initial member user ids (excluding the creator who becomes owner). */\n memberUserIds: z.array(z.string()).max(GROUP_MAX_MEMBERS - 1),\n});\nexport type CreateGroupBody = z.infer<typeof createGroupBodySchema>;\n\n// ─── Add / remove members ──────────────────────────────────────────────────\n\nexport const addGroupMembersBodySchema = z.object({\n userIds: z.array(z.string()).min(1).max(50),\n});\nexport type AddGroupMembersBody = z.infer<typeof addGroupMembersBodySchema>;\n"]}
1
+ {"version":3,"file":"group.js","sourceRoot":"","sources":["../src/group.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * User-created group chat wire shapes for the Community → Chat tab.\n *\n * The community-scoped group chats are wire-compatible with the\n * generic `Conversation` shape on chat-server (so a community group\n * shows up in the inbox like any other conversation). What lives here\n * is the COMMUNITY-side metadata used to create + administer the group\n * before chat-server takes over.\n *\n * @module community-schema/group\n */\n\nimport { z } from 'zod';\nimport { GROUP_MAX_MEMBERS, GROUP_NAME_MAX_CHARS, GROUP_NAME_MIN_CHARS } from './constants.js';\n\nexport const groupRoleSchema = z.enum(['owner', 'admin', 'member']);\nexport type GroupRole = z.infer<typeof groupRoleSchema>;\n\nexport const communityGroupWireSchema = z.object({\n _id: z.string(),\n /** The chat-server conversation id this group is bound to. */\n conversationId: z.string(),\n name: z.string().min(GROUP_NAME_MIN_CHARS).max(GROUP_NAME_MAX_CHARS),\n description: z.string().max(500).optional(),\n /** Group avatar / cover image. */\n imageUrl: z.string().url().optional(),\n createdByUserId: z.string(),\n memberCount: z.number().int().nonnegative(),\n createdAt: z.string().datetime(),\n\n /** Per-viewer state — included when the viewer is a member. */\n viewer: z\n .object({\n role: groupRoleSchema,\n joinedAt: z.string().datetime(),\n })\n .optional(),\n});\nexport type CommunityGroupWire = z.infer<typeof communityGroupWireSchema>;\n\n// ─── Create body ────────────────────────────────────────────────────────────\n\nexport const createGroupBodySchema = z.object({\n name: z.string().min(GROUP_NAME_MIN_CHARS).max(GROUP_NAME_MAX_CHARS),\n description: z.string().max(500).optional(),\n /** Initial member user ids (excluding the creator who becomes owner). */\n memberUserIds: z.array(z.string()).max(GROUP_MAX_MEMBERS - 1),\n});\nexport type CreateGroupBody = z.infer<typeof createGroupBodySchema>;\n\n// ─── Update body (owner / admin only) ──────────────────────────────────────\n\n/**\n * PATCH body for `/groups/:groupId`. All fields optional; sending\n * `null` for `description` or `imageUrl` clears that field. Sending\n * `name` requires the new value to satisfy the min/max constraints.\n */\nexport const updateGroupBodySchema = z.object({\n name: z.string().min(GROUP_NAME_MIN_CHARS).max(GROUP_NAME_MAX_CHARS).optional(),\n description: z.string().max(500).nullable().optional(),\n imageUrl: z.string().url().nullable().optional(),\n});\nexport type UpdateGroupBody = z.infer<typeof updateGroupBodySchema>;\n\n// ─── Add / remove members ──────────────────────────────────────────────────\n\nexport const addGroupMembersBodySchema = z.object({\n userIds: z.array(z.string()).min(1).max(50),\n});\nexport type AddGroupMembersBody = z.infer<typeof addGroupMembersBodySchema>;\n\n// ─── Member roster row ─────────────────────────────────────────────────────\n\n/**\n * One row in `GET /groups/:groupId/members` or the inlined member list\n * on `GET /groups/:groupId`. Carries display info so the roster\n * renders without an N+1 profile fetch.\n */\nexport const groupMemberWireSchema = z.object({\n userId: z.string(),\n displayName: z.string(),\n avatarUrl: z.string().url().optional(),\n localName: z.string().optional(),\n role: groupRoleSchema,\n joinedAt: z.string().datetime(),\n});\nexport type GroupMemberWire = z.infer<typeof groupMemberWireSchema>;\n\n/**\n * `GET /groups/:groupId` returns the full group + the first page of\n * members. Larger groups paginate the members via\n * `GET /groups/:groupId/members?cursor=…`.\n */\nexport const communityGroupWithMembersSchema = communityGroupWireSchema.extend({\n members: z.array(groupMemberWireSchema),\n membersNextCursor: z.string().nullable(),\n});\nexport type CommunityGroupWithMembers = z.infer<typeof communityGroupWithMembersSchema>;\n\n// ─── List response ─────────────────────────────────────────────────────────\n\n/**\n * Response shape for `GET /groups` (groups the viewer is a member of)\n * and `GET /groups/discover` (Phase 7+ — groups in viewer's geo scope).\n *\n * `nextCursor` is an opaque base64 string the client passes back to\n * fetch the next page; null when the current page is the last.\n */\nexport const groupsListResponseSchema = z.object({\n items: z.array(communityGroupWireSchema),\n nextCursor: z.string().nullable(),\n});\nexport type GroupsListResponse = z.infer<typeof groupsListResponseSchema>;\n\nexport const groupMembersListResponseSchema = z.object({\n items: z.array(groupMemberWireSchema),\n nextCursor: z.string().nullable(),\n});\nexport type GroupMembersListResponse = z.infer<typeof groupMembersListResponseSchema>;\n"]}