2025-05-19 10:23:36 +00:00
import { orderBy } from 'lodash-es'
import { getAttributeCategoryInfo } from '../constants/attributeCategories'
import { getAttributeTypeInfo } from '../constants/attributeTypes'
2025-06-11 22:28:53 +00:00
import { kycLevelClarifications } from '../constants/kycLevelClarifications'
2025-06-11 10:07:51 +00:00
import { kycLevels } from '../constants/kycLevels'
2025-05-25 11:21:35 +00:00
import { serviceVisibilitiesById } from '../constants/serviceVisibility'
2025-05-19 10:23:36 +00:00
import { READ_MORE_SENTENCE_LINK , verificationStatusesByValue } from '../constants/verificationStatus'
2025-06-15 13:18:22 +00:00
import { formatDaysAgo } from './timeAgo'
2025-05-19 10:23:36 +00:00
import type { Prisma } from '@prisma/client'
2025-06-10 17:42:42 +00:00
type NonDbAttribute = Prisma . AttributeGetPayload < {
select : {
title : true
type : true
category : true
description : true
privacyPoints : true
trustPoints : true
}
} > & {
slug : string
links : {
url : string
label : string
icon : string
} [ ]
}
2025-06-11 10:07:51 +00:00
type NonDbAttributeFull = NonDbAttribute & {
2025-06-10 17:42:42 +00:00
customize : (
service : Prisma.ServiceGetPayload < {
select : {
verificationStatus : true
serviceVisibility : true
2025-06-14 18:56:58 +00:00
isRecentlyApproved : true
approvedAt : true
2025-06-10 17:42:42 +00:00
createdAt : true
tosReviewAt : true
tosReview : true
onionUrls : true
i2pUrls : true
acceptedCurrencies : true
2025-06-11 10:07:51 +00:00
kycLevel : true
kycLevelClarification : true
2025-06-10 17:42:42 +00:00
}
} >
) = > Partial < Pick < NonDbAttribute , 'description' | 'title' > > & {
show : boolean
}
2025-06-11 10:07:51 +00:00
}
export const nonDbAttributes : NonDbAttributeFull [ ] = [
2025-06-10 17:42:42 +00:00
{
slug : 'verification-verified' ,
title : 'Verified' ,
type : 'GOOD' ,
category : 'TRUST' ,
description : ` ${ verificationStatusesByValue . VERIFICATION_SUCCESS . description } ${ READ_MORE_SENTENCE_LINK } ` ,
privacyPoints : verificationStatusesByValue.VERIFICATION_SUCCESS.privacyPoints ,
trustPoints : verificationStatusesByValue.VERIFICATION_SUCCESS.trustPoints ,
links : [
{
url : '/?verification=verified' ,
label : 'Search with this' ,
icon : 'ri:search-line' ,
} ,
] ,
customize : ( service ) = > ( {
show : service.verificationStatus === 'VERIFICATION_SUCCESS' ,
description : ` ${ verificationStatusesByValue . VERIFICATION_SUCCESS . description } ${ READ_MORE_SENTENCE_LINK } \ n \ nCheck out the [proof](#verification). ` ,
} ) ,
} ,
{
slug : 'verification-approved' ,
title : 'Approved' ,
type : 'INFO' ,
category : 'TRUST' ,
description : ` ${ verificationStatusesByValue . APPROVED . description } ${ READ_MORE_SENTENCE_LINK } ` ,
privacyPoints : verificationStatusesByValue.APPROVED.privacyPoints ,
trustPoints : verificationStatusesByValue.APPROVED.trustPoints ,
links : [
{
url : '/?verification=verified&verification=approved' ,
label : 'Search with this' ,
icon : 'ri:search-line' ,
} ,
] ,
customize : ( service ) = > ( {
show : service.verificationStatus === 'APPROVED' ,
} ) ,
} ,
{
slug : 'verification-community-contributed' ,
title : 'Community contributed' ,
type : 'WARNING' ,
category : 'TRUST' ,
description : ` ${ verificationStatusesByValue . COMMUNITY_CONTRIBUTED . description } ${ READ_MORE_SENTENCE_LINK } ` ,
privacyPoints : verificationStatusesByValue.COMMUNITY_CONTRIBUTED.privacyPoints ,
trustPoints : verificationStatusesByValue.COMMUNITY_CONTRIBUTED.trustPoints ,
links : [
{
url : '/?verification=community' ,
label : 'With this' ,
icon : 'ri:search-line' ,
} ,
{
url : '/?verification=verified&verification=approved' ,
label : 'Without this' ,
icon : 'ri:search-line' ,
} ,
] ,
customize : ( service ) = > ( {
show : service.verificationStatus === 'COMMUNITY_CONTRIBUTED' ,
} ) ,
} ,
{
slug : 'verification-scam' ,
title : 'Is SCAM' ,
type : 'BAD' ,
category : 'TRUST' ,
description : ` ${ verificationStatusesByValue . VERIFICATION_FAILED . description } ${ READ_MORE_SENTENCE_LINK } ` ,
privacyPoints : verificationStatusesByValue.VERIFICATION_FAILED.privacyPoints ,
trustPoints : verificationStatusesByValue.VERIFICATION_FAILED.trustPoints ,
links : [
{
url : '/?verification=scam' ,
label : 'With this' ,
icon : 'ri:search-line' ,
} ,
{
url : '/?verification=verified&verification=approved' ,
label : 'Without this' ,
icon : 'ri:search-line' ,
} ,
] ,
customize : ( service ) = > ( {
show : service.verificationStatus === 'VERIFICATION_FAILED' ,
description : ` ${ verificationStatusesByValue . VERIFICATION_FAILED . description } ${ READ_MORE_SENTENCE_LINK } \ n \ nCheck out the [proof](#verification). ` ,
} ) ,
} ,
2025-06-11 10:07:51 +00:00
. . . kycLevels . map < NonDbAttributeFull > ( ( kycLevel ) = > ( {
slug : ` kyc-level- ${ kycLevel . id } ` ,
title : kycLevel.name ,
type : kycLevel . attributeType ,
category : 'PRIVACY' ,
description : kycLevel.description ,
privacyPoints : kycLevel.privacyPoints ,
trustPoints : 0 ,
links : [
{
url : ` /?max-kyc= ${ kycLevel . id } ` ,
label : 'With this or better' ,
icon : 'ri:search-line' ,
} ,
] ,
2025-06-11 22:28:53 +00:00
customize : ( service ) = > ( {
show : service.kycLevel === kycLevel . value ,
} ) ,
2025-06-11 10:07:51 +00:00
} ) ) ,
2025-06-11 21:08:32 +00:00
. . . kycLevelClarifications
. filter ( ( clarification ) = > clarification . value !== 'NONE' )
. map < NonDbAttributeFull > ( ( clarification ) = > ( {
slug : ` kyc-clarification- ${ clarification . slug } ` ,
title : ` KYC ${ clarification . label } ` ,
type : clarification . attributeType ,
category : 'PRIVACY' ,
description : clarification.description ,
privacyPoints : clarification.privacyPoints ,
trustPoints : 0 ,
links : [ ] ,
customize : ( service ) = > ( {
show : service.kycLevelClarification === clarification . value ,
} ) ,
} ) ) ,
2025-06-10 17:42:42 +00:00
{
slug : 'archived' ,
title : serviceVisibilitiesById.ARCHIVED.label ,
type : 'WARNING' ,
category : 'TRUST' ,
description : serviceVisibilitiesById.ARCHIVED.longDescription ,
privacyPoints : 0 ,
trustPoints : 0 ,
links : [ ] ,
customize : ( service ) = > ( {
show : service.serviceVisibility === 'ARCHIVED' ,
} ) ,
} ,
{
2025-06-14 18:56:58 +00:00
slug : 'recently-approved' ,
title : 'Recently approved' ,
2025-06-10 17:42:42 +00:00
type : 'WARNING' ,
category : 'TRUST' ,
2025-06-14 18:56:58 +00:00
description : 'Approved on KYCnot.me less than 15 days ago. Proceed with caution.' ,
2025-06-10 17:42:42 +00:00
privacyPoints : 0 ,
trustPoints : - 5 ,
links : [ ] ,
customize : ( service ) = > ( {
2025-06-14 18:56:58 +00:00
show : service.isRecentlyApproved ,
2025-06-15 13:18:22 +00:00
description : ` Approved on KYCnot.me less than 15 days ago ${ service . approvedAt ? ` ( ${ formatDaysAgo ( service . approvedAt ) } ) ` : '' } . Proceed with caution. ` ,
2025-06-10 17:42:42 +00:00
} ) ,
} ,
{
slug : 'cannot-analyse-tos' ,
title : "Can't analyse ToS" ,
type : 'WARNING' ,
category : 'TRUST' ,
description :
'The Terms of Service page is not analyable by our AI. Possible reasons may be: captchas, client side rendering, DDoS protections, or non-text format.' ,
privacyPoints : 0 ,
trustPoints : - 3 ,
links : [ ] ,
customize : ( service ) = > ( {
show : service.tosReviewAt !== null && service . tosReview === null ,
} ) ,
} ,
{
2025-06-14 18:56:58 +00:00
slug : 'has-onion-or-i2p-urls' ,
title : 'Has Onion or I2P URLs' ,
2025-06-10 17:42:42 +00:00
type : 'GOOD' ,
category : 'PRIVACY' ,
2025-06-14 18:56:58 +00:00
description : 'Onion (Tor) and I2P URLs enhance privacy and anonymity.' ,
2025-06-10 17:42:42 +00:00
privacyPoints : 5 ,
trustPoints : 0 ,
links : [
{
2025-06-14 18:56:58 +00:00
url : '/?networks=onion&networks=i2p' ,
2025-06-10 17:42:42 +00:00
label : 'Search with this' ,
icon : 'ri:search-line' ,
} ,
] ,
customize : ( service ) = > ( {
2025-06-14 18:56:58 +00:00
show : service.onionUrls.length > 0 || service . i2pUrls . length > 0 ,
2025-06-10 17:42:42 +00:00
} ) ,
} ,
{
slug : 'monero-accepted' ,
title : 'Accepts Monero' ,
type : 'GOOD' ,
category : 'PRIVACY' ,
description :
'This service accepts Monero, a privacy-focused cryptocurrency that provides enhanced anonymity.' ,
privacyPoints : 5 ,
trustPoints : 0 ,
links : [
{
url : '/?currency=monero' ,
label : 'Search with this' ,
icon : 'ri:search-line' ,
} ,
] ,
customize : ( service ) = > ( {
show : service.acceptedCurrencies.includes ( 'MONERO' ) ,
} ) ,
} ,
]
2025-05-19 10:23:36 +00:00
export function sortAttributes <
T extends Prisma . AttributeGetPayload < {
select : {
title : true
privacyPoints : true
trustPoints : true
category : true
type : true
}
} > ,
> ( attributes : T [ ] ) : T [ ] {
return orderBy (
attributes ,
[
( { privacyPoints , trustPoints } ) = > ( privacyPoints + trustPoints < 0 ? 1 : 2 ) ,
( { privacyPoints , trustPoints } ) = > Math . abs ( privacyPoints + trustPoints ) ,
( { type } ) = > getAttributeTypeInfo ( type ) . order ,
( { category } ) = > getAttributeCategoryInfo ( category ) . order ,
'title' ,
] ,
[ 'asc' , 'desc' , 'asc' , 'asc' , 'asc' ]
)
}
export function makeNonDbAttributes (
2025-06-11 10:07:51 +00:00
service : Parameters < NonDbAttributeFull [ 'customize' ] > [ 0 ] ,
2025-05-19 10:23:36 +00:00
{ filter = false } : { filter? : boolean } = { }
) {
2025-06-10 17:42:42 +00:00
const attributes = nonDbAttributes . map ( ( { customize , . . . attribute } ) = > ( {
. . . attribute ,
. . . customize ( service ) ,
} ) )
2025-05-19 10:23:36 +00:00
2025-06-10 17:42:42 +00:00
if ( filter ) return attributes . filter ( ( { show } ) = > show )
2025-05-19 10:23:36 +00:00
2025-06-10 17:42:42 +00:00
return attributes
2025-05-19 10:23:36 +00:00
}