@flowfuse/file-server 1.14.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.
@@ -0,0 +1,130 @@
1
+ /**
2
+ * File Storage API
3
+ *
4
+ * @namespace files
5
+ * @memberof forge.fileserver
6
+ */
7
+
8
+ /** @typedef {import('fastify')} Fastify */
9
+ /** @typedef {import('fastify').FastifyReply} FastifyReply */
10
+ /** @typedef {import('fastify').FastifyRequest} FastifyRequest */
11
+
12
+ module.exports = async function (app, opts, done) {
13
+ app.addContentTypeParser('application/octet-stream', { parseAs: 'buffer' }, function (request, payload, done) {
14
+ done(null, payload)
15
+ })
16
+
17
+ /**
18
+ * Create/Update a File
19
+ *
20
+ * @name /v1/files/:teamId/:projectId/*
21
+ * @static
22
+ * @memberof forge.fileserver.files
23
+ */
24
+ app.post('/:teamId/:projectId/*', {
25
+
26
+ }, async (request, reply) => {
27
+ const path = request.params['*']
28
+ let quota = -1
29
+ if (request.quota?.file) {
30
+ quota = request.quota.file
31
+ } else if (app.config.driver.quota) {
32
+ quota = app.config.driver.quota
33
+ }
34
+ let currentSize = -1
35
+ if (quota !== -1) {
36
+ currentSize = await request.vfs.quota()
37
+ }
38
+ try {
39
+ if (request.headers.ff_mode === 'append') {
40
+ if (currentSize !== -1) {
41
+ const newSize = currentSize + request.body.length
42
+ if (newSize < quota) {
43
+ await request.vfs.append(path, request.body)
44
+ } else {
45
+ reply.code(413).send({ code: 'over_quota', error: 'Over Quota', limit: quota })
46
+ return
47
+ }
48
+ } else {
49
+ await request.vfs.append(path, request.body)
50
+ }
51
+ } else if (request.headers.ff_mode === 'ensureDir') {
52
+ await request.vfs.ensureDir(path, request.body)
53
+ } else {
54
+ // should really check if file exists first and compute the delta
55
+ if (currentSize !== -1) {
56
+ const newSize = currentSize + request.body.length
57
+ if (newSize < quota) {
58
+ await request.vfs.save(path, request.body)
59
+ } else {
60
+ reply.code(413).send({ code: 'over_quota', error: 'Over Quota', limit: quota })
61
+ return
62
+ }
63
+ } else {
64
+ await request.vfs.save(path, request.body)
65
+ }
66
+ }
67
+ reply.code(200).send()
68
+ } catch (err) {
69
+ console.log(err)
70
+ reply.code(400).send(err)
71
+ }
72
+ })
73
+
74
+ // /**
75
+ // * Stat a file
76
+ // *
77
+ // * @name /v1/files/:teamId/:projectId/*
78
+ // * @static
79
+ // * @memberof forge.fileserver.files
80
+ // */
81
+ // app.head('/:teamId/:projectId/*', async (request, reply) => {
82
+
83
+ // })
84
+
85
+ /**
86
+ * Retrieve a file
87
+ *
88
+ * @name /v1/files/:teamId/:projectId/*
89
+ * @static
90
+ * @memberof forge.fileserver.files
91
+ */
92
+ app.get('/:teamId/:projectId/*', async (/** @type {FastifyRequest} */ request, /** @type {FastifyReply} */ reply) => {
93
+ const path = request.params['*']
94
+ try {
95
+ const file = await request.vfs.read(path)
96
+ if (file) {
97
+ reply.send(file)
98
+ } else {
99
+ reply.code(404).send()
100
+ }
101
+ } catch (err) {
102
+ if (err.code === 'ENOENT') {
103
+ reply.code(404).send(err)
104
+ } else if (err.code === 'ENOTDIR') {
105
+ reply.code(400).send(err)
106
+ } else {
107
+ reply.code(500).send(err)
108
+ }
109
+ }
110
+ })
111
+
112
+ /**
113
+ * Delete a file
114
+ *
115
+ * @name /v1/files/:teamId/:projectId/*
116
+ * @static
117
+ * @memberof forge.fileserver.files
118
+ */
119
+ app.delete('/:teamId/:projectId/*', async (request, reply) => {
120
+ const path = request.params['*']
121
+ try {
122
+ await request.vfs.delete(path)
123
+ reply.code(200).send()
124
+ } catch (err) {
125
+ reply.code(err.statusCode || 400).send(err)
126
+ }
127
+ })
128
+
129
+ done()
130
+ }
@@ -0,0 +1,18 @@
1
+ const fp = require('fastify-plugin')
2
+
3
+ module.exports = fp(async function (app, opts, done) {
4
+ app.addHook('preHandler', app.checkAuth)
5
+ await app.register(require('./files'), { prefix: '/v1/files', logLevel: app.config.logging.http })
6
+ await app.register(require('./quota'), { prefix: '/v1/quota', logLevel: app.config.logging.http })
7
+ if (app.config.context?.type) {
8
+ await app.register(require('./context'), { prefix: '/v1/context', logLevel: app.config.logging.http })
9
+ }
10
+ app.decorate('getPaginationOptions', (request, defaults) => {
11
+ const result = { ...defaults, ...request.query }
12
+ if (result.query) {
13
+ result.query = result.query.trim()
14
+ }
15
+ return result
16
+ })
17
+ done()
18
+ })
@@ -0,0 +1,9 @@
1
+ module.exports = async function (app, opts, done) {
2
+ app.get('/:teamId/:projectId', async (request, reply) => {
3
+ reply.send({
4
+ used: await request.vfs.quota(request.params.teamId, request.params.projectId)
5
+ })
6
+ })
7
+
8
+ done()
9
+ }
package/index.js ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ const semver = require('semver')
5
+ const flowForgeFileServer = require('./forge/fileServer')
6
+ const YAML = require('yaml')
7
+
8
+ const app = (async function () {
9
+ if (!semver.satisfies(process.version, '>=16.0.0')) {
10
+ console.error(`FlowForge File Server requires at least NodeJS v16, ${process.version} found`)
11
+ process.exit(1)
12
+ }
13
+
14
+ try {
15
+ const options = {}
16
+ // The tests for `nr-persistent-context` run a local copy of the file-server
17
+ // and pass in the full config via this env var
18
+ if (process.env.FF_FS_TEST_CONFIG) {
19
+ try {
20
+ options.config = YAML.parse(process.env.FF_FS_TEST_CONFIG)
21
+ } catch (err) {
22
+ console.error('Failed to parse FF_FS_TEST_CONFIG:', err)
23
+ process.exitCode = 1
24
+ return
25
+ }
26
+ }
27
+
28
+ const server = await flowForgeFileServer(options)
29
+ let stopping = false
30
+ async function exitWhenStopped () {
31
+ if (!stopping) {
32
+ stopping = true
33
+ server.log.info('Stopping FlowForge File Server')
34
+ await server.close()
35
+ server.log.info('FlowForge File Server stopped')
36
+ process.exit(0)
37
+ }
38
+ }
39
+
40
+ process.on('SIGINT', exitWhenStopped)
41
+ process.on('SIGTERM', exitWhenStopped)
42
+ process.on('SIGHUP', exitWhenStopped)
43
+ process.on('SIGUSR2', exitWhenStopped) // for nodemon restart
44
+ process.on('SIGBREAK', exitWhenStopped)
45
+ process.on('message', function (m) { // for PM2 under window with --shutdown-with-message
46
+ if (m === 'shutdown') { exitWhenStopped() }
47
+ })
48
+
49
+ // Start the server
50
+ server.listen({ port: server.config.port, host: server.config.host }, function (err, address) {
51
+ if (err) {
52
+ console.error(err)
53
+ process.exit(1)
54
+ }
55
+ })
56
+ return server
57
+ } catch (err) {
58
+ console.error(err)
59
+ process.exitCode = 1
60
+ }
61
+ })()
62
+
63
+ module.exports = app
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@flowfuse/file-server",
3
+ "version": "1.14.0",
4
+ "description": "A basic Object Storage backend",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "start": "node index.js",
8
+ "serve": "cross-env NODE_ENV=development nodemon -w forge index.js",
9
+ "lint": "eslint -c .eslintrc \"forge/**/*.js\" \"test/**/*.js\" index.js",
10
+ "lint:fix": "eslint -c .eslintrc \"forge/**/*.js\" \"test/**/*.js\" index.js --fix",
11
+ "test": "mocha test/unit/**/*_spec.js --timeout 5000",
12
+ "test:nopg": "cross-env TEST_POSTGRES=false mocha test/unit/**/*_spec.js --timeout 5000"
13
+ },
14
+ "bin": {
15
+ "ff-file-storage": "./index.js"
16
+ },
17
+ "keywords": [
18
+ "flowfuse",
19
+ "object-store"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/FlowFuse/file-server.git"
24
+ },
25
+ "author": {
26
+ "name": "FlowFuse Inc."
27
+ },
28
+ "license": "Apache-2.0",
29
+ "bugs": {
30
+ "url": "https://github.com/FlowFuse/file-server/issues"
31
+ },
32
+ "homepage": "https://github.com/FlowFuse/file-server#readme",
33
+ "dependencies": {
34
+ "@aws-sdk/client-s3": "^3.388.0",
35
+ "@fastify/helmet": "^11.0.0",
36
+ "@node-red/util": "^3.1.0",
37
+ "fastify": "^4.21.0",
38
+ "fastify-metrics": "^10.3.0",
39
+ "fastify-plugin": "^4.5.1",
40
+ "got": "^11.8.6",
41
+ "pg": "^8.11.2",
42
+ "pino": "^8.15.1",
43
+ "pino-pretty": "^10.2.0",
44
+ "semver": "^7.5.4",
45
+ "sequelize": "^6.33.0",
46
+ "sqlite3": "^5.1.6",
47
+ "yaml": "^2.1.3"
48
+ },
49
+ "devDependencies": {
50
+ "cross-env": "^7.0.3",
51
+ "eslint": "^8.47.0",
52
+ "eslint-config-standard": "^17.1.0",
53
+ "eslint-plugin-no-only-tests": "^3.1.0",
54
+ "eslint-plugin-promise": "^6.1.1",
55
+ "mocha": "^10.1.0",
56
+ "mocha-cli": "^1.0.1",
57
+ "nodemon": "^3.0.1",
58
+ "should": "^13.2.3"
59
+ },
60
+ "engines": {
61
+ "node": ">=16.x"
62
+ }
63
+ }