From dac11a90555eca06d04b98dd2d44a5a6d87c9db8 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 25 Sep 2024 13:56:14 -0300 Subject: [PATCH 1/5] feat: filter global and local feed by language add a 'language' field in publicQuerySchema --- src/controllers/api/timelines.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/controllers/api/timelines.ts b/src/controllers/api/timelines.ts index 483676e1..0f5808ba 100644 --- a/src/controllers/api/timelines.ts +++ b/src/controllers/api/timelines.ts @@ -1,3 +1,4 @@ +import ISO6391 from 'iso-639-1'; import { NostrFilter } from '@nostrify/nostrify'; import { z } from 'zod'; @@ -20,11 +21,21 @@ const homeTimelineController: AppController = async (c) => { const publicQuerySchema = z.object({ local: booleanParamSchema.catch(false), instance: z.string().optional().catch(undefined), + language: z.string().max(2).transform((val, ctx) => { + if (!ISO6391.validate(val)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Not a valid language in ISO-639-1 format', + }); + return z.NEVER; + } + return val; + }).optional().catch(undefined), }); const publicTimelineController: AppController = (c) => { const params = c.get('pagination'); - const { local, instance } = publicQuerySchema.parse(c.req.query()); + const { local, instance, language } = publicQuerySchema.parse(c.req.query()); const filter: NostrFilter = { kinds: [1], ...params }; @@ -34,6 +45,10 @@ const publicTimelineController: AppController = (c) => { filter.search = `domain:${instance}`; } + if (language) { + filter.search = filter.search + ' ' + `language:${language}`; + } + return renderStatuses(c, [filter]); }; From 92aaca0d913f6cf4e807c51a29faef79bf96cf49 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 25 Sep 2024 16:51:23 -0300 Subject: [PATCH 2/5] feat: create languageSchema --- src/schema.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/schema.ts b/src/schema.ts index fc7efd01..6e64a411 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -1,3 +1,4 @@ +import ISO6391 from 'iso-639-1'; import { z } from 'zod'; /** Validates individual items in an array, dropping any that aren't valid. */ @@ -40,12 +41,24 @@ const fileSchema = z.custom((value) => value instanceof File); const percentageSchema = z.coerce.number().int().gte(1).lte(100); +const languageSchema = z.string().max(2).transform((val, ctx) => { + if (!ISO6391.validate(val)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Not a valid language in ISO-639-1 format', + }); + return z.NEVER; + } + return val; +}); + export { booleanParamSchema, decode64Schema, fileSchema, filteredArray, hashtagSchema, + languageSchema, percentageSchema, safeUrlSchema, }; From 12aaf8c67873a9eadcddc0d36fd0a47b4fc05ce4 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 25 Sep 2024 16:51:59 -0300 Subject: [PATCH 3/5] refactor: use languageSchema in createStatusSchema --- src/controllers/api/statuses.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index e0956e74..486ea28b 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -1,6 +1,5 @@ import { HTTPException } from '@hono/hono/http-exception'; import { NostrEvent, NSchema as n } from '@nostrify/nostrify'; -import ISO6391 from 'iso-639-1'; import 'linkify-plugin-hashtag'; import linkify from 'linkifyjs'; import { nip19 } from 'nostr-tools'; @@ -14,6 +13,7 @@ import { getAncestors, getAuthor, getDescendants, getEvent } from '@/queries.ts' import { addTag, deleteTag } from '@/utils/tags.ts'; import { asyncReplaceAll } from '@/utils/text.ts'; import { lookupPubkey } from '@/utils/lookup.ts'; +import { languageSchema } from '@/schema.ts'; import { Storages } from '@/storages.ts'; import { hydrateEvents } from '@/storages/hydrate.ts'; import { createEvent, paginated, paginatedList, parseBody, updateListEvent } from '@/utils/api.ts'; @@ -26,7 +26,7 @@ import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts'; const createStatusSchema = z.object({ in_reply_to_id: n.id().nullish(), - language: z.string().refine(ISO6391.validate).nullish(), + language: languageSchema.nullish(), media_ids: z.string().array().nullish(), poll: z.object({ options: z.string().array(), From 77f5965cdc733b656b2355df3490275efbb9744b Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 25 Sep 2024 16:52:54 -0300 Subject: [PATCH 4/5] refactor: return zod errors in publicTimelineController --- src/controllers/api/timelines.ts | 36 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/controllers/api/timelines.ts b/src/controllers/api/timelines.ts index 0f5808ba..8f783dd8 100644 --- a/src/controllers/api/timelines.ts +++ b/src/controllers/api/timelines.ts @@ -1,11 +1,10 @@ -import ISO6391 from 'iso-639-1'; import { NostrFilter } from '@nostrify/nostrify'; import { z } from 'zod'; import { type AppContext, type AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; import { getFeedPubkeys } from '@/queries.ts'; -import { booleanParamSchema } from '@/schema.ts'; +import { booleanParamSchema, languageSchema } from '@/schema.ts'; import { hydrateEvents } from '@/storages/hydrate.ts'; import { paginated } from '@/utils/api.ts'; import { getTagSet } from '@/utils/tags.ts'; @@ -19,36 +18,37 @@ const homeTimelineController: AppController = async (c) => { }; const publicQuerySchema = z.object({ - local: booleanParamSchema.catch(false), - instance: z.string().optional().catch(undefined), - language: z.string().max(2).transform((val, ctx) => { - if (!ISO6391.validate(val)) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Not a valid language in ISO-639-1 format', - }); - return z.NEVER; - } - return val; - }).optional().catch(undefined), + local: booleanParamSchema.default('false'), + instance: z.string().optional(), + language: languageSchema.optional(), }); const publicTimelineController: AppController = (c) => { const params = c.get('pagination'); - const { local, instance, language } = publicQuerySchema.parse(c.req.query()); + const result = publicQuerySchema.safeParse(c.req.query()); + + if (!result.success) { + return c.json({ error: 'Bad request', schema: result.error }, 400); + } + + const { local, instance, language } = result.data; const filter: NostrFilter = { kinds: [1], ...params }; + const search: `${string}:${string}`[] = []; + if (local) { - filter.search = `domain:${Conf.url.host}`; + search.push(`domain:${Conf.url.host}`); } else if (instance) { - filter.search = `domain:${instance}`; + search.push(`domain:${instance}`); } if (language) { - filter.search = filter.search + ' ' + `language:${language}`; + search.push(`language:${language}`); } + filter.search = search.join(' '); + return renderStatuses(c, [filter]); }; From 8ad101ce7b4a5392abb50c947f24e7a104aa3efd Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 25 Sep 2024 17:03:30 -0300 Subject: [PATCH 5/5] refactor: remove max(2) in languageSchema --- src/schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schema.ts b/src/schema.ts index 6e64a411..5efa7769 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -41,7 +41,7 @@ const fileSchema = z.custom((value) => value instanceof File); const percentageSchema = z.coerce.number().int().gte(1).lte(100); -const languageSchema = z.string().max(2).transform((val, ctx) => { +const languageSchema = z.string().transform((val, ctx) => { if (!ISO6391.validate(val)) { ctx.addIssue({ code: z.ZodIssueCode.custom,