/* * v1 Controller * ------------------------------------------------------------- * Put some description here! */ 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, } const share_views = { project: async function(item){ return '/dash/v1/project/view/'+item.id }, snippet: async function(item){ const project = await Project.findById(item.project_id); return '/dash/v1/project/snippet/'+project.id+'/view/'+item.uuid }, } class v1 { /* * Serve the main page. */ async main(req, res){ const projects = await Project.find({ archived: false, user_id: req.session.auth.uuid }) let view_find = { shared_user_ids: { $elemMatch: { $eq: req.session.auth.uuid } } } let edit_find = { edit_user_ids: { $elemMatch: { $eq: req.session.auth.uuid } } } const shared_projects = { view: await Project.find(view_find), edit: await Project.find(edit_find), } const shared_snippets = { view: await Snippet.find(view_find), edit: await Snippet.find(edit_find), } /* * Return the main view. * It must be passed the response. * View parameters can be passed as an optional third * argument to the view() method. */ return _flitter.view(res, 'dash_v1:main', { projects, shared_projects, shared_snippets, user: req.session.auth.user }) } new_project_show(req, res, next){ return _flitter.view(res, 'dash_v1:project', { show_back: true, title: 'Create New Project' }) } async project_edit_show(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.'}) } // check access perms if ( !await devbug.permission.project.edit(project, req.session.auth.user) ){ return _flitter.error(res, 401, {reason: 'You do not have permissions to edit this project.'}) } return _flitter.view(res, 'dash_v1:project', { show_back: true, title: 'Update Project', project_name: project.name, user: req.session.auth.user }) } async project_edit_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.'}) } if ( !req.body || !req.body.name ){ return _flitter.view(res, 'dash_v1:project', {user: req.session.auth.user, show_back: true, title: 'Update Project', project_name: project.name, errors: ['Project name is required.']}) } // check access perms if ( !await devbug.permission.project.edit(project, req.session.auth.user) ){ return _flitter.error(res, 401, {reason: 'You do not have permissions to edit this project.'}) } project.name = req.body.name await project.save() return res.redirect('/dash/v1') } async new_project_do(req, res, next){ if ( !req.body.name ){ return _flitter.view(res, 'dash_v1:project', {user: req.session.auth.user, show_back: true, title: 'Create Project', errors: ['Project name is required.']}) } const project = new Project({ name: req.body.name, user_id: req.session.auth.uuid, data: JSON.stringify({ created: Date.now(), modified: Date.now() }), shared_user_ids: [], }) await project.save() return res.redirect('/dash/v1') } async project_view(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 outs = await Out.find({ project_id: project.id }).sort('-created') const snippets = await Snippet.find({project_id: project.id}) if ( !await devbug.permission.project.view(project, req.session.auth.user) ){ return _flitter.error(res, 401, {reason: 'You do not have permission to view this project.'}) } return _flitter.view(res, 'dash_v1:view', {user: req.session.auth.user, snippets, project, outs, show_back: true, title: 'View: '+project.name }) } async out_view(req, res, next){ const out = await Out.findById(req.params.id) if ( !out ){ return _flitter.error(res, 404, {reason: 'Output not found with the specified ID.'}) } let pretty try { pretty = JSON.stringify(JSON.parse(out.data), null, 4) console.log('Pretty out: ', pretty) } catch (e){ return _flitter.error(res, 500, {reason: 'Unable to parse output data. Data contains invalid JSON.'}) } const project = await Project.findById(out.project_id) if ( !project || (!await devbug.permission.project.view(project, req.session.auth.user)) ){ return _flitter.error(res, 401, {reason: 'You do not have permission to view this project.'}) } return _flitter.view(res, 'dash_v1:out', {project, user: req.session.auth.user, out, prettyd:pretty, show_back: true, title: out.brief, title_small: true }); } async out_delete(req, res, next){ const out = await Out.findById(req.params.id) const project = await Project.findById(req.params.project) if ( !project || ( !await devbug.permission.project.edit(project, req.session.auth.user) ) ){ return _flitter.error(res, 401, {reason: 'You do not have permission to edit this project.'}) } if ( out ){ await out.delete() } return res.redirect('/dash/v1/project/view/'+req.params.project) } async project_delete_show(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.'}) } if ( !await devbug.permission.project.owns(project, req.session.auth.user) ){ return _flitter.error(res, 401, {reason: 'You do not have permission to edit this project.'}) } return _flitter.view(res, 'dash_v1:confirm', {user: req.session.auth.user, project, show_back: true, title: 'Are you sure?', text: 'Deleting this project will remove all stored breakpoint data. This action cannot be undone.', destination: '/dash/v1/project/delete/'+req.params.id}) } async project_delete_do(req, res, next){ const project = await Project.findById(req.params.id) if ( project && ( !await 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 ( project ){ const outs = await Out.find({project_id: project.id}) for ( const key in outs ){ await outs[key].delete() } await project.delete() } return res.redirect('/dash/v1') } view_code(req, res, next){ return _flitter.view(res, 'dash_v1:code', { user: req.session.auth.user, title: 'Using DevBug Inline' }) } async project_share_show(req, res, next){ 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.'}) if ( !await devbug.permission[req.params.api].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 = { uuid: { $in: [] } } read_find.uuid.$in = read_find.uuid.$in.concat(project.shared_user_ids) const read = await _flitter.model('User').find(read_find) // Find edit users const edit_find = { uuid: { $in : [] } } edit_find.uuid.$in = edit_find.uuid.$in.concat(project.edit_user_ids) const edit = await _flitter.model('User').find(edit_find) // Find other users const other_find = { uuid: { $nin: [ project.user_id ] } } other_find.uuid.$nin = other_find.uuid.$nin.concat(project.edit_user_ids).concat(project.shared_user_ids) const other = await _flitter.model('User').find(other_find) // Get the owner user const owner = await _flitter.model('User').findOne({ uuid: project.user_id }) const sharing = { read, edit, other, owner, current_owns: (project.user_id === req.session.auth.uuid) } 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 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 ( !await devbug.permission[req.params.api].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/'+req.params.api+'/share/'+project.id) } async project_share_edit_do(req, res, next){ 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 ( !await devbug.permission[req.params.api].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. if ( project.shared_user_ids.includes(target_user.uuid) ){ project.shared_user_ids.splice(project.shared_user_ids.indexOf(target_user.uuid), 1) } project.edit_user_ids.push(target_user.uuid) await project.save() } return res.redirect('/dash/v1/'+req.params.api+'/share/'+project.id) } async project_share_revoke(req, res, next){ 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 ( !await devbug.permission[req.params.api].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) if ( !(target_user.uuid === project.user_id) && (project.shared_user_ids.includes(target_user.uuid)) ){ project.shared_user_ids.splice(project.shared_user_ids.indexOf(target_user.uuid), 1) await project.save() } if ( to_dash ) return res.redirect('/dash/v1') return res.redirect('/dash/v1/'+req.params.api+'/share/'+project.id) } async project_share_revoke_edit(req, res, next){ 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 ( !await devbug.permission[req.params.api].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) if ( !(target_user.uuid === project.user_id) && (project.edit_user_ids.includes(target_user.uuid)) ){ project.edit_user_ids.splice(project.edit_user_ids.indexOf(target_user.uuid), 1) await project.save() } if ( to_dash ) return res.redirect('/dash/v1') return res.redirect('/dash/v1/'+req.params.api+'/share/'+project.id) } async project_share_transfer(req, res, next){ 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 ( !await devbug.permission[req.params.api].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) if ( project.shared_user_ids.includes(target_user.uuid) ){ project.shared_user_ids.splice(project.shared_user_ids.indexOf(target_user.uuid), 1) } if ( project.edit_user_ids.includes(target_user.uuid) ){ project.edit_user_ids.splice(project.edit_user_ids.indexOf(target_user.uuid), 1) } await project.save() return res.redirect('/dash/v1') } async project_share_invite(req, res, next){ 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.'}) if ( !await devbug.permission[req.params.api].owns(project, req.session.auth.user) ) return _flitter.error(res, 401, {reason: 'You do not have permission to edit this '+req.params.api+'.'}) let share_data = { project_id: project.id, api_type: req.params.api, by_user_id: req.session.auth.uuid, created_on: Date.now() } const share = new Invite(share_data) await share.save() return _flitter.view(res, 'dash_v1:invite', {share, project, title: 'Sharing link for '+req.params.api, show_back: true}) } async accept_invite(req, res, next){ if ( !req.session.invite ) return res.redirect('/dash/v1') const invite = await Invite.findById(req.session.invite_data.invite) if ( !invite ) return _flitter.error(res, 404, {reason: 'This invitation is no longer valid. Sorry.'}) const share_model = share_api[invite.api_type]; const project = await share_model.findById(req.session.invite_data.project) if ( !project ) return _flitter.error(res, 404, {reason: 'This '+invite.api_type+' no longer exists.'}) const user = await _flitter.model('User').findById(req.session.invite_data.user) if ( !user ) return _flitter.error(res, 404, {reason: 'This user no longer exists. Sorry.'}) if ( !project.shared_user_ids.includes(req.session.auth.uuid) && !(project.user_id === req.session.auth.uuid) ){ project.shared_user_ids.push(req.session.auth.uuid) await project.save() } invite.used = true await invite.save() req.session.invite = false req.session.invite_data = false return res.redirect(await share_views[invite.api_type](project)) } async project_snippet_new(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.'}) return _flitter.view(res, 'dash_v1:snippet', {project, user: req.session.auth.user, title: 'Create Snippet', show_back: true}); } async project_snippet_new_do(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.'}) 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}) } // 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 = { 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() 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.'}) if ( !await devbug.permission.snippet.view(snippet, req.session.auth.user) ) return _flitter.error(res, 401, {reason: 'You do not have permission to view this snippet.'}) const is_owner = await devbug.permission.snippet.owns(snippet, req.session.auth.user) return _flitter.view(res, 'dash_v1:snippet', {snippet, project, is_owner, user: req.session.auth.user, title: 'Snippet: '+snippet.name, show_back: true, readonly: true}) } async project_snippet_delete(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.'}) if ( !await devbug.permission.snippet.owns(snippet, req.session.auth.user) ) return _flitter.error(res, 401, {reason: 'You do not have permission to edit this snippet.'}) await snippet.delete() return res.redirect('/dash/v1/project/view/'+project.id) } async project_snippet_edit(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.'}) if ( !await devbug.permission.snippet.edit(snippet, req.session.auth.user) ) return _flitter.error(res, 401, {reason: 'You do not have permission to edit this snippet.'}) return _flitter.view(res, 'dash_v1:snippet', {project, snippet, user: req.session.auth.user, title: 'Snippet: '+snippet.name, readonly: false, show_back: true}) } async project_snippet_edit_do(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.'}) if ( !await devbug.permission.snippet.edit(snippet, req.session.auth.user) ) return _flitter.error(res, 401, {reason: 'You do not have permission to edit this snippet.'}) // 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', {snippet, project, user: req.session.auth.user, title: 'Update Snippet', show_back: true, errors:[fail]}) } snippet.name = req.body.title snippet.data = req.body.data snippet.mode = req.body.mode await snippet.save() return res.redirect('/dash/v1/project/snippet/'+project.id+'/view/'+snippet.uuid) } async show_usage_page(req, res, next){ const valid_pages = ['main', 'node', 'php', 'ecma', 'api']; if ( !valid_pages.includes(req.params.page) ) return _flitter.error(res, 404, {reason: 'The page could not be found.'}) const title = req.params.page.charAt(0).toUpperCase() + req.params.page.slice(1); return _flitter.view(res, 'dash_v1:use:'+req.params.page.replace(/\W/g, ''), { show_back: true, title: 'Using DevBug - '+title, user: req.session.auth.user, }) } } module.exports = exports = v1