@volcanicminds/backend 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +2 -0
- package/.nvmrc +1 -0
- package/.prettierignore +5 -0
- package/.prettierrc +9 -0
- package/DOCKER.md +23 -0
- package/Dockerfile +21 -0
- package/LICENSE +21 -0
- package/README.md +146 -0
- package/TODO.md +12 -0
- package/jest.config.js +188 -0
- package/lib/api/me/controller/me.ts +13 -0
- package/lib/api/me/routes.ts +98 -0
- package/lib/apollo/context.ts +11 -0
- package/lib/apollo/resolvers.ts +11 -0
- package/lib/apollo/type-defs.ts +9 -0
- package/lib/config/roles.ts +25 -0
- package/lib/index.ts +253 -0
- package/lib/loader/roles.ts +13 -0
- package/lib/loader/router.ts +179 -0
- package/lib/middleware/example.ts +12 -0
- package/lib/middleware/isAdmin.ts +15 -0
- package/lib/middleware/isAuthenticated.ts +16 -0
- package/lib/types/global.d.ts +66 -0
- package/lib/util/logger.ts +71 -0
- package/lib/util/mark.ts +25 -0
- package/lib/util/yn.ts +19 -0
- package/nodemon.json +15 -0
- package/package.json +60 -0
- package/test/example.test.js +5 -0
- package/tsconfig.json +28 -0
package/lib/index.ts
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import dotenv from 'dotenv'
|
|
4
|
+
dotenv.config()
|
|
5
|
+
|
|
6
|
+
import yn from './util/yn'
|
|
7
|
+
import logger from './util/logger'
|
|
8
|
+
import * as mark from './util/mark'
|
|
9
|
+
import * as loaderRoles from './loader/roles'
|
|
10
|
+
import * as loaderRouter from './loader/router'
|
|
11
|
+
|
|
12
|
+
import Fastify, { FastifyInstance } from 'fastify'
|
|
13
|
+
import swagger from '@fastify/swagger'
|
|
14
|
+
import swaggerUI from '@fastify/swagger-ui'
|
|
15
|
+
|
|
16
|
+
import cors from '@fastify/cors'
|
|
17
|
+
import helmet from '@fastify/helmet'
|
|
18
|
+
import compress from '@fastify/compress'
|
|
19
|
+
import rateLimit from '@fastify/rate-limit'
|
|
20
|
+
|
|
21
|
+
import { ApolloServer } from '@apollo/server'
|
|
22
|
+
import fastifyApollo, { fastifyApolloHandler, fastifyApolloDrainPlugin } from '@as-integrations/fastify'
|
|
23
|
+
import { myContextFunction, MyContext } from './apollo/context'
|
|
24
|
+
import resolvers from './apollo/resolvers'
|
|
25
|
+
import typeDefs from './apollo/type-defs'
|
|
26
|
+
|
|
27
|
+
const begin = new Date().getTime()
|
|
28
|
+
mark.print(logger)
|
|
29
|
+
|
|
30
|
+
export interface global {}
|
|
31
|
+
declare global {
|
|
32
|
+
var log: any
|
|
33
|
+
var roles: Roles
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
global.log = logger
|
|
37
|
+
global.roles = loaderRoles.load()
|
|
38
|
+
|
|
39
|
+
declare module 'fastify' {
|
|
40
|
+
export interface FastifyRequest {
|
|
41
|
+
user?: AuthenticatedUser
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function attachApollo(fastify: FastifyInstance) {
|
|
46
|
+
log.info('Attach ApolloServer to Fastify')
|
|
47
|
+
const apollo = new ApolloServer<MyContext>({
|
|
48
|
+
typeDefs,
|
|
49
|
+
resolvers,
|
|
50
|
+
plugins: [fastifyApolloDrainPlugin(fastify)]
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
await apollo.start()
|
|
54
|
+
|
|
55
|
+
return apollo
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function addApolloRouting(fastify: FastifyInstance, apollo: ApolloServer<MyContext> | null) {
|
|
59
|
+
if (apollo) {
|
|
60
|
+
log.info('Add graphql routes')
|
|
61
|
+
await fastify.register(fastifyApollo(apollo), {
|
|
62
|
+
context: myContextFunction
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// // OR
|
|
66
|
+
// fastify.post(
|
|
67
|
+
// '/graphql-alt',
|
|
68
|
+
// fastifyApolloHandler(apollo, {
|
|
69
|
+
// context: myContextFunction
|
|
70
|
+
// })
|
|
71
|
+
// )
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function addFastifyRouting(fastify: FastifyInstance) {
|
|
76
|
+
log.info('Add fastify routes')
|
|
77
|
+
|
|
78
|
+
fastify.addHook('onSend', async (req, reply) => {
|
|
79
|
+
log.debug('onSend')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
fastify.addHook('onResponse', async (req, reply) => {
|
|
83
|
+
log.debug('onResponse')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
fastify.addHook('onTimeout', async (req, reply) => {
|
|
87
|
+
log.debug('onTimeout')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
fastify.addHook('onReady', async () => {
|
|
91
|
+
log.debug('onReady')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
fastify.addHook('onClose', async (instance) => {
|
|
95
|
+
log.debug('onClose')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
fastify.addHook('onError', async (req, reply, error) => {
|
|
99
|
+
log.debug(`onError ${error}`)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
fastify.addHook('onRequest', async (req, reply) => {
|
|
103
|
+
log.debug(`onRequest ${req.method} ${req.url}`)
|
|
104
|
+
req.user = {
|
|
105
|
+
id: 306,
|
|
106
|
+
name: 'Huseyin',
|
|
107
|
+
roles: ['admin', 'public']
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
fastify.addHook('preParsing', async (req) => {
|
|
112
|
+
log.debug(`preParsing ${req.method} ${req.url}`)
|
|
113
|
+
req.user = {
|
|
114
|
+
id: 42,
|
|
115
|
+
name: 'Jane Doe',
|
|
116
|
+
roles: ['admin', 'public']
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const routes = loaderRouter.load()
|
|
121
|
+
routes && loaderRouter.apply(fastify, routes)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function addFastifySwagger(fastify: FastifyInstance) {
|
|
125
|
+
const { NODE_ENV, SWAGGER, SWAGGER_TITLE, SWAGGER_DESCRIPTION, SWAGGER_VERSION } = process.env
|
|
126
|
+
const loadSwagger = yn(SWAGGER, false)
|
|
127
|
+
|
|
128
|
+
if (loadSwagger && NODE_ENV !== 'production') {
|
|
129
|
+
log.info('Add swagger plugin')
|
|
130
|
+
|
|
131
|
+
await fastify.register(swagger, {
|
|
132
|
+
swagger: {
|
|
133
|
+
info: {
|
|
134
|
+
title: SWAGGER_TITLE || 'API Documentation',
|
|
135
|
+
description: SWAGGER_DESCRIPTION || 'List of available APIs and schemes to use',
|
|
136
|
+
version: SWAGGER_VERSION || '0.1.0'
|
|
137
|
+
},
|
|
138
|
+
consumes: ['application/json'],
|
|
139
|
+
produces: ['application/json']
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
await fastify.register(swaggerUI, {
|
|
144
|
+
routePrefix: '/documentation',
|
|
145
|
+
uiConfig: {
|
|
146
|
+
docExpansion: 'list',
|
|
147
|
+
deepLinking: true,
|
|
148
|
+
defaultModelsExpandDepth: 1
|
|
149
|
+
},
|
|
150
|
+
uiHooks: {
|
|
151
|
+
onRequest: function (request, reply, next) {
|
|
152
|
+
next()
|
|
153
|
+
},
|
|
154
|
+
preHandler: function (request, reply, next) {
|
|
155
|
+
next()
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
staticCSP: true,
|
|
159
|
+
transformStaticCSP: (header) => header
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
await fastify.put(
|
|
163
|
+
'/some-route/:id',
|
|
164
|
+
{
|
|
165
|
+
schema: {
|
|
166
|
+
description: 'post some data',
|
|
167
|
+
tags: ['user', 'code'],
|
|
168
|
+
deprecated: true,
|
|
169
|
+
summary: 'qwerty',
|
|
170
|
+
params: {
|
|
171
|
+
type: 'object',
|
|
172
|
+
properties: {
|
|
173
|
+
id: {
|
|
174
|
+
type: 'string',
|
|
175
|
+
description: 'user id'
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
body: {
|
|
180
|
+
type: 'object',
|
|
181
|
+
properties: {
|
|
182
|
+
hello: { type: 'string' },
|
|
183
|
+
obj: {
|
|
184
|
+
type: 'object',
|
|
185
|
+
properties: {
|
|
186
|
+
some: { type: 'string' }
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
response: {
|
|
192
|
+
201: {
|
|
193
|
+
description: 'Successful response',
|
|
194
|
+
type: 'object',
|
|
195
|
+
properties: {
|
|
196
|
+
hello: { type: 'string' }
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
default: {
|
|
200
|
+
description: 'Default response',
|
|
201
|
+
type: 'object',
|
|
202
|
+
properties: {
|
|
203
|
+
foo: { type: 'string' }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
(req, reply) => {}
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const opts = yn(process.env.LOG_FASTIFY, false) ? { logger: logger } : {}
|
|
214
|
+
Fastify(opts).then(async (fastify) => {
|
|
215
|
+
const { HOST: host = '0.0.0.0', PORT: port = '2230', GRAPHQL } = process.env
|
|
216
|
+
const { SRV_CORS, SRV_HELMET, SRV_RATELIMIT, SRV_COMPRESS } = process.env
|
|
217
|
+
|
|
218
|
+
const loadApollo = yn(GRAPHQL, true)
|
|
219
|
+
const addPluginCors = yn(SRV_CORS, true)
|
|
220
|
+
const addPluginHelmet = yn(SRV_HELMET, true)
|
|
221
|
+
const addPluginRateLimit = yn(SRV_RATELIMIT, true)
|
|
222
|
+
const addPluginCompress = yn(SRV_COMPRESS, true)
|
|
223
|
+
|
|
224
|
+
log.t && log.trace(`Attach Apollo Server ${loadApollo}`)
|
|
225
|
+
log.t && log.trace(`Add plugin CORS: ${addPluginCors}`)
|
|
226
|
+
log.t && log.trace(`Add plugin HELMET: ${!loadApollo ? addPluginHelmet : 'Not usable with Apollo'}`)
|
|
227
|
+
log.t && log.trace(`Add plugin COMPRESS: ${addPluginCompress}`)
|
|
228
|
+
log.t && log.trace(`Add plugin RATELIMIT: ${addPluginRateLimit}`)
|
|
229
|
+
|
|
230
|
+
const apollo = loadApollo ? await attachApollo(fastify) : null
|
|
231
|
+
// Helmet is not usable with Apollo Server
|
|
232
|
+
!loadApollo && addPluginHelmet && (await fastify.register(helmet))
|
|
233
|
+
|
|
234
|
+
// Usable with Apollo Server
|
|
235
|
+
addPluginRateLimit && (await fastify.register(rateLimit))
|
|
236
|
+
addPluginCors && (await fastify.register(cors))
|
|
237
|
+
addPluginCompress && (await fastify.register(compress))
|
|
238
|
+
|
|
239
|
+
await addFastifySwagger(fastify)
|
|
240
|
+
await addApolloRouting(fastify, apollo)
|
|
241
|
+
await addFastifyRouting(fastify)
|
|
242
|
+
|
|
243
|
+
fastify
|
|
244
|
+
.listen({
|
|
245
|
+
port: Number(port),
|
|
246
|
+
host: host
|
|
247
|
+
})
|
|
248
|
+
.then((address) => {
|
|
249
|
+
const elapsed = (new Date().getTime() - begin) / 100
|
|
250
|
+
log.info(`All stuff loaded in ${elapsed} sec`)
|
|
251
|
+
log.info(`🚀 Server ready at ${address}`)
|
|
252
|
+
})
|
|
253
|
+
})
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { FastifyReply, FastifyRequest } from 'fastify'
|
|
2
|
+
|
|
3
|
+
const glob = require('glob')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const methods = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH', 'OPTIONS']
|
|
6
|
+
|
|
7
|
+
export function load(): ConfiguredRoute[] {
|
|
8
|
+
const check = true,
|
|
9
|
+
print = true,
|
|
10
|
+
load = true
|
|
11
|
+
|
|
12
|
+
const validRoutes: ConfiguredRoute[] = []
|
|
13
|
+
const patterns = [`${__dirname}/../api/**/routes.{ts,js}`, `${process.cwd()}/src/api/**/routes.{ts,js}`]
|
|
14
|
+
|
|
15
|
+
patterns.forEach((pattern) => {
|
|
16
|
+
check && log.i && log.info('Looking for ' + pattern)
|
|
17
|
+
glob.sync(pattern).forEach((f: string, index: number, values: string[]) => {
|
|
18
|
+
const base = path.dirname(f)
|
|
19
|
+
const dir = path.basename(base)
|
|
20
|
+
const file = path.join(dir, path.basename(f))
|
|
21
|
+
|
|
22
|
+
// allow array or structure
|
|
23
|
+
const routesjs = require(f)
|
|
24
|
+
const { routes = [], config: defaultConfig = {} } = routesjs?.default
|
|
25
|
+
|
|
26
|
+
// adjust default config
|
|
27
|
+
if (!defaultConfig.enable) defaultConfig.enable = true
|
|
28
|
+
if (defaultConfig.deprecated == null) defaultConfig.deprecated = false
|
|
29
|
+
if (defaultConfig.controller == null) defaultConfig.controller = 'controller'
|
|
30
|
+
|
|
31
|
+
check && log.i && log.info(`Load ${file} with ${routes.length} routes defined`)
|
|
32
|
+
print && log.d && log.debug(`Valid routes loaded from ${file}`)
|
|
33
|
+
|
|
34
|
+
routes.forEach((route: Route, index: number) => {
|
|
35
|
+
const errors = []
|
|
36
|
+
const { method: methodCase, path: pathName = '/', handler, config, middlewares = [], roles = [] } = route
|
|
37
|
+
|
|
38
|
+
// specific route config
|
|
39
|
+
const {
|
|
40
|
+
title = '',
|
|
41
|
+
description = '',
|
|
42
|
+
enable = defaultConfig.enable || true,
|
|
43
|
+
deprecated = defaultConfig.deprecated || false,
|
|
44
|
+
version = defaultConfig.version || '',
|
|
45
|
+
params,
|
|
46
|
+
body,
|
|
47
|
+
response
|
|
48
|
+
} = config || {}
|
|
49
|
+
|
|
50
|
+
// adjust something
|
|
51
|
+
const endpoint = `${dir}${pathName.replace(/\/+$/, '')}`
|
|
52
|
+
const method = methodCase.toUpperCase()
|
|
53
|
+
const num = index + 1
|
|
54
|
+
const handlerParts = handler.split('.')
|
|
55
|
+
|
|
56
|
+
if (enable) {
|
|
57
|
+
if (!pathName.startsWith('/')) {
|
|
58
|
+
errors.push(`Error in [${file}] bad path [${pathName}] at route n. ${num}`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!methods.includes(method)) {
|
|
62
|
+
errors.push(`Error in [${file}] bad method [${method}] at route n. ${num}`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (handlerParts.length !== 2) {
|
|
66
|
+
errors.push(`Error in [${file}] bad handler [${handler}] at route n. ${num}`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const key = method + endpoint + version
|
|
70
|
+
if (validRoutes.some((r) => `${r.method}${r.path}${r.doc?.version}` === key)) {
|
|
71
|
+
errors.push(`Error in [${file}] duplicated path [${pathName}] at route n. ${num}`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (errors.length > 0) {
|
|
75
|
+
check && log.e && errors.forEach((error) => log.error(error))
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (errors.length == 0) {
|
|
80
|
+
enable && print
|
|
81
|
+
? log.d &&
|
|
82
|
+
log.debug(
|
|
83
|
+
`* Method [${method}] path ${endpoint} handler ${handler}, has middleware? ${
|
|
84
|
+
(middlewares && middlewares.length) || 'no'
|
|
85
|
+
}`
|
|
86
|
+
)
|
|
87
|
+
: !enable
|
|
88
|
+
? log.w && log.warn(`* Method [${method}] path ${endpoint} handler ${handler} disabled. Skip.`)
|
|
89
|
+
: log.i && log.info(`* Method [${method}] path ${endpoint} handler ${handler} enabled.`)
|
|
90
|
+
|
|
91
|
+
validRoutes.push({
|
|
92
|
+
handler,
|
|
93
|
+
method,
|
|
94
|
+
path: '/' + endpoint,
|
|
95
|
+
middlewares,
|
|
96
|
+
roles,
|
|
97
|
+
enable,
|
|
98
|
+
base,
|
|
99
|
+
file: path.join(base, defaultConfig.controller, handlerParts[0]),
|
|
100
|
+
func: handlerParts[1],
|
|
101
|
+
// swagger
|
|
102
|
+
doc: {
|
|
103
|
+
summary: title,
|
|
104
|
+
description,
|
|
105
|
+
deprecated,
|
|
106
|
+
version,
|
|
107
|
+
params,
|
|
108
|
+
body,
|
|
109
|
+
response
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
return validRoutes
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normalizeMiddlewarePath(base: string, middleware: string = '') {
|
|
121
|
+
const key = 'global.'
|
|
122
|
+
const idx = middleware.indexOf(key)
|
|
123
|
+
return idx == 0
|
|
124
|
+
? path.resolve(__dirname + '/../middleware/' + middleware.substring(key.length))
|
|
125
|
+
: path.resolve(base + '/middleware/' + middleware)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function apply(server: any, routes: ConfiguredRoute[]): void {
|
|
129
|
+
log.t && log.trace(`Apply ${routes.length} routes to server with pid ${process.pid}`)
|
|
130
|
+
|
|
131
|
+
routes.forEach(async ({ handler, method, path, middlewares, roles, enable, base, file, func, doc }) => {
|
|
132
|
+
if (enable) {
|
|
133
|
+
log.t && log.trace(`Add path ${method} ${path} on handle ${handler}`)
|
|
134
|
+
|
|
135
|
+
const allMiddlewares =
|
|
136
|
+
middlewares?.length > 0 ? middlewares.map((m) => require(normalizeMiddlewarePath(base, m))) : []
|
|
137
|
+
|
|
138
|
+
server.route({
|
|
139
|
+
method: method,
|
|
140
|
+
path: path,
|
|
141
|
+
schema: doc,
|
|
142
|
+
// preHandler: allMiddlewares,
|
|
143
|
+
handler: (request: FastifyRequest, reply: FastifyReply) => {
|
|
144
|
+
try {
|
|
145
|
+
if (roles?.length > 0) {
|
|
146
|
+
const userRoles = request.user?.roles || []
|
|
147
|
+
const resolvedRole = roles.filter((r) => userRoles.includes(r.code))
|
|
148
|
+
if (!resolvedRole || resolvedRole.length === 0) {
|
|
149
|
+
log.w && log.warn(`Not allowed to call ${method.toUpperCase()} ${path}`)
|
|
150
|
+
return reply.code(403).send()
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return require(file + '.ts')[func](request, reply)
|
|
154
|
+
} catch (err) {
|
|
155
|
+
log.e && log.error(`Cannot find ${file}.js or method ${func}: ${err}`)
|
|
156
|
+
return reply.code(500).send(`Invalid handler ${handler}`)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// server[method](path, (request: FastifyRequest, reply: FastifyReply) => {
|
|
162
|
+
// try {
|
|
163
|
+
// if (roles?.length > 0) {
|
|
164
|
+
// const userRoles = request.user?.roles || []
|
|
165
|
+
// const resolvedRole = roles.filter((r) => userRoles.includes(r.code))
|
|
166
|
+
// if (!resolvedRole || resolvedRole.length === 0) {
|
|
167
|
+
// log.w && log.warn(`Not allowed to call ${method.toUpperCase()} ${path}`)
|
|
168
|
+
// return reply.code(403).send()
|
|
169
|
+
// }
|
|
170
|
+
// }
|
|
171
|
+
// return require(file + '.ts')[func](request, reply)
|
|
172
|
+
// } catch (err) {
|
|
173
|
+
// log.e && log.error(`Cannot find ${file}.js or method ${func}: ${err}`)
|
|
174
|
+
// return reply.code(500).send(`Invalid handler ${handler}`)
|
|
175
|
+
// }
|
|
176
|
+
// })
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FastifyReply, FastifyRequest } from 'fastify'
|
|
2
|
+
|
|
3
|
+
const log = global.log
|
|
4
|
+
module.exports = (req: FastifyRequest, res: FastifyReply, next: any) => {
|
|
5
|
+
try {
|
|
6
|
+
// TODO: do something and then you can throw an exception or call next()..
|
|
7
|
+
return next()
|
|
8
|
+
} catch (err) {
|
|
9
|
+
log.e && log.error(`Upps, something just happened ${err}`)
|
|
10
|
+
res.code(403).send(err)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FastifyReply, FastifyRequest } from 'fastify'
|
|
2
|
+
|
|
3
|
+
const log = global.log
|
|
4
|
+
module.exports = (req: FastifyRequest, res: FastifyReply, next: any) => {
|
|
5
|
+
try {
|
|
6
|
+
if (!!req.user?.id && (req.user?.roles || []).includes(roles.admin.code)) {
|
|
7
|
+
log.d && log.trace('isAdmin - user id ' + req.user?.id)
|
|
8
|
+
return next()
|
|
9
|
+
}
|
|
10
|
+
throw new Error('User not valid')
|
|
11
|
+
} catch (err) {
|
|
12
|
+
log.e && log.error(`Upps, something just happened ${err}`)
|
|
13
|
+
res.code(403).send(err)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { FastifyReply, FastifyRequest } from 'fastify'
|
|
2
|
+
|
|
3
|
+
const log = global.log
|
|
4
|
+
module.exports = (req: FastifyRequest, res: FastifyReply, next: any) => {
|
|
5
|
+
try {
|
|
6
|
+
// TODO: do something and then you can throw an exception or call next()..
|
|
7
|
+
if (!!req.user?.id) {
|
|
8
|
+
log.d && log.trace('isAuthenticated - user id ' + req.user?.id)
|
|
9
|
+
return next()
|
|
10
|
+
}
|
|
11
|
+
throw new Error('User not authenticated')
|
|
12
|
+
} catch (err) {
|
|
13
|
+
log.e && log.error(`Upps, something just happened ${err}`)
|
|
14
|
+
res.code(403).send(err)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export {}
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
interface AuthenticatedUser {
|
|
5
|
+
id: number
|
|
6
|
+
name: string
|
|
7
|
+
roles: string[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface Role {
|
|
11
|
+
code: string
|
|
12
|
+
name: string
|
|
13
|
+
description: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare enum RoleKey {
|
|
17
|
+
public = 'public',
|
|
18
|
+
admin = 'admin',
|
|
19
|
+
backoffice = 'backoffice'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare type Roles = {
|
|
23
|
+
[key in RoleKey]: Role
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface RouteConfig {
|
|
27
|
+
title: string
|
|
28
|
+
description: string
|
|
29
|
+
enable: boolean
|
|
30
|
+
deprecated: boolean
|
|
31
|
+
version: string
|
|
32
|
+
params?: any
|
|
33
|
+
body?: any
|
|
34
|
+
response?: any
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface Route {
|
|
38
|
+
method: string
|
|
39
|
+
path: string
|
|
40
|
+
handler: string
|
|
41
|
+
roles: Role[]
|
|
42
|
+
config?: RouteConfig
|
|
43
|
+
middlewares: string[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface ConfiguredRoute {
|
|
47
|
+
enable: boolean
|
|
48
|
+
method: any
|
|
49
|
+
path: string
|
|
50
|
+
handler: any
|
|
51
|
+
file: string
|
|
52
|
+
func: any
|
|
53
|
+
base: string
|
|
54
|
+
middlewares: string[]
|
|
55
|
+
roles: Role[]
|
|
56
|
+
doc: {
|
|
57
|
+
summary?: string
|
|
58
|
+
description?: string
|
|
59
|
+
deprecated?: boolean
|
|
60
|
+
version?: string
|
|
61
|
+
params?: any
|
|
62
|
+
body?: any
|
|
63
|
+
response?: any
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal logger thanks to Pino
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// log.debug('test log test log test log')
|
|
8
|
+
// log.error('test log test log test log')
|
|
9
|
+
// log.warn('test log test log test log')
|
|
10
|
+
// log.info('test log test log test log')
|
|
11
|
+
// log.fatal('test log test log test log')
|
|
12
|
+
// log.trace('test log test log test log')
|
|
13
|
+
|
|
14
|
+
import pino from 'pino'
|
|
15
|
+
import yn from './yn'
|
|
16
|
+
|
|
17
|
+
const logLevels = ['fatal', 'error', 'warn', 'info', 'debug', 'trace']
|
|
18
|
+
|
|
19
|
+
const { LOG_LEVEL, LOG_COLORIZE, LOG_TIMESTAMP, LOG_TIMESTAMP_READABLE } = process.env
|
|
20
|
+
|
|
21
|
+
function getLogLevel(): string {
|
|
22
|
+
const lvl = LOG_LEVEL?.toLowerCase()
|
|
23
|
+
return LOG_LEVEL && logLevels.includes(lvl!) ? lvl! : 'debug'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const logColorize = yn(LOG_COLORIZE, true)
|
|
27
|
+
const logTimestamp = yn(LOG_TIMESTAMP, true)
|
|
28
|
+
const logTimestampReadable = yn(LOG_TIMESTAMP_READABLE, true)
|
|
29
|
+
|
|
30
|
+
const loggerConfig = {
|
|
31
|
+
level: getLogLevel(),
|
|
32
|
+
timestamp: logTimestamp,
|
|
33
|
+
transport: {
|
|
34
|
+
target: 'pino-pretty',
|
|
35
|
+
options: {
|
|
36
|
+
translateTime: logTimestampReadable ? 'yyyymmdd HH:MM:ss.l' : false,
|
|
37
|
+
colorize: logColorize
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let logger = pino(loggerConfig)
|
|
43
|
+
const logLevel = logger.levels.values[loggerConfig.level]
|
|
44
|
+
|
|
45
|
+
// Level: trace debug info warn error fatal silent
|
|
46
|
+
// Value: 10 20 30 40 50 60 Infinity
|
|
47
|
+
|
|
48
|
+
const loggerExt = Object.assign(logger, {
|
|
49
|
+
t: logLevel < 11,
|
|
50
|
+
d: logLevel < 21,
|
|
51
|
+
i: logLevel < 31,
|
|
52
|
+
w: logLevel < 41,
|
|
53
|
+
e: logLevel < 51,
|
|
54
|
+
f: logLevel < 61,
|
|
55
|
+
getLogLevel: getLogLevel,
|
|
56
|
+
loggerConfig: loggerConfig,
|
|
57
|
+
updateLevel: () => {
|
|
58
|
+
loggerExt.t = loggerExt.levelVal < 11
|
|
59
|
+
loggerExt.d = loggerExt.levelVal < 21
|
|
60
|
+
loggerExt.i = loggerExt.levelVal < 31
|
|
61
|
+
loggerExt.w = loggerExt.levelVal < 41
|
|
62
|
+
loggerExt.e = loggerExt.levelVal < 51
|
|
63
|
+
loggerExt.f = loggerExt.levelVal < 61
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
loggerExt.on('level-change', () => {
|
|
68
|
+
log.trace('Log level changed')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
export default loggerExt
|
package/lib/util/mark.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const pkg = require('../../package.json')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal mark printed at startup
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function print(logg: any = log): void {
|
|
8
|
+
logg.i && logg.info('Ciao')
|
|
9
|
+
logg.i && logg.info(` ,--. ,--. `)
|
|
10
|
+
logg.i && logg.info(`.--. ,--,---.| |,---.,--,--,--,--\\\`--',---. `)
|
|
11
|
+
logg.i && logg.info(` \\ '' | .-. | / .--' ,-. | ,--/ .--' `)
|
|
12
|
+
logg.i && logg.info(` \\ /' '-' | \\ \`--\\ '-' | || | \\ \`--. `)
|
|
13
|
+
logg.i && logg.info(` \`--' \`---'\`--'\`---'\`--\`--\`--''--\`--'\`---' `)
|
|
14
|
+
logg.i && logg.info('')
|
|
15
|
+
logg.t && logg.trace(`Package ${pkg.name}`)
|
|
16
|
+
logg.i && logg.info(`License ${pkg.license}`)
|
|
17
|
+
logg.i && logg.info(`Version ${pkg.version}`)
|
|
18
|
+
logg.i && logg.info(`Codename ${pkg.codename}`)
|
|
19
|
+
logg.i && logg.info(`Environment ${process.env.NODE_ENV}`)
|
|
20
|
+
logg.t && logg.trace(`Platform ${process.platform} ${process.arch}`)
|
|
21
|
+
logg.t && logg.trace(`Root path ${process.cwd()}`)
|
|
22
|
+
logg.t && logg.trace(`Node ${process.version}`)
|
|
23
|
+
logg.t && logg.trace(`Release ${JSON.stringify(process.release)}`)
|
|
24
|
+
logg.t && logg.trace(`Versions ${JSON.stringify(process.versions)}`)
|
|
25
|
+
}
|
package/lib/util/yn.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
export default function yn(value: any, defaultValue: boolean): boolean {
|
|
4
|
+
if (value === undefined || value === null) {
|
|
5
|
+
return defaultValue
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const val = String(value).trim()
|
|
9
|
+
|
|
10
|
+
if (/^(?:y|yes|true|1|on)$/i.test(val)) {
|
|
11
|
+
return true
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (/^(?:n|no|false|0|off)$/i.test(val)) {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return defaultValue || false
|
|
19
|
+
}
|