mirror of
https://github.com/versia-pub/api.git
synced 2025-12-06 16:38:20 +01:00
refactor(federation): 👽 Update all schemas to Working Draft 4
This commit is contained in:
parent
b462718dc0
commit
4a470f5f3c
|
|
@ -9,40 +9,42 @@ const validUser = {
|
||||||
uri: "https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a",
|
uri: "https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a",
|
||||||
bio: {
|
bio: {
|
||||||
"text/html": {
|
"text/html": {
|
||||||
content: "<p>Hey</p>\n",
|
content: "<p>Hey</p>",
|
||||||
|
remote: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
inbox: "https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/inbox",
|
||||||
|
indexable: false,
|
||||||
created_at: "2024-04-07T11:48:29.623Z",
|
created_at: "2024-04-07T11:48:29.623Z",
|
||||||
dislikes:
|
collections: {
|
||||||
"https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/dislikes",
|
|
||||||
featured:
|
featured:
|
||||||
"https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/featured",
|
"https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/featured",
|
||||||
likes: "https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/likes",
|
|
||||||
followers:
|
followers:
|
||||||
"https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/followers",
|
"https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/followers",
|
||||||
following:
|
following:
|
||||||
"https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/following",
|
"https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/following",
|
||||||
inbox: "https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/inbox",
|
|
||||||
outbox: "https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/outbox",
|
outbox: "https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/outbox",
|
||||||
indexable: false,
|
},
|
||||||
username: "jessew",
|
username: "jessew",
|
||||||
display_name: "Jesse Wierzbinski",
|
display_name: "Jesse Wierzbinski",
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
key: { "text/html": { content: "<p>Identity</p>\n" } },
|
key: { "text/html": { content: "<p>Identity</p>", remote: false } },
|
||||||
value: {
|
value: {
|
||||||
"text/html": {
|
"text/html": {
|
||||||
content:
|
content:
|
||||||
'<p><a href="https://keyoxide.org/aspe:keyoxide.org:NKLLPWPV7P35NEU7JP4K4ID4CA">https://keyoxide.org/aspe:keyoxide.org:NKLLPWPV7P35NEU7JP4K4ID4CA</a></p>\n',
|
'<p><a href="https://keyoxide.org/aspe:keyoxide.org:NKLLPWPV7P35NEU7JP4K4ID4CA">https://keyoxide.org/aspe:keyoxide.org:NKLLPWPV7P35NEU7JP4K4ID4CA</a></p>',
|
||||||
|
remote: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
public_key: {
|
public_key: {
|
||||||
actor: "https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a",
|
actor: "https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a",
|
||||||
public_key: "XXXXXXXX",
|
key: "XXXXXXXX",
|
||||||
|
algorithm: "ed25519",
|
||||||
},
|
},
|
||||||
extensions: { "org.lysand:custom_emojis": { emojis: [] } },
|
extensions: { "pub.versia:custom_emojis": { emojis: [] } },
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("RequestParserHandler", () => {
|
describe("RequestParserHandler", () => {
|
||||||
|
|
|
||||||
|
|
@ -5,23 +5,17 @@
|
||||||
|
|
||||||
import type { z } from "zod";
|
import type { z } from "zod";
|
||||||
import type {
|
import type {
|
||||||
ActionSchema,
|
DeleteSchema,
|
||||||
ActorPublicKeyDataSchema,
|
|
||||||
DislikeSchema,
|
|
||||||
EntitySchema,
|
EntitySchema,
|
||||||
ExtensionSchema,
|
|
||||||
FollowAcceptSchema,
|
FollowAcceptSchema,
|
||||||
FollowRejectSchema,
|
FollowRejectSchema,
|
||||||
FollowSchema,
|
FollowSchema,
|
||||||
LikeSchema,
|
GroupSchema,
|
||||||
|
InstanceMetadataSchema,
|
||||||
NoteSchema,
|
NoteSchema,
|
||||||
PatchSchema,
|
PublicKeyDataSchema,
|
||||||
PublicationSchema,
|
UnfollowSchema,
|
||||||
ReportSchema,
|
|
||||||
ServerMetadataSchema,
|
|
||||||
UndoSchema,
|
|
||||||
UserSchema,
|
UserSchema,
|
||||||
VisibilitySchema,
|
|
||||||
} from "./schemas/base";
|
} from "./schemas/base";
|
||||||
import type { ContentFormatSchema } from "./schemas/content_format";
|
import type { ContentFormatSchema } from "./schemas/content_format";
|
||||||
import type { ExtensionPropertySchema } from "./schemas/extensions";
|
import type { ExtensionPropertySchema } from "./schemas/extensions";
|
||||||
|
|
@ -34,23 +28,17 @@ type AnyZod = z.ZodType<any, any, any>;
|
||||||
type InferType<T extends AnyZod> = z.infer<T>;
|
type InferType<T extends AnyZod> = z.infer<T>;
|
||||||
|
|
||||||
export type Note = InferType<typeof NoteSchema>;
|
export type Note = InferType<typeof NoteSchema>;
|
||||||
export type Patch = InferType<typeof PatchSchema>;
|
export type ActorPublicKeyData = InferType<typeof PublicKeyDataSchema>;
|
||||||
export type ActorPublicKeyData = InferType<typeof ActorPublicKeyDataSchema>;
|
|
||||||
export type ExtensionProperty = InferType<typeof ExtensionPropertySchema>;
|
export type ExtensionProperty = InferType<typeof ExtensionPropertySchema>;
|
||||||
export type VanityExtension = InferType<typeof VanityExtensionSchema>;
|
export type VanityExtension = InferType<typeof VanityExtensionSchema>;
|
||||||
export type User = InferType<typeof UserSchema>;
|
export type User = InferType<typeof UserSchema>;
|
||||||
export type Action = InferType<typeof ActionSchema>;
|
|
||||||
export type Like = InferType<typeof LikeSchema>;
|
|
||||||
export type Undo = InferType<typeof UndoSchema>;
|
|
||||||
export type Dislike = InferType<typeof DislikeSchema>;
|
|
||||||
export type Follow = InferType<typeof FollowSchema>;
|
export type Follow = InferType<typeof FollowSchema>;
|
||||||
export type FollowAccept = InferType<typeof FollowAcceptSchema>;
|
export type FollowAccept = InferType<typeof FollowAcceptSchema>;
|
||||||
export type FollowReject = InferType<typeof FollowRejectSchema>;
|
export type FollowReject = InferType<typeof FollowRejectSchema>;
|
||||||
export type Extension = InferType<typeof ExtensionSchema>;
|
|
||||||
export type Report = InferType<typeof ReportSchema>;
|
|
||||||
export type ServerMetadata = InferType<typeof ServerMetadataSchema>;
|
|
||||||
export type ContentFormat = InferType<typeof ContentFormatSchema>;
|
export type ContentFormat = InferType<typeof ContentFormatSchema>;
|
||||||
export type CustomEmojiExtension = InferType<typeof CustomEmojiExtensionSchema>;
|
export type CustomEmojiExtension = InferType<typeof CustomEmojiExtensionSchema>;
|
||||||
export type Visibility = InferType<typeof VisibilitySchema>;
|
|
||||||
export type Publication = InferType<typeof PublicationSchema>;
|
|
||||||
export type Entity = InferType<typeof EntitySchema>;
|
export type Entity = InferType<typeof EntitySchema>;
|
||||||
|
export type Delete = InferType<typeof DeleteSchema>;
|
||||||
|
export type Group = InferType<typeof GroupSchema>;
|
||||||
|
export type InstanceMetadata = InferType<typeof InstanceMetadataSchema>;
|
||||||
|
export type Unfollow = InferType<typeof UnfollowSchema>;
|
||||||
|
|
|
||||||
|
|
@ -1,179 +1,245 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ContentFormatSchema } from "./content_format";
|
import {
|
||||||
|
ContentFormatSchema,
|
||||||
|
ImageOnlyContentFormatSchema,
|
||||||
|
TextOnlyContentFormatSchema,
|
||||||
|
} from "./content_format";
|
||||||
import { ExtensionPropertySchema } from "./extensions";
|
import { ExtensionPropertySchema } from "./extensions";
|
||||||
import { VanityExtensionSchema } from "./extensions/vanity";
|
import { VanityExtensionSchema } from "./extensions/vanity";
|
||||||
import { extensionTypeRegex } from "./regex";
|
import { extensionRegex, isISOString, semverRegex } from "./regex";
|
||||||
|
|
||||||
export const EntitySchema = z.object({
|
export const EntitySchema = z
|
||||||
id: z.string().uuid(),
|
.object({
|
||||||
created_at: z.string(),
|
id: z.string().max(512),
|
||||||
|
created_at: z
|
||||||
|
.string()
|
||||||
|
.refine((v) => isISOString(v), "must be a valid ISO8601 datetime"),
|
||||||
uri: z.string().url(),
|
uri: z.string().url(),
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
extensions: ExtensionPropertySchema.optional().nullable().nullable(),
|
extensions: ExtensionPropertySchema.optional().nullable(),
|
||||||
});
|
|
||||||
|
|
||||||
export const VisibilitySchema = z.enum([
|
|
||||||
"public",
|
|
||||||
"unlisted",
|
|
||||||
"private",
|
|
||||||
"direct",
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const PublicationSchema = EntitySchema.extend({
|
|
||||||
type: z.enum(["Note", "Patch"]),
|
|
||||||
author: z.string().url(),
|
|
||||||
content: ContentFormatSchema.optional().nullable(),
|
|
||||||
attachments: z.array(ContentFormatSchema).optional().nullable(),
|
|
||||||
replies_to: z.string().url().optional().nullable(),
|
|
||||||
quotes: z.string().url().optional().nullable(),
|
|
||||||
mentions: z.array(z.string().url()).optional().nullable(),
|
|
||||||
subject: z.string().optional().nullable(),
|
|
||||||
is_sensitive: z.boolean().optional().nullable(),
|
|
||||||
visibility: VisibilitySchema,
|
|
||||||
extensions: ExtensionPropertySchema.extend({
|
|
||||||
"org.lysand:reactions": z
|
|
||||||
.object({
|
|
||||||
reactions: z.string(),
|
|
||||||
})
|
})
|
||||||
.optional()
|
.strict();
|
||||||
.nullable(),
|
|
||||||
"org.lysand:polls": z
|
|
||||||
.object({
|
|
||||||
poll: z.object({
|
|
||||||
options: z.array(ContentFormatSchema),
|
|
||||||
votes: z.array(z.number().int().nonnegative()),
|
|
||||||
multiple_choice: z.boolean().optional().nullable(),
|
|
||||||
expires_at: z.string(),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.nullable(),
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.nullable(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const NoteSchema = PublicationSchema.extend({
|
export const NoteSchema = EntitySchema.extend({
|
||||||
type: z.literal("Note"),
|
type: z.literal("Note"),
|
||||||
});
|
attachments: z.array(ContentFormatSchema).optional().nullable(),
|
||||||
|
author: z.string().url(),
|
||||||
export const PatchSchema = PublicationSchema.extend({
|
category: z
|
||||||
type: z.literal("Patch"),
|
.enum([
|
||||||
patched_id: z.string().uuid(),
|
"microblog",
|
||||||
patched_at: z.string(),
|
"forum",
|
||||||
});
|
"blog",
|
||||||
|
"image",
|
||||||
export const ActorPublicKeyDataSchema = z.object({
|
"video",
|
||||||
public_key: z.string(),
|
"audio",
|
||||||
actor: z.string().url(),
|
"messaging",
|
||||||
});
|
])
|
||||||
|
.optional()
|
||||||
export const UserSchema = EntitySchema.extend({
|
.nullable(),
|
||||||
type: z.literal("User"),
|
content: TextOnlyContentFormatSchema.optional().nullable(),
|
||||||
display_name: z.string().optional().nullable(),
|
device: z
|
||||||
username: z.string(),
|
.object({
|
||||||
avatar: ContentFormatSchema.optional().nullable(),
|
name: z.string(),
|
||||||
header: ContentFormatSchema.optional().nullable(),
|
version: z.string().optional().nullable(),
|
||||||
indexable: z.boolean(),
|
url: z.string().url().optional().nullable(),
|
||||||
public_key: ActorPublicKeyDataSchema,
|
})
|
||||||
bio: ContentFormatSchema.optional().nullable(),
|
.strict()
|
||||||
fields: z
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
group: z
|
||||||
|
.string()
|
||||||
|
.url()
|
||||||
|
.or(z.enum(["public", "followers"]))
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
is_sensitive: z.boolean().optional().nullable(),
|
||||||
|
mentions: z.array(z.string().url()).optional().nullable(),
|
||||||
|
previews: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z
|
||||||
key: ContentFormatSchema,
|
.object({
|
||||||
value: ContentFormatSchema,
|
link: z.string().url(),
|
||||||
}),
|
title: z.string(),
|
||||||
|
description: z.string().optional().nullable(),
|
||||||
|
image: z.string().url().optional().nullable(),
|
||||||
|
icon: z.string().url().optional().nullable(),
|
||||||
|
})
|
||||||
|
.strict(),
|
||||||
)
|
)
|
||||||
.optional()
|
.optional()
|
||||||
.nullable(),
|
.nullable(),
|
||||||
featured: z.string().url(),
|
quotes: z.string().url().optional().nullable(),
|
||||||
followers: z.string().url(),
|
replies_to: z.string().url().optional().nullable(),
|
||||||
following: z.string().url(),
|
subject: z.string().optional().nullable(),
|
||||||
likes: z.string().url(),
|
|
||||||
dislikes: z.string().url(),
|
|
||||||
inbox: z.string().url(),
|
|
||||||
outbox: z.string().url(),
|
|
||||||
extensions: ExtensionPropertySchema.extend({
|
extensions: ExtensionPropertySchema.extend({
|
||||||
"org.lysand:vanity": VanityExtensionSchema.optional().nullable(),
|
"pub.versia:reactions": z
|
||||||
|
.object({
|
||||||
|
reactions: z.string().url(),
|
||||||
|
})
|
||||||
|
.strict()
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
"pub.versia:polls": z
|
||||||
|
.object({
|
||||||
|
options: z.array(TextOnlyContentFormatSchema),
|
||||||
|
votes: z.array(
|
||||||
|
z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.nonnegative()
|
||||||
|
.max(2 ** 64 - 1),
|
||||||
|
),
|
||||||
|
multiple_choice: z.boolean(),
|
||||||
|
expires_at: z
|
||||||
|
.string()
|
||||||
|
.refine(
|
||||||
|
(v) => isISOString(v),
|
||||||
|
"must be a valid ISO8601 datetime",
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.strict()
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.nullable(),
|
.nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ActionSchema = EntitySchema.extend({
|
export const PublicKeyDataSchema = z
|
||||||
type: z.union([
|
.object({
|
||||||
z.literal("Like"),
|
key: z.string().min(1),
|
||||||
z.literal("Dislike"),
|
actor: z.string().url(),
|
||||||
z.literal("Follow"),
|
algorithm: z.literal("ed25519"),
|
||||||
z.literal("FollowAccept"),
|
})
|
||||||
z.literal("FollowReject"),
|
.strict();
|
||||||
z.literal("Announce"),
|
|
||||||
z.literal("Undo"),
|
export const UserSchema = EntitySchema.extend({
|
||||||
]),
|
type: z.literal("User"),
|
||||||
|
avatar: ImageOnlyContentFormatSchema.optional().nullable(),
|
||||||
|
bio: TextOnlyContentFormatSchema.optional().nullable(),
|
||||||
|
display_name: z.string().optional().nullable(),
|
||||||
|
fields: z
|
||||||
|
.array(
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
key: TextOnlyContentFormatSchema,
|
||||||
|
value: TextOnlyContentFormatSchema,
|
||||||
|
})
|
||||||
|
.strict(),
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
username: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.regex(
|
||||||
|
/^[a-z0-9_-]+$/,
|
||||||
|
"must be lowercase, alphanumeric, and may contain _ or -",
|
||||||
|
),
|
||||||
|
header: ImageOnlyContentFormatSchema.optional().nullable(),
|
||||||
|
public_key: PublicKeyDataSchema,
|
||||||
|
manually_approves_followers: z.boolean().optional().nullable(),
|
||||||
|
indexable: z.boolean().optional().nullable(),
|
||||||
|
inbox: z.string().url(),
|
||||||
|
collections: z
|
||||||
|
.object({
|
||||||
|
featured: z.string().url(),
|
||||||
|
followers: z.string().url(),
|
||||||
|
following: z.string().url(),
|
||||||
|
outbox: z.string().url(),
|
||||||
|
"pub.versia:likes/Likes": z.string().url().optional().nullable(),
|
||||||
|
"pub.versia:likes/Dislikes": z.string().url().optional().nullable(),
|
||||||
|
})
|
||||||
|
.catchall(z.string().url()),
|
||||||
|
extensions: ExtensionPropertySchema.extend({
|
||||||
|
"pub.versia:vanity": VanityExtensionSchema.optional().nullable(),
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DeleteSchema = EntitySchema.extend({
|
||||||
|
uri: z.null().optional(),
|
||||||
|
type: z.literal("Delete"),
|
||||||
author: z.string().url(),
|
author: z.string().url(),
|
||||||
|
deleted_type: z.string(),
|
||||||
|
target: z.string().url(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const LikeSchema = ActionSchema.extend({
|
export const FollowSchema = EntitySchema.extend({
|
||||||
type: z.literal("Like"),
|
|
||||||
object: z.string().url(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const UndoSchema = ActionSchema.extend({
|
|
||||||
type: z.literal("Undo"),
|
|
||||||
object: z.string().url(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const DislikeSchema = ActionSchema.extend({
|
|
||||||
type: z.literal("Dislike"),
|
|
||||||
object: z.string().url(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const FollowSchema = ActionSchema.extend({
|
|
||||||
type: z.literal("Follow"),
|
type: z.literal("Follow"),
|
||||||
|
uri: z.null().optional(),
|
||||||
|
author: z.string().url(),
|
||||||
followee: z.string().url(),
|
followee: z.string().url(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const FollowAcceptSchema = ActionSchema.extend({
|
export const FollowAcceptSchema = EntitySchema.extend({
|
||||||
type: z.literal("FollowAccept"),
|
type: z.literal("FollowAccept"),
|
||||||
|
uri: z.null().optional(),
|
||||||
|
author: z.string().url(),
|
||||||
follower: z.string().url(),
|
follower: z.string().url(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const FollowRejectSchema = ActionSchema.extend({
|
export const FollowRejectSchema = EntitySchema.extend({
|
||||||
type: z.literal("FollowReject"),
|
type: z.literal("FollowReject"),
|
||||||
|
uri: z.null().optional(),
|
||||||
|
author: z.string().url(),
|
||||||
follower: z.string().url(),
|
follower: z.string().url(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ExtensionSchema = EntitySchema.extend({
|
export const UnfollowSchema = EntitySchema.extend({
|
||||||
type: z.literal("Extension"),
|
type: z.literal("Unfollow"),
|
||||||
extension_type: z
|
uri: z.null().optional(),
|
||||||
.string()
|
author: z.string().url(),
|
||||||
.regex(
|
followee: z.string().url(),
|
||||||
extensionTypeRegex,
|
});
|
||||||
"extension_type must be in the format 'namespaced_url:extension_name/ExtensionType', e.g. 'org.lysand:reactions/Reaction'. Notably, only the type can have uppercase letters.",
|
|
||||||
|
export const GroupSchema = EntitySchema.extend({
|
||||||
|
type: z.literal("Group"),
|
||||||
|
name: TextOnlyContentFormatSchema.optional().nullable(),
|
||||||
|
description: TextOnlyContentFormatSchema.optional().nullable(),
|
||||||
|
members: z.string().url(),
|
||||||
|
notes: z.string().url().optional().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const InstanceMetadataSchema = EntitySchema.extend({
|
||||||
|
type: z.literal("InstanceMetadata"),
|
||||||
|
id: z.null().optional(),
|
||||||
|
uri: z.null().optional(),
|
||||||
|
name: z.string().min(1),
|
||||||
|
software: z
|
||||||
|
.object({
|
||||||
|
name: z.string().min(1),
|
||||||
|
version: z.string().min(1),
|
||||||
|
})
|
||||||
|
.strict(),
|
||||||
|
compatibility: z
|
||||||
|
.object({
|
||||||
|
versions: z.array(
|
||||||
|
z.string().regex(semverRegex, "must be a valid SemVer version"),
|
||||||
),
|
),
|
||||||
});
|
extensions: z.array(
|
||||||
|
z
|
||||||
export const ReportSchema = ExtensionSchema.extend({
|
.string()
|
||||||
extension_type: z.literal("org.lysand:reports/Report"),
|
.min(1)
|
||||||
objects: z.array(z.string().url()),
|
.regex(
|
||||||
reason: z.string(),
|
extensionRegex,
|
||||||
comment: z.string().optional().nullable(),
|
"must be in the format 'namespaced_url:extension_name', e.g. 'pub.versia:reactions'",
|
||||||
});
|
),
|
||||||
|
),
|
||||||
export const ServerMetadataSchema = EntitySchema.omit({
|
})
|
||||||
created_at: true,
|
.strict(),
|
||||||
id: true,
|
description: TextOnlyContentFormatSchema.optional().nullable(),
|
||||||
uri: true,
|
host: z.string(),
|
||||||
}).extend({
|
shared_inbox: z.string().url().optional().nullable(),
|
||||||
type: z.literal("ServerMetadata"),
|
public_key: z
|
||||||
name: z.string(),
|
.object({
|
||||||
version: z.string(),
|
key: z.string().min(1),
|
||||||
description: z.string().optional().nullable(),
|
algorithm: z.literal("ed25519"),
|
||||||
website: z.string().optional().nullable(),
|
})
|
||||||
moderators: z.array(z.string()).optional().nullable(),
|
.strict(),
|
||||||
admins: z.array(z.string()).optional().nullable(),
|
moderators: z.string().url().optional().nullable(),
|
||||||
logo: ContentFormatSchema.optional().nullable(),
|
admins: z.string().url().optional().nullable(),
|
||||||
banner: ContentFormatSchema.optional().nullable(),
|
logo: ImageOnlyContentFormatSchema.optional().nullable(),
|
||||||
supported_extensions: z.array(z.string()),
|
banner: ImageOnlyContentFormatSchema.optional().nullable(),
|
||||||
extensions: z.record(z.string(), z.any()).optional().nullable(),
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,114 @@
|
||||||
import { types } from "mime-types";
|
import { types } from "mime-types";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const ContentFormatSchema = z.record(
|
const hashes = {
|
||||||
z.enum(Object.values(types) as [string, ...string[]]),
|
sha256: 64,
|
||||||
z.object({
|
sha512: 128,
|
||||||
|
"sha3-256": 64,
|
||||||
|
"sha3-512": 128,
|
||||||
|
"blake2b-256": 64,
|
||||||
|
"blake2b-512": 128,
|
||||||
|
"blake3-256": 64,
|
||||||
|
"blake3-512": 128,
|
||||||
|
md5: 32,
|
||||||
|
sha1: 40,
|
||||||
|
sha224: 56,
|
||||||
|
sha384: 96,
|
||||||
|
"sha3-224": 56,
|
||||||
|
"sha3-384": 96,
|
||||||
|
"blake2s-256": 64,
|
||||||
|
"blake2s-512": 128,
|
||||||
|
"blake3-224": 56,
|
||||||
|
"blake3-384": 96,
|
||||||
|
};
|
||||||
|
|
||||||
|
const contentFormatFromAllowedMimes = (allowedMimes: [string, ...string[]]) =>
|
||||||
|
z.record(
|
||||||
|
z.enum(allowedMimes),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
content: z.string(),
|
content: z.string(),
|
||||||
|
remote: z.boolean(),
|
||||||
description: z.string().optional().nullable(),
|
description: z.string().optional().nullable(),
|
||||||
size: z.number().int().nonnegative().optional().nullable(),
|
size: z
|
||||||
hash: z.record(z.string(), z.string()).optional().nullable(),
|
.number()
|
||||||
blurhash: z.string().optional().nullable(),
|
.int()
|
||||||
fps: z.number().int().nonnegative().optional().nullable(),
|
.nonnegative()
|
||||||
width: z.number().int().nonnegative().optional().nullable(),
|
.max(2 ** 64 - 1)
|
||||||
height: z.number().int().nonnegative().optional().nullable(),
|
.optional()
|
||||||
duration: z.number().nonnegative().optional().nullable(),
|
.nullable(),
|
||||||
}),
|
hash: z
|
||||||
|
.object(
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(hashes).map(([k, v]) => [
|
||||||
|
k,
|
||||||
|
z.string().length(v).optional().nullable(),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.strict()
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
thumbhash: z.string().optional().nullable(),
|
||||||
|
fps: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.nonnegative()
|
||||||
|
.max(2 ** 64 - 1)
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
width: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.nonnegative()
|
||||||
|
.max(2 ** 64 - 1)
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
height: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.nonnegative()
|
||||||
|
.max(2 ** 64 - 1)
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
duration: z
|
||||||
|
.number()
|
||||||
|
.nonnegative()
|
||||||
|
.max(2 ** 16 - 1)
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
})
|
||||||
|
.strict()
|
||||||
|
.refine(
|
||||||
|
(v) =>
|
||||||
|
v.remote
|
||||||
|
? z.string().url().safeParse(v.content).success
|
||||||
|
: true,
|
||||||
|
"if remote is true, content must be a valid URL",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ContentFormatSchema = contentFormatFromAllowedMimes(
|
||||||
|
Object.values(types) as [string, ...string[]],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ImageOnlyContentFormatSchema = contentFormatFromAllowedMimes(
|
||||||
|
Object.values(types).filter((v) => v.startsWith("image/")) as [
|
||||||
|
string,
|
||||||
|
...string[],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const TextOnlyContentFormatSchema = contentFormatFromAllowedMimes(
|
||||||
|
Object.values(types).filter((v) => v.startsWith("text/")) as [
|
||||||
|
string,
|
||||||
|
...string[],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const AudioOnlyContentFormatSchema = contentFormatFromAllowedMimes(
|
||||||
|
Object.values(types).filter((v) => v.startsWith("audio/")) as [
|
||||||
|
string,
|
||||||
|
...string[],
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { CustomEmojiExtensionSchema } from "./extensions/custom_emojis";
|
import { CustomEmojiExtensionSchema } from "./extensions/custom_emojis";
|
||||||
|
|
||||||
export const ExtensionPropertySchema = z.object({
|
export const ExtensionPropertySchema = z
|
||||||
"org.lysand:custom_emojis":
|
.object({
|
||||||
|
"pub.versia:custom_emojis":
|
||||||
CustomEmojiExtensionSchema.optional().nullable(),
|
CustomEmojiExtensionSchema.optional().nullable(),
|
||||||
});
|
})
|
||||||
|
.catchall(z.any());
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* @see https://versia.pub/extensions/custom-emojis
|
* @see https://versia.pub/extensions/custom-emojis
|
||||||
*/
|
*/
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ContentFormatSchema } from "../content_format";
|
import { ImageOnlyContentFormatSchema } from "../content_format";
|
||||||
import { emojiRegex } from "../regex";
|
import { emojiRegex } from "../regex";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -15,14 +15,14 @@ import { emojiRegex } from "../regex";
|
||||||
* {
|
* {
|
||||||
* // ...
|
* // ...
|
||||||
* "extensions": {
|
* "extensions": {
|
||||||
* "org.lysand:custom_emojis": {
|
* "pub.versia:custom_emojis": {
|
||||||
* "emojis": [
|
* "emojis": [
|
||||||
* {
|
* {
|
||||||
* "name": "happy_face",
|
* "name": ":happy_face:",
|
||||||
* "url": {
|
* "url": {
|
||||||
* "image/png": {
|
* "image/png": {
|
||||||
* "content": "https://cdn.example.com/emojis/happy_face.png",
|
* "content": "https://cdn.example.com/emojis/happy_face.png",
|
||||||
* "content_type": "image/png"
|
* "remote": true
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* },
|
* },
|
||||||
|
|
@ -35,16 +35,18 @@ import { emojiRegex } from "../regex";
|
||||||
*/
|
*/
|
||||||
export const CustomEmojiExtensionSchema = z.object({
|
export const CustomEmojiExtensionSchema = z.object({
|
||||||
emojis: z.array(
|
emojis: z.array(
|
||||||
z.object({
|
z
|
||||||
|
.object({
|
||||||
name: z
|
name: z
|
||||||
.string()
|
.string()
|
||||||
.min(1)
|
.min(1)
|
||||||
.max(256)
|
.max(256)
|
||||||
.regex(
|
.regex(
|
||||||
emojiRegex,
|
emojiRegex,
|
||||||
"Emoji name must be alphanumeric, underscores, or dashes.",
|
"Emoji name must be alphanumeric, underscores, or dashes, and surrounded by identifiers",
|
||||||
),
|
),
|
||||||
url: ContentFormatSchema,
|
url: ImageOnlyContentFormatSchema,
|
||||||
}),
|
})
|
||||||
|
.strict(),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
40
federation/schemas/extensions/likes.ts
Normal file
40
federation/schemas/extensions/likes.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
import { EntitySchema } from "../base";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Like entity
|
||||||
|
* @see https://versia.pub/extensions/likes
|
||||||
|
* @example
|
||||||
|
* {
|
||||||
|
* "id": "3e7e4750-afd4-4d99-a256-02f0710a0520",
|
||||||
|
* "type": "pub.versia:likes/Like",
|
||||||
|
* "created_at": "2021-01-01T00:00:00.000Z",
|
||||||
|
* "author": "https://example.com/users/6e0204a2-746c-4972-8602-c4f37fc63bbe",
|
||||||
|
* "uri": "https://example.com/likes/3e7e4750-afd4-4d99-a256-02f0710a0520",
|
||||||
|
* "liked": "https://otherexample.org/notes/fmKZ763jzIU8"
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const LikeSchema = EntitySchema.extend({
|
||||||
|
type: z.literal("pub.versia:likes/Like"),
|
||||||
|
author: z.string().url(),
|
||||||
|
liked: z.string().url(),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Dislike entity
|
||||||
|
* @see https://versia.pub/extensions/likes
|
||||||
|
* @example
|
||||||
|
* {
|
||||||
|
* "id": "3e7e4750-afd4-4d99-a256-02f0710a0520",
|
||||||
|
* "type": "pub.versia:likes/Dislike",
|
||||||
|
* "created_at": "2021-01-01T00:00:00.000Z",
|
||||||
|
* "author": "https://example.com/users/6e0204a2-746c-4972-8602-c4f37fc63bbe",
|
||||||
|
* "uri": "https://example.com/dislikes/3e7e4750-afd4-4d99-a256-02f0710a0520",
|
||||||
|
* "disliked": "https://otherexample.org/notes/fmKZ763jzIU8"
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const DislikeSchema = EntitySchema.extend({
|
||||||
|
type: z.literal("pub.versia:likes/Dislike"),
|
||||||
|
author: z.string().url(),
|
||||||
|
disliked: z.string().url(),
|
||||||
|
});
|
||||||
|
|
@ -5,88 +5,29 @@
|
||||||
* @see https://versia.pub/extensions/polls
|
* @see https://versia.pub/extensions/polls
|
||||||
*/
|
*/
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ExtensionSchema } from "../base";
|
import { EntitySchema } from "../base";
|
||||||
import { ContentFormatSchema } from "../content_format";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Poll extension entity
|
|
||||||
* @see https://versia.pub/extensions/polls
|
|
||||||
* @example
|
|
||||||
* {
|
|
||||||
* "type": "Extension",
|
|
||||||
* "id": "d6eb84ea-cd13-43f9-9c54-01244da8e5e3",
|
|
||||||
* "extension_type": "org.lysand:polls/Poll",
|
|
||||||
* "uri": "https://example.com/polls/d6eb84ea-cd13-43f9-9c54-01244da8e5e3",
|
|
||||||
* "options": [
|
|
||||||
* {
|
|
||||||
* "text/plain": {
|
|
||||||
* "content": "Red"
|
|
||||||
* }
|
|
||||||
* },
|
|
||||||
* {
|
|
||||||
* "text/plain": {
|
|
||||||
* "content": "Blue"
|
|
||||||
* }
|
|
||||||
* },
|
|
||||||
* {
|
|
||||||
* "text/plain": {
|
|
||||||
* "content": "Green"
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* ],
|
|
||||||
* "votes": [
|
|
||||||
* 9,
|
|
||||||
* 5,
|
|
||||||
* 0
|
|
||||||
* ],
|
|
||||||
* "multiple_choice": false,
|
|
||||||
* "expires_at": "2021-01-04T00:00:00.000Z"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
export const PollSchema = ExtensionSchema.extend({
|
|
||||||
extension_type: z.literal("org.lysand:polls/Poll"),
|
|
||||||
options: z.array(ContentFormatSchema),
|
|
||||||
votes: z.array(z.number().int().nonnegative()),
|
|
||||||
multiple_choice: z.boolean().optional().nullable(),
|
|
||||||
expires_at: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Vote extension entity
|
* @description Vote extension entity
|
||||||
* @see https://versia.pub/extensions/polls
|
* @see https://versia.pub/extensions/polls
|
||||||
* @example
|
* @example
|
||||||
* {
|
* {
|
||||||
* "type": "Extension",
|
* "id": "6f27bc77-58ee-4c9b-b804-8cc1c1182fa9",
|
||||||
* "id": "31c4de70-e266-4f61-b0f7-3767d3ccf565",
|
* "type": "pub.versia:polls/Vote",
|
||||||
|
* "uri": "https://example.com/actions/6f27bc77-58ee-4c9b-b804-8cc1c1182fa9",
|
||||||
* "created_at": "2021-01-01T00:00:00.000Z",
|
* "created_at": "2021-01-01T00:00:00.000Z",
|
||||||
* "uri": "https://example.com/votes/31c4de70-e266-4f61-b0f7-3767d3ccf565",
|
* "author": "https://example.com/users/6e0204a2-746c-4972-8602-c4f37fc63bbe",
|
||||||
* "extension_type": "org.lysand:polls/Vote",
|
* "poll": "https://example.com/notes/f08a124e-fe90-439e-8be4-15a428a72a19",
|
||||||
* "poll": "https://example.com/polls/31c4de70-e266-4f61-b0f7-3767d3ccf565",
|
|
||||||
* "option": 1
|
* "option": 1
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export const VoteSchema = ExtensionSchema.extend({
|
export const VoteSchema = EntitySchema.extend({
|
||||||
extension_type: z.literal("org.lysand:polls/Vote"),
|
type: z.literal("pub.versia:polls/Vote"),
|
||||||
|
author: z.string().url(),
|
||||||
poll: z.string().url(),
|
poll: z.string().url(),
|
||||||
option: z.number(),
|
option: z
|
||||||
});
|
.number()
|
||||||
|
.int()
|
||||||
/**
|
.nonnegative()
|
||||||
* @description Vote result extension entity
|
.max(2 ** 64 - 1),
|
||||||
* @see https://versia.pub/extensions/polls
|
|
||||||
* @example
|
|
||||||
* {
|
|
||||||
* "type": "Extension",
|
|
||||||
* "id": "c6d5755b-f42c-418f-ab53-2ee3705d6628",
|
|
||||||
* "created_at": "2021-01-01T00:00:00.000Z",
|
|
||||||
* "uri": "https://example.com/polls/c6d5755b-f42c-418f-ab53-2ee3705d6628/result",
|
|
||||||
* "extension_type": "org.lysand:polls/VoteResult",
|
|
||||||
* "poll": "https://example.com/polls/c6d5755b-f42c-418f-ab53-2ee3705d6628",
|
|
||||||
* "votes": [9, 5, 0]
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
export const VoteResultSchema = ExtensionSchema.extend({
|
|
||||||
extension_type: z.literal("org.lysand:polls/VoteResult"),
|
|
||||||
poll: z.string().url(),
|
|
||||||
votes: z.array(z.number().int().nonnegative()),
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,24 +5,25 @@
|
||||||
* @see https://versia.pub/extensions/reactions
|
* @see https://versia.pub/extensions/reactions
|
||||||
*/
|
*/
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ExtensionSchema } from "../base";
|
import { EntitySchema } from "../base";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Reaction extension entity
|
* @description Reaction extension entity
|
||||||
* @see https://versia.pub/extensions/reactions
|
* @see https://versia.pub/extensions/reactions
|
||||||
* @example
|
* @example
|
||||||
* {
|
* {
|
||||||
* "type": "Extension",
|
* "id": "6f27bc77-58ee-4c9b-b804-8cc1c1182fa9",
|
||||||
* "id": "d6eb84ea-cd13-43f9-9c54-01244da8e5e3",
|
* "type": "pub.versia:reactions/Reaction",
|
||||||
|
* "uri": "https://example.com/actions/6f27bc77-58ee-4c9b-b804-8cc1c1182fa9",
|
||||||
* "created_at": "2021-01-01T00:00:00.000Z",
|
* "created_at": "2021-01-01T00:00:00.000Z",
|
||||||
* "uri": "https://example.com/reactions/d6eb84ea-cd13-43f9-9c54-01244da8e5e3",
|
* "author": "https://example.com/users/6e0204a2-746c-4972-8602-c4f37fc63bbe",
|
||||||
* "extension_type": "org.lysand:reactions/Reaction",
|
* "object": "https://example.com/publications/f08a124e-fe90-439e-8be4-15a428a72a19",
|
||||||
* "object": "https://example.com/posts/d6eb84ea-cd13-43f9-9c54-01244da8e5e3",
|
* "content": "😀",
|
||||||
* "content": "👍"
|
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export const ReactionSchema = ExtensionSchema.extend({
|
export const ReactionSchema = EntitySchema.extend({
|
||||||
extension_type: z.literal("org.lysand:reactions/Reaction"),
|
type: z.literal("pub.versia:reactions/Reaction"),
|
||||||
|
author: z.string().url(),
|
||||||
object: z.string().url(),
|
object: z.string().url(),
|
||||||
content: z.string(),
|
content: z.string().min(1).max(256),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
21
federation/schemas/extensions/share.ts
Normal file
21
federation/schemas/extensions/share.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
import { EntitySchema } from "../base";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Share entity
|
||||||
|
* @see https://versia.pub/extensions/share
|
||||||
|
* @example
|
||||||
|
* {
|
||||||
|
* "id": "3e7e4750-afd4-4d99-a256-02f0710a0520",
|
||||||
|
* "type": "pub.versia:share/Share",
|
||||||
|
* "created_at": "2021-01-01T00:00:00.000Z",
|
||||||
|
* "author": "https://example.com/users/6e0204a2-746c-4972-8602-c4f37fc63bbe",
|
||||||
|
* "uri": "https://example.com/shares/3e7e4750-afd4-4d99-a256-02f0710a0520",
|
||||||
|
* "shared": "https://otherexample.org/notes/fmKZ763jzIU8"
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const ShareSchema = EntitySchema.extend({
|
||||||
|
type: z.literal("pub.versia:share/Share"),
|
||||||
|
author: z.string().url(),
|
||||||
|
shared: z.string().url(),
|
||||||
|
});
|
||||||
|
|
@ -6,7 +6,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ContentFormatSchema } from "../content_format";
|
import {
|
||||||
|
AudioOnlyContentFormatSchema,
|
||||||
|
ImageOnlyContentFormatSchema,
|
||||||
|
} from "../content_format";
|
||||||
|
import { isISOString } from "../regex";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Vanity extension entity
|
* @description Vanity extension entity
|
||||||
|
|
@ -17,29 +21,31 @@ import { ContentFormatSchema } from "../content_format";
|
||||||
* "type": "User",
|
* "type": "User",
|
||||||
* // ...
|
* // ...
|
||||||
* "extensions": {
|
* "extensions": {
|
||||||
* "org.lysand:vanity": {
|
* "pub.versia:vanity": {
|
||||||
* "avatar_overlay": {
|
* "avatar_overlays": [
|
||||||
|
* {
|
||||||
* "image/png": {
|
* "image/png": {
|
||||||
* "content": "https://cdn.example.com/ab5081cf-b11f-408f-92c2-7c246f290593/cat_ears.png",
|
* "content": "https://cdn.example.com/ab5081cf-b11f-408f-92c2-7c246f290593/cat_ears.png",
|
||||||
* "content_type": "image/png"
|
* "remote": true,
|
||||||
* }
|
* }
|
||||||
* },
|
* }
|
||||||
|
* ],
|
||||||
* "avatar_mask": {
|
* "avatar_mask": {
|
||||||
* "image/png": {
|
* "image/png": {
|
||||||
* "content": "https://cdn.example.com/d8c42be1-d0f7-43ef-b4ab-5f614e1beba4/rounded_square.jpeg",
|
* "content": "https://cdn.example.com/d8c42be1-d0f7-43ef-b4ab-5f614e1beba4/rounded_square.jpeg",
|
||||||
* "content_type": "image/jpeg"
|
* "remote": true,
|
||||||
* }
|
* }
|
||||||
* },
|
* },
|
||||||
* "background": {
|
* "background": {
|
||||||
* "image/png": {
|
* "image/png": {
|
||||||
* "content": "https://cdn.example.com/6492ddcd-311e-4921-9567-41b497762b09/untitled-file-0019822.png",
|
* "content": "https://cdn.example.com/6492ddcd-311e-4921-9567-41b497762b09/untitled-file-0019822.png",
|
||||||
* "content_type": "image/png"
|
* "remote": true,
|
||||||
* }
|
* }
|
||||||
* },
|
* },
|
||||||
* "audio": {
|
* "audio": {
|
||||||
* "audio/mpeg": {
|
* "audio/mpeg": {
|
||||||
* "content": "https://cdn.example.com/4da2f0d4-4728-4819-83e4-d614e4c5bebc/michael-jackson-thriller.mp3",
|
* "content": "https://cdn.example.com/4da2f0d4-4728-4819-83e4-d614e4c5bebc/michael-jackson-thriller.mp3",
|
||||||
* "content_type": "audio/mpeg"
|
* "remote": true,
|
||||||
* }
|
* }
|
||||||
* },
|
* },
|
||||||
* "pronouns": {
|
* "pronouns": {
|
||||||
|
|
@ -56,38 +62,46 @@ import { ContentFormatSchema } from "../content_format";
|
||||||
* },
|
* },
|
||||||
* "birthday": "1998-04-12",
|
* "birthday": "1998-04-12",
|
||||||
* "location": "+40.6894-074.0447/",
|
* "location": "+40.6894-074.0447/",
|
||||||
* "activitypub": [
|
|
||||||
* "@erikuden@mastodon.de"
|
|
||||||
* ],
|
|
||||||
* "aliases": [
|
* "aliases": [
|
||||||
* "https://burger.social/accounts/349ee237-c672-41c1-aadc-677e185f795a",
|
* "https://burger.social/accounts/349ee237-c672-41c1-aadc-677e185f795a",
|
||||||
* "https://social.lysand.org/users/f565ef02-035d-4974-ba5e-f62a8558331d"
|
* "https://versia.social/users/f565ef02-035d-4974-ba5e-f62a8558331d"
|
||||||
* ]
|
* ]
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export const VanityExtensionSchema = z.object({
|
export const VanityExtensionSchema = z
|
||||||
avatar_overlay: ContentFormatSchema.optional().nullable(),
|
.object({
|
||||||
avatar_mask: ContentFormatSchema.optional().nullable(),
|
avatar_overlays: z
|
||||||
background: ContentFormatSchema.optional().nullable(),
|
.array(ImageOnlyContentFormatSchema)
|
||||||
audio: ContentFormatSchema.optional().nullable(),
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
avatar_mask: ImageOnlyContentFormatSchema.optional().nullable(),
|
||||||
|
background: ImageOnlyContentFormatSchema.optional().nullable(),
|
||||||
|
audio: AudioOnlyContentFormatSchema.optional().nullable(),
|
||||||
pronouns: z.record(
|
pronouns: z.record(
|
||||||
z.string(),
|
z.string(),
|
||||||
z.array(
|
z.array(
|
||||||
z.union([
|
z.union([
|
||||||
z.object({
|
z
|
||||||
|
.object({
|
||||||
subject: z.string(),
|
subject: z.string(),
|
||||||
object: z.string(),
|
object: z.string(),
|
||||||
dependent_possessive: z.string(),
|
dependent_possessive: z.string(),
|
||||||
independent_possessive: z.string(),
|
independent_possessive: z.string(),
|
||||||
reflexive: z.string(),
|
reflexive: z.string(),
|
||||||
}),
|
})
|
||||||
|
.strict(),
|
||||||
z.string(),
|
z.string(),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
birthday: z.string().optional().nullable(),
|
birthday: z
|
||||||
|
.string()
|
||||||
|
.refine((v) => isISOString(v), "must be a valid ISO8601 datetime")
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
location: z.string().optional().nullable(),
|
location: z.string().optional().nullable(),
|
||||||
activitypub: z.string().optional().nullable(),
|
aliases: z.array(z.string().url()).optional().nullable(),
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
caseInsensitive,
|
|
||||||
charIn,
|
charIn,
|
||||||
|
charNotIn,
|
||||||
createRegExp,
|
createRegExp,
|
||||||
digit,
|
digit,
|
||||||
exactly,
|
exactly,
|
||||||
global,
|
global,
|
||||||
letter,
|
letter,
|
||||||
|
not,
|
||||||
oneOrMore,
|
oneOrMore,
|
||||||
} from "magic-regexp";
|
} from "magic-regexp";
|
||||||
|
|
||||||
|
|
@ -19,14 +20,21 @@ import {
|
||||||
* Regular expression for matching emojis.
|
* Regular expression for matching emojis.
|
||||||
*/
|
*/
|
||||||
export const emojiRegex: RegExp = createRegExp(
|
export const emojiRegex: RegExp = createRegExp(
|
||||||
// A-Z a-z 0-9 _ -
|
exactly(
|
||||||
oneOrMore(letter.or(digit).or(exactly("_")).or(exactly("-"))),
|
exactly(not.letter.or(not.digit).or(charNotIn("_-"))).times(1),
|
||||||
[caseInsensitive, global],
|
oneOrMore(letter.or(digit).or(charIn("_-"))),
|
||||||
|
exactly(not.letter.or(not.digit).or(charNotIn("_-"))).times(1),
|
||||||
|
),
|
||||||
|
[global],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const semverRegex: RegExp = new RegExp(
|
||||||
|
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/gm,
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regular expression for matching an extension_type
|
* Regular expression for matching an extension_type
|
||||||
* @example org.lysand:custom_emojis/Emoji
|
* @example pub.versia:custom_emojis/Emoji
|
||||||
*/
|
*/
|
||||||
export const extensionTypeRegex: RegExp = createRegExp(
|
export const extensionTypeRegex: RegExp = createRegExp(
|
||||||
// org namespace, then colon, then alphanumeric/_/-, then extension name
|
// org namespace, then colon, then alphanumeric/_/-, then extension name
|
||||||
|
|
@ -38,3 +46,21 @@ export const extensionTypeRegex: RegExp = createRegExp(
|
||||||
oneOrMore(exactly(letter.or(digit).or(charIn("_-")))),
|
oneOrMore(exactly(letter.or(digit).or(charIn("_-")))),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular expression for matching an extension
|
||||||
|
* @example pub.versia:custom_emojis
|
||||||
|
*/
|
||||||
|
export const extensionRegex: RegExp = createRegExp(
|
||||||
|
// org namespace, then colon, then alphanumeric/_/-, then extension name
|
||||||
|
exactly(
|
||||||
|
oneOrMore(exactly(letter.lowercase.or(digit).or(charIn("_-.")))),
|
||||||
|
exactly(":"),
|
||||||
|
oneOrMore(exactly(letter.lowercase.or(digit).or(charIn("_-")))),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const isISOString = (val: string | Date) => {
|
||||||
|
const d = new Date(val);
|
||||||
|
return !Number.isNaN(d.valueOf()) && d.toISOString() === val;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,13 @@
|
||||||
import type { z } from "zod";
|
import type { z } from "zod";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import {
|
import {
|
||||||
ActionSchema,
|
|
||||||
ActorPublicKeyDataSchema,
|
|
||||||
DislikeSchema,
|
|
||||||
EntitySchema,
|
EntitySchema,
|
||||||
ExtensionSchema,
|
|
||||||
FollowAcceptSchema,
|
FollowAcceptSchema,
|
||||||
FollowRejectSchema,
|
FollowRejectSchema,
|
||||||
FollowSchema,
|
FollowSchema,
|
||||||
LikeSchema,
|
|
||||||
NoteSchema,
|
NoteSchema,
|
||||||
PatchSchema,
|
PublicKeyDataSchema,
|
||||||
PublicationSchema,
|
|
||||||
ReportSchema,
|
|
||||||
ServerMetadataSchema,
|
|
||||||
UndoSchema,
|
|
||||||
UserSchema,
|
UserSchema,
|
||||||
VisibilitySchema,
|
|
||||||
} from "./schemas/base";
|
} from "./schemas/base";
|
||||||
import { ContentFormatSchema } from "./schemas/content_format";
|
import { ContentFormatSchema } from "./schemas/content_format";
|
||||||
import { ExtensionPropertySchema } from "./schemas/extensions";
|
import { ExtensionPropertySchema } from "./schemas/extensions";
|
||||||
|
|
@ -81,15 +71,6 @@ export class EntityValidator {
|
||||||
return this.validate(NoteSchema, data);
|
return this.validate(NoteSchema, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates a Patch entity.
|
|
||||||
* @param data - The data to validate
|
|
||||||
* @returns A promise that resolves to the validated data.
|
|
||||||
*/
|
|
||||||
public Patch(data: unknown): Promise<InferType<typeof PatchSchema>> {
|
|
||||||
return this.validate(PatchSchema, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates an ActorPublicKeyData entity.
|
* Validates an ActorPublicKeyData entity.
|
||||||
* @param data - The data to validate
|
* @param data - The data to validate
|
||||||
|
|
@ -97,8 +78,8 @@ export class EntityValidator {
|
||||||
*/
|
*/
|
||||||
public ActorPublicKeyData(
|
public ActorPublicKeyData(
|
||||||
data: unknown,
|
data: unknown,
|
||||||
): Promise<InferType<typeof ActorPublicKeyDataSchema>> {
|
): Promise<InferType<typeof PublicKeyDataSchema>> {
|
||||||
return this.validate(ActorPublicKeyDataSchema, data);
|
return this.validate(PublicKeyDataSchema, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -121,42 +102,6 @@ export class EntityValidator {
|
||||||
return this.validate(UserSchema, data);
|
return this.validate(UserSchema, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates an Action entity.
|
|
||||||
* @param data - The data to validate
|
|
||||||
* @returns A promise that resolves to the validated data.
|
|
||||||
*/
|
|
||||||
public Action(data: unknown): Promise<InferType<typeof ActionSchema>> {
|
|
||||||
return this.validate(ActionSchema, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates a Like entity.
|
|
||||||
* @param data - The data to validate
|
|
||||||
* @returns A promise that resolves to the validated data.
|
|
||||||
*/
|
|
||||||
public Like(data: unknown): Promise<InferType<typeof LikeSchema>> {
|
|
||||||
return this.validate(LikeSchema, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates an Undo entity.
|
|
||||||
* @param data - The data to validate
|
|
||||||
* @returns A promise that resolves to the validated data.
|
|
||||||
*/
|
|
||||||
public Undo(data: unknown): Promise<InferType<typeof UndoSchema>> {
|
|
||||||
return this.validate(UndoSchema, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates a Dislike entity.
|
|
||||||
* @param data - The data to validate
|
|
||||||
* @returns A promise that resolves to the validated data.
|
|
||||||
*/
|
|
||||||
public Dislike(data: unknown): Promise<InferType<typeof DislikeSchema>> {
|
|
||||||
return this.validate(DislikeSchema, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates a Follow entity.
|
* Validates a Follow entity.
|
||||||
* @param data - The data to validate
|
* @param data - The data to validate
|
||||||
|
|
@ -188,37 +133,6 @@ export class EntityValidator {
|
||||||
return this.validate(FollowRejectSchema, data);
|
return this.validate(FollowRejectSchema, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates an Extension entity.
|
|
||||||
* @param data - The data to validate
|
|
||||||
* @returns A promise that resolves to the validated data.
|
|
||||||
*/
|
|
||||||
public Extension(
|
|
||||||
data: unknown,
|
|
||||||
): Promise<InferType<typeof ExtensionSchema>> {
|
|
||||||
return this.validate(ExtensionSchema, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates a Report entity.
|
|
||||||
* @param data - The data to validate
|
|
||||||
* @returns A promise that resolves to the validated data.
|
|
||||||
*/
|
|
||||||
public Report(data: unknown): Promise<InferType<typeof ReportSchema>> {
|
|
||||||
return this.validate(ReportSchema, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates a ServerMetadata entity.
|
|
||||||
* @param data - The data to validate
|
|
||||||
* @returns A promise that resolves to the validated data.
|
|
||||||
*/
|
|
||||||
public ServerMetadata(
|
|
||||||
data: unknown,
|
|
||||||
): Promise<InferType<typeof ServerMetadataSchema>> {
|
|
||||||
return this.validate(ServerMetadataSchema, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates a ContentFormat entity.
|
* Validates a ContentFormat entity.
|
||||||
* @param data - The data to validate
|
* @param data - The data to validate
|
||||||
|
|
@ -241,28 +155,6 @@ export class EntityValidator {
|
||||||
return this.validate(CustomEmojiExtensionSchema, data);
|
return this.validate(CustomEmojiExtensionSchema, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates a Visibility entity.
|
|
||||||
* @param data - The data to validate
|
|
||||||
* @returns A promise that resolves to the validated data.
|
|
||||||
*/
|
|
||||||
public Visibility(
|
|
||||||
data: unknown,
|
|
||||||
): Promise<InferType<typeof VisibilitySchema>> {
|
|
||||||
return this.validate(VisibilitySchema, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates a Publication entity.
|
|
||||||
* @param data - The data to validate
|
|
||||||
* @returns A promise that resolves to the validated data.
|
|
||||||
*/
|
|
||||||
public Publication(
|
|
||||||
data: unknown,
|
|
||||||
): Promise<InferType<typeof PublicationSchema>> {
|
|
||||||
return this.validate(PublicationSchema, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates an Entity.
|
* Validates an Entity.
|
||||||
* @param data - The data to validate
|
* @param data - The data to validate
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue