jansathi-community-schema 0.1.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.
Files changed (58) hide show
  1. package/README.md +41 -0
  2. package/dist/area.d.ts +37 -0
  3. package/dist/area.d.ts.map +1 -0
  4. package/dist/area.js +51 -0
  5. package/dist/area.js.map +1 -0
  6. package/dist/comment.d.ts +54 -0
  7. package/dist/comment.d.ts.map +1 -0
  8. package/dist/comment.js +63 -0
  9. package/dist/comment.js.map +1 -0
  10. package/dist/constants.d.ts +42 -0
  11. package/dist/constants.d.ts.map +1 -0
  12. package/dist/constants.js +47 -0
  13. package/dist/constants.js.map +1 -0
  14. package/dist/engagement.d.ts +69 -0
  15. package/dist/engagement.d.ts.map +1 -0
  16. package/dist/engagement.js +50 -0
  17. package/dist/engagement.js.map +1 -0
  18. package/dist/enums.d.ts +149 -0
  19. package/dist/enums.d.ts.map +1 -0
  20. package/dist/enums.js +143 -0
  21. package/dist/enums.js.map +1 -0
  22. package/dist/feed.d.ts +311 -0
  23. package/dist/feed.d.ts.map +1 -0
  24. package/dist/feed.js +102 -0
  25. package/dist/feed.js.map +1 -0
  26. package/dist/group.d.ts +48 -0
  27. package/dist/group.d.ts.map +1 -0
  28. package/dist/group.js +45 -0
  29. package/dist/group.js.map +1 -0
  30. package/dist/index.d.ts +30 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +43 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/media.d.ts +48 -0
  35. package/dist/media.d.ts.map +1 -0
  36. package/dist/media.js +50 -0
  37. package/dist/media.js.map +1 -0
  38. package/dist/post.d.ts +130 -0
  39. package/dist/post.d.ts.map +1 -0
  40. package/dist/post.js +103 -0
  41. package/dist/post.js.map +1 -0
  42. package/dist/profile.d.ts +116 -0
  43. package/dist/profile.d.ts.map +1 -0
  44. package/dist/profile.js +70 -0
  45. package/dist/profile.js.map +1 -0
  46. package/dist/report.d.ts +62 -0
  47. package/dist/report.d.ts.map +1 -0
  48. package/dist/report.js +30 -0
  49. package/dist/report.js.map +1 -0
  50. package/dist/settings.d.ts +66 -0
  51. package/dist/settings.d.ts.map +1 -0
  52. package/dist/settings.js +75 -0
  53. package/dist/settings.js.map +1 -0
  54. package/dist/social.d.ts +95 -0
  55. package/dist/social.d.ts.map +1 -0
  56. package/dist/social.js +85 -0
  57. package/dist/social.js.map +1 -0
  58. package/package.json +50 -0
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Enum value arrays shared across the community feature.
3
+ *
4
+ * Each enum is exported as both a `*_VALUES` tuple and the corresponding
5
+ * Zod schema so backend Mongoose models and frontend forms validate
6
+ * against the exact same set.
7
+ *
8
+ * @module community-schema/enums
9
+ */
10
+ import { z } from 'zod';
11
+ /**
12
+ * The geographic level at which a piece of content is posted.
13
+ *
14
+ * Cascade-upward semantics: a viewer whose feed filter is set to L
15
+ * sees every post at L and every BROADER level that matches their
16
+ * area lineage. So:
17
+ * - `local` → matches local + district + state + national + global within the viewer's locality / village / urbanBody / etc.
18
+ * - `district` → matches district + state + national + global within the viewer's district
19
+ * - `state` → matches state + national + global within the viewer's state
20
+ * - `national` → matches national + global within the viewer's country
21
+ * - `global` → matches global only
22
+ *
23
+ * The full meaning of "local" is "anything below district" — i.e., the
24
+ * deepest area-lineage match (locality, village, urbanWard, …).
25
+ */
26
+ export declare const VISIBILITY_LEVEL_VALUES: readonly ["local", "district", "state", "national", "global"];
27
+ export declare const visibilityLevelSchema: z.ZodEnum<{
28
+ local: "local";
29
+ district: "district";
30
+ state: "state";
31
+ national: "national";
32
+ global: "global";
33
+ }>;
34
+ export type VisibilityLevel = z.infer<typeof visibilityLevelSchema>;
35
+ /**
36
+ * Numeric ranking used for the cascade-upward query and for sorting
37
+ * filter chips. `local` is the deepest (most specific), `global` the
38
+ * shallowest. Higher rank = broader scope.
39
+ */
40
+ export declare const VISIBILITY_LEVEL_RANK: Record<VisibilityLevel, number>;
41
+ /**
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.
45
+ *
46
+ * Engagement (upvote / reaction / comment) is keyed by
47
+ * (contentKind, contentId) so every kind gets the same engagement bar.
48
+ */
49
+ export declare const CONTENT_KIND_VALUES: readonly ["post", "lost_found", "voice_box"];
50
+ export declare const contentKindSchema: z.ZodEnum<{
51
+ post: "post";
52
+ lost_found: "lost_found";
53
+ voice_box: "voice_box";
54
+ }>;
55
+ export type ContentKind = z.infer<typeof contentKindSchema>;
56
+ /**
57
+ * Reaction types available on every feed item.
58
+ *
59
+ * `support` replaces Facebook's "Care" — better-suited to a civic /
60
+ * hyperlocal context where reacting to a complaint or lost-and-found
61
+ * post deserves a distinct "I support you" signal.
62
+ */
63
+ export declare const REACTION_TYPE_VALUES: readonly ["like", "love", "haha", "wow", "sad", "angry", "support"];
64
+ export declare const reactionTypeSchema: z.ZodEnum<{
65
+ like: "like";
66
+ love: "love";
67
+ haha: "haha";
68
+ wow: "wow";
69
+ sad: "sad";
70
+ angry: "angry";
71
+ support: "support";
72
+ }>;
73
+ export type ReactionType = z.infer<typeof reactionTypeSchema>;
74
+ /**
75
+ * Connection (mutual friend) lifecycle.
76
+ *
77
+ * `pending` — requester sent it, recipient hasn't acted yet.
78
+ * `accepted` — both sides confirmed; full visibility regardless of level.
79
+ * `declined` — recipient rejected; surfaces as "you can't reconnect for X days" on the requester side.
80
+ * `blocked` — either side blocked; permanently prevents reconnection.
81
+ */
82
+ export declare const CONNECTION_STATUS_VALUES: readonly ["pending", "accepted", "declined", "blocked"];
83
+ export declare const connectionStatusSchema: z.ZodEnum<{
84
+ pending: "pending";
85
+ accepted: "accepted";
86
+ declined: "declined";
87
+ blocked: "blocked";
88
+ }>;
89
+ export type ConnectionStatus = z.infer<typeof connectionStatusSchema>;
90
+ /**
91
+ * Profile-level privacy (single toggle in Settings).
92
+ *
93
+ * `public` — anyone in the matching geo scope sees this user's posts.
94
+ * `private` — only accepted Connections see this user's posts. Visibility
95
+ * level on each post still applies on top (a private post at
96
+ * state level is visible only to Connections who are in the
97
+ * same state).
98
+ */
99
+ export declare const PROFILE_PRIVACY_VALUES: readonly ["public", "private"];
100
+ export declare const profilePrivacySchema: z.ZodEnum<{
101
+ public: "public";
102
+ private: "private";
103
+ }>;
104
+ export type ProfilePrivacy = z.infer<typeof profilePrivacySchema>;
105
+ /**
106
+ * Reasons a piece of content can be reported. The list is deliberately
107
+ * short — finer categorization can come from the optional `details`
108
+ * free-text field on a report.
109
+ */
110
+ export declare const REPORT_REASON_VALUES: readonly ["spam", "harassment", "hate_speech", "misinformation", "sexual_content", "violence", "self_harm", "illegal_content", "other"];
111
+ export declare const reportReasonSchema: z.ZodEnum<{
112
+ spam: "spam";
113
+ harassment: "harassment";
114
+ hate_speech: "hate_speech";
115
+ misinformation: "misinformation";
116
+ sexual_content: "sexual_content";
117
+ violence: "violence";
118
+ self_harm: "self_harm";
119
+ illegal_content: "illegal_content";
120
+ other: "other";
121
+ }>;
122
+ export type ReportReason = z.infer<typeof reportReasonSchema>;
123
+ /** Moderator decision states on a report. */
124
+ export declare const REPORT_STATUS_VALUES: readonly ["pending", "reviewing", "actioned", "dismissed"];
125
+ export declare const reportStatusSchema: z.ZodEnum<{
126
+ pending: "pending";
127
+ reviewing: "reviewing";
128
+ actioned: "actioned";
129
+ dismissed: "dismissed";
130
+ }>;
131
+ export type ReportStatus = z.infer<typeof reportStatusSchema>;
132
+ /**
133
+ * Sort options exposed in the feed UI.
134
+ *
135
+ * `hot` — the default. Reddit-style score blending upvotes, comment count,
136
+ * and recency. Best surface for civic / community engagement.
137
+ * `new` — strict reverse-chronological. Useful when the viewer wants to
138
+ * catch up on everything fresh.
139
+ * `top` — highest upvote count over a configurable window (defaults to
140
+ * 24 hours on the client).
141
+ */
142
+ export declare const FEED_SORT_VALUES: readonly ["hot", "new", "top"];
143
+ export declare const feedSortSchema: z.ZodEnum<{
144
+ hot: "hot";
145
+ new: "new";
146
+ top: "top";
147
+ }>;
148
+ export type FeedSort = z.infer<typeof feedSortSchema>;
149
+ //# sourceMappingURL=enums.d.ts.map
@@ -0,0 +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"}
package/dist/enums.js ADDED
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Enum value arrays shared across the community feature.
3
+ *
4
+ * Each enum is exported as both a `*_VALUES` tuple and the corresponding
5
+ * Zod schema so backend Mongoose models and frontend forms validate
6
+ * against the exact same set.
7
+ *
8
+ * @module community-schema/enums
9
+ */
10
+ import { z } from 'zod';
11
+ // ─── Visibility scope ───────────────────────────────────────────────────────
12
+ /**
13
+ * The geographic level at which a piece of content is posted.
14
+ *
15
+ * Cascade-upward semantics: a viewer whose feed filter is set to L
16
+ * sees every post at L and every BROADER level that matches their
17
+ * area lineage. So:
18
+ * - `local` → matches local + district + state + national + global within the viewer's locality / village / urbanBody / etc.
19
+ * - `district` → matches district + state + national + global within the viewer's district
20
+ * - `state` → matches state + national + global within the viewer's state
21
+ * - `national` → matches national + global within the viewer's country
22
+ * - `global` → matches global only
23
+ *
24
+ * The full meaning of "local" is "anything below district" — i.e., the
25
+ * deepest area-lineage match (locality, village, urbanWard, …).
26
+ */
27
+ export const VISIBILITY_LEVEL_VALUES = [
28
+ 'local',
29
+ 'district',
30
+ 'state',
31
+ 'national',
32
+ 'global',
33
+ ];
34
+ export const visibilityLevelSchema = z.enum(VISIBILITY_LEVEL_VALUES);
35
+ /**
36
+ * Numeric ranking used for the cascade-upward query and for sorting
37
+ * filter chips. `local` is the deepest (most specific), `global` the
38
+ * shallowest. Higher rank = broader scope.
39
+ */
40
+ export const VISIBILITY_LEVEL_RANK = {
41
+ local: 0,
42
+ district: 1,
43
+ state: 2,
44
+ national: 3,
45
+ global: 4,
46
+ };
47
+ // ─── Polymorphic content kind ───────────────────────────────────────────────
48
+ /**
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.
52
+ *
53
+ * Engagement (upvote / reaction / comment) is keyed by
54
+ * (contentKind, contentId) so every kind gets the same engagement bar.
55
+ */
56
+ export const CONTENT_KIND_VALUES = ['post', 'lost_found', 'voice_box'];
57
+ export const contentKindSchema = z.enum(CONTENT_KIND_VALUES);
58
+ // ─── Reactions ──────────────────────────────────────────────────────────────
59
+ /**
60
+ * Reaction types available on every feed item.
61
+ *
62
+ * `support` replaces Facebook's "Care" — better-suited to a civic /
63
+ * hyperlocal context where reacting to a complaint or lost-and-found
64
+ * post deserves a distinct "I support you" signal.
65
+ */
66
+ export const REACTION_TYPE_VALUES = [
67
+ 'like',
68
+ 'love',
69
+ 'haha',
70
+ 'wow',
71
+ 'sad',
72
+ 'angry',
73
+ 'support',
74
+ ];
75
+ export const reactionTypeSchema = z.enum(REACTION_TYPE_VALUES);
76
+ // ─── Social graph ──────────────────────────────────────────────────────────
77
+ /**
78
+ * Connection (mutual friend) lifecycle.
79
+ *
80
+ * `pending` — requester sent it, recipient hasn't acted yet.
81
+ * `accepted` — both sides confirmed; full visibility regardless of level.
82
+ * `declined` — recipient rejected; surfaces as "you can't reconnect for X days" on the requester side.
83
+ * `blocked` — either side blocked; permanently prevents reconnection.
84
+ */
85
+ export const CONNECTION_STATUS_VALUES = [
86
+ 'pending',
87
+ 'accepted',
88
+ 'declined',
89
+ 'blocked',
90
+ ];
91
+ export const connectionStatusSchema = z.enum(CONNECTION_STATUS_VALUES);
92
+ // ─── Profile privacy ───────────────────────────────────────────────────────
93
+ /**
94
+ * Profile-level privacy (single toggle in Settings).
95
+ *
96
+ * `public` — anyone in the matching geo scope sees this user's posts.
97
+ * `private` — only accepted Connections see this user's posts. Visibility
98
+ * level on each post still applies on top (a private post at
99
+ * state level is visible only to Connections who are in the
100
+ * same state).
101
+ */
102
+ export const PROFILE_PRIVACY_VALUES = ['public', 'private'];
103
+ export const profilePrivacySchema = z.enum(PROFILE_PRIVACY_VALUES);
104
+ // ─── Moderation ────────────────────────────────────────────────────────────
105
+ /**
106
+ * Reasons a piece of content can be reported. The list is deliberately
107
+ * short — finer categorization can come from the optional `details`
108
+ * free-text field on a report.
109
+ */
110
+ export const REPORT_REASON_VALUES = [
111
+ 'spam',
112
+ 'harassment',
113
+ 'hate_speech',
114
+ 'misinformation',
115
+ 'sexual_content',
116
+ 'violence',
117
+ 'self_harm',
118
+ 'illegal_content',
119
+ 'other',
120
+ ];
121
+ export const reportReasonSchema = z.enum(REPORT_REASON_VALUES);
122
+ /** Moderator decision states on a report. */
123
+ export const REPORT_STATUS_VALUES = [
124
+ 'pending',
125
+ 'reviewing',
126
+ 'actioned',
127
+ 'dismissed',
128
+ ];
129
+ export const reportStatusSchema = z.enum(REPORT_STATUS_VALUES);
130
+ // ─── Feed sort ─────────────────────────────────────────────────────────────
131
+ /**
132
+ * Sort options exposed in the feed UI.
133
+ *
134
+ * `hot` — the default. Reddit-style score blending upvotes, comment count,
135
+ * and recency. Best surface for civic / community engagement.
136
+ * `new` — strict reverse-chronological. Useful when the viewer wants to
137
+ * catch up on everything fresh.
138
+ * `top` — highest upvote count over a configurable window (defaults to
139
+ * 24 hours on the client).
140
+ */
141
+ export const FEED_SORT_VALUES = ['hot', 'new', 'top'];
142
+ export const feedSortSchema = z.enum(FEED_SORT_VALUES);
143
+ //# sourceMappingURL=enums.js.map
@@ -0,0 +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"]}
package/dist/feed.d.ts ADDED
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Feed wire shapes — polymorphic union of community posts, lost-and-
3
+ * found reports, and voice-box (complaints + suggestions).
4
+ *
5
+ * The feed is a SINGLE list typed as `FeedItem = Post | LostFound | VoiceBox`.
6
+ * The client discriminates on `contentKind` and renders the matching
7
+ * card component.
8
+ *
9
+ * Strategy notes (server-side):
10
+ * - Read-time fanout. The server runs ONE parallel query per kind
11
+ * filtered by the same area-lineage match and the same cascade-
12
+ * upward visibility rule, then merges and sorts by `hot` score.
13
+ * - Cursor-based pagination. The cursor is opaque (base64 of
14
+ * `<sortKey>|<_id>`) so we can swap sort algorithms without a
15
+ * breaking change.
16
+ * - LostFound / VoiceBox shapes are pruned to the fields the feed
17
+ * card needs — they keep their richer detail-page shape in their
18
+ * own schemas (lost-found-schema, voice-box-schema if/when those
19
+ * get factored out).
20
+ *
21
+ * @module community-schema/feed
22
+ */
23
+ import { z } from 'zod';
24
+ import { FEED_PAGE_SIZE } from './constants.js';
25
+ /**
26
+ * Lost-and-found item projected into the feed. Mirrors the engagement
27
+ * + author shape every feed card needs, plus a small handful of
28
+ * lost-found-specific fields used by the card.
29
+ */
30
+ export declare const lostFoundFeedItemSchema: z.ZodObject<{
31
+ _id: z.ZodString;
32
+ contentKind: z.ZodLiteral<"lost_found">;
33
+ author: z.ZodObject<{
34
+ userId: z.ZodString;
35
+ displayName: z.ZodString;
36
+ avatarUrl: z.ZodOptional<z.ZodString>;
37
+ localName: z.ZodOptional<z.ZodString>;
38
+ }, z.core.$strip>;
39
+ kind: z.ZodEnum<{
40
+ lost: "lost";
41
+ found: "found";
42
+ }>;
43
+ title: z.ZodString;
44
+ description: z.ZodString;
45
+ imageUrl: z.ZodOptional<z.ZodString>;
46
+ upvoteCount: z.ZodNumber;
47
+ commentCount: z.ZodNumber;
48
+ reactionCounts: z.ZodRecord<z.ZodString, z.ZodNumber>;
49
+ createdAt: z.ZodString;
50
+ viewer: z.ZodOptional<z.ZodObject<{
51
+ upvoted: z.ZodBoolean;
52
+ reaction: z.ZodNullable<z.ZodString>;
53
+ isAuthor: z.ZodBoolean;
54
+ }, z.core.$strip>>;
55
+ }, z.core.$strip>;
56
+ export type LostFoundFeedItem = z.infer<typeof lostFoundFeedItemSchema>;
57
+ /**
58
+ * Voice-box (complaint or suggestion) item projected into the feed.
59
+ */
60
+ export declare const voiceBoxFeedItemSchema: z.ZodObject<{
61
+ _id: z.ZodString;
62
+ contentKind: z.ZodLiteral<"voice_box">;
63
+ author: z.ZodObject<{
64
+ userId: z.ZodString;
65
+ displayName: z.ZodString;
66
+ avatarUrl: z.ZodOptional<z.ZodString>;
67
+ localName: z.ZodOptional<z.ZodString>;
68
+ }, z.core.$strip>;
69
+ kind: z.ZodEnum<{
70
+ complaint: "complaint";
71
+ suggestion: "suggestion";
72
+ }>;
73
+ title: z.ZodString;
74
+ description: z.ZodString;
75
+ upvoteCount: z.ZodNumber;
76
+ commentCount: z.ZodNumber;
77
+ reactionCounts: z.ZodRecord<z.ZodString, z.ZodNumber>;
78
+ createdAt: z.ZodString;
79
+ viewer: z.ZodOptional<z.ZodObject<{
80
+ upvoted: z.ZodBoolean;
81
+ reaction: z.ZodNullable<z.ZodString>;
82
+ isAuthor: z.ZodBoolean;
83
+ }, z.core.$strip>>;
84
+ }, z.core.$strip>;
85
+ export type VoiceBoxFeedItem = z.infer<typeof voiceBoxFeedItemSchema>;
86
+ export declare const feedItemSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
87
+ _id: z.ZodString;
88
+ contentKind: z.ZodLiteral<"post">;
89
+ author: z.ZodObject<{
90
+ userId: z.ZodString;
91
+ displayName: z.ZodString;
92
+ avatarUrl: z.ZodOptional<z.ZodString>;
93
+ localName: z.ZodOptional<z.ZodString>;
94
+ }, z.core.$strip>;
95
+ text: z.ZodString;
96
+ images: z.ZodArray<z.ZodObject<{
97
+ url: z.ZodString;
98
+ thumbnailUrl: z.ZodOptional<z.ZodString>;
99
+ width: z.ZodNumber;
100
+ height: z.ZodNumber;
101
+ bytes: z.ZodNumber;
102
+ alt: z.ZodOptional<z.ZodString>;
103
+ }, z.core.$strip>>;
104
+ videos: z.ZodArray<z.ZodObject<{
105
+ url: z.ZodString;
106
+ thumbnailUrl: z.ZodString;
107
+ width: z.ZodNumber;
108
+ height: z.ZodNumber;
109
+ bytes: z.ZodNumber;
110
+ duration: z.ZodNumber;
111
+ }, z.core.$strip>>;
112
+ visibilityLevel: z.ZodEnum<{
113
+ local: "local";
114
+ district: "district";
115
+ state: "state";
116
+ national: "national";
117
+ global: "global";
118
+ }>;
119
+ areaLineage: z.ZodObject<{
120
+ countryId: z.ZodOptional<z.ZodString>;
121
+ stateId: z.ZodOptional<z.ZodString>;
122
+ districtId: z.ZodOptional<z.ZodString>;
123
+ localId: z.ZodOptional<z.ZodString>;
124
+ localName: z.ZodOptional<z.ZodString>;
125
+ }, z.core.$strip>;
126
+ upvoteCount: z.ZodNumber;
127
+ commentCount: z.ZodNumber;
128
+ reactionCounts: z.ZodRecord<z.ZodString, z.ZodNumber>;
129
+ createdAt: z.ZodString;
130
+ editedAt: z.ZodNullable<z.ZodString>;
131
+ deletedAt: z.ZodNullable<z.ZodString>;
132
+ viewer: z.ZodOptional<z.ZodObject<{
133
+ upvoted: z.ZodBoolean;
134
+ reaction: z.ZodNullable<z.ZodString>;
135
+ isAuthor: z.ZodBoolean;
136
+ }, z.core.$strip>>;
137
+ }, z.core.$strip>, z.ZodObject<{
138
+ _id: z.ZodString;
139
+ contentKind: z.ZodLiteral<"lost_found">;
140
+ author: z.ZodObject<{
141
+ userId: z.ZodString;
142
+ displayName: z.ZodString;
143
+ avatarUrl: z.ZodOptional<z.ZodString>;
144
+ localName: z.ZodOptional<z.ZodString>;
145
+ }, z.core.$strip>;
146
+ kind: z.ZodEnum<{
147
+ lost: "lost";
148
+ found: "found";
149
+ }>;
150
+ title: z.ZodString;
151
+ description: z.ZodString;
152
+ imageUrl: z.ZodOptional<z.ZodString>;
153
+ upvoteCount: z.ZodNumber;
154
+ commentCount: z.ZodNumber;
155
+ reactionCounts: z.ZodRecord<z.ZodString, z.ZodNumber>;
156
+ createdAt: z.ZodString;
157
+ viewer: z.ZodOptional<z.ZodObject<{
158
+ upvoted: z.ZodBoolean;
159
+ reaction: z.ZodNullable<z.ZodString>;
160
+ isAuthor: z.ZodBoolean;
161
+ }, z.core.$strip>>;
162
+ }, z.core.$strip>, z.ZodObject<{
163
+ _id: z.ZodString;
164
+ contentKind: z.ZodLiteral<"voice_box">;
165
+ author: z.ZodObject<{
166
+ userId: z.ZodString;
167
+ displayName: z.ZodString;
168
+ avatarUrl: z.ZodOptional<z.ZodString>;
169
+ localName: z.ZodOptional<z.ZodString>;
170
+ }, z.core.$strip>;
171
+ kind: z.ZodEnum<{
172
+ complaint: "complaint";
173
+ suggestion: "suggestion";
174
+ }>;
175
+ title: z.ZodString;
176
+ description: z.ZodString;
177
+ upvoteCount: z.ZodNumber;
178
+ commentCount: z.ZodNumber;
179
+ reactionCounts: z.ZodRecord<z.ZodString, z.ZodNumber>;
180
+ createdAt: z.ZodString;
181
+ viewer: z.ZodOptional<z.ZodObject<{
182
+ upvoted: z.ZodBoolean;
183
+ reaction: z.ZodNullable<z.ZodString>;
184
+ isAuthor: z.ZodBoolean;
185
+ }, z.core.$strip>>;
186
+ }, z.core.$strip>], "contentKind">;
187
+ export type FeedItem = z.infer<typeof feedItemSchema>;
188
+ export declare const feedQueryParamsSchema: z.ZodObject<{
189
+ level: z.ZodEnum<{
190
+ local: "local";
191
+ district: "district";
192
+ state: "state";
193
+ national: "national";
194
+ global: "global";
195
+ }>;
196
+ sort: z.ZodOptional<z.ZodEnum<{
197
+ hot: "hot";
198
+ new: "new";
199
+ top: "top";
200
+ }>>;
201
+ cursor: z.ZodOptional<z.ZodString>;
202
+ pageSize: z.ZodOptional<z.ZodNumber>;
203
+ }, z.core.$strip>;
204
+ export type FeedQueryParams = z.infer<typeof feedQueryParamsSchema>;
205
+ export declare const feedResponseSchema: z.ZodObject<{
206
+ items: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
207
+ _id: z.ZodString;
208
+ contentKind: z.ZodLiteral<"post">;
209
+ author: z.ZodObject<{
210
+ userId: z.ZodString;
211
+ displayName: z.ZodString;
212
+ avatarUrl: z.ZodOptional<z.ZodString>;
213
+ localName: z.ZodOptional<z.ZodString>;
214
+ }, z.core.$strip>;
215
+ text: z.ZodString;
216
+ images: z.ZodArray<z.ZodObject<{
217
+ url: z.ZodString;
218
+ thumbnailUrl: z.ZodOptional<z.ZodString>;
219
+ width: z.ZodNumber;
220
+ height: z.ZodNumber;
221
+ bytes: z.ZodNumber;
222
+ alt: z.ZodOptional<z.ZodString>;
223
+ }, z.core.$strip>>;
224
+ videos: z.ZodArray<z.ZodObject<{
225
+ url: z.ZodString;
226
+ thumbnailUrl: z.ZodString;
227
+ width: z.ZodNumber;
228
+ height: z.ZodNumber;
229
+ bytes: z.ZodNumber;
230
+ duration: z.ZodNumber;
231
+ }, z.core.$strip>>;
232
+ visibilityLevel: z.ZodEnum<{
233
+ local: "local";
234
+ district: "district";
235
+ state: "state";
236
+ national: "national";
237
+ global: "global";
238
+ }>;
239
+ areaLineage: z.ZodObject<{
240
+ countryId: z.ZodOptional<z.ZodString>;
241
+ stateId: z.ZodOptional<z.ZodString>;
242
+ districtId: z.ZodOptional<z.ZodString>;
243
+ localId: z.ZodOptional<z.ZodString>;
244
+ localName: z.ZodOptional<z.ZodString>;
245
+ }, z.core.$strip>;
246
+ upvoteCount: z.ZodNumber;
247
+ commentCount: z.ZodNumber;
248
+ reactionCounts: z.ZodRecord<z.ZodString, z.ZodNumber>;
249
+ createdAt: z.ZodString;
250
+ editedAt: z.ZodNullable<z.ZodString>;
251
+ deletedAt: z.ZodNullable<z.ZodString>;
252
+ viewer: z.ZodOptional<z.ZodObject<{
253
+ upvoted: z.ZodBoolean;
254
+ reaction: z.ZodNullable<z.ZodString>;
255
+ isAuthor: z.ZodBoolean;
256
+ }, z.core.$strip>>;
257
+ }, z.core.$strip>, z.ZodObject<{
258
+ _id: z.ZodString;
259
+ contentKind: z.ZodLiteral<"lost_found">;
260
+ author: z.ZodObject<{
261
+ userId: z.ZodString;
262
+ displayName: z.ZodString;
263
+ avatarUrl: z.ZodOptional<z.ZodString>;
264
+ localName: z.ZodOptional<z.ZodString>;
265
+ }, z.core.$strip>;
266
+ kind: z.ZodEnum<{
267
+ lost: "lost";
268
+ found: "found";
269
+ }>;
270
+ title: z.ZodString;
271
+ description: z.ZodString;
272
+ imageUrl: z.ZodOptional<z.ZodString>;
273
+ upvoteCount: z.ZodNumber;
274
+ commentCount: z.ZodNumber;
275
+ reactionCounts: z.ZodRecord<z.ZodString, z.ZodNumber>;
276
+ createdAt: z.ZodString;
277
+ viewer: z.ZodOptional<z.ZodObject<{
278
+ upvoted: z.ZodBoolean;
279
+ reaction: z.ZodNullable<z.ZodString>;
280
+ isAuthor: z.ZodBoolean;
281
+ }, z.core.$strip>>;
282
+ }, z.core.$strip>, z.ZodObject<{
283
+ _id: z.ZodString;
284
+ contentKind: z.ZodLiteral<"voice_box">;
285
+ author: z.ZodObject<{
286
+ userId: z.ZodString;
287
+ displayName: z.ZodString;
288
+ avatarUrl: z.ZodOptional<z.ZodString>;
289
+ localName: z.ZodOptional<z.ZodString>;
290
+ }, z.core.$strip>;
291
+ kind: z.ZodEnum<{
292
+ complaint: "complaint";
293
+ suggestion: "suggestion";
294
+ }>;
295
+ title: z.ZodString;
296
+ description: z.ZodString;
297
+ upvoteCount: z.ZodNumber;
298
+ commentCount: z.ZodNumber;
299
+ reactionCounts: z.ZodRecord<z.ZodString, z.ZodNumber>;
300
+ createdAt: z.ZodString;
301
+ viewer: z.ZodOptional<z.ZodObject<{
302
+ upvoted: z.ZodBoolean;
303
+ reaction: z.ZodNullable<z.ZodString>;
304
+ isAuthor: z.ZodBoolean;
305
+ }, z.core.$strip>>;
306
+ }, z.core.$strip>], "contentKind">>;
307
+ nextCursor: z.ZodNullable<z.ZodString>;
308
+ }, z.core.$strip>;
309
+ export type FeedResponse = z.infer<typeof feedResponseSchema>;
310
+ export { FEED_PAGE_SIZE };
311
+ //# sourceMappingURL=feed.d.ts.map
@@ -0,0 +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"}