Implement backend API and database services in Docker setup
- Added a new `api` service for the NestJS backend, including health checks and dependencies on PostgreSQL, Redis, and MinIO. - Introduced PostgreSQL and Redis services with health checks and configurations for data persistence. - Added MinIO for S3-compatible object storage and a one-shot service to initialize required buckets. - Updated the Nginx configuration to proxy requests to the new backend API and MinIO storage. - Enhanced the Dockerfile to support the new API environment variables and configurations. - Updated the `package.json` and `package-lock.json` to include new dependencies for QR code generation and other utilities. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
190
src/services/indeehub-api.service.ts
Normal file
190
src/services/indeehub-api.service.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* IndeeHub Self-Hosted API Service
|
||||
*
|
||||
* Dedicated API client for our self-hosted NestJS backend.
|
||||
* Uses the /api/ proxy configured in nginx.
|
||||
* Auth tokens are managed by nip98.service.ts.
|
||||
*/
|
||||
|
||||
import axios, { type AxiosInstance } from 'axios'
|
||||
import { indeehubApiConfig } from '../config/api.config'
|
||||
import { nip98Service } from './nip98.service'
|
||||
|
||||
class IndeehubApiService {
|
||||
private client: AxiosInstance
|
||||
|
||||
constructor() {
|
||||
this.client = axios.create({
|
||||
baseURL: indeehubApiConfig.baseURL,
|
||||
timeout: indeehubApiConfig.timeout,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
// Attach JWT token from NIP-98 session
|
||||
this.client.interceptors.request.use((config) => {
|
||||
const token = nip98Service.accessToken
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
})
|
||||
|
||||
// Auto-refresh on 401
|
||||
this.client.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
const originalRequest = error.config
|
||||
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||
originalRequest._retry = true
|
||||
const newToken = await nip98Service.refresh()
|
||||
if (newToken) {
|
||||
originalRequest.headers.Authorization = `Bearer ${newToken}`
|
||||
return this.client(originalRequest)
|
||||
}
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic typed GET request
|
||||
*/
|
||||
async get<T>(url: string): Promise<T> {
|
||||
const response = await this.client.get<T>(url)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic typed POST request
|
||||
*/
|
||||
async post<T>(url: string, data?: any): Promise<T> {
|
||||
const response = await this.client.post<T>(url, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic typed PATCH request
|
||||
*/
|
||||
async patch<T>(url: string, data?: any): Promise<T> {
|
||||
const response = await this.client.patch<T>(url, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic typed DELETE request
|
||||
*/
|
||||
async delete<T = void>(url: string): Promise<T> {
|
||||
const response = await this.client.delete<T>(url)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the API is reachable
|
||||
*/
|
||||
async healthCheck(): Promise<boolean> {
|
||||
try {
|
||||
await this.client.get('/nostr-auth/health', { timeout: 5000 })
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all published projects
|
||||
*/
|
||||
async getProjects(filters?: {
|
||||
status?: string
|
||||
type?: string
|
||||
genre?: string
|
||||
limit?: number
|
||||
offset?: number
|
||||
}): Promise<any[]> {
|
||||
const params = new URLSearchParams()
|
||||
if (filters?.status) params.append('status', filters.status)
|
||||
if (filters?.type) params.append('type', filters.type)
|
||||
if (filters?.genre) params.append('genre', filters.genre)
|
||||
if (filters?.limit) params.append('limit', String(filters.limit))
|
||||
if (filters?.offset) params.append('offset', String(filters.offset))
|
||||
|
||||
const url = `/projects${params.toString() ? `?${params.toString()}` : ''}`
|
||||
const response = await this.client.get(url)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single project by ID
|
||||
*/
|
||||
async getProject(id: string): Promise<any> {
|
||||
const response = await this.client.get(`/projects/${id}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get streaming URL for a content item
|
||||
* Returns different data based on deliveryMode (native vs partner)
|
||||
*/
|
||||
async getStreamingUrl(contentId: string): Promise<{
|
||||
url: string
|
||||
deliveryMode: 'native' | 'partner'
|
||||
keyUrl?: string
|
||||
drmToken?: string
|
||||
}> {
|
||||
const response = await this.client.get(`/contents/${contentId}/stream`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's library items
|
||||
*/
|
||||
async getLibrary(): Promise<any[]> {
|
||||
const response = await this.client.get('/library')
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a project to user's library
|
||||
*/
|
||||
async addToLibrary(projectId: string): Promise<any> {
|
||||
const response = await this.client.post('/library', { projectId })
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a project from user's library
|
||||
*/
|
||||
async removeFromLibrary(projectId: string): Promise<void> {
|
||||
await this.client.delete(`/library/${projectId}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user profile (requires auth)
|
||||
*/
|
||||
async getMe(): Promise<any> {
|
||||
const response = await this.client.get('/auth/me')
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get genres
|
||||
*/
|
||||
async getGenres(): Promise<any[]> {
|
||||
const response = await this.client.get('/genres')
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CDN URL for a storage path
|
||||
*/
|
||||
getCdnUrl(path: string): string {
|
||||
if (!path) return ''
|
||||
if (path.startsWith('http')) return path
|
||||
if (path.startsWith('/')) return path
|
||||
return `${indeehubApiConfig.cdnURL}/${path}`
|
||||
}
|
||||
}
|
||||
|
||||
export const indeehubApiService = new IndeehubApiService()
|
||||
Reference in New Issue
Block a user