@tbs-fitness/contract 0.5.0 → 0.7.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 +33 -9
- package/dist/cjs/auth/auth.dto.js +5 -0
- package/dist/cjs/enums/index.d.ts +80 -0
- package/dist/cjs/enums/index.js +97 -1
- package/dist/cjs/feature-flags/feature-flag.dto.d.ts +9 -0
- package/dist/cjs/feature-flags/feature-flag.dto.js +9 -1
- package/dist/cjs/food/client-food.dto.d.ts +49 -0
- package/dist/cjs/food/client-food.dto.js +26 -0
- package/dist/cjs/food/food-import.dto.d.ts +160 -0
- package/dist/cjs/food/food-import.dto.js +110 -0
- package/dist/cjs/food/food-suggestion.dto.d.ts +374 -0
- package/dist/cjs/food/food-suggestion.dto.js +170 -0
- package/dist/cjs/food/food.dto.d.ts +339 -0
- package/dist/cjs/food/food.dto.js +256 -0
- package/dist/cjs/food/index.d.ts +5 -0
- package/dist/cjs/food/index.js +21 -0
- package/dist/cjs/food/nutrition.d.ts +75 -0
- package/dist/cjs/food/nutrition.js +158 -0
- package/dist/esm/auth/auth.dto.js +5 -0
- package/dist/esm/enums/index.d.ts +80 -0
- package/dist/esm/enums/index.js +96 -0
- package/dist/esm/feature-flags/feature-flag.dto.d.ts +9 -0
- package/dist/esm/feature-flags/feature-flag.dto.js +8 -0
- package/dist/esm/food/client-food.dto.d.ts +49 -0
- package/dist/esm/food/client-food.dto.js +23 -0
- package/dist/esm/food/food-import.dto.d.ts +160 -0
- package/dist/esm/food/food-import.dto.js +107 -0
- package/dist/esm/food/food-suggestion.dto.d.ts +374 -0
- package/dist/esm/food/food-suggestion.dto.js +167 -0
- package/dist/esm/food/food.dto.d.ts +339 -0
- package/dist/esm/food/food.dto.js +253 -0
- package/dist/esm/food/index.d.ts +5 -0
- package/dist/esm/food/index.js +5 -0
- package/dist/esm/food/nutrition.d.ts +75 -0
- package/dist/esm/food/nutrition.js +144 -0
- package/package.json +11 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.foodImportRowResponseSchema = exports.foodImportBatchResponseSchema = exports.foodImportIssueSchema = exports.foodImportRowsQuerySchema = exports.foodImportListQuerySchema = exports.commitFoodImportSchema = exports.foodImportRowSchema = exports.importIdParamSchema = exports.FOOD_IMPORT_LIMITS = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const date_time_schema_js_1 = require("../common/date-time.schema.js");
|
|
6
|
+
const pagination_schema_js_1 = require("../common/pagination.schema.js");
|
|
7
|
+
const safe_text_schema_js_1 = require("../common/safe-text.schema.js");
|
|
8
|
+
const index_js_1 = require("../enums/index.js");
|
|
9
|
+
const food_dto_js_1 = require("./food.dto.js");
|
|
10
|
+
const nutrition_js_1 = require("./nutrition.js");
|
|
11
|
+
/** The file limits the API enforces, shared so the upload screen can say them up front. */
|
|
12
|
+
exports.FOOD_IMPORT_LIMITS = {
|
|
13
|
+
maxFileBytes: 2 * 1024 * 1024,
|
|
14
|
+
maxRows: 5000,
|
|
15
|
+
extensions: ['.csv', '.xlsx'],
|
|
16
|
+
};
|
|
17
|
+
exports.importIdParamSchema = zod_1.z.uuid();
|
|
18
|
+
const metadataKeyListSchema = zod_1.z.array(zod_1.z.string().min(1).max(60)).max(50);
|
|
19
|
+
/**
|
|
20
|
+
* One spreadsheet row after its cells are read. Values per 100 g (or per 100 ml
|
|
21
|
+
* for a serving in ml). Metadata are keys such as `protein` or `high-protein`,
|
|
22
|
+
* matched to the library's values. Has no defaults, so it also serves as the
|
|
23
|
+
* preview response.
|
|
24
|
+
*/
|
|
25
|
+
exports.foodImportRowSchema = zod_1.z
|
|
26
|
+
.strictObject({
|
|
27
|
+
name: (0, safe_text_schema_js_1.safeTextSchema)(food_dto_js_1.MAX_FOOD_NAME_LENGTH),
|
|
28
|
+
nameHi: (0, safe_text_schema_js_1.safeTextSchema)(food_dto_js_1.MAX_FOOD_NAME_LENGTH).nullable(),
|
|
29
|
+
namePa: (0, safe_text_schema_js_1.safeTextSchema)(food_dto_js_1.MAX_FOOD_NAME_LENGTH).nullable(),
|
|
30
|
+
englishAlias: (0, safe_text_schema_js_1.safeTextSchema)(200).nullable(),
|
|
31
|
+
category: zod_1.z.string().min(1).max(60),
|
|
32
|
+
subcategory: zod_1.z.string().min(1).max(60).nullable(),
|
|
33
|
+
servingLabel: (0, safe_text_schema_js_1.safeTextSchema)(food_dto_js_1.MAX_FOOD_NAME_LENGTH),
|
|
34
|
+
servingQuantity: (0, nutrition_js_1.quantitySchema)(),
|
|
35
|
+
servingUnit: zod_1.z.enum(index_js_1.SERVING_UNITS),
|
|
36
|
+
calories: (0, nutrition_js_1.nutrientAmountSchema)(),
|
|
37
|
+
protein: (0, nutrition_js_1.nutrientAmountSchema)(),
|
|
38
|
+
carbs: (0, nutrition_js_1.nutrientAmountSchema)(),
|
|
39
|
+
fat: (0, nutrition_js_1.nutrientAmountSchema)(),
|
|
40
|
+
sugar: (0, nutrition_js_1.nutrientAmountSchema)().nullable(),
|
|
41
|
+
fiber: (0, nutrition_js_1.nutrientAmountSchema)().nullable(),
|
|
42
|
+
solubleFiber: (0, nutrition_js_1.nutrientAmountSchema)().nullable(),
|
|
43
|
+
insolubleFiber: (0, nutrition_js_1.nutrientAmountSchema)().nullable(),
|
|
44
|
+
tags: metadataKeyListSchema,
|
|
45
|
+
mealTimings: metadataKeyListSchema,
|
|
46
|
+
notes: (0, safe_text_schema_js_1.safeTextSchema)(food_dto_js_1.MAX_FOOD_NOTES_LENGTH).nullable(),
|
|
47
|
+
})
|
|
48
|
+
.superRefine((row, ctx) => {
|
|
49
|
+
for (const issue of (0, nutrition_js_1.nutritionIssues)(row)) {
|
|
50
|
+
ctx.addIssue({
|
|
51
|
+
code: 'custom',
|
|
52
|
+
message: issue.message,
|
|
53
|
+
path: [issue.path],
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
exports.commitFoodImportSchema = zod_1.z.strictObject({
|
|
58
|
+
mode: zod_1.z.enum(index_js_1.FOOD_IMPORT_MODES).default('SKIP_DUPLICATES'),
|
|
59
|
+
/** Create categories, tags and other values the file uses but the library lacks. */
|
|
60
|
+
createMissingMetadata: zod_1.z.boolean().default(false),
|
|
61
|
+
});
|
|
62
|
+
exports.foodImportListQuerySchema = pagination_schema_js_1.paginationQuerySchema;
|
|
63
|
+
exports.foodImportRowsQuerySchema = pagination_schema_js_1.paginationQuerySchema.extend({
|
|
64
|
+
status: zod_1.z.enum(index_js_1.FOOD_IMPORT_ROW_STATUSES).optional(),
|
|
65
|
+
});
|
|
66
|
+
// Responses
|
|
67
|
+
exports.foodImportIssueSchema = zod_1.z.object({
|
|
68
|
+
/** The spreadsheet column, or null for a problem with the whole row. */
|
|
69
|
+
column: zod_1.z.string().nullable(),
|
|
70
|
+
code: zod_1.z.string(),
|
|
71
|
+
message: zod_1.z.string(),
|
|
72
|
+
severity: zod_1.z.enum(index_js_1.FOOD_IMPORT_ISSUE_SEVERITIES),
|
|
73
|
+
});
|
|
74
|
+
exports.foodImportBatchResponseSchema = zod_1.z.object({
|
|
75
|
+
id: zod_1.z.uuid(),
|
|
76
|
+
fileName: zod_1.z.string(),
|
|
77
|
+
fileType: zod_1.z.enum(index_js_1.FOOD_IMPORT_FILE_TYPES),
|
|
78
|
+
sheetName: zod_1.z.string().nullable(),
|
|
79
|
+
status: zod_1.z.enum(index_js_1.FOOD_IMPORT_STATUSES),
|
|
80
|
+
mode: zod_1.z.enum(index_js_1.FOOD_IMPORT_MODES).nullable(),
|
|
81
|
+
createMissingMetadata: zod_1.z.boolean(),
|
|
82
|
+
totalRows: zod_1.z.number().int(),
|
|
83
|
+
validRows: zod_1.z.number().int(),
|
|
84
|
+
invalidRows: zod_1.z.number().int(),
|
|
85
|
+
duplicateRows: zod_1.z.number().int(),
|
|
86
|
+
emptyRows: zod_1.z.number().int(),
|
|
87
|
+
createdCount: zod_1.z.number().int(),
|
|
88
|
+
updatedCount: zod_1.z.number().int(),
|
|
89
|
+
skippedCount: zod_1.z.number().int(),
|
|
90
|
+
/** Values the file uses that the library does not have yet, by type key. */
|
|
91
|
+
unknownMetadata: zod_1.z.array(zod_1.z.object({ typeKey: zod_1.z.string(), values: zod_1.z.array(zod_1.z.string()) })),
|
|
92
|
+
ignoredColumns: zod_1.z.array(zod_1.z.string()),
|
|
93
|
+
ignoredSheets: zod_1.z.array(zod_1.z.string()),
|
|
94
|
+
uploadedBy: zod_1.z.uuid(),
|
|
95
|
+
expiresAt: date_time_schema_js_1.utcTimestampSchema,
|
|
96
|
+
committedAt: date_time_schema_js_1.utcTimestampSchema.nullable(),
|
|
97
|
+
createdAt: date_time_schema_js_1.utcTimestampSchema,
|
|
98
|
+
});
|
|
99
|
+
exports.foodImportRowResponseSchema = zod_1.z.object({
|
|
100
|
+
id: zod_1.z.uuid(),
|
|
101
|
+
/** The row number as the spreadsheet shows it, counting the header as row 1. */
|
|
102
|
+
rowNumber: zod_1.z.number().int(),
|
|
103
|
+
status: zod_1.z.enum(index_js_1.FOOD_IMPORT_ROW_STATUSES),
|
|
104
|
+
/** The cells as read, by header. */
|
|
105
|
+
raw: zod_1.z.record(zod_1.z.string(), zod_1.z.string()),
|
|
106
|
+
parsed: exports.foodImportRowSchema.nullable(),
|
|
107
|
+
issues: zod_1.z.array(exports.foodImportIssueSchema),
|
|
108
|
+
matchedFoodId: zod_1.z.uuid().nullable(),
|
|
109
|
+
resultFoodId: zod_1.z.uuid().nullable(),
|
|
110
|
+
});
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Limits on what a client sends, shared so the app can check them before sending. */
|
|
3
|
+
export declare const FOOD_SUGGESTION_LIMITS: {
|
|
4
|
+
readonly maxPortionQuantity: 10000;
|
|
5
|
+
readonly maxCalories: 5000;
|
|
6
|
+
readonly maxMacroGrams: 1000;
|
|
7
|
+
readonly maxNoteLength: 300;
|
|
8
|
+
readonly maxPhotoBytes: number;
|
|
9
|
+
readonly photoContentTypes: readonly ["image/jpeg", "image/png", "image/webp"];
|
|
10
|
+
};
|
|
11
|
+
export declare const MAX_REVIEW_NOTE_LENGTH = 300;
|
|
12
|
+
export declare const MAX_REJECTION_REASON_LENGTH = 500;
|
|
13
|
+
export declare const suggestionIdParamSchema: z.ZodUUID;
|
|
14
|
+
/**
|
|
15
|
+
* A food a client could not find in the library. Values are the client's best
|
|
16
|
+
* guess for the portion they ate, not per 100 g.
|
|
17
|
+
*/
|
|
18
|
+
export declare const submitFoodSuggestionSchema: z.ZodObject<{
|
|
19
|
+
name: z.ZodString;
|
|
20
|
+
quantity: z.ZodNumber;
|
|
21
|
+
unit: z.ZodEnum<{
|
|
22
|
+
BOWL: "BOWL";
|
|
23
|
+
GRAM: "GRAM";
|
|
24
|
+
PIECE: "PIECE";
|
|
25
|
+
CUP: "CUP";
|
|
26
|
+
}>;
|
|
27
|
+
calories: z.ZodNumber;
|
|
28
|
+
protein: z.ZodOptional<z.ZodNumber>;
|
|
29
|
+
carbs: z.ZodOptional<z.ZodNumber>;
|
|
30
|
+
fat: z.ZodOptional<z.ZodNumber>;
|
|
31
|
+
note: z.ZodOptional<z.ZodString>;
|
|
32
|
+
photoKey: z.ZodOptional<z.ZodString>;
|
|
33
|
+
}, z.core.$strict>;
|
|
34
|
+
export type SubmitFoodSuggestionInput = z.input<typeof submitFoodSuggestionSchema>;
|
|
35
|
+
export type SubmitFoodSuggestionBody = z.output<typeof submitFoodSuggestionSchema>;
|
|
36
|
+
export declare const suggestionPhotoUploadSchema: z.ZodObject<{
|
|
37
|
+
contentType: z.ZodEnum<{
|
|
38
|
+
"image/jpeg": "image/jpeg";
|
|
39
|
+
"image/png": "image/png";
|
|
40
|
+
"image/webp": "image/webp";
|
|
41
|
+
}>;
|
|
42
|
+
sizeBytes: z.ZodNumber;
|
|
43
|
+
}, z.core.$strict>;
|
|
44
|
+
export type SuggestionPhotoUploadInput = z.input<typeof suggestionPhotoUploadSchema>;
|
|
45
|
+
export type SuggestionPhotoUploadBody = z.output<typeof suggestionPhotoUploadSchema>;
|
|
46
|
+
/** Where to upload the photo. The headers are signed and must be sent exactly. */
|
|
47
|
+
export declare const presignedUploadResponseSchema: z.ZodObject<{
|
|
48
|
+
key: z.ZodString;
|
|
49
|
+
url: z.ZodURL;
|
|
50
|
+
method: z.ZodLiteral<"PUT">;
|
|
51
|
+
headers: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
52
|
+
expiresAt: z.ZodISODateTime;
|
|
53
|
+
}, z.core.$strip>;
|
|
54
|
+
export type PresignedUploadResponse = z.output<typeof presignedUploadResponseSchema>;
|
|
55
|
+
/** The coach's corrected values for the portion the client logged. */
|
|
56
|
+
export declare const reviewedPortionSchema: z.ZodObject<{
|
|
57
|
+
name: z.ZodString;
|
|
58
|
+
quantity: z.ZodNumber;
|
|
59
|
+
unit: z.ZodEnum<{
|
|
60
|
+
BOWL: "BOWL";
|
|
61
|
+
GRAM: "GRAM";
|
|
62
|
+
PIECE: "PIECE";
|
|
63
|
+
CUP: "CUP";
|
|
64
|
+
}>;
|
|
65
|
+
grams: z.ZodNullable<z.ZodNumber>;
|
|
66
|
+
calories: z.ZodNumber;
|
|
67
|
+
protein: z.ZodNullable<z.ZodNumber>;
|
|
68
|
+
carbs: z.ZodNullable<z.ZodNumber>;
|
|
69
|
+
fat: z.ZodNullable<z.ZodNumber>;
|
|
70
|
+
}, z.core.$strict>;
|
|
71
|
+
export type ReviewedPortion = z.output<typeof reviewedPortionSchema>;
|
|
72
|
+
/**
|
|
73
|
+
* Approves a pending suggestion in one of three ways: add a new library food,
|
|
74
|
+
* link it to a food already in the library, or approve the portion without
|
|
75
|
+
* adding anything to the library.
|
|
76
|
+
*/
|
|
77
|
+
export declare const approveFoodSuggestionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
78
|
+
resolution: z.ZodLiteral<"ADDED_TO_LIBRARY">;
|
|
79
|
+
version: z.ZodNumber;
|
|
80
|
+
reviewed: z.ZodObject<{
|
|
81
|
+
name: z.ZodString;
|
|
82
|
+
quantity: z.ZodNumber;
|
|
83
|
+
unit: z.ZodEnum<{
|
|
84
|
+
BOWL: "BOWL";
|
|
85
|
+
GRAM: "GRAM";
|
|
86
|
+
PIECE: "PIECE";
|
|
87
|
+
CUP: "CUP";
|
|
88
|
+
}>;
|
|
89
|
+
grams: z.ZodNullable<z.ZodNumber>;
|
|
90
|
+
calories: z.ZodNumber;
|
|
91
|
+
protein: z.ZodNullable<z.ZodNumber>;
|
|
92
|
+
carbs: z.ZodNullable<z.ZodNumber>;
|
|
93
|
+
fat: z.ZodNullable<z.ZodNumber>;
|
|
94
|
+
}, z.core.$strict>;
|
|
95
|
+
food: z.ZodObject<{
|
|
96
|
+
name: z.ZodString;
|
|
97
|
+
nameHi: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
98
|
+
namePa: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
99
|
+
englishAlias: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
100
|
+
serving: z.ZodObject<{
|
|
101
|
+
label: z.ZodString;
|
|
102
|
+
quantity: z.ZodNumber;
|
|
103
|
+
unit: z.ZodEnum<{
|
|
104
|
+
g: "g";
|
|
105
|
+
ml: "ml";
|
|
106
|
+
}>;
|
|
107
|
+
}, z.core.$strict>;
|
|
108
|
+
nutrition: z.ZodObject<{
|
|
109
|
+
basis: z.ZodDefault<z.ZodEnum<{
|
|
110
|
+
PER_100: "PER_100";
|
|
111
|
+
PER_SERVING: "PER_SERVING";
|
|
112
|
+
}>>;
|
|
113
|
+
calories: z.ZodNumber;
|
|
114
|
+
protein: z.ZodNumber;
|
|
115
|
+
carbs: z.ZodNumber;
|
|
116
|
+
fat: z.ZodNumber;
|
|
117
|
+
sugar: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
118
|
+
fiber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
119
|
+
solubleFiber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
120
|
+
insolubleFiber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
121
|
+
}, z.core.$strict>;
|
|
122
|
+
metadataValueIds: z.ZodDefault<z.ZodArray<z.ZodUUID>>;
|
|
123
|
+
notes: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
124
|
+
}, z.core.$strict>;
|
|
125
|
+
reviewNote: z.ZodOptional<z.ZodString>;
|
|
126
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
127
|
+
resolution: z.ZodLiteral<"LINKED_TO_EXISTING">;
|
|
128
|
+
version: z.ZodNumber;
|
|
129
|
+
foodId: z.ZodUUID;
|
|
130
|
+
reviewed: z.ZodOptional<z.ZodObject<{
|
|
131
|
+
name: z.ZodString;
|
|
132
|
+
quantity: z.ZodNumber;
|
|
133
|
+
unit: z.ZodEnum<{
|
|
134
|
+
BOWL: "BOWL";
|
|
135
|
+
GRAM: "GRAM";
|
|
136
|
+
PIECE: "PIECE";
|
|
137
|
+
CUP: "CUP";
|
|
138
|
+
}>;
|
|
139
|
+
grams: z.ZodNullable<z.ZodNumber>;
|
|
140
|
+
calories: z.ZodNumber;
|
|
141
|
+
protein: z.ZodNullable<z.ZodNumber>;
|
|
142
|
+
carbs: z.ZodNullable<z.ZodNumber>;
|
|
143
|
+
fat: z.ZodNullable<z.ZodNumber>;
|
|
144
|
+
}, z.core.$strict>>;
|
|
145
|
+
reviewNote: z.ZodOptional<z.ZodString>;
|
|
146
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
147
|
+
resolution: z.ZodLiteral<"APPROVED_WITHOUT_LIBRARY">;
|
|
148
|
+
version: z.ZodNumber;
|
|
149
|
+
reviewed: z.ZodObject<{
|
|
150
|
+
name: z.ZodString;
|
|
151
|
+
quantity: z.ZodNumber;
|
|
152
|
+
unit: z.ZodEnum<{
|
|
153
|
+
BOWL: "BOWL";
|
|
154
|
+
GRAM: "GRAM";
|
|
155
|
+
PIECE: "PIECE";
|
|
156
|
+
CUP: "CUP";
|
|
157
|
+
}>;
|
|
158
|
+
grams: z.ZodNullable<z.ZodNumber>;
|
|
159
|
+
calories: z.ZodNumber;
|
|
160
|
+
protein: z.ZodNullable<z.ZodNumber>;
|
|
161
|
+
carbs: z.ZodNullable<z.ZodNumber>;
|
|
162
|
+
fat: z.ZodNullable<z.ZodNumber>;
|
|
163
|
+
}, z.core.$strict>;
|
|
164
|
+
reviewNote: z.ZodOptional<z.ZodString>;
|
|
165
|
+
}, z.core.$strict>], "resolution">;
|
|
166
|
+
export type ApproveFoodSuggestionInput = z.input<typeof approveFoodSuggestionSchema>;
|
|
167
|
+
export type ApproveFoodSuggestionBody = z.output<typeof approveFoodSuggestionSchema>;
|
|
168
|
+
/** The reason is shown to the client. */
|
|
169
|
+
export declare const rejectFoodSuggestionSchema: z.ZodObject<{
|
|
170
|
+
version: z.ZodNumber;
|
|
171
|
+
reason: z.ZodString;
|
|
172
|
+
}, z.core.$strict>;
|
|
173
|
+
export type RejectFoodSuggestionInput = z.input<typeof rejectFoodSuggestionSchema>;
|
|
174
|
+
export type RejectFoodSuggestionBody = z.output<typeof rejectFoodSuggestionSchema>;
|
|
175
|
+
export declare const foodSuggestionListQuerySchema: z.ZodObject<{
|
|
176
|
+
page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
177
|
+
pageSize: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
178
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
179
|
+
pending: "pending";
|
|
180
|
+
approved: "approved";
|
|
181
|
+
rejected: "rejected";
|
|
182
|
+
withdrawn: "withdrawn";
|
|
183
|
+
}>>;
|
|
184
|
+
search: z.ZodOptional<z.ZodString>;
|
|
185
|
+
}, z.core.$strip>;
|
|
186
|
+
export type FoodSuggestionListQuery = z.output<typeof foodSuggestionListQuerySchema>;
|
|
187
|
+
export declare const clientFoodSuggestionListQuerySchema: z.ZodObject<{
|
|
188
|
+
page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
189
|
+
pageSize: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
190
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
191
|
+
pending: "pending";
|
|
192
|
+
approved: "approved";
|
|
193
|
+
rejected: "rejected";
|
|
194
|
+
withdrawn: "withdrawn";
|
|
195
|
+
}>>;
|
|
196
|
+
}, z.core.$strip>;
|
|
197
|
+
export type ClientFoodSuggestionListQuery = z.output<typeof clientFoodSuggestionListQuerySchema>;
|
|
198
|
+
/** What the client sent. Never changes after submission. */
|
|
199
|
+
export declare const submittedFoodSchema: z.ZodObject<{
|
|
200
|
+
name: z.ZodString;
|
|
201
|
+
quantity: z.ZodNumber;
|
|
202
|
+
unit: z.ZodEnum<{
|
|
203
|
+
BOWL: "BOWL";
|
|
204
|
+
GRAM: "GRAM";
|
|
205
|
+
PIECE: "PIECE";
|
|
206
|
+
CUP: "CUP";
|
|
207
|
+
}>;
|
|
208
|
+
calories: z.ZodNumber;
|
|
209
|
+
protein: z.ZodNullable<z.ZodNumber>;
|
|
210
|
+
carbs: z.ZodNullable<z.ZodNumber>;
|
|
211
|
+
fat: z.ZodNullable<z.ZodNumber>;
|
|
212
|
+
note: z.ZodNullable<z.ZodString>;
|
|
213
|
+
hasPhoto: z.ZodBoolean;
|
|
214
|
+
}, z.core.$strip>;
|
|
215
|
+
export type SubmittedFood = z.output<typeof submittedFoodSchema>;
|
|
216
|
+
export declare const reviewedPortionResponseSchema: z.ZodObject<{
|
|
217
|
+
name: z.ZodString;
|
|
218
|
+
quantity: z.ZodNumber;
|
|
219
|
+
unit: z.ZodEnum<{
|
|
220
|
+
BOWL: "BOWL";
|
|
221
|
+
GRAM: "GRAM";
|
|
222
|
+
PIECE: "PIECE";
|
|
223
|
+
CUP: "CUP";
|
|
224
|
+
}>;
|
|
225
|
+
grams: z.ZodNullable<z.ZodNumber>;
|
|
226
|
+
calories: z.ZodNumber;
|
|
227
|
+
protein: z.ZodNullable<z.ZodNumber>;
|
|
228
|
+
carbs: z.ZodNullable<z.ZodNumber>;
|
|
229
|
+
fat: z.ZodNullable<z.ZodNumber>;
|
|
230
|
+
}, z.core.$strip>;
|
|
231
|
+
/** A suggestion as a coach reviews it. */
|
|
232
|
+
export declare const foodSuggestionResponseSchema: z.ZodObject<{
|
|
233
|
+
id: z.ZodUUID;
|
|
234
|
+
status: z.ZodEnum<{
|
|
235
|
+
pending: "pending";
|
|
236
|
+
approved: "approved";
|
|
237
|
+
rejected: "rejected";
|
|
238
|
+
withdrawn: "withdrawn";
|
|
239
|
+
}>;
|
|
240
|
+
client: z.ZodObject<{
|
|
241
|
+
id: z.ZodUUID;
|
|
242
|
+
fullName: z.ZodNullable<z.ZodString>;
|
|
243
|
+
}, z.core.$strip>;
|
|
244
|
+
submitted: z.ZodObject<{
|
|
245
|
+
name: z.ZodString;
|
|
246
|
+
quantity: z.ZodNumber;
|
|
247
|
+
unit: z.ZodEnum<{
|
|
248
|
+
BOWL: "BOWL";
|
|
249
|
+
GRAM: "GRAM";
|
|
250
|
+
PIECE: "PIECE";
|
|
251
|
+
CUP: "CUP";
|
|
252
|
+
}>;
|
|
253
|
+
calories: z.ZodNumber;
|
|
254
|
+
protein: z.ZodNullable<z.ZodNumber>;
|
|
255
|
+
carbs: z.ZodNullable<z.ZodNumber>;
|
|
256
|
+
fat: z.ZodNullable<z.ZodNumber>;
|
|
257
|
+
note: z.ZodNullable<z.ZodString>;
|
|
258
|
+
hasPhoto: z.ZodBoolean;
|
|
259
|
+
}, z.core.$strip>;
|
|
260
|
+
submittedAt: z.ZodISODateTime;
|
|
261
|
+
photoUrl: z.ZodNullable<z.ZodURL>;
|
|
262
|
+
resolution: z.ZodNullable<z.ZodEnum<{
|
|
263
|
+
ADDED_TO_LIBRARY: "ADDED_TO_LIBRARY";
|
|
264
|
+
LINKED_TO_EXISTING: "LINKED_TO_EXISTING";
|
|
265
|
+
APPROVED_WITHOUT_LIBRARY: "APPROVED_WITHOUT_LIBRARY";
|
|
266
|
+
}>>;
|
|
267
|
+
food: z.ZodNullable<z.ZodObject<{
|
|
268
|
+
id: z.ZodUUID;
|
|
269
|
+
name: z.ZodString;
|
|
270
|
+
deleted: z.ZodBoolean;
|
|
271
|
+
}, z.core.$strip>>;
|
|
272
|
+
reviewed: z.ZodNullable<z.ZodObject<{
|
|
273
|
+
name: z.ZodString;
|
|
274
|
+
quantity: z.ZodNumber;
|
|
275
|
+
unit: z.ZodEnum<{
|
|
276
|
+
BOWL: "BOWL";
|
|
277
|
+
GRAM: "GRAM";
|
|
278
|
+
PIECE: "PIECE";
|
|
279
|
+
CUP: "CUP";
|
|
280
|
+
}>;
|
|
281
|
+
grams: z.ZodNullable<z.ZodNumber>;
|
|
282
|
+
calories: z.ZodNumber;
|
|
283
|
+
protein: z.ZodNullable<z.ZodNumber>;
|
|
284
|
+
carbs: z.ZodNullable<z.ZodNumber>;
|
|
285
|
+
fat: z.ZodNullable<z.ZodNumber>;
|
|
286
|
+
}, z.core.$strip>>;
|
|
287
|
+
reviewNote: z.ZodNullable<z.ZodString>;
|
|
288
|
+
reviewedBy: z.ZodNullable<z.ZodObject<{
|
|
289
|
+
id: z.ZodUUID;
|
|
290
|
+
fullName: z.ZodNullable<z.ZodString>;
|
|
291
|
+
}, z.core.$strip>>;
|
|
292
|
+
reviewedAt: z.ZodNullable<z.ZodISODateTime>;
|
|
293
|
+
matchesLibraryFoodId: z.ZodNullable<z.ZodUUID>;
|
|
294
|
+
similarPendingCount: z.ZodNumber;
|
|
295
|
+
similarFoods: z.ZodArray<z.ZodObject<{
|
|
296
|
+
id: z.ZodUUID;
|
|
297
|
+
name: z.ZodString;
|
|
298
|
+
serving: z.ZodObject<{
|
|
299
|
+
label: z.ZodString;
|
|
300
|
+
quantity: z.ZodNumber;
|
|
301
|
+
unit: z.ZodEnum<{
|
|
302
|
+
g: "g";
|
|
303
|
+
ml: "ml";
|
|
304
|
+
}>;
|
|
305
|
+
}, z.core.$strip>;
|
|
306
|
+
per100: z.ZodObject<{
|
|
307
|
+
calories: z.ZodNumber;
|
|
308
|
+
protein: z.ZodNumber;
|
|
309
|
+
carbs: z.ZodNumber;
|
|
310
|
+
fat: z.ZodNumber;
|
|
311
|
+
sugar: z.ZodNullable<z.ZodNumber>;
|
|
312
|
+
fiber: z.ZodNullable<z.ZodNumber>;
|
|
313
|
+
solubleFiber: z.ZodNullable<z.ZodNumber>;
|
|
314
|
+
insolubleFiber: z.ZodNullable<z.ZodNumber>;
|
|
315
|
+
}, z.core.$strip>;
|
|
316
|
+
similarity: z.ZodNumber;
|
|
317
|
+
}, z.core.$strip>>;
|
|
318
|
+
version: z.ZodNumber;
|
|
319
|
+
}, z.core.$strip>;
|
|
320
|
+
export type FoodSuggestionResponse = z.output<typeof foodSuggestionResponseSchema>;
|
|
321
|
+
/** A suggestion as the client who sent it sees it. */
|
|
322
|
+
export declare const clientFoodSuggestionResponseSchema: z.ZodObject<{
|
|
323
|
+
id: z.ZodUUID;
|
|
324
|
+
status: z.ZodEnum<{
|
|
325
|
+
pending: "pending";
|
|
326
|
+
approved: "approved";
|
|
327
|
+
rejected: "rejected";
|
|
328
|
+
withdrawn: "withdrawn";
|
|
329
|
+
}>;
|
|
330
|
+
submitted: z.ZodObject<{
|
|
331
|
+
name: z.ZodString;
|
|
332
|
+
quantity: z.ZodNumber;
|
|
333
|
+
unit: z.ZodEnum<{
|
|
334
|
+
BOWL: "BOWL";
|
|
335
|
+
GRAM: "GRAM";
|
|
336
|
+
PIECE: "PIECE";
|
|
337
|
+
CUP: "CUP";
|
|
338
|
+
}>;
|
|
339
|
+
calories: z.ZodNumber;
|
|
340
|
+
protein: z.ZodNullable<z.ZodNumber>;
|
|
341
|
+
carbs: z.ZodNullable<z.ZodNumber>;
|
|
342
|
+
fat: z.ZodNullable<z.ZodNumber>;
|
|
343
|
+
note: z.ZodNullable<z.ZodString>;
|
|
344
|
+
hasPhoto: z.ZodBoolean;
|
|
345
|
+
}, z.core.$strip>;
|
|
346
|
+
submittedAt: z.ZodISODateTime;
|
|
347
|
+
resolution: z.ZodNullable<z.ZodEnum<{
|
|
348
|
+
ADDED_TO_LIBRARY: "ADDED_TO_LIBRARY";
|
|
349
|
+
LINKED_TO_EXISTING: "LINKED_TO_EXISTING";
|
|
350
|
+
APPROVED_WITHOUT_LIBRARY: "APPROVED_WITHOUT_LIBRARY";
|
|
351
|
+
}>>;
|
|
352
|
+
reviewed: z.ZodNullable<z.ZodObject<{
|
|
353
|
+
name: z.ZodString;
|
|
354
|
+
quantity: z.ZodNumber;
|
|
355
|
+
unit: z.ZodEnum<{
|
|
356
|
+
BOWL: "BOWL";
|
|
357
|
+
GRAM: "GRAM";
|
|
358
|
+
PIECE: "PIECE";
|
|
359
|
+
CUP: "CUP";
|
|
360
|
+
}>;
|
|
361
|
+
grams: z.ZodNullable<z.ZodNumber>;
|
|
362
|
+
calories: z.ZodNumber;
|
|
363
|
+
protein: z.ZodNullable<z.ZodNumber>;
|
|
364
|
+
carbs: z.ZodNullable<z.ZodNumber>;
|
|
365
|
+
fat: z.ZodNullable<z.ZodNumber>;
|
|
366
|
+
}, z.core.$strip>>;
|
|
367
|
+
reviewNote: z.ZodNullable<z.ZodString>;
|
|
368
|
+
reviewedAt: z.ZodNullable<z.ZodISODateTime>;
|
|
369
|
+
food: z.ZodNullable<z.ZodObject<{
|
|
370
|
+
id: z.ZodUUID;
|
|
371
|
+
name: z.ZodString;
|
|
372
|
+
}, z.core.$strip>>;
|
|
373
|
+
}, z.core.$strip>;
|
|
374
|
+
export type ClientFoodSuggestionResponse = z.output<typeof clientFoodSuggestionResponseSchema>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clientFoodSuggestionResponseSchema = exports.foodSuggestionResponseSchema = exports.reviewedPortionResponseSchema = exports.submittedFoodSchema = exports.clientFoodSuggestionListQuerySchema = exports.foodSuggestionListQuerySchema = exports.rejectFoodSuggestionSchema = exports.approveFoodSuggestionSchema = exports.reviewedPortionSchema = exports.presignedUploadResponseSchema = exports.suggestionPhotoUploadSchema = exports.submitFoodSuggestionSchema = exports.suggestionIdParamSchema = exports.MAX_REJECTION_REASON_LENGTH = exports.MAX_REVIEW_NOTE_LENGTH = exports.FOOD_SUGGESTION_LIMITS = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const date_time_schema_js_1 = require("../common/date-time.schema.js");
|
|
6
|
+
const pagination_schema_js_1 = require("../common/pagination.schema.js");
|
|
7
|
+
const safe_text_schema_js_1 = require("../common/safe-text.schema.js");
|
|
8
|
+
const index_js_1 = require("../enums/index.js");
|
|
9
|
+
const food_dto_js_1 = require("./food.dto.js");
|
|
10
|
+
const nutrition_js_1 = require("./nutrition.js");
|
|
11
|
+
/** Limits on what a client sends, shared so the app can check them before sending. */
|
|
12
|
+
exports.FOOD_SUGGESTION_LIMITS = {
|
|
13
|
+
maxPortionQuantity: 10000,
|
|
14
|
+
maxCalories: 5000,
|
|
15
|
+
maxMacroGrams: 1000,
|
|
16
|
+
maxNoteLength: 300,
|
|
17
|
+
maxPhotoBytes: 5 * 1024 * 1024,
|
|
18
|
+
photoContentTypes: ['image/jpeg', 'image/png', 'image/webp'],
|
|
19
|
+
};
|
|
20
|
+
exports.MAX_REVIEW_NOTE_LENGTH = 300;
|
|
21
|
+
exports.MAX_REJECTION_REASON_LENGTH = 500;
|
|
22
|
+
const MAX_PHOTO_KEY_LENGTH = 1024;
|
|
23
|
+
exports.suggestionIdParamSchema = zod_1.z.uuid();
|
|
24
|
+
const caloriesSchema = (0, nutrition_js_1.nutrientAmountSchema)(exports.FOOD_SUGGESTION_LIMITS.maxCalories);
|
|
25
|
+
const macroSchema = (0, nutrition_js_1.nutrientAmountSchema)(exports.FOOD_SUGGESTION_LIMITS.maxMacroGrams);
|
|
26
|
+
const portionQuantitySchema = (0, nutrition_js_1.quantitySchema)(exports.FOOD_SUGGESTION_LIMITS.maxPortionQuantity);
|
|
27
|
+
/**
|
|
28
|
+
* A food a client could not find in the library. Values are the client's best
|
|
29
|
+
* guess for the portion they ate, not per 100 g.
|
|
30
|
+
*/
|
|
31
|
+
exports.submitFoodSuggestionSchema = zod_1.z.strictObject({
|
|
32
|
+
name: (0, safe_text_schema_js_1.safeTextSchema)(food_dto_js_1.MAX_FOOD_NAME_LENGTH),
|
|
33
|
+
quantity: portionQuantitySchema,
|
|
34
|
+
unit: zod_1.z.enum(index_js_1.PORTION_UNITS),
|
|
35
|
+
calories: caloriesSchema,
|
|
36
|
+
protein: macroSchema.optional(),
|
|
37
|
+
carbs: macroSchema.optional(),
|
|
38
|
+
fat: macroSchema.optional(),
|
|
39
|
+
note: (0, safe_text_schema_js_1.safeTextSchema)(exports.FOOD_SUGGESTION_LIMITS.maxNoteLength).optional(),
|
|
40
|
+
/** The key returned by the photo upload request, after the upload succeeded. */
|
|
41
|
+
photoKey: zod_1.z.string().min(1).max(MAX_PHOTO_KEY_LENGTH).optional(),
|
|
42
|
+
});
|
|
43
|
+
exports.suggestionPhotoUploadSchema = zod_1.z.strictObject({
|
|
44
|
+
contentType: zod_1.z.enum(exports.FOOD_SUGGESTION_LIMITS.photoContentTypes),
|
|
45
|
+
sizeBytes: zod_1.z.number().int().min(1).max(exports.FOOD_SUGGESTION_LIMITS.maxPhotoBytes),
|
|
46
|
+
});
|
|
47
|
+
/** Where to upload the photo. The headers are signed and must be sent exactly. */
|
|
48
|
+
exports.presignedUploadResponseSchema = zod_1.z.object({
|
|
49
|
+
key: zod_1.z.string(),
|
|
50
|
+
url: zod_1.z.url(),
|
|
51
|
+
method: zod_1.z.literal('PUT'),
|
|
52
|
+
headers: zod_1.z.record(zod_1.z.string(), zod_1.z.string()),
|
|
53
|
+
expiresAt: date_time_schema_js_1.utcTimestampSchema,
|
|
54
|
+
});
|
|
55
|
+
/** The coach's corrected values for the portion the client logged. */
|
|
56
|
+
exports.reviewedPortionSchema = zod_1.z.strictObject({
|
|
57
|
+
name: (0, safe_text_schema_js_1.safeTextSchema)(food_dto_js_1.MAX_FOOD_NAME_LENGTH),
|
|
58
|
+
quantity: portionQuantitySchema,
|
|
59
|
+
unit: zod_1.z.enum(index_js_1.PORTION_UNITS),
|
|
60
|
+
/** The portion's weight in grams, when known: "1 bowl · 120 g". */
|
|
61
|
+
grams: portionQuantitySchema.nullable(),
|
|
62
|
+
calories: caloriesSchema,
|
|
63
|
+
protein: macroSchema.nullable(),
|
|
64
|
+
carbs: macroSchema.nullable(),
|
|
65
|
+
fat: macroSchema.nullable(),
|
|
66
|
+
});
|
|
67
|
+
const versionSchema = zod_1.z.number().int().min(1);
|
|
68
|
+
const reviewNoteSchema = (0, safe_text_schema_js_1.safeTextSchema)(exports.MAX_REVIEW_NOTE_LENGTH).optional();
|
|
69
|
+
/**
|
|
70
|
+
* Approves a pending suggestion in one of three ways: add a new library food,
|
|
71
|
+
* link it to a food already in the library, or approve the portion without
|
|
72
|
+
* adding anything to the library.
|
|
73
|
+
*/
|
|
74
|
+
exports.approveFoodSuggestionSchema = zod_1.z.discriminatedUnion('resolution', [
|
|
75
|
+
zod_1.z.strictObject({
|
|
76
|
+
resolution: zod_1.z.literal('ADDED_TO_LIBRARY'),
|
|
77
|
+
version: versionSchema,
|
|
78
|
+
reviewed: exports.reviewedPortionSchema,
|
|
79
|
+
food: food_dto_js_1.createFoodSchema,
|
|
80
|
+
reviewNote: reviewNoteSchema,
|
|
81
|
+
}),
|
|
82
|
+
zod_1.z.strictObject({
|
|
83
|
+
resolution: zod_1.z.literal('LINKED_TO_EXISTING'),
|
|
84
|
+
version: versionSchema,
|
|
85
|
+
foodId: zod_1.z.uuid(),
|
|
86
|
+
reviewed: exports.reviewedPortionSchema.optional(),
|
|
87
|
+
reviewNote: reviewNoteSchema,
|
|
88
|
+
}),
|
|
89
|
+
zod_1.z.strictObject({
|
|
90
|
+
resolution: zod_1.z.literal('APPROVED_WITHOUT_LIBRARY'),
|
|
91
|
+
version: versionSchema,
|
|
92
|
+
reviewed: exports.reviewedPortionSchema,
|
|
93
|
+
reviewNote: reviewNoteSchema,
|
|
94
|
+
}),
|
|
95
|
+
]);
|
|
96
|
+
/** The reason is shown to the client. */
|
|
97
|
+
exports.rejectFoodSuggestionSchema = zod_1.z.strictObject({
|
|
98
|
+
version: versionSchema,
|
|
99
|
+
reason: (0, safe_text_schema_js_1.safeTextSchema)(exports.MAX_REJECTION_REASON_LENGTH),
|
|
100
|
+
});
|
|
101
|
+
exports.foodSuggestionListQuerySchema = pagination_schema_js_1.paginationQuerySchema.extend({
|
|
102
|
+
status: zod_1.z.enum(index_js_1.FOOD_SUGGESTION_STATUSES).optional(),
|
|
103
|
+
search: zod_1.z.string().trim().min(1).max(food_dto_js_1.MAX_FOOD_SEARCH_LENGTH).optional(),
|
|
104
|
+
});
|
|
105
|
+
exports.clientFoodSuggestionListQuerySchema = pagination_schema_js_1.paginationQuerySchema.extend({ status: zod_1.z.enum(index_js_1.FOOD_SUGGESTION_STATUSES).optional() });
|
|
106
|
+
// Responses
|
|
107
|
+
/** What the client sent. Never changes after submission. */
|
|
108
|
+
exports.submittedFoodSchema = zod_1.z.object({
|
|
109
|
+
name: zod_1.z.string(),
|
|
110
|
+
quantity: zod_1.z.number(),
|
|
111
|
+
unit: zod_1.z.enum(index_js_1.PORTION_UNITS),
|
|
112
|
+
calories: zod_1.z.number(),
|
|
113
|
+
protein: zod_1.z.number().nullable(),
|
|
114
|
+
carbs: zod_1.z.number().nullable(),
|
|
115
|
+
fat: zod_1.z.number().nullable(),
|
|
116
|
+
note: zod_1.z.string().nullable(),
|
|
117
|
+
hasPhoto: zod_1.z.boolean(),
|
|
118
|
+
});
|
|
119
|
+
exports.reviewedPortionResponseSchema = zod_1.z.object({
|
|
120
|
+
name: zod_1.z.string(),
|
|
121
|
+
quantity: zod_1.z.number(),
|
|
122
|
+
unit: zod_1.z.enum(index_js_1.PORTION_UNITS),
|
|
123
|
+
grams: zod_1.z.number().nullable(),
|
|
124
|
+
calories: zod_1.z.number(),
|
|
125
|
+
protein: zod_1.z.number().nullable(),
|
|
126
|
+
carbs: zod_1.z.number().nullable(),
|
|
127
|
+
fat: zod_1.z.number().nullable(),
|
|
128
|
+
});
|
|
129
|
+
const linkedFoodSchema = zod_1.z.object({
|
|
130
|
+
id: zod_1.z.uuid(),
|
|
131
|
+
name: zod_1.z.string(),
|
|
132
|
+
/** True when the food was removed from the library after approval. */
|
|
133
|
+
deleted: zod_1.z.boolean(),
|
|
134
|
+
});
|
|
135
|
+
/** A suggestion as a coach reviews it. */
|
|
136
|
+
exports.foodSuggestionResponseSchema = zod_1.z.object({
|
|
137
|
+
id: zod_1.z.uuid(),
|
|
138
|
+
status: zod_1.z.enum(index_js_1.FOOD_SUGGESTION_STATUSES),
|
|
139
|
+
client: food_dto_js_1.userRefSchema,
|
|
140
|
+
submitted: exports.submittedFoodSchema,
|
|
141
|
+
submittedAt: date_time_schema_js_1.utcTimestampSchema,
|
|
142
|
+
/** A short-lived download link; only in the single-suggestion response. */
|
|
143
|
+
photoUrl: zod_1.z.url().nullable(),
|
|
144
|
+
resolution: zod_1.z.enum(index_js_1.FOOD_SUGGESTION_RESOLUTIONS).nullable(),
|
|
145
|
+
food: linkedFoodSchema.nullable(),
|
|
146
|
+
reviewed: exports.reviewedPortionResponseSchema.nullable(),
|
|
147
|
+
reviewNote: zod_1.z.string().nullable(),
|
|
148
|
+
reviewedBy: food_dto_js_1.userRefSchema.nullable(),
|
|
149
|
+
reviewedAt: date_time_schema_js_1.utcTimestampSchema.nullable(),
|
|
150
|
+
/** A library food with exactly the same name, if any. */
|
|
151
|
+
matchesLibraryFoodId: zod_1.z.uuid().nullable(),
|
|
152
|
+
/** Other clients' pending suggestions with the same name. */
|
|
153
|
+
similarPendingCount: zod_1.z.number().int(),
|
|
154
|
+
/** Library foods with similar names; only in the single-suggestion response. */
|
|
155
|
+
similarFoods: zod_1.z.array(food_dto_js_1.similarFoodResponseSchema),
|
|
156
|
+
version: zod_1.z.number().int(),
|
|
157
|
+
});
|
|
158
|
+
/** A suggestion as the client who sent it sees it. */
|
|
159
|
+
exports.clientFoodSuggestionResponseSchema = zod_1.z.object({
|
|
160
|
+
id: zod_1.z.uuid(),
|
|
161
|
+
status: zod_1.z.enum(index_js_1.FOOD_SUGGESTION_STATUSES),
|
|
162
|
+
submitted: exports.submittedFoodSchema,
|
|
163
|
+
submittedAt: date_time_schema_js_1.utcTimestampSchema,
|
|
164
|
+
resolution: zod_1.z.enum(index_js_1.FOOD_SUGGESTION_RESOLUTIONS).nullable(),
|
|
165
|
+
reviewed: exports.reviewedPortionResponseSchema.nullable(),
|
|
166
|
+
/** The coach's note, or the reason for a rejection. */
|
|
167
|
+
reviewNote: zod_1.z.string().nullable(),
|
|
168
|
+
reviewedAt: date_time_schema_js_1.utcTimestampSchema.nullable(),
|
|
169
|
+
food: zod_1.z.object({ id: zod_1.z.uuid(), name: zod_1.z.string() }).nullable(),
|
|
170
|
+
});
|