Initial commit of Vellum, an event photo product for guest uploads, host moderation, and original-quality galleries.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@album/contracts",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --noEmit",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.25.67"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const eventStatusSchema = z.enum(["draft", "published", "closed"]);
|
||||
export const groupRoleSchema = z.enum(["owner", "member"]);
|
||||
export const eventRoleSchema = z.enum([
|
||||
"owner",
|
||||
"manager",
|
||||
"moderator",
|
||||
"viewer",
|
||||
]);
|
||||
export const platformRoleSchema = z.enum([
|
||||
"super_admin",
|
||||
"admin",
|
||||
"moderator",
|
||||
"viewer",
|
||||
]);
|
||||
export const eventCreatePolicySchema = z.enum(["open", "invite", "admin_only"]);
|
||||
export const entitlementSourceSchema = z.enum([
|
||||
"signup_default",
|
||||
"invite",
|
||||
"code",
|
||||
"platform",
|
||||
]);
|
||||
export const photoProcessingStatusSchema = z.enum([
|
||||
"uploading",
|
||||
"processing",
|
||||
"ready",
|
||||
"failed",
|
||||
]);
|
||||
export const photoVisibilitySchema = z.enum([
|
||||
"pending",
|
||||
"public",
|
||||
"hidden",
|
||||
"private",
|
||||
"rejected",
|
||||
]);
|
||||
export const photoJobStatusSchema = z.enum([
|
||||
"pending",
|
||||
"processing",
|
||||
"completed",
|
||||
"failed",
|
||||
]);
|
||||
|
||||
export const allowedImageTypes = [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/heic",
|
||||
"image/heif",
|
||||
] as const;
|
||||
|
||||
export const allowedImageTypeSchema = z.enum(allowedImageTypes);
|
||||
export const MAX_PHOTO_BYTES = 25 * 1024 * 1024;
|
||||
|
||||
export const eventSlugSchema = z
|
||||
.string()
|
||||
.min(3)
|
||||
.max(64)
|
||||
.regex(
|
||||
/^[a-z0-9]+(?:-[a-z0-9]+)*$/,
|
||||
"Use lowercase letters, numbers, and hyphens",
|
||||
);
|
||||
|
||||
export const contributorNameSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.max(80)
|
||||
.optional()
|
||||
.transform((value) => {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
});
|
||||
|
||||
export const guestEmailSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.email()
|
||||
.max(200)
|
||||
.optional()
|
||||
.or(z.literal(""));
|
||||
|
||||
export const guestNoteSchema = z.string().trim().max(2000).optional();
|
||||
|
||||
export const createEventInputSchema = z.object({
|
||||
groupId: z.string().uuid().optional(),
|
||||
title: z.string().trim().min(1).max(120),
|
||||
slug: eventSlugSchema.optional(),
|
||||
description: z.string().trim().max(2000).optional(),
|
||||
startsAt: z.coerce.date().optional().nullable(),
|
||||
endsAt: z.coerce.date().optional().nullable(),
|
||||
inviteCode: z.string().trim().max(80).optional(),
|
||||
});
|
||||
|
||||
export const updateEventInputSchema = z.object({
|
||||
eventId: z.string().uuid(),
|
||||
title: z.string().trim().min(1).max(120).optional(),
|
||||
slug: eventSlugSchema.optional(),
|
||||
description: z.string().trim().max(2000).nullable().optional(),
|
||||
startsAt: z.coerce.date().nullable().optional(),
|
||||
endsAt: z.coerce.date().nullable().optional(),
|
||||
status: eventStatusSchema.optional(),
|
||||
listed: z.boolean().optional(),
|
||||
uploadEnabled: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const ensureGuestInputSchema = z.object({
|
||||
eventSlug: eventSlugSchema,
|
||||
displayName: contributorNameSchema,
|
||||
email: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(200)
|
||||
.optional()
|
||||
.transform((value) => {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
})
|
||||
.pipe(z.string().email().optional()),
|
||||
notifyWhenReady: z.boolean().optional(),
|
||||
note: guestNoteSchema,
|
||||
});
|
||||
|
||||
export const startSubmissionInputSchema = z.object({
|
||||
eventSlug: eventSlugSchema,
|
||||
});
|
||||
|
||||
export const createPhotoInputSchema = z.object({
|
||||
eventSlug: eventSlugSchema,
|
||||
submissionId: z.string().uuid(),
|
||||
contentType: allowedImageTypeSchema,
|
||||
fileName: z.string().min(1).max(255),
|
||||
byteSize: z.number().int().positive().max(MAX_PHOTO_BYTES),
|
||||
});
|
||||
|
||||
export const completePhotoInputSchema = z.object({
|
||||
photoId: z.string().uuid(),
|
||||
});
|
||||
|
||||
export const moderatePhotoInputSchema = z.object({
|
||||
photoId: z.string().uuid(),
|
||||
visibility: photoVisibilitySchema,
|
||||
});
|
||||
|
||||
export const moderateSubmissionInputSchema = z.object({
|
||||
submissionId: z.string().uuid(),
|
||||
visibility: photoVisibilitySchema,
|
||||
});
|
||||
|
||||
export const updateDeploymentSettingsInputSchema = z.object({
|
||||
openSignup: z.boolean().optional(),
|
||||
eventCreatePolicy: eventCreatePolicySchema.optional(),
|
||||
defaultEventLimit: z.number().int().min(0).max(1000).optional(),
|
||||
});
|
||||
|
||||
export const createEmailInviteInputSchema = z.object({
|
||||
email: z.string().trim().email().max(200),
|
||||
groupId: z.string().uuid().optional(),
|
||||
eventId: z.string().uuid().optional(),
|
||||
groupRole: groupRoleSchema.optional(),
|
||||
eventRole: eventRoleSchema.optional(),
|
||||
grantUnlimitedEvents: z.boolean().optional(),
|
||||
grantEventLimit: z.number().int().min(1).max(1000).optional(),
|
||||
grantComplimentary: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const createInviteCodeInputSchema = z.object({
|
||||
reusable: z.boolean().optional(),
|
||||
maxUses: z.number().int().min(1).max(10000).optional(),
|
||||
groupId: z.string().uuid().optional(),
|
||||
eventId: z.string().uuid().optional(),
|
||||
groupRole: groupRoleSchema.optional(),
|
||||
eventRole: eventRoleSchema.optional(),
|
||||
grantUnlimitedEvents: z.boolean().optional(),
|
||||
grantEventLimit: z.number().int().min(1).max(1000).optional(),
|
||||
grantComplimentary: z.boolean().optional(),
|
||||
expiresAt: z.coerce.date().optional().nullable(),
|
||||
});
|
||||
|
||||
export const redeemInviteInputSchema = z.object({
|
||||
token: z.string().trim().min(4).max(120),
|
||||
});
|
||||
|
||||
export const grantEntitlementInputSchema = z.object({
|
||||
groupId: z.string().uuid(),
|
||||
eventLimit: z.number().int().min(1).max(10000).nullable().optional(),
|
||||
expiresAt: z.coerce.date().optional().nullable(),
|
||||
complimentary: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const setPlatformRoleInputSchema = z.object({
|
||||
userId: z.string().min(1),
|
||||
role: platformRoleSchema.nullable(),
|
||||
});
|
||||
|
||||
export const setEventMemberInputSchema = z.object({
|
||||
eventId: z.string().uuid(),
|
||||
userId: z.string().min(1).optional(),
|
||||
email: z.string().trim().email().optional(),
|
||||
role: eventRoleSchema,
|
||||
});
|
||||
|
||||
export const setGroupMemberInputSchema = z.object({
|
||||
groupId: z.string().uuid(),
|
||||
userId: z.string().min(1).optional(),
|
||||
email: z.string().trim().email().optional(),
|
||||
role: groupRoleSchema,
|
||||
});
|
||||
|
||||
export type EventStatus = z.infer<typeof eventStatusSchema>;
|
||||
export type GroupRole = z.infer<typeof groupRoleSchema>;
|
||||
export type EventRole = z.infer<typeof eventRoleSchema>;
|
||||
export type PlatformRole = z.infer<typeof platformRoleSchema>;
|
||||
export type EventCreatePolicy = z.infer<typeof eventCreatePolicySchema>;
|
||||
export type PhotoProcessingStatus = z.infer<typeof photoProcessingStatusSchema>;
|
||||
export type PhotoVisibility = z.infer<typeof photoVisibilitySchema>;
|
||||
export type AllowedImageType = z.infer<typeof allowedImageTypeSchema>;
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user