Skip to content

Commit 64c8b64

Browse files
committed
Fix logger, allow separate instances
1 parent b3d2487 commit 64c8b64

3 files changed

Lines changed: 299 additions & 266 deletions

File tree

index.js

Lines changed: 5 additions & 266 deletions
Original file line numberDiff line numberDiff line change
@@ -1,272 +1,11 @@
11
'use strict'
22

33
// setup
4-
const http = require('http')
5-
const Pg = require('pg').Pool
6-
const jsonwebtoken = require('jsonwebtoken')
7-
const tinyParams = require('tiny-params')
8-
const port = process.env.PORT || process.env.port || 8091
9-
const handlers = {
10-
search: (name, req) => findAll(name, req.params),
11-
create: (name, req) => create(name, req.params),
12-
read: (name, req) => find(name, req.id, req.params),
13-
update: (name, req) => save(name, req.id, req.params),
14-
delete: (name, req) => destroy(name, req.id, req.params)
15-
}
16-
const scrud = {
17-
GET: 'search',
18-
'GET?': 'search',
19-
POST: 'create',
20-
'POST/': 'create',
21-
'GET/': 'read',
22-
'PUT/': 'update',
23-
'DELETE/': 'delete'
24-
}
25-
const hasBody = {create: true, update: true}
26-
const wlSign = [
27-
'algorithm',
28-
'expiresIn',
29-
'notBefore',
30-
'audience',
31-
'issuer',
32-
'jwtid',
33-
'subject',
34-
'noTimestamp',
35-
'header'
36-
]
37-
38-
// globals
39-
let logger
40-
let pgPool
41-
let jwtOpts
42-
let authTrans
43-
let pgPrefix = ''
44-
let base = ''
45-
let baseRgx = new RegExp(`^/?${base}/`)
46-
let maxBodyBytes = 1e6
47-
let resources = {}
48-
49-
// local helpers
50-
const logIt = (e) => typeof logger === 'function' ? logger(e) : console.log(e)
51-
52-
const cleanPath = (url) => {
53-
return decodeURIComponent(url).replace(baseRgx, '').replace(/\/$/, '')
54-
}
55-
56-
const parseId = (url) => {
57-
let id = (url.match(/\/(.+?)(\/|\?|$)/) || [])[1]
58-
return (id || '').match(/^\d+$/) ? parseInt(id, 10) : id || null
59-
}
60-
61-
const callPgFunc = (name, params) => {
62-
let q = `SELECT * FROM ${name}($1);`
63-
if (!pgPool) return Promise.reject(new Error('no database configured'))
64-
return new Promise((resolve, reject) => {
65-
pgPool.connect((err, client, done) => {
66-
if (err) return reject(err)
67-
client.query(q, [params], (err, result) => {
68-
done(err)
69-
if (err) return reject(err)
70-
resolve((result.rows[0] || {})[name] ? result.rows[0][name] : [])
71-
})
72-
})
73-
})
74-
}
75-
76-
const bodyParse = (req) => new Promise((resolve, reject) => {
77-
let body = ''
78-
req.on('data', (d) => {
79-
body += d.toString()
80-
if (body.length > maxBodyBytes) return reject(new Error('body too large'))
81-
})
82-
req.on('end', () => resolve(body ? JSON.parse(body) : {}))
83-
})
84-
85-
const noIdErr = () => JSON.stringify(new Error('no id passed'))
86-
87-
const filterObj = (obj, ary) => {
88-
let base = {}
89-
ary.forEach((o) => { base[o] = obj[o] })
90-
return base
91-
}
4+
const scrud = require('./scrud')
925

936
// exports
94-
module.exports = {
95-
register,
96-
start,
97-
sendData,
98-
sendErr,
99-
fourOhOne,
100-
fourOhFour,
101-
genToken,
102-
authenticate,
103-
find,
104-
findAll,
105-
create,
106-
save,
107-
destroy
108-
}
109-
110-
// register resource
111-
function register (name, opts = {}) {
112-
if (!name) return Promise.reject(new Error(`no name specified in register`))
113-
return new Promise((resolve, reject) => {
114-
let r = resources[name] = Object.assign(opts, {name})
115-
if (Array.isArray(r.skipAuth)) {
116-
let skippers = {}
117-
r.skipAuth.forEach((a) => { skippers[a] = true })
118-
r.skipAuth = skippers
119-
}
120-
return resolve(r)
121-
})
122-
}
123-
124-
// start server
125-
function start (opts = {}) {
126-
if (opts.namespace) pgPrefix = `${opts.namespace.toLowerCase()}_`
127-
if (opts.maxBodyBytes) maxBodyBytes = opts.maxBodyBytes
128-
if (opts.jsonwebtoken) jwtOpts = opts.jsonwebtoken
129-
if (opts.logger) base = opts.logger
130-
if (opts.base) base = opts.base
131-
if (opts.authTrans) authTrans = opts.authTrans
132-
baseRgx = new RegExp(`^/?${base}/`)
133-
return new Promise((resolve, reject) => {
134-
let server = http.createServer(handleRequest)
135-
server.listen(opts.port || port)
136-
if (opts.postgres) pgPool = new Pg(opts.postgres)
137-
return resolve(server)
138-
})
139-
}
140-
141-
// request handler
142-
function handleRequest (req, res) {
143-
if (!baseRgx.test(req.url)) return fourOhFour(res)
144-
let url = cleanPath(req.url)
145-
let matches = url.match(/^\/?(.+?)(\/|\?|$)/) || []
146-
let resource = resources[matches[1] || '']
147-
let modifier = matches[2]
148-
let action = scrud[`${req.method}${modifier}`]
149-
if (!resource || !action) return fourOhFour(res)
150-
let name = resource.name
151-
res.setHeader('Content-Type', 'application/json')
152-
res.setHeader('SCRUD', `${name}:${action}`)
153-
req.id = parseId(url)
154-
req.params = tinyParams(url)
155-
let headers = req.headers || {}
156-
let connection = req.connection || {}
157-
req.params.ip = headers['x-forwarded-for'] || connection.remoteAddress
158-
req.once('error', (err) => sendErr(res, err))
159-
let handler = resource[action] || actionHandler
160-
let jwt = (headers.authorization || '').replace(/^Bearer\s/, '')
161-
let callHandler = () => {
162-
if (!hasBody[action]) return handler(req, res, name, action)
163-
bodyParse(req).then((body) => {
164-
req.params = Object.assign(body, req.params)
165-
return handler(req, res, name, action)
166-
})
167-
}
168-
if (resource.skipAuth && resource.skipAuth[action]) return callHandler()
169-
authenticate(jwt).then((authData) => {
170-
req.auth = req.params.auth = authTrans ? authTrans(authData) : authData
171-
return callHandler()
172-
}).catch((err) => fourOhOne(res, err))
173-
}
174-
175-
function sendData (res, data = null) {
176-
return new Promise((resolve, reject) => {
177-
res.end(JSON.stringify({data, error: null}))
178-
return resolve()
179-
})
180-
}
181-
182-
function sendErr (res, err = new Error(), code = 500) {
183-
return new Promise((resolve, reject) => {
184-
res.statusCode = code
185-
logIt(err, 'fatal')
186-
err = err instanceof Error ? (err.message || err.name) : err.toString()
187-
res.end(JSON.stringify({data: null, error: err}))
188-
return resolve()
189-
})
190-
}
191-
192-
function fourOhOne (res, err = new Error(`unable to auhenticate request`)) {
193-
return sendErr(res, err, 401)
194-
}
195-
196-
function fourOhFour (res, err = new Error(`no match for requested route`)) {
197-
return sendErr(res, err, 404)
198-
}
199-
200-
function genToken (payload = {}) {
201-
let key = jwtOpts.secret || jwtOpts.privateKey
202-
let noOpts = () => new Error('missing required jsonwebtoken opts')
203-
if (!jwtOpts || !key) return Promise.reject(noOpts())
204-
let opts = filterObj(jwtOpts, wlSign)
205-
return new Promise((resolve, reject) => {
206-
jsonwebtoken.sign(payload, key, opts, (err, token) => {
207-
return err ? reject(err) : resolve(token)
208-
})
209-
})
210-
}
211-
212-
function authenticate (jwt) {
213-
let key = jwtOpts.secret || jwtOpts.publicKey
214-
if (!jwtOpts || !key) return Promise.resolve()
215-
return new Promise((resolve, reject) => {
216-
jsonwebtoken.verify(jwt, key, jwtOpts, (err, d = {}) => {
217-
return err ? reject(err) : resolve(d)
218-
})
219-
})
220-
}
221-
222-
// helper: find resource
223-
function find (resource, id, params) {
224-
if (!id && id !== 0) return Promise.reject(noIdErr())
225-
params.id_array = [id]
226-
let firstRecord = (d) => Promise.resolve(d[0])
227-
return callPgFunc(`${pgPrefix}${resource}_read`, params).then(firstRecord)
228-
}
229-
230-
// helper: find set of resources
231-
function findAll (resource, params) {
232-
Object.keys(params).forEach((k) => {
233-
if (!Array.isArray(params[k])) return
234-
params[`${k}_array`] = params[k]
235-
delete params[k]
236-
})
237-
return callPgFunc(`${pgPrefix}${resource}_search`, params)
238-
}
239-
240-
// helper: create resource
241-
function create (resource, params) {
242-
let firstRecord = (d) => Promise.resolve(d[0])
243-
return callPgFunc(`${pgPrefix}${resource}_create`, params).then(firstRecord)
244-
}
245-
246-
// helper: update resource
247-
function save (resource, id, params) {
248-
if (!id && id !== 0) return Promise.reject(noIdErr())
249-
params.id = id
250-
let firstRecord = (d) => Promise.resolve(d[0])
251-
return callPgFunc(`${pgPrefix}${resource}_update`, params).then(firstRecord)
252-
}
253-
254-
// helper: delete resource
255-
function destroy (resource, id, params) {
256-
if (!id && id !== 0) return Promise.reject(noIdErr())
257-
params.id = id
258-
return callPgFunc(`${pgPrefix}${resource}_delete`, params)
259-
}
260-
261-
// default handler for all resource methods
262-
function actionHandler (req, res, name, action) {
263-
let bq = resources[name].beforeQuery || {} // (req, res)
264-
if (typeof bq !== 'function') bq = bq[action]
265-
let bs = resources[name].beforeSend || {} // (req, res, data)
266-
if (typeof bs !== 'function') bs = bs[action]
267-
let act = () => handlers[action](name, req)
268-
let send = (d) => sendData(res, d)
269-
let finish = (d) => bs ? bs(req, res, d).then((d) => send(d)) : send(d)
270-
let run = () => bq ? bq(req, res).then(act).then(finish) : act().then(finish)
271-
return run().catch((e) => sendErr(res, e))
7+
module.exports = scrud
8+
module.exports.instance = () => {
9+
delete require.cache[require.resolve('./scrud')]
10+
return require('./scrud')
27211
}

0 commit comments

Comments
 (0)