mirror of
https://github.com/Cameri/nostream.git
synced 2025-07-27 22:12:17 +02:00
feat: Add health api endpoint or kind (#166)
* feat: handle GET /healthz * chore: simplify default settings * chore: keep client alive on message * chore: increase ws heartbeat timeout to 2min * chore: improve logging during startup * fix: zebedee callback crash on expired invoice * fix: QR code csp error * feat: add get-invoice-status-controller * feat: add get-invoice-status-controller factory * chore: refactor router * feat: get invoice status using rest api * fix: bad import Signed-off-by: Ricardo Arturo Cabral Mejía <me@ricardocabral.io> --------- Signed-off-by: Ricardo Arturo Cabral Mejía <me@ricardocabral.io>
This commit is contained in:
committed by
GitHub
parent
3af69967e5
commit
bfcdac51b0
@@ -15,13 +15,6 @@ payments:
|
|||||||
whitelists:
|
whitelists:
|
||||||
pubkeys:
|
pubkeys:
|
||||||
- replace-with-your-pubkey-in-hex
|
- replace-with-your-pubkey-in-hex
|
||||||
publication:
|
|
||||||
- enabled: false
|
|
||||||
description: Publication fee charged per event in msats (1000 msats = 1 satoshi)
|
|
||||||
amount: 10
|
|
||||||
whitelists:
|
|
||||||
pubkeys:
|
|
||||||
- replace-with-your-pubkey-in-hex
|
|
||||||
paymentsProcessors:
|
paymentsProcessors:
|
||||||
zebedee:
|
zebedee:
|
||||||
baseURL: https://api.zebedee.io/
|
baseURL: https://api.zebedee.io/
|
||||||
@@ -30,9 +23,8 @@ paymentsProcessors:
|
|||||||
- "3.225.112.64"
|
- "3.225.112.64"
|
||||||
- "::ffff:3.225.112.64"
|
- "::ffff:3.225.112.64"
|
||||||
network:
|
network:
|
||||||
maxPayloadSize: 262144
|
maxPayloadSize: 524288
|
||||||
remoteIpHeader: x-forwarded-for
|
remoteIpHeader: x-forwarded-for
|
||||||
idleTimeout: 60
|
|
||||||
workers:
|
workers:
|
||||||
count: 0
|
count: 0
|
||||||
mirroring:
|
mirroring:
|
||||||
@@ -41,11 +33,9 @@ limits:
|
|||||||
invoice:
|
invoice:
|
||||||
rateLimits:
|
rateLimits:
|
||||||
- period: 60000
|
- period: 60000
|
||||||
rate: 3
|
rate: 6
|
||||||
- period: 3600000
|
- period: 3600000
|
||||||
rate: 10
|
rate: 16
|
||||||
- period: 86400000
|
|
||||||
rate: 20
|
|
||||||
ipWhitelist:
|
ipWhitelist:
|
||||||
- "::1"
|
- "::1"
|
||||||
- "10.10.10.1"
|
- "10.10.10.1"
|
||||||
@@ -53,13 +43,11 @@ limits:
|
|||||||
connection:
|
connection:
|
||||||
rateLimits:
|
rateLimits:
|
||||||
- period: 1000
|
- period: 1000
|
||||||
rate: 6
|
rate: 12
|
||||||
- period: 60000
|
- period: 60000
|
||||||
rate: 30
|
rate: 48
|
||||||
- period: 3600000
|
- period: 3600000
|
||||||
rate: 300
|
rate: 300
|
||||||
- period: 86400000
|
|
||||||
rate: 1440
|
|
||||||
ipWhitelist:
|
ipWhitelist:
|
||||||
- "::1"
|
- "::1"
|
||||||
- "10.10.10.1"
|
- "10.10.10.1"
|
||||||
@@ -159,25 +147,12 @@ limits:
|
|||||||
maxFilters: 10
|
maxFilters: 10
|
||||||
message:
|
message:
|
||||||
rateLimits:
|
rateLimits:
|
||||||
# - description: 60 subscriptions/min
|
- description: 240 raw messages/min
|
||||||
# types:
|
|
||||||
# - REQ
|
|
||||||
# period: 60000
|
|
||||||
# rate: 60
|
|
||||||
# - description: 2880 subscriptions/hour
|
|
||||||
# types:
|
|
||||||
# - REQ
|
|
||||||
# period: 3600000
|
|
||||||
# rate: 2880
|
|
||||||
- description: 120 raw messages/min
|
|
||||||
period: 60000
|
period: 60000
|
||||||
rate: 120
|
rate: 240
|
||||||
- description: 3600 raw messages/hour
|
- description: 3600 raw messages/hour
|
||||||
period: 3600000
|
period: 3600000
|
||||||
rate: 3600
|
rate: 4800
|
||||||
- description: 86400 raw messages/day
|
|
||||||
period: 86400000
|
|
||||||
rate: 86400
|
|
||||||
ipWhitelist:
|
ipWhitelist:
|
||||||
- "::1"
|
- "::1"
|
||||||
- "10.10.10.1"
|
- "10.10.10.1"
|
||||||
|
@@ -106,6 +106,40 @@
|
|||||||
var expiresAt = "{{expires_at}}"
|
var expiresAt = "{{expires_at}}"
|
||||||
var timeout
|
var timeout
|
||||||
var paid = false
|
var paid = false
|
||||||
|
var fallbackTimeout
|
||||||
|
|
||||||
|
function getBackoffTime() {
|
||||||
|
return 5000 + Math.floor(Math.random() * 5000)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getInvoiceStatus() {
|
||||||
|
fetch(`/invoices/${reference}/status`).then(async (response) => {
|
||||||
|
const data = await response.json()
|
||||||
|
console.log('data', data)
|
||||||
|
const { status } = data;
|
||||||
|
|
||||||
|
if (status === 'pending') {
|
||||||
|
fallbackTimeout = setTimeout(getInvoiceStatus, getBackoffTime())
|
||||||
|
return
|
||||||
|
} else if (status === 'expired') {
|
||||||
|
hide('pending')
|
||||||
|
show('expired')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paid = true
|
||||||
|
|
||||||
|
clearTimeout(timeout)
|
||||||
|
|
||||||
|
hide('pending')
|
||||||
|
show('paid')
|
||||||
|
}, (error) => {
|
||||||
|
console.error('error fetching status', error)
|
||||||
|
fallbackTimeout = setTimeout(getInvoiceStatus, getBackoffTime())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fallbackTimeout = setTimeout(getInvoiceStatus, getBackoffTime)
|
||||||
|
|
||||||
function connect() {
|
function connect() {
|
||||||
var socket = new WebSocket(relayUrl)
|
var socket = new WebSocket(relayUrl)
|
||||||
@@ -137,22 +171,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'EOSE': {
|
|
||||||
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!paid && message[0] === 'EOSE' && message[1] === 'payment') {
|
if (!paid && message[0] === 'EOSE' && message[1] === 'payment') {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.length !== 3 || message[0] !== 'EVENT' || message[1] !== 'payment') {
|
if (message.length !== 3 || message[0] !== 'EVENT' || message[1] !== 'payment') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.onerror = console.error.bind(console)
|
socket.onerror = console.error.bind(console)
|
||||||
|
@@ -147,6 +147,7 @@ export class WebSocketAdapter extends EventEmitter implements IWebSocketAdapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async onClientMessage(raw: Buffer) {
|
private async onClientMessage(raw: Buffer) {
|
||||||
|
this.alive = true
|
||||||
let abortable = false
|
let abortable = false
|
||||||
let messageHandler: IMessageHandler & IAbortable | undefined = undefined
|
let messageHandler: IMessageHandler & IAbortable | undefined = undefined
|
||||||
try {
|
try {
|
||||||
|
@@ -14,7 +14,7 @@ import { WebServerAdapter } from './web-server-adapter'
|
|||||||
|
|
||||||
const debug = createLogger('web-socket-server-adapter')
|
const debug = createLogger('web-socket-server-adapter')
|
||||||
|
|
||||||
const WSS_CLIENT_HEALTH_PROBE_INTERVAL = 60000
|
const WSS_CLIENT_HEALTH_PROBE_INTERVAL = 120000
|
||||||
|
|
||||||
export class WebSocketServerAdapter extends WebServerAdapter implements IWebSocketServerAdapter {
|
export class WebSocketServerAdapter extends WebServerAdapter implements IWebSocketServerAdapter {
|
||||||
private webSocketsAdapters: WeakMap<WebSocket, IWebSocketAdapter>
|
private webSocketsAdapters: WeakMap<WebSocket, IWebSocketAdapter>
|
||||||
@@ -51,7 +51,10 @@ export class WebSocketServerAdapter extends WebServerAdapter implements IWebSock
|
|||||||
debug('closing')
|
debug('closing')
|
||||||
clearInterval(this.heartbeatInterval)
|
clearInterval(this.heartbeatInterval)
|
||||||
this.webSocketServer.clients.forEach((webSocket: WebSocket) => {
|
this.webSocketServer.clients.forEach((webSocket: WebSocket) => {
|
||||||
debug('terminating client %s', this.webSocketsAdapters.get(webSocket).getClientId())
|
const webSocketAdapter = this.webSocketsAdapters.get(webSocket)
|
||||||
|
if (webSocketAdapter) {
|
||||||
|
debug('terminating client %s: %s', webSocketAdapter.getClientId(), webSocketAdapter.getClientAddress())
|
||||||
|
}
|
||||||
webSocket.terminate()
|
webSocket.terminate()
|
||||||
})
|
})
|
||||||
debug('closing web socket server')
|
debug('closing web socket server')
|
||||||
|
@@ -93,15 +93,16 @@ export class App implements IRunnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logCentered(`${workerCount} workers started`, width)
|
logCentered(`${workerCount} client workers started`, width)
|
||||||
|
logCentered('1 maintenance worker started', width)
|
||||||
|
|
||||||
debug('settings: %O', settings)
|
debug('settings: %O', settings)
|
||||||
|
|
||||||
const host = `${hostname()}:${port}`
|
const host = `${hostname()}:${port}`
|
||||||
addOnion(torHiddenServicePort, host).then(value=>{
|
addOnion(torHiddenServicePort, host).then(value=>{
|
||||||
console.info(`tor hidden service address: ${value}:${torHiddenServicePort}`)
|
logCentered(`Tor hidden service: ${value}:${torHiddenServicePort}`, width)
|
||||||
}, () => {
|
}, () => {
|
||||||
console.error('Unable to add Tor hidden service. Skipping.')
|
logCentered('Tor hidden service: disabled', width)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,14 +19,16 @@ export class ZebedeeCallbackController implements IController {
|
|||||||
response: Response,
|
response: Response,
|
||||||
) {
|
) {
|
||||||
debug('request headers: %o', request.headers)
|
debug('request headers: %o', request.headers)
|
||||||
debug('request body: %o', request.body)
|
debug('request body: %O', request.body)
|
||||||
|
|
||||||
const invoice = fromZebedeeInvoice(request.body)
|
const invoice = fromZebedeeInvoice(request.body)
|
||||||
|
|
||||||
debug('invoice', invoice)
|
debug('invoice', invoice)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.paymentsService.updateInvoice(invoice)
|
if (!invoice.bolt11) {
|
||||||
|
await this.paymentsService.updateInvoice(invoice)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Unable to persist invoice ${invoice.id}`, error)
|
console.error(`Unable to persist invoice ${invoice.id}`, error)
|
||||||
|
|
||||||
|
50
src/controllers/invoices/get-invoice-status-controller.ts
Normal file
50
src/controllers/invoices/get-invoice-status-controller.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { Request, Response } from 'express'
|
||||||
|
import { IController } from '../../@types/controllers'
|
||||||
|
import { IInvoiceRepository } from '../../@types/repositories'
|
||||||
|
|
||||||
|
export class GetInvoiceStatusController implements IController {
|
||||||
|
public constructor(
|
||||||
|
private readonly invoiceRepository: IInvoiceRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async handleRequest(
|
||||||
|
request: Request,
|
||||||
|
response: Response,
|
||||||
|
): Promise<void> {
|
||||||
|
const invoiceId = request.params.invoiceId
|
||||||
|
if (!invoiceId) {
|
||||||
|
response
|
||||||
|
.status(400)
|
||||||
|
.setHeader('content-type', 'text/plain; charset=utf8')
|
||||||
|
.send('Invalid invoice')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const invoice = await this.invoiceRepository.findById(request.params.invoiceId)
|
||||||
|
|
||||||
|
if (!invoice) {
|
||||||
|
response
|
||||||
|
.status(404)
|
||||||
|
.setHeader('content-type', 'text/plain; charset=utf8')
|
||||||
|
.send('Invoice not found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response
|
||||||
|
.status(200)
|
||||||
|
.setHeader('content-type', 'application/json; charset=utf8')
|
||||||
|
.send(JSON.stringify({
|
||||||
|
id: invoice.id,
|
||||||
|
status: invoice.status,
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`get-invoice-status-controller: unable to get invoice ${invoiceId}:`, error)
|
||||||
|
|
||||||
|
response
|
||||||
|
.status(500)
|
||||||
|
.setHeader('content-type', 'text/plain; charset=utf8')
|
||||||
|
.send('Unable to get invoice status')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
src/factories/get-invoice-status-controller-factory.ts
Normal file
11
src/factories/get-invoice-status-controller-factory.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { GetInvoiceStatusController } from '../controllers/invoices/get-invoice-status-controller'
|
||||||
|
import { getReadReplicaDbClient } from '../database/client'
|
||||||
|
import { InvoiceRepository } from '../repositories/invoice-repository'
|
||||||
|
|
||||||
|
export const createGetInvoiceStatusController = () => {
|
||||||
|
const rrDbClient = getReadReplicaDbClient()
|
||||||
|
|
||||||
|
const invoiceRepository = new InvoiceRepository(rrDbClient)
|
||||||
|
|
||||||
|
return new GetInvoiceStatusController(invoiceRepository)
|
||||||
|
}
|
45
src/factories/web-app-factory.ts
Normal file
45
src/factories/web-app-factory.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import express from 'express'
|
||||||
|
import helmet from 'helmet'
|
||||||
|
|
||||||
|
import { createLogger } from './logger-factory'
|
||||||
|
import { createSettings } from './settings-factory'
|
||||||
|
import { rateLimiterMiddleware } from '../handlers/request-handlers/rate-limiter-middleware'
|
||||||
|
import router from '../routes'
|
||||||
|
|
||||||
|
const debug = createLogger('web-app-factory')
|
||||||
|
|
||||||
|
export const createWebApp = () => {
|
||||||
|
const app = express()
|
||||||
|
app
|
||||||
|
.disable('x-powered-by')
|
||||||
|
.use(rateLimiterMiddleware)
|
||||||
|
.use((req, res, next) => {
|
||||||
|
const settings = createSettings()
|
||||||
|
|
||||||
|
const relayUrl = new URL(settings.info.relay_url)
|
||||||
|
const webRelayUrl = new URL(relayUrl.toString())
|
||||||
|
webRelayUrl.protocol = (relayUrl.protocol === 'wss:') ? 'https:' : ':'
|
||||||
|
|
||||||
|
const directives = {
|
||||||
|
/**
|
||||||
|
* TODO: Remove 'unsafe-inline'
|
||||||
|
*/
|
||||||
|
'img-src': ["'self'", 'data:', 'https://cdn.zebedee.io/an/nostr/'],
|
||||||
|
'connect-src': ["'self'", settings.info.relay_url as string, webRelayUrl.toString()],
|
||||||
|
'default-src': ['"self"'],
|
||||||
|
'script-src-attr': ["'unsafe-inline'"],
|
||||||
|
'script-src': ["'self'", "'unsafe-inline'", 'https://cdn.jsdelivr.net/npm/', 'https://unpkg.com/', 'https://cdnjs.cloudflare.com/ajax/libs/'],
|
||||||
|
'style-src': ["'self'", 'https://cdn.jsdelivr.net/npm/'],
|
||||||
|
'font-src': ["'self'", 'https://cdn.jsdelivr.net/npm/'],
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('CSP directives: %o', directives)
|
||||||
|
|
||||||
|
return helmet.contentSecurityPolicy({ directives })(req, res, next)
|
||||||
|
})
|
||||||
|
.use('/favicon.ico', express.static('./resources/favicon.ico'))
|
||||||
|
.use('/css', express.static('./resources/css'))
|
||||||
|
.use(router)
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
@@ -1,6 +1,4 @@
|
|||||||
import { is, path, pathSatisfies } from 'ramda'
|
import { is, path, pathSatisfies } from 'ramda'
|
||||||
import express from 'express'
|
|
||||||
import helmet from 'helmet'
|
|
||||||
import http from 'http'
|
import http from 'http'
|
||||||
import process from 'process'
|
import process from 'process'
|
||||||
import { WebSocketServer } from 'ws'
|
import { WebSocketServer } from 'ws'
|
||||||
@@ -8,9 +6,8 @@ import { WebSocketServer } from 'ws'
|
|||||||
import { getMasterDbClient, getReadReplicaDbClient } from '../database/client'
|
import { getMasterDbClient, getReadReplicaDbClient } from '../database/client'
|
||||||
import { AppWorker } from '../app/worker'
|
import { AppWorker } from '../app/worker'
|
||||||
import { createSettings } from '../factories/settings-factory'
|
import { createSettings } from '../factories/settings-factory'
|
||||||
|
import { createWebApp } from './web-app-factory'
|
||||||
import { EventRepository } from '../repositories/event-repository'
|
import { EventRepository } from '../repositories/event-repository'
|
||||||
import { rateLimiterMiddleware } from '../handlers/request-handlers/rate-limiter-middleware'
|
|
||||||
import router from '../routes'
|
|
||||||
import { UserRepository } from '../repositories/user-repository'
|
import { UserRepository } from '../repositories/user-repository'
|
||||||
import { webSocketAdapterFactory } from './websocket-adapter-factory'
|
import { webSocketAdapterFactory } from './websocket-adapter-factory'
|
||||||
import { WebSocketServerAdapter } from '../adapters/web-socket-server-adapter'
|
import { WebSocketServerAdapter } from '../adapters/web-socket-server-adapter'
|
||||||
@@ -23,27 +20,7 @@ export const workerFactory = (): AppWorker => {
|
|||||||
|
|
||||||
const settings = createSettings()
|
const settings = createSettings()
|
||||||
|
|
||||||
const app = express()
|
const app = createWebApp()
|
||||||
app
|
|
||||||
.disable('x-powered-by')
|
|
||||||
.use(rateLimiterMiddleware)
|
|
||||||
.use(helmet.contentSecurityPolicy({
|
|
||||||
directives: {
|
|
||||||
/**
|
|
||||||
* TODO: Remove 'unsafe-inline'
|
|
||||||
*/
|
|
||||||
'img-src': ["'self'", 'https://cdn.zebedee.io/an/nostr/'],
|
|
||||||
'connect-src': [settings.info.relay_url as string],
|
|
||||||
'default-src': ['"self"'],
|
|
||||||
'script-src-attr': ["'unsafe-inline'"],
|
|
||||||
'script-src': ["'self'", "'unsafe-inline'", 'https://cdn.jsdelivr.net/npm/', 'https://unpkg.com/', 'https://cdnjs.cloudflare.com/ajax/libs/'],
|
|
||||||
'style-src': ["'self'", 'https://cdn.jsdelivr.net/npm/'],
|
|
||||||
'font-src': ["'self'", 'https://cdn.jsdelivr.net/npm/'],
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
.use('/favicon.ico', express.static('./resources/favicon.ico'))
|
|
||||||
.use('/css', express.static('./resources/css'))
|
|
||||||
.use(router)
|
|
||||||
|
|
||||||
// deepcode ignore HttpToHttps: we use proxies
|
// deepcode ignore HttpToHttps: we use proxies
|
||||||
const server = http.createServer(app)
|
const server = http.createServer(app)
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
import { NextFunction, Request, Response } from 'express'
|
||||||
|
|
||||||
|
export const getHealthRequestHandler = (_req: Request, res: Response, next: NextFunction) => {
|
||||||
|
res.status(200).setHeader('content-type', 'text/plain; charset=utf8').send('OK')
|
||||||
|
next()
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { Request, Response } from 'express'
|
||||||
|
|
||||||
|
import { createGetInvoiceStatusController } from '../../factories/get-invoice-status-controller-factory'
|
||||||
|
|
||||||
|
export const getInvoiceStatusRequestHandler = async (req: Request, res: Response) => {
|
||||||
|
const controller = createGetInvoiceStatusController()
|
||||||
|
|
||||||
|
await controller.handleRequest(req, res)
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
|
|
||||||
import callbacksRouter from './callbacks'
|
import callbacksRouter from './callbacks'
|
||||||
|
import { getHealthRequestHandler } from '../handlers/request-handlers/get-health-request-handler'
|
||||||
import { getTermsRequestHandler } from '../handlers/request-handlers/get-terms-request-handler'
|
import { getTermsRequestHandler } from '../handlers/request-handlers/get-terms-request-handler'
|
||||||
import invoiceRouter from './invoices'
|
import invoiceRouter from './invoices'
|
||||||
import { rootRequestHandler } from '../handlers/request-handlers/root-request-handler'
|
import { rootRequestHandler } from '../handlers/request-handlers/root-request-handler'
|
||||||
@@ -8,10 +9,10 @@ import { rootRequestHandler } from '../handlers/request-handlers/root-request-ha
|
|||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
|
||||||
router.get('/', rootRequestHandler)
|
router.get('/', rootRequestHandler)
|
||||||
|
router.get('/healthz', getHealthRequestHandler)
|
||||||
router.get('/terms', getTermsRequestHandler)
|
router.get('/terms', getTermsRequestHandler)
|
||||||
|
|
||||||
router.use('/invoices', invoiceRouter)
|
router.use('/invoices', invoiceRouter)
|
||||||
router.use('/callbacks', callbacksRouter)
|
router.use('/callbacks', callbacksRouter)
|
||||||
|
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
@@ -2,6 +2,7 @@ import { Router, urlencoded } from 'express'
|
|||||||
import { createPaymentsProcessor } from '../../factories/payments-processor-factory'
|
import { createPaymentsProcessor } from '../../factories/payments-processor-factory'
|
||||||
|
|
||||||
import { getInvoiceRequestHandler } from '../../handlers/request-handlers/get-invoice-request-handler'
|
import { getInvoiceRequestHandler } from '../../handlers/request-handlers/get-invoice-request-handler'
|
||||||
|
import { getInvoiceStatusRequestHandler } from '../../handlers/request-handlers/get-invoice-status-request-handler'
|
||||||
import { postInvoiceRequestHandler } from '../../handlers/request-handlers/post-invoice-request-handler'
|
import { postInvoiceRequestHandler } from '../../handlers/request-handlers/post-invoice-request-handler'
|
||||||
|
|
||||||
const invoiceRouter = Router()
|
const invoiceRouter = Router()
|
||||||
@@ -12,6 +13,7 @@ invoiceRouter
|
|||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
.get('/', getInvoiceRequestHandler)
|
.get('/', getInvoiceRequestHandler)
|
||||||
|
.get('/:invoiceId/status', getInvoiceStatusRequestHandler)
|
||||||
.post('/', urlencoded({ extended: true }), postInvoiceRequestHandler)
|
.post('/', urlencoded({ extended: true }), postInvoiceRequestHandler)
|
||||||
|
|
||||||
export default invoiceRouter
|
export default invoiceRouter
|
||||||
|
Reference in New Issue
Block a user