add basic API plus minor updates and fixes
This commit is contained in:
@@ -201,6 +201,41 @@ Some reviews may be spam or fake. Read comments carefully and **always do your o
|
|||||||
|
|
||||||
To **see comments waiting for moderation**, toggle the switch in the comments section. These comments show up with a yellow background and a "pending" label.
|
To **see comments waiting for moderation**, toggle the switch in the comments section. These comments show up with a yellow background and a "pending" label.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
Access basic service data via our public API.
|
||||||
|
|
||||||
|
**Attribution:** Please credit **KYCnot.me** if you use data from this API.
|
||||||
|
|
||||||
|
### `GET /api/v1/service/[id]`
|
||||||
|
|
||||||
|
Fetches details for a single service.
|
||||||
|
|
||||||
|
- **`[id]`**: Can be a service ID, slug, name, or any registered URL (including .onion/.i2p).
|
||||||
|
|
||||||
|
**Example Requests:**
|
||||||
|
|
||||||
|
```
|
||||||
|
/api/v1/service/bisq
|
||||||
|
/api/v1/service/https://bisq.network
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example Response (200 OK):**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "Bisq",
|
||||||
|
"description": "Decentralized Bitcoin exchange network.",
|
||||||
|
"kycLevel": 0,
|
||||||
|
"categories": ["exchange"],
|
||||||
|
"serviceUrls": ["https://bisq.network/"],
|
||||||
|
"onionUrls": [],
|
||||||
|
"i2pUrls": [],
|
||||||
|
"tosUrls": ["https://bisq.network/terms-of-service/"],
|
||||||
|
"kycnotmeUrl": "https://kycnot.me/service/bisq"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
If you like this project, you can **support** it through these methods:
|
If you like this project, you can **support** it through these methods:
|
||||||
|
|||||||
121
web/src/pages/api/v1/service/[...id].ts
Normal file
121
web/src/pages/api/v1/service/[...id].ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import { prisma } from '../../../../lib/prisma'
|
||||||
|
|
||||||
|
import type { Prisma } from '@prisma/client'
|
||||||
|
import type { APIRoute } from 'astro'
|
||||||
|
|
||||||
|
const MAX_ID_LENGTH = 2048
|
||||||
|
|
||||||
|
export const GET: APIRoute = async ({ params }) => {
|
||||||
|
const { id } = params
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return new Response(JSON.stringify({ error: 'ID parameter is missing' }), {
|
||||||
|
status: 400,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.length > MAX_ID_LENGTH) {
|
||||||
|
return new Response(JSON.stringify({ error: 'ID parameter is too long' }), {
|
||||||
|
status: 400,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const orConditions: Prisma.ServiceWhereInput[] = [
|
||||||
|
{ slug: id },
|
||||||
|
{ name: id },
|
||||||
|
{ serviceUrls: { has: id } },
|
||||||
|
{ onionUrls: { has: id } },
|
||||||
|
{ i2pUrls: { has: id } },
|
||||||
|
]
|
||||||
|
|
||||||
|
// Try direct ID lookup first
|
||||||
|
const numericId = parseInt(id, 10)
|
||||||
|
|
||||||
|
if (!isNaN(numericId)) {
|
||||||
|
orConditions.push({ id: numericId })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith('http://') || id.startsWith('https://')) {
|
||||||
|
let alternativeId: string
|
||||||
|
if (id.endsWith('/')) {
|
||||||
|
alternativeId = id.slice(0, -1) // Remove trailing slash
|
||||||
|
} else {
|
||||||
|
alternativeId = id + '/' // Add trailing slash
|
||||||
|
}
|
||||||
|
orConditions.push({ serviceUrls: { has: alternativeId } })
|
||||||
|
orConditions.push({ onionUrls: { has: alternativeId } })
|
||||||
|
orConditions.push({ i2pUrls: { has: alternativeId } })
|
||||||
|
} else {
|
||||||
|
// For non-HTTP/S IDs, check as is (could be a direct onion/i2p address without protocol)
|
||||||
|
orConditions.push({ serviceUrls: { has: id } })
|
||||||
|
orConditions.push({ onionUrls: { has: id } })
|
||||||
|
orConditions.push({ i2pUrls: { has: id } })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const service = await prisma.service.findFirst({
|
||||||
|
where: {
|
||||||
|
OR: orConditions,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
slug: true,
|
||||||
|
description: true,
|
||||||
|
kycLevel: true,
|
||||||
|
categories: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
slug: true,
|
||||||
|
icon: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
serviceUrls: true,
|
||||||
|
onionUrls: true,
|
||||||
|
i2pUrls: true,
|
||||||
|
tosUrls: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!service) {
|
||||||
|
return new Response(JSON.stringify({ error: 'Service not found' }), {
|
||||||
|
status: 404,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = {
|
||||||
|
name: service.name,
|
||||||
|
description: service.description,
|
||||||
|
kycLevel: service.kycLevel,
|
||||||
|
categories: service.categories.map((category) => category.slug),
|
||||||
|
serviceUrls: service.serviceUrls,
|
||||||
|
onionUrls: service.onionUrls,
|
||||||
|
i2pUrls: service.i2pUrls,
|
||||||
|
tosUrls: service.tosUrls,
|
||||||
|
kycnotmeUrl: `https://kycnot.me/service/${service.slug}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(responseData), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching service:', error)
|
||||||
|
return new Response(JSON.stringify({ error: 'Internal server error' }), {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user