mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 05:56:53 +00:00
1e2a5e3862
The URL check used `new URL(...)`, and this package compiles against lib ES2022 alone so that it carries neither DOM nor Node globals, which is why the shared typecheck refused it. `z.url()` plus a protocol test does the same job with names the package actually has; z.url() on its own would take mailto: and javascript:. The three collab:link events also needed their fixtures and their place in the pinned inventory count, the guard that stops the fixture map drifting from the registry.
84 lines
3.6 KiB
TypeScript
84 lines
3.6 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
/**
|
|
* Collab API contract — single source of truth for the /api/trips/:tripId/collab
|
|
* endpoints (shared notes + file attachments, decision polls, group chat with
|
|
* reactions, link previews).
|
|
*
|
|
* Trip-scoped; mutations use 'collab_edit' (file uploads use 'file_upload'). The
|
|
* legacy route (server/src/routes/collab.ts) wraps collabService and broadcasts
|
|
* over WebSocket + fires chat/note notifications. Rows are wide and kept open;
|
|
* the request schemas + the bespoke 400/403/404 controller messages pin the rest.
|
|
*/
|
|
|
|
export const collabNoteCreateRequestSchema = z.object({
|
|
title: z.string().min(1),
|
|
// The desktop notes form clears optional fields by sending explicit null
|
|
// (the service coerces falsy to its defaults), so they are all nullable.
|
|
content: z.string().nullable().optional(),
|
|
category: z.string().nullable().optional(),
|
|
color: z.string().nullable().optional(),
|
|
website: z.string().nullable().optional(),
|
|
});
|
|
export type CollabNoteCreateRequest = z.infer<typeof collabNoteCreateRequestSchema>;
|
|
|
|
export const collabNoteUpdateRequestSchema = z.object({
|
|
title: z.string().optional(),
|
|
// Same null-clearing protocol as create (the desktop form resends the whole
|
|
// note object, with cleared fields as explicit null).
|
|
content: z.string().nullable().optional(),
|
|
category: z.string().nullable().optional(),
|
|
color: z.string().nullable().optional(),
|
|
pinned: z.union([z.boolean(), z.number()]).optional(),
|
|
website: z.string().nullable().optional(),
|
|
});
|
|
export type CollabNoteUpdateRequest = z.infer<typeof collabNoteUpdateRequestSchema>;
|
|
|
|
export const collabPollCreateRequestSchema = z.object({
|
|
question: z.string().min(1),
|
|
options: z.array(z.unknown()).min(2),
|
|
multiple: z.boolean().optional(),
|
|
multiple_choice: z.boolean().optional(),
|
|
deadline: z.string().optional(),
|
|
});
|
|
export type CollabPollCreateRequest = z.infer<typeof collabPollCreateRequestSchema>;
|
|
|
|
export const collabPollVoteRequestSchema = z.object({
|
|
option_index: z.number(),
|
|
});
|
|
export type CollabPollVoteRequest = z.infer<typeof collabPollVoteRequestSchema>;
|
|
|
|
// `z.url()` rather than `new URL(...)`: this package compiles against lib
|
|
// ES2022 only, deliberately, so it stays free of both DOM and Node globals and
|
|
// `URL` is not one of the names it has. The protocol check stays, because
|
|
// z.url() alone would accept mailto: and javascript:.
|
|
const httpUrl = z.url().refine((value) => /^https?:\/\//i.test(value.trim()), 'A valid http(s) URL is required');
|
|
|
|
export const collabLinkCreateRequestSchema = z.object({
|
|
title: z.string().trim().min(1),
|
|
url: httpUrl,
|
|
pinned: z.union([z.boolean(), z.number()]).optional(),
|
|
});
|
|
export type CollabLinkCreateRequest = z.infer<typeof collabLinkCreateRequestSchema>;
|
|
|
|
export const collabLinkUpdateRequestSchema = z.object({
|
|
title: z.string().trim().min(1).optional(),
|
|
url: httpUrl.optional(),
|
|
pinned: z.union([z.boolean(), z.number()]).optional(),
|
|
});
|
|
export type CollabLinkUpdateRequest = z.infer<typeof collabLinkUpdateRequestSchema>;
|
|
|
|
// text may be empty when the chat message is image-only (multipart). The
|
|
// controller still rejects a request with neither text nor files.
|
|
export const collabMessageCreateRequestSchema = z.object({
|
|
text: z.string().max(5000).optional(),
|
|
// Multipart fields arrive as strings; JSON chat still sends a number/null.
|
|
reply_to: z.union([z.number(), z.string(), z.null()]).optional(),
|
|
});
|
|
export type CollabMessageCreateRequest = z.infer<typeof collabMessageCreateRequestSchema>;
|
|
|
|
export const collabReactionRequestSchema = z.object({
|
|
emoji: z.string().min(1),
|
|
});
|
|
export type CollabReactionRequest = z.infer<typeof collabReactionRequestSchema>;
|