founder-schema 0.4.0 → 0.5.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 +26 -1
- package/dist/core/errors.d.ts +13 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +27 -3
- package/dist/core/errors.js.map +1 -1
- package/dist/core/limits.d.ts +27 -4
- package/dist/core/limits.d.ts.map +1 -1
- package/dist/core/limits.js +27 -4
- package/dist/core/limits.js.map +1 -1
- package/dist/core/responses.d.ts +12 -0
- package/dist/core/responses.d.ts.map +1 -1
- package/dist/core/validation.d.ts +2 -0
- package/dist/core/validation.d.ts.map +1 -1
- package/dist/core/validation.js +2 -0
- package/dist/core/validation.js.map +1 -1
- package/dist/domains/account/account.d.ts +2 -0
- package/dist/domains/account/account.d.ts.map +1 -1
- package/dist/domains/account/account.js +2 -0
- package/dist/domains/account/account.js.map +1 -1
- package/dist/domains/feed/feed.d.ts +62 -25
- package/dist/domains/feed/feed.d.ts.map +1 -1
- package/dist/domains/feed/feed.js +23 -20
- package/dist/domains/feed/feed.js.map +1 -1
- package/dist/domains/media/media.d.ts +134 -0
- package/dist/domains/media/media.d.ts.map +1 -0
- package/dist/domains/media/media.js +110 -0
- package/dist/domains/media/media.js.map +1 -0
- package/dist/domains/onboarding/onboarding.d.ts +11 -11
- package/dist/domains/onboarding/onboarding.d.ts.map +1 -1
- package/dist/domains/onboarding/onboarding.js +15 -15
- package/dist/domains/onboarding/onboarding.js.map +1 -1
- package/dist/domains/profile/founder-profile.d.ts +24 -0
- package/dist/domains/profile/founder-profile.d.ts.map +1 -1
- package/dist/domains/profile/founder-profile.js +23 -1
- package/dist/domains/profile/founder-profile.js.map +1 -1
- package/dist/domains/uploads/uploads.d.ts +52 -64
- package/dist/domains/uploads/uploads.d.ts.map +1 -1
- package/dist/domains/uploads/uploads.js +43 -60
- package/dist/domains/uploads/uploads.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* founder-schema — Media: what an uploaded image becomes
|
|
3
|
+
*
|
|
4
|
+
* THE PIPELINE, end to end (0.5.0):
|
|
5
|
+
*
|
|
6
|
+
* 1. The client shrinks the photo (long edge at most the largest size in
|
|
7
|
+
* `MEDIA_SIZES`, WebP or JPEG) and asks for an upload slot:
|
|
8
|
+
* `POST /api/v1/uploads/presign` with a purpose, a content type and the
|
|
9
|
+
* exact byte count (`uploadPresignBodySchema`).
|
|
10
|
+
* 2. It PUTs the bytes straight to storage, with the headers it was given.
|
|
11
|
+
* Nothing passes through the API server.
|
|
12
|
+
* 3. It calls `POST /api/v1/uploads/:mediaId/complete`. The server checks the
|
|
13
|
+
* file, decodes it fully, applies the photo's rotation, REMOVES EVERY
|
|
14
|
+
* PIECE OF METADATA (GPS position, camera, timestamps), re-encodes it as
|
|
15
|
+
* WebP at each size in `MEDIA_SIZES`, deletes the raw upload, and answers
|
|
16
|
+
* with `uploadedMediaSchema`.
|
|
17
|
+
* 4. A post (`attachedMediaSchema`) or the avatar (`setAvatarBodySchema`,
|
|
18
|
+
* onboarding's `avatarMediaId`) takes the media id. One upload is used
|
|
19
|
+
* once, by its owner, for the purpose it was uploaded for.
|
|
20
|
+
*
|
|
21
|
+
* What nobody sees: the raw upload. It sits in a folder the CDN cannot reach
|
|
22
|
+
* and is deleted when processing finishes, or by storage itself after a day.
|
|
23
|
+
* Only the re-encoded sizes are ever served.
|
|
24
|
+
*
|
|
25
|
+
* An image never attached is deleted after `MEDIA_UNATTACHED_TTL_HOURS`; a
|
|
26
|
+
* deleted post's images, a replaced avatar, and an erased account's images are
|
|
27
|
+
* deleted from storage by the server, so a copied link stops working.
|
|
28
|
+
*
|
|
29
|
+
* @module founder-schema/domains/media
|
|
30
|
+
*/
|
|
31
|
+
import { z } from 'zod';
|
|
32
|
+
/** What an upload is for. It decides the sizes made and where it may be attached. */
|
|
33
|
+
export declare const MEDIA_PURPOSE_VALUES: readonly ["post", "avatar"];
|
|
34
|
+
export declare const mediaPurposeSchema: z.ZodEnum<{
|
|
35
|
+
post: "post";
|
|
36
|
+
avatar: "avatar";
|
|
37
|
+
}>;
|
|
38
|
+
export type MediaPurpose = z.infer<typeof mediaPurposeSchema>;
|
|
39
|
+
/**
|
|
40
|
+
* The content types a member may upload. Every one is decoded and re-encoded
|
|
41
|
+
* to WebP before anyone sees it, so this list is about what the server can
|
|
42
|
+
* DECODE safely, not what it serves.
|
|
43
|
+
*
|
|
44
|
+
* Not HEIC: an iPhone's photo picker converts HEIC to JPEG for the web, and a
|
|
45
|
+
* client that shrinks the photo first re-encodes it anyway. SVG never: it is a
|
|
46
|
+
* document that can carry script, not an image.
|
|
47
|
+
*/
|
|
48
|
+
export declare const MEDIA_UPLOAD_CONTENT_TYPES: readonly ["image/jpeg", "image/png", "image/webp", "image/gif"];
|
|
49
|
+
export declare const mediaUploadContentTypeSchema: z.ZodEnum<{
|
|
50
|
+
"image/jpeg": "image/jpeg";
|
|
51
|
+
"image/png": "image/png";
|
|
52
|
+
"image/webp": "image/webp";
|
|
53
|
+
"image/gif": "image/gif";
|
|
54
|
+
}>;
|
|
55
|
+
export type MediaUploadContentType = z.infer<typeof mediaUploadContentTypeSchema>;
|
|
56
|
+
/**
|
|
57
|
+
* The sizes each purpose is stored at, smallest first.
|
|
58
|
+
*
|
|
59
|
+
* - post: the LONG edge, aspect kept. 540 for a feed card on a plain screen,
|
|
60
|
+
* 1080 for the same card on a phone's dense screen, 2048 for the full-screen
|
|
61
|
+
* view. An image smaller than a size is never enlarged, so a small image
|
|
62
|
+
* has fewer sizes.
|
|
63
|
+
* - avatar: a SQUARE, cropped around the most interesting region (usually a
|
|
64
|
+
* face). 192 for every small circle (bylines, lists, chat) up to 64 CSS
|
|
65
|
+
* pixels on a 3x screen, 512 for a profile header.
|
|
66
|
+
*
|
|
67
|
+
* A client shrinks a photo to the largest size here before uploading: anything
|
|
68
|
+
* bigger is thrown away by the server anyway, and costs the member data.
|
|
69
|
+
*/
|
|
70
|
+
export declare const MEDIA_SIZES: {
|
|
71
|
+
readonly post: readonly [540, 1080, 2048];
|
|
72
|
+
readonly avatar: readonly [192, 512];
|
|
73
|
+
};
|
|
74
|
+
/** One stored size of an image. */
|
|
75
|
+
export declare const mediaVariantSchema: z.ZodObject<{
|
|
76
|
+
width: z.ZodNumber;
|
|
77
|
+
height: z.ZodNumber;
|
|
78
|
+
url: z.ZodURL;
|
|
79
|
+
}, z.core.$strip>;
|
|
80
|
+
export type MediaVariant = z.infer<typeof mediaVariantSchema>;
|
|
81
|
+
/**
|
|
82
|
+
* A processed image, as every read shows it.
|
|
83
|
+
*
|
|
84
|
+
* `variants` is a ready-made `srcset`: smallest first, each with its own pixel
|
|
85
|
+
* width. `url`, `width` and `height` repeat the largest one, so a client that
|
|
86
|
+
* ignores `variants` still renders correctly and can reserve the image's box
|
|
87
|
+
* from the aspect ratio before it loads (no layout shift).
|
|
88
|
+
*
|
|
89
|
+
* `placeholder` is a ThumbHash (base64): a ~30 character blurred preview a
|
|
90
|
+
* client can paint instantly while the image loads.
|
|
91
|
+
*
|
|
92
|
+
* URLs point at our media CDN. They come from our own pipeline, never from a
|
|
93
|
+
* member, so a plain `z.url()`.
|
|
94
|
+
*/
|
|
95
|
+
export declare const mediaImageSchema: z.ZodObject<{
|
|
96
|
+
id: z.ZodString;
|
|
97
|
+
url: z.ZodURL;
|
|
98
|
+
width: z.ZodNumber;
|
|
99
|
+
height: z.ZodNumber;
|
|
100
|
+
variants: z.ZodArray<z.ZodObject<{
|
|
101
|
+
width: z.ZodNumber;
|
|
102
|
+
height: z.ZodNumber;
|
|
103
|
+
url: z.ZodURL;
|
|
104
|
+
}, z.core.$strip>>;
|
|
105
|
+
placeholder: z.ZodOptional<z.ZodString>;
|
|
106
|
+
}, z.core.$strip>;
|
|
107
|
+
export type MediaImage = z.infer<typeof mediaImageSchema>;
|
|
108
|
+
/**
|
|
109
|
+
* `POST /api/v1/uploads/:mediaId/complete` answers with this: the image as it
|
|
110
|
+
* will appear, not yet attached to anything.
|
|
111
|
+
*
|
|
112
|
+
* `expiresAt` is when the server deletes it if it is still unattached
|
|
113
|
+
* (`MEDIA_UNATTACHED_TTL_HOURS` after the slot was issued). A composer left
|
|
114
|
+
* open longer than that must upload again.
|
|
115
|
+
*/
|
|
116
|
+
export declare const uploadedMediaSchema: z.ZodObject<{
|
|
117
|
+
id: z.ZodString;
|
|
118
|
+
url: z.ZodURL;
|
|
119
|
+
width: z.ZodNumber;
|
|
120
|
+
height: z.ZodNumber;
|
|
121
|
+
variants: z.ZodArray<z.ZodObject<{
|
|
122
|
+
width: z.ZodNumber;
|
|
123
|
+
height: z.ZodNumber;
|
|
124
|
+
url: z.ZodURL;
|
|
125
|
+
}, z.core.$strip>>;
|
|
126
|
+
placeholder: z.ZodOptional<z.ZodString>;
|
|
127
|
+
purpose: z.ZodEnum<{
|
|
128
|
+
post: "post";
|
|
129
|
+
avatar: "avatar";
|
|
130
|
+
}>;
|
|
131
|
+
expiresAt: z.ZodISODateTime;
|
|
132
|
+
}, z.core.$strip>;
|
|
133
|
+
export type UploadedMedia = z.infer<typeof uploadedMediaSchema>;
|
|
134
|
+
//# sourceMappingURL=media.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../../src/domains/media/media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,qFAAqF;AACrF,eAAO,MAAM,oBAAoB,6BAA8B,CAAC;AAChE,eAAO,MAAM,kBAAkB;;;EAA+B,CAAC;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,iEAK7B,CAAC;AACX,eAAO,MAAM,4BAA4B;;;;;EAAqC,CAAC;AAC/E,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,WAAW;;;CAGoC,CAAC;AAE7D,mCAAmC;AACnC,eAAO,MAAM,kBAAkB;;;;iBAI7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;iBAO3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;iBAG9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* founder-schema — Media: what an uploaded image becomes
|
|
3
|
+
*
|
|
4
|
+
* THE PIPELINE, end to end (0.5.0):
|
|
5
|
+
*
|
|
6
|
+
* 1. The client shrinks the photo (long edge at most the largest size in
|
|
7
|
+
* `MEDIA_SIZES`, WebP or JPEG) and asks for an upload slot:
|
|
8
|
+
* `POST /api/v1/uploads/presign` with a purpose, a content type and the
|
|
9
|
+
* exact byte count (`uploadPresignBodySchema`).
|
|
10
|
+
* 2. It PUTs the bytes straight to storage, with the headers it was given.
|
|
11
|
+
* Nothing passes through the API server.
|
|
12
|
+
* 3. It calls `POST /api/v1/uploads/:mediaId/complete`. The server checks the
|
|
13
|
+
* file, decodes it fully, applies the photo's rotation, REMOVES EVERY
|
|
14
|
+
* PIECE OF METADATA (GPS position, camera, timestamps), re-encodes it as
|
|
15
|
+
* WebP at each size in `MEDIA_SIZES`, deletes the raw upload, and answers
|
|
16
|
+
* with `uploadedMediaSchema`.
|
|
17
|
+
* 4. A post (`attachedMediaSchema`) or the avatar (`setAvatarBodySchema`,
|
|
18
|
+
* onboarding's `avatarMediaId`) takes the media id. One upload is used
|
|
19
|
+
* once, by its owner, for the purpose it was uploaded for.
|
|
20
|
+
*
|
|
21
|
+
* What nobody sees: the raw upload. It sits in a folder the CDN cannot reach
|
|
22
|
+
* and is deleted when processing finishes, or by storage itself after a day.
|
|
23
|
+
* Only the re-encoded sizes are ever served.
|
|
24
|
+
*
|
|
25
|
+
* An image never attached is deleted after `MEDIA_UNATTACHED_TTL_HOURS`; a
|
|
26
|
+
* deleted post's images, a replaced avatar, and an erased account's images are
|
|
27
|
+
* deleted from storage by the server, so a copied link stops working.
|
|
28
|
+
*
|
|
29
|
+
* @module founder-schema/domains/media
|
|
30
|
+
*/
|
|
31
|
+
import { z } from 'zod';
|
|
32
|
+
import { MEDIA_PLACEHOLDER_MAX } from '../../core/limits.js';
|
|
33
|
+
/** What an upload is for. It decides the sizes made and where it may be attached. */
|
|
34
|
+
export const MEDIA_PURPOSE_VALUES = ['post', 'avatar'];
|
|
35
|
+
export const mediaPurposeSchema = z.enum(MEDIA_PURPOSE_VALUES);
|
|
36
|
+
/**
|
|
37
|
+
* The content types a member may upload. Every one is decoded and re-encoded
|
|
38
|
+
* to WebP before anyone sees it, so this list is about what the server can
|
|
39
|
+
* DECODE safely, not what it serves.
|
|
40
|
+
*
|
|
41
|
+
* Not HEIC: an iPhone's photo picker converts HEIC to JPEG for the web, and a
|
|
42
|
+
* client that shrinks the photo first re-encodes it anyway. SVG never: it is a
|
|
43
|
+
* document that can carry script, not an image.
|
|
44
|
+
*/
|
|
45
|
+
export const MEDIA_UPLOAD_CONTENT_TYPES = [
|
|
46
|
+
'image/jpeg',
|
|
47
|
+
'image/png',
|
|
48
|
+
'image/webp',
|
|
49
|
+
'image/gif',
|
|
50
|
+
];
|
|
51
|
+
export const mediaUploadContentTypeSchema = z.enum(MEDIA_UPLOAD_CONTENT_TYPES);
|
|
52
|
+
/**
|
|
53
|
+
* The sizes each purpose is stored at, smallest first.
|
|
54
|
+
*
|
|
55
|
+
* - post: the LONG edge, aspect kept. 540 for a feed card on a plain screen,
|
|
56
|
+
* 1080 for the same card on a phone's dense screen, 2048 for the full-screen
|
|
57
|
+
* view. An image smaller than a size is never enlarged, so a small image
|
|
58
|
+
* has fewer sizes.
|
|
59
|
+
* - avatar: a SQUARE, cropped around the most interesting region (usually a
|
|
60
|
+
* face). 192 for every small circle (bylines, lists, chat) up to 64 CSS
|
|
61
|
+
* pixels on a 3x screen, 512 for a profile header.
|
|
62
|
+
*
|
|
63
|
+
* A client shrinks a photo to the largest size here before uploading: anything
|
|
64
|
+
* bigger is thrown away by the server anyway, and costs the member data.
|
|
65
|
+
*/
|
|
66
|
+
export const MEDIA_SIZES = {
|
|
67
|
+
post: [540, 1080, 2048],
|
|
68
|
+
avatar: [192, 512],
|
|
69
|
+
};
|
|
70
|
+
/** One stored size of an image. */
|
|
71
|
+
export const mediaVariantSchema = z.object({
|
|
72
|
+
width: z.number().int().positive(),
|
|
73
|
+
height: z.number().int().positive(),
|
|
74
|
+
url: z.url(),
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* A processed image, as every read shows it.
|
|
78
|
+
*
|
|
79
|
+
* `variants` is a ready-made `srcset`: smallest first, each with its own pixel
|
|
80
|
+
* width. `url`, `width` and `height` repeat the largest one, so a client that
|
|
81
|
+
* ignores `variants` still renders correctly and can reserve the image's box
|
|
82
|
+
* from the aspect ratio before it loads (no layout shift).
|
|
83
|
+
*
|
|
84
|
+
* `placeholder` is a ThumbHash (base64): a ~30 character blurred preview a
|
|
85
|
+
* client can paint instantly while the image loads.
|
|
86
|
+
*
|
|
87
|
+
* URLs point at our media CDN. They come from our own pipeline, never from a
|
|
88
|
+
* member, so a plain `z.url()`.
|
|
89
|
+
*/
|
|
90
|
+
export const mediaImageSchema = z.object({
|
|
91
|
+
id: z.string(),
|
|
92
|
+
url: z.url(),
|
|
93
|
+
width: z.number().int().positive(),
|
|
94
|
+
height: z.number().int().positive(),
|
|
95
|
+
variants: z.array(mediaVariantSchema).min(1).max(MEDIA_SIZES.post.length),
|
|
96
|
+
placeholder: z.string().max(MEDIA_PLACEHOLDER_MAX).optional(),
|
|
97
|
+
});
|
|
98
|
+
/**
|
|
99
|
+
* `POST /api/v1/uploads/:mediaId/complete` answers with this: the image as it
|
|
100
|
+
* will appear, not yet attached to anything.
|
|
101
|
+
*
|
|
102
|
+
* `expiresAt` is when the server deletes it if it is still unattached
|
|
103
|
+
* (`MEDIA_UNATTACHED_TTL_HOURS` after the slot was issued). A composer left
|
|
104
|
+
* open longer than that must upload again.
|
|
105
|
+
*/
|
|
106
|
+
export const uploadedMediaSchema = mediaImageSchema.extend({
|
|
107
|
+
purpose: mediaPurposeSchema,
|
|
108
|
+
expiresAt: z.iso.datetime(),
|
|
109
|
+
});
|
|
110
|
+
//# sourceMappingURL=media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.js","sourceRoot":"","sources":["../../../src/domains/media/media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,qFAAqF;AACrF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAU,CAAC;AAChE,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AAG/D;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,YAAY;IACZ,WAAW;IACX,YAAY;IACZ,WAAW;CACH,CAAC;AACX,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;AAG/E;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC;IACvB,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC;CACwC,CAAC;AAE7D,mCAAmC;AACnC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAClC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACnC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE;CACb,CAAC,CAAC;AAGH;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE;IACZ,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAClC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACnC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC;IACzE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,QAAQ,EAAE;CAC9D,CAAC,CAAC;AAGH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,gBAAgB,CAAC,MAAM,CAAC;IACzD,OAAO,EAAE,kBAAkB;IAC3B,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC","sourcesContent":["/**\n * founder-schema — Media: what an uploaded image becomes\n *\n * THE PIPELINE, end to end (0.5.0):\n *\n * 1. The client shrinks the photo (long edge at most the largest size in\n * `MEDIA_SIZES`, WebP or JPEG) and asks for an upload slot:\n * `POST /api/v1/uploads/presign` with a purpose, a content type and the\n * exact byte count (`uploadPresignBodySchema`).\n * 2. It PUTs the bytes straight to storage, with the headers it was given.\n * Nothing passes through the API server.\n * 3. It calls `POST /api/v1/uploads/:mediaId/complete`. The server checks the\n * file, decodes it fully, applies the photo's rotation, REMOVES EVERY\n * PIECE OF METADATA (GPS position, camera, timestamps), re-encodes it as\n * WebP at each size in `MEDIA_SIZES`, deletes the raw upload, and answers\n * with `uploadedMediaSchema`.\n * 4. A post (`attachedMediaSchema`) or the avatar (`setAvatarBodySchema`,\n * onboarding's `avatarMediaId`) takes the media id. One upload is used\n * once, by its owner, for the purpose it was uploaded for.\n *\n * What nobody sees: the raw upload. It sits in a folder the CDN cannot reach\n * and is deleted when processing finishes, or by storage itself after a day.\n * Only the re-encoded sizes are ever served.\n *\n * An image never attached is deleted after `MEDIA_UNATTACHED_TTL_HOURS`; a\n * deleted post's images, a replaced avatar, and an erased account's images are\n * deleted from storage by the server, so a copied link stops working.\n *\n * @module founder-schema/domains/media\n */\n\nimport { z } from 'zod';\nimport { MEDIA_PLACEHOLDER_MAX } from '../../core/limits.js';\n\n/** What an upload is for. It decides the sizes made and where it may be attached. */\nexport const MEDIA_PURPOSE_VALUES = ['post', 'avatar'] as const;\nexport const mediaPurposeSchema = z.enum(MEDIA_PURPOSE_VALUES);\nexport type MediaPurpose = z.infer<typeof mediaPurposeSchema>;\n\n/**\n * The content types a member may upload. Every one is decoded and re-encoded\n * to WebP before anyone sees it, so this list is about what the server can\n * DECODE safely, not what it serves.\n *\n * Not HEIC: an iPhone's photo picker converts HEIC to JPEG for the web, and a\n * client that shrinks the photo first re-encodes it anyway. SVG never: it is a\n * document that can carry script, not an image.\n */\nexport const MEDIA_UPLOAD_CONTENT_TYPES = [\n 'image/jpeg',\n 'image/png',\n 'image/webp',\n 'image/gif',\n] as const;\nexport const mediaUploadContentTypeSchema = z.enum(MEDIA_UPLOAD_CONTENT_TYPES);\nexport type MediaUploadContentType = z.infer<typeof mediaUploadContentTypeSchema>;\n\n/**\n * The sizes each purpose is stored at, smallest first.\n *\n * - post: the LONG edge, aspect kept. 540 for a feed card on a plain screen,\n * 1080 for the same card on a phone's dense screen, 2048 for the full-screen\n * view. An image smaller than a size is never enlarged, so a small image\n * has fewer sizes.\n * - avatar: a SQUARE, cropped around the most interesting region (usually a\n * face). 192 for every small circle (bylines, lists, chat) up to 64 CSS\n * pixels on a 3x screen, 512 for a profile header.\n *\n * A client shrinks a photo to the largest size here before uploading: anything\n * bigger is thrown away by the server anyway, and costs the member data.\n */\nexport const MEDIA_SIZES = {\n post: [540, 1080, 2048],\n avatar: [192, 512],\n} as const satisfies Record<MediaPurpose, readonly number[]>;\n\n/** One stored size of an image. */\nexport const mediaVariantSchema = z.object({\n width: z.number().int().positive(),\n height: z.number().int().positive(),\n url: z.url(),\n});\nexport type MediaVariant = z.infer<typeof mediaVariantSchema>;\n\n/**\n * A processed image, as every read shows it.\n *\n * `variants` is a ready-made `srcset`: smallest first, each with its own pixel\n * width. `url`, `width` and `height` repeat the largest one, so a client that\n * ignores `variants` still renders correctly and can reserve the image's box\n * from the aspect ratio before it loads (no layout shift).\n *\n * `placeholder` is a ThumbHash (base64): a ~30 character blurred preview a\n * client can paint instantly while the image loads.\n *\n * URLs point at our media CDN. They come from our own pipeline, never from a\n * member, so a plain `z.url()`.\n */\nexport const mediaImageSchema = z.object({\n id: z.string(),\n url: z.url(),\n width: z.number().int().positive(),\n height: z.number().int().positive(),\n variants: z.array(mediaVariantSchema).min(1).max(MEDIA_SIZES.post.length),\n placeholder: z.string().max(MEDIA_PLACEHOLDER_MAX).optional(),\n});\nexport type MediaImage = z.infer<typeof mediaImageSchema>;\n\n/**\n * `POST /api/v1/uploads/:mediaId/complete` answers with this: the image as it\n * will appear, not yet attached to anything.\n *\n * `expiresAt` is when the server deletes it if it is still unattached\n * (`MEDIA_UNATTACHED_TTL_HOURS` after the slot was issued). A composer left\n * open longer than that must upload again.\n */\nexport const uploadedMediaSchema = mediaImageSchema.extend({\n purpose: mediaPurposeSchema,\n expiresAt: z.iso.datetime(),\n});\nexport type UploadedMedia = z.infer<typeof uploadedMediaSchema>;\n"]}
|
|
@@ -274,7 +274,7 @@ export declare const onboardingFormSchema: z.ZodObject<{
|
|
|
274
274
|
dateOfBirth: z.ZodString;
|
|
275
275
|
headline: z.ZodString;
|
|
276
276
|
about: z.ZodString;
|
|
277
|
-
|
|
277
|
+
avatarMediaId: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
278
278
|
skills: z.ZodArray<z.ZodEnum<{
|
|
279
279
|
engineering: "engineering";
|
|
280
280
|
product: "product";
|
|
@@ -442,8 +442,8 @@ export declare const EMPTY_ONBOARDING_VENTURE: OnboardingForm['venture'];
|
|
|
442
442
|
* Every field is checked against its own vocabulary and cut to its own limit;
|
|
443
443
|
* one that does not fit becomes "not answered" (`''`, `null`, `[]`) and the
|
|
444
444
|
* rest of the draft survives. Never coerced into a different answer: a value
|
|
445
|
-
* outside a vocabulary is dropped, not mapped to the nearest one. A photo
|
|
446
|
-
* is never restored (see `
|
|
445
|
+
* outside a vocabulary is dropped, not mapped to the nearest one. A photo
|
|
446
|
+
* is never restored (see `avatarMediaId`), and agreement only counts for the
|
|
447
447
|
* CURRENT guidelines.
|
|
448
448
|
*
|
|
449
449
|
* It fills in no answer the member did not give: a blank country stays blank.
|
|
@@ -478,7 +478,7 @@ export declare const completeOnboardingBodySchema: z.ZodObject<{
|
|
|
478
478
|
dateOfBirth: z.ZodString;
|
|
479
479
|
headline: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
|
|
480
480
|
about: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
|
|
481
|
-
|
|
481
|
+
avatarMediaId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
482
482
|
phone: z.ZodString;
|
|
483
483
|
links: z.ZodOptional<z.ZodObject<{
|
|
484
484
|
linkedin: z.ZodOptional<z.ZodURL>;
|
|
@@ -684,7 +684,7 @@ export declare const ONBOARDING_STEP_SCHEMAS: {
|
|
|
684
684
|
prefer_not_to_say: "prefer_not_to_say";
|
|
685
685
|
}>;
|
|
686
686
|
dateOfBirth: z.ZodString;
|
|
687
|
-
|
|
687
|
+
avatarMediaId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
688
688
|
}, z.core.$strip>;
|
|
689
689
|
readonly contact: z.ZodObject<{
|
|
690
690
|
links: z.ZodOptional<z.ZodObject<{
|
|
@@ -898,12 +898,12 @@ export declare const onboardingDraftKeySchema: z.ZodEnum<{
|
|
|
898
898
|
interests: "interests";
|
|
899
899
|
gender: "gender";
|
|
900
900
|
dateOfBirth: "dateOfBirth";
|
|
901
|
-
|
|
901
|
+
avatarMediaId: "avatarMediaId";
|
|
902
902
|
phone: "phone";
|
|
903
903
|
businessType: "businessType";
|
|
904
904
|
guidelinesVersion: "guidelinesVersion";
|
|
905
905
|
}>;
|
|
906
|
-
export declare const ONBOARDING_DRAFT_KEYS: ("location" | "about" | "displayName" | "headline" | "venture" | "skills" | "goals" | "links" | "interests" | "gender" | "dateOfBirth" | "
|
|
906
|
+
export declare const ONBOARDING_DRAFT_KEYS: ("location" | "about" | "displayName" | "headline" | "venture" | "skills" | "goals" | "links" | "interests" | "gender" | "dateOfBirth" | "avatarMediaId" | "phone" | "businessType" | "guidelinesVersion")[];
|
|
907
907
|
export type OnboardingDraftKey = z.infer<typeof onboardingDraftKeySchema>;
|
|
908
908
|
/**
|
|
909
909
|
* Where the member got to, kept in its own collection, separate from the
|
|
@@ -932,7 +932,7 @@ export declare const onboardingDraftSchema: z.ZodRecord<z.ZodEnum<{
|
|
|
932
932
|
interests: "interests";
|
|
933
933
|
gender: "gender";
|
|
934
934
|
dateOfBirth: "dateOfBirth";
|
|
935
|
-
|
|
935
|
+
avatarMediaId: "avatarMediaId";
|
|
936
936
|
phone: "phone";
|
|
937
937
|
businessType: "businessType";
|
|
938
938
|
guidelinesVersion: "guidelinesVersion";
|
|
@@ -952,7 +952,7 @@ export declare const onboardingProgressSchema: z.ZodObject<{
|
|
|
952
952
|
interests: "interests";
|
|
953
953
|
gender: "gender";
|
|
954
954
|
dateOfBirth: "dateOfBirth";
|
|
955
|
-
|
|
955
|
+
avatarMediaId: "avatarMediaId";
|
|
956
956
|
phone: "phone";
|
|
957
957
|
businessType: "businessType";
|
|
958
958
|
guidelinesVersion: "guidelinesVersion";
|
|
@@ -978,7 +978,7 @@ export declare const onboardingProgressUpdateSchema: z.ZodObject<{
|
|
|
978
978
|
interests: "interests";
|
|
979
979
|
gender: "gender";
|
|
980
980
|
dateOfBirth: "dateOfBirth";
|
|
981
|
-
|
|
981
|
+
avatarMediaId: "avatarMediaId";
|
|
982
982
|
phone: "phone";
|
|
983
983
|
businessType: "businessType";
|
|
984
984
|
guidelinesVersion: "guidelinesVersion";
|
|
@@ -1008,7 +1008,7 @@ export declare const onboardingStatusSchema: z.ZodObject<{
|
|
|
1008
1008
|
interests: "interests";
|
|
1009
1009
|
gender: "gender";
|
|
1010
1010
|
dateOfBirth: "dateOfBirth";
|
|
1011
|
-
|
|
1011
|
+
avatarMediaId: "avatarMediaId";
|
|
1012
1012
|
phone: "phone";
|
|
1013
1013
|
businessType: "businessType";
|
|
1014
1014
|
guidelinesVersion: "guidelinesVersion";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../../../src/domains/onboarding/onboarding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAmCxB;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,KAAK,CAAC;AAChC,qEAAqE;AACrE,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CAgB1E;AAED,4DAA4D;AAC5D,eAAO,MAAM,aAAa,aAGqC,CAAC;AAChE,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAYpD;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,aAG2D,CAAC;AAC1F,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAI5D,eAAO,MAAM,oBAAoB,2CAA4C,CAAC;AAC9E,eAAO,MAAM,kBAAkB;;;;EAA+B,CAAC;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,YAAY,iiCA6Ef,CAAC;AACX,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAuB,CAAC;AAChD,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,yEAOtB,CAAC;AACX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,gBAAgB,CAAC;IACtB,oFAAoF;IACpF,QAAQ,EAAE,mBAAmB,gBAAgB,EAAE,CAAC;IAChD,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;CAC1B;AAQD,eAAO,MAAM,gBAAgB,EAAE,SAAS,aAAa,EAmFpD,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,eAAO,MAAM,aAAa,KAAK,CAAC;AAIhC;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,8FAQvB,CAAC;AACX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAItE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,EAAG,YAAqB,CAAC;AACxD,eAAO,MAAM,uBAAuB,4BAElC,CAAC;AAIH;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;iBAI7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,QAA+B,CAAC;AAIjE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../../../src/domains/onboarding/onboarding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAmCxB;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,KAAK,CAAC;AAChC,qEAAqE;AACrE,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CAgB1E;AAED,4DAA4D;AAC5D,eAAO,MAAM,aAAa,aAGqC,CAAC;AAChE,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAYpD;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,aAG2D,CAAC;AAC1F,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAI5D,eAAO,MAAM,oBAAoB,2CAA4C,CAAC;AAC9E,eAAO,MAAM,kBAAkB;;;;EAA+B,CAAC;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,YAAY,iiCA6Ef,CAAC;AACX,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAuB,CAAC;AAChD,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,yEAOtB,CAAC;AACX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,gBAAgB,CAAC;IACtB,oFAAoF;IACpF,QAAQ,EAAE,mBAAmB,gBAAgB,EAAE,CAAC;IAChD,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;CAC1B;AAQD,eAAO,MAAM,gBAAgB,EAAE,SAAS,aAAa,EAmFpD,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,eAAO,MAAM,aAAa,KAAK,CAAC;AAIhC;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,8FAQvB,CAAC;AACX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAItE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,EAAG,YAAqB,CAAC;AACxD,eAAO,MAAM,uBAAuB,4BAElC,CAAC;AAIH;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;iBAI7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,QAA+B,CAAC;AAIjE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsD/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAqFlE,mDAAmD;AACnD,eAAO,MAAM,wBAAwB,EAAE,cAAc,CAAC,SAAS,CAM9D,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAGlE;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyDvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAClF,8DAA8D;AAC9D,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAIvF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmBuB,CAAC;AAE5D,wFAAwF;AACxF,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,iBAAiB,IAAI,CAAC,CAAC,KAAK,CACnE,CAAC,OAAO,uBAAuB,CAAC,CAAC,CAAC,CAAC,CACpC,CAAC;AAEF,gCAAgC;AAChC,MAAM,MAAM,eAAe,GAAG,MAAM,sBAAsB,CAAC;AAE3D;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACnC,sFAAsF;IACtF,GAAG,EAAE,iBAAiB,CAAC;IACvB,sEAAsE;IACtE,QAAQ,EAAE,oBAAoB,iBAAiB,QAAQ,CAAC;IACxD,OAAO,EAAE,oBAAoB,iBAAiB,OAAO,CAAC;IACtD,4DAA4D;IAC5D,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;IACnC;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,eAAO,MAAM,wBAAwB,EAAE,SAAS,oBAAoB,EAWnE,CAAC;AAEF,eAAO,MAAM,sBAAsB,QAAkC,CAAC;AAEtE,wEAAwE;AACxE,eAAO,MAAM,sBAAsB,EAAE,SAAS,CAAC,SAAS,eAAe,EAAE,CAAC,EAC3B,CAAC;AAEhD,kFAAkF;AAClF,eAAO,MAAM,gCAAgC;;;iBAG3C,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAC;AAI1F;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;EAA+B,CAAC;AACrE,eAAO,MAAM,qBAAqB,8MAAmC,CAAC;AACtE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;mCAAyD,CAAC;AAC5F,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;iBAGnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E;;;;GAIG;AACH,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;iBAGzC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAEtF;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;iBAIjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC"}
|
|
@@ -24,11 +24,10 @@
|
|
|
24
24
|
* @module founder-schema/domains/onboarding
|
|
25
25
|
*/
|
|
26
26
|
import { z } from 'zod';
|
|
27
|
-
import { GENDER_VALUES, GOAL_VALUES, genderSchema, goalSchema, INDUSTRY_VALUES, industrySchema, locationSchema, optionalTrimmedString, phoneSchema, SKILL_VALUES, STARTUP_STAGE_VALUES, skillSchema, startupStageSchema, } from '../../core/common.js';
|
|
27
|
+
import { GENDER_VALUES, GOAL_VALUES, genderSchema, goalSchema, INDUSTRY_VALUES, industrySchema, locationSchema, objectIdSchema, optionalTrimmedString, phoneSchema, SKILL_VALUES, STARTUP_STAGE_VALUES, skillSchema, startupStageSchema, } from '../../core/common.js';
|
|
28
28
|
import { ABOUT_MAX, CITY_MAX, COUNTRY_MAX, DISPLAY_NAME_MAX, GOALS_MIN, HEADLINE_MAX, LINK_URL_MAX, SKILLS_MAX, STATE_MAX, TEAM_SIZE_MAX, VENTURE_NAME_MAX, } from '../../core/limits.js';
|
|
29
29
|
import { VALIDATION_MESSAGE } from '../../core/validation.js';
|
|
30
30
|
import { founderLinksSchema, ventureSchema } from '../profile/founder-profile.js';
|
|
31
|
-
import { uploadKeySchema } from '../uploads/uploads.js';
|
|
32
31
|
// ─── Age ─────────────────────────────────────────────────────────────────────
|
|
33
32
|
/**
|
|
34
33
|
* 18, ruled by the user 2026-09-21. Under-18 would pull in parental-consent
|
|
@@ -399,12 +398,13 @@ export const onboardingFormSchema = z.object({
|
|
|
399
398
|
headline: z.string().max(HEADLINE_MAX),
|
|
400
399
|
about: z.string().max(ABOUT_MAX),
|
|
401
400
|
/**
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
401
|
+
* The completed upload's id (`media`, purpose `avatar`). Allowed in the form,
|
|
402
|
+
* and NEVER restored by `readOnboardingDraft`: an upload not attached within
|
|
403
|
+
* `MEDIA_UNATTACHED_TTL_HOURS` is deleted, so an id from an older draft would
|
|
404
|
+
* fail at Finish, on the last screen, about a photo picked days ago. Clients
|
|
405
|
+
* hold it for the visit and do not save it.
|
|
406
406
|
*/
|
|
407
|
-
|
|
407
|
+
avatarMediaId: objectIdSchema.nullable(),
|
|
408
408
|
skills: z.array(skillSchema).max(SKILLS_MAX),
|
|
409
409
|
/** As typed; the finish body takes `phoneSchema`'s ten digits. */
|
|
410
410
|
phone: z.string(),
|
|
@@ -476,7 +476,7 @@ const draftReaderSchema = z.object({
|
|
|
476
476
|
dateOfBirth: draftText(DATE_INPUT_MAX),
|
|
477
477
|
headline: draftText(HEADLINE_MAX),
|
|
478
478
|
about: draftText(ABOUT_MAX),
|
|
479
|
-
|
|
479
|
+
avatarMediaId: z
|
|
480
480
|
.unknown()
|
|
481
481
|
.optional()
|
|
482
482
|
.transform(() => null),
|
|
@@ -525,8 +525,8 @@ export const EMPTY_ONBOARDING_VENTURE = {
|
|
|
525
525
|
* Every field is checked against its own vocabulary and cut to its own limit;
|
|
526
526
|
* one that does not fit becomes "not answered" (`''`, `null`, `[]`) and the
|
|
527
527
|
* rest of the draft survives. Never coerced into a different answer: a value
|
|
528
|
-
* outside a vocabulary is dropped, not mapped to the nearest one. A photo
|
|
529
|
-
* is never restored (see `
|
|
528
|
+
* outside a vocabulary is dropped, not mapped to the nearest one. A photo
|
|
529
|
+
* is never restored (see `avatarMediaId`), and agreement only counts for the
|
|
530
530
|
* CURRENT guidelines.
|
|
531
531
|
*
|
|
532
532
|
* It fills in no answer the member did not give: a blank country stays blank.
|
|
@@ -578,11 +578,11 @@ export const completeOnboardingBodySchema = z.object({
|
|
|
578
578
|
headline: optionalTrimmedString(HEADLINE_MAX),
|
|
579
579
|
about: optionalTrimmedString(ABOUT_MAX),
|
|
580
580
|
/**
|
|
581
|
-
*
|
|
582
|
-
*
|
|
583
|
-
*
|
|
581
|
+
* A completed avatar upload (`media`, purpose `avatar`). The server attaches
|
|
582
|
+
* it inside the same transaction that creates the profile, so a refused
|
|
583
|
+
* Finish leaves the upload unattached (and swept), never half-used.
|
|
584
584
|
*/
|
|
585
|
-
|
|
585
|
+
avatarMediaId: objectIdSchema.optional(),
|
|
586
586
|
phone: phoneSchema,
|
|
587
587
|
links: contactLinksSchema.optional(),
|
|
588
588
|
/** Absent when the member skipped the step or refused the permission. */
|
|
@@ -629,7 +629,7 @@ export const ONBOARDING_STEP_SCHEMAS = {
|
|
|
629
629
|
dateOfBirth: true,
|
|
630
630
|
headline: true,
|
|
631
631
|
about: true,
|
|
632
|
-
|
|
632
|
+
avatarMediaId: true,
|
|
633
633
|
// Here because ruling D8 folded the design's orphan "title, achievements,
|
|
634
634
|
// skills" screen into step one. It was on no step until 0.2.0, which made
|
|
635
635
|
// it the one finish field no step owned.
|