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.
|
||||
|
||||
## 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
|
||||
|
||||
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