diff options
| author | Jules Laplace <jules@okfoc.us> | 2014-09-10 09:26:36 -0400 |
|---|---|---|
| committer | Jules Laplace <jules@okfoc.us> | 2014-09-10 09:26:36 -0400 |
| commit | fcc74fbe841d542da252d5688e7b90b1e2799224 (patch) | |
| tree | 5a6cee797637fa4cf3ca2dd57e5cbb0669cecd57 | |
| parent | 6663ede5b27c2d4aa0caa1072463b97af8de8b57 (diff) | |
| parent | 6d2746ad8a24f1ac3da5e9cb2ed452b73da20b71 (diff) | |
merge
37 files changed, 1252 insertions, 69 deletions
@@ -6,6 +6,7 @@ "jquery": "1.11.0", "momentjs": "~2.5.1", "lodash": "", - "fiber": "" + "fiber": "", + "jquery-jsonview": "1.2.0" } } diff --git a/public/assets/img/profile.png b/public/assets/img/profile.png Binary files differnew file mode 100644 index 0000000..bde68e0 --- /dev/null +++ b/public/assets/img/profile.png diff --git a/public/assets/javascripts/ui/lib/Parser.js b/public/assets/javascripts/ui/lib/Parser.js index 1cf0418..52c96e6 100644 --- a/public/assets/javascripts/ui/lib/Parser.js +++ b/public/assets/javascripts/ui/lib/Parser.js @@ -21,7 +21,7 @@ var Parser = { } }, tag: function (media) { - return '<img src="' + media.url + '" onerror="imgError(this);">'; + return '<img src="' + media.url + '">'; } }, { type: 'video', @@ -43,7 +43,7 @@ var Parser = { video.load() }, tag: function (media) { - return '<video src="' + media.url + '" onerror="imgError(this);">'; + return '<video src="' + media.url + '">'; } }, { type: 'youtube', @@ -73,7 +73,8 @@ var Parser = { }) }, tag: function (media) { - return '<img class="video" type="youtube" vid="'+media.token+'" src="'+media.thumbnail+'"><span class="playvid">▶</span>'; + // return '<img class="video" type="youtube" vid="'+media.token+'" src="'+media.thumbnail+'"><span class="playvid">▶</span>'; + return '<div class="video" style="width: ' + media.width + 'px; height: ' + media.height + 'px; overflow: hidden; position: relative;"><iframe frameborder="0" scrolling="no" seamless="seamless" webkitallowfullscreen="webkitAllowFullScreen" mozallowfullscreen="mozallowfullscreen" allowfullscreen="allowfullscreen" id="okplayer" width="' + media.width + '" height="' + media.height + '" src="http://youtube.com/embed/' + media.token + '?showinfo=0" style="position: absolute; top: 0px; left: 0px; width: ' + media.width + 'px; height: ' + media.height + 'px;"></iframe></div>' } }, { type: 'vimeo', @@ -101,7 +102,8 @@ var Parser = { }) }, tag: function (media) { - return '<img class="video" type="vimeo" vid="'+media.token+'" src="'+media.thumbnail+'"><span class="playvid">▶</span>'; + // return '<img class="video" type="vimeo" vid="'+media.token+'" src="'+media.thumbnail+'"><span class="playvid">▶</span>'; + return '<div class="video" style="width: ' + media.width + 'px; height: ' + media.height + 'px; overflow: hidden; position: relative;"><iframe frameborder="0" scrolling="no" seamless="seamless" webkitallowfullscreen="webkitAllowFullScreen" mozallowfullscreen="mozallowfullscreen" allowfullscreen="allowfullscreen" id="okplayer" src="http://player.vimeo.com/video/' + media.token + '?api=1&js_api=1&title=0&byline=0&portrait=0&playbar=0&player_id=okplayer&loop=0&autoplay=0" width="' + media.width + '" height="' + media.height + '" style="position: absolute; top: 0px; left: 0px; width: ' + media.width + 'px; height: ' + media.height + 'px;"></iframe></div>' } }, /* @@ -165,5 +167,18 @@ var Parser = { if (! matched) { cb(null) } - } -}
\ No newline at end of file + }, + + tag: function (media){ + if (media.type in Parser.lookup) { + return Parser.lookup[media.type].tag(media) + } + return "" + }, + + thumbnail: function (media) { + return '<img src="' + (media.thumbnail || media.url) + '" class="thumb">'; + }, + +}; +Parser.lookup = _.indexBy(Parser.integrations, 'type'); diff --git a/public/assets/javascripts/ui/site/ProjectList.js b/public/assets/javascripts/ui/site/ProjectList.js index d772b20..ee1b89f 100644 --- a/public/assets/javascripts/ui/site/ProjectList.js +++ b/public/assets/javascripts/ui/site/ProjectList.js @@ -1,7 +1,7 @@ var ProjectList = View.extend({ - el: "#projectList", + el: ".projectList", events: { "mouseenter td.border": 'spinOn', @@ -24,4 +24,3 @@ var ProjectList = View.extend({ } }) - diff --git a/public/assets/javascripts/ui/site/StaffView.js b/public/assets/javascripts/ui/site/StaffView.js new file mode 100644 index 0000000..fdf39d2 --- /dev/null +++ b/public/assets/javascripts/ui/site/StaffView.js @@ -0,0 +1,49 @@ +var StaffView = View.extend({ + el: ".page", + + events: { + "click #toggle-staff": "toggleStaff", + }, + + initialize: function() { + this.$toggleStaff = $("#toggle-staff") + this.$mediaEmbed = $("#media-embed") + if (this.$toggleStaff.length && this.$toggleStaff.data().isstaff) { + this.$toggleStaff.html("Is Staff") + } + if (this.$mediaEmbed.length) { + var media = this.$mediaEmbed.data() + this.$mediaEmbed.html( Parser.tag( media ) ) + } + }, + + load: function() { + $(".json").each(function(){ + $(this).JSONView( this.innerText ) + }).show() + + this.projectList = new ProjectList () + }, + + toggleStaff: function(){ + var state = ! this.$toggleStaff.data().isstaff + var verb = state ? "promote this user to staff?" : "remove this user from staff?" + ConfirmModal.confirm("Are you sure you want to " + verb, function(){ + $.ajax({ + type: "put", + dataType: "json", + url: window.location.href + "/bless", + data: { + state: state, + _csrf: $("#_csrf").val(), + }, + success: function(data){ + this.$toggleStaff.data("isstaff", data.state) + this.$toggleStaff.html(data.state ? "Is Staff" : "Make Staff") + $("#is-staff").html(data.state ? "yes" : "no") + }.bind(this) + }) + }.bind(this)) + }, + +}) diff --git a/public/assets/stylesheets/app.css b/public/assets/stylesheets/app.css index 5922ab5..17a7dc0 100755 --- a/public/assets/stylesheets/app.css +++ b/public/assets/stylesheets/app.css @@ -161,23 +161,26 @@ h5 { text-align:center; } -.page .profile { +.page.profile { color:white; } -.page table { +.page table.demo, +.page table.profilepage, +.page table.projectList { width: 100%; border-top: 1px solid; margin: 40px 0 0 0; border-spacing: 0; - clear:nboth; + clear: both; } -.page tr { +.page table.profilepage tr, +.page table.projectList tr { height: 400px; } .page table.showcase { height:70vh; } -.page table td.border { +.page table.projectList td.border { position: relative; border-right: 1px solid; } @@ -191,7 +194,9 @@ iframe.embed { z-index: -1; pointer-events: none; } -.page table td { +.page table.demo td, +.page table.profilepage td, +.page table.projectList td { width: 33.3333%; background-size: cover; background-repeat: no-repeat; @@ -238,7 +243,7 @@ iframe.embed { color:white; } -#projectList .editBtn { +.projectList .editBtn { position: absolute; right: 10px; top: 10px; diff --git a/public/assets/stylesheets/staff.css b/public/assets/stylesheets/staff.css new file mode 100644 index 0000000..aa21f9b --- /dev/null +++ b/public/assets/stylesheets/staff.css @@ -0,0 +1,103 @@ +* { + font-weight: 300; +} +th, b { + font-weight: bold; +} +table { + display: inline-block; + margin-right: 40px; + vertical-align: top; +} +td, th { + text-align: left; + padding: 2px 5px; +} +.page { + text-align: left; +} +.footer { + text-align: center; +} +h1 { + text-align: left; + display: inline-block; +} +nav { + display: inline-block; + text-align: left; +} +nav a { + padding-left: 20px; +} +hr { + border: 1px solid #bbb; + margin: 5px auto 10px; +} +.body { + width: 80%; + margin: 0 auto; +} +.json { + display: none; +} +.jsonview { + border: 1px solid #ddd; + background: rgba(238,238,238,0.5); + padding: 4px; + margin: 0 auto; + max-width: 100%; + overflow-x: auto; + margin-bottom: 20px; + max-height: 60vh; +} +.jsonview * { + font-family: monospace !important; + font-size: 12px; +} +.jsonview .collapser { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} +.staff { + font-size: 15px; +} +.staff .editLinks a { + color: #00f; +} +#iframe-embed, #iframe-embed tr, #iframe-embed td { + width: 79vw; +} +#iframe-embed td { + padding: 0; +} +h2 { + margin: 20px auto; +} +.avatar { + height: 40px; + width: 40px; + display: inline-block; + background-size: cover; +} +#actions button { + float: none; + width: auto; +} +iframe.embed { + position: static; + width: 100%; + height: 44vw; + border: 1px solid black; +} +.page table.projectList, +.page table.projectList td.border { + border: 0; +} +#pagination { + margin: 10px 0; +} +#pagination a { + color: #00f; +}
\ No newline at end of file diff --git a/server/index.js b/server/index.js index e9efef0..952ade9 100644 --- a/server/index.js +++ b/server/index.js @@ -97,7 +97,7 @@ site.route = function () { app.get('/profile', views.profile) app.get('/profile/edit', views.profile) - app.get('/profile/:name', views.profile) + app.get('/profile/:username', views.profile) app.get('/about', views.docs); app.get('/about/:name/edit', views.docs); @@ -106,9 +106,6 @@ site.route = function () { app.get('/api/profile', middleware.ensureAuthenticated, api.profile.show) app.put('/api/profile', middleware.ensureAuthenticated, api.profile.update) - app.get('/staff', middleware.ensureAuthenticated, middleware.ensureIsStaff, views.staff.index); - app.get('/staff/bless', middleware.ensureAuthenticated, views.staff.bless); - app.get('/api/docs', middleware.ensureAuthenticated, middleware.ensureIsStaff, api.docs.show) app.post('/api/docs/new', middleware.ensureAuthenticated, middleware.ensureIsStaff, api.docs.create) app.post('/api/docs/edit', middleware.ensureAuthenticated, middleware.ensureIsStaff, api.docs.update) @@ -148,6 +145,7 @@ site.route = function () { app.get('/test/*', middleware.ensureAuthenticated, middleware.ensureIsStaff, views.modal) + views.staff.route(app) } diff --git a/server/lib/api/projects.js b/server/lib/api/projects.js index 2a5beff..da41b48 100644 --- a/server/lib/api/projects.js +++ b/server/lib/api/projects.js @@ -41,6 +41,7 @@ var projects = { data.media = JSON.parse(data.media) data.colors = JSON.parse(data.colors) data.startPosition = JSON.parse(data.startPosition) + data.created_at = new Date () upload.put("projects", req.files.thumbnail, { unacceptable: function(err){ @@ -72,6 +73,7 @@ var projects = { data.name = util.sanitize(data.name) data.slug = util.slugify(data.name) data.description = util.sanitize(data.description) + data.updated_at = new Date () if (req.files.thumbnail) { upload.put("projects", req.files.thumbnail, { diff --git a/server/lib/auth/index.js b/server/lib/auth/index.js index 99af9b5..c2275ff 100644 --- a/server/lib/auth/index.js +++ b/server/lib/auth/index.js @@ -111,6 +111,7 @@ var auth = { return info ? res.json(info) : res.redirect("/login"); } + user.last_seen = new Date () user.last_ip = util.ip2num( req.ip ) user.save(function(err, data){ if (err) console.err('error setting ip for user') }) @@ -173,7 +174,8 @@ var auth = { email: email, created_ip: util.ip2num( req.ip ), last_ip: util.ip2num( req.ip ), - created_at: new Date () + created_at: new Date (), + last_seen: new Date (), } new User(data).save(function(err, user){ if (err || ! data) { return res.json({ error: err }) } diff --git a/server/lib/middleware.js b/server/lib/middleware.js index 9d6236a..9790f8f 100644 --- a/server/lib/middleware.js +++ b/server/lib/middleware.js @@ -89,7 +89,7 @@ var middleware = { }) } }, - + } module.exports = middleware diff --git a/server/lib/schemas/User.js b/server/lib/schemas/User.js index b64f8fc..180a140 100644 --- a/server/lib/schemas/User.js +++ b/server/lib/schemas/User.js @@ -64,6 +64,7 @@ var UserSchema = new mongoose.Schema({ isStaff: { type: Boolean, default: false }, created_at: { type: Date }, updated_at: { type: Date }, + last_seen: { type: Date }, created_ip: { type: Number }, last_ip: { type: Number }, }); diff --git a/server/lib/util.js b/server/lib/util.js index 87e2d54..791d3e2 100644 --- a/server/lib/util.js +++ b/server/lib/util.js @@ -8,7 +8,6 @@ var nonAlphanumerics = new RegExp('[^-_a-zA-Z0-9]', 'g') var consecutiveDashes = new RegExp("-+", 'g') var entities = new RegExp("[<>&]", 'g') - var util = {} util.trim = function (s){ return (s || "").replace(whitespaceHead,"").replace(whitespaceTail,"") } @@ -19,6 +18,9 @@ util.slugify = function (s){ util.sanitize = function (s){ return (s || "").replace(entities, "") } +util.escape = function (s){ + return (s || "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") +} util.capitalize = function (s) { return (s || "").split(" ").map(util.capitalizeWord).join(" "); } @@ -46,6 +48,7 @@ util.ip2num = function(dot) { } util.num2ip = function(num) { + if (! num) return "" var d = num % 256; for (var i = 3; i > 0; i--) { num = Math.floor(num/256); diff --git a/server/lib/views.js b/server/lib/views/index.js index b3c1d18..99be956 100644 --- a/server/lib/views.js +++ b/server/lib/views/index.js @@ -1,12 +1,12 @@ /* jshint node: true */ -var User = require('./schemas/User'), - Project = require('./schemas/Project'), - Documentation = require('./schemas/Documentation'), - Collaborator = require('./schemas/Collaborator'), - config = require('../../config'), +var User = require('../schemas/User'), + Project = require('../schemas/Project'), + Documentation = require('../schemas/Documentation'), + Collaborator = require('../schemas/Collaborator'), + config = require('../../../config'), marked = require('marked'), - util = require('./util'), + util = require('../util'), _ = require('lodash'), moment = require('moment'); @@ -20,6 +20,8 @@ marked.setOptions({ var views = {} +views.staff = require('./staff') + views.editor_new = function (req, res) { if (! req.user) { res.redirect('/') @@ -115,7 +117,7 @@ views.docs = function (req, res){ } views.profile = function (req, res) { - var username = req.params[0] || (req.user && req.user.username) + var username = req.params.username || (req.user && req.user.username) if (username) { User.findOne({ username: username }, function (err, user) { user ? next(user) : done(err, {}, []) @@ -149,16 +151,4 @@ views.profile = function (req, res) { } } -views.staff = { - index: function(req, res){ - res.render('staff') - }, - bless: function(req, res){ - req.user.isStaff = true - req.user.save(function(){ - res.redirect("/staff") - }) - }, -} - module.exports = views diff --git a/server/lib/views/staff.js b/server/lib/views/staff.js new file mode 100644 index 0000000..2ffea0d --- /dev/null +++ b/server/lib/views/staff.js @@ -0,0 +1,515 @@ +/* jshint node: true */ + +var User = require('../schemas/User'), + Project = require('../schemas/Project'), + Media = require('../schemas/Media'), + Collaborator = require('../schemas/Collaborator'), + config = require('../../../config'), + middleware = require('../middleware'), + util = require('../util'), + _ = require('lodash'), + moment = require('moment'); + + +var staff = module.exports = { + + fields: { + user: "_id username displayName photo created_at updated_at last_seen created_ip last_ip", + project: "_id name slug user_id privacy created_at updated_at", + }, + + defaults: { + user: { + _id: "", username: "", displayName: "", + created_at: "", updated_at: "", created_ip: "", last_ip: "", + }, + }, + + middleware: { + + ensureUsers: function(req, res, next){ + var paginationInfo = res.locals.pagination = {} + var criteria = req.criteria || {} + var limit = paginationInfo.limit = Math.min( Number(req.query.limit) || 50, 200 ) + var offset = paginationInfo.offset = Number(req.query.offset) || 0 + var sort + paginationInfo.sort = req.query.sort + paginationInfo.sortOptions = ["date", "last_seen", "username"] + switch (req.query.sort) { + case 'date': + sort = {'created_at': -1} + break + case 'last_seen': + sort = {'last_seen': -1} + break + case 'username': + default: + sort = {'username': 1} + paginationInfo.sort = "username" + break + } + User.find(criteria) + .select(staff.fields.user) + .sort(sort) + .skip(offset) + .limit(limit) + .exec(function (err, users) { + res.locals.users = users.map(staff.helpers.user) + next() + }) + }, + + ensureRecentUsers: function(req, res, next){ + var dreq = { query: { sort: 'last_seen', limit: 20, offset: 0 } } + staff.middleware.ensureUsers(dreq, res, next) + }, + + ensureProjects: function(req, res, next){ + var paginationInfo = res.locals.pagination = {} + var criteria = req.criteria || {} + var limit = paginationInfo.limit = Math.min( Number(req.query.limit) || 50, 200 ) + var offset = paginationInfo.offset = Number(req.query.offset) || 0 + var sort + paginationInfo.sort = req.query.sort + paginationInfo.sortOptions = ["date", "name"] + switch (req.query.sort) { + case 'date': + sort = {'created_at': -1} + break + case 'name': + default: + paginationInfo.sort = "name" + sort = {'slug': 1} + break + } + Project.find(criteria) + .select(staff.fields.project) + .sort(sort) + .skip(offset) + .limit(limit) + .exec(function (err, projects) { + res.locals.projects = projects.map(staff.helpers.project) + next() + }) + }, + + ensureMedia: function(req, res, next){ + var paginationInfo = res.locals.pagination = {} + var criteria = req.criteria || {} + var limit = paginationInfo.limit = Math.min( Number(req.query.limit) || 50, 200 ) + var offset = paginationInfo.offset = Number(req.query.offset) || 0 + var sort + paginationInfo.sort = req.query.sort + paginationInfo.sortOptions = ["date"] + switch (req.query.sort) { + default: + case 'date': + paginationInfo.sort = "date" + sort = {'created_at': -1} + break + } + Media.find(criteria) + // .select(staff.fields.media) + .sort(sort) + .skip(offset) + .limit(limit) + .exec(function (err, media) { + res.locals.media = media.map(staff.helpers.media) + next() + }) + }, + + ensureRecentProjects: function(req, res, next){ + var dreq = { params: { sort: 'created_at', limit: 20, offset: 0 } } + staff.middleware.ensureProjects(dreq, res, next) + }, + + ensureProjectsUsers: function(req, res, next){ + if (! res.locals.projects || ! res.locals.projects.length) { return next() } + staff.middleware.ensureObjectsUsers(res.locals.projects, next) + }, + + ensureMediaUsers: function(req, res, next){ + if (! res.locals.media || ! res.locals.media.length) { return next() } + staff.middleware.ensureObjectsUsers(res.locals.media, next) + }, + + ensureMediaUser: function(req, res, next){ + if (! res.locals.media) { return next() } + staff.middleware.ensureObjectsUsers([ res.locals.media ], function(){ + res.locals.mediaUser = res.locals.media.User + next() + }) + }, + + ensureObjectsUsers: function(objects, next){ + var dedupe = {}, user_ids + objects.forEach(function(obj){ + dedupe[ obj.user_id ] = dedupe[ obj.user_id ] || [] + dedupe[ obj.user_id ].push(obj) + }) + user_ids = _.keys(dedupe) + User.find({ _id: user_ids }) + .select(staff.fields.user) + .exec(function (err, users) { + users.forEach(function(user){ + dedupe[user._id].forEach(function(obj){ + obj.user = user + }) + }) + next() + }) + }, + + ensureProfile: function(req, res, next){ + var username = req.params.username + if (username) { + User.findOne({ username: username }, function (err, user) { + if (user) { + res.locals.profile = req.method == "GET" ? staff.helpers.user(user) : user + } + else { + res.locals.profile = null + } + next() + }) + } + else { + res.locals.profile = null + next() + } + }, + + ensureSingleMedia: function(req, res, next){ + var id = req.params.id + if (id) { + Media.findOne({ _id: id }, function (err, media) { + if (media) { + res.locals.media = req.method == "GET" ? staff.helpers.media(media) : media + } + else { + res.locals.media = null + } + next() + }) + } + else { + res.locals.media = null + next() + } + }, + + ensureUsersCount: function(req, res, next){ + User.count({}, function(err, count){ + res.locals.userCount = count || 0 + next() + }) + }, + + ensureProjectsCount: function(req, res, next){ + Project.count({}, function(err, count){ + res.locals.projectCount = count || 0 + next() + }) + }, + + ensureMediaCount: function(req, res, next){ + Media.count({}, function(err, count){ + res.locals.mediaCount = count || 0 + next() + }) + }, + + ensureProfileProjectCount: function(req, res, next){ + if (! res.locals.profile) { return next() } + Project.count({ user_id: res.locals.profile._id}, function(err, count){ + res.locals.profile.projectCount = count || 0 + next() + }) + }, + + ensureProfileMediaCount: function(req, res, next){ + if (! res.locals.profile) { return next() } + Media.count({ user_id: res.locals.profile._id}, function(err, count){ + res.locals.profile.mediaCount = count || 0 + next() + }) + }, + + ensureProfileProjects: function(req, res, next){ + if (! res.locals.profile) { return next() } + Project.find({ user_id: res.locals.profile._id }, staff.fields.project, function(err, projects){ + res.locals.projects = projects.map(staff.helpers.project) + next() + }) + }, + + ensureProfileMedia: function(req, res, next){ + if (! res.locals.profile) { return next() } + req.criteria = { user_id: res.locals.profile._id } + staff.middleware.ensureMedia(req, res, next) + }, + + ensureProject: function(req, res, next){ + res.locals.project = req.project + next() + }, + + ensureProjectUser: function(req, res, next){ + if (! res.locals.project) { return next() } + User.findOne({ _id: res.locals.project.user_id }, staff.fields.user, function(err, user){ + res.locals.projectUser = staff.helpers.user(user) || staff.defaults.user + next() + }) + }, + + ensureProjectCollaborators: function(req, res, next){ + if (! res.locals.project) { + res.locals.collaborators = [] + return next() + } + Collaborator.find({ project_id: res.locals.project._id}, function(err, collaborators){ + res.locals.collaborators = collaborators || [] + next() + }) + }, + }, + + helpers: { + user: function(user){ + user = user.toObject() + user.last_seen = moment( user.last_seen || user.updated_at || user.created_at ).fromNow() + user.created_ip = util.num2ip( user.created_ip ) + user.last_ip = util.num2ip( user.last_ip ) + return user + }, + + project: function(project){ + project = project.toObject() + project.date = moment( project.updated_at || project.created_at ).format("M/DD/YYYY H:MM") + project.user = {} + return project + }, + + media: function(media){ + media = media.toObject() + media.date = moment( media.updated_at || media.created_at ).format("M/DD/YYYY H:MM") + media.user = {} + media.shortUrl = media.url.replace(/^http.:\/\//,"") + return media + } + }, + + route: function(app){ + app.get('/staff', + middleware.ensureAuthenticated, + middleware.ensureIsStaff, + + staff.middleware.ensureRecentUsers, + staff.middleware.ensureUsersCount, + staff.middleware.ensureProjectsCount, + staff.middleware.ensureMediaCount, + + staff.index + ); + + // + // users + + app.get('/staff/users', + middleware.ensureAuthenticated, + middleware.ensureIsStaff, + + staff.middleware.ensureUsersCount, + staff.middleware.ensureUsers, + + staff.users.index + ); + app.get('/staff/users/:username', + middleware.ensureAuthenticated, + middleware.ensureIsStaff, + + staff.middleware.ensureProfile, + staff.middleware.ensureProfileProjectCount, + staff.middleware.ensureProfileMediaCount, + staff.middleware.ensureProfileProjects, + + staff.users.show + ); + app.get('/staff/users/:username/media', + middleware.ensureAuthenticated, + middleware.ensureIsStaff, + + staff.middleware.ensureProfile, + staff.middleware.ensureProfileMedia, + staff.middleware.ensureProfileMediaCount, + + staff.users.media + ); + app.put('/staff/users/:username/bless', + middleware.ensureAuthenticated, + middleware.ensureIsStaff, + + staff.middleware.ensureProfile, + + staff.users.bless + ); + + // + // projects + + app.get('/staff/projects', + middleware.ensureAuthenticated, + middleware.ensureIsStaff, + + staff.middleware.ensureProjectsCount, + + staff.middleware.ensureProjects, + staff.middleware.ensureProjectsUsers, + + staff.projects.index + ); + app.get('/staff/projects/:slug', + middleware.ensureAuthenticated, + middleware.ensureIsStaff, + + middleware.ensureProject, + staff.middleware.ensureProject, + staff.middleware.ensureProjectUser, + staff.middleware.ensureProjectCollaborators, + + staff.projects.show + ); + + // + // media + + app.get('/staff/media', + middleware.ensureAuthenticated, + middleware.ensureIsStaff, + + staff.middleware.ensureMediaCount, + + staff.middleware.ensureMedia, + staff.middleware.ensureMediaUsers, + + staff.media.index + ); + app.get('/staff/media/:id', + middleware.ensureAuthenticated, + middleware.ensureIsStaff, + + staff.middleware.ensureSingleMedia, + staff.middleware.ensureMediaUser, + + staff.media.show + ); + + }, + + paginate: function(req, res){ + var info = res.locals.pagination + info.query = "sort=" + info.sort + "&limit=" + info.limit + info.first_page = 0 + info.last_page = Math.max(0, info.max - info.limit) + info.sortOptions = info.sortOptions + if (info.offset > 0) { + info.prev_page = Math.max(0, info.offset - info.limit) + } + else { + info.prev_page = -1 + } + if (info.count == info.limit && info.offset + info.limit < info.max) { + info.next_page = info.offset + info.limit + } + else { + info.next_page = -1 + } + }, + + index: function(req, res){ + res.render('staff/index') + }, + + // /staff/users/ + // /staff/users/:username + users: { + index: function(req, res){ + res.locals.pagination.count = res.locals.users.length + res.locals.pagination.max = res.locals.userCount + staff.paginate(req, res) + res.render('staff/users/index') + }, + show: function(req, res){ + if (res.locals.profile) { + res.render('staff/users/show', { + profileJSON: util.escape( JSON.stringify( res.locals.profile ) ) + }) + } + else { + res.render('staff/users/show_404') + } + }, + media: function(req, res){ + if (res.locals.profile) { + res.locals.pagination.count = res.locals.media.length + res.locals.pagination.max = res.locals.profile.mediaCount + staff.paginate(req, res) + res.render('staff/users/media') + } + else { + res.render('staff/users/show_404') + } + }, + bless: function(req, res){ + res.locals.profile.isStaff = req.body.state == "true" + res.locals.profile.save(function(err, user){ + res.json({ state: user.isStaff }) + }) + }, + }, + + // /staff/projects/ + // /staff/projects/:name + projects: { + index: function(req, res){ + res.locals.pagination.count = res.locals.projects.length + res.locals.pagination.max = res.locals.projectCount + staff.paginate(req, res) + res.render('staff/projects/index') + }, + show: function(req, res){ + if (res.locals.project) { + res.render('staff/projects/show', { + projectJSON: util.escape( JSON.stringify( res.locals.project ) ), + projectUserJSON: util.escape( JSON.stringify( res.locals.projectUser ) ), + collaboratorsJSON: util.escape( JSON.stringify( res.locals.collaborators ) ), + }) + } + else { + res.render('staff/projects/show_404') + } + }, + }, + + media: { + index: function(req, res){ + res.locals.pagination.count = res.locals.media.length + res.locals.pagination.max = res.locals.mediaCount + staff.paginate(req, res) + res.render('staff/media/index') + }, + show: function(req, res){ + if (res.locals.media) { + res.render('staff/media/show', { + mediaJSON: util.escape( JSON.stringify( res.locals.media ) ), + mediaUserJSON: util.escape( JSON.stringify( res.locals.mediaUser ) ), + }) + } + else { + res.render('staff/media/show_404') + } + }, + } + +}
\ No newline at end of file diff --git a/views/partials/footer.ejs b/views/partials/footer.ejs index 685aec1..3f816f0 100644 --- a/views/partials/footer.ejs +++ b/views/partials/footer.ejs @@ -13,6 +13,9 @@ <span> you are signed in as → <a href="/profile/[[- user.username ]]">[[- user.displayName ]]</a> + [[ if (user.isStaff) { ]] + <a href="/staff">Staff Area</a> + [[ } ]] <a href="/logout" class="topLink">Sign Out?</a> </span> [[ } ]] diff --git a/views/projects/list-projects.ejs b/views/projects/list-projects.ejs index c78bf9f..c41ae07 100644 --- a/views/projects/list-projects.ejs +++ b/views/projects/list-projects.ejs @@ -1,6 +1,6 @@ [[ if (projects.length) { ]] - <table id="projectList"> + <table class="projectList"> <tr> [[ projects.forEach(function(project, i) { ]] diff --git a/views/staff.ejs b/views/staff.ejs deleted file mode 100644 index 0db8ebc..0000000 --- a/views/staff.ejs +++ /dev/null @@ -1,26 +0,0 @@ -<!doctype html> -<html> -<head> - <title>vvalls</title> - [[ include partials/meta ]] -</head> -<body class="loading"> -<div class="rapper page"> - [[ include partials/header ]] - - <br clear="all"> - <h1>Staff Area</h1> - <!-- - - recent users - - rooms list - - projects list - --> - - [[ include partials/confirm-modal ]] - [[ include partials/footer ]] -</div> - - -</body> -[[ include partials/scripts ]] -</html> diff --git a/views/staff/_footer.ejs b/views/staff/_footer.ejs new file mode 100644 index 0000000..839db4a --- /dev/null +++ b/views/staff/_footer.ejs @@ -0,0 +1,16 @@ + </div> + [[ include ../partials/confirm-modal ]] + [[ include ../partials/footer ]] +</div> + +</body> +[[ include ../partials/scripts ]] +<script type="text/javascript" src="/assets/javascripts/vendor/bower_components/jquery-jsonview/dist/jquery.jsonview.js"></script> +<script type="text/javascript" src="/assets/javascripts/ui/site/StaffView.js"></script> +<script> +$(function(){ + var staffView = new StaffView () + staffView.load() +}) +</script> +</html> diff --git a/views/staff/_header.ejs b/views/staff/_header.ejs new file mode 100644 index 0000000..3bbf4f1 --- /dev/null +++ b/views/staff/_header.ejs @@ -0,0 +1,15 @@ +<!doctype html> +<html> +<head> + <title>vvalls | staff</title> + [[ include ../partials/meta ]] + <link rel="stylesheet" href="/assets/javascripts/vendor/bower_components/jquery-jsonview/dist/jquery.jsonview.css"></script> + <link rel="stylesheet" href="/assets/stylesheets/staff.css"></script> + <input type="hidden" id="_csrf" name="_csrf" value="[[- token ]]"> +</head> +<body class="loading"> +<div class="rapper page staff"> + [[ include ../partials/header ]] + + <br clear="all"> + <div class="body"> diff --git a/views/staff/_media.ejs b/views/staff/_media.ejs new file mode 100644 index 0000000..19e9d0b --- /dev/null +++ b/views/staff/_media.ejs @@ -0,0 +1,37 @@ +<table id="users"> +[[ media.forEach(function(media){ ]] + <tr> + <td class="editLinks"> + <a href="/staff/media/[[- media._id ]]">[view]</a> + </td> + <td> + <a href="/staff/users/[[- media.user.username ]]">[[- media.user.username ]]</a> + </td> + <td> + [[- media.date ]] + </td> + <td> + [[- media.width ]]x[[- media.height ]] + </td> + <td> + [[- media.type ]] + </td> + <td> + [[- media.token ]] + </td> + <td> + <a class="medialink" href="[[- media.url ]]" target="_blank">[[- media.shortUrl ]]</a> + </td> + </tr> +[[ }) ]] +</table> + +<style> +.medialink { + max-width: 30vw; + display: inline-block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +</style>
\ No newline at end of file diff --git a/views/staff/_pagination.ejs b/views/staff/_pagination.ejs new file mode 100644 index 0000000..6c3bfb1 --- /dev/null +++ b/views/staff/_pagination.ejs @@ -0,0 +1,17 @@ +[[ if (pagination.prev_page !== -1 && pagination.next_page !== -1) { ]] + <div id="pagination"> + + [[ if (pagination.prev_page !== -1) { ]] + <a href="?[[- pagination.query ]]&offset=[[- pagination.prev_page ]]">←</a> + [[ } else { ]] + ← + [[ } ]] + | + [[ if (pagination.next_page !== -1) { ]] + <a href="?[[- pagination.query ]]&offset=[[- pagination.next_page ]]">Next page →</a> + [[ } else { ]] + → + [[ } ]] + + </div> +[[ } ]]
\ No newline at end of file diff --git a/views/staff/_projects.ejs b/views/staff/_projects.ejs new file mode 100644 index 0000000..9e37a6c --- /dev/null +++ b/views/staff/_projects.ejs @@ -0,0 +1,24 @@ +<table id="users"> +[[ projects.forEach(function(project){ ]] + <tr> + <td> + <a href="/staff/projects/[[- project.slug ]]">[[- project.name ]]</a> + </td> + <td class="editLinks"> + <a href="/project/[[- project.slug ]]">[view]</a> + <a href="/project/[[- project.slug ]]/edit">[edit]</a> + </td> + [[ if (project.user) { ]] + <td> + <a href="/staff/users/[[- project.user.username ]]">[[- project.user.username ]]</a> + </td> + [[ } ]] + <td> + [[- project.date ]] + </td> + <td> + [[- project.privacy ? "private" : "" ]] + </td> + </tr> +[[ }) ]] +</table> diff --git a/views/staff/_stats.ejs b/views/staff/_stats.ejs new file mode 100644 index 0000000..d1ad96f --- /dev/null +++ b/views/staff/_stats.ejs @@ -0,0 +1,17 @@ +<table> + <tr> + <th>stats</th> + </tr> + <tr> + <td><a href="/staff/users">users</a></td> + <td>[[- userCount ]]</td> + </tr> + <tr> + <td><a href="/staff/projects">projects</a></td> + <td>[[- projectCount ]]</td> + </tr> + <tr> + <td><a href="/staff/media">media</a></td> + <td>[[- mediaCount ]]</td> + </tr> +</table> diff --git a/views/staff/_users.ejs b/views/staff/_users.ejs new file mode 100644 index 0000000..d46058f --- /dev/null +++ b/views/staff/_users.ejs @@ -0,0 +1,21 @@ +<table id="users"> +[[ users.forEach(function(user){ ]] + <tr> + <td> + <a href="/staff/users/[[- user.username ]]"><div style="background-image:url([[- user.photo ]])" class="avatar"></div></a> + </td> + <td> + <a href="/staff/users/[[- user.username ]]">[[- user.username ]]</a> + </td> + <td> + [[- user.displayName ]] + </td> + <td class="editLinks"> + <a href="/profile/[[- user.username ]]">[view profile]</a> + </td> + <td> + [[- user.last_seen ]] + </td> + </tr> +[[ }) ]] +</table> diff --git a/views/staff/_users_recent.ejs b/views/staff/_users_recent.ejs new file mode 100644 index 0000000..b452c08 --- /dev/null +++ b/views/staff/_users_recent.ejs @@ -0,0 +1,26 @@ +<table id="users"> + <tr> + <th colspan="4"> + recent users + </th> + </tr> +[[ users.forEach(function(user){ ]] + <tr> + <td> + <a href="/staff/users/[[- user.username ]]"><div style="background-image:url([[- user.photo || '/assets/img/profile.png' ]])" class="avatar"></div></a> + </td> + <td> + <a href="/staff/users/[[- user.username ]]">[[- user.username ]]</a> + </td> + <td class="editLinks"> + <a href="/profile/[[- user.username ]]">[view profile]</a> + </td> + <td> + [[- user.displayName ]] + </td> + <td> + [[- user.last_seen ]] + </td> + </tr> +[[ }) ]] +</table> diff --git a/views/staff/index.ejs b/views/staff/index.ejs new file mode 100644 index 0000000..5ca7269 --- /dev/null +++ b/views/staff/index.ejs @@ -0,0 +1,16 @@ +[[ include _header ]] + + <h1>Staff Area</h1> + + <nav> + <a href="/staff/users">users</a> + <a href="/staff/projects">projects</a> + <a href="/staff/media">media</a> + </nav> + + <hr> + + [[ include _users_recent ]] + [[ include _stats ]] + +[[ include _footer ]]
\ No newline at end of file diff --git a/views/staff/media/index.ejs b/views/staff/media/index.ejs new file mode 100644 index 0000000..516af2d --- /dev/null +++ b/views/staff/media/index.ejs @@ -0,0 +1,18 @@ +[[ include ../_header ]] + + <h1>Media</h1> + + <nav> + <a href="/staff">home</a> + <a href="/staff/users">users</a> + <a href="/staff/projects">projects</a> + <a href="/staff/media">media</a> + </nav> + + <hr> + +[[ include ../_pagination ]] +[[ include ../_media ]] +[[ include ../_pagination ]] + +[[ include ../_footer ]] diff --git a/views/staff/media/show.ejs b/views/staff/media/show.ejs new file mode 100644 index 0000000..76dcd32 --- /dev/null +++ b/views/staff/media/show.ejs @@ -0,0 +1,45 @@ +[[ include ../_header ]] + + <h1>Media: [[- media.type ]]</h1> + + <nav> + <a href="/staff">home</a> + <a href="/staff/users">users</a> + <a href="/staff/projects">projects</a> + <a href="/staff/media">media</a> + </nav> + + <hr> + + <center> + <div id="media-embed" data-type="[[- media.type ]]" data-token="[[- media.token ]]" data-url="[[- media.url ]]" data-width="[[- media.width ]]" data-height="[[- media.height ]]" style="width: [[- media.width ]]px; height: [[- media.height ]]px"> + </div> + </center> + + <table> + <tr> + <td> + uploaded by + </td> + <td> + [[ if (media.user.photo) { ]] + <a href="/staff/users/[[- media.user.username ]]"><div style="background-image:url([[- media.user.photo ]])" class="avatar"></div></a> + [[ } ]] + </td> + <td> + <a href="/staff/users/[[- media.user.username ]]">[[- media.user.username ]]</a> + </td> + <td> + [[- media.user.displayName ]] + </td> + <td class="editLinks"> + <a href="/profile/[[- media.user.username ]]">[view profile]</a> + </td> + </tr> + </table> + + <div class="json"> + [[- mediaJSON ]] + </div> + +[[ include ../_footer ]] diff --git a/views/staff/media/show_404.ejs b/views/staff/media/show_404.ejs new file mode 100644 index 0000000..f07cef2 --- /dev/null +++ b/views/staff/media/show_404.ejs @@ -0,0 +1,14 @@ +[[ include ../_header ]] + + <h1>Media not found</h1> + + <nav> + <a href="/staff">home</a> + <a href="/staff/users">users</a> + <a href="/staff/projects">projects</a> + <a href="/staff/media">media</a> + </nav> + + <hr> + +[[ include ../_footer ]] diff --git a/views/staff/projects/index.ejs b/views/staff/projects/index.ejs new file mode 100644 index 0000000..482ea25 --- /dev/null +++ b/views/staff/projects/index.ejs @@ -0,0 +1,18 @@ +[[ include ../_header ]] + + <h1>Projects</h1> + + <nav> + <a href="/staff">home</a> + <a href="/staff/users">users</a> + <a href="/staff/projects">projects</a> + <a href="/staff/media">media</a> + </nav> + + <hr> + +[[ include ../_pagination ]] +[[ include ../_projects ]] +[[ include ../_pagination ]] + +[[ include ../_footer ]] diff --git a/views/staff/projects/show.ejs b/views/staff/projects/show.ejs new file mode 100644 index 0000000..0fdb00b --- /dev/null +++ b/views/staff/projects/show.ejs @@ -0,0 +1,73 @@ +[[ include ../_header ]] + + <h1>[[- project.name ]]</h1> + + <nav> + <a href="/staff">home</a> + <a href="/staff/users">users</a> + <a href="/staff/projects">projects</a> + <a href="/staff/media">media</a> + </nav> + + <hr> + + <table> + <tr> + <td> + <a href="/staff/projects/[[- project.slug ]]">[[- project.name ]]</a> + </td> + <td class="editLinks"> + <a href="/project/[[- project.slug ]]">[view]</a> + <a href="/project/[[- project.slug ]]/edit">[edit]</a> + </td> + <td> + [[- project.date ]] + </td> + <td> + [[- project.privacy ? "private" : "" ]] + </td> + </tr> + <tr> + <td> + [[ if (projectUser.photo) { ]] + <a href="/staff/users/[[- projectUser.username ]]"><div style="background-image:url([[- projectUser.photo ]])" class="avatar"></div></a> + [[ } ]] + <a href="/staff/users/[[- projectUser.username ]]">[[- projectUser.username ]]</a> + </td> + </tr> + <tr> + <td colspan="999" class="description"> + "[[- project.description ]]" + </td> + </tr> + </table> + + <br> + <br> + <table id="iframe-embed" class="projectList"> + <tr> + <td class="border"> + <iframe src="/project/fafafa/view?noui=1&mute=1" class="embed"></iframe> + </td> + </tr> + </table> + <br> + <br> + + <div class="json"> + [[- projectJSON ]] + </div> + + <h2>Author</h2> + + <div class="json"> + [[- projectUserJSON ]] + </div> + + <h2>Collaborators</h2> + + <div class="json"> + [[- collaborators.length ? collaboratorsJSON : '"no collaborators"' ]] + </div> + +[[ include ../_footer ]] diff --git a/views/staff/projects/show_404.ejs b/views/staff/projects/show_404.ejs new file mode 100644 index 0000000..70320c0 --- /dev/null +++ b/views/staff/projects/show_404.ejs @@ -0,0 +1,14 @@ +[[ include ../_header ]] + + <h1>Project not found</h1> + + <nav> + <a href="/staff">home</a> + <a href="/staff/users">users</a> + <a href="/staff/projects">projects</a> + <a href="/staff/media">media</a> + </nav> + + <hr> + +[[ include ../_footer ]] diff --git a/views/staff/users/index.ejs b/views/staff/users/index.ejs new file mode 100644 index 0000000..f14d666 --- /dev/null +++ b/views/staff/users/index.ejs @@ -0,0 +1,18 @@ +[[ include ../_header ]] + + <h1>Users</h1> + + <nav> + <a href="/staff">home</a> + <a href="/staff/users">users</a> + <a href="/staff/projects">projects</a> + <a href="/staff/media">media</a> + </nav> + + <hr> + +[[ include ../_pagination ]] +[[ include ../_users ]] +[[ include ../_pagination ]] + +[[ include ../_footer ]] diff --git a/views/staff/users/media.ejs b/views/staff/users/media.ejs new file mode 100644 index 0000000..b13e5fb --- /dev/null +++ b/views/staff/users/media.ejs @@ -0,0 +1,18 @@ +[[ include ../_header ]] + + <h1>Users</h1> + + <nav> + <a href="/staff">home</a> + <a href="/staff/users">users</a> + <a href="/staff/projects">projects</a> + <a href="/staff/media">media</a> + </nav> + + <hr> + +[[ include ../_pagination ]] +[[ include ../_media ]] +[[ include ../_pagination ]] + +[[ include ../_footer ]] diff --git a/views/staff/users/show.ejs b/views/staff/users/show.ejs new file mode 100644 index 0000000..8e9b447 --- /dev/null +++ b/views/staff/users/show.ejs @@ -0,0 +1,103 @@ +[[ include ../_header ]] + <h1>User: [[- profile.username ]]</h1> + + <nav> + <a href="/staff">home</a> + <a href="/staff/users">users</a> + <a href="/staff/projects">projects</a> + <a href="/staff/media">media</a> + </nav> + + <hr> + + <table id="users"> + <tr> + <td rowspan="999" valign="top"> + <a href="/staff/users/[[- profile.username ]]"><div style="background-image:url([[- profile.photo ]])" class="avatar"></div></a> + </td> + <td> + <a href="/staff/users/[[- profile.username ]]">[[- profile.username ]]</a> + </td> + <td> + [[- profile.displayName ]] + </td> + <td class="editLinks"> + <a href="/profile/[[- profile.username ]]">[view profile]</a> + <a href="/staff/users/[[- profile.username ]]/media">[view media]</a> + </td> + </tr> + </table> + + <h2>Profile</h2> + + <table> + <tr> + <th> + location + </th> + <td> + [[- profile.location ]] + </td> + </tr> + <tr> + <th> + last seen + </th> + <td> + [[- profile.last_seen ]] + </td> + </tr> + <tr> + <th> + projects + </th> + <td> + [[- profile.projectCount ]] + </td> + </tr> + <tr> + <th> + media + </th> + <td> + [[- profile.mediaCount ]] <a href="/staff/users/[[- profile.username ]]/media">[show]</a> + </td> + </tr> + <tr> + <th> + ip addresses + </th> + <td> + [[- profile.created_ip ]] + [[- profile.last_ip ]] + </td> + </tr> + <tr> + <th> + is admin? + </th> + <td id="is-staff"> + [[- profile.isStaff ? "yes" : "no" ]] + </td> + </tr> + </table> + + <br><br> + <div id="actions"> + [[ if (String(user._id) != String(profile._id)) { ]] + <button id="toggle-staff" data-isStaff="[[- !! profile.isStaff ]]">Make Staff</button> + [[ } ]] + </div> + + <h2>Projects</h2> + + [[- include ../_projects ]] + + <br> + <br> + + <div class="json"> + [[- profileJSON ]] + </div> + +[[ include ../_footer ]] diff --git a/views/staff/users/show_404.ejs b/views/staff/users/show_404.ejs new file mode 100644 index 0000000..bcd0271 --- /dev/null +++ b/views/staff/users/show_404.ejs @@ -0,0 +1,13 @@ +[[ include ../_header ]] + <h1>User not found</h1> + + <nav> + <a href="/staff">home</a> + <a href="/staff/users">users</a> + <a href="/staff/projects">projects</a> + <a href="/staff/media">media</a> + </nav> + + <hr> + +[[ include ../_footer ]] |
