cyclic structure resolution & edit sharing

master
Garrett Mills 5 years ago
parent 2dafb07cea
commit a078b768da

@ -152,9 +152,26 @@ const breakpoint = (html = false, name = null) => {
}
if ( window ) window.out = out; window.breakpoint = breakpoint;
// ===========================================================`
}
},
permission: {
project: {
edit(project, target_user){
if ( project.user_id === target_user.uuid ) return true
else if ( project.edit_user_ids.includes(target_user.uuid) ) return true
else return false
},
view(project, target_user){
if ( project.user_id === target_user.uuid ) return true
else if ( project.shared_user_ids.includes(target_user.uuid) ) return true
else if ( project.edit_user_ids.includes(target_user.uuid) ) return true
else return false
},
owns(project, target_user){
return (project.user_id === target_user.uuid)
}
}
},
}
}
name(){

@ -0,0 +1,52 @@
// ===========================================================
// DEVBUG INLINE DEBUGGING HELPER - FOR USE WITH DEVBUG SERVER
// TODO: REMOVE BEFORE COMMITTING
let outs = {}
let devbug_url = 'http://localhost:8000/'
let project_api_key = 'CHANGEME'
const out = (key, what, group="") => {
if ( group ){
if ( Object.keys(outs).includes(group) ) outs[group][key] = what
else outs[group] = {}; outs[group][key] = what
}
else {
outs[key] = what
}
}
const breakpoint = (html = false, name = null) => {
var e = new Error();
(function() {
// Load better json
var js_decycle = document.createElement("script");
js_decycle.src = devbug_url+"assets/cycle.js";
js_decycle.type = 'text/javascript';
js_decycle.onload = () => {
// Load the script
var script = document.createElement("script");
script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js';
script.type = 'text/javascript';
script.onload = () => {
var $ = window.jQuery;
$(() => {
var s = e.stack.split('at'); var caller = '';
if ( s.length < 3 ) s = e.stack.split('@')
if ( s.length > 2 ) caller = s[2].trim()
else if ( s.length > 1 ) caller = s[1]
else caller = 'Unable to determine stacktrace'
var data = new FormData();
console.log(JSON.decycle({brief: (name ? name : 'Breakpoint: ')+caller,data: outs}))
data.append('data', JSON.stringify(JSON.rmref(JSON.decycle({brief: (name ? name : 'Breakpoint: ')+caller,data: outs}))))
$.ajax({
url: devbug_url+'api/v1/out/'+project_api_key,
data: data, cache: false, contentType: false, processData: false, method: 'POST', type: 'POST',
success: (res) => { console.log('DevBug POST Completed'); console.log(res) }
})
});
};
document.getElementsByTagName("head")[0].appendChild(script);
}
document.getElementsByTagName("head")[0].appendChild(js_decycle);
})();
}
window.out = out; window.breakpoint = breakpoint;
// ===========================================================

@ -0,0 +1,118 @@
if (typeof JSON.rmref !== "function") {
JSON.rmref = function rmref(o){
function eachRecursive(obj) {
for (var k in obj)
{
if (typeof obj[k] == "object" && obj[k] !== null)
eachRecursive(obj[k]);
else {
for ( var key in obj ){
if ( key === "$ref" ){
obj[k] = "cyclic structure removed";
break;
}
}
}
}
}
eachRecursive(o)
return o
}
}
if (typeof JSON.decycle !== "function") {
JSON.decycle = function decycle(object, replacer) {
"use strict";
var objects = new WeakMap(); // object to path mappings
return (function derez(value, path) {
var old_path; // The path of an earlier occurance of value
var nu; // The new object or array
if (replacer !== undefined) {
value = replacer(value);
}
if (
typeof value === "object"
&& value !== null
&& !(value instanceof Boolean)
&& !(value instanceof Date)
&& !(value instanceof Number)
&& !(value instanceof RegExp)
&& !(value instanceof String)
) {
old_path = objects.get(value);
if (old_path !== undefined) {
return {$ref: old_path};
}
objects.set(value, path);
if (Array.isArray(value)) {
nu = [];
value.forEach(function (element, i) {
nu[i] = derez(element, path + "[" + i + "]");
});
} else {
nu = {};
Object.keys(value).forEach(function (name) {
nu[name] = derez(
value[name],
path + "[" + JSON.stringify(name) + "]"
);
});
}
return nu;
}
return value;
}(object, "$"));
};
}
if (typeof JSON.retrocycle !== "function") {
JSON.retrocycle = function retrocycle($) {
"use strict";
var px = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\(?:[\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*")\])*$/;
(function rez(value) {
if (value && typeof value === "object") {
if (Array.isArray(value)) {
value.forEach(function (element, i) {
if (typeof element === "object" && element !== null) {
var path = element.$ref;
if (typeof path === "string" && px.test(path)) {
value[i] = eval(path);
} else {
rez(element);
}
}
});
} else {
Object.keys(value).forEach(function (name) {
var item = value[name];
if (typeof item === "object" && item !== null) {
var path = item.$ref;
if (typeof path === "string" && px.test(path)) {
value[name] = eval(path);
} else {
rez(item);
}
}
});
}
}
}($));
return $;
};
}

@ -3,6 +3,7 @@ function output(inp) {
}
function syntaxHighlight(json) {
console.log(json)
json = JSON.stringify(JSON.parse(json.replace(/&quot;/g, '"')), undefined, 4)
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {

@ -15,15 +15,26 @@ class v1 {
const projects = await Project.find({ archived: false, user_id: req.session.auth.uuid })
let find = {
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 = await Project.find(find)
const shared_projects = {
view: await Project.find(view_find),
edit: await Project.find(edit_find),
}
/*
* Return the main view.
@ -45,7 +56,7 @@ class v1 {
}
// check access perms
if ( !(project.user_id === req.session.auth.uuid) ){
if ( !devbug.permission.project.edit(project, req.session.auth.user) ){
return _flitter.error(res, 401, {reason: 'You do not have permissions to edit this project.'})
}
@ -63,8 +74,8 @@ class v1 {
}
// check access perms
if ( !(project.user_id === req.session.auth.uuid) ){
return _flitter.error(res, 401, {reason: 'Project not found with the specified ID.'})
if ( !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
@ -102,7 +113,7 @@ class v1 {
const outs = await Out.find({ project_id: project.id }).sort('-created')
if ( !(project.user_id === req.session.auth.uuid) && !(project.shared_user_ids.includes(req.session.auth.uuid)) ){
if ( !devbug.permission.project.view(project, req.session.auth.user) ){
return _flitter.error(res, 401, {reason: 'You do not have permission to view this project.'})
}
@ -119,6 +130,7 @@ class v1 {
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.'})
@ -126,11 +138,10 @@ class v1 {
const project = await Project.findById(out.project_id)
if ( !project || (!(project.user_id === req.session.auth.uuid) && !(project.shared_user_ids.includes(req.session.auth.uuid))) ){
if ( !project || (!devbug.permission.project.view(project, req.session.auth.user)) ){
return _flitter.error(res, 401, {reason: 'You do not have permission to view this project.'})
}
// TODO permission access check
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 });
}
@ -138,7 +149,7 @@ class v1 {
const out = await Out.findById(req.params.id)
const project = await Project.findById(req.params.project)
if ( !project || ( !(project.user_id === req.session.auth.uuid) ) ){
if ( !project || ( !devbug.permission.project.edit(project, req.session.auth.user) ) ){
return _flitter.error(res, 401, {reason: 'You do not have permission to edit this project.'})
}
@ -155,7 +166,7 @@ class v1 {
return _flitter.error(res, 404, {reason: 'Project not found with the specified ID.'})
}
if ( !(project.user_id === req.session.auth.uuid) ){
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.'})
}
@ -165,7 +176,7 @@ class v1 {
async project_delete_do(req, res, next){
const project = await Project.findById(req.params.id)
if ( project && ( !(project.user_id === req.session.auth.uuid) ) ){
if ( project && ( !devbug.permission.project.owns(project, req.session.auth.user) ) ){
return _flitter.error(res, 401, {reason: 'You do not have permission to edit this project.'})
}
@ -191,24 +202,44 @@ class v1 {
if ( !project ) return _flitter.error(res, 404, {reason: 'Project not found with the specified ID.'})
if ( !(project.user_id === req.session.auth.uuid) ) 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 project.'})
let find = {
uuid: { $nin: [] }
// Find read-only users
const read_find = {
uuid: { $in: [] }
}
find.uuid.$nin.push(req.session.auth.uuid)
find.uuid.$nin = find.uuid.$nin.concat(project.shared_user_ids)
const to_share = await _flitter.model('User').find(find)
find = {
uuid: { $in: find.uuid.$nin }
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)
}
const shared = await _flitter.model('User').find(find)
return _flitter.view(res, 'dash_v1:share', { user: req.session.auth.user, sharing: { to_share, shared }, project, title: 'Share Project: '+project.name, show_back: true })
return _flitter.view(res, 'dash_v1:share', { user: req.session.auth.user, sharing, project, title: 'Share Project: '+project.name, show_back: true })
}
async project_share_do(req, res, next){
@ -218,7 +249,7 @@ class v1 {
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 ( !(project.user_id === req.session.auth.uuid) ) 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 project."})
if ( !(project.user_id === target_user.uuid) && !(project.shared_user_ids.includes(target_user.uuid)) ){
project.shared_user_ids.push(target_user.uuid)
@ -227,6 +258,28 @@ class v1 {
return res.redirect('/dash/v1/project/share/'+project.id)
}
async project_share_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.'})
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 ( !(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/project/share/'+project.id)
}
async project_share_revoke(req, res, next){
const project = await Project.findById(req.params.id)
@ -235,7 +288,7 @@ class v1 {
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 ( !(project.user_id === req.session.auth.uuid || project.shared_user_ids.includes(req.session.auth.uuid)) ) 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 project."})
const to_dash = project.shared_user_ids.includes(req.session.auth.uuid)
@ -248,6 +301,27 @@ class v1 {
return res.redirect('/dash/v1/project/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 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."})
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/project/share/'+project.id)
}
async project_share_transfer(req, res, next){
const project = await Project.findById(req.params.id)
@ -256,14 +330,18 @@ class v1 {
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 ( !project.user_id === req.session.auth.uuid ) 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 project.'})
project.user_id = target_user.uuid
project.shared_user_ids.push(req.session.auth.uuid)
if ( project.shared_user_ids.includes(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()
@ -274,7 +352,7 @@ class v1 {
const project = await Project.findById(req.params.id)
if ( !project ) return _flitter.error(res, 404, {reason: 'Project not found with the specified ID.'})
if ( !project.user_id === req.session.auth.uuid ) 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 project.'})
let share_data = {
project_id: project.id,

@ -10,6 +10,7 @@ const Project = {
archived: { type: Boolean, default: false },
data: String,
shared_user_ids: [String],
edit_user_ids: [String],
uuid: { type: String, default: uuid }
}

@ -43,7 +43,9 @@ const v1 = {
'/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 ],

@ -17,11 +17,8 @@ block content
| function.
pre
code #{devbug.code.php}
h2 JavaScript (Web)
p
| This snippet works by loading jQuery via a script tag when a breakpoint is triggered.
pre
code #{devbug.code.js}
h2
a(href="/assets/agents/web.js" target="_blank") JavaScript (Web)
h2 Using the API
p You can post output to DevBug projects from anywhere using the DevBug API. Here's how:
pre

@ -24,7 +24,7 @@ block content
li
a.action(href='/dash/v1/project/edit/'+project.id) Edit
if shared_projects
if shared_projects.view || shared_projects.edit
h3 Projects Shared With Me
table
thead
@ -32,7 +32,18 @@ block content
th(scope='col' style='min-width: 250px') Name
th(scope='col') Actions
tbody
each project in shared_projects
each project in shared_projects.edit
tr
td #{project.name}
td
ul(style='list-style-type: none; margin: 0; padding: 0;')
li
a.action(href='/dash/v1/project/view/'+project.id) View
li
a.action(href='/dash/v1/project/share/'+project.id+'/revoke/'+user.uuid+'/edit') Remove
li
a.action(href='/dash/v1/project/edit/'+project.id) Edit
each project in shared_projects.view
tr
td #{project.name}
td

@ -1,48 +1,88 @@
extends ./template
block content
h2 Shared With
h2 Owned By
table
thead
tr
th(scope='col' style='min-width: 250px') Username
th(scope='col') Actions
tbody
each user in sharing.shared
tr
td #{(sharing.current_owns ? `You (${sharing.owner.username})` : sharing.owner.username)}
if sharing.read.length > 0
br
h2 Shared With (Read-Only)
table
thead
tr
td #{(user.uuid === project.user_id ? user.username + " (Owner)" : user.username)}
td
ul(style='list-style-type: none; margin: 0; padding: 0;')
if !(user.uuid === project.user_id)
li
a.action(href='/dash/v1/project/share/'+project.id+'/revoke/'+user.uuid) Revoke
li
a.action(href='/dash/v1/project/share/'+project.id+'/transfer/'+user.uuid) Transfer Ownership
else
li
strike Revoke
li
strike Transfer Ownership
th(scope='col' style='min-width: 250px') Username
th(scope='col') Actions
tbody
each user in sharing.read
tr
td #{(user.uuid === project.user_id ? user.username + " (Owner)" : user.username)}
td
ul(style='list-style-type: none; margin: 0; padding: 0;')
if !(user.uuid === project.user_id)
li
a.action(href='/dash/v1/project/share/'+project.id+'/revoke/'+user.uuid) Revoke
li
a.action(href='/dash/v1/project/share/'+project.id+'/transfer/'+user.uuid) Transfer Ownership
else
li
strike Revoke
li
strike Transfer Ownership
if sharing.edit.length > 0
br
h2 Shared With (Edit)
table
thead
tr
th(scope='col' style='min-width: 250px') Username
th(scope='col') Actions
tbody
each user in sharing.edit
tr
td #{(user.uuid === project.user_id ? user.username + " (Owner)" : user.username)}
td
ul(style='list-style-type: none; margin: 0; padding: 0;')
if !(user.uuid === project.user_id)
li
a.action(href='/dash/v1/project/share/'+project.id+'/revoke/'+user.uuid+'/edit') Revoke
li
a.action(href='/dash/v1/project/share/'+project.id+'/transfer/'+user.uuid) Transfer Ownership
else
li
strike Revoke
li
strike Transfer Ownership
br
h2 Share With New User
a.btn(href='/dash/v1/project/share/'+project.id+'/invite') Generate Sharing Link
br
br
table
thead
tr
th(scope='col' style='min-width: 250px') Username
th(scope='col') Actions
tbody
each user in sharing.to_share
if sharing.other.length > 0
br
br
table
thead
tr
td #{(user.uuid === project.user_id ? user.username + " (Owner)" : user.username)}
td
ul(style='list-style-type: none; margin: 0; padding: 0;')
if !(user.uuid === project.user_id)
li
a.action(href='/dash/v1/project/share/' + project.id + '/share/'+user.uuid) Share
else
th(scope='col' style='min-width: 250px') Username
th(scope='col') Actions
tbody
each user in sharing.other
tr
td #{(user.uuid === project.user_id ? user.username + " (Owner)" : user.username)}
td
ul(style='list-style-type: none; margin: 0; padding: 0;')
if !(user.uuid === project.user_id)
li
a.action(href='/dash/v1/project/share/' + project.id + '/share/'+user.uuid) Share (View)
li
a.action(href='/dash/v1/project/share/' + project.id + '/share/'+user.uuid+'/edit') Share (Edit)
else
li
strike Share
li
strike Share
li
a.action(href='/dash/v1/project/share/' + project.id + '/transfer/' + user.uuid) Transfer Ownership
a.action(href='/dash/v1/project/share/' + project.id + '/transfer/' + user.uuid) Transfer Ownership

@ -16,5 +16,6 @@ block content
ul(style='list-style-type: none; margin: 0; padding: 0;')
li
a.action(href='/dash/v1/out/view/'+out.id) View
li
a.action(href='/dash/v1/out/delete/'+out.id+'/'+project.id) Delete
if ( devbug.permission.project.edit(project, user) )
li
a.action(href='/dash/v1/out/delete/'+out.id+'/'+project.id) Delete

Loading…
Cancel
Save