Release 202505311001

This commit is contained in:
pluja
2025-05-31 10:01:35 +00:00
parent 22944fcdb3
commit 41fea45dbb
8 changed files with 89 additions and 42 deletions

View File

@@ -46,6 +46,9 @@ export const apiServiceActions = {
const service = await prisma.service.findFirst({ const service = await prisma.service.findFirst({
where: { where: {
listedAt: { lte: new Date() },
serviceVisibility: { in: ['PUBLIC', 'ARCHIVED', 'UNLISTED'] },
OR: [ OR: [
...(input.id ? ([{ id: input.id }] satisfies Prisma.ServiceWhereInput[]) : []), ...(input.id ? ([{ id: input.id }] satisfies Prisma.ServiceWhereInput[]) : []),
...(input.slug ? ([{ slug: input.slug }] satisfies Prisma.ServiceWhereInput[]) : []), ...(input.slug ? ([{ slug: input.slug }] satisfies Prisma.ServiceWhereInput[]) : []),
@@ -76,10 +79,20 @@ export const apiServiceActions = {
i2pUrls: true, i2pUrls: true,
tosUrls: true, tosUrls: true,
referral: 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({ throw new ActionError({
code: 'NOT_FOUND', code: 'NOT_FOUND',
message: 'Service not found', message: 'Service not found',
@@ -91,6 +104,7 @@ export const apiServiceActions = {
slug: service.slug, slug: service.slug,
name: service.name, name: service.name,
description: service.description, description: service.description,
serviceVisibility: service.serviceVisibility,
verificationStatus: service.verificationStatus, verificationStatus: service.verificationStatus,
verificationStatusInfo: pick(getVerificationStatusInfo(service.verificationStatus), [ verificationStatusInfo: pick(getVerificationStatusInfo(service.verificationStatus), [
'value', 'value',
@@ -99,9 +113,11 @@ export const apiServiceActions = {
'labelShort', 'labelShort',
'description', 'description',
]), ]),
verifiedAt: service.verifiedAt,
kycLevel: service.kycLevel, kycLevel: service.kycLevel,
kycLevelInfo: pick(getKycLevelInfo(service.kycLevel.toString()), ['value', 'name', 'description']), kycLevelInfo: pick(getKycLevelInfo(service.kycLevel.toString()), ['value', 'name', 'description']),
categories: service.categories, categories: service.categories,
listedAt: service.listedAt,
serviceUrls: [...service.serviceUrls, ...service.onionUrls, ...service.i2pUrls].map( serviceUrls: [...service.serviceUrls, ...service.onionUrls, ...service.i2pUrls].map(
(url) => url + (service.referral ?? '') (url) => url + (service.referral ?? '')
), ),

View File

@@ -1,10 +1,4 @@
import { import { Currency } from '@prisma/client'
Currency,
ServiceSuggestionStatus,
ServiceSuggestionType,
ServiceVisibility,
VerificationStatus,
} from '@prisma/client'
import { z } from 'astro/zod' import { z } from 'astro/zod'
import { ActionError } from 'astro:actions' import { ActionError } from 'astro:actions'
import { formatDistanceStrict } from 'date-fns' import { formatDistanceStrict } from 'date-fns'
@@ -118,9 +112,9 @@ export const serviceSuggestionActions = {
const serviceSuggestion = await prisma.serviceSuggestion.create({ const serviceSuggestion = await prisma.serviceSuggestion.create({
data: { data: {
type: ServiceSuggestionType.EDIT_SERVICE, type: 'EDIT_SERVICE',
notes: combinedNotes, notes: combinedNotes,
status: ServiceSuggestionStatus.PENDING, status: 'PENDING',
userId: context.locals.user.id, userId: context.locals.user.id,
serviceId: service.id, serviceId: service.id,
}, },
@@ -229,12 +223,12 @@ export const serviceSuggestionActions = {
kycLevel: input.kycLevel, kycLevel: input.kycLevel,
acceptedCurrencies: input.acceptedCurrencies, acceptedCurrencies: input.acceptedCurrencies,
imageUrl, imageUrl,
verificationStatus: VerificationStatus.COMMUNITY_CONTRIBUTED, verificationStatus: 'COMMUNITY_CONTRIBUTED',
overallScore: 0, overallScore: 0,
privacyScore: 0, privacyScore: 0,
trustScore: 0, trustScore: 0,
listedAt: new Date(), listedAt: new Date(),
serviceVisibility: ServiceVisibility.UNLISTED, serviceVisibility: 'UNLISTED',
categories: { categories: {
connect: input.categories.map((id) => ({ id })), connect: input.categories.map((id) => ({ id })),
}, },
@@ -250,8 +244,8 @@ export const serviceSuggestionActions = {
const serviceSuggestion = await tx.serviceSuggestion.create({ const serviceSuggestion = await tx.serviceSuggestion.create({
data: { data: {
notes: input.notes, notes: input.notes,
type: ServiceSuggestionType.CREATE_SERVICE, type: 'CREATE_SERVICE',
status: ServiceSuggestionStatus.PENDING, status: 'PENDING',
userId: context.locals.user.id, userId: context.locals.user.id,
serviceId: service.id, serviceId: service.id,
}, },

View File

@@ -15,17 +15,31 @@ const links = [
icon: 'ri:git-repository-line', icon: 'ri:git-repository-line',
external: true, 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, href: '/docs/api',
label: 'Tor', label: 'API',
icon: 'onion', icon: 'ri:plug-line',
external: true, external: false,
},
{
href: I2P_ADDRESS,
label: 'I2P',
icon: 'i2p',
external: true,
}, },
{ {
href: '/about', href: '/about',

View File

@@ -65,7 +65,7 @@ export const {
value: 'ARCHIVED', value: 'ARCHIVED',
slug: 'archived', slug: 'archived',
label: 'Archived', label: 'Archived',
description: 'No longer operational', description: 'No longer operational.',
longDescription: longDescription:
'Archived service, no longer exists or ceased operations. Information may be outdated.', 'Archived service, no longer exists or ceased operations. Information may be outdated.',
icon: 'ri:archive-line', icon: 'ri:archive-line',

View File

@@ -1,8 +1,8 @@
--- ---
import { RELEASE_DATE, RELEASE_NUMBER } from 'astro:env/server' import { RELEASE_DATE, RELEASE_NUMBER } from 'astro:env/server'
import TimeFormatted from '../../components/TimeFormatted.astro'
import MiniLayout from '../../layouts/MiniLayout.astro' import MiniLayout from '../../layouts/MiniLayout.astro'
import { timeAgo } from '../../lib/timeAgo'
const releaseDate = const releaseDate =
RELEASE_DATE && !isNaN(new Date(RELEASE_DATE).getTime()) ? new Date(RELEASE_DATE) : undefined RELEASE_DATE && !isNaN(new Date(RELEASE_DATE).getTime()) ? new Date(RELEASE_DATE) : undefined
@@ -37,7 +37,7 @@ const releaseDate =
{ {
!!releaseDate && ( !!releaseDate && (
<p class="text-day-500 mt-2"> <p class="text-day-500 mt-2">
(<TimeFormatted date={releaseDate} hourPrecision daysUntilDate={Infinity} />) (<time datetime={releaseDate.toISOString()}>{timeAgo.format(releaseDate, 'round')}</time>)
</p> </p>
) )
} }

View File

@@ -10,6 +10,7 @@ icon: 'ri:plug-line'
import { SOURCE_CODE_URL } from 'astro:env/server' import { SOURCE_CODE_URL } from 'astro:env/server'
import { kycLevels } from '../../constants/kycLevels' import { kycLevels } from '../../constants/kycLevels'
import { verificationStatuses } from '../../constants/verificationStatus' import { verificationStatuses } from '../../constants/verificationStatus'
import { serviceVisibilities } from '../../constants/serviceVisibility'
Access basic service data via our public API. Access basic service data via our public API.
@@ -41,6 +42,7 @@ type ServiceResponse = {
slug: string slug: string
name: string name: string
description: string description: string
serviceVisibility: 'PUBLIC' | 'ARCHIVED' | 'UNLISTED'
verificationStatus: 'VERIFICATION_SUCCESS' | 'APPROVED' | 'COMMUNITY_CONTRIBUTED' | 'VERIFICATION_FAILED' verificationStatus: 'VERIFICATION_SUCCESS' | 'APPROVED' | 'COMMUNITY_CONTRIBUTED' | 'VERIFICATION_FAILED'
verificationStatusInfo: { verificationStatusInfo: {
value: 'VERIFICATION_SUCCESS' | 'APPROVED' | 'COMMUNITY_CONTRIBUTED' | 'VERIFICATION_FAILED' value: 'VERIFICATION_SUCCESS' | 'APPROVED' | 'COMMUNITY_CONTRIBUTED' | 'VERIFICATION_FAILED'
@@ -49,6 +51,7 @@ type ServiceResponse = {
labelShort: string labelShort: string
description: string description: string
} }
verifiedAt: Date | null
kycLevel: 0 | 1 | 2 | 3 | 4 kycLevel: 0 | 1 | 2 | 3 | 4
kycLevelInfo: { kycLevelInfo: {
value: 0 | 1 | 2 | 3 | 4 value: 0 | 1 | 2 | 3 | 4
@@ -59,13 +62,14 @@ type ServiceResponse = {
name: string name: string
slug: string slug: string
}[] }[]
listedAt: Date
serviceUrls: string[] serviceUrls: string[]
tosUrls: string[] tosUrls: string[]
kycnotmeUrl: `https://kycnot.me/service/${service.slug}` kycnotmeUrl: `https://kycnot.me/service/${service.slug}`
} }
``` ```
### KYC Levels #### KYC Levels
<ul> <ul>
{kycLevels.map((level) => ( {kycLevels.map((level) => (
@@ -75,7 +79,7 @@ type ServiceResponse = {
))} ))}
</ul> </ul>
### Verification Status #### Verification Status
<ul> <ul>
{verificationStatuses.map((status) => ( {verificationStatuses.map((status) => (
@@ -85,7 +89,19 @@ type ServiceResponse = {
))} ))}
</ul> </ul>
### Example Request #### Service Visibility
<ul>
{serviceVisibilities.filter((visibility) => visibility.value === 'PUBLIC' || visibility.value === 'ARCHIVED' || visibility.value === 'UNLISTED').map((visibility) => (
<li key={visibility.value}>
<strong>{visibility.value}</strong>: {visibility.longDescription}
</li>
))}
</ul>
### Examples
#### Request
```zsh ```zsh
curl -X QUERY https://kycnot.me/api/v1/service/get \ 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"}' -d '{"slug": "my-example-service"}'
``` ```
### Example Response #### Response
```json ```json
{ {
"name": "My Example Service", "name": "My Example Service",
"description": "This is a description of my example service", "description": "This is a description of my example service",
"serviceVisibility": "PUBLIC",
"verificationStatus": "VERIFICATION_SUCCESS", "verificationStatus": "VERIFICATION_SUCCESS",
"verificationStatusInfo": { "verificationStatusInfo": {
"value": "VERIFICATION_SUCCESS", "value": "VERIFICATION_SUCCESS",
@@ -107,6 +124,7 @@ curl -X QUERY https://kycnot.me/api/v1/service/get \
"labelShort": "Verified", "labelShort": "Verified",
"description": "Thoroughly tested and verified by the team. But things might change, this is not a guarantee." "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, "kycLevel": 0,
"kycLevelInfo": { "kycLevelInfo": {
"value": 0, "value": 0,
@@ -119,6 +137,7 @@ curl -X QUERY https://kycnot.me/api/v1/service/get \
"slug": "exchange" "slug": "exchange"
} }
], ],
"listedAt": "2025-05-31T19:09:18.043Z",
"serviceUrls": [ "serviceUrls": [
"https://example.com", "https://example.com",
"http://c9ikae0fdidzh1ufrzp022e5uqfvz6ofxlkycz59cvo6fdxjgx7ekl9e.onion" "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 **404 Not Found**: Service not found

View File

@@ -218,16 +218,12 @@ const servicesQMatch = filters.q ? await findServicesBySimilarity(filters.q) : n
const where = { const where = {
id: servicesQMatch ? { in: servicesQMatch.map(({ id }) => id) } : undefined, id: servicesQMatch ? { in: servicesQMatch.map(({ id }) => id) } : undefined,
listedAt: { listedAt: { lte: new Date() },
lte: new Date(),
},
categories: filters.categories.length ? { some: { slug: { in: filters.categories } } } : undefined, categories: filters.categories.length ? { some: { slug: { in: filters.categories } } } : undefined,
verificationStatus: { verificationStatus: {
in: includeScams ? uniq([...filters.verification, 'VERIFICATION_FAILED'] as const) : filters.verification, in: includeScams ? uniq([...filters.verification, 'VERIFICATION_FAILED'] as const) : filters.verification,
}, },
serviceVisibility: { serviceVisibility: { in: ['PUBLIC', 'ARCHIVED'] },
in: ['PUBLIC', 'ARCHIVED'],
},
overallScore: { gte: filters['min-score'] }, overallScore: { gte: filters['min-score'] },
acceptedCurrencies: filters.currencies.length acceptedCurrencies: filters.currencies.length
? filters['currency-mode'] === 'and' ? filters['currency-mode'] === 'and'
@@ -319,9 +315,8 @@ const [categories, [services, totalServices], countCommunityOnly, attributes] =
select: { select: {
services: { services: {
where: { where: {
serviceVisibility: { serviceVisibility: { in: ['PUBLIC', 'ARCHIVED'] },
in: ['PUBLIC', 'ARCHIVED'], listedAt: { lte: new Date() },
},
}, },
}, },
}, },

View File

@@ -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' }, orderBy: { createdAt: 'desc' },
take: 5, take: 5,
}, },