From e16c9b64ed86a2b62535f316a30a4df9573d6481 Mon Sep 17 00:00:00 2001 From: pluja Date: Sat, 31 May 2025 10:01:35 +0000 Subject: [PATCH] Release 202505311001 --- web/src/actions/api/service.ts | 18 +++++++++++++- web/src/actions/serviceSuggestion.ts | 20 ++++++--------- web/src/components/Footer.astro | 34 ++++++++++++++++++-------- web/src/constants/serviceVisibility.ts | 2 +- web/src/pages/admin/releases.astro | 4 +-- web/src/pages/docs/api.mdx | 29 ++++++++++++++++++---- web/src/pages/index.astro | 13 +++------- web/src/pages/u/[username].astro | 11 ++++++++- 8 files changed, 89 insertions(+), 42 deletions(-) diff --git a/web/src/actions/api/service.ts b/web/src/actions/api/service.ts index ead6305..58c4e5f 100644 --- a/web/src/actions/api/service.ts +++ b/web/src/actions/api/service.ts @@ -46,6 +46,9 @@ export const apiServiceActions = { const service = await prisma.service.findFirst({ where: { + listedAt: { lte: new Date() }, + serviceVisibility: { in: ['PUBLIC', 'ARCHIVED', 'UNLISTED'] }, + OR: [ ...(input.id ? ([{ id: input.id }] satisfies Prisma.ServiceWhereInput[]) : []), ...(input.slug ? ([{ slug: input.slug }] satisfies Prisma.ServiceWhereInput[]) : []), @@ -76,10 +79,20 @@ export const apiServiceActions = { i2pUrls: true, tosUrls: true, referral: true, + listedAt: true, + verifiedAt: true, + serviceVisibility: true, }, }) - if (!service) { + if ( + !service || + (service.serviceVisibility !== 'PUBLIC' && + service.serviceVisibility !== 'ARCHIVED' && + service.serviceVisibility !== 'UNLISTED') || + !service.listedAt || + service.listedAt > new Date() + ) { throw new ActionError({ code: 'NOT_FOUND', message: 'Service not found', @@ -91,6 +104,7 @@ export const apiServiceActions = { slug: service.slug, name: service.name, description: service.description, + serviceVisibility: service.serviceVisibility, verificationStatus: service.verificationStatus, verificationStatusInfo: pick(getVerificationStatusInfo(service.verificationStatus), [ 'value', @@ -99,9 +113,11 @@ export const apiServiceActions = { 'labelShort', 'description', ]), + verifiedAt: service.verifiedAt, kycLevel: service.kycLevel, kycLevelInfo: pick(getKycLevelInfo(service.kycLevel.toString()), ['value', 'name', 'description']), categories: service.categories, + listedAt: service.listedAt, serviceUrls: [...service.serviceUrls, ...service.onionUrls, ...service.i2pUrls].map( (url) => url + (service.referral ?? '') ), diff --git a/web/src/actions/serviceSuggestion.ts b/web/src/actions/serviceSuggestion.ts index 74b0e62..c595345 100644 --- a/web/src/actions/serviceSuggestion.ts +++ b/web/src/actions/serviceSuggestion.ts @@ -1,10 +1,4 @@ -import { - Currency, - ServiceSuggestionStatus, - ServiceSuggestionType, - ServiceVisibility, - VerificationStatus, -} from '@prisma/client' +import { Currency } from '@prisma/client' import { z } from 'astro/zod' import { ActionError } from 'astro:actions' import { formatDistanceStrict } from 'date-fns' @@ -118,9 +112,9 @@ export const serviceSuggestionActions = { const serviceSuggestion = await prisma.serviceSuggestion.create({ data: { - type: ServiceSuggestionType.EDIT_SERVICE, + type: 'EDIT_SERVICE', notes: combinedNotes, - status: ServiceSuggestionStatus.PENDING, + status: 'PENDING', userId: context.locals.user.id, serviceId: service.id, }, @@ -229,12 +223,12 @@ export const serviceSuggestionActions = { kycLevel: input.kycLevel, acceptedCurrencies: input.acceptedCurrencies, imageUrl, - verificationStatus: VerificationStatus.COMMUNITY_CONTRIBUTED, + verificationStatus: 'COMMUNITY_CONTRIBUTED', overallScore: 0, privacyScore: 0, trustScore: 0, listedAt: new Date(), - serviceVisibility: ServiceVisibility.UNLISTED, + serviceVisibility: 'UNLISTED', categories: { connect: input.categories.map((id) => ({ id })), }, @@ -250,8 +244,8 @@ export const serviceSuggestionActions = { const serviceSuggestion = await tx.serviceSuggestion.create({ data: { notes: input.notes, - type: ServiceSuggestionType.CREATE_SERVICE, - status: ServiceSuggestionStatus.PENDING, + type: 'CREATE_SERVICE', + status: 'PENDING', userId: context.locals.user.id, serviceId: service.id, }, diff --git a/web/src/components/Footer.astro b/web/src/components/Footer.astro index 37a4eda..7e9d849 100644 --- a/web/src/components/Footer.astro +++ b/web/src/components/Footer.astro @@ -15,17 +15,31 @@ const links = [ icon: 'ri:git-repository-line', external: true, }, + ...(Astro.url.origin !== new URL(ONION_ADDRESS).origin + ? [ + { + href: ONION_ADDRESS, + label: 'Tor', + icon: 'onion', + external: true, + }, + ] + : []), + ...(Astro.url.origin !== new URL(I2P_ADDRESS).origin + ? [ + { + href: I2P_ADDRESS, + label: 'I2P', + icon: 'i2p', + external: true, + }, + ] + : []), { - href: ONION_ADDRESS, - label: 'Tor', - icon: 'onion', - external: true, - }, - { - href: I2P_ADDRESS, - label: 'I2P', - icon: 'i2p', - external: true, + href: '/docs/api', + label: 'API', + icon: 'ri:plug-line', + external: false, }, { href: '/about', diff --git a/web/src/constants/serviceVisibility.ts b/web/src/constants/serviceVisibility.ts index ca9634e..3864286 100644 --- a/web/src/constants/serviceVisibility.ts +++ b/web/src/constants/serviceVisibility.ts @@ -65,7 +65,7 @@ export const { value: 'ARCHIVED', slug: 'archived', label: 'Archived', - description: 'No longer operational', + description: 'No longer operational.', longDescription: 'Archived service, no longer exists or ceased operations. Information may be outdated.', icon: 'ri:archive-line', diff --git a/web/src/pages/admin/releases.astro b/web/src/pages/admin/releases.astro index 9c25637..b86e20b 100644 --- a/web/src/pages/admin/releases.astro +++ b/web/src/pages/admin/releases.astro @@ -1,8 +1,8 @@ --- import { RELEASE_DATE, RELEASE_NUMBER } from 'astro:env/server' -import TimeFormatted from '../../components/TimeFormatted.astro' import MiniLayout from '../../layouts/MiniLayout.astro' +import { timeAgo } from '../../lib/timeAgo' const releaseDate = RELEASE_DATE && !isNaN(new Date(RELEASE_DATE).getTime()) ? new Date(RELEASE_DATE) : undefined @@ -37,7 +37,7 @@ const releaseDate = { !!releaseDate && (

- () + ()

) } diff --git a/web/src/pages/docs/api.mdx b/web/src/pages/docs/api.mdx index debd5c6..a9208d9 100644 --- a/web/src/pages/docs/api.mdx +++ b/web/src/pages/docs/api.mdx @@ -10,6 +10,7 @@ icon: 'ri:plug-line' import { SOURCE_CODE_URL } from 'astro:env/server' import { kycLevels } from '../../constants/kycLevels' import { verificationStatuses } from '../../constants/verificationStatus' +import { serviceVisibilities } from '../../constants/serviceVisibility' Access basic service data via our public API. @@ -41,6 +42,7 @@ type ServiceResponse = { slug: string name: string description: string + serviceVisibility: 'PUBLIC' | 'ARCHIVED' | 'UNLISTED' verificationStatus: 'VERIFICATION_SUCCESS' | 'APPROVED' | 'COMMUNITY_CONTRIBUTED' | 'VERIFICATION_FAILED' verificationStatusInfo: { value: 'VERIFICATION_SUCCESS' | 'APPROVED' | 'COMMUNITY_CONTRIBUTED' | 'VERIFICATION_FAILED' @@ -49,6 +51,7 @@ type ServiceResponse = { labelShort: string description: string } + verifiedAt: Date | null kycLevel: 0 | 1 | 2 | 3 | 4 kycLevelInfo: { value: 0 | 1 | 2 | 3 | 4 @@ -59,13 +62,14 @@ type ServiceResponse = { name: string slug: string }[] + listedAt: Date serviceUrls: string[] tosUrls: string[] kycnotmeUrl: `https://kycnot.me/service/${service.slug}` } ``` -### KYC Levels +#### KYC Levels -### Verification Status +#### Verification Status -### Example Request +#### Service Visibility + + + +### Examples + +#### Request ```zsh curl -X QUERY https://kycnot.me/api/v1/service/get \ @@ -93,12 +109,13 @@ curl -X QUERY https://kycnot.me/api/v1/service/get \ -d '{"slug": "my-example-service"}' ``` -### Example Response +#### Response ```json { "name": "My Example Service", "description": "This is a description of my example service", + "serviceVisibility": "PUBLIC", "verificationStatus": "VERIFICATION_SUCCESS", "verificationStatusInfo": { "value": "VERIFICATION_SUCCESS", @@ -107,6 +124,7 @@ curl -X QUERY https://kycnot.me/api/v1/service/get \ "labelShort": "Verified", "description": "Thoroughly tested and verified by the team. But things might change, this is not a guarantee." }, + "verifiedAt": "2025-01-20T07:12:29.393Z", "kycLevel": 0, "kycLevelInfo": { "value": 0, @@ -119,6 +137,7 @@ curl -X QUERY https://kycnot.me/api/v1/service/get \ "slug": "exchange" } ], + "listedAt": "2025-05-31T19:09:18.043Z", "serviceUrls": [ "https://example.com", "http://c9ikae0fdidzh1ufrzp022e5uqfvz6ofxlkycz59cvo6fdxjgx7ekl9e.onion" @@ -128,7 +147,7 @@ curl -X QUERY https://kycnot.me/api/v1/service/get \ } ``` -### Error Responses +#### Error Responses **404 Not Found**: Service not found diff --git a/web/src/pages/index.astro b/web/src/pages/index.astro index a68286d..21ff00c 100644 --- a/web/src/pages/index.astro +++ b/web/src/pages/index.astro @@ -218,16 +218,12 @@ const servicesQMatch = filters.q ? await findServicesBySimilarity(filters.q) : n const where = { id: servicesQMatch ? { in: servicesQMatch.map(({ id }) => id) } : undefined, - listedAt: { - lte: new Date(), - }, + listedAt: { lte: new Date() }, categories: filters.categories.length ? { some: { slug: { in: filters.categories } } } : undefined, verificationStatus: { in: includeScams ? uniq([...filters.verification, 'VERIFICATION_FAILED'] as const) : filters.verification, }, - serviceVisibility: { - in: ['PUBLIC', 'ARCHIVED'], - }, + serviceVisibility: { in: ['PUBLIC', 'ARCHIVED'] }, overallScore: { gte: filters['min-score'] }, acceptedCurrencies: filters.currencies.length ? filters['currency-mode'] === 'and' @@ -319,9 +315,8 @@ const [categories, [services, totalServices], countCommunityOnly, attributes] = select: { services: { where: { - serviceVisibility: { - in: ['PUBLIC', 'ARCHIVED'], - }, + serviceVisibility: { in: ['PUBLIC', 'ARCHIVED'] }, + listedAt: { lte: new Date() }, }, }, }, diff --git a/web/src/pages/u/[username].astro b/web/src/pages/u/[username].astro index 6e489f2..c8250e7 100644 --- a/web/src/pages/u/[username].astro +++ b/web/src/pages/u/[username].astro @@ -94,7 +94,16 @@ const user = await Astro.locals.banners.try('user', async () => { }, }, }, - where: { service: { serviceVisibility: 'PUBLIC' } }, + where: { + service: { + listedAt: { + lte: new Date(), + }, + serviceVisibility: { + in: ['PUBLIC', 'ARCHIVED'], + }, + }, + }, orderBy: { createdAt: 'desc' }, take: 5, },