Release 202505311001
This commit is contained in:
@@ -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 ?? '')
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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() },
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user