Files
Sankofa/api/src/services/api-marketplace.ts
T
defiQUG 9daf1fd378 Apply Composer changes: comprehensive API updates, migrations, middleware, and infrastructure improvements
- Add comprehensive database migrations (001-024) for schema evolution
- Enhance API schema with expanded type definitions and resolvers
- Add new middleware: audit logging, rate limiting, MFA enforcement, security, tenant auth
- Implement new services: AI optimization, billing, blockchain, compliance, marketplace
- Add adapter layer for cloud integrations (Cloudflare, Kubernetes, Proxmox, storage)
- Update Crossplane provider with enhanced VM management capabilities
- Add comprehensive test suite for API endpoints and services
- Update frontend components with improved GraphQL subscriptions and real-time updates
- Enhance security configurations and headers (CSP, CORS, etc.)
- Update documentation and configuration files
- Add new CI/CD workflows and validation scripts
- Implement design system improvements and UI enhancements
2025-12-12 18:01:35 -08:00

449 lines
12 KiB
TypeScript

import { getDb } from '../db'
import { Context } from '../types/context'
import { GraphQLError } from 'graphql'
import { logger } from '../lib/logger'
import crypto from 'crypto'
export interface APIMarketplaceListing {
id: string
name: string
description: string
provider: string
endpoint: string
documentationUrl: string | null
category: string
pricing: {
model: string
basePrice: number | null
perRequestPrice: number | null
freeTier: {
requestsPerMonth: number
features: string[]
} | null
}
rating: number
reviewCount: number
requestCount: number
status: string
createdAt: Date
updatedAt: Date
}
export interface APISubscription {
id: string
listingId: string
userId: string
status: string
apiKey: string | null
createdAt: Date
updatedAt: Date
}
export async function getAPIMarketplaceListings(filter?: {
category?: string
search?: string
status?: string
limit?: number
offset?: number
}): Promise<APIMarketplaceListing[]> {
const db = getDb()
const conditions: string[] = []
const params: any[] = []
let paramIndex = 1
if (filter?.category) {
conditions.push(`category = $${paramIndex++}`)
params.push(filter.category)
}
if (filter?.search) {
conditions.push(`(name ILIKE $${paramIndex} OR description ILIKE $${paramIndex})`)
params.push(`%${filter.search}%`)
paramIndex++
}
if (filter?.status) {
conditions.push(`status = $${paramIndex++}`)
params.push(filter.status)
}
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
const limit = filter?.limit || 50
const offset = filter?.offset || 0
const result = await db.query(`
SELECT
l.*,
COALESCE(AVG(r.rating), 0) as rating,
COUNT(DISTINCT r.id) as review_count,
COUNT(DISTINCT s.id) as request_count
FROM api_marketplace_listings l
LEFT JOIN api_marketplace_reviews r ON r.listing_id = l.id
LEFT JOIN api_subscriptions s ON s.listing_id = l.id
${whereClause}
GROUP BY l.id
ORDER BY l.created_at DESC
LIMIT $${paramIndex++} OFFSET $${paramIndex++}
`, [...params, limit, offset])
return result.rows.map(row => ({
id: row.id,
name: row.name,
description: row.description,
provider: row.provider,
endpoint: row.endpoint,
documentationUrl: row.documentation_url,
category: row.category,
pricing: {
model: row.pricing_model,
basePrice: row.base_price,
perRequestPrice: row.per_request_price,
freeTier: row.free_tier ? JSON.parse(row.free_tier) : null,
},
rating: parseFloat(row.rating) || 0,
reviewCount: parseInt(row.review_count) || 0,
requestCount: parseInt(row.request_count) || 0,
status: row.status,
createdAt: new Date(row.created_at),
updatedAt: new Date(row.updated_at),
}))
}
export async function getAPIMarketplaceListing(id: string): Promise<APIMarketplaceListing | null> {
const db = getDb()
const result = await db.query(`
SELECT
l.*,
COALESCE(AVG(r.rating), 0) as rating,
COUNT(DISTINCT r.id) as review_count,
COUNT(DISTINCT s.id) as request_count
FROM api_marketplace_listings l
LEFT JOIN api_marketplace_reviews r ON r.listing_id = l.id
LEFT JOIN api_subscriptions s ON s.listing_id = l.id
WHERE l.id = $1
GROUP BY l.id
`, [id])
if (result.rows.length === 0) {
return null
}
const row = result.rows[0]
return {
id: row.id,
name: row.name,
description: row.description,
provider: row.provider,
endpoint: row.endpoint,
documentationUrl: row.documentation_url,
category: row.category,
pricing: {
model: row.pricing_model,
basePrice: row.base_price,
perRequestPrice: row.per_request_price,
freeTier: row.free_tier ? JSON.parse(row.free_tier) : null,
},
rating: parseFloat(row.rating) || 0,
reviewCount: parseInt(row.review_count) || 0,
requestCount: parseInt(row.request_count) || 0,
status: row.status,
createdAt: new Date(row.created_at),
updatedAt: new Date(row.updated_at),
}
}
export async function getMyAPISubscriptions(context: Context): Promise<APISubscription[]> {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
const db = getDb()
const result = await db.query(
'SELECT * FROM api_subscriptions WHERE user_id = $1 ORDER BY created_at DESC',
[context.user.id]
)
return result.rows.map(row => ({
id: row.id,
listingId: row.listing_id,
userId: row.user_id,
status: row.status,
apiKey: row.api_key,
createdAt: new Date(row.created_at),
updatedAt: new Date(row.updated_at),
}))
}
export async function createAPIMarketplaceListing(
context: Context,
input: {
name: string
description: string
provider: string
endpoint: string
documentationUrl?: string
category: string
pricing: {
model: string
basePrice?: number
perRequestPrice?: number
freeTier?: {
requestsPerMonth: number
features: string[]
}
}
}
): Promise<APIMarketplaceListing> {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
const db = getDb()
const result = await db.query(
`INSERT INTO api_marketplace_listings
(name, description, provider, endpoint, documentation_url, category, pricing_model, base_price, per_request_price, free_tier, status, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'PENDING', NOW(), NOW())
RETURNING *`,
[
input.name,
input.description,
input.provider,
input.endpoint,
input.documentationUrl || null,
input.category,
input.pricing.model,
input.pricing.basePrice || null,
input.pricing.perRequestPrice || null,
input.pricing.freeTier ? JSON.stringify(input.pricing.freeTier) : null,
]
)
const row = result.rows[0]
return {
id: row.id,
name: row.name,
description: row.description,
provider: row.provider,
endpoint: row.endpoint,
documentationUrl: row.documentation_url,
category: row.category,
pricing: {
model: row.pricing_model,
basePrice: row.base_price,
perRequestPrice: row.per_request_price,
freeTier: row.free_tier ? JSON.parse(row.free_tier) : null,
},
rating: 0,
reviewCount: 0,
requestCount: 0,
status: row.status,
createdAt: new Date(row.created_at),
updatedAt: new Date(row.updated_at),
}
}
export async function updateAPIMarketplaceListing(
context: Context,
id: string,
input: {
name?: string
description?: string
status?: string
pricing?: {
model: string
basePrice?: number
perRequestPrice?: number
freeTier?: {
requestsPerMonth: number
features: string[]
}
}
}
): Promise<APIMarketplaceListing> {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
const db = getDb()
const updates: string[] = []
const params: any[] = []
let paramIndex = 1
if (input.name) {
updates.push(`name = $${paramIndex++}`)
params.push(input.name)
}
if (input.description) {
updates.push(`description = $${paramIndex++}`)
params.push(input.description)
}
if (input.status) {
updates.push(`status = $${paramIndex++}`)
params.push(input.status)
}
if (input.pricing) {
updates.push(`pricing_model = $${paramIndex++}`)
params.push(input.pricing.model)
if (input.pricing.basePrice !== undefined) {
updates.push(`base_price = $${paramIndex++}`)
params.push(input.pricing.basePrice)
}
if (input.pricing.perRequestPrice !== undefined) {
updates.push(`per_request_price = $${paramIndex++}`)
params.push(input.pricing.perRequestPrice)
}
if (input.pricing.freeTier) {
updates.push(`free_tier = $${paramIndex++}`)
params.push(JSON.stringify(input.pricing.freeTier))
}
}
updates.push(`updated_at = NOW()`)
params.push(id)
const result = await db.query(
`UPDATE api_marketplace_listings SET ${updates.join(', ')} WHERE id = $${paramIndex} RETURNING *`,
params
)
if (result.rows.length === 0) {
throw new GraphQLError('Listing not found', {
extensions: { code: 'NOT_FOUND' },
})
}
const row = result.rows[0]
const ratingResult = await db.query(
'SELECT AVG(rating) as rating, COUNT(*) as count FROM api_marketplace_reviews WHERE listing_id = $1',
[id]
)
return {
id: row.id,
name: row.name,
description: row.description,
provider: row.provider,
endpoint: row.endpoint,
documentationUrl: row.documentation_url,
category: row.category,
pricing: {
model: row.pricing_model,
basePrice: row.base_price,
perRequestPrice: row.per_request_price,
freeTier: row.free_tier ? JSON.parse(row.free_tier) : null,
},
rating: parseFloat(ratingResult.rows[0]?.rating) || 0,
reviewCount: parseInt(ratingResult.rows[0]?.count) || 0,
requestCount: 0,
status: row.status,
createdAt: new Date(row.created_at),
updatedAt: new Date(row.updated_at),
}
}
export async function deleteAPIMarketplaceListing(context: Context, id: string): Promise<boolean> {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
const db = getDb()
await db.query('DELETE FROM api_subscriptions WHERE listing_id = $1', [id])
await db.query('DELETE FROM api_marketplace_reviews WHERE listing_id = $1', [id])
await db.query('DELETE FROM api_marketplace_listings WHERE id = $1', [id])
return true
}
export async function subscribeToAPI(context: Context, listingId: string): Promise<APISubscription> {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
const db = getDb()
// Check if already subscribed
const existing = await db.query(
'SELECT * FROM api_subscriptions WHERE user_id = $1 AND listing_id = $2',
[context.user.id, listingId]
)
if (existing.rows.length > 0) {
throw new GraphQLError('Already subscribed', {
extensions: { code: 'VALIDATION_ERROR' },
})
}
// Generate API key
const apiKey = `sk_${crypto.randomBytes(32).toString('hex')}`
const result = await db.query(
`INSERT INTO api_subscriptions (listing_id, user_id, status, api_key, created_at, updated_at)
VALUES ($1, $2, 'ACTIVE', $3, NOW(), NOW())
RETURNING *`,
[listingId, context.user.id, apiKey]
)
const row = result.rows[0]
return {
id: row.id,
listingId: row.listing_id,
userId: row.user_id,
status: row.status,
apiKey: row.api_key,
createdAt: new Date(row.created_at),
updatedAt: new Date(row.updated_at),
}
}
export async function unsubscribeFromAPI(context: Context, subscriptionId: string): Promise<boolean> {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
const db = getDb()
// Check ownership
const sub = await db.query(
'SELECT user_id FROM api_subscriptions WHERE id = $1',
[subscriptionId]
)
if (sub.rows.length === 0) {
throw new GraphQLError('Subscription not found', {
extensions: { code: 'NOT_FOUND' },
})
}
if (sub.rows[0].user_id !== context.user.id) {
throw new GraphQLError('Permission denied', {
extensions: { code: 'FORBIDDEN' },
})
}
await db.query(
'UPDATE api_subscriptions SET status = $1, updated_at = NOW() WHERE id = $2',
['CANCELLED', subscriptionId]
)
return true
}