admin page
parent
8ca648ec9d
commit
bc9c67459c
|
@ -17,11 +17,59 @@ module.exports = {
|
|||
listPublishers: async function (req, res) {
|
||||
try {
|
||||
const publishers = await PublishKey.find({
|
||||
select: ['id', 'user', 'appid', 'url', 'created_at', 'updated_at']
|
||||
select: ['id', 'user', 'appid', 'url', 'name', 'whitelisted', 'verified', 'verification_key', 'created_at', 'updated_at']
|
||||
}).populate('user')
|
||||
return res.json(publishers)
|
||||
} catch (e) {
|
||||
return (new HttpError(500, e.message)).send(res)
|
||||
}
|
||||
},
|
||||
editUser: async function (req, res) {
|
||||
try {
|
||||
const id = req.param('id')
|
||||
const patchData = req.param('patch')
|
||||
const updated = await User.updateOne({ id }).set({
|
||||
...patchData
|
||||
})
|
||||
for (const key in updated) {
|
||||
if (patchData[key] === undefined && key !== 'id') delete updated[key]
|
||||
}
|
||||
return res.json(updated)
|
||||
} catch (e) {
|
||||
return (new HttpError(500, e.message)).send(res)
|
||||
}
|
||||
},
|
||||
editPublisher: async function (req, res) {
|
||||
try {
|
||||
const id = req.param('id')
|
||||
const patchData = req.param('patch')
|
||||
const updated = await PublishKey.updateOne({ id }).set({
|
||||
...patchData
|
||||
})
|
||||
for (const key in updated) {
|
||||
if (patchData[key] === undefined && key !== 'id') delete updated[key]
|
||||
}
|
||||
return res.json(updated)
|
||||
} catch (e) {
|
||||
return (new HttpError(500, e.message)).send(res)
|
||||
}
|
||||
},
|
||||
deleteUser: async function (req, res) {
|
||||
try {
|
||||
const id = req.param('id')
|
||||
await User.destroyOne({ id })
|
||||
return res.status(204).send()
|
||||
} catch (e) {
|
||||
return (new HttpError(500, e.message)).send(res)
|
||||
}
|
||||
},
|
||||
deletePublisher: async function (req, res) {
|
||||
try {
|
||||
const id = req.param('id')
|
||||
await PublishKey.destroyOne({ id })
|
||||
return res.status(204).send()
|
||||
} catch (e) {
|
||||
return (new HttpError(500, e.message)).send(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@ const HttpError = require('../errors/HttpError')
|
|||
module.exports = {
|
||||
create: async function (req, res) {
|
||||
try {
|
||||
const url = req.param('url')
|
||||
if (!url.length) throw new Error('Name cannot be blank')
|
||||
const name = req.param('name')
|
||||
if (!name.length) throw new Error('Name cannot be blank')
|
||||
|
||||
const created = await PublishKey.create({
|
||||
user: req.user.id,
|
||||
url
|
||||
name
|
||||
}).fetch()
|
||||
return res.json(created)
|
||||
} catch (e) {
|
||||
|
|
|
@ -20,11 +20,14 @@ module.exports = {
|
|||
model: 'User',
|
||||
required: true
|
||||
},
|
||||
url: {
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
url: 'string',
|
||||
whitelisted: 'boolean',
|
||||
verified: 'boolean',
|
||||
verification_key: 'string',
|
||||
appid: {
|
||||
type: 'string'
|
||||
},
|
||||
|
@ -35,6 +38,7 @@ module.exports = {
|
|||
beforeCreate: async function (key, next) {
|
||||
key.appid = await generateToken({ bytes: 12 })
|
||||
key.secret = await generateToken({ bytes: 48 })
|
||||
key.verification_key = await generateToken({ bytes: 24 })
|
||||
next()
|
||||
},
|
||||
beforeUpdate: async function (key, next) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
module.exports = async function (req, res, next) {
|
||||
if (process.env.NODE_ENV === 'development') return next()
|
||||
if (req.user && (req.user.id === 1 || req.user.admin)) next()
|
||||
else res.status(403).json({ error: 'You are not permitted to perform this action.' })
|
||||
}
|
||||
|
|
|
@ -6,9 +6,8 @@
|
|||
* @docs :: http://sailsjs.org/#!documentation/policies
|
||||
*/
|
||||
module.exports = function (req, res, next) {
|
||||
if (req.session.authenticated) {
|
||||
return next()
|
||||
}
|
||||
res.status(403).json({ error: 'You are not permitted to perform this action.' })
|
||||
// res.redirect('/login')
|
||||
if (process.env.NODE_ENV === 'development') return next()
|
||||
if (req.session.authenticated) return next()
|
||||
// res.status(403).json({ error: 'You are not permitted to perform this action.' })
|
||||
res.redirect('/login')
|
||||
}
|
||||
|
|
|
@ -7,7 +7,9 @@ const getPath = str => window.location.hostname === 'localhost' ? `http://localh
|
|||
const ACTIONS = {
|
||||
set_working: 'set_working',
|
||||
error: 'error',
|
||||
set_admin_data: 'set_admin_data'
|
||||
set_admin_data: 'set_admin_data',
|
||||
a_update_user: 'a_update_user',
|
||||
a_update_publisher: 'a_update_publisher'
|
||||
}
|
||||
|
||||
export default ACTIONS
|
||||
|
@ -49,3 +51,51 @@ export const fetchAdminData = () => async (dispatch, getState) => {
|
|||
dispatch(setWorking(false))
|
||||
}
|
||||
}
|
||||
|
||||
export const patchUser = ({ id, ...data }) => async (dispatch, getState) => {
|
||||
dispatch(setWorking(true))
|
||||
try {
|
||||
const { data: user } = await Ajax.patch({
|
||||
url: getPath('/admin/api/users/' + id),
|
||||
data: {
|
||||
patch: data
|
||||
},
|
||||
noProcess: true
|
||||
})
|
||||
dispatch({
|
||||
type: ACTIONS.a_update_user,
|
||||
data: user
|
||||
})
|
||||
} catch (e) {
|
||||
dispatch({
|
||||
type: ACTIONS.error,
|
||||
data: e
|
||||
})
|
||||
} finally {
|
||||
dispatch(setWorking(false))
|
||||
}
|
||||
}
|
||||
|
||||
export const patchPublisher = ({ id, ...data }) => async (dispatch, getState) => {
|
||||
dispatch(setWorking(true))
|
||||
try {
|
||||
const { data: publisher } = await Ajax.patch({
|
||||
url: getPath('/admin/api/publishers/' + id),
|
||||
data: {
|
||||
patch: data
|
||||
},
|
||||
noProcess: true
|
||||
})
|
||||
dispatch({
|
||||
type: ACTIONS.a_update_publisher,
|
||||
data: publisher
|
||||
})
|
||||
} catch (e) {
|
||||
dispatch({
|
||||
type: ACTIONS.error,
|
||||
data: e
|
||||
})
|
||||
} finally {
|
||||
dispatch(setWorking(false))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -175,13 +175,13 @@ export const editUser = (user) => async (dispatch, getState) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const createNewPublisher = (url) => async (dispatch, getState) => {
|
||||
export const createNewPublisher = (name) => async (dispatch, getState) => {
|
||||
dispatch(setWorking(true))
|
||||
try {
|
||||
const { data } = await Ajax.post({
|
||||
url: getPath('/api/keys'),
|
||||
data: {
|
||||
url
|
||||
name
|
||||
}
|
||||
})
|
||||
dispatch(addPublisher(data))
|
||||
|
|
|
@ -49,7 +49,7 @@ export const clearError = () => ({
|
|||
|
||||
export const setLoggedIn = (data) => (dispatch, getState) => {
|
||||
window.localStorage.setItem('roe-token', JSON.stringify(data))
|
||||
window.location.href = '/targets'
|
||||
window.location.href = '/keys'
|
||||
}
|
||||
|
||||
export const checkEmail = email => async (dispatch, getState) => {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { BrowserRouter as Router, Route, NavLink, Switch, Redirect } from 'react
|
|||
import Progress from './components/Progress'
|
||||
import appReducer from './reducers'
|
||||
import adminReducer from './reducers/admin'
|
||||
import { fetchAdminData } from './actions/admin'
|
||||
import { fetchAdminData, patchUser, patchPublisher } from './actions/admin'
|
||||
import Util from './lib/Util'
|
||||
|
||||
import '../styles/admin.scss'
|
||||
|
@ -55,7 +55,7 @@ class App extends React.Component {
|
|||
<span className='flex'>{user.email}</span>
|
||||
<span className='flex'>
|
||||
<label for={`is-admin-${user.id}`} className='cb-label'>Admin?</label>
|
||||
<input className='checkbox' type='checkbox' defaultChecked={user.admin} id={`is-admin-${user.id}`} />
|
||||
<input className='checkbox' type='checkbox' checked={user.admin} onChange={() => this.dispatch(patchUser({ id: user.id, admin: !user.admin }))} id={`is-admin-${user.id}`} />
|
||||
<label for={`is-admin-${user.id}`} />
|
||||
</span>
|
||||
<div className='stack flex flex-container flex-vertical'>
|
||||
|
@ -71,12 +71,12 @@ class App extends React.Component {
|
|||
return (
|
||||
<li className='uri-list-item flex-container' key={`is-whitelisted-${pub.id}`}>
|
||||
<div className='stack flex flex-container flex-vertical'>
|
||||
<span className='flex'><span className='name'>{pub.url}</span><span className='appid'>{pub.appid}</span></span>
|
||||
<span className='flex'><span className='name'>{pub.name}</span><span className='appid'>{pub.appid}</span></span>
|
||||
<span className='flex'>{pub.user.email}</span>
|
||||
</div>
|
||||
<span className='flex'>
|
||||
<label for={`is-whitelisted-${pub.id}`} className='cb-label'>Whitelisted?</label>
|
||||
<input className='checkbox' type='checkbox' defaultChecked={pub.whitelisted} id={`is-whitelisted-${pub.id}`} />
|
||||
<input className='checkbox' type='checkbox' checked={pub.whitelisted} onChange={() => this.dispatch(patchPublisher({ id: pub.id, whitelisted: !pub.whitelisted }))} id={`is-whitelisted-${pub.id}`} />
|
||||
<label for={`is-whitelisted-${pub.id}`} />
|
||||
</span>
|
||||
<div className='stack flex flex-container flex-vertical'>
|
||||
|
@ -102,7 +102,7 @@ class App extends React.Component {
|
|||
<ul>
|
||||
<li><NavLink to='/users'>Users</NavLink></li>
|
||||
<li><NavLink to='/publishers'>Publishers</NavLink></li>
|
||||
<li><a href='/targets'>Exit admin</a></li>
|
||||
<li><a href='/keys'>Exit admin</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
<section className={'content flex' + (this.state.working ? ' working' : '')}>
|
||||
|
|
|
@ -40,7 +40,7 @@ const CarouselItem = props => (
|
|||
{props.inputs}
|
||||
<span className='carousel-error'>{props.error}</span>
|
||||
<div className='button-row'>
|
||||
<a href='#' onClick={props.onSmallButtonClick}>{props.smallButton}</a>
|
||||
<a href='#' onClick={e => handleClick(e, props.onSmallButtonClick)}>{props.smallButton}</a>
|
||||
<button className='btn btn-primary' type='submit' >
|
||||
{props.button}
|
||||
</button>
|
||||
|
|
|
@ -21,7 +21,7 @@ class PublisherListItem extends React.Component {
|
|||
<li className='uri-list-item publisher-list-item flex-container'>
|
||||
<div className='stack flex site-name flex-container flex-vertical'>
|
||||
<span className='label'>Website name</span>
|
||||
<span className='value'>{this.props.item.url}</span>
|
||||
<span className='value'>{this.props.item.name}</span>
|
||||
</div>
|
||||
<div className='flex flex-container'>
|
||||
<div className='stack flex-container flex-vertical'>
|
||||
|
|
|
@ -59,12 +59,12 @@ export default class Ajax {
|
|||
|
||||
var fd = null
|
||||
var qs = ''
|
||||
if (opts.data && opts.method.toLowerCase() !== 'get') {
|
||||
if (!opts.noProcess && opts.data && opts.method.toLowerCase() !== 'get') {
|
||||
fd = new FormData()
|
||||
for (let key in opts.data) {
|
||||
fd.append(key, opts.data[key])
|
||||
}
|
||||
} else if (opts.data) {
|
||||
} else if (!opts.noProcess && opts.data) {
|
||||
qs += '?'
|
||||
let params = []
|
||||
for (let key in opts.data) {
|
||||
|
@ -73,6 +73,14 @@ export default class Ajax {
|
|||
qs += params.join('&')
|
||||
}
|
||||
|
||||
if (opts.noProcess) {
|
||||
opts.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...opts.headers
|
||||
}
|
||||
try { fd = JSON.stringify(opts.data) } catch (e) { console.warn(e) }
|
||||
}
|
||||
|
||||
xhr.onload = () => {
|
||||
if (!('' + xhr.status).startsWith('2')) { return xhr.onerror() }
|
||||
var data = xhr.response
|
||||
|
|
|
@ -4,6 +4,7 @@ import Actions from '../actions/admin'
|
|||
|
||||
const reducer = (state = {}, action) => {
|
||||
const { type, data } = action
|
||||
let ind
|
||||
switch (type) {
|
||||
case Actions.set_working:
|
||||
return {
|
||||
|
@ -15,6 +16,20 @@ const reducer = (state = {}, action) => {
|
|||
users: data.users,
|
||||
publishers: data.publishers
|
||||
}
|
||||
case Actions.a_update_user:
|
||||
const modifiedUsers = [ ...state.users ]
|
||||
ind = modifiedUsers.findIndex(x => x.id === data.id)
|
||||
modifiedUsers[ind] = { ...modifiedUsers[ind], ...data }
|
||||
return {
|
||||
users: modifiedUsers
|
||||
}
|
||||
case Actions.a_update_publisher:
|
||||
const modifiedPublishers = [ ...state.publishers ]
|
||||
ind = modifiedPublishers.findIndex(x => x.id === data.id)
|
||||
modifiedPublishers[ind] = { ...modifiedPublishers[ind], ...data }
|
||||
return {
|
||||
publishers: modifiedPublishers
|
||||
}
|
||||
default: return {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
position: relative;
|
||||
|
||||
.error-box {
|
||||
height: 30px;
|
||||
min-height: 30px;
|
||||
line-height: 30px;
|
||||
background: $red;
|
||||
color: white;
|
||||
|
|
|
@ -45,6 +45,6 @@ module.exports.policies = {
|
|||
},
|
||||
|
||||
AdminController: {
|
||||
'*': [ 'adminAuth' ]
|
||||
'*': [ 'sessionAuth', 'adminAuth' ]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,11 @@ module.exports.routes = {
|
|||
'DELETE /api/keys/:id': 'PublishKeyController.delete',
|
||||
|
||||
'GET /admin/api/users': 'AdminController.listUsers',
|
||||
'GET /admin/api/publishers': 'AdminController.listPublishers'
|
||||
'GET /admin/api/publishers': 'AdminController.listPublishers',
|
||||
'PATCH /admin/api/users/:id': 'AdminController.editUser',
|
||||
'PATCH /admin/api/publishers/:id': 'AdminController.editPublisher',
|
||||
'DELETE /admin/api/users/:id': 'AdminController.deleteUser',
|
||||
'DELETE /admin/api/publishers/:id': 'AdminController.deletePublisher'
|
||||
|
||||
// ╦ ╦╔═╗╔╗ ╦ ╦╔═╗╔═╗╦╔═╔═╗
|
||||
// ║║║║╣ ╠╩╗╠═╣║ ║║ ║╠╩╗╚═╗
|
||||
|
|
|
@ -6,9 +6,13 @@ exports.up = function (knex, Promise) {
|
|||
knex.schema.createTable('publishkey', t => {
|
||||
t.increments('id').primary()
|
||||
t.integer('user').notNullable().references('user.id').onDelete('CASCADE').onUpdate('CASCADE')
|
||||
t.string('name')
|
||||
t.string('url')
|
||||
t.string('appid')
|
||||
t.string('secret')
|
||||
t.boolean('whitelisted')
|
||||
t.string('verification_key')
|
||||
t.boolean('whitelisted').defaultTo(false)
|
||||
t.boolean('verified').defaultTo(false)
|
||||
t.integer('created_at')
|
||||
t.integer('updated_at')
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue