From 6be8d4d46ef510e362e08ffa7e3836b83b454a8e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 10 Mar 2023 12:42:49 -0600 Subject: [PATCH] Add groups schemas with zod --- app/soapbox/schemas/custom-emoji.ts | 17 ++++++++ app/soapbox/schemas/group-relationship.ts | 12 ++++++ app/soapbox/schemas/group.ts | 49 +++++++++++++++++++++++ app/soapbox/schemas/utils.ts | 21 ++++++++++ 4 files changed, 99 insertions(+) create mode 100644 app/soapbox/schemas/custom-emoji.ts create mode 100644 app/soapbox/schemas/group-relationship.ts create mode 100644 app/soapbox/schemas/group.ts create mode 100644 app/soapbox/schemas/utils.ts diff --git a/app/soapbox/schemas/custom-emoji.ts b/app/soapbox/schemas/custom-emoji.ts new file mode 100644 index 000000000..68c49c587 --- /dev/null +++ b/app/soapbox/schemas/custom-emoji.ts @@ -0,0 +1,17 @@ +import z from 'zod'; + +/** + * Represents a custom emoji. + * https://docs.joinmastodon.org/entities/CustomEmoji/ + */ +const customEmojiSchema = z.object({ + category: z.string().catch(''), + shortcode: z.string(), + static_url: z.string().catch(''), + url: z.string(), + visible_in_picker: z.boolean().catch(true), +}); + +type CustomEmoji = z.infer; + +export { customEmojiSchema, CustomEmoji }; diff --git a/app/soapbox/schemas/group-relationship.ts b/app/soapbox/schemas/group-relationship.ts new file mode 100644 index 000000000..8339466ab --- /dev/null +++ b/app/soapbox/schemas/group-relationship.ts @@ -0,0 +1,12 @@ +import z from 'zod'; + +const groupRelationshipSchema = z.object({ + id: z.string(), + member: z.boolean().catch(false), + requested: z.boolean().catch(false), + role: z.string().nullish().catch(null), +}); + +type GroupRelationship = z.infer; + +export { groupRelationshipSchema, GroupRelationship }; \ No newline at end of file diff --git a/app/soapbox/schemas/group.ts b/app/soapbox/schemas/group.ts new file mode 100644 index 000000000..74246adbc --- /dev/null +++ b/app/soapbox/schemas/group.ts @@ -0,0 +1,49 @@ +import escapeTextContentForBrowser from 'escape-html'; +import z from 'zod'; + +import emojify from 'soapbox/features/emoji'; +import { unescapeHTML } from 'soapbox/utils/html'; + +import { customEmojiSchema } from './custom-emoji'; +import { groupRelationshipSchema } from './group-relationship'; +import { filteredArray, makeCustomEmojiMap } from './utils'; + +const avatarMissing = require('assets/images/avatar-missing.png'); +const headerMissing = require('assets/images/header-missing.png'); + +const groupSchema = z.object({ + avatar: z.string().catch(avatarMissing), + avatar_static: z.string().catch(''), + created_at: z.string().datetime().catch(new Date().toUTCString()), + display_name: z.string().catch(''), + domain: z.string().catch(''), + emojis: filteredArray(customEmojiSchema), + group_visibility: z.string().catch(''), // TruthSocial + header: z.string().catch(headerMissing), + header_static: z.string().catch(''), + id: z.string().catch(''), + locked: z.boolean().catch(false), + membership_required: z.boolean().catch(false), + members_count: z.number().optional().catch(undefined), + note: z.string().catch('').refine(note => note === '

' ? '' : note), + relationship: groupRelationshipSchema.optional().catch(undefined), // Dummy field to be overwritten later + statuses_visibility: z.string().catch('public'), + uri: z.string().catch(''), + url: z.string().catch(''), +}).refine(group => { + group.avatar_static = group.avatar_static || group.avatar; + group.header_static = group.header_static || group.header; + group.locked = group.locked || group.group_visibility === 'members_only'; // TruthSocial + + const customEmojiMap = makeCustomEmojiMap(group.emojis); + return { + ...group, + display_name_html: emojify(escapeTextContentForBrowser(group.display_name), customEmojiMap), + note_emojified: emojify(group.note, customEmojiMap), + note_plain: unescapeHTML(group.note), + }; +}); + +type Group = z.infer; + +export { groupSchema, Group }; \ No newline at end of file diff --git a/app/soapbox/schemas/utils.ts b/app/soapbox/schemas/utils.ts new file mode 100644 index 000000000..d0bc4cc8f --- /dev/null +++ b/app/soapbox/schemas/utils.ts @@ -0,0 +1,21 @@ +import z from 'zod'; + +import type { CustomEmoji } from './custom-emoji'; + +/** Validates individual items in an array, dropping any that aren't valid. */ +function filteredArray(schema: T) { + return z.any().array().transform((arr) => ( + arr.map((item) => schema.safeParse(item).success ? item as z.infer : undefined) + .filter((item): item is z.infer => Boolean(item)) + )); +} + +/** Map a list of CustomEmoji to their shortcodes. */ +function makeCustomEmojiMap(customEmojis: CustomEmoji[]) { + return customEmojis.reduce>((result, emoji) => { + result[`:${emoji.shortcode}:`] = emoji; + return result; + }, {}); +} + +export { filteredArray, makeCustomEmojiMap }; \ No newline at end of file