mirror of
https://github.com/Permissionless-Software-Foundation/psf-bch-api-base.git
synced 2026-09-22 17:12:02 -07:00
Getting rid of placeholder controller libs
This commit is contained in:
@@ -1,241 +0,0 @@
|
||||
/*
|
||||
Nostr Relay WebSocket adapter.
|
||||
Handles WebSocket connections to Nostr relays and manages message sending/receiving.
|
||||
*/
|
||||
|
||||
import WebSocket from 'ws'
|
||||
import config from '../config/index.js'
|
||||
import wlogger from './wlogger.js'
|
||||
|
||||
class NostrRelayAdapter {
|
||||
constructor (localConfig = {}) {
|
||||
this.config = config
|
||||
this.relayUrl = localConfig.relayUrl || config.nostrRelayUrl
|
||||
this.ws = null
|
||||
this.isConnected = false
|
||||
this.reconnectAttempts = 0
|
||||
this.maxReconnectAttempts = 5
|
||||
this.reconnectDelay = 5000 // 5 seconds
|
||||
this.messageHandlers = new Map() // Map subscription_id to handlers
|
||||
this.pendingMessages = [] // Queue messages while disconnected
|
||||
this.eventResolvers = new Map() // Map event_id to promise resolvers for OK responses
|
||||
this.subscriptionHandlers = new Map() // Map subscription_id to event handlers
|
||||
|
||||
// Bind methods
|
||||
this.connect = this.connect.bind(this)
|
||||
this.disconnect = this.disconnect.bind(this)
|
||||
this.sendEvent = this.sendEvent.bind(this)
|
||||
this.sendReq = this.sendReq.bind(this)
|
||||
this.sendClose = this.sendClose.bind(this)
|
||||
this.handleMessage = this.handleMessage.bind(this)
|
||||
this.handleError = this.handleError.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
}
|
||||
|
||||
async connect () {
|
||||
if (this.ws && this.isConnected) {
|
||||
return true
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
wlogger.info(`Connecting to Nostr relay: ${this.relayUrl}`)
|
||||
this.ws = new WebSocket(this.relayUrl)
|
||||
|
||||
this.ws.on('open', () => {
|
||||
wlogger.info('Connected to Nostr relay')
|
||||
this.isConnected = true
|
||||
this.reconnectAttempts = 0
|
||||
|
||||
// Send any pending messages
|
||||
while (this.pendingMessages.length > 0) {
|
||||
const message = this.pendingMessages.shift()
|
||||
this.ws.send(JSON.stringify(message))
|
||||
}
|
||||
|
||||
resolve(true)
|
||||
})
|
||||
|
||||
this.ws.on('message', (data) => {
|
||||
try {
|
||||
const message = JSON.parse(data.toString())
|
||||
this.handleMessage(message)
|
||||
} catch (err) {
|
||||
wlogger.error('Error parsing relay message:', err)
|
||||
}
|
||||
})
|
||||
|
||||
this.ws.on('error', this.handleError)
|
||||
this.ws.on('close', this.handleClose)
|
||||
|
||||
// Timeout after 10 seconds
|
||||
setTimeout(() => {
|
||||
if (!this.isConnected) {
|
||||
reject(new Error('Connection timeout'))
|
||||
}
|
||||
}, 10000)
|
||||
} catch (err) {
|
||||
wlogger.error('Error connecting to relay:', err)
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async disconnect () {
|
||||
if (this.ws) {
|
||||
this.ws.close()
|
||||
this.ws = null
|
||||
this.isConnected = false
|
||||
wlogger.info('Disconnected from Nostr relay')
|
||||
}
|
||||
}
|
||||
|
||||
handleMessage (message) {
|
||||
if (!Array.isArray(message) || message.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const [type, ...args] = message
|
||||
|
||||
switch (type) {
|
||||
case 'EVENT':
|
||||
// ["EVENT", <subscription_id>, <event>]
|
||||
if (args.length >= 2) {
|
||||
const subscriptionId = args[0]
|
||||
const event = args[1]
|
||||
const handler = this.subscriptionHandlers.get(subscriptionId)
|
||||
if (handler) {
|
||||
handler.onEvent(event)
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 'OK':
|
||||
// ["OK", <event_id>, <true|false>, <message>]
|
||||
if (args.length >= 2) {
|
||||
const eventId = args[0]
|
||||
const accepted = args[1]
|
||||
const message = args[2] || ''
|
||||
const resolver = this.eventResolvers.get(eventId)
|
||||
if (resolver) {
|
||||
resolver({ accepted, message })
|
||||
this.eventResolvers.delete(eventId)
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 'EOSE':
|
||||
// ["EOSE", <subscription_id>]
|
||||
if (args.length >= 1) {
|
||||
const subscriptionId = args[0]
|
||||
const handler = this.subscriptionHandlers.get(subscriptionId)
|
||||
if (handler) {
|
||||
handler.onEose()
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 'CLOSED':
|
||||
// ["CLOSED", <subscription_id>, <message>]
|
||||
if (args.length >= 1) {
|
||||
const subscriptionId = args[0]
|
||||
const message = args[1] || ''
|
||||
const handler = this.subscriptionHandlers.get(subscriptionId)
|
||||
if (handler) {
|
||||
handler.onClosed(message)
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 'NOTICE':
|
||||
// ["NOTICE", <message>]
|
||||
if (args.length >= 1) {
|
||||
const message = args[0]
|
||||
wlogger.warn('Relay notice:', message)
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
wlogger.warn('Unknown message type from relay:', type)
|
||||
}
|
||||
}
|
||||
|
||||
handleError (error) {
|
||||
wlogger.error('WebSocket error:', error)
|
||||
this.isConnected = false
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
const now = new Date()
|
||||
wlogger.warn(`WebSocket connection closed at ${now.toLocaleString()}`)
|
||||
this.isConnected = false
|
||||
|
||||
// Attempt to reconnect
|
||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this.reconnectAttempts++
|
||||
wlogger.info(`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`)
|
||||
setTimeout(() => {
|
||||
this.connect().catch(err => {
|
||||
wlogger.error('Reconnection failed:', err)
|
||||
})
|
||||
}, this.reconnectDelay)
|
||||
}
|
||||
}
|
||||
|
||||
async sendMessage (message) {
|
||||
if (!this.isConnected || !this.ws) {
|
||||
// Queue message for when connection is established
|
||||
this.pendingMessages.push(message)
|
||||
await this.connect()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
this.ws.send(JSON.stringify(message))
|
||||
} catch (err) {
|
||||
wlogger.error('Error sending message:', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async sendEvent (event) {
|
||||
// ["EVENT", <event>]
|
||||
const message = ['EVENT', event]
|
||||
await this.sendMessage(message)
|
||||
|
||||
// Return a promise that resolves when we get the OK response
|
||||
return new Promise((resolve, reject) => {
|
||||
this.eventResolvers.set(event.id, resolve)
|
||||
// Timeout after 30 seconds
|
||||
setTimeout(() => {
|
||||
if (this.eventResolvers.has(event.id)) {
|
||||
this.eventResolvers.delete(event.id)
|
||||
reject(new Error('Timeout waiting for OK response'))
|
||||
}
|
||||
}, 30000)
|
||||
})
|
||||
}
|
||||
|
||||
async sendReq (subscriptionId, filters, handlers) {
|
||||
// ["REQ", <subscription_id>, <filters>]
|
||||
await this.connect()
|
||||
|
||||
// Store handlers for this subscription
|
||||
this.subscriptionHandlers.set(subscriptionId, handlers)
|
||||
|
||||
const message = ['REQ', subscriptionId, ...filters]
|
||||
await this.sendMessage(message)
|
||||
}
|
||||
|
||||
async sendClose (subscriptionId) {
|
||||
// ["CLOSE", <subscription_id>]
|
||||
const message = ['CLOSE', subscriptionId]
|
||||
await this.sendMessage(message)
|
||||
|
||||
// Clean up handlers
|
||||
this.subscriptionHandlers.delete(subscriptionId)
|
||||
this.messageHandlers.delete(subscriptionId)
|
||||
}
|
||||
}
|
||||
|
||||
export default NostrRelayAdapter
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
REST API Controller library for the /event route
|
||||
*/
|
||||
|
||||
// Local libraries
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
class EventRESTControllerLib {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating /event REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating /event REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.publishEvent = this.publishEvent.bind(this)
|
||||
this.handleError = this.handleError.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /event Publish a Nostr event
|
||||
* @apiPermission public
|
||||
* @apiName PublishEvent
|
||||
* @apiGroup Event
|
||||
*
|
||||
* @apiDescription Publish a signed Nostr event to the relay. Maps to the Nostr WebSocket protocol message: ["EVENT", <event>]
|
||||
*
|
||||
* @apiParam {String} id Event ID (32-bytes lowercase hex-encoded sha256)
|
||||
* @apiParam {String} pubkey Public key of event creator (32-bytes lowercase hex-encoded)
|
||||
* @apiParam {Number} created_at Unix timestamp in seconds
|
||||
* @apiParam {Number} kind Integer between 0 and 65535
|
||||
* @apiParam {Array} tags Array of tag arrays
|
||||
* @apiParam {String} content Event content (arbitrary string)
|
||||
* @apiParam {String} sig Signature (64-bytes lowercase hex)
|
||||
*
|
||||
* @apiExample {json} Example usage:
|
||||
* {
|
||||
* "id": "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36",
|
||||
* "pubkey": "2c7e76c0f8dc1dca9d0197c7d19be580a8d074ccada6a2f6ebe056ae41092e9",
|
||||
* "created_at": 1672531200,
|
||||
* "kind": 1,
|
||||
* "tags": [],
|
||||
* "content": "Hello, Nostr!",
|
||||
* "sig": "abc123..."
|
||||
* }
|
||||
*
|
||||
* @apiSuccess {Boolean} accepted Whether the event was accepted by the relay
|
||||
* @apiSuccess {String} message Optional message from the relay
|
||||
* @apiSuccess {String} eventId The event ID
|
||||
*
|
||||
* @apiError {String} error Error message
|
||||
*/
|
||||
async publishEvent (req, res) {
|
||||
try {
|
||||
const eventData = req.body
|
||||
|
||||
// Check if eventData is missing or empty
|
||||
if (!eventData || (typeof eventData === 'object' && Object.keys(eventData).length === 0)) {
|
||||
return res.status(400).json({
|
||||
error: 'Event data is required'
|
||||
})
|
||||
}
|
||||
|
||||
const result = await this.useCases.publishEvent.execute(eventData)
|
||||
|
||||
if (result.accepted) {
|
||||
return res.status(200).json(result)
|
||||
} else {
|
||||
return res.status(400).json(result)
|
||||
}
|
||||
} catch (err) {
|
||||
return this.handleError(err, req, res)
|
||||
}
|
||||
}
|
||||
|
||||
handleError (err, req, res) {
|
||||
wlogger.error('Error in EventRESTController:', err)
|
||||
|
||||
// Return 400 for validation errors, 500 for other errors
|
||||
// Validation errors indicate the client sent bad data
|
||||
const isValidationError = err.message && err.message.includes('Invalid event structure')
|
||||
const statusCode = isValidationError ? 400 : 500
|
||||
|
||||
return res.status(statusCode).json({
|
||||
error: err.message || 'Internal server error'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default EventRESTControllerLib
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
REST API library for the /event route.
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
import express from 'express'
|
||||
|
||||
// Local libraries.
|
||||
import EventRESTControllerLib from './controller.js'
|
||||
|
||||
class EventRouter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating Event REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating Event REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
const dependencies = {
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Encapsulate dependencies.
|
||||
this.eventRESTController = new EventRESTControllerLib(dependencies)
|
||||
|
||||
// Instantiate the router and set the base route.
|
||||
this.baseUrl = '/event'
|
||||
this.router = express.Router()
|
||||
}
|
||||
|
||||
attach (app) {
|
||||
if (!app) {
|
||||
throw new Error(
|
||||
'Must pass app object when attaching REST API controllers.'
|
||||
)
|
||||
}
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.post('/', this.eventRESTController.publishEvent)
|
||||
|
||||
// Attach the Controller routes to the Express app.
|
||||
app.use(this.baseUrl, this.router)
|
||||
}
|
||||
}
|
||||
|
||||
export default EventRouter
|
||||
@@ -1,296 +0,0 @@
|
||||
/*
|
||||
REST API Controller library for the /req route
|
||||
*/
|
||||
|
||||
// Local libraries
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
class ReqRESTControllerLib {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating /req REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating /req REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.queryEvents = this.queryEvents.bind(this)
|
||||
this.createSubscription = this.createSubscription.bind(this)
|
||||
this.closeSubscription = this.closeSubscription.bind(this)
|
||||
this.handleError = this.handleError.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /req/:subId Query events (stateless)
|
||||
* @apiPermission public
|
||||
* @apiName QueryEvents
|
||||
* @apiGroup Request
|
||||
*
|
||||
* @apiDescription Query events from the relay with filters. Returns events immediately. Maps to: ["REQ", <sub_id>, <filters>]
|
||||
*
|
||||
* @apiParam {String} subId Subscription ID (unique identifier)
|
||||
* @apiParam {String} filters JSON-encoded filters object (query parameter)
|
||||
*
|
||||
* @apiExample {curl} Example usage:
|
||||
* curl -X GET "http://localhost:3000/req/sub1?filters=[{\"kinds\":[1],\"limit\":10}]"
|
||||
*
|
||||
* @apiSuccess {Array} events Array of Nostr events
|
||||
*
|
||||
* @apiError {String} error Error message
|
||||
*/
|
||||
async queryEvents (req, res) {
|
||||
try {
|
||||
const { subId } = req.params
|
||||
let filters = req.query.filters
|
||||
|
||||
if (!subId) {
|
||||
return res.status(400).json({
|
||||
error: 'Subscription ID is required'
|
||||
})
|
||||
}
|
||||
|
||||
// Parse filters from query string
|
||||
if (typeof filters === 'string') {
|
||||
try {
|
||||
filters = JSON.parse(filters)
|
||||
} catch (err) {
|
||||
return res.status(400).json({
|
||||
error: 'Invalid filters JSON'
|
||||
})
|
||||
}
|
||||
} else if (!filters) {
|
||||
// If no filters provided, accept filters from query params
|
||||
filters = {}
|
||||
if (req.query.kinds) {
|
||||
filters.kinds = JSON.parse(req.query.kinds)
|
||||
}
|
||||
if (req.query.authors) {
|
||||
filters.authors = JSON.parse(req.query.authors)
|
||||
}
|
||||
if (req.query.ids) {
|
||||
filters.ids = JSON.parse(req.query.ids)
|
||||
}
|
||||
if (req.query.limit) {
|
||||
filters.limit = parseInt(req.query.limit)
|
||||
}
|
||||
if (req.query.since) {
|
||||
filters.since = parseInt(req.query.since)
|
||||
}
|
||||
if (req.query.until) {
|
||||
filters.until = parseInt(req.query.until)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure filters is an array (Nostr protocol expects array of filters)
|
||||
const filtersArray = Array.isArray(filters) ? filters : [filters]
|
||||
|
||||
const events = await this.useCases.queryEvents.execute(filtersArray, subId)
|
||||
|
||||
return res.status(200).json(events)
|
||||
} catch (err) {
|
||||
return this.handleError(err, req, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /req/:subId Create subscription (SSE)
|
||||
* @apiPermission public
|
||||
* @apiName CreateSubscription
|
||||
* @apiGroup Request
|
||||
*
|
||||
* @apiDescription Create a subscription for Server-Sent Events. Maps to: ["REQ", <sub_id>, <filters>]
|
||||
*
|
||||
* @apiParam {String} subId Subscription ID (unique identifier)
|
||||
* @apiParam {Object} filters Filters object in request body
|
||||
*
|
||||
* @apiExample {json} Example usage:
|
||||
* {
|
||||
* "kinds": [1],
|
||||
* "authors": ["2c7e76c0f8dc1dca9d0197c7d19be580a8d074ccada6a2f6ebe056ae41092e9"]
|
||||
* }
|
||||
*
|
||||
* @apiSuccess {String} message Success message
|
||||
*
|
||||
* @apiError {String} error Error message
|
||||
*/
|
||||
async createSubscription (req, res) {
|
||||
try {
|
||||
const { subId } = req.params
|
||||
const filters = req.body
|
||||
|
||||
if (!subId) {
|
||||
return res.status(400).json({
|
||||
error: 'Subscription ID is required'
|
||||
})
|
||||
}
|
||||
|
||||
if (!filters || (typeof filters === 'object' && Object.keys(filters).length === 0)) {
|
||||
return res.status(400).json({
|
||||
error: 'Filters are required'
|
||||
})
|
||||
}
|
||||
|
||||
// Ensure filters is an array
|
||||
const filtersArray = Array.isArray(filters) ? filters : [filters]
|
||||
|
||||
// Set up Server-Sent Events
|
||||
res.setHeader('Content-Type', 'text/event-stream')
|
||||
res.setHeader('Cache-Control', 'no-cache')
|
||||
res.setHeader('Connection', 'keep-alive')
|
||||
res.setHeader('X-Accel-Buffering', 'no') // Disable buffering in nginx
|
||||
|
||||
// Track if response is still writable
|
||||
let isResponseWritable = true
|
||||
|
||||
// Helper function to safely write to SSE stream
|
||||
const safeWrite = (data) => {
|
||||
if (!isResponseWritable) {
|
||||
return false
|
||||
}
|
||||
try {
|
||||
if (!res.writable || res.destroyed || res.closed) {
|
||||
isResponseWritable = false
|
||||
return false
|
||||
}
|
||||
return res.write(data)
|
||||
} catch (err) {
|
||||
wlogger.warn(`Error writing to SSE stream for subscription ${subId}:`, err.message)
|
||||
isResponseWritable = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Handle response stream errors
|
||||
res.on('error', (err) => {
|
||||
wlogger.warn(`Response stream error for subscription ${subId}:`, err.message)
|
||||
isResponseWritable = false
|
||||
// Clean up subscription on stream error
|
||||
this.useCases.manageSubscription.closeSubscription(subId).catch(closeErr => {
|
||||
wlogger.error('Error closing subscription on stream error:', closeErr)
|
||||
})
|
||||
})
|
||||
|
||||
// Send initial connection message
|
||||
if (!safeWrite(`data: ${JSON.stringify({ type: 'connected', subscriptionId: subId })}\n\n`)) {
|
||||
wlogger.warn(`Failed to send initial connection message for subscription ${subId}`)
|
||||
return res.status(500).json({ error: 'Failed to establish SSE connection' })
|
||||
}
|
||||
|
||||
// Handle events
|
||||
const onEvent = (event) => {
|
||||
if (!safeWrite(`data: ${JSON.stringify({ type: 'event', data: event })}\n\n`)) {
|
||||
wlogger.debug(`Cannot write event to SSE stream for subscription ${subId} - connection may be closed`)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle EOSE
|
||||
const onEose = () => {
|
||||
if (!safeWrite(`data: ${JSON.stringify({ type: 'eose' })}\n\n`)) {
|
||||
wlogger.debug(`Cannot write EOSE to SSE stream for subscription ${subId} - connection may be closed`)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle CLOSED
|
||||
const onClosed = (message) => {
|
||||
if (safeWrite(`data: ${JSON.stringify({ type: 'closed', message })}\n\n`)) {
|
||||
try {
|
||||
if (!res.destroyed && !res.closed) {
|
||||
res.end()
|
||||
}
|
||||
} catch (err) {
|
||||
wlogger.warn(`Error ending SSE stream for subscription ${subId}:`, err.message)
|
||||
}
|
||||
}
|
||||
isResponseWritable = false
|
||||
}
|
||||
|
||||
// Create subscription
|
||||
await this.useCases.manageSubscription.createSubscription(
|
||||
subId,
|
||||
filtersArray,
|
||||
onEvent,
|
||||
onEose,
|
||||
onClosed
|
||||
)
|
||||
|
||||
// Handle client disconnect
|
||||
req.on('close', () => {
|
||||
wlogger.info(`Client disconnected from subscription ${subId}`)
|
||||
isResponseWritable = false
|
||||
this.useCases.manageSubscription.closeSubscription(subId).catch(err => {
|
||||
wlogger.error('Error closing subscription on disconnect:', err)
|
||||
})
|
||||
})
|
||||
|
||||
// Handle response finish
|
||||
res.on('finish', () => {
|
||||
isResponseWritable = false
|
||||
})
|
||||
} catch (err) {
|
||||
return this.handleError(err, req, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {put} /req/:subId Create subscription (SSE) - alternative method
|
||||
* @apiPermission public
|
||||
* @apiName CreateSubscriptionPut
|
||||
* @apiGroup Request
|
||||
*
|
||||
* @apiDescription Same as POST /req/:subId - create a subscription for Server-Sent Events
|
||||
*/
|
||||
async createSubscriptionPut (req, res) {
|
||||
return this.createSubscription(req, res)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {delete} /req/:subId Close subscription
|
||||
* @apiPermission public
|
||||
* @apiName CloseSubscription
|
||||
* @apiGroup Request
|
||||
*
|
||||
* @apiDescription Close an existing subscription. Maps to: ["CLOSE", <sub_id>]
|
||||
*
|
||||
* @apiParam {String} subId Subscription ID to close
|
||||
*
|
||||
* @apiSuccess {String} message Success message
|
||||
*
|
||||
* @apiError {String} error Error message
|
||||
*/
|
||||
async closeSubscription (req, res) {
|
||||
try {
|
||||
const { subId } = req.params
|
||||
|
||||
if (!subId) {
|
||||
return res.status(400).json({
|
||||
error: 'Subscription ID is required'
|
||||
})
|
||||
}
|
||||
|
||||
await this.useCases.manageSubscription.closeSubscription(subId)
|
||||
|
||||
return res.status(200).json({
|
||||
message: `Subscription ${subId} closed successfully`
|
||||
})
|
||||
} catch (err) {
|
||||
return this.handleError(err, req, res)
|
||||
}
|
||||
}
|
||||
|
||||
handleError (err, req, res) {
|
||||
wlogger.error('Error in ReqRESTController:', err)
|
||||
return res.status(500).json({
|
||||
error: err.message || 'Internal server error'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default ReqRESTControllerLib
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
REST API library for the /req route.
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
import express from 'express'
|
||||
|
||||
// Local libraries.
|
||||
import ReqRESTControllerLib from './controller.js'
|
||||
|
||||
class ReqRouter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating Req REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating Req REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
const dependencies = {
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Encapsulate dependencies.
|
||||
this.reqRESTController = new ReqRESTControllerLib(dependencies)
|
||||
|
||||
// Instantiate the router and set the base route.
|
||||
this.router = express.Router()
|
||||
}
|
||||
|
||||
attach (app) {
|
||||
if (!app) {
|
||||
throw new Error(
|
||||
'Must pass app object when attaching REST API controllers.'
|
||||
)
|
||||
}
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
// Handle empty subId case first
|
||||
this.router.get('/', (req, res) => {
|
||||
res.status(400).json({
|
||||
error: 'Subscription ID is required'
|
||||
})
|
||||
})
|
||||
this.router.post('/', (req, res) => {
|
||||
res.status(400).json({
|
||||
error: 'Subscription ID is required'
|
||||
})
|
||||
})
|
||||
this.router.delete('/', (req, res) => {
|
||||
res.status(400).json({
|
||||
error: 'Subscription ID is required'
|
||||
})
|
||||
})
|
||||
|
||||
// Routes with subId parameter
|
||||
this.router.get('/:subId', this.reqRESTController.queryEvents)
|
||||
this.router.post('/:subId', this.reqRESTController.createSubscription)
|
||||
this.router.put('/:subId', this.reqRESTController.createSubscriptionPut)
|
||||
this.router.delete('/:subId', this.reqRESTController.closeSubscription)
|
||||
|
||||
// Attach the Controller routes to the Express app.
|
||||
app.use('/req', this.router)
|
||||
}
|
||||
}
|
||||
|
||||
export default ReqRouter
|
||||
@@ -2,7 +2,7 @@
|
||||
Use cases for interacting with the BCH full node blockchain RPC interface.
|
||||
*/
|
||||
|
||||
import wlogger from '../../adapters/wlogger.js'
|
||||
import wlogger from '../adapters/wlogger.js'
|
||||
|
||||
class BlockchainUseCases {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -8,7 +8,7 @@
|
||||
// import PublishEventUseCase from './publish-event.js'
|
||||
// import QueryEventsUseCase from './query-events.js'
|
||||
// import ManageSubscriptionUseCase from './manage-subscription.js'
|
||||
import BlockchainUseCases from './blockchain/index.js'
|
||||
import BlockchainUseCases from './full-node-blockchain-use-cases.js'
|
||||
|
||||
class UseCases {
|
||||
constructor (localConfig = {}) {
|
||||
|
||||
Reference in New Issue
Block a user