From 5bff5407a59f1be493c7551bad4398ba8cde3b57 Mon Sep 17 00:00:00 2001 From: glmdev Date: Wed, 24 Jul 2019 09:52:58 -0500 Subject: [PATCH] more snippet sharing --- app/MiscUnit.js | 15 ++++ app/controllers/dash/v1.controller.js | 90 +++++++++++++++------- app/models/v1/Snippet.model.js | 6 +- app/routing/routers/dash/v1.routes.js | 14 ++-- app/views/dash_v1/share.pug | 28 +++---- app/views/dash_v1/snippet.pug | 106 +++++++++++++++++++++++--- 6 files changed, 198 insertions(+), 61 deletions(-) diff --git a/app/MiscUnit.js b/app/MiscUnit.js index 55f0b5b..2cca64a 100644 --- a/app/MiscUnit.js +++ b/app/MiscUnit.js @@ -169,6 +169,21 @@ if ( window ) window.out = out; window.breakpoint = breakpoint; owns(project, target_user){ return (project.user_id === target_user.uuid) } + }, + snippet: { + edit(snippet, target_user){ + if ( snippet.user_id === target_user.uuid ) return true + else if ( snippet.edit_user_ids.includes(target_user.uuid) ) return true + else return false + }, + view(snippet, target_user){ + if ( this.edit(snippet, target_user) ) return true + else if ( project.shared_user_ids.includes(target_user.uuid) ) return true + else return false + }, + owns(snippet, target_user){ + return snippet.user_id === target_user.uuid + } } }, } diff --git a/app/controllers/dash/v1.controller.js b/app/controllers/dash/v1.controller.js index 5371168..38134e0 100644 --- a/app/controllers/dash/v1.controller.js +++ b/app/controllers/dash/v1.controller.js @@ -7,6 +7,10 @@ const Project = _flitter.model('v1:Project') const Out = _flitter.model('v1:Out') const Invite = _flitter.model('v1:Invite') const Snippet = _flitter.model('v1:Snippet') +const share_api = { + project: Project, + snippet: Snippet, +} class v1 { /* @@ -199,11 +203,13 @@ class v1 { } async project_share_show(req, res, next){ - const project = await Project.findById(req.params.id) + const share_model = share_api[req.params.api] + if ( !share_model ) return _flitter.error(res, 400, {reason: 'Invalid Share API endpoint.'}) + const project = await share_model.findById(req.params.id) - if ( !project ) return _flitter.error(res, 404, {reason: 'Project not found with the specified ID.'}) + if ( !project ) return _flitter.error(res, 404, {reason: req.params.api+' not found with the specified ID.'}) - if ( !devbug.permission.project.owns(project, req.session.auth.user) ) return _flitter.error(res, 401, {reason: 'You do not have permission to edit this project.'}) + if ( !devbug.permission.project.owns(project, req.session.auth.user) ) return _flitter.error(res, 401, {reason: 'You do not have permission to edit this '+req.params.api+'.'}) // Find read-only users const read_find = { @@ -240,34 +246,38 @@ class v1 { current_owns: (project.user_id === req.session.auth.uuid) } - return _flitter.view(res, 'dash_v1:share', { user: req.session.auth.user, sharing, project, title: 'Share Project: '+project.name, show_back: true }) + return _flitter.view(res, 'dash_v1:share', { user: req.session.auth.user, sharing, item: project, api: req.params.api, title: 'Share '+req.params.api+': '+project.name, show_back: true }) } async project_share_do(req, res, next){ - const project = await Project.findById(req.params.id) - if ( !project ) return _flitter.error(res, 404, {reason: 'Project not found with the specified ID.'}) + const share_model = share_api[req.params.api] + if ( !share_model ) return _flitter.error(res, 400, {reason: 'Invalid Share API endpoint.'}) + const project = await share_model.findById(req.params.id) + if ( !project ) return _flitter.error(res, 404, {reason: req.params.api+' not found with the specified ID.'}) const target_user = await _flitter.model('User').findOne({uuid: req.params.user}) if ( !target_user ) return _flitter.error(res, 404, {reason: 'User not found with the specified ID.'}) - if ( !devbug.permission.project.owns(project, req.session.auth.user) ) return _flitter.error(res, 401, {reason: "You do not have permission to edit this project."}) + if ( !devbug.permission.project.owns(project, req.session.auth.user) ) return _flitter.error(res, 401, {reason: "You do not have permission to edit this "+req.params.api+"."}) if ( !(project.user_id === target_user.uuid) && !(project.shared_user_ids.includes(target_user.uuid)) ){ project.shared_user_ids.push(target_user.uuid) await project.save() } - return res.redirect('/dash/v1/project/share/'+project.id) + return res.redirect('/dash/v1/'+req.params.api+'/share/'+project.id) } async project_share_edit_do(req, res, next){ - const project = await Project.findById(req.params.id) + const share_model = share_api[req.params.api] + if ( !share_model ) return _flitter.error(res, 400, {reason: 'Invalid Share API endpoint.'}) + const project = await share_model.findById(req.params.id) if ( !project ) return _flitter.error(res, 404, {reason: 'Project not found with the specified ID.'}) const target_user = await _flitter.model('User').findOne({uuid: req.params.user}) if ( !target_user ) return _flitter.error(res, 404, {reason: 'User not found with the specified ID.'}) - if ( !devbug.permission.project.owns(project, req.session.auth.user) ) return _flitter.error(res, 401, {reason: "You do not have permission to edit this project."}) + if ( !devbug.permission.project.owns(project, req.session.auth.user) ) return _flitter.error(res, 401, {reason: "You do not have permission to edit this "+req.params.api+"."}) if ( !(project.user_id === target_user.uuid) && !(project.edit_user_ids.includes(target_user.uuid)) ){ // check if read access. If so, revoke. @@ -279,17 +289,19 @@ class v1 { await project.save() } - return res.redirect('/dash/v1/project/share/'+project.id) + return res.redirect('/dash/v1/'+req.params.api+'/share/'+project.id) } async project_share_revoke(req, res, next){ - const project = await Project.findById(req.params.id) - if ( !project ) return _flitter.error(res, 404, {reason: 'Project not found with the specified ID.'}) + const share_model = share_api[req.params.api] + if ( !share_model ) return _flitter.error(res, 400, {reason: 'Invalid Share API endpoint.'}) + const project = await share_model.findById(req.params.id) + if ( !project ) return _flitter.error(res, 404, {reason: req.params.api+' not found with the specified ID.'}) const target_user = await _flitter.model('User').findOne({uuid: req.params.user}) if ( !target_user ) return _flitter.error(res, 404, {reason: 'User not found with the specified ID.'}) - if ( !devbug.permission.project.view(project, req.session.auth.user) ) return _flitter.error(res, 401, {reason: "You do not have permission to edit this project."}) + if ( !devbug.permission.project.view(project, req.session.auth.user) ) return _flitter.error(res, 401, {reason: "You do not have permission to edit this "+req.params.api+"."}) const to_dash = project.shared_user_ids.includes(req.session.auth.uuid) @@ -300,17 +312,19 @@ class v1 { if ( to_dash ) return res.redirect('/dash/v1') - return res.redirect('/dash/v1/project/share/'+project.id) + return res.redirect('/dash/v1/'+req.params.api+'/share/'+project.id) } async project_share_revoke_edit(req, res, next){ - const project = await Project.findById(req.params.id) - if ( !project ) return _flitter.error(res, 404, {reason: 'Project not found with the specified ID.'}) + const share_model = share_api[req.params.api] + if ( !share_model ) return _flitter.error(res, 400, {reason: 'Invalid Share API endpoint.'}) + const project = await share_model.findById(req.params.id) + if ( !project ) return _flitter.error(res, 404, {reason: req.params.api+' not found with the specified ID.'}) const target_user = await _flitter.model('User').findOne({uuid: req.params.user}) if ( !target_user ) return _flitter.error(res, 404, {reason: 'User not found with the specified ID.'}) - if ( !devbug.permission.project.view(project, req.session.auth.user) ) return _flitter.error(res, 401, {reason: "You do not have permission to edit this project."}) + if ( !devbug.permission.project.view(project, req.session.auth.user) ) return _flitter.error(res, 401, {reason: "You do not have permission to edit this "+req.params.api+"."}) const to_dash = project.edit_user_ids.includes(req.session.auth.uuid) @@ -321,17 +335,19 @@ class v1 { if ( to_dash ) return res.redirect('/dash/v1') - return res.redirect('/dash/v1/project/share/'+project.id) + return res.redirect('/dash/v1/'+req.params.api+'/share/'+project.id) } async project_share_transfer(req, res, next){ - const project = await Project.findById(req.params.id) - if ( !project ) return _flitter.error(res, 404, {reason: 'Project not found with the specified ID.'}) + const share_model = share_api[req.params.api] + if ( !share_model ) return _flitter.error(res, 400, {reason: 'Invalid Share API endpoint.'}) + const project = await share_model.findById(req.params.id) + if ( !project ) return _flitter.error(res, 404, {reason: req.params.api+' not found with the specified ID.'}) const target_user = await _flitter.model('User').findOne({uuid: req.params.user}) if ( !target_user ) return _flitter.error(res, 404, {reason: 'User not found with the specified ID.'}) - if ( !devbug.permission.project.owns(project, req.session.auth.user) ) return _flitter.error(res, 401, {reason: 'You do not have permission to edit this project.'}) + if ( !devbug.permission.project.owns(project, req.session.auth.user) ) return _flitter.error(res, 401, {reason: 'You do not have permission to edit this '+req.params.api+'.'}) project.user_id = target_user.uuid project.shared_user_ids.push(req.session.auth.uuid) @@ -404,29 +420,47 @@ class v1 { if ( !project ) return _flitter.error(res, 404, {reason: 'The specified project does not exist.'}) if ( !req.body.title || !req.body.data ){ - return _flitter.view(res, 'dash_v1:snippet', {project, user: req.session.auth.user, title: 'Create Snippet', show_back: true}); + return _flitter.view(res, 'dash_v1:snippet', {project, user: req.session.auth.user, title: 'Create Snippet', show_back: true}) + } + + // check required fields: title, data, mode + let fail = false + + if ( !req.body.title ) fail = 'Snippet title is required.' + else if ( !req.body.data ) fail = 'Snippet data is required.' + else if ( !req.body.mode ) fail = 'Snippet mode is required.' + + if ( fail ){ + return _flitter.view(res, 'dash_v1:snippet', {project, user: req.session.auth.user, title: 'Create Snippet', show_back: true, errors:[fail]}) } const snippet_data = { - title: req.body.title, + name: req.body.title, data: req.body.data, + mode: req.body.mode, user_id: req.session.auth.uuid, - }; + project_id: project.id, + } + + console.log({snippet_data}) - const snippet = new Snippet(snippet_data); - await snippet.save(); + const snippet = new Snippet(snippet_data) + await snippet.save() return res.redirect('/dash/v1/project/snippet/'+req.params.id+'/view/'+snippet.uuid) } + // TODO access checks async project_snippet_view(req, res, next){ const project = await Project.findById(req.params.id) if ( !project ) return _flitter.error(res, 404, {reason: 'The specified project does not exist.'}) const snippet = await Snippet.findOne({uuid: req.params.snippet}) if ( !snippet ) return _flitter.error(res, 404, {reason: 'The specified snippet does not exist.'}) + + console.log('snippet mode', snippet.mode) - return _flitter.view(res, 'dash_v1:snippet', {snippet, project, user: req.session.auth.user, title: snippet.title, show_back: true, readonly: true}) + return _flitter.view(res, 'dash_v1:snippet', {snippet, project, user: req.session.auth.user, title: 'Snippet: '+snippet.name, show_back: true, readonly: true}) } } diff --git a/app/models/v1/Snippet.model.js b/app/models/v1/Snippet.model.js index b5681a0..54ee8a5 100644 --- a/app/models/v1/Snippet.model.js +++ b/app/models/v1/Snippet.model.js @@ -5,7 +5,7 @@ */ const uuid = require('uuid/v4') const Snippet = { - title: String, + name: String, user_id: String, archived: { type: Boolean, default: false }, data: String, @@ -13,6 +13,8 @@ const Snippet = { edit_user_ids: [String], uuid: { type: String, default: uuid }, public_share: String, + mode: String, + project_id: String, } -module.exports = exports = Snippet \ No newline at end of file +module.exports = exports = Snippet diff --git a/app/routing/routers/dash/v1.routes.js b/app/routing/routers/dash/v1.routes.js index 3ee6b67..5964159 100644 --- a/app/routing/routers/dash/v1.routes.js +++ b/app/routing/routers/dash/v1.routes.js @@ -41,13 +41,13 @@ const v1 = { '/project/view/:id': [ _flitter.controller('dash:v1').project_view ], '/project/delete/:id': [ _flitter.controller('dash:v1').project_delete_show ], '/project/edit/:id': [ _flitter.controller('dash:v1').project_edit_show ], - '/project/share/:id': [ _flitter.controller('dash:v1').project_share_show ], - '/project/share/:id/share/:user': [ _flitter.controller('dash:v1').project_share_do ], - '/project/share/:id/share/:user/edit': [ _flitter.controller('dash:v1').project_share_edit_do ], - '/project/share/:id/revoke/:user': [ _flitter.controller('dash:v1').project_share_revoke ], - '/project/share/:id/revoke/:user/edit': [ _flitter.controller('dash:v1').project_share_revoke_edit ], - '/project/share/:id/transfer/:user': [ _flitter.controller('dash:v1').project_share_transfer ], - '/project/share/:id/invite': [ _flitter.controller('dash:v1').project_share_invite ], + '/:api/share/:id': [ _flitter.controller('dash:v1').project_share_show ], + '/:api/share/:id/share/:user': [ _flitter.controller('dash:v1').project_share_do ], + '/:api/share/:id/share/:user/edit': [ _flitter.controller('dash:v1').project_share_edit_do ], + '/:api/share/:id/revoke/:user': [ _flitter.controller('dash:v1').project_share_revoke ], + '/:api/share/:id/revoke/:user/edit': [ _flitter.controller('dash:v1').project_share_revoke_edit ], + '/:api/share/:id/transfer/:user': [ _flitter.controller('dash:v1').project_share_transfer ], + '/:api/share/:id/invite': [ _flitter.controller('dash:v1').project_share_invite ], '/project/snippet/:id/new': [ _flitter.controller('dash:v1').project_snippet_new ], '/project/snippet/:id/view/:snippet': [ _flitter.controller('dash:v1').project_snippet_view ], diff --git a/app/views/dash_v1/share.pug b/app/views/dash_v1/share.pug index 02f2c06..8115cef 100644 --- a/app/views/dash_v1/share.pug +++ b/app/views/dash_v1/share.pug @@ -20,14 +20,14 @@ block content tbody each user in sharing.read tr - td #{(user.uuid === project.user_id ? user.username + " (Owner)" : user.username)} + td #{(user.uuid === item.user_id ? user.username + " (Owner)" : user.username)} td ul(style='list-style-type: none; margin: 0; padding: 0;') - if !(user.uuid === project.user_id) + if !(user.uuid === item.user_id) li.action-li - a.action(href='/dash/v1/project/share/'+project.id+'/revoke/'+user.uuid) Revoke + a.action(href='/dash/v1/'+api+'/share/'+item.id+'/revoke/'+user.uuid) Revoke li - a.action(href='/dash/v1/project/share/'+project.id+'/transfer/'+user.uuid) Transfer Ownership + a.action(href='/dash/v1/'+api+'/share/'+item.id+'/transfer/'+user.uuid) Transfer Ownership else li.action-li strike Revoke @@ -45,14 +45,14 @@ block content tbody each user in sharing.edit tr - td #{(user.uuid === project.user_id ? user.username + " (Owner)" : user.username)} + td #{(user.uuid === item.user_id ? user.username + " (Owner)" : user.username)} td ul(style='list-style-type: none; margin: 0; padding: 0;') - if !(user.uuid === project.user_id) + if !(user.uuid === item.user_id) li.action-li - a.action(href='/dash/v1/project/share/'+project.id+'/revoke/'+user.uuid+'/edit') Revoke + a.action(href='/dash/v1/'+api+'/share/'+item.id+'/revoke/'+user.uuid+'/edit') Revoke li - a.action(href='/dash/v1/project/share/'+project.id+'/transfer/'+user.uuid) Transfer Ownership + a.action(href='/dash/v1/'+api+'/share/'+item.id+'/transfer/'+user.uuid) Transfer Ownership else li.action-li strike Revoke @@ -60,7 +60,7 @@ block content strike Transfer Ownership br h2 Share With New User - a.btn(href='/dash/v1/project/share/'+project.id+'/invite') Generate Sharing Link + a.btn(href='/dash/v1/'+api+'/share/'+item.id+'/invite') Generate Sharing Link if sharing.other.length > 0 br @@ -73,16 +73,16 @@ block content tbody each user in sharing.other tr - td #{(user.uuid === project.user_id ? user.username + " (Owner)" : user.username)} + td #{(user.uuid === item.user_id ? user.username + " (Owner)" : user.username)} td ul(style='list-style-type: none; margin: 0; padding: 0;') - if !(user.uuid === project.user_id) + if !(user.uuid === item.user_id) li.action-li - a.action(href='/dash/v1/project/share/' + project.id + '/share/'+user.uuid) Share (View) + a.action(href='/dash/v1/'+api+'/share/' + item.id + '/share/'+user.uuid) Share (View) li.action-li - a.action(href='/dash/v1/project/share/' + project.id + '/share/'+user.uuid+'/edit') Share (Edit) + a.action(href='/dash/v1/'+api+'/share/' + item.id + '/share/'+user.uuid+'/edit') Share (Edit) else li.action-li strike Share li.action-li - a.action(href='/dash/v1/project/share/' + project.id + '/transfer/' + user.uuid) Transfer Ownership + a.action(href='/dash/v1/'+api+'/share/' + item.id + '/transfer/' + user.uuid) Transfer Ownership diff --git a/app/views/dash_v1/snippet.pug b/app/views/dash_v1/snippet.pug index 81ffcb7..149c250 100644 --- a/app/views/dash_v1/snippet.pug +++ b/app/views/dash_v1/snippet.pug @@ -3,21 +3,103 @@ block content if errors each error in errors p(style='color: red; font-weight: bold;') #{error} - if (!readonly) - form#snippet_form(method='post' enctype='multipart/form-data') - label(for='snippet_name') Snippet Title: - input#snippet_name(type='text' name='title' autofocus required) - input#snippet_value(type='hidden' name='data' required) + form#snippet_form(method='post' enctype='multipart/form-data') + label(for='snippet_name') Snippet Title: + input#snippet_name(type='text' name='title' placeholder='required' value=snippet ? snippet.name : '' autofocus required readonly=!!readonly) + label(for='snippet_mode' style='margin-left: 10px;') Language: + select#snippet_mode(name='mode' onchange='selectonchange()' required disabled=!!readonly) + option(value='ace/mode/c_cpp') C/C++ + option(value='ace/mode/css') CSS + option(value='ace/mode/gitignore') .gitignore + option(value='ace/mode/go') Go + option(value='ace/mode/html') HTML + option(value='ace/mode/java') Java + option(value='ace/mode/javascript') JavaScript + option(value='ace/mode/json') JSON + option(value='ace/mode/mysql') MySQL + option(value='ace/mode/perl') Perl + option(value='ace/mode/pgsql') Postgres + option(value='ace/mode/php') PHP + option(value='ace/mode/python') Python + option(value='ace/mode/ruby') Ruby + option(value='ace/mode/rust') Rust + option(value='ace/mode/sql') SQL + option(value='ace/mode/text') Text + option(value='ace/mode/typescript') TypeScript + option(value='ace/mode/apache_conf') Apache Configuration + option(value='ace/mode/asl') ASL + option(value='ace/mode/assembly_x86') Assembly (x86) + option(value='ace/mode/autohotkey') AutoHotKey + option(value='ace/mode/crystal') Crystal + option(value='ace/mode/clojure') Clojure + option(value='ace/mode/cobol') COBOL + option(value='ace/mode/coffee') CoffeeScript + option(value='ace/mode/csharp') C# + option(value='ace/mode/d') D + option(value='ace/mode/django') django + option(value='ace/mode/dockerfile') Dockerfile + option(value='ace/mode/ejs') EJS + option(value='ace/mode/elixir') Elixir + option(value='ace/mode/fortran') Fortran + option(value='ace/mode/haml') HAML + option(value='ace/mode/handlebars') Handlebars + option(value='ace/mode/html_elixir') HTML (Elixir) + option(value='ace/mode/html_ruby') HTML (Ruby) + option(value='ace/mode/ini') INI + option(value='ace/mode/jade') Jade + option(value='ace/mode/julia') Julia + option(value='ace/mode/kotlin') Kotlin + option(value='ace/mode/latex') LaTeX + option(value='ace/mode/less') Less + option(value='ace/mode/lisp') Lisp + option(value='ace/mode/lua') Lua + option(value='ace/mode/makefile') Makefile + option(value='ace/mode/markdown') Markdown + option(value='ace/mode/matlab') MATLAB + option(value='ace/mode/nginx') NGINX + option(value='ace/mode/objectivec') Objective-C + option(value='ace/mode/pascal') Pascal + option(value='ace/mode/perl6') Perl 6 + option(value='ace/mode/php_laravel_blade') PHP (blade template) + option(value='ace/mode/puppet') Puppet + option(value='ace/mode/powershell') PowerShell + option(value='ace/mode/r') R + option(value='ace/mode/sass') SASS + option(value='ace/mode/scala') Scala + option(value='ace/mode/scss') SCSS + option(value='ace/mode/sh') /bin/sh + option(value='ace/mode/slim') Slim + option(value='ace/mode/snippets') Snippets + option(value='ace/mode/sqlserver') SQL Server + option(value='ace/mode/svg') SVG + option(value='ace/mode/swift') Swift + option(value='ace/mode/tcl') TCL + option(value='ace/mode/tex') Tex + option(value='ace/mode/vbscript') VBScript + option(value='ace/mode/xml') XML + option(value='ace/mode/yaml') YAML + input#snippet_value(type='hidden' name='data' required) + if snippet && user.uuid === snippet.user_id + a.btn(href='/dash/v1/snippet/share/'+snippet.id style='margin-left: 20px') Share Snippet pre#editor #{ snippet ? snippet.data : '' } if (!readonly) button(onclick='submitSnippet()') #{ snippet ? 'Update Snippet' : 'Create Snippet' } + input#preset_mode(type='hidden' value=snippet ? snippet.mode : 'ace/mode/javascript') block scripts script(src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.5/ace.js") script. + const readonly = #{!!readonly} + const preset_mode = document.getElementById('preset_mode').value const editor = ace.edit('editor'); editor.setTheme('ace/theme/cobalt'); - //- editor.session.setMode('ace/mode/javascript'); + console.log('preset mode: ', preset_mode); + editor.session.setMode(preset_mode); + document.getElementById('snippet_mode').value = preset_mode + + if ( readonly ){ + editor.setOption('readOnly', true) + } function trim(s){ return ( s || '' ).replace( /^\s+|\s+$/g, '' ); @@ -27,10 +109,14 @@ block scripts const form = document.getElementById('snippet_form'); const value = document.getElementById('snippet_value'); const name = document.getElementById('snippet_name'); + const mode = document.getElementById('snippet_mode'); value.value = editor.getValue(); - if ( trim(value.value) && trim(name.value) ) form.submit(); + if ( trim(value.value) && trim(name.value) && trim(mode.value) ) form.submit(); + } + + function selectonchange() { + const mode = document.getElementById('snippet_mode'); + console.log('set mode: ', mode.value); + editor.session.setMode(mode.value); } - if readonly - script. - editor.setOption('readOnly', true) \ No newline at end of file