From ea40f17d3c95433cc910c27d3235c3f7a0901215 Mon Sep 17 00:00:00 2001 From: pluja Date: Wed, 28 May 2025 13:48:27 +0000 Subject: [PATCH] Release 202505281348 --- web/prisma/seed.ts | 4 +- web/src/actions/admin/service.ts | 35 ++++--- web/src/actions/serviceSuggestion.ts | 22 +++-- .../components/ServicesSearchResults.astro | 6 +- web/src/lib/urls.ts | 25 +++++ web/src/lib/zodUtils.ts | 2 +- .../pages/admin/services/[slug]/edit.astro | 98 ++++++++----------- web/src/pages/admin/services/new.astro | 30 ++---- web/src/pages/index.astro | 1 - web/src/pages/service-suggestion/new.astro | 53 +++++----- 10 files changed, 134 insertions(+), 142 deletions(-) diff --git a/web/prisma/seed.ts b/web/prisma/seed.ts index 2f21e55..4a75079 100755 --- a/web/prisma/seed.ts +++ b/web/prisma/seed.ts @@ -916,7 +916,7 @@ const specialUsersData = { verifiedLink: 'https://kycnot.me', totalKarma: 1001, link: 'https://kycnot.me', - picture: 'https://comments.kycnot.me/api/users/549f290e-0542-4c18-b437-5b64b35758f0/avatar?size=L', + picture: 'https://kycnot.me/files/users/pictures/c277dc0f2f.png', }, moderator: { name: 'moderator_dev', @@ -928,7 +928,7 @@ const specialUsersData = { verifiedLink: 'https://kycnot.me', totalKarma: 1001, link: 'https://kycnot.me', - picture: 'https://comments.kycnot.me/api/users/549f290e-0542-4c18-b437-5b64b35758f0/avatar?size=L', + picture: 'https://kycnot.me/files/users/pictures/c277dc0f2f.png', }, verified: { name: 'verified_dev', diff --git a/web/src/actions/admin/service.ts b/web/src/actions/admin/service.ts index a504dca..9710890 100644 --- a/web/src/actions/admin/service.ts +++ b/web/src/actions/admin/service.ts @@ -6,12 +6,8 @@ import slugify from 'slugify' import { defineProtectedAction } from '../../lib/defineProtectedAction' import { saveFileLocally } from '../../lib/fileStorage' import { prisma } from '../../lib/prisma' -import { - imageFileSchema, - stringListOfUrlsSchema, - stringListOfUrlsSchemaRequired, - zodCohercedNumber, -} from '../../lib/zodUtils' +import { separateServiceUrlsByType } from '../../lib/urls' +import { imageFileSchema, stringListOfUrlsSchemaRequired, zodCohercedNumber } from '../../lib/zodUtils' const serviceSchemaBase = z.object({ id: z.number().int().positive(), @@ -19,11 +15,10 @@ const serviceSchemaBase = z.object({ .string() .regex(/^[a-z0-9-]+$/, 'Allowed characters: lowercase letters, numbers, and hyphens') .optional(), - name: z.string().min(1).max(20), + name: z.string().min(1).max(40), description: z.string().min(1), - serviceUrls: stringListOfUrlsSchemaRequired, + allServiceUrls: stringListOfUrlsSchemaRequired, tosUrls: stringListOfUrlsSchemaRequired, - onionUrls: stringListOfUrlsSchema, kycLevel: z.coerce.number().int().min(0).max(4), attributes: z.array(z.coerce.number().int().positive()), categories: z.array(z.coerce.number().int().positive()).min(1), @@ -85,13 +80,20 @@ export const adminServiceActions = { ? await saveFileLocally(input.imageFile, input.imageFile.name) : undefined + const { + web: serviceUrls, + onion: onionUrls, + i2p: i2pUrls, + } = separateServiceUrlsByType(input.allServiceUrls) + const service = await prisma.service.create({ data: { name: input.name, description: input.description, - serviceUrls: input.serviceUrls, + serviceUrls, tosUrls: input.tosUrls, - onionUrls: input.onionUrls, + onionUrls, + i2pUrls, kycLevel: input.kycLevel, verificationStatus: input.verificationStatus, verificationSummary: input.verificationSummary, @@ -187,14 +189,21 @@ export const adminServiceActions = { const attributesToAdd = input.attributes.filter((aId) => !existingAttributeIds.includes(aId)) const attributesToRemove = existingAttributeIds.filter((aId) => !input.attributes.includes(aId)) + const { + web: serviceUrls, + onion: onionUrls, + i2p: i2pUrls, + } = separateServiceUrlsByType(input.allServiceUrls) + const service = await prisma.service.update({ where: { id: input.id }, data: { name: input.name, description: input.description, - serviceUrls: input.serviceUrls, + serviceUrls, tosUrls: input.tosUrls, - onionUrls: input.onionUrls, + onionUrls, + i2pUrls, kycLevel: input.kycLevel, verificationStatus: input.verificationStatus, verificationSummary: input.verificationSummary, diff --git a/web/src/actions/serviceSuggestion.ts b/web/src/actions/serviceSuggestion.ts index 8baa39b..d3833fb 100644 --- a/web/src/actions/serviceSuggestion.ts +++ b/web/src/actions/serviceSuggestion.ts @@ -14,12 +14,8 @@ import { defineProtectedAction } from '../lib/defineProtectedAction' import { saveFileLocally } from '../lib/fileStorage' import { handleHoneypotTrap } from '../lib/honeypot' import { prisma } from '../lib/prisma' -import { - imageFileSchemaRequired, - stringListOfUrlsSchema, - stringListOfUrlsSchemaRequired, - zodCohercedNumber, -} from '../lib/zodUtils' +import { separateServiceUrlsByType } from '../lib/urls' +import { imageFileSchemaRequired, stringListOfUrlsSchemaRequired, zodCohercedNumber } from '../lib/zodUtils' import type { Prisma } from '@prisma/client' @@ -161,9 +157,8 @@ export const serviceSuggestionActions = { { message: 'Slug must be unique, try a different one' } ), description: z.string().min(1).max(SUGGESTION_DESCRIPTION_MAX_LENGTH), - serviceUrls: stringListOfUrlsSchemaRequired, + allServiceUrls: stringListOfUrlsSchemaRequired, tosUrls: stringListOfUrlsSchemaRequired, - onionUrls: stringListOfUrlsSchema, kycLevel: zodCohercedNumber(z.coerce.number().int().min(0).max(4)), attributes: z.array(z.coerce.number().int().positive()), categories: z.array(z.coerce.number().int().positive()).min(1), @@ -210,6 +205,12 @@ export const serviceSuggestionActions = { const imageUrl = await saveFileLocally(input.imageFile, input.imageFile.name) + const { + web: serviceUrls, + onion: onionUrls, + i2p: i2pUrls, + } = separateServiceUrlsByType(input.allServiceUrls) + const { serviceSuggestion, service } = await prisma.$transaction(async (tx) => { const serviceSelect = { id: true, @@ -221,9 +222,10 @@ export const serviceSuggestionActions = { name: input.name, slug: input.slug, description: input.description, - serviceUrls: input.serviceUrls, + serviceUrls, tosUrls: input.tosUrls, - onionUrls: input.onionUrls, + onionUrls, + i2pUrls, kycLevel: input.kycLevel, acceptedCurrencies: input.acceptedCurrencies, imageUrl, diff --git a/web/src/components/ServicesSearchResults.astro b/web/src/components/ServicesSearchResults.astro index 34f59dc..1303df4 100644 --- a/web/src/components/ServicesSearchResults.astro +++ b/web/src/components/ServicesSearchResults.astro @@ -21,7 +21,6 @@ type Props = HTMLAttributes<'div'> & { pageSize: number sortSeed?: string filters: ServicesFiltersObject - includeScams: boolean countCommunityOnly: number | null inlineIcons?: boolean } @@ -35,15 +34,12 @@ const { sortSeed, class: className, filters, - includeScams, countCommunityOnly, inlineIcons, ...divProps } = Astro.props -const hasScams = - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - filters.verification.includes('VERIFICATION_FAILED') || includeScams +const hasScams = filters.verification.includes('VERIFICATION_FAILED') const hasSomeScam = !!services?.some((service) => service.verificationStatus.includes('VERIFICATION_FAILED')) const hasCommunityContributed = filters.verification.includes('COMMUNITY_CONTRIBUTED') diff --git a/web/src/lib/urls.ts b/web/src/lib/urls.ts index a35b3c6..ba40ee7 100644 --- a/web/src/lib/urls.ts +++ b/web/src/lib/urls.ts @@ -113,3 +113,28 @@ export function urlDomain(url: URL | string) { } return url.origin } + +export function separateServiceUrlsByType(allServiceUrls: string[]) { + const result: { + web: string[] + onion: string[] + i2p: string[] + } = { + web: [], + onion: [], + i2p: [], + } + + for (const url of allServiceUrls) { + const parsedUrl = new URL(url) + if (parsedUrl.origin.endsWith('.onion')) { + result.onion.push(url) + } else if (parsedUrl.origin.endsWith('.b32.i2p')) { + result.i2p.push(url) + } else { + result.web.push(url) + } + } + + return result +} diff --git a/web/src/lib/zodUtils.ts b/web/src/lib/zodUtils.ts index e1d1b95..b2f5a3f 100644 --- a/web/src/lib/zodUtils.ts +++ b/web/src/lib/zodUtils.ts @@ -19,7 +19,7 @@ export const zodUrlOptionalProtocol = z.preprocess( const cleanInput = input.trim().replace(/\/$/, '') return !/^\w+:\/\//i.test(cleanInput) ? `https://${cleanInput}` : cleanInput }, - z.string().refine((value) => /^(https?):\/\/(?=.*\.[a-z]{2,})[^\s$.?#].[^\s]*$/i.test(value), { + z.string().refine((value) => /^(https?):\/\/(?=.*\.[a-z0-9]{2,})[^\s$.?#].[^\s]*$/i.test(value), { message: 'Invalid URL', }) ) diff --git a/web/src/pages/admin/services/[slug]/edit.astro b/web/src/pages/admin/services/[slug]/edit.astro index abce1aa..889b64c 100644 --- a/web/src/pages/admin/services/[slug]/edit.astro +++ b/web/src/pages/admin/services/[slug]/edit.astro @@ -233,16 +233,30 @@ if (!service) return Astro.rewrite('/404') enctype="multipart/form-data" > - +
+ + + +
- - -
+
- -
- -
- +