From f629e6f3bde0a84549ee7de28c11cab26776077d Mon Sep 17 00:00:00 2001 From: garrettmills Date: Sun, 9 Feb 2020 04:37:21 -0600 Subject: [PATCH] Enable file uploader component --- .gitignore | 4 +- app/controllers/api/v1/File.controller.js | 137 ++++++++++++++++++ app/models/api/FileGroup.model.js | 27 ++++ .../upload/UploadFile.middleware.js | 12 ++ app/routing/routers/api/v1.routes.js | 8 + config/server.config.js | 3 +- config/upload.config.js | 36 +++++ 7 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 app/controllers/api/v1/File.controller.js create mode 100644 app/models/api/FileGroup.model.js create mode 100644 app/routing/middleware/upload/UploadFile.middleware.js create mode 100644 config/upload.config.js diff --git a/.gitignore b/.gitignore index dfa4e9f..8a6018b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ .idea .idea/* - +uploads/* ### Node ### # Logs logs @@ -95,4 +95,4 @@ www/** ### VScode stuff ### .vscode* -.vscode/** \ No newline at end of file +.vscode/** diff --git a/app/controllers/api/v1/File.controller.js b/app/controllers/api/v1/File.controller.js new file mode 100644 index 0000000..8314c43 --- /dev/null +++ b/app/controllers/api/v1/File.controller.js @@ -0,0 +1,137 @@ +const Controller = require('libflitter/controller/Controller') +const Page = require('../../../models/api/Page.model') +const Node = require('../../../models/api/Node.model') +const FileGroup = require('../../../models/api/FileGroup.model') +const { ObjectId } = require('mongodb') + +/* + * File Controller + * ------------------------------------------------------------- + * Put some description here! + */ +class File extends Controller { + static get services() { + return [...super.services, 'models'] + } + + async create_config(req, res) { + const PageId = req.params.PageId + + let page = await Page.findOne({UUID: PageId}) + if ( !page ) return res.status(404).message('Page not found with that ID.').api({}) + if ( !page.accessible_by(req.user) ) return req.security.deny() + + const NodeId = req.params.NodeId + + let node = await Node.findOne({UUID: NodeId}) + if ( !node ) return res.status(404).message('Node not found with that ID.').api({}) + + const group = new FileGroup({ + NodeId: node.UUID, + PageId: page.UUID, + FileIds: [], + }) + + await group.save() + req.user.allow(`files:${group.UUID}`) + await req.user.save() + + return res.api(group) + } + + async get_config(req, res) { + const PageId = req.params.PageId + + let page = await Page.findOne({UUID: PageId}) + if ( !page ) return res.status(404).message('Page not found with that ID.').api({}) + if ( !page.accessible_by(req.user) ) return req.security.deny() + + const NodeId = req.params.NodeId + + let node = await Node.findOne({UUID: NodeId}) + if ( !node ) return res.status(404).message('Node not found with that ID.').api({}) + + const group = await FileGroup.findOne({UUID: req.params.FilesId}) + if ( !group ) return res.status(404).message('Invalid file group.').api({}) + if ( !group.accessible_by(req.user) ) return req.security.deny() + + const File = this.models.get('upload::File') + const files = await File.find({_id: {$in: group.FileIds.map(x => ObjectId(x))}}) + group.files = files + + return res.api(group) + } + + async save_upload(req, res) { + const PageId = req.params.PageId + + let page = await Page.findOne({UUID: PageId}) + if ( !page ) return res.status(404).message('Page not found with that ID.').api({}) + if ( !page.accessible_by(req.user) ) return req.security.deny() + + const NodeId = req.params.NodeId + + let node = await Node.findOne({UUID: NodeId}) + if ( !node ) return res.status(404).message('Node not found with that ID.').api({}) + + const group = await FileGroup.findOne({UUID: req.params.FilesId}) + if ( !group ) return res.status(404).message('Invalid file group.').api({}) + if ( !group.accessible_by(req.user) ) return req.security.deny() + + if ( req.uploads.uploaded_file ) { + group.FileIds.push(req.uploads.uploaded_file.id) + } + + await group.save() + return res.redirect('/') + } + + async download(req, res) { + const PageId = req.params.PageId + + let page = await Page.findOne({UUID: PageId}) + if ( !page ) return res.status(404).message('Page not found with that ID.').api({}) + if ( !page.accessible_by(req.user) ) return req.security.deny() + + const NodeId = req.params.NodeId + + let node = await Node.findOne({UUID: NodeId}) + if ( !node ) return res.status(404).message('Node not found with that ID.').api({}) + + const group = await FileGroup.findOne({UUID: req.params.FilesId}) + if ( !group ) return res.status(404).message('Invalid file group.').api({}) + if ( !group.accessible_by(req.user) ) return req.security.deny() + + if ( !group.FileIds.includes(req.params.FileId) ) { + return req.security.deny() + } + + const File = this.models.get('upload::File') + const file = await File.findOne({_id: ObjectId(req.params.FileId)}) + if ( !file ) return res.status(404).api({}) + + return file.send(res) + } + + async delete_group(req, res) { + const PageId = req.params.PageId + + let page = await Page.findOne({UUID: PageId}) + if ( !page ) return res.status(404).message('Page not found with that ID.').api({}) + if ( !page.accessible_by(req.user) ) return req.security.deny() + + const NodeId = req.params.NodeId + + let node = await Node.findOne({UUID: NodeId}) + if ( !node ) return res.status(404).message('Node not found with that ID.').api({}) + + const group = await FileGroup.findOne({UUID: req.params.FilesId}) + if ( !group ) return res.status(404).message('Invalid file group.').api({}) + if ( !group.accessible_by(req.user) ) return req.security.deny() + + await group.delete() + return res.api({}) + } +} + +module.exports = exports = File diff --git a/app/models/api/FileGroup.model.js b/app/models/api/FileGroup.model.js new file mode 100644 index 0000000..e976e1d --- /dev/null +++ b/app/models/api/FileGroup.model.js @@ -0,0 +1,27 @@ +const Model = require('flitter-orm/src/model/Model') +const uuid = require('uuid/v4') + +/* + * FileGroup Model + * ------------------------------------------------------------- + * Put some description here! + */ +class FileGroup extends Model { + static get schema() { + // Return a flitter-orm schema here. + return { + NodeId: String, + PageId: String, + FileIds: [String], + UUID: {type: String, default: () => uuid()} + } + } + + accessible_by(user, mode = 'view') { + return user.can(`files:${this.UUID}:${mode}`) + } + + // Static and instance methods can go here +} + +module.exports = exports = FileGroup diff --git a/app/routing/middleware/upload/UploadFile.middleware.js b/app/routing/middleware/upload/UploadFile.middleware.js new file mode 100644 index 0000000..53fe9fb --- /dev/null +++ b/app/routing/middleware/upload/UploadFile.middleware.js @@ -0,0 +1,12 @@ +const Middleware = require('flitter-upload/middleware/UploadFile') + +/* + * Middleware to upload the files included in the request + * to the default file store backend. Stores instances of + * the "upload::File" model in "request.uploads". + */ +class UploadFile extends Middleware { + +} + +module.exports = exports = UploadFile diff --git a/app/routing/routers/api/v1.routes.js b/app/routing/routers/api/v1.routes.js index 8a53dab..57e28fb 100644 --- a/app/routing/routers/api/v1.routes.js +++ b/app/routing/routers/api/v1.routes.js @@ -36,6 +36,9 @@ const index = { * or middleware that are applied in order. */ get: { + '/files/:PageId/:NodeId/get/:FilesId': ['controller::api:v1:File.get_config'], + '/files/:PageId/:NodeId/get/:FilesId/:FileId': ['controller::api:v1:File.download'], + '/hello_world': ['controller::api:v1:Misc.hello_world'], '/page/:PageId': ['controller::api:v1:Page.get_page'], '/page/:PageId/nodes': ['controller::api:v1:Page.get_nodes'], @@ -49,6 +52,11 @@ const index = { }, post: { + '/file/upload/:PageId/:NodeId/:FilesId': ['middleware::upload:UploadFile', 'controller::api:v1:File.save_upload'], + '/files/:PageId/:NodeId/create': ['controller::api:v1:File.create_config'], + '/files/:PageId/:NodeId/delete/:FilesId': ['controller::api:v1:File.delete_group'], + + '/page/:PageId/save': ['controller::api:v1:Page.save_page'], '/page/:PageId/nodes/save': ['controller::api:v1:Page.save_nodes'], '/page/create': ['controller::api:v1:Page.create_top_level'], diff --git a/config/server.config.js b/config/server.config.js index 38f3050..56689c1 100644 --- a/config/server.config.js +++ b/config/server.config.js @@ -34,6 +34,7 @@ const server_config = { uploads: { + enable: true, /* * Used by flitter-upload. Path for uploaded files. * Should be relative to the application root. @@ -58,4 +59,4 @@ const server_config = { } -module.exports = server_config \ No newline at end of file +module.exports = server_config diff --git a/config/upload.config.js b/config/upload.config.js new file mode 100644 index 0000000..98c2f7c --- /dev/null +++ b/config/upload.config.js @@ -0,0 +1,36 @@ +/* + * flitter-upload configuration + * --------------------------------------------------------------- + * Specifies the configuration for various uploader aspects. Mainly, + * contains the configuration for the different file upload backends. + */ +const upload_config = { + /* + * The name of the upload backend to use by default. + */ + default_store: 'flitter', + enabled: true, + + /* + * Stores available to the uploader. + */ + stores: { + + /* + * Example of the basic, filesystem-backed uploader. + * The name of the store is arbitrary. Here, it's called 'flitter'. + */ + flitter: { + enabled: true, + + // This is a filesystem backed 'FlitterStore' + type: 'FlitterStore', + + // Destination for uploaded files. Will be relative to the root + // path of the application. + destination: './uploads', + }, + }, +} + +module.exports = exports = upload_config