Merge pull request #30 from EbookFoundation/add-push-address-page
Add push address pagepull/24/head
commit
666cbccea0
|
@ -39,6 +39,7 @@ module.exports = {
|
||||||
throw new HttpError(500, err.message)
|
throw new HttpError(500, err.message)
|
||||||
}
|
}
|
||||||
await Book.update({ id: result.id }, { storage: uploaded[0].fd })
|
await Book.update({ id: result.id }, { storage: uploaded[0].fd })
|
||||||
|
sendUpdatesAsync(result.id)
|
||||||
return res.json({
|
return res.json({
|
||||||
...result
|
...result
|
||||||
})
|
})
|
||||||
|
@ -70,3 +71,11 @@ module.exports = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sendUpdatesAsync (id) {
|
||||||
|
const book = await Book.find({ id })
|
||||||
|
const targets = await TargetUrl.find()
|
||||||
|
for (const i in targets) {
|
||||||
|
sails.log('sending ' + book.id + ' info to ' + targets[i].url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,55 @@
|
||||||
|
'use strict'
|
||||||
|
const HttpError = require('../errors/HttpError')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
show: function (req, res) {
|
show: function (req, res) {
|
||||||
res.view('pages/temp', {
|
res.view('pages/targets', {
|
||||||
email: req.user.email
|
email: req.user.email
|
||||||
|
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
create: async function (req, res) {
|
||||||
|
try {
|
||||||
|
const url = await TargetUrl.create({
|
||||||
|
user: req.user.id
|
||||||
|
}).fetch()
|
||||||
|
return res.json(url)
|
||||||
|
} catch (e) {
|
||||||
|
return (new HttpError(500, e.message)).send(res)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
edit: async function (req, res) {
|
||||||
|
try {
|
||||||
|
const id = req.param('id')
|
||||||
|
const value = req.param('url')
|
||||||
|
if (value.length) {
|
||||||
|
const url = await TargetUrl.update({ id, user: req.user.id }, { url: value }).fetch()
|
||||||
|
return res.json(url)
|
||||||
|
} else {
|
||||||
|
await TargetUrl.destroyOne({ id })
|
||||||
|
return res.status(204).send()
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return (new HttpError(500, e.message)).send(res)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delete: async function (req, res) {
|
||||||
|
try {
|
||||||
|
const id = +req.param('id')
|
||||||
|
await TargetUrl.destroyOne({ id })
|
||||||
|
return res.status(204).send()
|
||||||
|
} catch (e) {
|
||||||
|
return (new HttpError(500, e.message)).send(res)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
list: async function (req, res) {
|
||||||
|
try {
|
||||||
|
const urls = await TargetUrl.find({
|
||||||
|
user: req.user.id
|
||||||
|
})
|
||||||
|
return res.json(urls)
|
||||||
|
} catch (e) {
|
||||||
|
return (new HttpError(500, e.message)).send(res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
module.exports = {
|
||||||
|
attributes: {
|
||||||
|
id: {
|
||||||
|
type: 'number',
|
||||||
|
unique: true,
|
||||||
|
autoIncrement: true
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
model: 'User',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: 'string'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,6 @@ module.exports = function (req, res, next) {
|
||||||
if (req.session.authenticated) {
|
if (req.session.authenticated) {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
res.status(403).json({ error: 'You are not permitted to perform this action.' })
|
// res.status(403).json({ error: 'You are not permitted to perform this action.' })
|
||||||
// res.redirect('/login')
|
res.redirect('/login')
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
import Ajax from '../lib/Ajax'
|
||||||
|
|
||||||
|
const ACTIONS = {
|
||||||
|
set_working: 'set_working',
|
||||||
|
add_url: 'add_url',
|
||||||
|
edit_url: 'edit_url',
|
||||||
|
delete_url: 'delete_url',
|
||||||
|
list_url: 'list_url',
|
||||||
|
error: 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ACTIONS
|
||||||
|
|
||||||
|
export const setWorking = working => ({
|
||||||
|
type: ACTIONS.set_working,
|
||||||
|
data: working
|
||||||
|
})
|
||||||
|
|
||||||
|
export const setUrls = (urls) => ({
|
||||||
|
type: ACTIONS.list_url,
|
||||||
|
data: urls
|
||||||
|
})
|
||||||
|
|
||||||
|
export const addUrl = url => ({
|
||||||
|
type: ACTIONS.add_url,
|
||||||
|
data: url
|
||||||
|
})
|
||||||
|
|
||||||
|
export const changeUrlField = (id, value) => ({
|
||||||
|
type: ACTIONS.edit_url,
|
||||||
|
data: {
|
||||||
|
id,
|
||||||
|
value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const removeUrl = id => async (dispatch, getState) => {
|
||||||
|
dispatch(setWorking(true))
|
||||||
|
try {
|
||||||
|
await Ajax.delete({
|
||||||
|
url: '/api/targets/' + id
|
||||||
|
})
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.delete_url,
|
||||||
|
data: id
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.error,
|
||||||
|
data: e
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
dispatch(setWorking(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchUrls = () => async (dispatch, getState) => {
|
||||||
|
dispatch(setWorking(true))
|
||||||
|
try {
|
||||||
|
const { data } = await Ajax.get({
|
||||||
|
url: '/api/targets'
|
||||||
|
})
|
||||||
|
dispatch(setUrls(data))
|
||||||
|
} catch (e) {
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.error,
|
||||||
|
data: e
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
dispatch(setWorking(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createNewUrl = () => async (dispatch, getState) => {
|
||||||
|
dispatch(setWorking(true))
|
||||||
|
try {
|
||||||
|
const { data } = await Ajax.post({
|
||||||
|
url: '/api/targets'
|
||||||
|
})
|
||||||
|
dispatch(addUrl(data))
|
||||||
|
} catch (e) {
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.error,
|
||||||
|
data: e
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
dispatch(setWorking(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setUrl = (id, value) => async (dispatch, getState) => {
|
||||||
|
dispatch(setWorking(true))
|
||||||
|
try {
|
||||||
|
await Ajax.patch({
|
||||||
|
url: '/api/targets/' + id,
|
||||||
|
data: {
|
||||||
|
url: value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.error,
|
||||||
|
data: e
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
dispatch(setWorking(false))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import '../../styles/shared/iconbutton.scss'
|
||||||
|
|
||||||
|
function getSVG (icon) {
|
||||||
|
switch (icon) {
|
||||||
|
case 'delete': return '<svg viewBox="0 0 24 24"><path d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" /></svg>'
|
||||||
|
default: return icon || 'missing icon prop'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const IconButton = props => {
|
||||||
|
return (
|
||||||
|
<button className='button icon' onClick={props.onClick} dangerouslySetInnerHTML={{ __html: getSVG(props.icon) }} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IconButton
|
|
@ -5,14 +5,17 @@ import React from 'react'
|
||||||
import '../../styles/shared/underlineinput.scss'
|
import '../../styles/shared/underlineinput.scss'
|
||||||
|
|
||||||
const UnderlineInput = props => (
|
const UnderlineInput = props => (
|
||||||
<div className='underlined-input'>
|
<div className={'underlined-input ' + (props.className ? props.className : '')}>
|
||||||
<input
|
<input
|
||||||
type={props.type}
|
type={props.type}
|
||||||
name={props.name}
|
name={props.name}
|
||||||
value={props.value}
|
value={props.value}
|
||||||
className={(props.value.length ? 'has-content' : '')}
|
className={(props.value.length ? 'has-content' : '') + (props.pattern
|
||||||
|
? (props.value.length && !props.pattern.test(props.value) ? ' invalid' : '')
|
||||||
|
: '')}
|
||||||
autoComplete='nothing'
|
autoComplete='nothing'
|
||||||
onChange={props.onChange} />
|
onChange={props.onChange}
|
||||||
|
onBlur={props.onBlur} />
|
||||||
<div className='reacts-to'>
|
<div className='reacts-to'>
|
||||||
<label className='placeholder'>{props.placeholder}</label>
|
<label className='placeholder'>{props.placeholder}</label>
|
||||||
<div className='underline' />
|
<div className='underline' />
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import IconButton from '../components/IconButton'
|
||||||
|
import UnderlineInput from '../components/UnderlineInput'
|
||||||
|
import '../../styles/shared/urilistitem.scss'
|
||||||
|
import { changeUrlField, setUrl, removeUrl } from '../actions/targets'
|
||||||
|
|
||||||
|
const uriRegex = /(.+:\/\/)?(.+\.)*(.+\.).{1,}(:\d+)?(.+)?/i
|
||||||
|
|
||||||
|
class UriListItem extends React.Component {
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<li className='uri-list-item flex-container'>
|
||||||
|
<UnderlineInput
|
||||||
|
className='uri flex'
|
||||||
|
type='text'
|
||||||
|
name={'url-' + this.props.id}
|
||||||
|
placeholder='Destination URL'
|
||||||
|
value={'' + this.props.url}
|
||||||
|
pattern={uriRegex}
|
||||||
|
onChange={(e) => this.props.dispatch(changeUrlField(this.props.id, e.target.value))}
|
||||||
|
onBlur={(e) => this.props.dispatch(setUrl(this.props.id, e.target.value))} />
|
||||||
|
<IconButton icon='delete' onClick={() => this.props.dispatch(removeUrl(this.props.id))} />
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UriListItem
|
|
@ -81,8 +81,11 @@ export default class Ajax {
|
||||||
}
|
}
|
||||||
|
|
||||||
xhr.onload = () => {
|
xhr.onload = () => {
|
||||||
if (xhr.status !== 200) { return xhr.onerror() }
|
if (!('' + xhr.status).startsWith('2')) { return xhr.onerror() }
|
||||||
var data = xhr.response
|
var data = xhr.response
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data)
|
||||||
|
} catch (e) {}
|
||||||
resolve({
|
resolve({
|
||||||
data,
|
data,
|
||||||
xhr
|
xhr
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
import Actions from '../actions/targets'
|
||||||
|
|
||||||
|
const reducer = (state = {}, action) => {
|
||||||
|
const { type, data } = action
|
||||||
|
let urls
|
||||||
|
switch (type) {
|
||||||
|
case Actions.set_working:
|
||||||
|
return {
|
||||||
|
working: data
|
||||||
|
}
|
||||||
|
case Actions.list_url:
|
||||||
|
return {
|
||||||
|
urls: data || []
|
||||||
|
}
|
||||||
|
case Actions.add_url:
|
||||||
|
return {
|
||||||
|
urls: state.urls.concat(data),
|
||||||
|
error: ''
|
||||||
|
}
|
||||||
|
case Actions.delete_url:
|
||||||
|
return {
|
||||||
|
urls: state.urls.filter(x => x.id !== data),
|
||||||
|
error: ''
|
||||||
|
}
|
||||||
|
case Actions.edit_url:
|
||||||
|
urls = state.urls
|
||||||
|
urls.find(x => x.id === data.id).url = data.value
|
||||||
|
return {
|
||||||
|
urls: urls
|
||||||
|
}
|
||||||
|
case Actions.error:
|
||||||
|
return {
|
||||||
|
error: data.message
|
||||||
|
}
|
||||||
|
default: return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default reducer
|
|
@ -0,0 +1,81 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import Progress from './components/Progress'
|
||||||
|
import UriListItem from './containers/UriListItem'
|
||||||
|
import reducer from './reducers/targets'
|
||||||
|
import { fetchUrls, createNewUrl } from './actions/targets'
|
||||||
|
import '../styles/targets.scss'
|
||||||
|
|
||||||
|
class App extends React.Component {
|
||||||
|
constructor () {
|
||||||
|
super()
|
||||||
|
this.state = {
|
||||||
|
error: '',
|
||||||
|
user: {
|
||||||
|
email: '',
|
||||||
|
password: ''
|
||||||
|
},
|
||||||
|
urls: [{
|
||||||
|
id: 1,
|
||||||
|
url: 'http'
|
||||||
|
}],
|
||||||
|
working: false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dispatch = this.dispatch.bind(this)
|
||||||
|
this.getRegisteredUris = this.getRegisteredUris.bind(this)
|
||||||
|
}
|
||||||
|
dispatch (action) {
|
||||||
|
if (!action) throw new Error('dispatch: missing action')
|
||||||
|
if (action instanceof Function) {
|
||||||
|
action(this.dispatch, () => this.state)
|
||||||
|
} else {
|
||||||
|
const changes = reducer(this.state, action)
|
||||||
|
if (!changes || !Object.keys(changes).length) return
|
||||||
|
this.setState({
|
||||||
|
...changes
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentDidMount () {
|
||||||
|
this.dispatch(fetchUrls())
|
||||||
|
}
|
||||||
|
getRegisteredUris () {
|
||||||
|
return this.state.urls.map((item, i) => {
|
||||||
|
return (<UriListItem
|
||||||
|
key={i}
|
||||||
|
dispatch={this.dispatch}
|
||||||
|
id={item.id}
|
||||||
|
url={item.url} />)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div className='root-container flex-container'>
|
||||||
|
<aside className='nav nav-left'>
|
||||||
|
<header>
|
||||||
|
<h1>RoE</h1>
|
||||||
|
</header>
|
||||||
|
</aside>
|
||||||
|
<section className={'content flex' + (this.state.working ? ' working' : '')}>
|
||||||
|
<Progress bound />
|
||||||
|
{this.state.error && <div className='error-box'>{this.state.error}</div>}
|
||||||
|
<header className='flex-container'>
|
||||||
|
<div className='flex'>
|
||||||
|
<h1>Push URIs</h1>
|
||||||
|
<h2>Newly published books will be sent to these addresses.</h2>
|
||||||
|
</div>
|
||||||
|
<button className='btn' onClick={() => this.dispatch(createNewUrl())}>New address</button>
|
||||||
|
</header>
|
||||||
|
<ul className='list'>
|
||||||
|
{this.getRegisteredUris()}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(<App />, document.getElementById('root'))
|
|
@ -16,6 +16,12 @@ body,
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-container {
|
.flex-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ $background-2: white;
|
||||||
|
|
||||||
$text-dark-1: $black-1;
|
$text-dark-1: $black-1;
|
||||||
$text-dark-2: $black-2;
|
$text-dark-2: $black-2;
|
||||||
|
$text-light-1: white;
|
||||||
|
$text-light-2: rgba(255,255,255,.75);
|
||||||
|
|
||||||
$accent-1: #731212;
|
$accent-1: #731212;
|
||||||
$accent-2: #9a834d;
|
$accent-2: #9a834d;
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
@import '../lib/vars';
|
||||||
|
|
||||||
|
.button.icon {
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
width: 40px;
|
||||||
|
padding: 5px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: background 0.2s $transition,
|
||||||
|
box-shadow 0.2s $transition;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $black-4;
|
||||||
|
box-shadow: $shadow-1;
|
||||||
|
}
|
||||||
|
path {
|
||||||
|
fill: $black-2;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
.nav-left {
|
||||||
|
min-width: 300px;
|
||||||
|
height: 100%;
|
||||||
|
background: $accent-1;
|
||||||
|
color: $text-light-1;
|
||||||
|
box-shadow: $shadow-1;
|
||||||
|
}
|
|
@ -71,6 +71,9 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.invalid {
|
||||||
|
color: $red;
|
||||||
|
}
|
||||||
&.invalid:focus + .reacts-to,
|
&.invalid:focus + .reacts-to,
|
||||||
&.invalid:active + .reacts-to,
|
&.invalid:active + .reacts-to,
|
||||||
&.invalid.has-content + .reacts-to {
|
&.invalid.has-content + .reacts-to {
|
||||||
|
@ -79,7 +82,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.invalid + .reacts-to {
|
&.invalid + .reacts-to {
|
||||||
.underline {
|
.underline:before {
|
||||||
background: $red;
|
background: $red;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
@import '../lib/vars';
|
||||||
|
|
||||||
|
.uri-list-item {
|
||||||
|
height: 60px;
|
||||||
|
line-height: 60px;
|
||||||
|
background: $background-1;
|
||||||
|
box-shadow: $shadow-0;
|
||||||
|
|
||||||
|
& > .button.icon {
|
||||||
|
margin: 7px 5px 3px 5px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
@import 'lib/default';
|
||||||
|
@import 'shared/twopanels';
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
header {
|
||||||
|
height: 50px;
|
||||||
|
line-height: 50px;
|
||||||
|
padding: 0 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 14px 0 42px 0;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.error-box {
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
background: $red;
|
||||||
|
color: white;
|
||||||
|
padding: 0 14px;
|
||||||
|
margin: -14px 0 8px 0;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
padding: 0 14px;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-shadow: 1px 1px 2px $black-3;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 4px;
|
||||||
|
color: $text-dark-2;
|
||||||
|
text-shadow: 1px 1px 2px $black-4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
margin: 14px;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
&.working {
|
||||||
|
& > .progress {
|
||||||
|
top: 0;
|
||||||
|
height: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
<% var key, item %>
|
||||||
|
<% htmlWebpackPlugin.options.links = htmlWebpackPlugin.options.links || [] %>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>RoE - Push Targets</title>
|
||||||
|
<% for (item of htmlWebpackPlugin.options.links) {
|
||||||
|
if (typeof item === 'string' || item instanceof String) { item = { href: item, rel: 'stylesheet' } } %>
|
||||||
|
<link<% for (key in item) { %> <%= key %>="<%= item[key] %>"<% } %> /><%
|
||||||
|
} %>
|
||||||
|
<meta name="viewport" content="initial-scale=1, width=device-width, maximum-scale=1, minimum-scale=1, user-scalable=no" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -66,7 +66,12 @@ module.exports.routes = {
|
||||||
'POST /api/publish': 'BooksController.publish',
|
'POST /api/publish': 'BooksController.publish',
|
||||||
|
|
||||||
'GET /api/books': 'BooksController.list',
|
'GET /api/books': 'BooksController.list',
|
||||||
'GET /api/me': 'UserController.me'
|
'GET /api/me': 'UserController.me',
|
||||||
|
|
||||||
|
'POST /api/targets': 'TargetController.create',
|
||||||
|
'PATCH /api/targets/:id': 'TargetController.edit',
|
||||||
|
'DELETE /api/targets/:id': 'TargetController.delete',
|
||||||
|
'GET /api/targets': 'TargetController.list'
|
||||||
|
|
||||||
// ╦ ╦╔═╗╔╗ ╦ ╦╔═╗╔═╗╦╔═╔═╗
|
// ╦ ╦╔═╗╔╗ ╦ ╦╔═╗╔═╗╦╔═╔═╗
|
||||||
// ║║║║╣ ╠╩╗╠═╣║ ║║ ║╠╩╗╚═╗
|
// ║║║║╣ ╠╩╗╠═╣║ ║║ ║╠╩╗╚═╗
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
"User",
|
"User",
|
||||||
"Book",
|
"Book",
|
||||||
"Passport",
|
"Passport",
|
||||||
|
"TargetUrl",
|
||||||
"_"
|
"_"
|
||||||
],
|
],
|
||||||
"parser": "babel-eslint"
|
"parser": "babel-eslint"
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<%- partial('../../.tmp/public/targets.html') %>
|
|
@ -1,2 +0,0 @@
|
||||||
authed: <%- email %><br />
|
|
||||||
<a href="/logout">Logout</a>
|
|
|
@ -9,7 +9,8 @@ module.exports = (env, argv) => {
|
||||||
return {
|
return {
|
||||||
mode: mode || 'development',
|
mode: mode || 'development',
|
||||||
entry: {
|
entry: {
|
||||||
login: './assets/js/login.js'
|
login: './assets/js/login.js',
|
||||||
|
targets: './assets/js/targets.js'
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.join(__dirname, '/.tmp/public'),
|
path: path.join(__dirname, '/.tmp/public'),
|
||||||
|
@ -36,7 +37,14 @@ module.exports = (env, argv) => {
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: 'assets/templates/login.html',
|
template: 'assets/templates/login.html',
|
||||||
links: mode === 'production' ? [{ rel: 'stylesheet', type: 'text/css', href: 'login.css' }] : [],
|
links: mode === 'production' ? [{ rel: 'stylesheet', type: 'text/css', href: 'login.css' }] : [],
|
||||||
filename: path.join(__dirname, '/.tmp/public/login.html')
|
filename: path.join(__dirname, '/.tmp/public/login.html'),
|
||||||
|
chunks: ['login']
|
||||||
|
}),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: 'assets/templates/targets.html',
|
||||||
|
links: mode === 'production' ? [{ rel: 'stylesheet', type: 'text/css', href: 'targets.css' }] : [],
|
||||||
|
filename: path.join(__dirname, '/.tmp/public/targets.html'),
|
||||||
|
chunks: ['targets']
|
||||||
}),
|
}),
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: '[name].css'
|
filename: '[name].css'
|
||||||
|
|
Loading…
Reference in New Issue