jansathi-community-schema 0.43.0 → 0.45.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/feed.d.ts +7 -0
- package/dist/feed.d.ts.map +1 -1
- package/dist/feed.js +11 -1
- package/dist/feed.js.map +1 -1
- package/dist/poll.d.ts +4 -0
- package/dist/poll.d.ts.map +1 -1
- package/dist/poll.js +8 -0
- package/dist/poll.js.map +1 -1
- package/dist/post.d.ts +2 -0
- package/dist/post.d.ts.map +1 -1
- package/dist/post.js +10 -0
- package/dist/post.js.map +1 -1
- package/dist/report.d.ts +2 -0
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +10 -1
- package/dist/report.js.map +1 -1
- package/dist/repost.d.ts +1 -0
- package/dist/repost.d.ts.map +1 -1
- package/dist/suggestions-complaints.d.ts +2 -0
- package/dist/suggestions-complaints.d.ts.map +1 -1
- package/dist/suggestions-complaints.js +3 -0
- package/dist/suggestions-complaints.js.map +1 -1
- package/package.json +1 -1
package/dist/feed.d.ts
CHANGED
|
@@ -669,6 +669,7 @@ export declare const suggestionsComplaintsFeedItemSchema: z.ZodObject<{
|
|
|
669
669
|
}, z.core.$strip>>>;
|
|
670
670
|
identityVerified: z.ZodOptional<z.ZodBoolean>;
|
|
671
671
|
}, z.core.$strip>;
|
|
672
|
+
isAnonymous: z.ZodOptional<z.ZodBoolean>;
|
|
672
673
|
kind: z.ZodEnum<{
|
|
673
674
|
complaint: "complaint";
|
|
674
675
|
suggestion: "suggestion";
|
|
@@ -848,6 +849,7 @@ export declare const feedItemSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
848
849
|
}, z.core.$strip>>>;
|
|
849
850
|
identityVerified: z.ZodOptional<z.ZodBoolean>;
|
|
850
851
|
}, z.core.$strip>;
|
|
852
|
+
isAnonymous: z.ZodOptional<z.ZodBoolean>;
|
|
851
853
|
text: z.ZodString;
|
|
852
854
|
images: z.ZodArray<z.ZodObject<{
|
|
853
855
|
url: z.ZodString;
|
|
@@ -1162,6 +1164,7 @@ export declare const feedItemSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1162
1164
|
}, z.core.$strip>>>;
|
|
1163
1165
|
identityVerified: z.ZodOptional<z.ZodBoolean>;
|
|
1164
1166
|
}, z.core.$strip>;
|
|
1167
|
+
isAnonymous: z.ZodOptional<z.ZodBoolean>;
|
|
1165
1168
|
kind: z.ZodEnum<{
|
|
1166
1169
|
complaint: "complaint";
|
|
1167
1170
|
suggestion: "suggestion";
|
|
@@ -1425,6 +1428,7 @@ export declare const feedItemSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1425
1428
|
}, z.core.$strip>>>;
|
|
1426
1429
|
identityVerified: z.ZodOptional<z.ZodBoolean>;
|
|
1427
1430
|
}, z.core.$strip>;
|
|
1431
|
+
isAnonymous: z.ZodOptional<z.ZodBoolean>;
|
|
1428
1432
|
question: z.ZodString;
|
|
1429
1433
|
options: z.ZodArray<z.ZodObject<{
|
|
1430
1434
|
index: z.ZodNumber;
|
|
@@ -1726,6 +1730,7 @@ export declare const feedResponseSchema: z.ZodObject<{
|
|
|
1726
1730
|
}, z.core.$strip>>>;
|
|
1727
1731
|
identityVerified: z.ZodOptional<z.ZodBoolean>;
|
|
1728
1732
|
}, z.core.$strip>;
|
|
1733
|
+
isAnonymous: z.ZodOptional<z.ZodBoolean>;
|
|
1729
1734
|
text: z.ZodString;
|
|
1730
1735
|
images: z.ZodArray<z.ZodObject<{
|
|
1731
1736
|
url: z.ZodString;
|
|
@@ -2040,6 +2045,7 @@ export declare const feedResponseSchema: z.ZodObject<{
|
|
|
2040
2045
|
}, z.core.$strip>>>;
|
|
2041
2046
|
identityVerified: z.ZodOptional<z.ZodBoolean>;
|
|
2042
2047
|
}, z.core.$strip>;
|
|
2048
|
+
isAnonymous: z.ZodOptional<z.ZodBoolean>;
|
|
2043
2049
|
kind: z.ZodEnum<{
|
|
2044
2050
|
complaint: "complaint";
|
|
2045
2051
|
suggestion: "suggestion";
|
|
@@ -2303,6 +2309,7 @@ export declare const feedResponseSchema: z.ZodObject<{
|
|
|
2303
2309
|
}, z.core.$strip>>>;
|
|
2304
2310
|
identityVerified: z.ZodOptional<z.ZodBoolean>;
|
|
2305
2311
|
}, z.core.$strip>;
|
|
2312
|
+
isAnonymous: z.ZodOptional<z.ZodBoolean>;
|
|
2306
2313
|
question: z.ZodString;
|
|
2307
2314
|
options: z.ZodArray<z.ZodObject<{
|
|
2308
2315
|
index: z.ZodNumber;
|
package/dist/feed.d.ts.map
CHANGED
|
@@ -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;AAShD;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,4IAY7B,CAAC;AACX,eAAO,MAAM,uBAAuB;;;;;;;;;;;;EAAqC,CAAC;AAC1E,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,4CAA6C,CAAC;AACnF,eAAO,MAAM,qBAAqB;;;;EAAmC,CAAC;AACtE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAcpE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuBlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE;;+CAE+C;AAC/C,eAAO,MAAM,wBAAwB;;;iBAejC,CAAC;AACL,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8ElC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,uEAM5B,CAAC;AACX,eAAO,MAAM,sBAAsB;;;;;;EAAoC,CAAC;AACxE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;GAGG;AACH,eAAO,MAAM,4BAA4B,yHAS/B,CAAC;AACX,eAAO,MAAM,yBAAyB;;;;;;;;;EAAuC,CAAC;AAC9E,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E;;wDAEwD;AACxD,eAAO,MAAM,qBAAqB;;;iBAKhC,CAAC;AACH,eAAO,MAAM,mBAAmB;;iBAA0C,CAAC;AAC3E,eAAO,MAAM,qBAAqB;;;iBAIhC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgEnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E;;;GAGG;AACH,eAAO,MAAM,sCAAsC,
|
|
1
|
+
{"version":3,"file":"feed.d.ts","sourceRoot":"","sources":["../src/feed.ts"],"names":[],"mappings":"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"}
|
package/dist/feed.js
CHANGED
|
@@ -314,7 +314,12 @@ export const donateItemFeedItemSchema = z.object({
|
|
|
314
314
|
* Suggestions & complaints priority — mirrors `IReformIssueReport.priority`.
|
|
315
315
|
* Drives the side-stripe colour on the complaint card.
|
|
316
316
|
*/
|
|
317
|
-
export const SUGGESTIONS_COMPLAINTS_PRIORITY_VALUES = [
|
|
317
|
+
export const SUGGESTIONS_COMPLAINTS_PRIORITY_VALUES = [
|
|
318
|
+
'low',
|
|
319
|
+
'medium',
|
|
320
|
+
'high',
|
|
321
|
+
'critical',
|
|
322
|
+
];
|
|
318
323
|
export const suggestionsComplaintsPrioritySchema = z.enum(SUGGESTIONS_COMPLAINTS_PRIORITY_VALUES);
|
|
319
324
|
/**
|
|
320
325
|
* Suggestions & complaints lifecycle status. Five states matching the model — the
|
|
@@ -411,7 +416,12 @@ export const suggestionsComplaintsLocationSchema = z.object({
|
|
|
411
416
|
export const suggestionsComplaintsFeedItemSchema = z.object({
|
|
412
417
|
_id: z.string(),
|
|
413
418
|
contentKind: z.literal('suggestions_complaints'),
|
|
419
|
+
/** Author of the report. When `isAnonymous` is true, the server ships
|
|
420
|
+
* a true-anonymous snapshot (no userId / username / avatar / display
|
|
421
|
+
* name) — no way to open the author's profile or contact them. */
|
|
414
422
|
author: postAuthorSnapshotSchema,
|
|
423
|
+
/** Reporter opted to stay anonymous. */
|
|
424
|
+
isAnonymous: z.boolean().optional(),
|
|
415
425
|
/** `'complaint'` or `'suggestion'` — drives the card chrome (priority
|
|
416
426
|
* stripe vs. Reddit-vote rail). */
|
|
417
427
|
kind: z.enum(['complaint', 'suggestion']),
|
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 suggestions-complaints (complaints + suggestions).\n *\n * The feed is a SINGLE list typed as `FeedItem = Post | LostFound | SuggestionsComplaints`.\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 / SuggestionsComplaints 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, suggestions-complaints-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 { crowdfundingFeedItemSchema } from './crowdfunding.js';\nimport { voteValueSchema } from './engagement.js';\nimport { feedSortSchema, visibilityLevelSchema } from './enums.js';\nimport { communityPollFeedItemSchema } from './poll.js';\nimport { communityPostWireSchema, postAuthorSnapshotSchema } from './post.js';\n\n// ─── Per-kind feed envelopes ───────────────────────────────────────────────\n\n/**\n * Lost-and-found category enum — mirrors the reform-backend\n * `LOST_FOUND_CATEGORY` map. Stable English snake_case slugs; the\n * card maps each to an icon + localised label.\n */\nexport const LOST_FOUND_CATEGORY_VALUES = [\n 'electronics',\n 'jewelry',\n 'documents',\n 'clothing',\n 'pets',\n 'bags_luggage',\n 'keys',\n 'wallet_purse',\n 'phone',\n 'id_cards',\n 'other',\n] as const;\nexport const lostFoundCategorySchema = z.enum(LOST_FOUND_CATEGORY_VALUES);\nexport type LostFoundCategory = z.infer<typeof lostFoundCategorySchema>;\n\n/**\n * Lost-and-found status — `active` is the in-feed state, `resolved`\n * means a claim closed the report, `expired` means the auto-archive\n * window passed.\n */\nexport const LOST_FOUND_STATUS_VALUES = ['active', 'resolved', 'expired'] as const;\nexport const lostFoundStatusSchema = z.enum(LOST_FOUND_STATUS_VALUES);\nexport type LostFoundStatus = z.infer<typeof lostFoundStatusSchema>;\n\n/**\n * Slim area reference snapshot — mirrors the donations PickupAddress\n * AreaRefSnapshot. Stored on lost-found reports so the location pill\n * can show \"Locality, District, State\" without an Area lookup.\n */\nconst lostFoundAreaRefSchema = z.object({\n _id: z.string().optional(),\n name: z.string(),\n type: z.string().optional(),\n urbanBodyType: z.string().optional(),\n});\n\n/**\n * Structured location for a lost-found report. Mirrors the donations\n * pickup address with deliberate omissions: lost-found reports\n * describe an INCIDENT spot, not a delivery address, so\n * - `housePlotDetails` and `pincode` are dropped (no postal logistics);\n * - `coords` are dropped (no GPS capture — the reporter usually isn't\n * standing at the spot when they file the report);\n * - `landmark` + `directions` are kept (the user's free-text\n * description of where).\n *\n * Every field is optional so a sparse report (\"I lost it somewhere in\n * Mumbai\") is still valid.\n */\nexport const lostFoundLocationSchema = z.object({\n country: z.string().optional(),\n state: lostFoundAreaRefSchema.optional(),\n division: lostFoundAreaRefSchema.optional(),\n district: lostFoundAreaRefSchema.optional(),\n subDistrict: lostFoundAreaRefSchema.optional(),\n areaType: z.enum(['RURAL', 'URBAN']).optional(),\n block: lostFoundAreaRefSchema.optional(),\n gramPanchayat: lostFoundAreaRefSchema.optional(),\n village: lostFoundAreaRefSchema.optional(),\n hamlet: lostFoundAreaRefSchema.optional(),\n ruralWard: lostFoundAreaRefSchema.optional(),\n urbanBody: lostFoundAreaRefSchema.optional(),\n urbanWard: lostFoundAreaRefSchema.optional(),\n locality: lostFoundAreaRefSchema.optional(),\n policeStation: lostFoundAreaRefSchema.optional(),\n /** Free-text landmark, e.g. \"near the bus stop\". */\n landmark: z.string().max(200).optional(),\n /** Free-text additional context, e.g. \"south-west corner near the\n * parking lot\". Stored as `directions` on the model to match the\n * donations naming so the shared address normaliser works without\n * a per-feature aliasing layer. */\n directions: z.string().max(500).optional(),\n});\nexport type LostFoundLocation = z.infer<typeof lostFoundLocationSchema>;\n\n/** HH:MM-format time window on the report's date. Either endpoint may\n * be omitted, so the user can record \"after 2pm\", \"before 4pm\", or\n * \"between 2 and 4pm\" with the same shape. */\nexport const lostFoundTimeRangeSchema = z\n .object({\n startTime: z\n .string()\n .regex(/^([01]\\d|2[0-3]):[0-5]\\d$/, 'startTime must be HH:MM (24-hour)')\n .optional(),\n endTime: z\n .string()\n .regex(/^([01]\\d|2[0-3]):[0-5]\\d$/, 'endTime must be HH:MM (24-hour)')\n .optional(),\n })\n // At least one endpoint must be set — an empty `{}` adds no\n // information and is omitted by the form before submit.\n .refine((tr) => Boolean(tr.startTime || tr.endTime), {\n message: 'timeRange must include at least startTime or endTime',\n });\nexport type LostFoundTimeRange = z.infer<typeof lostFoundTimeRangeSchema>;\n\n/**\n * Lost-and-found item projected into the feed.\n *\n * v3 wire — extended to carry every field the production card renders\n * inline, so a single feed read serves the whole row. The detail page\n * (which loads `IReformLostFoundReport` directly) carries the long\n * tail of fields like `creatorName`, `resolvedWith`, audit logs.\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 ribbon banner colour. */\n kind: z.enum(['lost', 'found']),\n title: z.string(),\n description: z.string(),\n\n // ─── v3 additions (all optional for backward compat) ─────────────\n // Live consumers reading the v1 wire keep working because every\n // new field is optional. The v3 projector populates them; the v3\n // card reads them. As consumers migrate to v3, the optionality can\n // tighten in a future minor bump.\n /** Curated category — drives the category icon + label chip.\n * Optional only until the v3 projector ships. */\n category: lostFoundCategorySchema.optional(),\n /** All images attached to the report (up to 5). The card renders a\n * swipeable carousel; the legacy `imageUrl` field below is kept\n * as a deprecated alias for `images[0]`. */\n images: z.array(z.string().url()).max(5).optional(),\n /** Where the item was lost / found.\n *\n * Structured shape: the area-hierarchy snapshot the user picked in\n * the LostFoundLocationDrawer plus optional `landmark` /\n * `directions` free-text. The card composes a single human-readable\n * string from these for the location pill.\n *\n * Backward compat: rows created before this migration carry a plain\n * string. The projector converts those legacy values to\n * `{ landmark: <string> }` on the way out so the wire always speaks\n * the new shape and the card stays simple. */\n location: lostFoundLocationSchema.optional(),\n /** Event date — when the item was lost / found. Distinct from\n * `createdAt`. */\n date: z.string().datetime().optional(),\n /** Optional time window on the report's `date` when the user\n * believes the item was lost or found. Both endpoints are HH:MM\n * strings (24-hour) and both are optional so the user can supply\n * just a start, just an end, or both. Omitted entirely when the\n * user couldn't narrow it down to a time. */\n timeRange: lostFoundTimeRangeSchema.optional(),\n /** For `kind === 'found'`: instructions on where to collect the\n * item. Null on `lost` reports. */\n collectionPoint: z.string().nullable().optional(),\n /** Public dial-back number captured on the report. */\n contactPhone: z.string().nullable().optional(),\n /** Lifecycle status. */\n status: lostFoundStatusSchema.optional(),\n /** Distinct-claimant count — drives the \"3 claims\" social-proof\n * chip. Maintained by the LostFoundClaim service. */\n claimCount: z.number().int().nonnegative().optional(),\n /** Free-text reward note (\"₹2,500\", \"Will treat to dinner\"). */\n rewardOffered: z.string().max(40).nullable().optional(),\n\n /** Deprecated single primary image. New consumers should read\n * `images[0]`; kept on the wire for the v1 card path. */\n imageUrl: z.string().url().optional(),\n\n /** Inherited engagement axis — same shape as posts. Score is the\n * signed net (upvotes - downvotes), can be negative. */\n score: z.number().int(),\n commentCount: z.number().int().nonnegative(),\n repostCount: 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 vote: voteValueSchema.nullable(),\n reaction: z.string().nullable(),\n bookmarked: z.boolean(),\n isAuthor: z.boolean(),\n reposted: z.boolean(),\n })\n .optional(),\n});\nexport type LostFoundFeedItem = z.infer<typeof lostFoundFeedItemSchema>;\n\n/**\n * Donate-item status — mirrors the reform-backend\n * `ITEM_DONATION_STATUS` enum. `CANCELLED` is a terminal out-of-band\n * state (donor or admin withdrew the listing).\n */\nexport const DONATE_ITEM_STATUS_VALUES = [\n 'PENDING',\n 'VERIFIED',\n 'AT_CENTER',\n 'CANCELLED',\n 'DONATED',\n] as const;\nexport const donateItemStatusSchema = z.enum(DONATE_ITEM_STATUS_VALUES);\nexport type DonateItemStatus = z.infer<typeof donateItemStatusSchema>;\n\n/**\n * Item-condition labels surfaced on the chip. Mirrors the reform-\n * backend `ITEM_CONDITION` enum.\n */\nexport const DONATE_ITEM_CONDITION_VALUES = [\n 'NEW',\n 'LIKE_NEW',\n 'GOOD',\n 'FAIR',\n 'OLD_FUNCTIONAL',\n 'NEEDS_MINOR_REPAIR',\n 'NEEDS_MAJOR_REPAIR',\n 'FOR_PARTS',\n] as const;\nexport const donateItemConditionSchema = z.enum(DONATE_ITEM_CONDITION_VALUES);\nexport type DonateItemCondition = z.infer<typeof donateItemConditionSchema>;\n\n/** Audit summary shapes — short, denormalised projections of the\n * underlying pipeline blocks. The detail page carries the full\n * notes; the feed card just needs the \"who + when\". */\nexport const donateAuditByAtSchema = z.object({\n /** Display name of the person who performed the step. */\n byName: z.string(),\n /** When the step happened. */\n at: z.string().datetime(),\n});\nexport const donateAuditAtSchema = z.object({ at: z.string().datetime() });\nexport const donateDonatedToSchema = z.object({\n /** Name of the recipient (person or institution). */\n name: z.string(),\n at: z.string().datetime(),\n});\n\n/**\n * Donated-item listing projected into the feed.\n *\n * v3 wire — extended to carry every field the production card renders\n * inline. The full pickup-address sub-doc, verification notes, and\n * pipeline notes stay on the detail page.\n */\nexport const donateItemFeedItemSchema = z.object({\n _id: z.string(),\n contentKind: z.literal('donate_item'),\n /** Author of the donation. When `isAnonymous` is true, the wire\n * still ships a placeholder author (initials + neutral gradient,\n * no real `username` / `displayName`) — the server scrubs PII so\n * the wire is the privacy boundary, not the card. */\n author: postAuthorSnapshotSchema,\n\n /** Display name of the item. */\n itemName: z.string(),\n /** Optional free-text description. */\n description: z.string().optional(),\n /** Free-text category for now (the backend enum is widening; keeping\n * this open avoids a schema break each time a category is added). */\n category: z.string(),\n /** Item condition. Optional only because legacy rows pre-date the\n * field; new writes always populate it. */\n condition: donateItemConditionSchema.optional(),\n\n /** Lifecycle status — `string` for backward compat with v1\n * consumers that received an open string; v3 consumers should\n * refine against `donateItemStatusSchema`. */\n status: z.string(),\n\n // ─── v3 additions (all optional for backward compat) ─────────────\n /** Estimated worth in INR (₹). Null when the donor declined. */\n estimatedWorthInr: z.number().int().nullable().optional(),\n /** All photos attached to the donation (up to 3). */\n photos: z.array(z.string().url()).max(3).optional(),\n /** Donor opted to hide identity on the public feed. */\n isAnonymous: z.boolean().optional(),\n /** Distance from the viewer's `currentLocation` in km. */\n distanceKm: z.number().nonnegative().nullable().optional(),\n /** Date after which the donor is no longer available. */\n availableUntil: z.string().datetime().nullable().optional(),\n /** Pipeline audit summaries — each null when the step hasn't\n * happened yet. The card progressively reveals the blocks as the\n * donation moves through the pipeline. */\n verification: donateAuditByAtSchema.nullable().optional(),\n pickup: donateAuditByAtSchema.nullable().optional(),\n centerSubmission: donateAuditAtSchema.nullable().optional(),\n donatedTo: donateDonatedToSchema.nullable().optional(),\n\n /** Deprecated single primary image. New consumers should read\n * `photos[0]`; kept on the wire for the v1 card path. */\n imageUrl: z.string().url().optional(),\n\n score: z.number().int(),\n commentCount: z.number().int().nonnegative(),\n repostCount: 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 vote: voteValueSchema.nullable(),\n reaction: z.string().nullable(),\n bookmarked: z.boolean(),\n isAuthor: z.boolean(),\n reposted: z.boolean(),\n })\n .optional(),\n});\nexport type DonateItemFeedItem = z.infer<typeof donateItemFeedItemSchema>;\n\n/**\n * Suggestions & complaints priority — mirrors `IReformIssueReport.priority`.\n * Drives the side-stripe colour on the complaint card.\n */\nexport const SUGGESTIONS_COMPLAINTS_PRIORITY_VALUES = ['low', 'medium', 'high', 'critical'] as const;\nexport const suggestionsComplaintsPrioritySchema = z.enum(SUGGESTIONS_COMPLAINTS_PRIORITY_VALUES);\nexport type SuggestionsComplaintsPriority = z.infer<typeof suggestionsComplaintsPrioritySchema>;\n\n/**\n * Suggestions & complaints lifecycle status. Five states matching the model — the\n * card surfaces `pending`/`under_review`/`in_progress` as in-flight\n * pills, `resolved` as a green resolution footer, `rejected` as\n * neutral with an explanation.\n */\nexport const SUGGESTIONS_COMPLAINTS_STATUS_VALUES = [\n 'pending',\n 'under_review',\n 'in_progress',\n 'resolved',\n 'rejected',\n] as const;\nexport const suggestionsComplaintsStatusSchema = z.enum(SUGGESTIONS_COMPLAINTS_STATUS_VALUES);\nexport type SuggestionsComplaintsStatus = z.infer<typeof suggestionsComplaintsStatusSchema>;\n\n/** Inline-playable audio attachment (voice complaint). The model\n * stores `IssueAudio` with `url + gcpPath + uploadedAt + duration`;\n * the wire is trimmed to what the card needs to render the WhatsApp-\n * style voice note bar. */\nexport const suggestionsComplaintsAudioSchema = z.object({\n url: z.string().url(),\n /** Duration in seconds. Drives the bar's duration chip. */\n durationSec: z.number().positive(),\n});\n\n/** Inline-playable video attachment (single native upload). */\nexport const suggestionsComplaintsVideoSchema = z.object({\n url: z.string().url(),\n thumbnailUrl: z.string().url(),\n durationSec: z.number().positive(),\n});\n\n/** Officer-response summary projected onto the feed wire so the card\n * can render the sky-blue \"Official response\" block without a\n * second round trip. Full text + reply audit log live on the\n * detail page. */\nexport const suggestionsComplaintsOfficerResponseSchema = z.object({\n /** Display name of the assigned officer. */\n officerName: z.string(),\n /** Officer's designation (Pradhan / Mayor / Junior Engineer / ...).\n * Null when the assigned user has no civic designation. */\n designation: z.string().nullable(),\n /** Truncated response text — max 280 chars. The full note lives on\n * the detail page. */\n text: z.string().max(280),\n respondedAt: z.string().datetime(),\n});\n\n/** Structured incident-location for a suggestion/complaint report.\n *\n * Same shape as `lostFoundLocationSchema` — area-hierarchy snapshot\n * the user picked in the location drawer (state / district /\n * locality / etc.) plus optional `landmark` + `directions` free-text.\n * Lat/lng intentionally NOT on the wire to keep reporter privacy\n * intact; the card composes a single human-readable string from\n * these via `formatLostFoundLocation` (the helper is shape-agnostic).\n *\n * Legacy rows authored before this migration carry a flat\n * `{ address, district, pinCode }` shape on the model; the feed\n * projector coerces those into `{ landmark: address, district:\n * { name: district } }` on the way out so consumers only see one\n * shape. */\nconst suggestionsComplaintsAreaRefSchema = z.object({\n _id: z.string().optional(),\n name: z.string(),\n type: z.string().optional(),\n urbanBodyType: z.string().optional(),\n});\n\nexport const suggestionsComplaintsLocationSchema = z.object({\n country: z.string().optional(),\n state: suggestionsComplaintsAreaRefSchema.optional(),\n division: suggestionsComplaintsAreaRefSchema.optional(),\n district: suggestionsComplaintsAreaRefSchema.optional(),\n subDistrict: suggestionsComplaintsAreaRefSchema.optional(),\n areaType: z.enum(['RURAL', 'URBAN']).optional(),\n block: suggestionsComplaintsAreaRefSchema.optional(),\n gramPanchayat: suggestionsComplaintsAreaRefSchema.optional(),\n village: suggestionsComplaintsAreaRefSchema.optional(),\n hamlet: suggestionsComplaintsAreaRefSchema.optional(),\n ruralWard: suggestionsComplaintsAreaRefSchema.optional(),\n urbanBody: suggestionsComplaintsAreaRefSchema.optional(),\n urbanWard: suggestionsComplaintsAreaRefSchema.optional(),\n locality: suggestionsComplaintsAreaRefSchema.optional(),\n policeStation: suggestionsComplaintsAreaRefSchema.optional(),\n landmark: z.string().max(200).optional(),\n directions: z.string().max(500).optional(),\n});\nexport type SuggestionsComplaintsLocation = z.infer<typeof suggestionsComplaintsLocationSchema>;\n\n/**\n * Suggestions & complaints (complaint or suggestion) item projected into the feed.\n *\n * v3 wire — extended from the v1 stub to carry every field the\n * civic-grade card renders. Privacy: `reporterPhone` is replaced by a\n * `hasReporterPhone` boolean so the public feed can show a Call CTA\n * without exposing the raw number; the detail page issues the\n * authenticated read that returns the actual tel: target.\n */\nexport const suggestionsComplaintsFeedItemSchema = z.object({\n _id: z.string(),\n contentKind: z.literal('suggestions_complaints'),\n author: postAuthorSnapshotSchema,\n\n /** `'complaint'` or `'suggestion'` — drives the card chrome (priority\n * stripe vs. Reddit-vote rail). */\n kind: z.enum(['complaint', 'suggestion']),\n title: z.string(),\n description: z.string(),\n\n // ─── v3 additions (all optional for backward compat) ─────────────\n // The v1 wire shipped only `kind / title / description`. The v3\n // card needs the full set; until the projector is upgraded the\n // fields here remain optional so production reads still validate.\n /** Free-text civic category (Water Supply / Electricity / ...). */\n category: z.string().optional(),\n /** Severity — drives the priority side stripe. */\n priority: suggestionsComplaintsPrioritySchema.optional(),\n /** Lifecycle status. */\n status: suggestionsComplaintsStatusSchema.optional(),\n\n /** Photo evidence — cap is 3 (matches the form policy). Legacy\n * rows authored before the cap was tightened may carry up to 10;\n * consumers should clamp at render time if they care. */\n images: z.array(z.string().url()).max(10).optional(),\n /** Voice-note attachment. */\n audio: suggestionsComplaintsAudioSchema.nullable().optional(),\n /** Single native video upload. */\n video: suggestionsComplaintsVideoSchema.nullable().optional(),\n /** External video links (YouTube / Vimeo). */\n videoLinks: z.array(z.string().url()).max(5).optional(),\n\n /** Address breakdown the card renders. */\n location: suggestionsComplaintsLocationSchema.optional(),\n\n /** Whether the report has a public-contact phone. The Call pill\n * links to a separate authenticated read that resolves the\n * number — keeping the raw phone out of the cached feed\n * response. */\n hasReporterPhone: z.boolean().optional(),\n\n /** Assigned-officer response summary. */\n officerResponse: suggestionsComplaintsOfficerResponseSchema.nullable().optional(),\n /** Resolution timestamp — surfaces the green resolution footer. */\n resolvedAt: z.string().datetime().nullable().optional(),\n\n score: z.number().int(),\n commentCount: z.number().int().nonnegative(),\n repostCount: 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 vote: voteValueSchema.nullable(),\n reaction: z.string().nullable(),\n bookmarked: z.boolean(),\n isAuthor: z.boolean(),\n reposted: z.boolean(),\n })\n .optional(),\n});\nexport type SuggestionsComplaintsFeedItem = z.infer<typeof suggestionsComplaintsFeedItemSchema>;\n\n// ─── Polymorphic union ─────────────────────────────────────────────────────\n\nexport const feedItemSchema = z.discriminatedUnion('contentKind', [\n communityPostWireSchema,\n lostFoundFeedItemSchema,\n suggestionsComplaintsFeedItemSchema,\n donateItemFeedItemSchema,\n communityPollFeedItemSchema,\n crowdfundingFeedItemSchema,\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 suggestions-complaints (complaints + suggestions).\n *\n * The feed is a SINGLE list typed as `FeedItem = Post | LostFound | SuggestionsComplaints`.\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 / SuggestionsComplaints 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, suggestions-complaints-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 { crowdfundingFeedItemSchema } from './crowdfunding.js';\nimport { voteValueSchema } from './engagement.js';\nimport { feedSortSchema, visibilityLevelSchema } from './enums.js';\nimport { communityPollFeedItemSchema } from './poll.js';\nimport { communityPostWireSchema, postAuthorSnapshotSchema } from './post.js';\n\n// ─── Per-kind feed envelopes ───────────────────────────────────────────────\n\n/**\n * Lost-and-found category enum — mirrors the reform-backend\n * `LOST_FOUND_CATEGORY` map. Stable English snake_case slugs; the\n * card maps each to an icon + localised label.\n */\nexport const LOST_FOUND_CATEGORY_VALUES = [\n 'electronics',\n 'jewelry',\n 'documents',\n 'clothing',\n 'pets',\n 'bags_luggage',\n 'keys',\n 'wallet_purse',\n 'phone',\n 'id_cards',\n 'other',\n] as const;\nexport const lostFoundCategorySchema = z.enum(LOST_FOUND_CATEGORY_VALUES);\nexport type LostFoundCategory = z.infer<typeof lostFoundCategorySchema>;\n\n/**\n * Lost-and-found status — `active` is the in-feed state, `resolved`\n * means a claim closed the report, `expired` means the auto-archive\n * window passed.\n */\nexport const LOST_FOUND_STATUS_VALUES = ['active', 'resolved', 'expired'] as const;\nexport const lostFoundStatusSchema = z.enum(LOST_FOUND_STATUS_VALUES);\nexport type LostFoundStatus = z.infer<typeof lostFoundStatusSchema>;\n\n/**\n * Slim area reference snapshot — mirrors the donations PickupAddress\n * AreaRefSnapshot. Stored on lost-found reports so the location pill\n * can show \"Locality, District, State\" without an Area lookup.\n */\nconst lostFoundAreaRefSchema = z.object({\n _id: z.string().optional(),\n name: z.string(),\n type: z.string().optional(),\n urbanBodyType: z.string().optional(),\n});\n\n/**\n * Structured location for a lost-found report. Mirrors the donations\n * pickup address with deliberate omissions: lost-found reports\n * describe an INCIDENT spot, not a delivery address, so\n * - `housePlotDetails` and `pincode` are dropped (no postal logistics);\n * - `coords` are dropped (no GPS capture — the reporter usually isn't\n * standing at the spot when they file the report);\n * - `landmark` + `directions` are kept (the user's free-text\n * description of where).\n *\n * Every field is optional so a sparse report (\"I lost it somewhere in\n * Mumbai\") is still valid.\n */\nexport const lostFoundLocationSchema = z.object({\n country: z.string().optional(),\n state: lostFoundAreaRefSchema.optional(),\n division: lostFoundAreaRefSchema.optional(),\n district: lostFoundAreaRefSchema.optional(),\n subDistrict: lostFoundAreaRefSchema.optional(),\n areaType: z.enum(['RURAL', 'URBAN']).optional(),\n block: lostFoundAreaRefSchema.optional(),\n gramPanchayat: lostFoundAreaRefSchema.optional(),\n village: lostFoundAreaRefSchema.optional(),\n hamlet: lostFoundAreaRefSchema.optional(),\n ruralWard: lostFoundAreaRefSchema.optional(),\n urbanBody: lostFoundAreaRefSchema.optional(),\n urbanWard: lostFoundAreaRefSchema.optional(),\n locality: lostFoundAreaRefSchema.optional(),\n policeStation: lostFoundAreaRefSchema.optional(),\n /** Free-text landmark, e.g. \"near the bus stop\". */\n landmark: z.string().max(200).optional(),\n /** Free-text additional context, e.g. \"south-west corner near the\n * parking lot\". Stored as `directions` on the model to match the\n * donations naming so the shared address normaliser works without\n * a per-feature aliasing layer. */\n directions: z.string().max(500).optional(),\n});\nexport type LostFoundLocation = z.infer<typeof lostFoundLocationSchema>;\n\n/** HH:MM-format time window on the report's date. Either endpoint may\n * be omitted, so the user can record \"after 2pm\", \"before 4pm\", or\n * \"between 2 and 4pm\" with the same shape. */\nexport const lostFoundTimeRangeSchema = z\n .object({\n startTime: z\n .string()\n .regex(/^([01]\\d|2[0-3]):[0-5]\\d$/, 'startTime must be HH:MM (24-hour)')\n .optional(),\n endTime: z\n .string()\n .regex(/^([01]\\d|2[0-3]):[0-5]\\d$/, 'endTime must be HH:MM (24-hour)')\n .optional(),\n })\n // At least one endpoint must be set — an empty `{}` adds no\n // information and is omitted by the form before submit.\n .refine((tr) => Boolean(tr.startTime || tr.endTime), {\n message: 'timeRange must include at least startTime or endTime',\n });\nexport type LostFoundTimeRange = z.infer<typeof lostFoundTimeRangeSchema>;\n\n/**\n * Lost-and-found item projected into the feed.\n *\n * v3 wire — extended to carry every field the production card renders\n * inline, so a single feed read serves the whole row. The detail page\n * (which loads `IReformLostFoundReport` directly) carries the long\n * tail of fields like `creatorName`, `resolvedWith`, audit logs.\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 ribbon banner colour. */\n kind: z.enum(['lost', 'found']),\n title: z.string(),\n description: z.string(),\n\n // ─── v3 additions (all optional for backward compat) ─────────────\n // Live consumers reading the v1 wire keep working because every\n // new field is optional. The v3 projector populates them; the v3\n // card reads them. As consumers migrate to v3, the optionality can\n // tighten in a future minor bump.\n /** Curated category — drives the category icon + label chip.\n * Optional only until the v3 projector ships. */\n category: lostFoundCategorySchema.optional(),\n /** All images attached to the report (up to 5). The card renders a\n * swipeable carousel; the legacy `imageUrl` field below is kept\n * as a deprecated alias for `images[0]`. */\n images: z.array(z.string().url()).max(5).optional(),\n /** Where the item was lost / found.\n *\n * Structured shape: the area-hierarchy snapshot the user picked in\n * the LostFoundLocationDrawer plus optional `landmark` /\n * `directions` free-text. The card composes a single human-readable\n * string from these for the location pill.\n *\n * Backward compat: rows created before this migration carry a plain\n * string. The projector converts those legacy values to\n * `{ landmark: <string> }` on the way out so the wire always speaks\n * the new shape and the card stays simple. */\n location: lostFoundLocationSchema.optional(),\n /** Event date — when the item was lost / found. Distinct from\n * `createdAt`. */\n date: z.string().datetime().optional(),\n /** Optional time window on the report's `date` when the user\n * believes the item was lost or found. Both endpoints are HH:MM\n * strings (24-hour) and both are optional so the user can supply\n * just a start, just an end, or both. Omitted entirely when the\n * user couldn't narrow it down to a time. */\n timeRange: lostFoundTimeRangeSchema.optional(),\n /** For `kind === 'found'`: instructions on where to collect the\n * item. Null on `lost` reports. */\n collectionPoint: z.string().nullable().optional(),\n /** Public dial-back number captured on the report. */\n contactPhone: z.string().nullable().optional(),\n /** Lifecycle status. */\n status: lostFoundStatusSchema.optional(),\n /** Distinct-claimant count — drives the \"3 claims\" social-proof\n * chip. Maintained by the LostFoundClaim service. */\n claimCount: z.number().int().nonnegative().optional(),\n /** Free-text reward note (\"₹2,500\", \"Will treat to dinner\"). */\n rewardOffered: z.string().max(40).nullable().optional(),\n\n /** Deprecated single primary image. New consumers should read\n * `images[0]`; kept on the wire for the v1 card path. */\n imageUrl: z.string().url().optional(),\n\n /** Inherited engagement axis — same shape as posts. Score is the\n * signed net (upvotes - downvotes), can be negative. */\n score: z.number().int(),\n commentCount: z.number().int().nonnegative(),\n repostCount: 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 vote: voteValueSchema.nullable(),\n reaction: z.string().nullable(),\n bookmarked: z.boolean(),\n isAuthor: z.boolean(),\n reposted: z.boolean(),\n })\n .optional(),\n});\nexport type LostFoundFeedItem = z.infer<typeof lostFoundFeedItemSchema>;\n\n/**\n * Donate-item status — mirrors the reform-backend\n * `ITEM_DONATION_STATUS` enum. `CANCELLED` is a terminal out-of-band\n * state (donor or admin withdrew the listing).\n */\nexport const DONATE_ITEM_STATUS_VALUES = [\n 'PENDING',\n 'VERIFIED',\n 'AT_CENTER',\n 'CANCELLED',\n 'DONATED',\n] as const;\nexport const donateItemStatusSchema = z.enum(DONATE_ITEM_STATUS_VALUES);\nexport type DonateItemStatus = z.infer<typeof donateItemStatusSchema>;\n\n/**\n * Item-condition labels surfaced on the chip. Mirrors the reform-\n * backend `ITEM_CONDITION` enum.\n */\nexport const DONATE_ITEM_CONDITION_VALUES = [\n 'NEW',\n 'LIKE_NEW',\n 'GOOD',\n 'FAIR',\n 'OLD_FUNCTIONAL',\n 'NEEDS_MINOR_REPAIR',\n 'NEEDS_MAJOR_REPAIR',\n 'FOR_PARTS',\n] as const;\nexport const donateItemConditionSchema = z.enum(DONATE_ITEM_CONDITION_VALUES);\nexport type DonateItemCondition = z.infer<typeof donateItemConditionSchema>;\n\n/** Audit summary shapes — short, denormalised projections of the\n * underlying pipeline blocks. The detail page carries the full\n * notes; the feed card just needs the \"who + when\". */\nexport const donateAuditByAtSchema = z.object({\n /** Display name of the person who performed the step. */\n byName: z.string(),\n /** When the step happened. */\n at: z.string().datetime(),\n});\nexport const donateAuditAtSchema = z.object({ at: z.string().datetime() });\nexport const donateDonatedToSchema = z.object({\n /** Name of the recipient (person or institution). */\n name: z.string(),\n at: z.string().datetime(),\n});\n\n/**\n * Donated-item listing projected into the feed.\n *\n * v3 wire — extended to carry every field the production card renders\n * inline. The full pickup-address sub-doc, verification notes, and\n * pipeline notes stay on the detail page.\n */\nexport const donateItemFeedItemSchema = z.object({\n _id: z.string(),\n contentKind: z.literal('donate_item'),\n /** Author of the donation. When `isAnonymous` is true, the wire\n * still ships a placeholder author (initials + neutral gradient,\n * no real `username` / `displayName`) — the server scrubs PII so\n * the wire is the privacy boundary, not the card. */\n author: postAuthorSnapshotSchema,\n\n /** Display name of the item. */\n itemName: z.string(),\n /** Optional free-text description. */\n description: z.string().optional(),\n /** Free-text category for now (the backend enum is widening; keeping\n * this open avoids a schema break each time a category is added). */\n category: z.string(),\n /** Item condition. Optional only because legacy rows pre-date the\n * field; new writes always populate it. */\n condition: donateItemConditionSchema.optional(),\n\n /** Lifecycle status — `string` for backward compat with v1\n * consumers that received an open string; v3 consumers should\n * refine against `donateItemStatusSchema`. */\n status: z.string(),\n\n // ─── v3 additions (all optional for backward compat) ─────────────\n /** Estimated worth in INR (₹). Null when the donor declined. */\n estimatedWorthInr: z.number().int().nullable().optional(),\n /** All photos attached to the donation (up to 3). */\n photos: z.array(z.string().url()).max(3).optional(),\n /** Donor opted to hide identity on the public feed. */\n isAnonymous: z.boolean().optional(),\n /** Distance from the viewer's `currentLocation` in km. */\n distanceKm: z.number().nonnegative().nullable().optional(),\n /** Date after which the donor is no longer available. */\n availableUntil: z.string().datetime().nullable().optional(),\n /** Pipeline audit summaries — each null when the step hasn't\n * happened yet. The card progressively reveals the blocks as the\n * donation moves through the pipeline. */\n verification: donateAuditByAtSchema.nullable().optional(),\n pickup: donateAuditByAtSchema.nullable().optional(),\n centerSubmission: donateAuditAtSchema.nullable().optional(),\n donatedTo: donateDonatedToSchema.nullable().optional(),\n\n /** Deprecated single primary image. New consumers should read\n * `photos[0]`; kept on the wire for the v1 card path. */\n imageUrl: z.string().url().optional(),\n\n score: z.number().int(),\n commentCount: z.number().int().nonnegative(),\n repostCount: 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 vote: voteValueSchema.nullable(),\n reaction: z.string().nullable(),\n bookmarked: z.boolean(),\n isAuthor: z.boolean(),\n reposted: z.boolean(),\n })\n .optional(),\n});\nexport type DonateItemFeedItem = z.infer<typeof donateItemFeedItemSchema>;\n\n/**\n * Suggestions & complaints priority — mirrors `IReformIssueReport.priority`.\n * Drives the side-stripe colour on the complaint card.\n */\nexport const SUGGESTIONS_COMPLAINTS_PRIORITY_VALUES = [\n 'low',\n 'medium',\n 'high',\n 'critical',\n] as const;\nexport const suggestionsComplaintsPrioritySchema = z.enum(SUGGESTIONS_COMPLAINTS_PRIORITY_VALUES);\nexport type SuggestionsComplaintsPriority = z.infer<typeof suggestionsComplaintsPrioritySchema>;\n\n/**\n * Suggestions & complaints lifecycle status. Five states matching the model — the\n * card surfaces `pending`/`under_review`/`in_progress` as in-flight\n * pills, `resolved` as a green resolution footer, `rejected` as\n * neutral with an explanation.\n */\nexport const SUGGESTIONS_COMPLAINTS_STATUS_VALUES = [\n 'pending',\n 'under_review',\n 'in_progress',\n 'resolved',\n 'rejected',\n] as const;\nexport const suggestionsComplaintsStatusSchema = z.enum(SUGGESTIONS_COMPLAINTS_STATUS_VALUES);\nexport type SuggestionsComplaintsStatus = z.infer<typeof suggestionsComplaintsStatusSchema>;\n\n/** Inline-playable audio attachment (voice complaint). The model\n * stores `IssueAudio` with `url + gcpPath + uploadedAt + duration`;\n * the wire is trimmed to what the card needs to render the WhatsApp-\n * style voice note bar. */\nexport const suggestionsComplaintsAudioSchema = z.object({\n url: z.string().url(),\n /** Duration in seconds. Drives the bar's duration chip. */\n durationSec: z.number().positive(),\n});\n\n/** Inline-playable video attachment (single native upload). */\nexport const suggestionsComplaintsVideoSchema = z.object({\n url: z.string().url(),\n thumbnailUrl: z.string().url(),\n durationSec: z.number().positive(),\n});\n\n/** Officer-response summary projected onto the feed wire so the card\n * can render the sky-blue \"Official response\" block without a\n * second round trip. Full text + reply audit log live on the\n * detail page. */\nexport const suggestionsComplaintsOfficerResponseSchema = z.object({\n /** Display name of the assigned officer. */\n officerName: z.string(),\n /** Officer's designation (Pradhan / Mayor / Junior Engineer / ...).\n * Null when the assigned user has no civic designation. */\n designation: z.string().nullable(),\n /** Truncated response text — max 280 chars. The full note lives on\n * the detail page. */\n text: z.string().max(280),\n respondedAt: z.string().datetime(),\n});\n\n/** Structured incident-location for a suggestion/complaint report.\n *\n * Same shape as `lostFoundLocationSchema` — area-hierarchy snapshot\n * the user picked in the location drawer (state / district /\n * locality / etc.) plus optional `landmark` + `directions` free-text.\n * Lat/lng intentionally NOT on the wire to keep reporter privacy\n * intact; the card composes a single human-readable string from\n * these via `formatLostFoundLocation` (the helper is shape-agnostic).\n *\n * Legacy rows authored before this migration carry a flat\n * `{ address, district, pinCode }` shape on the model; the feed\n * projector coerces those into `{ landmark: address, district:\n * { name: district } }` on the way out so consumers only see one\n * shape. */\nconst suggestionsComplaintsAreaRefSchema = z.object({\n _id: z.string().optional(),\n name: z.string(),\n type: z.string().optional(),\n urbanBodyType: z.string().optional(),\n});\n\nexport const suggestionsComplaintsLocationSchema = z.object({\n country: z.string().optional(),\n state: suggestionsComplaintsAreaRefSchema.optional(),\n division: suggestionsComplaintsAreaRefSchema.optional(),\n district: suggestionsComplaintsAreaRefSchema.optional(),\n subDistrict: suggestionsComplaintsAreaRefSchema.optional(),\n areaType: z.enum(['RURAL', 'URBAN']).optional(),\n block: suggestionsComplaintsAreaRefSchema.optional(),\n gramPanchayat: suggestionsComplaintsAreaRefSchema.optional(),\n village: suggestionsComplaintsAreaRefSchema.optional(),\n hamlet: suggestionsComplaintsAreaRefSchema.optional(),\n ruralWard: suggestionsComplaintsAreaRefSchema.optional(),\n urbanBody: suggestionsComplaintsAreaRefSchema.optional(),\n urbanWard: suggestionsComplaintsAreaRefSchema.optional(),\n locality: suggestionsComplaintsAreaRefSchema.optional(),\n policeStation: suggestionsComplaintsAreaRefSchema.optional(),\n landmark: z.string().max(200).optional(),\n directions: z.string().max(500).optional(),\n});\nexport type SuggestionsComplaintsLocation = z.infer<typeof suggestionsComplaintsLocationSchema>;\n\n/**\n * Suggestions & complaints (complaint or suggestion) item projected into the feed.\n *\n * v3 wire — extended from the v1 stub to carry every field the\n * civic-grade card renders. Privacy: `reporterPhone` is replaced by a\n * `hasReporterPhone` boolean so the public feed can show a Call CTA\n * without exposing the raw number; the detail page issues the\n * authenticated read that returns the actual tel: target.\n */\nexport const suggestionsComplaintsFeedItemSchema = z.object({\n _id: z.string(),\n contentKind: z.literal('suggestions_complaints'),\n /** Author of the report. When `isAnonymous` is true, the server ships\n * a true-anonymous snapshot (no userId / username / avatar / display\n * name) — no way to open the author's profile or contact them. */\n author: postAuthorSnapshotSchema,\n /** Reporter opted to stay anonymous. */\n isAnonymous: z.boolean().optional(),\n\n /** `'complaint'` or `'suggestion'` — drives the card chrome (priority\n * stripe vs. Reddit-vote rail). */\n kind: z.enum(['complaint', 'suggestion']),\n title: z.string(),\n description: z.string(),\n\n // ─── v3 additions (all optional for backward compat) ─────────────\n // The v1 wire shipped only `kind / title / description`. The v3\n // card needs the full set; until the projector is upgraded the\n // fields here remain optional so production reads still validate.\n /** Free-text civic category (Water Supply / Electricity / ...). */\n category: z.string().optional(),\n /** Severity — drives the priority side stripe. */\n priority: suggestionsComplaintsPrioritySchema.optional(),\n /** Lifecycle status. */\n status: suggestionsComplaintsStatusSchema.optional(),\n\n /** Photo evidence — cap is 3 (matches the form policy). Legacy\n * rows authored before the cap was tightened may carry up to 10;\n * consumers should clamp at render time if they care. */\n images: z.array(z.string().url()).max(10).optional(),\n /** Voice-note attachment. */\n audio: suggestionsComplaintsAudioSchema.nullable().optional(),\n /** Single native video upload. */\n video: suggestionsComplaintsVideoSchema.nullable().optional(),\n /** External video links (YouTube / Vimeo). */\n videoLinks: z.array(z.string().url()).max(5).optional(),\n\n /** Address breakdown the card renders. */\n location: suggestionsComplaintsLocationSchema.optional(),\n\n /** Whether the report has a public-contact phone. The Call pill\n * links to a separate authenticated read that resolves the\n * number — keeping the raw phone out of the cached feed\n * response. */\n hasReporterPhone: z.boolean().optional(),\n\n /** Assigned-officer response summary. */\n officerResponse: suggestionsComplaintsOfficerResponseSchema.nullable().optional(),\n /** Resolution timestamp — surfaces the green resolution footer. */\n resolvedAt: z.string().datetime().nullable().optional(),\n\n score: z.number().int(),\n commentCount: z.number().int().nonnegative(),\n repostCount: 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 vote: voteValueSchema.nullable(),\n reaction: z.string().nullable(),\n bookmarked: z.boolean(),\n isAuthor: z.boolean(),\n reposted: z.boolean(),\n })\n .optional(),\n});\nexport type SuggestionsComplaintsFeedItem = z.infer<typeof suggestionsComplaintsFeedItemSchema>;\n\n// ─── Polymorphic union ─────────────────────────────────────────────────────\n\nexport const feedItemSchema = z.discriminatedUnion('contentKind', [\n communityPostWireSchema,\n lostFoundFeedItemSchema,\n suggestionsComplaintsFeedItemSchema,\n donateItemFeedItemSchema,\n communityPollFeedItemSchema,\n crowdfundingFeedItemSchema,\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/poll.d.ts
CHANGED
|
@@ -109,6 +109,7 @@ export declare const communityPollWireSchema: z.ZodObject<{
|
|
|
109
109
|
}, z.core.$strip>>>;
|
|
110
110
|
identityVerified: z.ZodOptional<z.ZodBoolean>;
|
|
111
111
|
}, z.core.$strip>;
|
|
112
|
+
isAnonymous: z.ZodOptional<z.ZodBoolean>;
|
|
112
113
|
question: z.ZodString;
|
|
113
114
|
options: z.ZodArray<z.ZodObject<{
|
|
114
115
|
index: z.ZodNumber;
|
|
@@ -193,6 +194,7 @@ export declare const communityPollFeedItemSchema: z.ZodObject<{
|
|
|
193
194
|
}, z.core.$strip>>>;
|
|
194
195
|
identityVerified: z.ZodOptional<z.ZodBoolean>;
|
|
195
196
|
}, z.core.$strip>;
|
|
197
|
+
isAnonymous: z.ZodOptional<z.ZodBoolean>;
|
|
196
198
|
question: z.ZodString;
|
|
197
199
|
options: z.ZodArray<z.ZodObject<{
|
|
198
200
|
index: z.ZodNumber;
|
|
@@ -252,6 +254,7 @@ export declare const createPollBodySchema: z.ZodObject<{
|
|
|
252
254
|
after_close: "after_close";
|
|
253
255
|
}>;
|
|
254
256
|
communityId: z.ZodOptional<z.ZodString>;
|
|
257
|
+
isAnonymous: z.ZodDefault<z.ZodBoolean>;
|
|
255
258
|
}, z.core.$strip>;
|
|
256
259
|
export type CreatePollBody = z.infer<typeof createPollBodySchema>;
|
|
257
260
|
/**
|
|
@@ -308,6 +311,7 @@ export declare const voteResponseSchema: z.ZodObject<{
|
|
|
308
311
|
}, z.core.$strip>>>;
|
|
309
312
|
identityVerified: z.ZodOptional<z.ZodBoolean>;
|
|
310
313
|
}, z.core.$strip>;
|
|
314
|
+
isAnonymous: z.ZodOptional<z.ZodBoolean>;
|
|
311
315
|
question: z.ZodString;
|
|
312
316
|
options: z.ZodArray<z.ZodObject<{
|
|
313
317
|
index: z.ZodNumber;
|
package/dist/poll.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"poll.d.ts","sourceRoot":"","sources":["../src/poll.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAiBxB;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,8BAA8B,kCAAmC,CAAC;AAC/E,eAAO,MAAM,2BAA2B;;;EAAyC,CAAC;AAClF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAIhF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB;;;;iBAQ/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,gDAAgD;AAChD,eAAO,MAAM,qBAAqB;;iBAEhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAIpE;;;;GAIG;AACH,eAAO,MAAM,uBAAuB
|
|
1
|
+
{"version":3,"file":"poll.d.ts","sourceRoot":"","sources":["../src/poll.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAiBxB;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,8BAA8B,kCAAmC,CAAC;AAC/E,eAAO,MAAM,2BAA2B;;;EAAyC,CAAC;AAClF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAIhF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB;;;;iBAQ/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,gDAAgD;AAChD,eAAO,MAAM,qBAAqB;;iBAEhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAIpE;;;;GAIG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmElC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAIxE;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAA0B,CAAC;AACnE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAIhF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;iBAgC9B,CAAC;AACJ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB;;iBAW7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAE7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
|
package/dist/poll.js
CHANGED
|
@@ -81,7 +81,12 @@ export const pollOptionInputSchema = z.object({
|
|
|
81
81
|
export const communityPollWireSchema = z.object({
|
|
82
82
|
_id: z.string(),
|
|
83
83
|
contentKind: z.literal('poll'),
|
|
84
|
+
/** Author of the poll. When `isAnonymous` is true, the server ships a
|
|
85
|
+
* true-anonymous snapshot (no userId / username / avatar / display
|
|
86
|
+
* name) — no way to open the author's profile or contact them. */
|
|
84
87
|
author: postAuthorSnapshotSchema,
|
|
88
|
+
/** Author opted to post the poll anonymously. */
|
|
89
|
+
isAnonymous: z.boolean().optional(),
|
|
85
90
|
/** The poll question. Renders as the card's primary text. */
|
|
86
91
|
question: z.string().min(POLL_QUESTION_MIN_CHARS).max(POLL_QUESTION_MAX_CHARS),
|
|
87
92
|
/** Options 0..N-1 (N in `[POLL_MIN_OPTIONS, POLL_MAX_OPTIONS]`).
|
|
@@ -164,6 +169,9 @@ export const createPollBodySchema = z
|
|
|
164
169
|
/** Optional community scope. If set the author must be an active
|
|
165
170
|
* member of the community — the server checks. */
|
|
166
171
|
communityId: z.string().optional(),
|
|
172
|
+
/** When true, the author's identity is hidden on the public wire.
|
|
173
|
+
* Server-enforced: only an identity-verified user may set this. */
|
|
174
|
+
isAnonymous: z.boolean().default(false),
|
|
167
175
|
})
|
|
168
176
|
.refine((b) => {
|
|
169
177
|
// Options must be unique by their trimmed label — duplicate
|
package/dist/poll.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"poll.js","sourceRoot":"","sources":["../src/poll.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Community poll wire shapes.\n *\n * A poll is a structured-choice feed item: a question + 2..6 options +\n * a fixed expiry. Lives in its own `community_polls` collection (NOT\n * an extension of `CommunityPost`); votes live in a separate\n * `poll_votes` collection keyed by `(pollId, userId)`. Per-option vote\n * counts + total-voter count are denormalised onto the poll so a feed\n * read serves the whole card in one document fetch.\n *\n * Industry alignment:\n * - Twitter / X: single-select, time-limited (5min..7d), results live.\n * - WhatsApp / Telegram: single OR multi-select, time-limited.\n * - Facebook: single OR multi-select, results live, no expiry.\n * - Reddit: single-select, 6h..7d, results post-vote.\n *\n * Our design:\n * - Required expiry, 5min..30d. Composer presets 1d / 3d / 7d / 14d.\n * - Author opts in for multi-select via `allowsMultipleChoices`.\n * - Author opts in for results-after-close (default: live) via\n * `resultsVisibility`. Server hides per-option counts on the wire\n * when the poll is still open AND `resultsVisibility === 'after_close'`,\n * so a peeking viewer can't fish results out of the API.\n *\n * Edits are NOT supported: changing question / options / expiry after\n * a vote has been cast would invalidate that vote. The author can\n * delete (soft-delete, freezes the poll) or close-early (sets\n * `closesAt` to now), but not edit.\n *\n * @module community-schema/poll\n */\n\nimport { z } from 'zod';\nimport { areaLineageSnapshotSchema } from './area.js';\nimport {\n POLL_MAX_DURATION_MS,\n POLL_MAX_OPTIONS,\n POLL_MIN_DURATION_MS,\n POLL_MIN_OPTIONS,\n POLL_OPTION_MAX_CHARS,\n POLL_OPTION_MIN_CHARS,\n POLL_QUESTION_MAX_CHARS,\n POLL_QUESTION_MIN_CHARS,\n} from './constants.js';\nimport { voteValueSchema } from './engagement.js';\nimport { postAuthorSnapshotSchema } from './post.js';\n\n// ─── Results visibility ────────────────────────────────────────────────────\n\n/**\n * When per-option vote counts become visible to voters.\n *\n * - `live` — counts visible to everyone, always. Default;\n * matches Facebook / Telegram. Encourages\n * engagement via \"see what's winning\".\n * - `after_close` — counts hidden until the poll closes. The wire\n * shape's option counts are server-replaced with\n * `0` during the open window so a curious viewer\n * can't fish results out via the JSON. Best for\n * genuinely-blind decisions (community polls\n * about admin selection, etc.).\n */\nexport const POLL_RESULTS_VISIBILITY_VALUES = ['live', 'after_close'] as const;\nexport const pollResultsVisibilitySchema = z.enum(POLL_RESULTS_VISIBILITY_VALUES);\nexport type PollResultsVisibility = z.infer<typeof pollResultsVisibilitySchema>;\n\n// ─── Poll option (denormalised on the poll doc) ────────────────────────────\n\n/**\n * One choice in the poll. `index` is the canonical identifier the vote\n * body refers to (a stable integer 0..N-1 — letting the client send\n * indices instead of labels avoids hash-collision games with non-ASCII\n * labels). `voteCount` is the denormalised aggregate, updated atomically\n * by the vote endpoint and zeroed on the wire when `resultsVisibility`\n * is `after_close` AND the poll is still open.\n */\nexport const pollOptionWireSchema = z.object({\n index: z\n .number()\n .int()\n .min(0)\n .max(POLL_MAX_OPTIONS - 1),\n label: z.string().min(POLL_OPTION_MIN_CHARS).max(POLL_OPTION_MAX_CHARS),\n voteCount: z.number().int().nonnegative(),\n});\nexport type PollOptionWire = z.infer<typeof pollOptionWireSchema>;\n\n/** Composer-side option (no vote count yet). */\nexport const pollOptionInputSchema = z.object({\n label: z.string().trim().min(POLL_OPTION_MIN_CHARS).max(POLL_OPTION_MAX_CHARS),\n});\nexport type PollOptionInput = z.infer<typeof pollOptionInputSchema>;\n\n// ─── Wire shape — what the server returns ──────────────────────────────────\n\n/**\n * A community poll as it appears in the feed and on a poll detail\n * page. Engagement counts mirror the post wire so the same engagement\n * bar component renders for every kind.\n */\nexport const communityPollWireSchema = z.object({\n _id: z.string(),\n contentKind: z.literal('poll'),\n author: postAuthorSnapshotSchema,\n\n /** The poll question. Renders as the card's primary text. */\n question: z.string().min(POLL_QUESTION_MIN_CHARS).max(POLL_QUESTION_MAX_CHARS),\n /** Options 0..N-1 (N in `[POLL_MIN_OPTIONS, POLL_MAX_OPTIONS]`).\n * Per-option `voteCount` is zeroed on the wire while the poll is\n * open AND `resultsVisibility === 'after_close'` (the\n * total-voters count is still surfaced so the card can show\n * \"X voted\" without revealing how it splits). */\n options: z.array(pollOptionWireSchema).min(POLL_MIN_OPTIONS).max(POLL_MAX_OPTIONS),\n\n /** Number of UNIQUE voters. Always visible — even when option\n * counts are hidden — so the card can render \"X have voted\" as a\n * social proof signal. */\n totalVoters: z.number().int().nonnegative(),\n\n /** `true` when the author let voters pick more than one option. */\n allowsMultipleChoices: z.boolean(),\n /** Results visibility policy. See `pollResultsVisibilitySchema`. */\n resultsVisibility: pollResultsVisibilitySchema,\n\n /** ISO-string. The poll is closed when this is `<= now()`. Vote\n * endpoints reject after this; the client also hides the Vote CTA. */\n closesAt: z.string().datetime(),\n\n // Authorship area + scope (identical to communityPostWireSchema)\n areaLineage: areaLineageSnapshotSchema,\n communityId: z.string().optional(),\n\n // Denormalised engagement counters\n /** Net score = upvotes - downvotes. Signed — can be negative. */\n score: z.number().int(),\n commentCount: z.number().int().nonnegative(),\n repostCount: z.number().int().nonnegative(),\n reactionCounts: z.record(z.string(), z.number().int().nonnegative()),\n\n // Lifecycle\n createdAt: z.string().datetime(),\n /** Polls are never edited (see module docstring); editedAt is kept\n * on the wire as `null` for parity with the post envelope so card\n * templates can share a single byline component. */\n editedAt: z.string().datetime().nullable(),\n deletedAt: z.string().datetime().nullable(),\n\n // Per-viewer state (filled at read time)\n viewer: z\n .object({\n vote: voteValueSchema.nullable(),\n reaction: z.string().nullable(),\n bookmarked: z.boolean(),\n isAuthor: z.boolean(),\n reposted: z.boolean(),\n /** Indices the viewer voted for. Empty array means \"hasn't\n * voted yet\". Populated for every viewer regardless of\n * `resultsVisibility` — a voter can always see their own\n * choice. */\n votedOptionIndices: z.array(z.number().int().nonnegative()),\n })\n .optional(),\n});\nexport type CommunityPollWire = z.infer<typeof communityPollWireSchema>;\n\n// ─── Feed envelope ─────────────────────────────────────────────────────────\n\n/**\n * Same shape as `communityPollWireSchema` — the wire IS the feed\n * envelope (no pruning needed because polls are compact: a question +\n * ≤6 short option labels + counters). Aliased so consumers can import\n * the feed-envelope name in parallel with the existing lostFound /\n * suggestionsComplaints / donateItem envelopes.\n */\nexport const communityPollFeedItemSchema = communityPollWireSchema;\nexport type CommunityPollFeedItem = z.infer<typeof communityPollFeedItemSchema>;\n\n// ─── Create body ───────────────────────────────────────────────────────────\n\n/**\n * Composer payload: what the frontend sends to\n * `POST /api/v1/community/polls`. Author + areaLineage + initial vote\n * counts are filled server-side from the authenticated request and\n * cannot be spoofed by the client.\n */\nexport const createPollBodySchema = z\n .object({\n question: z.string().trim().min(POLL_QUESTION_MIN_CHARS).max(POLL_QUESTION_MAX_CHARS),\n options: z.array(pollOptionInputSchema).min(POLL_MIN_OPTIONS).max(POLL_MAX_OPTIONS),\n /** Duration in milliseconds from now until `closesAt`. Validated\n * against `POLL_MIN_DURATION_MS` / `POLL_MAX_DURATION_MS`. The\n * server resolves `closesAt = createdAt + durationMs` instead of\n * taking an absolute date so clock skew between client and server\n * can't push a poll into the past. */\n durationMs: z.number().int().min(POLL_MIN_DURATION_MS).max(POLL_MAX_DURATION_MS),\n allowsMultipleChoices: z.boolean(),\n resultsVisibility: pollResultsVisibilitySchema,\n /** Optional community scope. If set the author must be an active\n * member of the community — the server checks. */\n communityId: z.string().optional(),\n })\n .refine(\n (b) => {\n // Options must be unique by their trimmed label — duplicate\n // options fragment votes and confuse voters.\n const seen = new Set<string>();\n for (const o of b.options) {\n const k = o.label.trim().toLowerCase();\n if (seen.has(k)) return false;\n seen.add(k);\n }\n return true;\n },\n { message: 'Poll options must be unique', path: ['options'] },\n );\nexport type CreatePollBody = z.infer<typeof createPollBodySchema>;\n\n// ─── Vote body + response ──────────────────────────────────────────────────\n\n/**\n * What the client sends to `POST /api/v1/community/polls/:id/votes`\n * (cast OR change a vote — the server upserts on `(pollId, userId)`).\n *\n * For single-choice polls, `optionIndices` must be a single-element\n * array. The server enforces `length === 1` when the poll's\n * `allowsMultipleChoices` is false.\n */\nexport const castVoteBodySchema = z.object({\n optionIndices: z\n .array(\n z\n .number()\n .int()\n .min(0)\n .max(POLL_MAX_OPTIONS - 1),\n )\n .min(1)\n .max(POLL_MAX_OPTIONS),\n});\nexport type CastVoteBody = z.infer<typeof castVoteBodySchema>;\n\n/**\n * Server returns the updated poll wire after a vote / unvote so the\n * client can swap its local state for the authoritative copy (vote\n * counts, viewer.votedOptionIndices, totalVoters).\n */\nexport const voteResponseSchema = z.object({\n poll: communityPollWireSchema,\n});\nexport type VoteResponse = z.infer<typeof voteResponseSchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"poll.js","sourceRoot":"","sources":["../src/poll.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Community poll wire shapes.\n *\n * A poll is a structured-choice feed item: a question + 2..6 options +\n * a fixed expiry. Lives in its own `community_polls` collection (NOT\n * an extension of `CommunityPost`); votes live in a separate\n * `poll_votes` collection keyed by `(pollId, userId)`. Per-option vote\n * counts + total-voter count are denormalised onto the poll so a feed\n * read serves the whole card in one document fetch.\n *\n * Industry alignment:\n * - Twitter / X: single-select, time-limited (5min..7d), results live.\n * - WhatsApp / Telegram: single OR multi-select, time-limited.\n * - Facebook: single OR multi-select, results live, no expiry.\n * - Reddit: single-select, 6h..7d, results post-vote.\n *\n * Our design:\n * - Required expiry, 5min..30d. Composer presets 1d / 3d / 7d / 14d.\n * - Author opts in for multi-select via `allowsMultipleChoices`.\n * - Author opts in for results-after-close (default: live) via\n * `resultsVisibility`. Server hides per-option counts on the wire\n * when the poll is still open AND `resultsVisibility === 'after_close'`,\n * so a peeking viewer can't fish results out of the API.\n *\n * Edits are NOT supported: changing question / options / expiry after\n * a vote has been cast would invalidate that vote. The author can\n * delete (soft-delete, freezes the poll) or close-early (sets\n * `closesAt` to now), but not edit.\n *\n * @module community-schema/poll\n */\n\nimport { z } from 'zod';\nimport { areaLineageSnapshotSchema } from './area.js';\nimport {\n POLL_MAX_DURATION_MS,\n POLL_MAX_OPTIONS,\n POLL_MIN_DURATION_MS,\n POLL_MIN_OPTIONS,\n POLL_OPTION_MAX_CHARS,\n POLL_OPTION_MIN_CHARS,\n POLL_QUESTION_MAX_CHARS,\n POLL_QUESTION_MIN_CHARS,\n} from './constants.js';\nimport { voteValueSchema } from './engagement.js';\nimport { postAuthorSnapshotSchema } from './post.js';\n\n// ─── Results visibility ────────────────────────────────────────────────────\n\n/**\n * When per-option vote counts become visible to voters.\n *\n * - `live` — counts visible to everyone, always. Default;\n * matches Facebook / Telegram. Encourages\n * engagement via \"see what's winning\".\n * - `after_close` — counts hidden until the poll closes. The wire\n * shape's option counts are server-replaced with\n * `0` during the open window so a curious viewer\n * can't fish results out via the JSON. Best for\n * genuinely-blind decisions (community polls\n * about admin selection, etc.).\n */\nexport const POLL_RESULTS_VISIBILITY_VALUES = ['live', 'after_close'] as const;\nexport const pollResultsVisibilitySchema = z.enum(POLL_RESULTS_VISIBILITY_VALUES);\nexport type PollResultsVisibility = z.infer<typeof pollResultsVisibilitySchema>;\n\n// ─── Poll option (denormalised on the poll doc) ────────────────────────────\n\n/**\n * One choice in the poll. `index` is the canonical identifier the vote\n * body refers to (a stable integer 0..N-1 — letting the client send\n * indices instead of labels avoids hash-collision games with non-ASCII\n * labels). `voteCount` is the denormalised aggregate, updated atomically\n * by the vote endpoint and zeroed on the wire when `resultsVisibility`\n * is `after_close` AND the poll is still open.\n */\nexport const pollOptionWireSchema = z.object({\n index: z\n .number()\n .int()\n .min(0)\n .max(POLL_MAX_OPTIONS - 1),\n label: z.string().min(POLL_OPTION_MIN_CHARS).max(POLL_OPTION_MAX_CHARS),\n voteCount: z.number().int().nonnegative(),\n});\nexport type PollOptionWire = z.infer<typeof pollOptionWireSchema>;\n\n/** Composer-side option (no vote count yet). */\nexport const pollOptionInputSchema = z.object({\n label: z.string().trim().min(POLL_OPTION_MIN_CHARS).max(POLL_OPTION_MAX_CHARS),\n});\nexport type PollOptionInput = z.infer<typeof pollOptionInputSchema>;\n\n// ─── Wire shape — what the server returns ──────────────────────────────────\n\n/**\n * A community poll as it appears in the feed and on a poll detail\n * page. Engagement counts mirror the post wire so the same engagement\n * bar component renders for every kind.\n */\nexport const communityPollWireSchema = z.object({\n _id: z.string(),\n contentKind: z.literal('poll'),\n /** Author of the poll. When `isAnonymous` is true, the server ships a\n * true-anonymous snapshot (no userId / username / avatar / display\n * name) — no way to open the author's profile or contact them. */\n author: postAuthorSnapshotSchema,\n /** Author opted to post the poll anonymously. */\n isAnonymous: z.boolean().optional(),\n\n /** The poll question. Renders as the card's primary text. */\n question: z.string().min(POLL_QUESTION_MIN_CHARS).max(POLL_QUESTION_MAX_CHARS),\n /** Options 0..N-1 (N in `[POLL_MIN_OPTIONS, POLL_MAX_OPTIONS]`).\n * Per-option `voteCount` is zeroed on the wire while the poll is\n * open AND `resultsVisibility === 'after_close'` (the\n * total-voters count is still surfaced so the card can show\n * \"X voted\" without revealing how it splits). */\n options: z.array(pollOptionWireSchema).min(POLL_MIN_OPTIONS).max(POLL_MAX_OPTIONS),\n\n /** Number of UNIQUE voters. Always visible — even when option\n * counts are hidden — so the card can render \"X have voted\" as a\n * social proof signal. */\n totalVoters: z.number().int().nonnegative(),\n\n /** `true` when the author let voters pick more than one option. */\n allowsMultipleChoices: z.boolean(),\n /** Results visibility policy. See `pollResultsVisibilitySchema`. */\n resultsVisibility: pollResultsVisibilitySchema,\n\n /** ISO-string. The poll is closed when this is `<= now()`. Vote\n * endpoints reject after this; the client also hides the Vote CTA. */\n closesAt: z.string().datetime(),\n\n // Authorship area + scope (identical to communityPostWireSchema)\n areaLineage: areaLineageSnapshotSchema,\n communityId: z.string().optional(),\n\n // Denormalised engagement counters\n /** Net score = upvotes - downvotes. Signed — can be negative. */\n score: z.number().int(),\n commentCount: z.number().int().nonnegative(),\n repostCount: z.number().int().nonnegative(),\n reactionCounts: z.record(z.string(), z.number().int().nonnegative()),\n\n // Lifecycle\n createdAt: z.string().datetime(),\n /** Polls are never edited (see module docstring); editedAt is kept\n * on the wire as `null` for parity with the post envelope so card\n * templates can share a single byline component. */\n editedAt: z.string().datetime().nullable(),\n deletedAt: z.string().datetime().nullable(),\n\n // Per-viewer state (filled at read time)\n viewer: z\n .object({\n vote: voteValueSchema.nullable(),\n reaction: z.string().nullable(),\n bookmarked: z.boolean(),\n isAuthor: z.boolean(),\n reposted: z.boolean(),\n /** Indices the viewer voted for. Empty array means \"hasn't\n * voted yet\". Populated for every viewer regardless of\n * `resultsVisibility` — a voter can always see their own\n * choice. */\n votedOptionIndices: z.array(z.number().int().nonnegative()),\n })\n .optional(),\n});\nexport type CommunityPollWire = z.infer<typeof communityPollWireSchema>;\n\n// ─── Feed envelope ─────────────────────────────────────────────────────────\n\n/**\n * Same shape as `communityPollWireSchema` — the wire IS the feed\n * envelope (no pruning needed because polls are compact: a question +\n * ≤6 short option labels + counters). Aliased so consumers can import\n * the feed-envelope name in parallel with the existing lostFound /\n * suggestionsComplaints / donateItem envelopes.\n */\nexport const communityPollFeedItemSchema = communityPollWireSchema;\nexport type CommunityPollFeedItem = z.infer<typeof communityPollFeedItemSchema>;\n\n// ─── Create body ───────────────────────────────────────────────────────────\n\n/**\n * Composer payload: what the frontend sends to\n * `POST /api/v1/community/polls`. Author + areaLineage + initial vote\n * counts are filled server-side from the authenticated request and\n * cannot be spoofed by the client.\n */\nexport const createPollBodySchema = z\n .object({\n question: z.string().trim().min(POLL_QUESTION_MIN_CHARS).max(POLL_QUESTION_MAX_CHARS),\n options: z.array(pollOptionInputSchema).min(POLL_MIN_OPTIONS).max(POLL_MAX_OPTIONS),\n /** Duration in milliseconds from now until `closesAt`. Validated\n * against `POLL_MIN_DURATION_MS` / `POLL_MAX_DURATION_MS`. The\n * server resolves `closesAt = createdAt + durationMs` instead of\n * taking an absolute date so clock skew between client and server\n * can't push a poll into the past. */\n durationMs: z.number().int().min(POLL_MIN_DURATION_MS).max(POLL_MAX_DURATION_MS),\n allowsMultipleChoices: z.boolean(),\n resultsVisibility: pollResultsVisibilitySchema,\n /** Optional community scope. If set the author must be an active\n * member of the community — the server checks. */\n communityId: z.string().optional(),\n /** When true, the author's identity is hidden on the public wire.\n * Server-enforced: only an identity-verified user may set this. */\n isAnonymous: z.boolean().default(false),\n })\n .refine(\n (b) => {\n // Options must be unique by their trimmed label — duplicate\n // options fragment votes and confuse voters.\n const seen = new Set<string>();\n for (const o of b.options) {\n const k = o.label.trim().toLowerCase();\n if (seen.has(k)) return false;\n seen.add(k);\n }\n return true;\n },\n { message: 'Poll options must be unique', path: ['options'] },\n );\nexport type CreatePollBody = z.infer<typeof createPollBodySchema>;\n\n// ─── Vote body + response ──────────────────────────────────────────────────\n\n/**\n * What the client sends to `POST /api/v1/community/polls/:id/votes`\n * (cast OR change a vote — the server upserts on `(pollId, userId)`).\n *\n * For single-choice polls, `optionIndices` must be a single-element\n * array. The server enforces `length === 1` when the poll's\n * `allowsMultipleChoices` is false.\n */\nexport const castVoteBodySchema = z.object({\n optionIndices: z\n .array(\n z\n .number()\n .int()\n .min(0)\n .max(POLL_MAX_OPTIONS - 1),\n )\n .min(1)\n .max(POLL_MAX_OPTIONS),\n});\nexport type CastVoteBody = z.infer<typeof castVoteBodySchema>;\n\n/**\n * Server returns the updated poll wire after a vote / unvote so the\n * client can swap its local state for the authoritative copy (vote\n * counts, viewer.votedOptionIndices, totalVoters).\n */\nexport const voteResponseSchema = z.object({\n poll: communityPollWireSchema,\n});\nexport type VoteResponse = z.infer<typeof voteResponseSchema>;\n"]}
|
package/dist/post.d.ts
CHANGED
|
@@ -167,6 +167,7 @@ export declare const communityPostWireSchema: z.ZodObject<{
|
|
|
167
167
|
}, z.core.$strip>>>;
|
|
168
168
|
identityVerified: z.ZodOptional<z.ZodBoolean>;
|
|
169
169
|
}, z.core.$strip>;
|
|
170
|
+
isAnonymous: z.ZodOptional<z.ZodBoolean>;
|
|
170
171
|
text: z.ZodString;
|
|
171
172
|
images: z.ZodArray<z.ZodObject<{
|
|
172
173
|
url: z.ZodString;
|
|
@@ -299,6 +300,7 @@ export declare const createPostBodySchema: z.ZodObject<{
|
|
|
299
300
|
duration: z.ZodNumber;
|
|
300
301
|
}, z.core.$strip>>>;
|
|
301
302
|
communityId: z.ZodOptional<z.ZodString>;
|
|
303
|
+
isAnonymous: z.ZodDefault<z.ZodBoolean>;
|
|
302
304
|
}, z.core.$strip>;
|
|
303
305
|
export type CreatePostBody = z.infer<typeof createPostBodySchema>;
|
|
304
306
|
/**
|
package/dist/post.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post.d.ts","sourceRoot":"","sources":["../src/post.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+CnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAI1E;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuB/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB
|
|
1
|
+
{"version":3,"file":"post.d.ts","sourceRoot":"","sources":["../src/post.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+CnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAI1E;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuB/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8ElC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAIxE;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;iBAW/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;iBAE/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
|
package/dist/post.js
CHANGED
|
@@ -133,7 +133,13 @@ export const repostedSourceSchema = z.object({
|
|
|
133
133
|
export const communityPostWireSchema = z.object({
|
|
134
134
|
_id: z.string(),
|
|
135
135
|
contentKind: z.literal('post'),
|
|
136
|
+
/** Author of the post. When `isAnonymous` is true, the server ships a
|
|
137
|
+
* true-anonymous snapshot (no userId / username / avatar / display
|
|
138
|
+
* name) so the wire is the privacy boundary — there is no way to open
|
|
139
|
+
* the author's profile or contact them. */
|
|
136
140
|
author: postAuthorSnapshotSchema,
|
|
141
|
+
/** Author opted to post anonymously. */
|
|
142
|
+
isAnonymous: z.boolean().optional(),
|
|
137
143
|
// Body — for a repost, this carries the caption (may be empty).
|
|
138
144
|
text: z.string().max(POST_MAX_BODY_CHARS),
|
|
139
145
|
images: z.array(postImageSchema).max(POST_MAX_IMAGES),
|
|
@@ -210,6 +216,10 @@ export const createPostBodySchema = z.object({
|
|
|
210
216
|
/** When set, the post is scoped to a topical community. The author
|
|
211
217
|
* must be an active member; non-members get a 403. */
|
|
212
218
|
communityId: z.string().optional(),
|
|
219
|
+
/** When true, the author's identity is hidden on the public wire
|
|
220
|
+
* (true-anonymous author snapshot). Server-enforced: only an
|
|
221
|
+
* identity-verified user may set this. */
|
|
222
|
+
isAnonymous: z.boolean().default(false),
|
|
213
223
|
});
|
|
214
224
|
/**
|
|
215
225
|
* What the client sends to `PATCH /api/v1/community/posts/:id`. Only
|
package/dist/post.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post.js","sourceRoot":"","sources":["../src/post.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Community post wire shapes.\n *\n * A \"post\" is the native community content kind (text-primary + media).\n * Lost-and-found and suggestions-complaints items show up in the same feed via\n * polymorphic union; their shapes live in their own schema packages\n * and are projected into a `FeedItem` envelope (see `./feed.ts`).\n *\n * @module community-schema/post\n */\n\nimport { z } from 'zod';\nimport { areaLineageSnapshotSchema } from './area.js';\nimport { communityDesignationSchema } from './community.js';\nimport { POST_MAX_BODY_CHARS, POST_MAX_IMAGES, POST_MAX_VIDEOS } from './constants.js';\nimport { voteValueSchema } from './engagement.js';\nimport { contentKindSchema } from './enums.js';\nimport { linkPreviewSchema } from './link-preview.js';\nimport { postImageSchema, postVideoSchema } from './media.js';\nimport { usernameSchema } from './username.js';\n\n// ─── Author snapshot (denormalised on every post) ──────────────────────────\n\n/**\n * The author's display info attached to every feed row.\n *\n * Two classes of fields here:\n *\n * - **Live (refreshed at read time)**: `username`, `displayName`,\n * `avatarUrl`. The projector batch-joins these against the User\n * collection per page, so a name or avatar change instantly\n * propagates to old posts and the byline link always targets the\n * CURRENT username (no stale `/oldhandle` 404 after a rename).\n *\n * - **Snapshotted at write time**: `localName`. This is \"where the\n * author was when posting\" — a deliberate snapshot, not a stale\n * read of their current location.\n *\n * @module community-schema/post (postAuthorSnapshotSchema)\n */\nexport const postAuthorSnapshotSchema = z.object({\n userId: z.string(),\n /** Public URL handle. Live-joined from User at read time so the\n * byline links to `/<currentUsername>` even after a rename. */\n username: usernameSchema,\n displayName: z.string(),\n avatarUrl: z.string().url().optional(),\n /**\n * The author's \"local\" area label at post time, e.g. \"Rohini Sector 15\".\n * Powers the byline \"<DisplayName> · <localName> · <relativeTime>\".\n * Snapshotted at write time — does NOT track the author's current\n * location.\n */\n localName: z.string().optional(),\n /**\n * The author's community-scoped representation label, used to\n * render \"Hari Joshi (Chairman)\" suffixes in feed-card bylines\n * inside a community. **Live-joined** from `CommunityMembership`\n * at read time (per the community + author pair), the same way\n * `username` and `avatarUrl` are live-resolved — so a designation\n * change propagates to old posts immediately.\n *\n * Populated only when the post lives inside a community\n * (`communityId` set) AND the author has a designation set in\n * that community. `null` otherwise. Global / cross-community feed\n * surfaces (the feed tab, the profile page) leave this null even\n * if the post is community-scoped — the byline there doesn't have\n * a \"current community\" context to attach a designation to.\n */\n designation: communityDesignationSchema.nullable().optional(),\n /**\n * Whether the author's real-world identity has been verified by a\n * Jansathi volunteer / leader (UDP step 9 + the verifiedFields\n * gate on the user-detailed-profile model). **Live-joined** from\n * `User.identityVerified.verified` at read time, the same way\n * `username` / `avatarUrl` / `designation` are — so a verification\n * change propagates instantly to every old post's byline.\n *\n * Drives the green check-badge next to the display name on every\n * feed card, plus client-side gating of leader-only actions like\n * starting a community.\n *\n * Optional so backends that haven't started populating the field\n * yet still serialize a valid wire shape — readers should treat\n * `undefined` as \"not verified\".\n */\n identityVerified: z.boolean().optional(),\n});\nexport type PostAuthorSnapshot = z.infer<typeof postAuthorSnapshotSchema>;\n\n// ─── Reposted source snapshot ──────────────────────────────────────────────\n\n/**\n * Compact embed shape used when a CommunityPost is a repost (caption +\n * optional quote of another item). Modelled after Twitter's quote-tweet\n * embed: byline + truncated text/title + first image + kind badge + the\n * moment-of-creation timestamp.\n *\n * Engagement counts are intentionally NOT included — they'd drift\n * relative to the live source. The card embeds this as a static teaser\n * that links to the source for the live numbers.\n *\n * Backend snapshots these fields at repost-create time from the source\n * row (post text / item title / first photo / status badge), so the\n * embed survives source edits / soft-deletes / hides — the user's\n * reaction at repost time is preserved.\n */\nexport const repostedSourceSchema = z.object({\n /** Which kind was reposted. Lets the embed render the right icon. */\n contentKind: contentKindSchema,\n /** Source row's id — tap-embed navigates to its thread / detail page. */\n _id: z.string(),\n /** Author snapshot of the source at repost time. */\n author: postAuthorSnapshotSchema,\n /** Primary text. For posts: the body text (truncated). For lost_found /\n * suggestions_complaints / donate_item: the title. */\n title: z.string(),\n /** Optional secondary line. For posts: unused. For legacy kinds: the\n * description (truncated server-side). */\n body: z.string().optional(),\n /** First image if any. */\n imageUrl: z.string().url().optional(),\n /** Small kind-specific badge:\n * - posts → undefined\n * - lost_found → 'lost' | 'found'\n * - suggestions_complaints → 'complaint' | 'suggestion'\n * - donate_item → 'PENDING' | 'VERIFIED' | 'AT_CENTER' | 'DONATED'\n */\n kindBadge: z.string().optional(),\n createdAt: z.string().datetime(),\n});\nexport type RepostedSource = z.infer<typeof repostedSourceSchema>;\n\n// ─── Wire shape — what the server returns ──────────────────────────────────\n\n/**\n * A community post as it appears in the feed and on the single-post\n * page. Engagement counts are denormalised onto the document for\n * fast feed sorting; the per-user reaction state (\"did I upvote this?\")\n * is computed at read time and returned in the `viewer` block.\n */\nexport const communityPostWireSchema = z.object({\n _id: z.string(),\n contentKind: z.literal('post'),\n author: postAuthorSnapshotSchema,\n\n // Body — for a repost, this carries the caption (may be empty).\n text: z.string().max(POST_MAX_BODY_CHARS),\n images: z.array(postImageSchema).max(POST_MAX_IMAGES),\n videos: z.array(postVideoSchema).max(POST_MAX_VIDEOS),\n\n /** Auto-extracted embed for the first eligible URL in `text`. Server-\n * derived at write/edit time (the client never sets this directly).\n * Renderers consume the persisted blob and never re-fetch. Absent on\n * posts whose body has no embeddable URL. */\n linkPreview: linkPreviewSchema.optional(),\n\n // Authorship area\n /** Snapshot of where the author was at write time. Drives the\n * area-proximity cascade and ranking in feed.service. */\n areaLineage: areaLineageSnapshotSchema,\n /** When set, the post is scoped to a topical community and is NOT\n * surfaced via the geo cascade. The community feed adds an explicit\n * `communityId` filter to its read queries. Posts authored from the\n * global composer leave this null. */\n communityId: z.string().optional(),\n\n // Denormalised engagement counters (drive feed sort + card render)\n /** Net score = upvotes - downvotes. SIGNED — can be negative when\n * downvotes exceed upvotes. The EngagementBar renders this as the\n * centre number between the up / down arrows. */\n score: z.number().int(),\n commentCount: z.number().int().nonnegative(),\n /** How many times this post has been reposted. Incremented by the\n * repost endpoint; reposts of reposts are blocked server-side. */\n repostCount: z.number().int().nonnegative(),\n /** Map of `ReactionType` → count, e.g. { like: 4, love: 1 }. */\n reactionCounts: z.record(z.string(), z.number().int().nonnegative()),\n\n /** Source-snapshot when this post is a repost. Absent on regular posts.\n * The wrapper post owns its own engagement counters; this embed is\n * a static teaser of the original at repost time. */\n repostOf: repostedSourceSchema.optional(),\n\n // Lifecycle\n createdAt: z.string().datetime(),\n /** Last edit timestamp, or null if never edited. Edit window is\n * enforced by the server at write time, not by this schema. */\n editedAt: z.string().datetime().nullable(),\n deletedAt: z.string().datetime().nullable(),\n\n // Per-viewer state (filled by the server at read time)\n viewer: z\n .object({\n /** The viewer's vote on this target. `'up'` and `'down'`\n * each contribute ±1 to `score`; `null` means no vote. */\n vote: voteValueSchema.nullable(),\n /** The reaction the viewer chose, or null. Reaction is an\n * independent axis from `vote` — a viewer can both react and\n * vote on the same item. */\n reaction: z.string().nullable(),\n /** True when the viewer saved this post to their bookmarks.\n * Bookmarks are private and don't affect any aggregate count\n * exposed to other viewers. */\n bookmarked: z.boolean(),\n /** True when the viewer authored the post (controls edit / delete buttons). */\n isAuthor: z.boolean(),\n /** True when the viewer has already reposted this source. The\n * client uses this to render the repost button as \"already\n * reposted\" rather than as a fresh CTA. */\n reposted: z.boolean(),\n })\n .optional(),\n});\nexport type CommunityPostWire = z.infer<typeof communityPostWireSchema>;\n\n// ─── Create / update bodies ────────────────────────────────────────────────\n\n/**\n * What the client sends to `POST /api/v1/community/posts`. The author\n * is taken from the authenticated request; the area lineage is resolved\n * server-side from the user's `currentLocation` (the client cannot\n * spoof a different area).\n */\nexport const createPostBodySchema = z.object({\n text: z.string().min(1).max(POST_MAX_BODY_CHARS),\n images: z.array(postImageSchema).max(POST_MAX_IMAGES).optional(),\n videos: z.array(postVideoSchema).max(POST_MAX_VIDEOS).optional(),\n /** When set, the post is scoped to a topical community. The author\n * must be an active member; non-members get a 403. */\n communityId: z.string().optional(),\n});\nexport type CreatePostBody = z.infer<typeof createPostBodySchema>;\n\n/**\n * What the client sends to `PATCH /api/v1/community/posts/:id`. Only\n * the text body is editable — media additions / removals require a new\n * post (matches Twitter / FB convention).\n */\nexport const updatePostBodySchema = z.object({\n text: z.string().min(1).max(POST_MAX_BODY_CHARS),\n});\nexport type UpdatePostBody = z.infer<typeof updatePostBodySchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"post.js","sourceRoot":"","sources":["../src/post.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Community post wire shapes.\n *\n * A \"post\" is the native community content kind (text-primary + media).\n * Lost-and-found and suggestions-complaints items show up in the same feed via\n * polymorphic union; their shapes live in their own schema packages\n * and are projected into a `FeedItem` envelope (see `./feed.ts`).\n *\n * @module community-schema/post\n */\n\nimport { z } from 'zod';\nimport { areaLineageSnapshotSchema } from './area.js';\nimport { communityDesignationSchema } from './community.js';\nimport { POST_MAX_BODY_CHARS, POST_MAX_IMAGES, POST_MAX_VIDEOS } from './constants.js';\nimport { voteValueSchema } from './engagement.js';\nimport { contentKindSchema } from './enums.js';\nimport { linkPreviewSchema } from './link-preview.js';\nimport { postImageSchema, postVideoSchema } from './media.js';\nimport { usernameSchema } from './username.js';\n\n// ─── Author snapshot (denormalised on every post) ──────────────────────────\n\n/**\n * The author's display info attached to every feed row.\n *\n * Two classes of fields here:\n *\n * - **Live (refreshed at read time)**: `username`, `displayName`,\n * `avatarUrl`. The projector batch-joins these against the User\n * collection per page, so a name or avatar change instantly\n * propagates to old posts and the byline link always targets the\n * CURRENT username (no stale `/oldhandle` 404 after a rename).\n *\n * - **Snapshotted at write time**: `localName`. This is \"where the\n * author was when posting\" — a deliberate snapshot, not a stale\n * read of their current location.\n *\n * @module community-schema/post (postAuthorSnapshotSchema)\n */\nexport const postAuthorSnapshotSchema = z.object({\n userId: z.string(),\n /** Public URL handle. Live-joined from User at read time so the\n * byline links to `/<currentUsername>` even after a rename. */\n username: usernameSchema,\n displayName: z.string(),\n avatarUrl: z.string().url().optional(),\n /**\n * The author's \"local\" area label at post time, e.g. \"Rohini Sector 15\".\n * Powers the byline \"<DisplayName> · <localName> · <relativeTime>\".\n * Snapshotted at write time — does NOT track the author's current\n * location.\n */\n localName: z.string().optional(),\n /**\n * The author's community-scoped representation label, used to\n * render \"Hari Joshi (Chairman)\" suffixes in feed-card bylines\n * inside a community. **Live-joined** from `CommunityMembership`\n * at read time (per the community + author pair), the same way\n * `username` and `avatarUrl` are live-resolved — so a designation\n * change propagates to old posts immediately.\n *\n * Populated only when the post lives inside a community\n * (`communityId` set) AND the author has a designation set in\n * that community. `null` otherwise. Global / cross-community feed\n * surfaces (the feed tab, the profile page) leave this null even\n * if the post is community-scoped — the byline there doesn't have\n * a \"current community\" context to attach a designation to.\n */\n designation: communityDesignationSchema.nullable().optional(),\n /**\n * Whether the author's real-world identity has been verified by a\n * Jansathi volunteer / leader (UDP step 9 + the verifiedFields\n * gate on the user-detailed-profile model). **Live-joined** from\n * `User.identityVerified.verified` at read time, the same way\n * `username` / `avatarUrl` / `designation` are — so a verification\n * change propagates instantly to every old post's byline.\n *\n * Drives the green check-badge next to the display name on every\n * feed card, plus client-side gating of leader-only actions like\n * starting a community.\n *\n * Optional so backends that haven't started populating the field\n * yet still serialize a valid wire shape — readers should treat\n * `undefined` as \"not verified\".\n */\n identityVerified: z.boolean().optional(),\n});\nexport type PostAuthorSnapshot = z.infer<typeof postAuthorSnapshotSchema>;\n\n// ─── Reposted source snapshot ──────────────────────────────────────────────\n\n/**\n * Compact embed shape used when a CommunityPost is a repost (caption +\n * optional quote of another item). Modelled after Twitter's quote-tweet\n * embed: byline + truncated text/title + first image + kind badge + the\n * moment-of-creation timestamp.\n *\n * Engagement counts are intentionally NOT included — they'd drift\n * relative to the live source. The card embeds this as a static teaser\n * that links to the source for the live numbers.\n *\n * Backend snapshots these fields at repost-create time from the source\n * row (post text / item title / first photo / status badge), so the\n * embed survives source edits / soft-deletes / hides — the user's\n * reaction at repost time is preserved.\n */\nexport const repostedSourceSchema = z.object({\n /** Which kind was reposted. Lets the embed render the right icon. */\n contentKind: contentKindSchema,\n /** Source row's id — tap-embed navigates to its thread / detail page. */\n _id: z.string(),\n /** Author snapshot of the source at repost time. */\n author: postAuthorSnapshotSchema,\n /** Primary text. For posts: the body text (truncated). For lost_found /\n * suggestions_complaints / donate_item: the title. */\n title: z.string(),\n /** Optional secondary line. For posts: unused. For legacy kinds: the\n * description (truncated server-side). */\n body: z.string().optional(),\n /** First image if any. */\n imageUrl: z.string().url().optional(),\n /** Small kind-specific badge:\n * - posts → undefined\n * - lost_found → 'lost' | 'found'\n * - suggestions_complaints → 'complaint' | 'suggestion'\n * - donate_item → 'PENDING' | 'VERIFIED' | 'AT_CENTER' | 'DONATED'\n */\n kindBadge: z.string().optional(),\n createdAt: z.string().datetime(),\n});\nexport type RepostedSource = z.infer<typeof repostedSourceSchema>;\n\n// ─── Wire shape — what the server returns ──────────────────────────────────\n\n/**\n * A community post as it appears in the feed and on the single-post\n * page. Engagement counts are denormalised onto the document for\n * fast feed sorting; the per-user reaction state (\"did I upvote this?\")\n * is computed at read time and returned in the `viewer` block.\n */\nexport const communityPostWireSchema = z.object({\n _id: z.string(),\n contentKind: z.literal('post'),\n /** Author of the post. When `isAnonymous` is true, the server ships a\n * true-anonymous snapshot (no userId / username / avatar / display\n * name) so the wire is the privacy boundary — there is no way to open\n * the author's profile or contact them. */\n author: postAuthorSnapshotSchema,\n /** Author opted to post anonymously. */\n isAnonymous: z.boolean().optional(),\n\n // Body — for a repost, this carries the caption (may be empty).\n text: z.string().max(POST_MAX_BODY_CHARS),\n images: z.array(postImageSchema).max(POST_MAX_IMAGES),\n videos: z.array(postVideoSchema).max(POST_MAX_VIDEOS),\n\n /** Auto-extracted embed for the first eligible URL in `text`. Server-\n * derived at write/edit time (the client never sets this directly).\n * Renderers consume the persisted blob and never re-fetch. Absent on\n * posts whose body has no embeddable URL. */\n linkPreview: linkPreviewSchema.optional(),\n\n // Authorship area\n /** Snapshot of where the author was at write time. Drives the\n * area-proximity cascade and ranking in feed.service. */\n areaLineage: areaLineageSnapshotSchema,\n /** When set, the post is scoped to a topical community and is NOT\n * surfaced via the geo cascade. The community feed adds an explicit\n * `communityId` filter to its read queries. Posts authored from the\n * global composer leave this null. */\n communityId: z.string().optional(),\n\n // Denormalised engagement counters (drive feed sort + card render)\n /** Net score = upvotes - downvotes. SIGNED — can be negative when\n * downvotes exceed upvotes. The EngagementBar renders this as the\n * centre number between the up / down arrows. */\n score: z.number().int(),\n commentCount: z.number().int().nonnegative(),\n /** How many times this post has been reposted. Incremented by the\n * repost endpoint; reposts of reposts are blocked server-side. */\n repostCount: z.number().int().nonnegative(),\n /** Map of `ReactionType` → count, e.g. { like: 4, love: 1 }. */\n reactionCounts: z.record(z.string(), z.number().int().nonnegative()),\n\n /** Source-snapshot when this post is a repost. Absent on regular posts.\n * The wrapper post owns its own engagement counters; this embed is\n * a static teaser of the original at repost time. */\n repostOf: repostedSourceSchema.optional(),\n\n // Lifecycle\n createdAt: z.string().datetime(),\n /** Last edit timestamp, or null if never edited. Edit window is\n * enforced by the server at write time, not by this schema. */\n editedAt: z.string().datetime().nullable(),\n deletedAt: z.string().datetime().nullable(),\n\n // Per-viewer state (filled by the server at read time)\n viewer: z\n .object({\n /** The viewer's vote on this target. `'up'` and `'down'`\n * each contribute ±1 to `score`; `null` means no vote. */\n vote: voteValueSchema.nullable(),\n /** The reaction the viewer chose, or null. Reaction is an\n * independent axis from `vote` — a viewer can both react and\n * vote on the same item. */\n reaction: z.string().nullable(),\n /** True when the viewer saved this post to their bookmarks.\n * Bookmarks are private and don't affect any aggregate count\n * exposed to other viewers. */\n bookmarked: z.boolean(),\n /** True when the viewer authored the post (controls edit / delete buttons). */\n isAuthor: z.boolean(),\n /** True when the viewer has already reposted this source. The\n * client uses this to render the repost button as \"already\n * reposted\" rather than as a fresh CTA. */\n reposted: z.boolean(),\n })\n .optional(),\n});\nexport type CommunityPostWire = z.infer<typeof communityPostWireSchema>;\n\n// ─── Create / update bodies ────────────────────────────────────────────────\n\n/**\n * What the client sends to `POST /api/v1/community/posts`. The author\n * is taken from the authenticated request; the area lineage is resolved\n * server-side from the user's `currentLocation` (the client cannot\n * spoof a different area).\n */\nexport const createPostBodySchema = z.object({\n text: z.string().min(1).max(POST_MAX_BODY_CHARS),\n images: z.array(postImageSchema).max(POST_MAX_IMAGES).optional(),\n videos: z.array(postVideoSchema).max(POST_MAX_VIDEOS).optional(),\n /** When set, the post is scoped to a topical community. The author\n * must be an active member; non-members get a 403. */\n communityId: z.string().optional(),\n /** When true, the author's identity is hidden on the public wire\n * (true-anonymous author snapshot). Server-enforced: only an\n * identity-verified user may set this. */\n isAnonymous: z.boolean().default(false),\n});\nexport type CreatePostBody = z.infer<typeof createPostBodySchema>;\n\n/**\n * What the client sends to `PATCH /api/v1/community/posts/:id`. Only\n * the text body is editable — media additions / removals require a new\n * post (matches Twitter / FB convention).\n */\nexport const updatePostBodySchema = z.object({\n text: z.string().min(1).max(POST_MAX_BODY_CHARS),\n});\nexport type UpdatePostBody = z.infer<typeof updatePostBodySchema>;\n"]}
|
package/dist/report.d.ts
CHANGED
|
@@ -105,6 +105,7 @@ export declare const reportRecordWireSchema: z.ZodObject<{
|
|
|
105
105
|
suggestions_complaints: "suggestions_complaints";
|
|
106
106
|
donate_item: "donate_item";
|
|
107
107
|
poll: "poll";
|
|
108
|
+
crowdfunding: "crowdfunding";
|
|
108
109
|
user: "user";
|
|
109
110
|
comment: "comment";
|
|
110
111
|
}>;
|
|
@@ -147,6 +148,7 @@ export declare const reportSubmissionResponseSchema: z.ZodObject<{
|
|
|
147
148
|
suggestions_complaints: "suggestions_complaints";
|
|
148
149
|
donate_item: "donate_item";
|
|
149
150
|
poll: "poll";
|
|
151
|
+
crowdfunding: "crowdfunding";
|
|
150
152
|
user: "user";
|
|
151
153
|
comment: "comment";
|
|
152
154
|
}>;
|
package/dist/report.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;EAQlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAIxE;;;GAGG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;iBASlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;iBAI/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;EAQlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAIxE;;;GAGG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;iBASlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;iBAI/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkBjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;;;;GAMG;AACH,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAGzC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC"}
|
package/dist/report.js
CHANGED
|
@@ -75,7 +75,16 @@ export const reportUserBodySchema = z.object({
|
|
|
75
75
|
*/
|
|
76
76
|
export const reportRecordWireSchema = z.object({
|
|
77
77
|
_id: z.string(),
|
|
78
|
-
targetKind: z.enum([
|
|
78
|
+
targetKind: z.enum([
|
|
79
|
+
'post',
|
|
80
|
+
'lost_found',
|
|
81
|
+
'suggestions_complaints',
|
|
82
|
+
'donate_item',
|
|
83
|
+
'poll',
|
|
84
|
+
'crowdfunding',
|
|
85
|
+
'comment',
|
|
86
|
+
'user',
|
|
87
|
+
]),
|
|
79
88
|
contentId: z.string(),
|
|
80
89
|
targetUserId: z.string().optional(),
|
|
81
90
|
reason: reportReasonSchema,
|
package/dist/report.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEpE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,IAAI,CAAC;IAC5C,MAAM;IACN,YAAY;IACZ,wBAAwB;IACxB,aAAa;IACb,MAAM;IACN,cAAc;IACd,SAAS;CACV,CAAC,CAAC;AAGH,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C;uDACmD;IACnD,WAAW,EAAE,uBAAuB;IACpC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,MAAM,EAAE,kBAAkB;IAC1B;2DACuD;IACvD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;CACzC,CAAC,CAAC;AAGH;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IACxB,MAAM,EAAE,kBAAkB;IAC1B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;CACzC,CAAC,CAAC;AAGH,8EAA8E;AAE9E;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEpE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,IAAI,CAAC;IAC5C,MAAM;IACN,YAAY;IACZ,wBAAwB;IACxB,aAAa;IACb,MAAM;IACN,cAAc;IACd,SAAS;CACV,CAAC,CAAC;AAGH,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C;uDACmD;IACnD,WAAW,EAAE,uBAAuB;IACpC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,MAAM,EAAE,kBAAkB;IAC1B;2DACuD;IACvD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;CACzC,CAAC,CAAC;AAGH;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IACxB,MAAM,EAAE,kBAAkB;IAC1B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;CACzC,CAAC,CAAC;AAGH,8EAA8E;AAE9E;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC;QACjB,MAAM;QACN,YAAY;QACZ,wBAAwB;QACxB,aAAa;QACb,MAAM;QACN,cAAc;QACd,SAAS;QACT,MAAM;KACP,CAAC;IACF,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,MAAM,EAAE,kBAAkB;IAC1B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,MAAM,EAAE,kBAAkB;IAC1B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACjC,CAAC,CAAC;AAGH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,CAAC,MAAM,CAAC;IACrD,MAAM,EAAE,sBAAsB;IAC9B,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;CACrB,CAAC,CAAC","sourcesContent":["/**\n * Content + user reporting wire shapes for moderation.\n *\n * Any signed-in viewer can report:\n * - feed content (post / lost-found / suggestions-complaints)\n * - comments (one-level thread under a feed item)\n * - users (the profile + everything they author)\n *\n * Reports land in a moderation queue surfaced to admins; admin actions\n * are out of scope for this schema package.\n *\n * Endpoints:\n * POST /reports file a content report\n * POST /reports/user file a user report\n *\n * Both return a `ReportSubmissionResponse` carrying the resulting\n * record + a `created` flag so the UI can distinguish first-submission\n * from idempotent repeat (and toast accordingly).\n *\n * @module community-schema/report\n */\n\nimport { z } from 'zod';\nimport { reportReasonSchema, reportStatusSchema } from './enums.js';\n\n/**\n * The kinds of community CONTENT that can be reported. Wider than\n * `contentKindSchema` (which covers only feed items) because comments\n * are also reportable. Lives here — not in `enums.ts` — to keep the\n * feed/post schemas free of the comment value.\n *\n * Stays in lockstep with `contentKindSchema` + `'comment'`. When a\n * new content kind ships (e.g. polls), it must be added here too —\n * otherwise the report endpoint 400s on the new kind.\n */\nexport const reportContentKindSchema = z.enum([\n 'post',\n 'lost_found',\n 'suggestions_complaints',\n 'donate_item',\n 'poll',\n 'crowdfunding',\n 'comment',\n]);\nexport type ReportContentKind = z.infer<typeof reportContentKindSchema>;\n\n// ─── Body shapes ───────────────────────────────────────────────────────────\n\n/**\n * Body for `POST /api/v1/community/reports`. Reports a piece of\n * content (post / lost-found / suggestions-complaints / comment).\n */\nexport const reportContentBodySchema = z.object({\n /** Still named `contentKind` for backward compat with 0.1.x callers;\n * semantically it now also includes 'comment'. */\n contentKind: reportContentKindSchema,\n contentId: z.string(),\n reason: reportReasonSchema,\n /** Free-text detail. Required when `reason === 'other'`, optional\n * otherwise (the server enforces the conditional). */\n details: z.string().max(2000).optional(),\n});\nexport type ReportContentBody = z.infer<typeof reportContentBodySchema>;\n\n/**\n * Body for `POST /api/v1/community/reports/user`. Reports a user's\n * profile / general behaviour rather than a specific piece of content.\n */\nexport const reportUserBodySchema = z.object({\n targetUserId: z.string(),\n reason: reportReasonSchema,\n details: z.string().max(2000).optional(),\n});\nexport type ReportUserBody = z.infer<typeof reportUserBodySchema>;\n\n// ─── Record + response shapes ──────────────────────────────────────────────\n\n/**\n * Mongo-side record of a single report, projected for wire.\n *\n * For content reports `targetKind` is one of the content kinds and\n * `contentId` holds the target; `targetUserId` is undefined. For user\n * reports `targetKind === 'user'`, `targetUserId` is set, and\n * `contentId` is an empty string (no content target).\n */\nexport const reportRecordWireSchema = z.object({\n _id: z.string(),\n targetKind: z.enum([\n 'post',\n 'lost_found',\n 'suggestions_complaints',\n 'donate_item',\n 'poll',\n 'crowdfunding',\n 'comment',\n 'user',\n ]),\n contentId: z.string(),\n targetUserId: z.string().optional(),\n reason: reportReasonSchema,\n details: z.string().nullable(),\n status: reportStatusSchema,\n createdAt: z.string().datetime(),\n});\nexport type ReportRecordWire = z.infer<typeof reportRecordWireSchema>;\n\n/**\n * Common response envelope for both `POST /reports` and\n * `POST /reports/user`. `created` is `true` on first submission and\n * `false` when the backend's idempotency check returned an existing\n * row — the client uses this to distinguish \"thanks for reporting\"\n * from \"you've already reported this\".\n */\nexport const reportSubmissionResponseSchema = z.object({\n record: reportRecordWireSchema,\n created: z.boolean(),\n});\nexport type ReportSubmissionResponse = z.infer<typeof reportSubmissionResponseSchema>;\n"]}
|
package/dist/repost.d.ts
CHANGED
|
@@ -74,6 +74,7 @@ export declare const createRepostResponseSchema: z.ZodObject<{
|
|
|
74
74
|
}, z.core.$strip>>>;
|
|
75
75
|
identityVerified: z.ZodOptional<z.ZodBoolean>;
|
|
76
76
|
}, z.core.$strip>;
|
|
77
|
+
isAnonymous: z.ZodOptional<z.ZodBoolean>;
|
|
77
78
|
text: z.ZodString;
|
|
78
79
|
images: z.ZodArray<z.ZodObject<{
|
|
79
80
|
url: z.ZodString;
|
package/dist/repost.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repost.d.ts","sourceRoot":"","sources":["../src/repost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;iBASjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;;GAIG;AACH,eAAO,MAAM,0BAA0B
|
|
1
|
+
{"version":3,"file":"repost.d.ts","sourceRoot":"","sources":["../src/repost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;iBASjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;;GAIG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAErC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC"}
|
|
@@ -191,6 +191,7 @@ export declare const suggestionsComplaintsBodyObjectSchema: z.ZodObject<{
|
|
|
191
191
|
url: z.ZodString;
|
|
192
192
|
gcpPath: z.ZodString;
|
|
193
193
|
}, z.core.$strip>>;
|
|
194
|
+
isAnonymous: z.ZodDefault<z.ZodBoolean>;
|
|
194
195
|
}, z.core.$strip>;
|
|
195
196
|
export declare const createSuggestionsComplaintsBodySchema: z.ZodObject<{
|
|
196
197
|
location: z.ZodObject<{
|
|
@@ -302,6 +303,7 @@ export declare const createSuggestionsComplaintsBodySchema: z.ZodObject<{
|
|
|
302
303
|
url: z.ZodString;
|
|
303
304
|
gcpPath: z.ZodString;
|
|
304
305
|
}, z.core.$strip>>;
|
|
306
|
+
isAnonymous: z.ZodDefault<z.ZodBoolean>;
|
|
305
307
|
}, z.core.$strip>;
|
|
306
308
|
export type CreateSuggestionsComplaintsBody = z.infer<typeof suggestionsComplaintsBodyObjectSchema>;
|
|
307
309
|
/** A partially-filled draft as the multi-step form builds it up. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suggestions-complaints.d.ts","sourceRoot":"","sources":["../src/suggestions-complaints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,eAAO,MAAM,kCAAkC,sCAAuC,CAAC;AACvF,eAAO,MAAM,+BAA+B;;;EAA6C,CAAC;AAC1F,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAGxF,eAAO,MAAM,yBAAyB,ubAyB5B,CAAC;AACX,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;EAAoC,CAAC;AACzE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,eAAO,MAAM,0BAA0B,2NAY7B,CAAC;AACX,eAAO,MAAM,wBAAwB;;;;;;;;;;;;EAAqC,CAAC;AAC3E,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,oEAAoE;AACpE,eAAO,MAAM,sCAAsC,uoBAGzC,CAAC;AAGX,eAAO,MAAM,sCAAsC,KAAK,CAAC;AACzD,eAAO,MAAM,sCAAsC,MAAM,CAAC;AAC1D,eAAO,MAAM,4CAA4C,OAAO,CAAC;AACjE,0EAA0E;AAC1E,eAAO,MAAM,uDAAuD,KAAK,CAAC;AAC1E,eAAO,MAAM,iCAAiC,IAAI,CAAC;AACnD,eAAO,MAAM,sCAAsC,IAAI,CAAC;AASxD;0DAC0D;AAC1D,eAAO,MAAM,uBAAuB;;;iBAGlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAIxE,eAAO,MAAM,qCAAqC
|
|
1
|
+
{"version":3,"file":"suggestions-complaints.d.ts","sourceRoot":"","sources":["../src/suggestions-complaints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,eAAO,MAAM,kCAAkC,sCAAuC,CAAC;AACvF,eAAO,MAAM,+BAA+B;;;EAA6C,CAAC;AAC1F,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAGxF,eAAO,MAAM,yBAAyB,ubAyB5B,CAAC;AACX,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;EAAoC,CAAC;AACzE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,eAAO,MAAM,0BAA0B,2NAY7B,CAAC;AACX,eAAO,MAAM,wBAAwB;;;;;;;;;;;;EAAqC,CAAC;AAC3E,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,oEAAoE;AACpE,eAAO,MAAM,sCAAsC,uoBAGzC,CAAC;AAGX,eAAO,MAAM,sCAAsC,KAAK,CAAC;AACzD,eAAO,MAAM,sCAAsC,MAAM,CAAC;AAC1D,eAAO,MAAM,4CAA4C,OAAO,CAAC;AACjE,0EAA0E;AAC1E,eAAO,MAAM,uDAAuD,KAAK,CAAC;AAC1E,eAAO,MAAM,iCAAiC,IAAI,CAAC;AACnD,eAAO,MAAM,sCAAsC,IAAI,CAAC;AASxD;0DAC0D;AAC1D,eAAO,MAAM,uBAAuB;;;iBAGlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAIxE,eAAO,MAAM,qCAAqC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuBhD,CAAC;AAKH,eAAO,MAAM,qCAAqC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBASjD,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qCAAqC,CAAC,CAAC;AAEpG,oEAAoE;AACpE,MAAM,MAAM,0BAA0B,GAAG,OAAO,CAAC,+BAA+B,CAAC,CAAC;AAKlF,eAAO,MAAM,8BAA8B;uBAC/B,0BAA0B,KAAG,OAAO;2BAEhC,0BAA0B,KAAG,OAAO;wBAGvC,0BAA0B,KAAG,OAAO;8BAC9B,0BAA0B,KAAG,OAAO;IAIrD;uEACmE;2BACrD,0BAA0B,KAAG,OAAO;CAC1C,CAAC"}
|
|
@@ -111,6 +111,9 @@ export const suggestionsComplaintsBodyObjectSchema = z.object({
|
|
|
111
111
|
video: uploadedMediaPathSchema.optional(),
|
|
112
112
|
videoLinks: z.array(z.string().url()).max(SUGGESTIONS_COMPLAINTS_MAX_VIDEO_LINKS).optional(),
|
|
113
113
|
audio: uploadedMediaPathSchema.optional(),
|
|
114
|
+
/** When true, the reporter's identity is hidden on the public wire.
|
|
115
|
+
* Server-enforced: only an identity-verified user may set this. */
|
|
116
|
+
isAnonymous: z.boolean().default(false),
|
|
114
117
|
});
|
|
115
118
|
// ... and the full create schema with the cross-field rule (suggestions need
|
|
116
119
|
// a real description). Use the BASE object for `.pick()`/`.extend()`; use this
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suggestions-complaints.js","sourceRoot":"","sources":["../src/suggestions-complaints.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Suggestions & Complaints — create-flow schema (community kind).\n * ===============================================================\n * Single source of truth for the WRITE path of the suggestions/complaints\n * surface. Mirrors the\n * `crowdfunding.ts` module shape: this file owns the create body + the\n * categories + per-step completeness validators; the display/feed shapes\n * stay in `feed.ts`.\n *\n * Consumed by:\n * - reform-backend (controller validation) — replaces the duplicated\n * local `issue-report.schema.ts` categories + body schema.\n * - the product frontends (the multi-step `StepForm` config delegates\n * its `isFilled` predicates to `suggestionsComplaintsStepValid`).\n *\n * @module community-schema/suggestions-complaints\n */\n\nimport { z } from 'zod';\nimport { type SuggestionsComplaintsLocation, suggestionsComplaintsLocationSchema } from './feed.js';\n\n// ─── Location ────────────────────────────────────────────────────────────────\n// The structured area-hierarchy location lives canonically in `feed.ts`\n// (`suggestionsComplaintsLocationSchema`). It's imported above for the create\n// body below and re-exported from the package root via the feed barrel, so\n// the create flow and the display flow share one definition.\n\n// ─── Type (complaint | suggestion) ──────────────────────────────────────────\nexport const SUGGESTIONS_COMPLAINTS_TYPE_VALUES = ['complaint', 'suggestion'] as const;\nexport const suggestionsComplaintsTypeSchema = z.enum(SUGGESTIONS_COMPLAINTS_TYPE_VALUES);\nexport type SuggestionsComplaintsType = z.infer<typeof suggestionsComplaintsTypeSchema>;\n\n// ─── Categories ──────────────────────────────────────────────────────────────\nexport const COMPLAINT_CATEGORY_VALUES = [\n 'Road & Transport',\n 'Water Supply',\n 'Electricity',\n 'Drainage & Sewage',\n 'Healthcare',\n 'Education',\n 'Sanitation',\n 'Waste Management',\n 'Street Lighting',\n 'Crime & Safety',\n 'Traffic Issues',\n 'Corruption',\n 'Unemployment',\n 'Poverty & Hunger',\n 'Child Labor',\n 'Air Pollution',\n 'Water Pollution',\n 'Noise Pollution',\n 'Deforestation',\n 'Illegal Dumping',\n 'Government Services',\n 'Documentation Issues',\n 'Public Grievances',\n 'Other Issues',\n] as const;\nexport const complaintCategorySchema = z.enum(COMPLAINT_CATEGORY_VALUES);\nexport type ComplaintCategory = z.infer<typeof complaintCategorySchema>;\n\nexport const SUGGESTION_CATEGORY_VALUES = [\n 'Infrastructure',\n 'Healthcare',\n 'Education',\n 'Environment',\n 'Public Safety',\n 'Government Services',\n 'Community Development',\n 'Youth & Employment',\n 'Women & Child Welfare',\n 'Technology & Innovation',\n 'Other',\n] as const;\nexport const suggestionCategorySchema = z.enum(SUGGESTION_CATEGORY_VALUES);\nexport type SuggestionCategory = z.infer<typeof suggestionCategorySchema>;\n\n/** All categories accepted on the wire (complaint ∪ suggestion). */\nexport const SUGGESTIONS_COMPLAINTS_CATEGORY_VALUES = [\n ...COMPLAINT_CATEGORY_VALUES,\n ...SUGGESTION_CATEGORY_VALUES,\n] as const;\n\n// ─── Field constraints (exported so callers never hard-code numbers) ─────────\nexport const SUGGESTIONS_COMPLAINTS_TITLE_MIN_CHARS = 10;\nexport const SUGGESTIONS_COMPLAINTS_TITLE_MAX_CHARS = 200;\nexport const SUGGESTIONS_COMPLAINTS_DESCRIPTION_MAX_CHARS = 2000;\n/** Suggestions must explain themselves; complaints may leave it blank. */\nexport const SUGGESTIONS_COMPLAINTS_SUGGESTION_DESCRIPTION_MIN_CHARS = 20;\nexport const SUGGESTIONS_COMPLAINTS_MAX_IMAGES = 3;\nexport const SUGGESTIONS_COMPLAINTS_MAX_VIDEO_LINKS = 5;\n\nconst titleSchema = z\n .string()\n .trim()\n .min(SUGGESTIONS_COMPLAINTS_TITLE_MIN_CHARS, 'Title must be at least 10 characters')\n .max(SUGGESTIONS_COMPLAINTS_TITLE_MAX_CHARS, 'Title must not exceed 200 characters');\n\n// ─── Uploaded media (signed-URL path) ────────────────────────────────────────\n/** A file already uploaded to GCS via a signed URL. Shared across the\n * community create flows that use signed-URL uploads. */\nexport const uploadedMediaPathSchema = z.object({\n url: z.string().min(1),\n gcpPath: z.string().min(1),\n});\nexport type UploadedMediaPath = z.infer<typeof uploadedMediaPathSchema>;\n\n// ─── Create body ─────────────────────────────────────────────────────────────\n// Base object (pickable — used for per-field slice validation) ...\nexport const suggestionsComplaintsBodyObjectSchema = z.object({\n location: suggestionsComplaintsLocationSchema,\n type: suggestionsComplaintsTypeSchema.default('complaint'),\n category: z.enum(SUGGESTIONS_COMPLAINTS_CATEGORY_VALUES as unknown as [string, ...string[]], {\n message: 'Invalid category selected',\n }),\n title: titleSchema,\n description: z\n .string()\n .trim()\n .max(\n SUGGESTIONS_COMPLAINTS_DESCRIPTION_MAX_CHARS,\n 'Description must not exceed 2000 characters',\n )\n .optional()\n .or(z.literal('')),\n images: z.array(uploadedMediaPathSchema).max(SUGGESTIONS_COMPLAINTS_MAX_IMAGES).optional(),\n video: uploadedMediaPathSchema.optional(),\n videoLinks: z.array(z.string().url()).max(SUGGESTIONS_COMPLAINTS_MAX_VIDEO_LINKS).optional(),\n audio: uploadedMediaPathSchema.optional(),\n});\n\n// ... and the full create schema with the cross-field rule (suggestions need\n// a real description). Use the BASE object for `.pick()`/`.extend()`; use this\n// refined one for the final whole-body validation.\nexport const createSuggestionsComplaintsBodySchema = suggestionsComplaintsBodyObjectSchema.refine(\n (d) =>\n d.type !== 'suggestion' ||\n (typeof d.description === 'string' &&\n d.description.trim().length >= SUGGESTIONS_COMPLAINTS_SUGGESTION_DESCRIPTION_MIN_CHARS),\n {\n message: 'Please describe your suggestion in at least 20 characters',\n path: ['description'],\n },\n);\n\nexport type CreateSuggestionsComplaintsBody = z.infer<typeof suggestionsComplaintsBodyObjectSchema>;\n\n/** A partially-filled draft as the multi-step form builds it up. */\nexport type SuggestionsComplaintsDraft = Partial<CreateSuggestionsComplaintsBody>;\n\n// ─── Per-step completeness validators (the SSOT the StepForm config delegates\n// its `isFilled` predicates to — so no validation rule is duplicated in\n// the frontend). Each returns whether that step is complete. ──────────────\nexport const suggestionsComplaintsStepValid = {\n type: (d: SuggestionsComplaintsDraft): boolean =>\n suggestionsComplaintsTypeSchema.safeParse(d.type).success,\n category: (d: SuggestionsComplaintsDraft): boolean =>\n typeof d.category === 'string' &&\n (SUGGESTIONS_COMPLAINTS_CATEGORY_VALUES as readonly string[]).includes(d.category),\n title: (d: SuggestionsComplaintsDraft): boolean => titleSchema.safeParse(d.title).success,\n description: (d: SuggestionsComplaintsDraft): boolean =>\n d.type !== 'suggestion' ||\n (typeof d.description === 'string' &&\n d.description.trim().length >= SUGGESTIONS_COMPLAINTS_SUGGESTION_DESCRIPTION_MIN_CHARS),\n /** Location is \"set\" once the state is chosen (the area hierarchy is only\n * useful for the civic feed when at least the state is known). */\n location: (d: SuggestionsComplaintsDraft): boolean => !!d.location?.state,\n} as const;\n"]}
|
|
1
|
+
{"version":3,"file":"suggestions-complaints.js","sourceRoot":"","sources":["../src/suggestions-complaints.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Suggestions & Complaints — create-flow schema (community kind).\n * ===============================================================\n * Single source of truth for the WRITE path of the suggestions/complaints\n * surface. Mirrors the\n * `crowdfunding.ts` module shape: this file owns the create body + the\n * categories + per-step completeness validators; the display/feed shapes\n * stay in `feed.ts`.\n *\n * Consumed by:\n * - reform-backend (controller validation) — replaces the duplicated\n * local `issue-report.schema.ts` categories + body schema.\n * - the product frontends (the multi-step `StepForm` config delegates\n * its `isFilled` predicates to `suggestionsComplaintsStepValid`).\n *\n * @module community-schema/suggestions-complaints\n */\n\nimport { z } from 'zod';\nimport { type SuggestionsComplaintsLocation, suggestionsComplaintsLocationSchema } from './feed.js';\n\n// ─── Location ────────────────────────────────────────────────────────────────\n// The structured area-hierarchy location lives canonically in `feed.ts`\n// (`suggestionsComplaintsLocationSchema`). It's imported above for the create\n// body below and re-exported from the package root via the feed barrel, so\n// the create flow and the display flow share one definition.\n\n// ─── Type (complaint | suggestion) ──────────────────────────────────────────\nexport const SUGGESTIONS_COMPLAINTS_TYPE_VALUES = ['complaint', 'suggestion'] as const;\nexport const suggestionsComplaintsTypeSchema = z.enum(SUGGESTIONS_COMPLAINTS_TYPE_VALUES);\nexport type SuggestionsComplaintsType = z.infer<typeof suggestionsComplaintsTypeSchema>;\n\n// ─── Categories ──────────────────────────────────────────────────────────────\nexport const COMPLAINT_CATEGORY_VALUES = [\n 'Road & Transport',\n 'Water Supply',\n 'Electricity',\n 'Drainage & Sewage',\n 'Healthcare',\n 'Education',\n 'Sanitation',\n 'Waste Management',\n 'Street Lighting',\n 'Crime & Safety',\n 'Traffic Issues',\n 'Corruption',\n 'Unemployment',\n 'Poverty & Hunger',\n 'Child Labor',\n 'Air Pollution',\n 'Water Pollution',\n 'Noise Pollution',\n 'Deforestation',\n 'Illegal Dumping',\n 'Government Services',\n 'Documentation Issues',\n 'Public Grievances',\n 'Other Issues',\n] as const;\nexport const complaintCategorySchema = z.enum(COMPLAINT_CATEGORY_VALUES);\nexport type ComplaintCategory = z.infer<typeof complaintCategorySchema>;\n\nexport const SUGGESTION_CATEGORY_VALUES = [\n 'Infrastructure',\n 'Healthcare',\n 'Education',\n 'Environment',\n 'Public Safety',\n 'Government Services',\n 'Community Development',\n 'Youth & Employment',\n 'Women & Child Welfare',\n 'Technology & Innovation',\n 'Other',\n] as const;\nexport const suggestionCategorySchema = z.enum(SUGGESTION_CATEGORY_VALUES);\nexport type SuggestionCategory = z.infer<typeof suggestionCategorySchema>;\n\n/** All categories accepted on the wire (complaint ∪ suggestion). */\nexport const SUGGESTIONS_COMPLAINTS_CATEGORY_VALUES = [\n ...COMPLAINT_CATEGORY_VALUES,\n ...SUGGESTION_CATEGORY_VALUES,\n] as const;\n\n// ─── Field constraints (exported so callers never hard-code numbers) ─────────\nexport const SUGGESTIONS_COMPLAINTS_TITLE_MIN_CHARS = 10;\nexport const SUGGESTIONS_COMPLAINTS_TITLE_MAX_CHARS = 200;\nexport const SUGGESTIONS_COMPLAINTS_DESCRIPTION_MAX_CHARS = 2000;\n/** Suggestions must explain themselves; complaints may leave it blank. */\nexport const SUGGESTIONS_COMPLAINTS_SUGGESTION_DESCRIPTION_MIN_CHARS = 20;\nexport const SUGGESTIONS_COMPLAINTS_MAX_IMAGES = 3;\nexport const SUGGESTIONS_COMPLAINTS_MAX_VIDEO_LINKS = 5;\n\nconst titleSchema = z\n .string()\n .trim()\n .min(SUGGESTIONS_COMPLAINTS_TITLE_MIN_CHARS, 'Title must be at least 10 characters')\n .max(SUGGESTIONS_COMPLAINTS_TITLE_MAX_CHARS, 'Title must not exceed 200 characters');\n\n// ─── Uploaded media (signed-URL path) ────────────────────────────────────────\n/** A file already uploaded to GCS via a signed URL. Shared across the\n * community create flows that use signed-URL uploads. */\nexport const uploadedMediaPathSchema = z.object({\n url: z.string().min(1),\n gcpPath: z.string().min(1),\n});\nexport type UploadedMediaPath = z.infer<typeof uploadedMediaPathSchema>;\n\n// ─── Create body ─────────────────────────────────────────────────────────────\n// Base object (pickable — used for per-field slice validation) ...\nexport const suggestionsComplaintsBodyObjectSchema = z.object({\n location: suggestionsComplaintsLocationSchema,\n type: suggestionsComplaintsTypeSchema.default('complaint'),\n category: z.enum(SUGGESTIONS_COMPLAINTS_CATEGORY_VALUES as unknown as [string, ...string[]], {\n message: 'Invalid category selected',\n }),\n title: titleSchema,\n description: z\n .string()\n .trim()\n .max(\n SUGGESTIONS_COMPLAINTS_DESCRIPTION_MAX_CHARS,\n 'Description must not exceed 2000 characters',\n )\n .optional()\n .or(z.literal('')),\n images: z.array(uploadedMediaPathSchema).max(SUGGESTIONS_COMPLAINTS_MAX_IMAGES).optional(),\n video: uploadedMediaPathSchema.optional(),\n videoLinks: z.array(z.string().url()).max(SUGGESTIONS_COMPLAINTS_MAX_VIDEO_LINKS).optional(),\n audio: uploadedMediaPathSchema.optional(),\n /** When true, the reporter's identity is hidden on the public wire.\n * Server-enforced: only an identity-verified user may set this. */\n isAnonymous: z.boolean().default(false),\n});\n\n// ... and the full create schema with the cross-field rule (suggestions need\n// a real description). Use the BASE object for `.pick()`/`.extend()`; use this\n// refined one for the final whole-body validation.\nexport const createSuggestionsComplaintsBodySchema = suggestionsComplaintsBodyObjectSchema.refine(\n (d) =>\n d.type !== 'suggestion' ||\n (typeof d.description === 'string' &&\n d.description.trim().length >= SUGGESTIONS_COMPLAINTS_SUGGESTION_DESCRIPTION_MIN_CHARS),\n {\n message: 'Please describe your suggestion in at least 20 characters',\n path: ['description'],\n },\n);\n\nexport type CreateSuggestionsComplaintsBody = z.infer<typeof suggestionsComplaintsBodyObjectSchema>;\n\n/** A partially-filled draft as the multi-step form builds it up. */\nexport type SuggestionsComplaintsDraft = Partial<CreateSuggestionsComplaintsBody>;\n\n// ─── Per-step completeness validators (the SSOT the StepForm config delegates\n// its `isFilled` predicates to — so no validation rule is duplicated in\n// the frontend). Each returns whether that step is complete. ──────────────\nexport const suggestionsComplaintsStepValid = {\n type: (d: SuggestionsComplaintsDraft): boolean =>\n suggestionsComplaintsTypeSchema.safeParse(d.type).success,\n category: (d: SuggestionsComplaintsDraft): boolean =>\n typeof d.category === 'string' &&\n (SUGGESTIONS_COMPLAINTS_CATEGORY_VALUES as readonly string[]).includes(d.category),\n title: (d: SuggestionsComplaintsDraft): boolean => titleSchema.safeParse(d.title).success,\n description: (d: SuggestionsComplaintsDraft): boolean =>\n d.type !== 'suggestion' ||\n (typeof d.description === 'string' &&\n d.description.trim().length >= SUGGESTIONS_COMPLAINTS_SUGGESTION_DESCRIPTION_MIN_CHARS),\n /** Location is \"set\" once the state is chosen (the area hierarchy is only\n * useful for the civic feed when at least the state is known). */\n location: (d: SuggestionsComplaintsDraft): boolean => !!d.location?.state,\n} as const;\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jansathi-community-schema",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"description": "Shared Zod schemas + TypeScript types for the Jansathi hyperlocal community feature (feed, posts, engagement, social graph, groups, communities).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|