founder-schema 0.6.0 → 0.8.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/README.md +29 -0
- package/dist/core/common.d.ts +8 -1
- package/dist/core/common.d.ts.map +1 -1
- package/dist/core/common.js +27 -0
- package/dist/core/common.js.map +1 -1
- package/dist/core/errors.d.ts +7 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +11 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/limits.d.ts +5 -1
- package/dist/core/limits.d.ts.map +1 -1
- package/dist/core/limits.js +5 -1
- package/dist/core/limits.js.map +1 -1
- package/dist/core/responses.d.ts +6 -0
- package/dist/core/responses.d.ts.map +1 -1
- package/dist/domains/contributions/contributions.d.ts +9 -2
- package/dist/domains/contributions/contributions.d.ts.map +1 -1
- package/dist/domains/contributions/contributions.js +14 -4
- package/dist/domains/contributions/contributions.js.map +1 -1
- package/dist/domains/feed/feed.d.ts +478 -6
- package/dist/domains/feed/feed.d.ts.map +1 -1
- package/dist/domains/feed/feed.js +152 -9
- package/dist/domains/feed/feed.js.map +1 -1
- package/dist/domains/moderation/moderation.d.ts +2 -0
- package/dist/domains/moderation/moderation.d.ts.map +1 -1
- package/dist/domains/moderation/moderation.js +9 -2
- package/dist/domains/moderation/moderation.js.map +1 -1
- package/dist/domains/notifications/notifications.d.ts +53 -4
- package/dist/domains/notifications/notifications.d.ts.map +1 -1
- package/dist/domains/notifications/notifications.js +40 -4
- package/dist/domains/notifications/notifications.js.map +1 -1
- package/dist/domains/products/products.d.ts +21 -6
- package/dist/domains/products/products.d.ts.map +1 -1
- package/dist/domains/products/products.js +31 -10
- package/dist/domains/products/products.js.map +1 -1
- package/dist/domains/profile/founder-profile.d.ts +2 -1
- package/dist/domains/profile/founder-profile.d.ts.map +1 -1
- package/dist/domains/profile/founder-profile.js +2 -1
- package/dist/domains/profile/founder-profile.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"moderation.js","sourceRoot":"","sources":["../../../src/domains/moderation/moderation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema: moderation\n *\n * GET /api/v1/moderation/queue ?limit&cursor\n * what members reported and nobody has decided on, oldest report first\n * GET /api/v1/moderation/content ?state&limit&cursor\n * by state, newest first: visible is posts and polls by when posted;\n * hidden and removed also hold comments (so a hidden comment can be\n * found and put back), by when hidden or removed\n * A decision that changes nothing (hiding what is already hidden, removing\n * what its author deleted) leaves the reports open and writes nothing: for\n * those, dismiss.\n * POST /api/v1/moderation/:contentKind/:contentId/decision { action, note? }\n * decide on one post, poll, comment, product or review; answers the\n * item as it now is\n *\n * PRODUCTS AND REVIEWS (0.6.0) are reported and decided here too. A product is\n * hidden or restored, never removed by staff (it is its team's work; hiding\n * takes it out of Discover and off every page but its team's). A review is\n * hidden, restored or removed like a comment. The hidden and removed lists\n * hold them too, so a hidden product can be found and put back.\n *\n * ONE ITEM PER THING, NOT PER REPORT. Five members reporting one post is one\n * row with five reports, so a moderator decides once. The queue's order is\n * the oldest open report first: whoever has waited longest is seen first, and\n * a pile-on cannot jump the line.\n *\n * THE DECISIONS\n * dismiss it stays up; its open reports close as dismissed\n * hide out of every feed and thread, kept, restorable; reports actioned\n * restore a hidden item goes back up\n * remove deleted as its author deleting it would (counts move), not\n * restorable from here; reports actioned\n * Every decision on someone else's content is written to the audit log, with\n * the note when there is one.\n *\n * A report's `note` was written by a member: untrusted text, shown as text.\n *\n * @module founder-schema/domains/moderation\n */\n\nimport { z } from 'zod';\nimport {\n emptyStringToUndefined,\n objectIdSchema,\n optionalTrimmedString,\n paginationQuerySchema,\n} from '../../core/common.js';\nimport { MODERATION_NOTE_MAX } from '../../core/limits.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport { type ReportTargetKind, reportReasonSchema, reportTargetKindSchema } from '../feed/feed.js';\n\n/**\n * Where something is read in the app: a post or poll, or a product page (for a\n * product, and for a review of it).\n */\nexport const MODERATION_CONTEXT_KIND_VALUES = ['post', 'poll', 'product'] as const;\nexport const moderationContextKindSchema = z.enum(MODERATION_CONTEXT_KIND_VALUES);\nexport type ModerationContextKind = z.infer<typeof moderationContextKindSchema>;\n\n/** Where a post, poll, comment, product or review stands. */\nexport const MODERATION_STATE_VALUES = ['visible', 'hidden', 'removed'] as const;\nexport const moderationStateSchema = z.enum(MODERATION_STATE_VALUES);\nexport type ModerationState = z.infer<typeof moderationStateSchema>;\n\nexport const MODERATION_ACTION_VALUES = ['dismiss', 'hide', 'restore', 'remove'] as const;\nexport const moderationActionSchema = z.enum(MODERATION_ACTION_VALUES);\nexport type ModerationAction = z.infer<typeof moderationActionSchema>;\n\n/**\n * Which decisions apply to which kind of thing: everything, except that a\n * product is never removed by staff (it is its team's work; hiding it takes it\n * out of Discover and off every page but its team's). The decision dialog\n * offers these, and the server refuses anything else with\n * `MODERATION_ACTION_INVALID`. Exhaustive by type.\n */\nexport const MODERATION_ACTIONS_BY_TARGET_KIND = {\n post: MODERATION_ACTION_VALUES,\n poll: MODERATION_ACTION_VALUES,\n comment: MODERATION_ACTION_VALUES,\n review: MODERATION_ACTION_VALUES,\n product: ['dismiss', 'hide', 'restore'],\n} as const satisfies Record<ReportTargetKind, readonly ModerationAction[]>;\n\nconst count = z.number().int().min(0);\n\n/** One post, poll, comment, product or review as moderation sees it. */\nexport const moderationItemSchema = z.object({\n target: z.object({ kind: reportTargetKindSchema, id: objectIdSchema }),\n /**\n * Where it is read in the app: the post or poll itself, or the one a comment\n * is on; the product page for a product or a review.\n */\n content: z.object({ kind: moderationContextKindSchema, id: objectIdSchema }),\n /**\n * Its first words, cut to `MODERATION_EXCERPT_MAX`: a poll's question, a\n * product's name and tagline, a review's stars and text.\n */\n excerpt: z.string(),\n /** Who wrote it; for a product, its owner. */\n author: z.object({\n id: objectIdSchema,\n displayName: z.string(),\n avatarUrl: z.url().nullable(),\n }),\n createdAt: z.iso.datetime(),\n state: moderationStateSchema,\n reports: z.object({\n /** Reports nobody has decided on. */\n open: count,\n /** Every report ever filed about it. */\n total: count,\n /** Why it was reported, among the open ones, most-given first. */\n reasons: z.array(z.object({ reason: reportReasonSchema, count })),\n /** The reporters' notes, among the open ones, newest first (a few, not all). */\n notes: z.array(z.string()),\n /** When the oldest open report was filed. Null when none is open. */\n oldestOpenAt: z.iso.datetime().nullable(),\n }),\n /** What the asking staff member may do to it now. */\n viewer: z.object({\n canDismiss: z.boolean(),\n canHide: z.boolean(),\n canRestore: z.boolean(),\n canRemove: z.boolean(),\n }),\n});\nexport type ModerationItem = z.infer<typeof moderationItemSchema>;\n\nexport const moderationPageSchema = paginatedSchema(moderationItemSchema);\nexport type ModerationPage = z.infer<typeof moderationPageSchema>;\n\n/** `GET /moderation/content`. Visible when not said. */\nexport const moderationContentQuerySchema = paginationQuerySchema.extend({\n state: z.preprocess(emptyStringToUndefined, moderationStateSchema.default('visible')),\n});\nexport type ModerationContentQuery = z.infer<typeof moderationContentQuerySchema>;\nexport type ModerationContentQueryInput = z.input<typeof moderationContentQuerySchema>;\n\n/** `POST /moderation/:contentKind/:contentId/decision`: what is decided on. */\nexport const moderationTargetParamsSchema = z.object({\n contentKind: reportTargetKindSchema,\n contentId: objectIdSchema,\n});\nexport type ModerationTargetParams = z.infer<typeof moderationTargetParamsSchema>;\n\n/** `POST /moderation/:contentKind/:contentId/decision`. */\nexport const moderationDecisionBodySchema = z.object({\n action: moderationActionSchema,\n /** Why, for the audit log. Optional: an obvious spam removal needs no essay. */\n note: optionalTrimmedString(MODERATION_NOTE_MAX),\n});\nexport type ModerationDecisionBody = z.infer<typeof moderationDecisionBodySchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"moderation.js","sourceRoot":"","sources":["../../../src/domains/moderation/moderation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema: moderation\n *\n * GET /api/v1/moderation/queue ?limit&cursor\n * what members reported and nobody has decided on, oldest report first\n * GET /api/v1/moderation/content ?state&limit&cursor\n * by state, newest first: visible is posts and polls by when posted;\n * hidden and removed also hold comments (so a hidden comment can be\n * found and put back), by when hidden or removed\n * A decision that changes nothing (hiding what is already hidden, removing\n * what its author deleted) leaves the reports open and writes nothing: for\n * those, dismiss.\n * POST /api/v1/moderation/:contentKind/:contentId/decision { action, note? }\n * decide on one post, poll, comment, product or review; answers the\n * item as it now is\n *\n * PRODUCTS AND REVIEWS (0.6.0) are reported and decided here too. A product is\n * hidden or restored, never removed by staff (it is its team's work; hiding\n * takes it out of Discover and off every page but its team's). A review is\n * hidden, restored or removed like a comment. The hidden and removed lists\n * hold them too, so a hidden product can be found and put back.\n *\n * ONE ITEM PER THING, NOT PER REPORT. Five members reporting one post is one\n * row with five reports, so a moderator decides once. The queue's order is\n * the oldest open report first: whoever has waited longest is seen first, and\n * a pile-on cannot jump the line.\n *\n * THE DECISIONS\n * dismiss it stays up; its open reports close as dismissed\n * hide out of every feed and thread, kept, restorable; reports actioned\n * restore a hidden item goes back up\n * remove deleted as its author deleting it would (counts move), not\n * restorable from here; reports actioned\n * Every decision on someone else's content is written to the audit log, with\n * the note when there is one.\n *\n * A report's `note` was written by a member: untrusted text, shown as text.\n *\n * @module founder-schema/domains/moderation\n */\n\nimport { z } from 'zod';\nimport {\n emptyStringToUndefined,\n objectIdSchema,\n optionalTrimmedString,\n paginationQuerySchema,\n} from '../../core/common.js';\nimport { MODERATION_NOTE_MAX } from '../../core/limits.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport { type ReportTargetKind, reportReasonSchema, reportTargetKindSchema } from '../feed/feed.js';\n\n/**\n * Where something is read in the app: a post or poll, or a product page (for a\n * product, and for a review of it).\n */\nexport const MODERATION_CONTEXT_KIND_VALUES = ['post', 'poll', 'product'] as const;\nexport const moderationContextKindSchema = z.enum(MODERATION_CONTEXT_KIND_VALUES);\nexport type ModerationContextKind = z.infer<typeof moderationContextKindSchema>;\n\n/** Where a post, poll, comment, product or review stands. */\nexport const MODERATION_STATE_VALUES = ['visible', 'hidden', 'removed'] as const;\nexport const moderationStateSchema = z.enum(MODERATION_STATE_VALUES);\nexport type ModerationState = z.infer<typeof moderationStateSchema>;\n\nexport const MODERATION_ACTION_VALUES = ['dismiss', 'hide', 'restore', 'remove'] as const;\nexport const moderationActionSchema = z.enum(MODERATION_ACTION_VALUES);\nexport type ModerationAction = z.infer<typeof moderationActionSchema>;\n\n/**\n * Which decisions apply to which kind of thing: everything, except that a\n * product is never removed by staff (it is its team's work; hiding it takes it\n * out of Discover and off every page but its team's). The decision dialog\n * offers these, and the server refuses anything else with\n * `MODERATION_ACTION_INVALID`. Exhaustive by type.\n */\nexport const MODERATION_ACTIONS_BY_TARGET_KIND = {\n post: MODERATION_ACTION_VALUES,\n poll: MODERATION_ACTION_VALUES,\n comment: MODERATION_ACTION_VALUES,\n review: MODERATION_ACTION_VALUES,\n product: ['dismiss', 'hide', 'restore'],\n} as const satisfies Record<ReportTargetKind, readonly ModerationAction[]>;\n\nconst count = z.number().int().min(0);\n\n/** One post, poll, comment, product or review as moderation sees it. */\nexport const moderationItemSchema = z.object({\n target: z.object({ kind: reportTargetKindSchema, id: objectIdSchema }),\n /**\n * Where it is read in the app: the post or poll itself, or the one a comment\n * is on; the product page for a product or a review.\n */\n content: z.object({ kind: moderationContextKindSchema, id: objectIdSchema }),\n /**\n * Its first words AS THEY ARE NOW, cut to `MODERATION_EXCERPT_MAX`: a poll's\n * question, a product's name and tagline, a review's stars and text.\n */\n excerpt: z.string(),\n /**\n * The words as they were when first reported, when the author has edited\n * them since (0.8.0): both are shown, so an edit can neither hide what was\n * reported nor slip new words past a moderator judging the old ones.\n * Absent when unedited since the report.\n */\n asReported: z.string().optional(),\n /** Who wrote it; for a product, its owner. */\n author: z.object({\n id: objectIdSchema,\n displayName: z.string(),\n avatarUrl: z.url().nullable(),\n }),\n createdAt: z.iso.datetime(),\n state: moderationStateSchema,\n reports: z.object({\n /** Reports nobody has decided on. */\n open: count,\n /** Every report ever filed about it. */\n total: count,\n /** Why it was reported, among the open ones, most-given first. */\n reasons: z.array(z.object({ reason: reportReasonSchema, count })),\n /** The reporters' notes, among the open ones, newest first (a few, not all). */\n notes: z.array(z.string()),\n /** When the oldest open report was filed. Null when none is open. */\n oldestOpenAt: z.iso.datetime().nullable(),\n }),\n /** What the asking staff member may do to it now. */\n viewer: z.object({\n canDismiss: z.boolean(),\n canHide: z.boolean(),\n canRestore: z.boolean(),\n canRemove: z.boolean(),\n }),\n});\nexport type ModerationItem = z.infer<typeof moderationItemSchema>;\n\nexport const moderationPageSchema = paginatedSchema(moderationItemSchema);\nexport type ModerationPage = z.infer<typeof moderationPageSchema>;\n\n/** `GET /moderation/content`. Visible when not said. */\nexport const moderationContentQuerySchema = paginationQuerySchema.extend({\n state: z.preprocess(emptyStringToUndefined, moderationStateSchema.default('visible')),\n});\nexport type ModerationContentQuery = z.infer<typeof moderationContentQuerySchema>;\nexport type ModerationContentQueryInput = z.input<typeof moderationContentQuerySchema>;\n\n/** `POST /moderation/:contentKind/:contentId/decision`: what is decided on. */\nexport const moderationTargetParamsSchema = z.object({\n contentKind: reportTargetKindSchema,\n contentId: objectIdSchema,\n});\nexport type ModerationTargetParams = z.infer<typeof moderationTargetParamsSchema>;\n\n/** `POST /moderation/:contentKind/:contentId/decision`. */\nexport const moderationDecisionBodySchema = z.object({\n action: moderationActionSchema,\n /** Why, for the audit log. Optional: an obvious spam removal needs no essay. */\n note: optionalTrimmedString(MODERATION_NOTE_MAX),\n});\nexport type ModerationDecisionBody = z.infer<typeof moderationDecisionBodySchema>;\n"]}
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* - ONE ROW PER RECIPIENT, written when the event happens.
|
|
7
7
|
* - REPEATS MERGE: a second comment on your post updates the same row
|
|
8
|
-
* ("
|
|
9
|
-
* events it holds and `actor` the latest.
|
|
8
|
+
* ("2 new comments, latest from Asha"), it never adds one. `count` is how
|
|
9
|
+
* many events it holds and `actor` the latest. A member repeating the same
|
|
10
|
+
* act (reacting, taking it back, reacting again) does not ring it again.
|
|
10
11
|
* - THE ACTOR IS LOOKED UP WHEN READ, never copied in: a renamed member shows
|
|
11
12
|
* their new name, and a deleted one shows as null ("A former member").
|
|
12
13
|
* - PREFERENCES ARE CHECKED before writing (Jansathi stores them and never
|
|
@@ -39,8 +40,15 @@ import { z } from 'zod';
|
|
|
39
40
|
* review_reply a product's team answered your review
|
|
40
41
|
* feedback_helpful a team marked your feedback as having helped
|
|
41
42
|
* tester_confirmed a team confirmed you tested their product
|
|
43
|
+
* post_reaction someone reacted to your post or poll (0.8.0)
|
|
44
|
+
* comment_reaction someone reacted to your comment (0.8.0)
|
|
45
|
+
* repost someone reposted your post or poll (0.8.0)
|
|
46
|
+
*
|
|
47
|
+
* The three 0.8.0 kinds merge per target like the others. A reaction counts
|
|
48
|
+
* once: changing it to another type is not a new event, and taking it back
|
|
49
|
+
* does not take the event back.
|
|
42
50
|
*/
|
|
43
|
-
export declare const NOTIFICATION_KIND_VALUES: readonly ["comment_on_post", "reply_to_comment", "product_invite", "product_invite_accepted", "product_review", "review_reply", "feedback_helpful", "tester_confirmed"];
|
|
51
|
+
export declare const NOTIFICATION_KIND_VALUES: readonly ["comment_on_post", "reply_to_comment", "product_invite", "product_invite_accepted", "product_review", "review_reply", "feedback_helpful", "tester_confirmed", "post_reaction", "comment_reaction", "repost"];
|
|
44
52
|
export declare const notificationKindSchema: z.ZodEnum<{
|
|
45
53
|
feedback_helpful: "feedback_helpful";
|
|
46
54
|
tester_confirmed: "tester_confirmed";
|
|
@@ -50,15 +58,20 @@ export declare const notificationKindSchema: z.ZodEnum<{
|
|
|
50
58
|
product_invite_accepted: "product_invite_accepted";
|
|
51
59
|
product_review: "product_review";
|
|
52
60
|
review_reply: "review_reply";
|
|
61
|
+
post_reaction: "post_reaction";
|
|
62
|
+
comment_reaction: "comment_reaction";
|
|
63
|
+
repost: "repost";
|
|
53
64
|
}>;
|
|
54
65
|
export type NotificationKind = z.infer<typeof notificationKindSchema>;
|
|
55
66
|
/** The switches a member has, each covering one or more kinds. */
|
|
56
|
-
export declare const NOTIFICATION_PREFERENCE_VALUES: readonly ["comments", "replies", "productActivity", "recognition"];
|
|
67
|
+
export declare const NOTIFICATION_PREFERENCE_VALUES: readonly ["comments", "replies", "productActivity", "recognition", "reactions", "reposts"];
|
|
57
68
|
export declare const notificationPreferenceSchema: z.ZodEnum<{
|
|
58
69
|
comments: "comments";
|
|
59
70
|
replies: "replies";
|
|
60
71
|
productActivity: "productActivity";
|
|
61
72
|
recognition: "recognition";
|
|
73
|
+
reactions: "reactions";
|
|
74
|
+
reposts: "reposts";
|
|
62
75
|
}>;
|
|
63
76
|
export type NotificationPreference = z.infer<typeof notificationPreferenceSchema>;
|
|
64
77
|
/**
|
|
@@ -74,6 +87,9 @@ export declare const NOTIFICATION_PREFERENCE_OF: {
|
|
|
74
87
|
readonly review_reply: "productActivity";
|
|
75
88
|
readonly feedback_helpful: "recognition";
|
|
76
89
|
readonly tester_confirmed: "recognition";
|
|
90
|
+
readonly post_reaction: "reactions";
|
|
91
|
+
readonly comment_reaction: "reactions";
|
|
92
|
+
readonly repost: "reposts";
|
|
77
93
|
};
|
|
78
94
|
/**
|
|
79
95
|
* One row of the bell. Which of the optional targets are present depends on
|
|
@@ -87,6 +103,9 @@ export declare const NOTIFICATION_PREFERENCE_OF: {
|
|
|
87
103
|
* review_reply product, reviewId
|
|
88
104
|
* feedback_helpful product, content, commentId
|
|
89
105
|
* tester_confirmed product
|
|
106
|
+
* post_reaction content, reaction
|
|
107
|
+
* comment_reaction content, commentId, reaction, excerpt (your comment)
|
|
108
|
+
* repost content (yours, the one reposted), repostId (the latest repost, to read their words)
|
|
90
109
|
*
|
|
91
110
|
* A target that no longer exists (a deleted post, product or review) is
|
|
92
111
|
* absent rather than pointing at a 404; the row still says what happened.
|
|
@@ -102,6 +121,9 @@ export declare const notificationSchema: z.ZodObject<{
|
|
|
102
121
|
product_invite_accepted: "product_invite_accepted";
|
|
103
122
|
product_review: "product_review";
|
|
104
123
|
review_reply: "review_reply";
|
|
124
|
+
post_reaction: "post_reaction";
|
|
125
|
+
comment_reaction: "comment_reaction";
|
|
126
|
+
repost: "repost";
|
|
105
127
|
}>;
|
|
106
128
|
actor: z.ZodNullable<z.ZodObject<{
|
|
107
129
|
userId: z.ZodString;
|
|
@@ -126,10 +148,20 @@ export declare const notificationSchema: z.ZodObject<{
|
|
|
126
148
|
logoUrl: z.ZodOptional<z.ZodURL>;
|
|
127
149
|
}, z.core.$strip>>;
|
|
128
150
|
reviewId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
151
|
+
repostId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
129
152
|
role: z.ZodOptional<z.ZodEnum<{
|
|
130
153
|
team: "team";
|
|
131
154
|
cofounder: "cofounder";
|
|
132
155
|
}>>;
|
|
156
|
+
reaction: z.ZodOptional<z.ZodEnum<{
|
|
157
|
+
support: "support";
|
|
158
|
+
like: "like";
|
|
159
|
+
celebrate: "celebrate";
|
|
160
|
+
insightful: "insightful";
|
|
161
|
+
curious: "curious";
|
|
162
|
+
love: "love";
|
|
163
|
+
haha: "haha";
|
|
164
|
+
}>>;
|
|
133
165
|
excerpt: z.ZodOptional<z.ZodString>;
|
|
134
166
|
readAt: z.ZodNullable<z.ZodISODateTime>;
|
|
135
167
|
lastAt: z.ZodISODateTime;
|
|
@@ -148,6 +180,9 @@ export declare const notificationPageSchema: z.ZodObject<{
|
|
|
148
180
|
product_invite_accepted: "product_invite_accepted";
|
|
149
181
|
product_review: "product_review";
|
|
150
182
|
review_reply: "review_reply";
|
|
183
|
+
post_reaction: "post_reaction";
|
|
184
|
+
comment_reaction: "comment_reaction";
|
|
185
|
+
repost: "repost";
|
|
151
186
|
}>;
|
|
152
187
|
actor: z.ZodNullable<z.ZodObject<{
|
|
153
188
|
userId: z.ZodString;
|
|
@@ -172,10 +207,20 @@ export declare const notificationPageSchema: z.ZodObject<{
|
|
|
172
207
|
logoUrl: z.ZodOptional<z.ZodURL>;
|
|
173
208
|
}, z.core.$strip>>;
|
|
174
209
|
reviewId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
210
|
+
repostId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
175
211
|
role: z.ZodOptional<z.ZodEnum<{
|
|
176
212
|
team: "team";
|
|
177
213
|
cofounder: "cofounder";
|
|
178
214
|
}>>;
|
|
215
|
+
reaction: z.ZodOptional<z.ZodEnum<{
|
|
216
|
+
support: "support";
|
|
217
|
+
like: "like";
|
|
218
|
+
celebrate: "celebrate";
|
|
219
|
+
insightful: "insightful";
|
|
220
|
+
curious: "curious";
|
|
221
|
+
love: "love";
|
|
222
|
+
haha: "haha";
|
|
223
|
+
}>>;
|
|
179
224
|
excerpt: z.ZodOptional<z.ZodString>;
|
|
180
225
|
readAt: z.ZodNullable<z.ZodISODateTime>;
|
|
181
226
|
lastAt: z.ZodISODateTime;
|
|
@@ -199,6 +244,8 @@ export declare const notificationPreferencesSchema: z.ZodObject<{
|
|
|
199
244
|
replies: z.ZodBoolean;
|
|
200
245
|
productActivity: z.ZodBoolean;
|
|
201
246
|
recognition: z.ZodBoolean;
|
|
247
|
+
reactions: z.ZodBoolean;
|
|
248
|
+
reposts: z.ZodBoolean;
|
|
202
249
|
}, z.core.$strip>;
|
|
203
250
|
export type NotificationPreferences = z.infer<typeof notificationPreferencesSchema>;
|
|
204
251
|
/** Only the switches that changed; no defaults (a patch states what was sent). */
|
|
@@ -207,6 +254,8 @@ export declare const updateNotificationPreferencesBodySchema: z.ZodObject<{
|
|
|
207
254
|
replies: z.ZodOptional<z.ZodBoolean>;
|
|
208
255
|
productActivity: z.ZodOptional<z.ZodBoolean>;
|
|
209
256
|
recognition: z.ZodOptional<z.ZodBoolean>;
|
|
257
|
+
reactions: z.ZodOptional<z.ZodBoolean>;
|
|
258
|
+
reposts: z.ZodOptional<z.ZodBoolean>;
|
|
210
259
|
}, z.core.$strip>;
|
|
211
260
|
export type UpdateNotificationPreferencesBody = z.infer<typeof updateNotificationPreferencesBodySchema>;
|
|
212
261
|
//# sourceMappingURL=notifications.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../../src/domains/notifications/notifications.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../../src/domains/notifications/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,wBAAwB,wNAY3B,CAAC;AACX,eAAO,MAAM,sBAAsB;;;;;;;;;;;;EAAmC,CAAC;AACvE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,kEAAkE;AAClE,eAAO,MAAM,8BAA8B,4FAOjC,CAAC;AACX,eAAO,MAAM,4BAA4B;;;;;;;EAAyC,CAAC;AACnF,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF;;;GAGG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;CAYqC,CAAC;AAE7E;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoC7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAsC,CAAC;AAC1E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,iBAAiB;AACjB,eAAO,MAAM,6BAA6B;;iBAExC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,eAAO,MAAM,wBAAwB;;iBAA+C,CAAC;AACrF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,+CAA+C;AAC/C,eAAO,MAAM,6BAA6B;;;;;;;iBASxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,kFAAkF;AAClF,eAAO,MAAM,uCAAuC;;;;;;;iBAOlD,CAAC;AACH,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CACrD,OAAO,uCAAuC,CAC/C,CAAC"}
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* - ONE ROW PER RECIPIENT, written when the event happens.
|
|
7
7
|
* - REPEATS MERGE: a second comment on your post updates the same row
|
|
8
|
-
* ("
|
|
9
|
-
* events it holds and `actor` the latest.
|
|
8
|
+
* ("2 new comments, latest from Asha"), it never adds one. `count` is how
|
|
9
|
+
* many events it holds and `actor` the latest. A member repeating the same
|
|
10
|
+
* act (reacting, taking it back, reacting again) does not ring it again.
|
|
10
11
|
* - THE ACTOR IS LOOKED UP WHEN READ, never copied in: a renamed member shows
|
|
11
12
|
* their new name, and a deleted one shows as null ("A former member").
|
|
12
13
|
* - PREFERENCES ARE CHECKED before writing (Jansathi stores them and never
|
|
@@ -31,7 +32,7 @@ import { z } from 'zod';
|
|
|
31
32
|
import { objectIdSchema } from '../../core/common.js';
|
|
32
33
|
import { NOTIFICATION_EXCERPT_MAX } from '../../core/limits.js';
|
|
33
34
|
import { paginatedSchema } from '../../core/responses.js';
|
|
34
|
-
import { contentKindSchema } from '../feed/feed.js';
|
|
35
|
+
import { contentKindSchema, reactionTypeSchema } from '../feed/feed.js';
|
|
35
36
|
import { productChipSchema, productInvitableRoleSchema } from '../products/products.js';
|
|
36
37
|
import { memberBylineSchema } from '../profile/founder-profile.js';
|
|
37
38
|
/**
|
|
@@ -45,6 +46,13 @@ import { memberBylineSchema } from '../profile/founder-profile.js';
|
|
|
45
46
|
* review_reply a product's team answered your review
|
|
46
47
|
* feedback_helpful a team marked your feedback as having helped
|
|
47
48
|
* tester_confirmed a team confirmed you tested their product
|
|
49
|
+
* post_reaction someone reacted to your post or poll (0.8.0)
|
|
50
|
+
* comment_reaction someone reacted to your comment (0.8.0)
|
|
51
|
+
* repost someone reposted your post or poll (0.8.0)
|
|
52
|
+
*
|
|
53
|
+
* The three 0.8.0 kinds merge per target like the others. A reaction counts
|
|
54
|
+
* once: changing it to another type is not a new event, and taking it back
|
|
55
|
+
* does not take the event back.
|
|
48
56
|
*/
|
|
49
57
|
export const NOTIFICATION_KIND_VALUES = [
|
|
50
58
|
'comment_on_post',
|
|
@@ -55,6 +63,9 @@ export const NOTIFICATION_KIND_VALUES = [
|
|
|
55
63
|
'review_reply',
|
|
56
64
|
'feedback_helpful',
|
|
57
65
|
'tester_confirmed',
|
|
66
|
+
'post_reaction',
|
|
67
|
+
'comment_reaction',
|
|
68
|
+
'repost',
|
|
58
69
|
];
|
|
59
70
|
export const notificationKindSchema = z.enum(NOTIFICATION_KIND_VALUES);
|
|
60
71
|
/** The switches a member has, each covering one or more kinds. */
|
|
@@ -63,6 +74,8 @@ export const NOTIFICATION_PREFERENCE_VALUES = [
|
|
|
63
74
|
'replies',
|
|
64
75
|
'productActivity',
|
|
65
76
|
'recognition',
|
|
77
|
+
'reactions',
|
|
78
|
+
'reposts',
|
|
66
79
|
];
|
|
67
80
|
export const notificationPreferenceSchema = z.enum(NOTIFICATION_PREFERENCE_VALUES);
|
|
68
81
|
/**
|
|
@@ -78,6 +91,9 @@ export const NOTIFICATION_PREFERENCE_OF = {
|
|
|
78
91
|
review_reply: 'productActivity',
|
|
79
92
|
feedback_helpful: 'recognition',
|
|
80
93
|
tester_confirmed: 'recognition',
|
|
94
|
+
post_reaction: 'reactions',
|
|
95
|
+
comment_reaction: 'reactions',
|
|
96
|
+
repost: 'reposts',
|
|
81
97
|
};
|
|
82
98
|
/**
|
|
83
99
|
* One row of the bell. Which of the optional targets are present depends on
|
|
@@ -91,6 +107,9 @@ export const NOTIFICATION_PREFERENCE_OF = {
|
|
|
91
107
|
* review_reply product, reviewId
|
|
92
108
|
* feedback_helpful product, content, commentId
|
|
93
109
|
* tester_confirmed product
|
|
110
|
+
* post_reaction content, reaction
|
|
111
|
+
* comment_reaction content, commentId, reaction, excerpt (your comment)
|
|
112
|
+
* repost content (yours, the one reposted), repostId (the latest repost, to read their words)
|
|
94
113
|
*
|
|
95
114
|
* A target that no longer exists (a deleted post, product or review) is
|
|
96
115
|
* absent rather than pointing at a 404; the row still says what happened.
|
|
@@ -100,14 +119,25 @@ export const notificationSchema = z.object({
|
|
|
100
119
|
kind: notificationKindSchema,
|
|
101
120
|
/** The latest person to do it, live; null for a deleted account. */
|
|
102
121
|
actor: memberBylineSchema.nullable(),
|
|
103
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* How many EVENTS this row merges, at least 1: three comments by one person
|
|
124
|
+
* are 3. Word it as events ("3 new comments, latest from Asha"), never as
|
|
125
|
+
* people ("Asha and 2 others"), which this count does not know.
|
|
126
|
+
*/
|
|
104
127
|
count: z.number().int().positive(),
|
|
105
128
|
content: z.object({ kind: contentKindSchema, id: objectIdSchema }).optional(),
|
|
106
129
|
commentId: objectIdSchema.optional(),
|
|
107
130
|
product: productChipSchema.optional(),
|
|
108
131
|
reviewId: objectIdSchema.optional(),
|
|
132
|
+
/**
|
|
133
|
+
* For `repost` (0.8.0): the latest repost itself, so the row opens the
|
|
134
|
+
* reposter's words above your post. Gone once that repost is.
|
|
135
|
+
*/
|
|
136
|
+
repostId: objectIdSchema.optional(),
|
|
109
137
|
/** For `product_invite`: the role offered. */
|
|
110
138
|
role: productInvitableRoleSchema.optional(),
|
|
139
|
+
/** For the reaction kinds: the latest reaction, as it is now (0.8.0). */
|
|
140
|
+
reaction: reactionTypeSchema.optional(),
|
|
111
141
|
/**
|
|
112
142
|
* The first words of what was written, for the comment and review kinds
|
|
113
143
|
* (the latest one, on a merged row), cut to `NOTIFICATION_EXCERPT_MAX`. Gone
|
|
@@ -133,6 +163,10 @@ export const notificationPreferencesSchema = z.object({
|
|
|
133
163
|
replies: z.boolean(),
|
|
134
164
|
productActivity: z.boolean(),
|
|
135
165
|
recognition: z.boolean(),
|
|
166
|
+
/** Reactions to your posts, polls and comments (0.8.0). */
|
|
167
|
+
reactions: z.boolean(),
|
|
168
|
+
/** Reposts of your posts and polls (0.8.0). */
|
|
169
|
+
reposts: z.boolean(),
|
|
136
170
|
});
|
|
137
171
|
/** Only the switches that changed; no defaults (a patch states what was sent). */
|
|
138
172
|
export const updateNotificationPreferencesBodySchema = z.object({
|
|
@@ -140,5 +174,7 @@ export const updateNotificationPreferencesBodySchema = z.object({
|
|
|
140
174
|
replies: z.boolean().optional(),
|
|
141
175
|
productActivity: z.boolean().optional(),
|
|
142
176
|
recognition: z.boolean().optional(),
|
|
177
|
+
reactions: z.boolean().optional(),
|
|
178
|
+
reposts: z.boolean().optional(),
|
|
143
179
|
});
|
|
144
180
|
//# sourceMappingURL=notifications.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../src/domains/notifications/notifications.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Notifications: the bell\n *\n * The design the user approved on 2026-10-05 (\"Founder Circle Schema Review\"):\n *\n * - ONE ROW PER RECIPIENT, written when the event happens.\n * - REPEATS MERGE: a second comment on your post updates the same row\n * (\"Asha and 2 others commented\"), it never adds one. `count` is how many\n * events it holds and `actor` the latest.\n * - THE ACTOR IS LOOKED UP WHEN READ, never copied in: a renamed member shows\n * their new name, and a deleted one shows as null (\"A former member\").\n * - PREFERENCES ARE CHECKED before writing (Jansathi stores them and never\n * reads them). A team invitation is always delivered: it needs an answer.\n * - KEPT `NOTIFICATION_RETENTION_DAYS`, then deleted.\n * - Nobody is notified of their own action.\n *\n * In-app only. Push stays chat's (async-service) until a channel is designed:\n * a client polls the unread count (on focus, and every minute while visible).\n *\n * Routes (all signed in):\n * GET /api/v1/notifications paginationQuerySchema -> notificationPageSchema\n * GET /api/v1/notifications/unread-count -> notificationUnreadCountSchema\n * POST /api/v1/notifications/read mark every one read -> notificationUnreadCountSchema\n * PUT /api/v1/notifications/:notificationId/read -> notificationSchema\n * GET /api/v1/notifications/preferences -> notificationPreferencesSchema\n * PUT /api/v1/notifications/preferences updateNotificationPreferencesBodySchema -> notificationPreferencesSchema\n *\n * @module founder-schema/domains/notifications\n */\n\nimport { z } from 'zod';\nimport { objectIdSchema } from '../../core/common.js';\nimport { NOTIFICATION_EXCERPT_MAX } from '../../core/limits.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport { contentKindSchema } from '../feed/feed.js';\nimport { productChipSchema, productInvitableRoleSchema } from '../products/products.js';\nimport { memberBylineSchema } from '../profile/founder-profile.js';\n\n/**\n * What happened. ⚠ Stored per row: append only.\n *\n * comment_on_post someone commented on your post or poll\n * reply_to_comment someone replied to your comment\n * product_invite a team invited you (always delivered)\n * product_invite_accepted someone accepted your team's invitation\n * product_review someone reviewed a product you are on the team of\n * review_reply a product's team answered your review\n * feedback_helpful a team marked your feedback as having helped\n * tester_confirmed a team confirmed you tested their product\n */\nexport const NOTIFICATION_KIND_VALUES = [\n 'comment_on_post',\n 'reply_to_comment',\n 'product_invite',\n 'product_invite_accepted',\n 'product_review',\n 'review_reply',\n 'feedback_helpful',\n 'tester_confirmed',\n] as const;\nexport const notificationKindSchema = z.enum(NOTIFICATION_KIND_VALUES);\nexport type NotificationKind = z.infer<typeof notificationKindSchema>;\n\n/** The switches a member has, each covering one or more kinds. */\nexport const NOTIFICATION_PREFERENCE_VALUES = [\n 'comments',\n 'replies',\n 'productActivity',\n 'recognition',\n] as const;\nexport const notificationPreferenceSchema = z.enum(NOTIFICATION_PREFERENCE_VALUES);\nexport type NotificationPreference = z.infer<typeof notificationPreferenceSchema>;\n\n/**\n * Which switch silences which kind. Null: always delivered. Exhaustive by\n * type, so a kind added without a decision here does not compile.\n */\nexport const NOTIFICATION_PREFERENCE_OF = {\n comment_on_post: 'comments',\n reply_to_comment: 'replies',\n product_invite: null,\n product_invite_accepted: 'productActivity',\n product_review: 'productActivity',\n review_reply: 'productActivity',\n feedback_helpful: 'recognition',\n tester_confirmed: 'recognition',\n} as const satisfies Record<NotificationKind, NotificationPreference | null>;\n\n/**\n * One row of the bell. Which of the optional targets are present depends on\n * `kind`:\n *\n * comment_on_post content, commentId\n * reply_to_comment content, commentId (the reply)\n * product_invite product, role\n * product_invite_accepted product\n * product_review product, reviewId\n * review_reply product, reviewId\n * feedback_helpful product, content, commentId\n * tester_confirmed product\n *\n * A target that no longer exists (a deleted post, product or review) is\n * absent rather than pointing at a 404; the row still says what happened.\n */\nexport const notificationSchema = z.object({\n id: objectIdSchema,\n kind: notificationKindSchema,\n /** The latest person to do it, live; null for a deleted account. */\n actor: memberBylineSchema.nullable(),\n /** How many events this row merges (\"and N others\" is count - 1). At least 1. */\n count: z.number().int().positive(),\n content: z.object({ kind: contentKindSchema, id: objectIdSchema }).optional(),\n commentId: objectIdSchema.optional(),\n product: productChipSchema.optional(),\n reviewId: objectIdSchema.optional(),\n /** For `product_invite`: the role offered. */\n role: productInvitableRoleSchema.optional(),\n /**\n * The first words of what was written, for the comment and review kinds\n * (the latest one, on a merged row), cut to `NOTIFICATION_EXCERPT_MAX`. Gone\n * when that comment or review is deleted or hidden, and when its writer's\n * account is.\n */\n excerpt: z.string().max(NOTIFICATION_EXCERPT_MAX).optional(),\n /** Null until opened (or until \"mark all read\"). A merged repeat makes it unread again. */\n readAt: z.iso.datetime().nullable(),\n /** When it last happened: the list's order. */\n lastAt: z.iso.datetime(),\n createdAt: z.iso.datetime(),\n});\nexport type Notification = z.infer<typeof notificationSchema>;\n\nexport const notificationPageSchema = paginatedSchema(notificationSchema);\nexport type NotificationPage = z.infer<typeof notificationPageSchema>;\n\n/** The badge. */\nexport const notificationUnreadCountSchema = z.object({\n unread: z.number().int().nonnegative(),\n});\nexport type NotificationUnreadCount = z.infer<typeof notificationUnreadCountSchema>;\n\nexport const notificationParamsSchema = z.object({ notificationId: objectIdSchema });\nexport type NotificationParams = z.infer<typeof notificationParamsSchema>;\n\n/** Every switch, as stored (on by default). */\nexport const notificationPreferencesSchema = z.object({\n comments: z.boolean(),\n replies: z.boolean(),\n productActivity: z.boolean(),\n recognition: z.boolean(),\n});\nexport type NotificationPreferences = z.infer<typeof notificationPreferencesSchema>;\n\n/** Only the switches that changed; no defaults (a patch states what was sent). */\nexport const updateNotificationPreferencesBodySchema = z.object({\n comments: z.boolean().optional(),\n replies: z.boolean().optional(),\n productActivity: z.boolean().optional(),\n recognition: z.boolean().optional(),\n});\nexport type UpdateNotificationPreferencesBody = z.infer<\n typeof updateNotificationPreferencesBodySchema\n>;\n"]}
|
|
1
|
+
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../src/domains/notifications/notifications.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Notifications: the bell\n *\n * The design the user approved on 2026-10-05 (\"Founder Circle Schema Review\"):\n *\n * - ONE ROW PER RECIPIENT, written when the event happens.\n * - REPEATS MERGE: a second comment on your post updates the same row\n * (\"2 new comments, latest from Asha\"), it never adds one. `count` is how\n * many events it holds and `actor` the latest. A member repeating the same\n * act (reacting, taking it back, reacting again) does not ring it again.\n * - THE ACTOR IS LOOKED UP WHEN READ, never copied in: a renamed member shows\n * their new name, and a deleted one shows as null (\"A former member\").\n * - PREFERENCES ARE CHECKED before writing (Jansathi stores them and never\n * reads them). A team invitation is always delivered: it needs an answer.\n * - KEPT `NOTIFICATION_RETENTION_DAYS`, then deleted.\n * - Nobody is notified of their own action.\n *\n * In-app only. Push stays chat's (async-service) until a channel is designed:\n * a client polls the unread count (on focus, and every minute while visible).\n *\n * Routes (all signed in):\n * GET /api/v1/notifications paginationQuerySchema -> notificationPageSchema\n * GET /api/v1/notifications/unread-count -> notificationUnreadCountSchema\n * POST /api/v1/notifications/read mark every one read -> notificationUnreadCountSchema\n * PUT /api/v1/notifications/:notificationId/read -> notificationSchema\n * GET /api/v1/notifications/preferences -> notificationPreferencesSchema\n * PUT /api/v1/notifications/preferences updateNotificationPreferencesBodySchema -> notificationPreferencesSchema\n *\n * @module founder-schema/domains/notifications\n */\n\nimport { z } from 'zod';\nimport { objectIdSchema } from '../../core/common.js';\nimport { NOTIFICATION_EXCERPT_MAX } from '../../core/limits.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport { contentKindSchema, reactionTypeSchema } from '../feed/feed.js';\nimport { productChipSchema, productInvitableRoleSchema } from '../products/products.js';\nimport { memberBylineSchema } from '../profile/founder-profile.js';\n\n/**\n * What happened. ⚠ Stored per row: append only.\n *\n * comment_on_post someone commented on your post or poll\n * reply_to_comment someone replied to your comment\n * product_invite a team invited you (always delivered)\n * product_invite_accepted someone accepted your team's invitation\n * product_review someone reviewed a product you are on the team of\n * review_reply a product's team answered your review\n * feedback_helpful a team marked your feedback as having helped\n * tester_confirmed a team confirmed you tested their product\n * post_reaction someone reacted to your post or poll (0.8.0)\n * comment_reaction someone reacted to your comment (0.8.0)\n * repost someone reposted your post or poll (0.8.0)\n *\n * The three 0.8.0 kinds merge per target like the others. A reaction counts\n * once: changing it to another type is not a new event, and taking it back\n * does not take the event back.\n */\nexport const NOTIFICATION_KIND_VALUES = [\n 'comment_on_post',\n 'reply_to_comment',\n 'product_invite',\n 'product_invite_accepted',\n 'product_review',\n 'review_reply',\n 'feedback_helpful',\n 'tester_confirmed',\n 'post_reaction',\n 'comment_reaction',\n 'repost',\n] as const;\nexport const notificationKindSchema = z.enum(NOTIFICATION_KIND_VALUES);\nexport type NotificationKind = z.infer<typeof notificationKindSchema>;\n\n/** The switches a member has, each covering one or more kinds. */\nexport const NOTIFICATION_PREFERENCE_VALUES = [\n 'comments',\n 'replies',\n 'productActivity',\n 'recognition',\n 'reactions',\n 'reposts',\n] as const;\nexport const notificationPreferenceSchema = z.enum(NOTIFICATION_PREFERENCE_VALUES);\nexport type NotificationPreference = z.infer<typeof notificationPreferenceSchema>;\n\n/**\n * Which switch silences which kind. Null: always delivered. Exhaustive by\n * type, so a kind added without a decision here does not compile.\n */\nexport const NOTIFICATION_PREFERENCE_OF = {\n comment_on_post: 'comments',\n reply_to_comment: 'replies',\n product_invite: null,\n product_invite_accepted: 'productActivity',\n product_review: 'productActivity',\n review_reply: 'productActivity',\n feedback_helpful: 'recognition',\n tester_confirmed: 'recognition',\n post_reaction: 'reactions',\n comment_reaction: 'reactions',\n repost: 'reposts',\n} as const satisfies Record<NotificationKind, NotificationPreference | null>;\n\n/**\n * One row of the bell. Which of the optional targets are present depends on\n * `kind`:\n *\n * comment_on_post content, commentId\n * reply_to_comment content, commentId (the reply)\n * product_invite product, role\n * product_invite_accepted product\n * product_review product, reviewId\n * review_reply product, reviewId\n * feedback_helpful product, content, commentId\n * tester_confirmed product\n * post_reaction content, reaction\n * comment_reaction content, commentId, reaction, excerpt (your comment)\n * repost content (yours, the one reposted), repostId (the latest repost, to read their words)\n *\n * A target that no longer exists (a deleted post, product or review) is\n * absent rather than pointing at a 404; the row still says what happened.\n */\nexport const notificationSchema = z.object({\n id: objectIdSchema,\n kind: notificationKindSchema,\n /** The latest person to do it, live; null for a deleted account. */\n actor: memberBylineSchema.nullable(),\n /**\n * How many EVENTS this row merges, at least 1: three comments by one person\n * are 3. Word it as events (\"3 new comments, latest from Asha\"), never as\n * people (\"Asha and 2 others\"), which this count does not know.\n */\n count: z.number().int().positive(),\n content: z.object({ kind: contentKindSchema, id: objectIdSchema }).optional(),\n commentId: objectIdSchema.optional(),\n product: productChipSchema.optional(),\n reviewId: objectIdSchema.optional(),\n /**\n * For `repost` (0.8.0): the latest repost itself, so the row opens the\n * reposter's words above your post. Gone once that repost is.\n */\n repostId: objectIdSchema.optional(),\n /** For `product_invite`: the role offered. */\n role: productInvitableRoleSchema.optional(),\n /** For the reaction kinds: the latest reaction, as it is now (0.8.0). */\n reaction: reactionTypeSchema.optional(),\n /**\n * The first words of what was written, for the comment and review kinds\n * (the latest one, on a merged row), cut to `NOTIFICATION_EXCERPT_MAX`. Gone\n * when that comment or review is deleted or hidden, and when its writer's\n * account is.\n */\n excerpt: z.string().max(NOTIFICATION_EXCERPT_MAX).optional(),\n /** Null until opened (or until \"mark all read\"). A merged repeat makes it unread again. */\n readAt: z.iso.datetime().nullable(),\n /** When it last happened: the list's order. */\n lastAt: z.iso.datetime(),\n createdAt: z.iso.datetime(),\n});\nexport type Notification = z.infer<typeof notificationSchema>;\n\nexport const notificationPageSchema = paginatedSchema(notificationSchema);\nexport type NotificationPage = z.infer<typeof notificationPageSchema>;\n\n/** The badge. */\nexport const notificationUnreadCountSchema = z.object({\n unread: z.number().int().nonnegative(),\n});\nexport type NotificationUnreadCount = z.infer<typeof notificationUnreadCountSchema>;\n\nexport const notificationParamsSchema = z.object({ notificationId: objectIdSchema });\nexport type NotificationParams = z.infer<typeof notificationParamsSchema>;\n\n/** Every switch, as stored (on by default). */\nexport const notificationPreferencesSchema = z.object({\n comments: z.boolean(),\n replies: z.boolean(),\n productActivity: z.boolean(),\n recognition: z.boolean(),\n /** Reactions to your posts, polls and comments (0.8.0). */\n reactions: z.boolean(),\n /** Reposts of your posts and polls (0.8.0). */\n reposts: z.boolean(),\n});\nexport type NotificationPreferences = z.infer<typeof notificationPreferencesSchema>;\n\n/** Only the switches that changed; no defaults (a patch states what was sent). */\nexport const updateNotificationPreferencesBodySchema = z.object({\n comments: z.boolean().optional(),\n replies: z.boolean().optional(),\n productActivity: z.boolean().optional(),\n recognition: z.boolean().optional(),\n reactions: z.boolean().optional(),\n reposts: z.boolean().optional(),\n});\nexport type UpdateNotificationPreferencesBody = z.infer<\n typeof updateNotificationPreferencesBodySchema\n>;\n"]}
|
|
@@ -153,9 +153,11 @@ export type ProductAsk = z.infer<typeof productAskSchema>;
|
|
|
153
153
|
*
|
|
154
154
|
* trending recent upvotes, reviews and feedback, newer counting more
|
|
155
155
|
* (recomputed every 15 minutes). The default.
|
|
156
|
-
* new
|
|
157
|
-
*
|
|
158
|
-
*
|
|
156
|
+
* new newest first by when it was FIRST listed (unlisting and
|
|
157
|
+
* listing again does not move it back to the top)
|
|
158
|
+
* top_rated best rated, pulled toward a neutral 3 stars by 5 imaginary
|
|
159
|
+
* reviews so a few perfect ratings cannot beat many excellent
|
|
160
|
+
* ones; only products with `REVIEW_AVERAGE_MIN_COUNT` reviews
|
|
159
161
|
*/
|
|
160
162
|
export declare const PRODUCT_SORT_VALUES: readonly ["trending", "new", "top_rated"];
|
|
161
163
|
export declare const productSortSchema: z.ZodEnum<{
|
|
@@ -181,7 +183,11 @@ export declare const productLinksSchema: z.ZodObject<{
|
|
|
181
183
|
github: z.ZodOptional<z.ZodURL>;
|
|
182
184
|
}, z.core.$strip>;
|
|
183
185
|
export type ProductLinks = z.infer<typeof productLinksSchema>;
|
|
184
|
-
/**
|
|
186
|
+
/**
|
|
187
|
+
* A demo video: a link to one YouTube VIDEO, played in the page (never
|
|
188
|
+
* uploaded). A channel or playlist link is refused: the page cannot embed it
|
|
189
|
+
* (`youtubeVideoId`, 0.7.0).
|
|
190
|
+
*/
|
|
185
191
|
export declare const productVideoUrlSchema: z.ZodURL;
|
|
186
192
|
/**
|
|
187
193
|
* A product named on something else: the chip on a post or poll, a
|
|
@@ -228,6 +234,7 @@ export declare const productCardViewerSchema: z.ZodObject<{
|
|
|
228
234
|
owner: "owner";
|
|
229
235
|
cofounder: "cofounder";
|
|
230
236
|
}>>;
|
|
237
|
+
canRepresent: z.ZodOptional<z.ZodBoolean>;
|
|
231
238
|
}, z.core.$strip>;
|
|
232
239
|
/**
|
|
233
240
|
* A product on Discover, on a profile, in "my products". `cover` is the first
|
|
@@ -321,6 +328,7 @@ export declare const productCardSchema: z.ZodObject<{
|
|
|
321
328
|
launch: "launch";
|
|
322
329
|
ideas: "ideas";
|
|
323
330
|
}>>;
|
|
331
|
+
hiddenByStaff: z.ZodOptional<z.ZodBoolean>;
|
|
324
332
|
viewer: z.ZodOptional<z.ZodObject<{
|
|
325
333
|
upvoted: z.ZodBoolean;
|
|
326
334
|
canUpvote: z.ZodBoolean;
|
|
@@ -329,6 +337,7 @@ export declare const productCardSchema: z.ZodObject<{
|
|
|
329
337
|
owner: "owner";
|
|
330
338
|
cofounder: "cofounder";
|
|
331
339
|
}>>;
|
|
340
|
+
canRepresent: z.ZodOptional<z.ZodBoolean>;
|
|
332
341
|
}, z.core.$strip>>;
|
|
333
342
|
}, z.core.$strip>;
|
|
334
343
|
export type ProductCard = z.infer<typeof productCardSchema>;
|
|
@@ -681,7 +690,9 @@ export declare const productInvitesSchema: z.ZodObject<{
|
|
|
681
690
|
export type ProductInvites = z.infer<typeof productInvitesSchema>;
|
|
682
691
|
/**
|
|
683
692
|
* `GET /products/mine`: every product the viewer is on the team of, archived
|
|
684
|
-
* included, owned first. Not paged: a founder owns at most `PRODUCTS_OWNED_MAX
|
|
693
|
+
* included, owned first. Not paged: a founder owns at most `PRODUCTS_OWNED_MAX`,
|
|
694
|
+
* and joins teams by accepting invitations one at a time, so the list stays
|
|
695
|
+
* short in practice; there is no hard cap on teams joined.
|
|
685
696
|
*/
|
|
686
697
|
export declare const myProductsSchema: z.ZodObject<{
|
|
687
698
|
items: z.ZodArray<z.ZodObject<{
|
|
@@ -771,6 +782,7 @@ export declare const myProductsSchema: z.ZodObject<{
|
|
|
771
782
|
launch: "launch";
|
|
772
783
|
ideas: "ideas";
|
|
773
784
|
}>>;
|
|
785
|
+
hiddenByStaff: z.ZodOptional<z.ZodBoolean>;
|
|
774
786
|
viewer: z.ZodOptional<z.ZodObject<{
|
|
775
787
|
upvoted: z.ZodBoolean;
|
|
776
788
|
canUpvote: z.ZodBoolean;
|
|
@@ -779,6 +791,7 @@ export declare const myProductsSchema: z.ZodObject<{
|
|
|
779
791
|
owner: "owner";
|
|
780
792
|
cofounder: "cofounder";
|
|
781
793
|
}>>;
|
|
794
|
+
canRepresent: z.ZodOptional<z.ZodBoolean>;
|
|
782
795
|
}, z.core.$strip>>;
|
|
783
796
|
}, z.core.$strip>>;
|
|
784
797
|
}, z.core.$strip>;
|
|
@@ -883,6 +896,7 @@ export declare const productCardPageSchema: z.ZodObject<{
|
|
|
883
896
|
launch: "launch";
|
|
884
897
|
ideas: "ideas";
|
|
885
898
|
}>>;
|
|
899
|
+
hiddenByStaff: z.ZodOptional<z.ZodBoolean>;
|
|
886
900
|
viewer: z.ZodOptional<z.ZodObject<{
|
|
887
901
|
upvoted: z.ZodBoolean;
|
|
888
902
|
canUpvote: z.ZodBoolean;
|
|
@@ -891,6 +905,7 @@ export declare const productCardPageSchema: z.ZodObject<{
|
|
|
891
905
|
owner: "owner";
|
|
892
906
|
cofounder: "cofounder";
|
|
893
907
|
}>>;
|
|
908
|
+
canRepresent: z.ZodOptional<z.ZodBoolean>;
|
|
894
909
|
}, z.core.$strip>>;
|
|
895
910
|
}, z.core.$strip>>;
|
|
896
911
|
nextCursor: z.ZodNullable<z.ZodString>;
|
|
@@ -927,7 +942,7 @@ export declare const productTesterPageSchema: z.ZodObject<{
|
|
|
927
942
|
nextCursor: z.ZodNullable<z.ZodString>;
|
|
928
943
|
}, z.core.$strip>;
|
|
929
944
|
export type ProductTesterPage = z.infer<typeof productTesterPageSchema>;
|
|
930
|
-
/**
|
|
945
|
+
/** Co-founder invitations first, then team; within each, the oldest invitation first. */
|
|
931
946
|
export declare const productPendingInvitePageSchema: z.ZodObject<{
|
|
932
947
|
items: z.ZodArray<z.ZodObject<{
|
|
933
948
|
member: z.ZodObject<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"products.d.ts","sourceRoot":"","sources":["../../../src/domains/products/products.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA4BxB,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAKpC;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,+CAAgD,CAAC;AAClF,eAAO,MAAM,kBAAkB;;;;;EAA+B,CAAC;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,uBAAuB,gGAS1B,CAAC;AACX,eAAO,MAAM,qBAAqB;;;;;;;;;EAAkC,CAAC;AACrE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,6EAA6E;AAC7E,eAAO,MAAM,yBAAyB,iCAAkC,CAAC;AACzE,eAAO,MAAM,uBAAuB;;;EAAoC,CAAC;AACzE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE;;;GAGG;AACH,eAAO,MAAM,qBAAqB,iCAAkC,CAAC;AACrE,eAAO,MAAM,mBAAmB;;;EAAgC,CAAC;AACjE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,4EAA4E;AAC5E,eAAO,MAAM,wBAAwB,yCAA0C,CAAC;AAChF,eAAO,MAAM,qBAAqB;;;;EAAmC,CAAC;AACtE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,oGAAoG;AACpG,eAAO,MAAM,6BAA6B,gCAAiC,CAAC;AAC5E,eAAO,MAAM,0BAA0B;;;EAAwC,CAAC;AAChF,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,qDAAsD,CAAC;AACtF,eAAO,MAAM,gBAAgB;;;;;EAA6B,CAAC;AAC3D,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D
|
|
1
|
+
{"version":3,"file":"products.d.ts","sourceRoot":"","sources":["../../../src/domains/products/products.ts"],"names":[],"mappings":"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"}
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
* @module founder-schema/domains/products
|
|
72
72
|
*/
|
|
73
73
|
import { z } from 'zod';
|
|
74
|
-
import { emptyStringToUndefined, httpUrlOnHosts, httpUrlSchema, industrySchema, multiValueQuery, objectIdSchema, optionalTrimmedString, paginationQuerySchema, productListingRequirementSchema,
|
|
74
|
+
import { emptyStringToUndefined, httpUrlOnHosts, httpUrlSchema, industrySchema, multiValueQuery, objectIdSchema, optionalTrimmedString, paginationQuerySchema, productListingRequirementSchema, youtubeVideoId, } from '../../core/common.js';
|
|
75
75
|
import { DISCOVER_FILTER_VALUES_MAX, DISCOVER_TEXT_MAX, PRODUCT_CAPTION_MAX, PRODUCT_DESCRIPTION_MAX, PRODUCT_NAME_MAX, PRODUCT_SCREENSHOTS_MAX, PRODUCT_TAGLINE_MAX, } from '../../core/limits.js';
|
|
76
76
|
import { paginatedSchema } from '../../core/responses.js';
|
|
77
77
|
import { VALIDATION_MESSAGE } from '../../core/validation.js';
|
|
@@ -132,9 +132,11 @@ export const productAskSchema = z.enum(PRODUCT_ASK_VALUES);
|
|
|
132
132
|
*
|
|
133
133
|
* trending recent upvotes, reviews and feedback, newer counting more
|
|
134
134
|
* (recomputed every 15 minutes). The default.
|
|
135
|
-
* new
|
|
136
|
-
*
|
|
137
|
-
*
|
|
135
|
+
* new newest first by when it was FIRST listed (unlisting and
|
|
136
|
+
* listing again does not move it back to the top)
|
|
137
|
+
* top_rated best rated, pulled toward a neutral 3 stars by 5 imaginary
|
|
138
|
+
* reviews so a few perfect ratings cannot beat many excellent
|
|
139
|
+
* ones; only products with `REVIEW_AVERAGE_MIN_COUNT` reviews
|
|
138
140
|
*/
|
|
139
141
|
export const PRODUCT_SORT_VALUES = ['trending', 'new', 'top_rated'];
|
|
140
142
|
export const productSortSchema = z.enum(PRODUCT_SORT_VALUES);
|
|
@@ -151,12 +153,18 @@ export { PRODUCT_LISTING_REQUIREMENT_VALUES, productListingRequirementSchema, }
|
|
|
151
153
|
*/
|
|
152
154
|
export const productLinksSchema = z.object({
|
|
153
155
|
website: httpUrlSchema.optional(),
|
|
154
|
-
appStore: httpUrlOnHosts(['apps.apple.com'], VALIDATION_MESSAGE.appStoreLinkHost).optional(),
|
|
156
|
+
appStore: httpUrlOnHosts(['apps.apple.com', 'itunes.apple.com'], VALIDATION_MESSAGE.appStoreLinkHost).optional(),
|
|
155
157
|
playStore: httpUrlOnHosts(['play.google.com'], VALIDATION_MESSAGE.playStoreLinkHost).optional(),
|
|
156
|
-
github: httpUrlOnHosts(['github.com'], VALIDATION_MESSAGE.githubLinkHost).optional(),
|
|
158
|
+
github: httpUrlOnHosts(['github.com', 'www.github.com'], VALIDATION_MESSAGE.githubLinkHost).optional(),
|
|
159
|
+
});
|
|
160
|
+
/**
|
|
161
|
+
* A demo video: a link to one YouTube VIDEO, played in the page (never
|
|
162
|
+
* uploaded). A channel or playlist link is refused: the page cannot embed it
|
|
163
|
+
* (`youtubeVideoId`, 0.7.0).
|
|
164
|
+
*/
|
|
165
|
+
export const productVideoUrlSchema = httpUrlSchema.refine((url) => youtubeVideoId(url) !== null, {
|
|
166
|
+
error: VALIDATION_MESSAGE.videoLinkHost,
|
|
157
167
|
});
|
|
158
|
-
/** A demo video: a YouTube link, played in the page. Video is never uploaded. */
|
|
159
|
-
export const productVideoUrlSchema = httpUrlOnHosts(YOUTUBE_HOSTS, VALIDATION_MESSAGE.videoLinkHost);
|
|
160
168
|
const platformsSchema = z
|
|
161
169
|
.array(productPlatformSchema)
|
|
162
170
|
.max(PRODUCT_PLATFORM_VALUES.length)
|
|
@@ -195,6 +203,12 @@ export const productCardViewerSchema = z.object({
|
|
|
195
203
|
canUpvote: z.boolean(),
|
|
196
204
|
/** Their role on its team, or null. */
|
|
197
205
|
role: productTeamRoleSchema.nullable(),
|
|
206
|
+
/**
|
|
207
|
+
* They may act FOR it now (0.7.0): on its team, and it is neither archived
|
|
208
|
+
* nor hidden by staff. The server's own rule, for a picker such as the
|
|
209
|
+
* composer's "about which product" (the page's `viewer.canRepresent`).
|
|
210
|
+
*/
|
|
211
|
+
canRepresent: z.boolean().optional(),
|
|
198
212
|
});
|
|
199
213
|
/**
|
|
200
214
|
* A product on Discover, on a profile, in "my products". `cover` is the first
|
|
@@ -216,6 +230,11 @@ export const productCardSchema = z.object({
|
|
|
216
230
|
rating: productRatingSchema,
|
|
217
231
|
upvoteCount: z.number().int().nonnegative(),
|
|
218
232
|
openAsks: z.array(productAskSchema),
|
|
233
|
+
/**
|
|
234
|
+
* Staff hid it (0.7.0). Only ever on a card its team or staff can see (in
|
|
235
|
+
* "my products", on a team member's profile): a "Hidden by moderators" badge.
|
|
236
|
+
*/
|
|
237
|
+
hiddenByStaff: z.boolean().optional(),
|
|
219
238
|
viewer: productCardViewerSchema.optional(),
|
|
220
239
|
});
|
|
221
240
|
/** One person on a product's team, live-joined like every byline. */
|
|
@@ -305,7 +324,9 @@ export const productInviteSchema = z.object({
|
|
|
305
324
|
export const productInvitesSchema = z.object({ items: z.array(productInviteSchema) });
|
|
306
325
|
/**
|
|
307
326
|
* `GET /products/mine`: every product the viewer is on the team of, archived
|
|
308
|
-
* included, owned first. Not paged: a founder owns at most `PRODUCTS_OWNED_MAX
|
|
327
|
+
* included, owned first. Not paged: a founder owns at most `PRODUCTS_OWNED_MAX`,
|
|
328
|
+
* and joins teams by accepting invitations one at a time, so the list stays
|
|
329
|
+
* short in practice; there is no hard cap on teams joined.
|
|
309
330
|
*/
|
|
310
331
|
export const myProductsSchema = z.object({ items: z.array(productCardSchema) });
|
|
311
332
|
/** A member the team confirmed tested the product. */
|
|
@@ -316,7 +337,7 @@ export const productTesterSchema = z.object({
|
|
|
316
337
|
export const productCardPageSchema = paginatedSchema(productCardSchema);
|
|
317
338
|
export const productTeamPageSchema = paginatedSchema(productTeamMemberSchema);
|
|
318
339
|
export const productTesterPageSchema = paginatedSchema(productTesterSchema);
|
|
319
|
-
/**
|
|
340
|
+
/** Co-founder invitations first, then team; within each, the oldest invitation first. */
|
|
320
341
|
export const productPendingInvitePageSchema = paginatedSchema(productPendingInviteSchema);
|
|
321
342
|
/** `PUT|DELETE /products/:id/upvote`: the state it settled on and the fresh count. */
|
|
322
343
|
export const productUpvoteResponseSchema = z.object({
|