WiP on namespaces and users
This commit is contained in:
parent
432e6ffaeb
commit
1b73282e90
18 changed files with 513 additions and 0 deletions
2
app.js
2
app.js
|
@ -42,6 +42,7 @@ const grapejs = require('./routes/grapejs');
|
|||
const mosaico = require('./routes/mosaico');
|
||||
const reports = require('./routes/reports');
|
||||
const reportsTemplates = require('./routes/report-templates');
|
||||
const namespaces = require('./routes/namespaces');
|
||||
|
||||
const app = express();
|
||||
|
||||
|
@ -213,6 +214,7 @@ app.use('/api', api);
|
|||
app.use('/editorapi', editorapi);
|
||||
app.use('/grapejs', grapejs);
|
||||
app.use('/mosaico', mosaico);
|
||||
app.use('/namespaces', namespaces);
|
||||
|
||||
if (config.reports && config.reports.enabled === true) {
|
||||
app.use('/reports', reports);
|
||||
|
|
|
@ -177,3 +177,18 @@ templates=[["demo", "Demo Template"]]
|
|||
# The bottom line is that if people who are creating report templates or have write access to the DB cannot be trusted,
|
||||
# then it's safer to switch off the reporting functionality below.
|
||||
enabled=false
|
||||
|
||||
|
||||
[shares.list.master]
|
||||
name="Master"
|
||||
description="All permissions"
|
||||
permissions=["view"]
|
||||
|
||||
|
||||
[shares.namespace.master]
|
||||
name="Master"
|
||||
description="All permissions"
|
||||
|
||||
[shares.namespace.master.permissions]
|
||||
list=["view"]
|
||||
namespace=["view", "edit", "create", "delete", "create list"]
|
15
lib/knex.js
Normal file
15
lib/knex.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('config');
|
||||
|
||||
const knex = require('knex')({
|
||||
client: 'mysql',
|
||||
connection: config.mysql,
|
||||
migrations: {
|
||||
directory: __dirname + '/../setup/knex/migrations'
|
||||
}
|
||||
});
|
||||
|
||||
knex.migrate.latest();
|
||||
|
||||
module.exports = knex;
|
8
lib/models/namespaces.js
Normal file
8
lib/models/namespaces.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('config');
|
||||
const knex = require('../knex');
|
||||
|
||||
module.exports.list = () => {
|
||||
return knex('namespaces');
|
||||
};
|
|
@ -72,6 +72,7 @@
|
|||
"jquery-file-upload-middleware": "^0.1.8",
|
||||
"jsdom": "^9.12.0",
|
||||
"juice": "^4.0.2",
|
||||
"knex": "^0.13.0",
|
||||
"libmime": "^3.1.0",
|
||||
"marked": "^0.3.6",
|
||||
"memory-cache": "^0.1.6",
|
||||
|
|
50
public/fancytree/jquery.fancytree-all.min.js
vendored
Normal file
50
public/fancytree/jquery.fancytree-all.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
public/fancytree/skin-bootstrap/ui.fancytree.min.css
vendored
Normal file
6
public/fancytree/skin-bootstrap/ui.fancytree.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
public/fancytree/skin-bootstrap/vline-rtl.gif
Normal file
BIN
public/fancytree/skin-bootstrap/vline-rtl.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 842 B |
BIN
public/fancytree/skin-bootstrap/vline.gif
Normal file
BIN
public/fancytree/skin-bootstrap/vline.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 844 B |
68
routes/namespaces.js
Normal file
68
routes/namespaces.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
const passport = require('../lib/passport');
|
||||
const router = new express.Router();
|
||||
const _ = require('../lib/translate')._;
|
||||
const namespaces = require('../lib/models/namespaces');
|
||||
|
||||
router.all('/*', (req, res, next) => {
|
||||
if (!req.user) {
|
||||
req.flash('danger', _('Need to be logged in to access restricted content'));
|
||||
return res.redirect('/users/login?next=' + encodeURIComponent(req.originalUrl));
|
||||
}
|
||||
// res.setSelectedMenu('namespaces');
|
||||
next();
|
||||
});
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
res.render('namespaces/namespaces', {
|
||||
title: _('Namespaces'),
|
||||
useFancyTree: true
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/list/ajax', (req, res, next) => {
|
||||
const entries = {};
|
||||
const roots = [];
|
||||
|
||||
namespaces.list().then(rows => {
|
||||
for (let row of rows) {
|
||||
let entry;
|
||||
if (!entries[row.id]) {
|
||||
entry = {
|
||||
children: []
|
||||
};
|
||||
entries[row.id] = entry;
|
||||
} else {
|
||||
entry = entries[row.id];
|
||||
}
|
||||
|
||||
if (row.parent) {
|
||||
if (entries[row.parent]) {
|
||||
entries[row.parent] = {
|
||||
children: []
|
||||
};
|
||||
}
|
||||
|
||||
entries[row.parent].children.push(entry);
|
||||
|
||||
} else {
|
||||
roots.push(entry);
|
||||
}
|
||||
|
||||
entry.title = row.name;
|
||||
entry.key = row.id;
|
||||
}
|
||||
|
||||
console.log(roots);
|
||||
return res.json(roots);
|
||||
|
||||
}).catch(err => {
|
||||
return res.json({
|
||||
error: err.message || err,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
9
setup/knex/config.js
Normal file
9
setup/knex/config.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
if (!process.env.NODE_CONFIG_DIR) {
|
||||
process.env.NODE_CONFIG_DIR = __dirname + '/../../config';
|
||||
}
|
||||
|
||||
const config = require('config');
|
||||
|
||||
module.exports = config;
|
8
setup/knex/knexfile.js
Normal file
8
setup/knex/knexfile.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('./config');
|
||||
|
||||
module.exports = {
|
||||
client: 'mysql',
|
||||
connection: config.mysql
|
||||
};
|
75
setup/knex/migrations/20170506102634_base.js
Normal file
75
setup/knex/migrations/20170506102634_base.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
exports.up = function(knex, Promise) {
|
||||
/* This is shows what it would look like when we specify the "users" table with Knex.
|
||||
In some sense, this is probably the most complicated table we have in Mailtrain.
|
||||
|
||||
return knex.schema.hasTable('users'))
|
||||
.then(exists => {
|
||||
if (!exists) {
|
||||
return knex.schema.createTable('users', table => {
|
||||
table.increments('id').primary();
|
||||
table.string('username').notNullable().defaultTo('');
|
||||
table.string('password').notNullable().defaultTo('');
|
||||
table.string('email').notNullable();
|
||||
table.string('access_token', 40).index();
|
||||
table.string('reset_token').index();
|
||||
table.dateTime('reset_expire');
|
||||
table.timestamp('created').defaultTo(knex.fn.now());
|
||||
})
|
||||
|
||||
// INNODB tables have the limit of 767 bytes for an index.
|
||||
// Combined with the charset used, this poses limits on the size of keys. Knex does not offer API
|
||||
// for such settings, thus we resort to raw queries.
|
||||
.raw('ALTER TABLE `users` MODIFY `email` VARCHAR(255) CHARACTER SET utf8 NOT NULL')
|
||||
.raw('ALTER TABLE `users` ADD UNIQUE KEY `email` (`email`)')
|
||||
.raw('ALTER TABLE `users` ADD KEY `username` (`username`(191))')
|
||||
.raw('ALTER TABLE `users` ADD KEY `check_reset` (`username`(191),`reset_token`,`reset_expire`)')
|
||||
|
||||
.then(() => knex('users').insert({
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
password: '$2a$10$FZV.tFT252o4iiHoZ9b2sOZOc.EBDOcY2.9HNCtNwshtSLf21mB1i',
|
||||
email: 'hostmaster@sathyasai.org'
|
||||
}));
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
// We should check here if the tables already exist and upgrade them to db_schema_version 28, which is the baseline.
|
||||
// For now, we just check whether our DB is up-to-date based on the existing SQL migration infrastructure in Mailtrain.
|
||||
return knex('settings').where({key: 'db_schema_version'}).first('value')
|
||||
.then(row => {
|
||||
if (!row || Number(row.value) !== 28) {
|
||||
throw new Error('Unsupported DB schema version: ' + row.value);
|
||||
}
|
||||
})
|
||||
|
||||
// We have to update data types of primary keys and related foreign keys. Mailtrain uses unsigned int(11), while
|
||||
// Knex uses unsigned int (which is unsigned int(10) ).
|
||||
.then(() => knex.schema
|
||||
.raw('ALTER TABLE `users` MODIFY `id` int unsigned not null auto_increment')
|
||||
.raw('ALTER TABLE `lists` MODIFY `id` int unsigned not null auto_increment')
|
||||
.raw('ALTER TABLE `confirmations` MODIFY `list` int unsigned not null')
|
||||
.raw('ALTER TABLE `custom_fields` MODIFY `list` int unsigned not null')
|
||||
.raw('ALTER TABLE `importer` MODIFY `list` int unsigned not null')
|
||||
.raw('ALTER TABLE `segments` MODIFY `list` int unsigned not null')
|
||||
.raw('ALTER TABLE `triggers` MODIFY `list` int unsigned not null')
|
||||
.raw('ALTER TABLE `custom_forms` MODIFY `list` int unsigned not null')
|
||||
)
|
||||
|
||||
/*
|
||||
Remaining foreign keys:
|
||||
-----------------------
|
||||
|
||||
links campaign campaigns id
|
||||
segment_rules segment segments id
|
||||
import_failed import importer id
|
||||
rss parent campaigns id
|
||||
attachments campaign campaigns id
|
||||
custom_forms_data form custom_forms id
|
||||
report_template report_template report_templates id
|
||||
*/
|
||||
};
|
||||
|
||||
exports.down = function(knex, Promise) {
|
||||
// return knex.schema.dropTable('users');
|
||||
};
|
31
setup/knex/migrations/20170507083345_create_namespaces.js
Normal file
31
setup/knex/migrations/20170507083345_create_namespaces.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
exports.up = function(knex, Promise) {
|
||||
return knex.schema.createTable('namespaces', table => {
|
||||
table.increments('id').primary();
|
||||
table.string('name');
|
||||
table.text('description');
|
||||
table.integer('parent').unsigned().references('namespaces.id').onDelete('CASCADE');
|
||||
})
|
||||
.table('lists', table => {
|
||||
table.integer('namespace').unsigned().notNullable();
|
||||
})
|
||||
|
||||
.then(() => knex('namespaces').insert({
|
||||
id: 1,
|
||||
name: 'Global',
|
||||
description: 'Global namespace'
|
||||
}))
|
||||
|
||||
.then(() => knex('lists').update({
|
||||
namespace: 1
|
||||
}))
|
||||
|
||||
.then(() => knex.schema.table('lists', table => {
|
||||
table.foreign('namespace').references('namespaces.id').onDelete('CASCADE');
|
||||
}))
|
||||
|
||||
;
|
||||
};
|
||||
|
||||
exports.down = function(knex, Promise) {
|
||||
return knex.schema.dropTable('namespaces');
|
||||
};
|
51
setup/knex/migrations/20170507084114_create_permissions.js
Normal file
51
setup/knex/migrations/20170507084114_create_permissions.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
const config = require('../config');
|
||||
|
||||
exports.up = function(knex, Promise) {
|
||||
return knex.schema.createTable('shares_list', table => {
|
||||
table.increments('id').primary();
|
||||
table.integer('list').unsigned().notNullable().references('lists.id').onDelete('CASCADE');
|
||||
table.integer('user').unsigned().notNullable().references('users.id').onDelete('CASCADE');
|
||||
table.integer('level').notNullable();
|
||||
table.unique(['list', 'user']);
|
||||
})
|
||||
|
||||
.createTable('shares_namespace', table => {
|
||||
table.increments('id').primary();
|
||||
table.integer('namespace').unsigned().notNullable().references('namespaces.id').onDelete('CASCADE');
|
||||
table.integer('user').unsigned().notNullable().references('users.id').onDelete('CASCADE');
|
||||
table.string('level', 64).notNullable();
|
||||
table.unique(['namespace', 'user']);
|
||||
})
|
||||
|
||||
.createTable('permissions_list', table => {
|
||||
table.increments('id').primary();
|
||||
table.integer('list').unsigned().notNullable().references('lists.id').onDelete('CASCADE');
|
||||
table.integer('user').unsigned().notNullable().references('users.id').onDelete('CASCADE');
|
||||
table.string('permission', 64).notNullable();
|
||||
table.unique(['list', 'user', 'permission']);
|
||||
})
|
||||
|
||||
.createTable('permissions_namespace', table => {
|
||||
table.increments('id').primary();
|
||||
table.integer('namespace').unsigned().notNullable().references('lists.id').onDelete('CASCADE');
|
||||
table.integer('user').unsigned().notNullable().references('users.id').onDelete('CASCADE');
|
||||
table.string('permission', 64).notNullable();
|
||||
table.unique(['namespace', 'user', 'permission']);
|
||||
})
|
||||
|
||||
.then(() => knex('shares_namespace').insert({
|
||||
id: 1,
|
||||
namespace: 1,
|
||||
user: 1,
|
||||
level: 'master'
|
||||
}))
|
||||
|
||||
;
|
||||
};
|
||||
|
||||
exports.down = function(knex, Promise) {
|
||||
return knex.schema.dropTable('shares_namespace')
|
||||
.dropTable('shares_list')
|
||||
.dropTable('permissions_namespace')
|
||||
.dropTable('permissions_list');
|
||||
};
|
|
@ -25,6 +25,10 @@
|
|||
<link rel="stylesheet" href="/summernote/summernote.css">
|
||||
{{/if}}
|
||||
|
||||
{{#if useFancyTree}}
|
||||
<link rel="stylesheet" href="/fancytree/skin-bootstrap/ui.fancytree.min.css" >
|
||||
{{/if}}
|
||||
|
||||
{{#each customStyles}}
|
||||
<link rel="stylesheet" href="{{this}}">
|
||||
{{/each}}
|
||||
|
@ -33,6 +37,20 @@
|
|||
<script src="{{this}}"></script>
|
||||
{{/each}}
|
||||
|
||||
<style type="text/css">
|
||||
#tree .fancytree-container {
|
||||
height: 100px;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
}
|
||||
#tree .fancytree-active {
|
||||
background-color: #5094ce;
|
||||
}
|
||||
#tree .fancytree-title {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="{{#if user}}logged-in user-{{user.username}}{{/if}}">
|
||||
|
@ -166,6 +184,12 @@
|
|||
<script src="/javascript/editor.js"></script>
|
||||
{{/if}}
|
||||
|
||||
{{#if useFancyTree}}
|
||||
<script src="/javascript/jquery-ui-1.12.1.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="/fancytree/jquery.fancytree-all.min.js"></script>
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{> tracking_scripts}}
|
||||
|
||||
</body>
|
||||
|
|
39
views/namespaces/edit.hbs
Normal file
39
views/namespaces/edit.hbs
Normal file
|
@ -0,0 +1,39 @@
|
|||
<ol class="breadcrumb">
|
||||
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
|
||||
<li>{{#translate}}Namespaces{{/translate}}</li>
|
||||
<li class="active">{{#translate}}Edit Namespace{{/translate}}</li>
|
||||
</ol>
|
||||
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-primary" href="/reports/create" role="button"><i class="glyphicon glyphicon-plus"></i> {{#translate}}Create Namespace{{/translate}}</a>
|
||||
</div>
|
||||
|
||||
<h2>{{#translate}}Namespaces{{/translate}}</h2>
|
||||
|
||||
<hr>
|
||||
|
||||
<form method="post" class="delete-form" id="namespaces-delete" action="/namespaces/delete">
|
||||
<input type="hidden" name="_csrf" value="{{csrfToken}}">
|
||||
<input type="hidden" name="id" value="{{id}}" />
|
||||
</form>
|
||||
|
||||
<form class="form-horizontal" method="post" action="/namespaces/edit">
|
||||
<input type="hidden" name="_csrf" value="{{csrfToken}}">
|
||||
<input type="hidden" name="id" value="{{id}}" />
|
||||
|
||||
<div>
|
||||
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<div class="pull-right">
|
||||
<button type="submit" form="namespaces-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> {{#translate}}Delete Namespace{{/translate}}</button>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> {{#translate}}Update{{/translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
111
views/namespaces/namespaces.hbs
Normal file
111
views/namespaces/namespaces.hbs
Normal file
|
@ -0,0 +1,111 @@
|
|||
<ol class="breadcrumb">
|
||||
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
|
||||
<li class="active">{{#translate}}Namespaces{{/translate}}</li>
|
||||
</ol>
|
||||
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-primary" href="/reports/create" role="button"><i class="glyphicon glyphicon-plus"></i> {{#translate}}Create Namespace{{/translate}}</a>
|
||||
</div>
|
||||
|
||||
<h2>{{#translate}}Namespaces{{/translate}}</h2>
|
||||
|
||||
<hr>
|
||||
|
||||
<div id="tree">
|
||||
</div>
|
||||
|
||||
<div id="treetable-container" style="height: 100px; overflow: auto;">
|
||||
<table id="treetable" class="table table-hover table-striped table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>B</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
glyph_opts = {
|
||||
map: {
|
||||
expanderClosed: 'glyphicon glyphicon-menu-right',
|
||||
expanderLazy: 'glyphicon glyphicon-menu-right', // glyphicon-plus-sign
|
||||
expanderOpen: 'glyphicon glyphicon-menu-down', // glyphicon-collapse-down
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$('#tree').fancytree({
|
||||
extensions: ['glyph'],
|
||||
glyph: glyph_opts,
|
||||
selectMode: 1,
|
||||
icon: false,
|
||||
autoScroll: true,
|
||||
source: [
|
||||
{title: 'A', key: '1', expanded: true},
|
||||
{title: 'B', key: '2', expanded: true, folder: true, children: [
|
||||
{title: 'BA', key: '3', expanded: true, folder: true, children: [
|
||||
{title: 'BAA', key: '4', expanded: true},
|
||||
{title: 'BAB', key: '5', expanded: true}
|
||||
]},
|
||||
{title: 'BB', key: '6', expanded: true, folder: true, children: [
|
||||
{title: 'BBA', key: '7', expanded: true},
|
||||
{title: 'BBB', key: '8', expanded: true}
|
||||
]}
|
||||
]}
|
||||
]
|
||||
});
|
||||
|
||||
/*
|
||||
$('#treetable').fancytree({
|
||||
extensions: ['glyph', 'table'],
|
||||
glyph: glyph_opts,
|
||||
selectMode: 1,
|
||||
icon: false,
|
||||
autoScroll: true,
|
||||
scrollParent: $("#treetable-container"),
|
||||
source: [
|
||||
{title: 'A', key: '1', expanded: true},
|
||||
{title: 'B', key: '2', expanded: true, folder: true, children: [
|
||||
{title: 'BA', key: '3', expanded: true, folder: true, children: [
|
||||
{title: 'BAA', key: '4', expanded: true},
|
||||
{title: 'BAB', key: '5', expanded: true}
|
||||
]},
|
||||
{title: 'BB', key: '6', expanded: true, folder: true, children: [
|
||||
{title: 'BBA', key: '7', expanded: true},
|
||||
{title: 'BBB', key: '8', expanded: true}
|
||||
]}
|
||||
]}
|
||||
],
|
||||
table: {
|
||||
nodeColumnIdx: 0
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
$('#treetable').fancytree({
|
||||
extensions: ['glyph', 'table'],
|
||||
glyph: glyph_opts,
|
||||
selectMode: 1,
|
||||
icon: false,
|
||||
autoScroll: true,
|
||||
scrollParent: $("#treetable-container"),
|
||||
source: {
|
||||
url: "/namespaces/list/ajax",
|
||||
cache: true
|
||||
},
|
||||
table: {
|
||||
nodeColumnIdx: 0
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
Loading…
Add table
Add a link
Reference in a new issue