From 82f4ad31d436265c2f22fd649f497e620bc22b9e Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sat, 8 Sep 2018 22:20:19 +0300 Subject: [PATCH 1/9] Allow custom ballot generation without token, add tests for it --- config/database/dev-people.sql | 1 + config/siteselection/ballot-data.js | 24 +++++---- .../test/hugo-nominations.spec.js | 2 + integration-tests/test/siteselect.spec.js | 51 +++++++++++++++++++ server/lib/ballot.js | 42 ++++++++++----- 5 files changed, 95 insertions(+), 25 deletions(-) create mode 100644 integration-tests/test/siteselect.spec.js diff --git a/config/database/dev-people.sql b/config/database/dev-people.sql index 8bf2e2a..6b44d82 100644 --- a/config/database/dev-people.sql +++ b/config/database/dev-people.sql @@ -42,6 +42,7 @@ ALTER SEQUENCE member_number_seq RESTART WITH 42; CREATE FUNCTION reset_test_users() RETURNS void AS $$ BEGIN UPDATE keys SET key='key', expires=NULL WHERE email='admin@example.com'; + UPDATE keys SET key='key', expires=NULL WHERE email='site-select@example.com'; UPDATE keys SET key='key', expires='2017-08-13' WHERE email='expired@example.com'; END; $$ LANGUAGE plpgsql; diff --git a/config/siteselection/ballot-data.js b/config/siteselection/ballot-data.js index bd325ce..e793ae4 100644 --- a/config/siteselection/ballot-data.js +++ b/config/siteselection/ballot-data.js @@ -1,14 +1,16 @@ -function ballotData({ - member_number, - legal_name, - email, - city, - state, - country, - badge_name, - paper_pubs, +function ballotData( + { + member_number, + legal_name, + email, + city, + state, + country, + badge_name, + paper_pubs + }, token -}) { +) { const address = (paper_pubs && paper_pubs.address.split(/[\n\r]+/)) || [''] return { info: { @@ -24,7 +26,7 @@ function ballotData({ City: city || '', Country: paper_pubs ? paper_pubs.country : country || '', 'Membership number': member_number || '........', - 'Voting token': token, + 'Voting token': token || '', 'E-mail': email, 'State/Province/Prefecture': state || '', 'Badge name': badge_name || '', diff --git a/integration-tests/test/hugo-nominations.spec.js b/integration-tests/test/hugo-nominations.spec.js index 698c873..a428f8e 100644 --- a/integration-tests/test/hugo-nominations.spec.js +++ b/integration-tests/test/hugo-nominations.spec.js @@ -10,6 +10,8 @@ const host = 'localhost:4430' const admin = request.agent(`https://${host}`, { ca }) const nominator = request.agent(`https://${host}`, { ca }) +if (!config.modules.hugo) return + const randomString = () => (Math.random().toString(36) + '0000000').slice(2, 7) describe('Hugo nominations', () => { diff --git a/integration-tests/test/siteselect.spec.js b/integration-tests/test/siteselect.spec.js new file mode 100644 index 0000000..a787c34 --- /dev/null +++ b/integration-tests/test/siteselect.spec.js @@ -0,0 +1,51 @@ +const assert = require('assert') +const fs = require('fs') +const request = require('supertest') + +const ca = fs.readFileSync('../proxy/ssl/localhost.cert', 'utf8') +const host = 'localhost:4430' +const admin = request.agent(`https://${host}`, { ca }) +const member = request.agent(`https://${host}`, { ca }) + +describe('Site selection', () => { + let id = null + before(() => { + const email = 'member@example.com' + const key = 'key' + return member + .get('/api/login') + .query({ email, key }) + .expect('set-cookie', /w75/) + .expect(200, { status: 'success', email }) + .then(() => member.get('/api/user')) + .then(res => { + id = res.body.people[0].id + assert.equal(typeof id, 'number') + }) + }) + + before(() => { + const email = 'site-select@example.com' + const key = 'key' + return admin + .get('/api/login') + .query({ email, key }) + .expect('set-cookie', /w75/) + .expect(200, { status: 'success', email }) + .then(() => admin.get('/api/user')) + .then(res => { + assert.notEqual(res.body.roles.indexOf('siteselection'), -1) + }) + }) + + it('member: get own ballot', () => { + return member + .get(`/api/people/${id}/ballot`) + .expect(200) + .expect('Content-Type', 'application/pdf') + }) + + it("member: fail to get others' ballot", () => { + return member.get(`/api/people/${id - 1}/ballot`).expect(401) + }) +}) diff --git a/server/lib/ballot.js b/server/lib/ballot.js index a563f5c..c7695fb 100644 --- a/server/lib/ballot.js +++ b/server/lib/ballot.js @@ -1,4 +1,7 @@ const fetch = require('node-fetch') +const { AuthError, InputError } = require('@kansa/errors') + +// source is at /config/siteselection/ballot-data.js const ballotData = require('/ss-ballot-data') class Ballot { @@ -9,23 +12,34 @@ class Ballot { getBallot(req, res, next) { const id = parseInt(req.params.id) - this.db - .any( - ` - SELECT member_number, legal_name, email, city, state, country, badge_name, paper_pubs, m.data->>'token' as token - FROM People p JOIN Payments m ON (p.id = m.person_id) - WHERE p.id = $1 AND m.type = 'ss-token' AND m.data->>'token' IS NOT NULL`, - id - ) - .then(data => { - if (data.length === 0) throw { status: 404, message: 'Not found' } - return fetch('http://tuohi:3000/ss-ballot.pdf', { + if (isNaN(id) || id <= 0) + return next(new InputError('Invalid id parameter')) + const email = req.session.user && req.session.user.email + if (!email) return next(new AuthError()) + return this.db + .task(async t => { + const person = await t.oneOrNone( + `SELECT + member_number, legal_name, email, city, state, country, + badge_name, paper_pubs + FROM People WHERE id = $(id) and email = $(email)`, + { id, email } + ) + if (!person) throw new AuthError() + const token = await t.oneOrNone( + `SELECT data->>'token' AS token + FROM payments WHERE + person_id = $1 AND type = 'ss-token' AND data->>'token' IS NOT NULL + LIMIT 1`, + id, + r => r && r.token + ) + const data = ballotData(person, token) + const pdfRes = await fetch('http://tuohi:3000/ss-ballot.pdf', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(ballotData(data[0])) + body: JSON.stringify(data) }) - }) - .then(pdfRes => { res.setHeader('Content-Type', 'application/pdf') pdfRes.body.pipe(res) }) From 6e361066c0c99936b53aad6b8f69474060327856 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sat, 8 Sep 2018 22:30:47 +0300 Subject: [PATCH 2/9] Add tests for site-selection token & voter lists --- integration-tests/test/siteselect.spec.js | 42 +++++++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/integration-tests/test/siteselect.spec.js b/integration-tests/test/siteselect.spec.js index a787c34..ec235bd 100644 --- a/integration-tests/test/siteselect.spec.js +++ b/integration-tests/test/siteselect.spec.js @@ -38,14 +38,42 @@ describe('Site selection', () => { }) }) - it('member: get own ballot', () => { - return member + it('member: get own ballot', () => + member .get(`/api/people/${id}/ballot`) .expect(200) - .expect('Content-Type', 'application/pdf') - }) + .expect('Content-Type', 'application/pdf')) - it("member: fail to get others' ballot", () => { - return member.get(`/api/people/${id - 1}/ballot`).expect(401) - }) + it("member: fail to get others' ballot", () => + member.get(`/api/people/${id - 1}/ballot`).expect(401)) + + it('member: fail to list tokens', () => + member.get(`/api/siteselect/tokens.json`).expect(401)) + + it('member: fail to list voters', () => + member.get(`/api/siteselect/voters.json`).expect(401)) + + it('admin: list tokens as JSON', () => + admin + .get(`/api/siteselect/tokens.json`) + .expect(200) + .expect(res => assert(Array.isArray(res.body)))) + + it('admin: list tokens as CSV', () => + admin + .get(`/api/siteselect/tokens.csv`) + .expect(200) + .expect('Content-Type', /text\/csv/)) + + it('admin: list voters as JSON', () => + admin + .get(`/api/siteselect/voters.json`) + .expect(200) + .expect(res => assert(Array.isArray(res.body)))) + + it('admin: list voters as CSV', () => + admin + .get(`/api/siteselect/voters.csv`) + .expect(200) + .expect('Content-Type', /text\/csv/)) }) From 8207b56a8fc6cc8c6bd833b072bb2f1bfed4a9e7 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sat, 8 Sep 2018 22:47:21 +0300 Subject: [PATCH 3/9] siteselect: Allow admin to get member ballot --- integration-tests/test/siteselect.spec.js | 6 ++++++ server/lib/ballot.js | 13 ++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/integration-tests/test/siteselect.spec.js b/integration-tests/test/siteselect.spec.js index ec235bd..5769ac1 100644 --- a/integration-tests/test/siteselect.spec.js +++ b/integration-tests/test/siteselect.spec.js @@ -53,6 +53,12 @@ describe('Site selection', () => { it('member: fail to list voters', () => member.get(`/api/siteselect/voters.json`).expect(401)) + it('admin: get member ballot', () => + admin + .get(`/api/people/${id}/ballot`) + .expect(200) + .expect('Content-Type', 'application/pdf')) + it('admin: list tokens as JSON', () => admin .get(`/api/siteselect/tokens.json`) diff --git a/server/lib/ballot.js b/server/lib/ballot.js index c7695fb..c3899fa 100644 --- a/server/lib/ballot.js +++ b/server/lib/ballot.js @@ -14,17 +14,16 @@ class Ballot { const id = parseInt(req.params.id) if (isNaN(id) || id <= 0) return next(new InputError('Invalid id parameter')) - const email = req.session.user && req.session.user.email - if (!email) return next(new AuthError()) + const { user } = req.session + if (!user || !user.email) return next(new AuthError()) return this.db .task(async t => { - const person = await t.oneOrNone( - `SELECT + let pq = `SELECT member_number, legal_name, email, city, state, country, badge_name, paper_pubs - FROM People WHERE id = $(id) and email = $(email)`, - { id, email } - ) + FROM People WHERE id = $(id)` + if (!user.siteselection) pq += ` and email = $(email)` + const person = await t.oneOrNone(pq, { id, email: user.email }) if (!person) throw new AuthError() const token = await t.oneOrNone( `SELECT data->>'token' AS token From 94894ba79bfb8f7480202d097f2b7f4da7604d9d Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sat, 8 Sep 2018 23:10:11 +0300 Subject: [PATCH 4/9] Split siteselect into its own module --- config/kansa.yaml | 3 ++ integration-tests/test/siteselect.spec.js | 10 ++++-- .../siteselect/lib/admin.js | 31 +++---------------- {server => modules/siteselect}/lib/ballot.js | 3 +- modules/siteselect/lib/router.js | 23 ++++++++++++++ modules/siteselect/lib/token.js | 24 ++++++++++++++ modules/siteselect/package.json | 15 +++++++++ server/lib/people/router.js | 3 -- server/lib/router.js | 9 ------ server/lib/types/payment.js | 2 +- server/lib/types/token.js | 24 ++++++++++++++ 11 files changed, 103 insertions(+), 44 deletions(-) rename server/lib/siteselect.js => modules/siteselect/lib/admin.js (84%) rename {server => modules/siteselect}/lib/ballot.js (92%) create mode 100644 modules/siteselect/lib/router.js create mode 100644 modules/siteselect/lib/token.js create mode 100644 modules/siteselect/package.json create mode 100644 server/lib/types/token.js diff --git a/config/kansa.yaml b/config/kansa.yaml index 1554b9f..635b95b 100644 --- a/config/kansa.yaml +++ b/config/kansa.yaml @@ -54,6 +54,9 @@ modules: # Art show management raami: false + # Site selection token management + siteselect: true + # Invite generator for a Slack organisation slack: #org: worldcon75 diff --git a/integration-tests/test/siteselect.spec.js b/integration-tests/test/siteselect.spec.js index 5769ac1..078d5a2 100644 --- a/integration-tests/test/siteselect.spec.js +++ b/integration-tests/test/siteselect.spec.js @@ -1,12 +1,16 @@ const assert = require('assert') const fs = require('fs') const request = require('supertest') +const YAML = require('yaml').default +const config = YAML.parse(fs.readFileSync('../config/kansa.yaml', 'utf8')) const ca = fs.readFileSync('../proxy/ssl/localhost.cert', 'utf8') const host = 'localhost:4430' const admin = request.agent(`https://${host}`, { ca }) const member = request.agent(`https://${host}`, { ca }) +if (!config.modules.siteselect) return + describe('Site selection', () => { let id = null before(() => { @@ -40,12 +44,12 @@ describe('Site selection', () => { it('member: get own ballot', () => member - .get(`/api/people/${id}/ballot`) + .get(`/api/siteselect/${id}/ballot`) .expect(200) .expect('Content-Type', 'application/pdf')) it("member: fail to get others' ballot", () => - member.get(`/api/people/${id - 1}/ballot`).expect(401)) + member.get(`/api/siteselect/${id - 1}/ballot`).expect(401)) it('member: fail to list tokens', () => member.get(`/api/siteselect/tokens.json`).expect(401)) @@ -55,7 +59,7 @@ describe('Site selection', () => { it('admin: get member ballot', () => admin - .get(`/api/people/${id}/ballot`) + .get(`/api/siteselect/${id}/ballot`) .expect(200) .expect('Content-Type', 'application/pdf')) diff --git a/server/lib/siteselect.js b/modules/siteselect/lib/admin.js similarity index 84% rename from server/lib/siteselect.js rename to modules/siteselect/lib/admin.js index 73f681a..30a4d8c 100644 --- a/server/lib/siteselect.js +++ b/modules/siteselect/lib/admin.js @@ -1,28 +1,7 @@ -const randomstring = require('randomstring') const { InputError } = require('@kansa/common/errors') +const { parseToken } = require('./token') -class Siteselect { - static generateToken() { - return randomstring.generate({ - length: 6, - charset: 'ABCDEFHJKLMNPQRTUVWXY0123456789' - }) - } - - static parseToken(token) { - return ( - token && - token - .trim() - .toUpperCase() - .replace(/G/g, '6') - .replace(/I/g, '1') - .replace(/O/g, '0') - .replace(/S/g, '5') - .replace(/Z/g, '2') - ) - } - +class Admin { constructor(db) { this.db = db this.findToken = this.findToken.bind(this) @@ -33,7 +12,7 @@ class Siteselect { } findToken(req, res, next) { - const token = Siteselect.parseToken(req.params.token) + const token = parseToken(req.params.token) if (!token) return res.status(404).json({ error: 'not found' }) this.db .oneOrNone(`SELECT * FROM token_lookup WHERE token=$1`, token) @@ -85,7 +64,7 @@ class Siteselect { vote(req, res, next) { const { id } = req.params - const token = Siteselect.parseToken(req.body.token) + const token = parseToken(req.body.token) let { voter_name, voter_email } = req.body this.db .task(dbTask => @@ -133,4 +112,4 @@ class Siteselect { } } -module.exports = Siteselect +module.exports = Admin diff --git a/server/lib/ballot.js b/modules/siteselect/lib/ballot.js similarity index 92% rename from server/lib/ballot.js rename to modules/siteselect/lib/ballot.js index c3899fa..57f8a9c 100644 --- a/server/lib/ballot.js +++ b/modules/siteselect/lib/ballot.js @@ -1,5 +1,5 @@ const fetch = require('node-fetch') -const { AuthError, InputError } = require('@kansa/errors') +const { AuthError, InputError } = require('@kansa/common/errors') // source is at /config/siteselection/ballot-data.js const ballotData = require('/ss-ballot-data') @@ -15,7 +15,6 @@ class Ballot { if (isNaN(id) || id <= 0) return next(new InputError('Invalid id parameter')) const { user } = req.session - if (!user || !user.email) return next(new AuthError()) return this.db .task(async t => { let pq = `SELECT diff --git a/modules/siteselect/lib/router.js b/modules/siteselect/lib/router.js new file mode 100644 index 0000000..a04dd40 --- /dev/null +++ b/modules/siteselect/lib/router.js @@ -0,0 +1,23 @@ +const express = require('express') +const { isSignedIn, hasRole } = require('@kansa/common/auth-user') +const Admin = require('./admin') +const Ballot = require('./ballot') + +module.exports = db => { + const router = express.Router() + + const ballot = new Ballot(db) + router.get('/:id/ballot', isSignedIn, ballot.getBallot) + + const admin = new Admin(db) + router.use('/tokens*', hasRole('siteselection')) + router.get('/tokens.:fmt', admin.getTokens) + router.get('/tokens/:token', admin.findToken) + + router.use('/voters*', hasRole('siteselection')) + router.get('/voters.:fmt', admin.getVoters) + router.get('/voters/:id', admin.findVoterTokens) + router.post('/voters/:id', admin.vote) + + return router +} diff --git a/modules/siteselect/lib/token.js b/modules/siteselect/lib/token.js new file mode 100644 index 0000000..d80383f --- /dev/null +++ b/modules/siteselect/lib/token.js @@ -0,0 +1,24 @@ +const randomstring = require('randomstring') + +function generateToken() { + return randomstring.generate({ + length: 6, + charset: 'ABCDEFHJKLMNPQRTUVWXY0123456789' + }) +} + +function parseToken(token) { + return ( + token && + token + .trim() + .toUpperCase() + .replace(/G/g, '6') + .replace(/I/g, '1') + .replace(/O/g, '0') + .replace(/S/g, '5') + .replace(/Z/g, '2') + ) +} + +module.exports = { generateToken, parseToken } diff --git a/modules/siteselect/package.json b/modules/siteselect/package.json new file mode 100644 index 0000000..a15ad3c --- /dev/null +++ b/modules/siteselect/package.json @@ -0,0 +1,15 @@ +{ + "name": "@kansa/siteselect", + "version": "1.0.0", + "description": "Worldcon Site Selection for Kansa", + "private": true, + "license": "Apache-2.0", + "repository": "maailma/kansa", + "main": "lib/router.js", + "peerDependencies": { + "@kansa/common": "1.x", + "express": "4.x", + "node-fetch": "*", + "randomstring": "1.x" + } +} diff --git a/server/lib/people/router.js b/server/lib/people/router.js index 19d705e..3d38f88 100644 --- a/server/lib/people/router.js +++ b/server/lib/people/router.js @@ -2,7 +2,6 @@ const express = require('express') const { isSignedIn, hasRole, matchesId } = require('@kansa/common/auth-user') const badge = require('../badge') -const Ballot = require('../ballot') const addPerson = require('./add') const { getPerson, getPrevNames, getPersonLog } = require('./get') @@ -77,8 +76,6 @@ module.exports = (db, ctx) => { .catch(next) }) - const ballot = new Ballot(db) - router.get('/:id/ballot', ballot.getBallot) router.get('/:id/badge', badge.getBadge) router.get('/:id/barcode.:fmt', badge.getBarcode) router.post('/:id/print', hasRole('member_admin'), badge.logPrint) diff --git a/server/lib/router.js b/server/lib/router.js index 221595f..2d9f9ec 100644 --- a/server/lib/router.js +++ b/server/lib/router.js @@ -7,7 +7,6 @@ const peopleRouter = require('./people/router') const adminRouter = require('./admin/router') const getConfig = require('./get-config') const Purchase = require('./purchase') -const Siteselect = require('./siteselect') const userRouter = require('./user/router') module.exports = (db, ctx) => { @@ -44,13 +43,5 @@ module.exports = (db, ctx) => { router.use('/people', ar) router.use('/people', peopleRouter(db, ctx)) - const siteselect = new Siteselect(db) - router.use('/siteselect', hasRole('siteselection')) - router.get('/siteselect/tokens.:fmt', siteselect.getTokens) - router.get('/siteselect/tokens/:token', siteselect.findToken) - router.get('/siteselect/voters.:fmt', siteselect.getVoters) - router.get('/siteselect/voters/:id', siteselect.findVoterTokens) - router.post('/siteselect/voters/:id', siteselect.vote) - return router } diff --git a/server/lib/types/payment.js b/server/lib/types/payment.js index 8e0bb47..018e1e7 100644 --- a/server/lib/types/payment.js +++ b/server/lib/types/payment.js @@ -1,7 +1,7 @@ const Stripe = require('stripe') const config = require('@kansa/common/config') const { InputError } = require('@kansa/common/errors') -const { generateToken } = require('../siteselect') +const { generateToken } = require('./token') function checkData(shape, data) { const missing = shape diff --git a/server/lib/types/token.js b/server/lib/types/token.js new file mode 100644 index 0000000..d80383f --- /dev/null +++ b/server/lib/types/token.js @@ -0,0 +1,24 @@ +const randomstring = require('randomstring') + +function generateToken() { + return randomstring.generate({ + length: 6, + charset: 'ABCDEFHJKLMNPQRTUVWXY0123456789' + }) +} + +function parseToken(token) { + return ( + token && + token + .trim() + .toUpperCase() + .replace(/G/g, '6') + .replace(/I/g, '1') + .replace(/O/g, '0') + .replace(/S/g, '5') + .replace(/Z/g, '2') + ) +} + +module.exports = { generateToken, parseToken } From 430258edbaa708094e80b8f7a14edb22859f61dc Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 9 Sep 2018 20:28:38 +0300 Subject: [PATCH 5/9] Add NotFoundError to common/errors & use it in modules/siteselect --- common/README.md | 4 +++- common/errors.js | 10 +++++++++- modules/siteselect/lib/admin.js | 10 +++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/common/README.md b/common/README.md index 128918c..2580640 100644 --- a/common/README.md +++ b/common/README.md @@ -44,13 +44,15 @@ server start. ## Errors ```js -const { AuthError, InputError } = require('@kansa/common/errors') +const { AuthError, InputError, NotFoundError } = require('@kansa/common/errors') ``` ### `new AuthError(message: string)` ### `new InputError(message: string)` +### `new NotFoundError(message: string)` + Handled by the server's error handling. May also have their `status` set. ## Log entries diff --git a/common/errors.js b/common/errors.js index 6c16ba0..12bd6f0 100644 --- a/common/errors.js +++ b/common/errors.js @@ -13,4 +13,12 @@ function InputError(message = 'Input error') { } InputError.prototype = new Error() -module.exports = { AuthError, InputError } +function NotFoundError(message = 'Not Found') { + this.name = 'NotFoundError' + this.message = message + this.status = 404 + this.stack = new Error().stack +} +NotFoundError.prototype = new Error() + +module.exports = { AuthError, InputError, NotFoundError } diff --git a/modules/siteselect/lib/admin.js b/modules/siteselect/lib/admin.js index 30a4d8c..8662a30 100644 --- a/modules/siteselect/lib/admin.js +++ b/modules/siteselect/lib/admin.js @@ -1,4 +1,4 @@ -const { InputError } = require('@kansa/common/errors') +const { InputError, NotFoundError } = require('@kansa/common/errors') const { parseToken } = require('./token') class Admin { @@ -13,19 +13,19 @@ class Admin { findToken(req, res, next) { const token = parseToken(req.params.token) - if (!token) return res.status(404).json({ error: 'not found' }) + if (!token) return next(new NotFoundError()) this.db .oneOrNone(`SELECT * FROM token_lookup WHERE token=$1`, token) .then(data => { - if (data) res.json(data) - else res.status(404).json({ error: 'not found' }) + if (!data) throw new NotFoundError() + res.json(data) }) .catch(next) } findVoterTokens(req, res, next) { const { id } = req.params - if (!id) return res.status(404).json({ error: 'not found' }) + if (!id) return next(new NotFoundError()) this.db .any( ` From 17ea35d3710c687f7385b8702042aae099886c12 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 9 Sep 2018 22:33:28 +0300 Subject: [PATCH 6/9] Add a default-disabled module for badge previews & prints --- config/kansa.yaml | 4 ++ integration-tests/test/badge.spec.js | 20 +++--- modules/badge/lib/badge.js | 102 +++++++++++++++++++++++++++ modules/badge/lib/router.js | 12 ++++ modules/badge/package.json | 14 ++++ 5 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 modules/badge/lib/badge.js create mode 100644 modules/badge/lib/router.js create mode 100644 modules/badge/package.json diff --git a/config/kansa.yaml b/config/kansa.yaml index 635b95b..bc3f81f 100644 --- a/config/kansa.yaml +++ b/config/kansa.yaml @@ -42,6 +42,10 @@ modules: # Superadmin actions: get & set admin levels, mass sync actions admin: true + # Badge previews and print logging. Uses tarra, the badge-printer, which in + # turn will need to have the proper fonts included. + badge: false + # Nomination and voting for the Hugo Awards hugo: true diff --git a/integration-tests/test/badge.spec.js b/integration-tests/test/badge.spec.js index 95862aa..ea5a22c 100644 --- a/integration-tests/test/badge.spec.js +++ b/integration-tests/test/badge.spec.js @@ -1,9 +1,11 @@ const assert = require('assert') const fs = require('fs') const request = require('supertest') -//const YAML = require('yaml').default +const YAML = require('yaml').default + +const config = YAML.parse(fs.readFileSync('../config/kansa.yaml', 'utf8')) +if (!config.modules.badge) return -//const config = YAML.parse(fs.readFileSync('../config/kansa.yaml', 'utf8')) const ca = fs.readFileSync('../proxy/ssl/localhost.cert', 'utf8') const host = 'localhost:4430' @@ -41,12 +43,12 @@ describe('Badges & barcodes', () => { it('get own badge', () => member - .get(`/api/people/${id}/badge`) + .get(`/api/badge/${id}`) .expect(200) .expect('Content-Type', pngType)) it("fail to get other's badge", () => - member.get(`/api/people/${id - 1}/badge`).expect(401)) + member.get(`/api/badge/${id - 1}`).expect(401)) it('get own barcode with id as PNG', () => member @@ -62,7 +64,7 @@ describe('Badges & barcodes', () => { it('fail to log own badge as printed', () => member - .post(`/api/people/${id}/print`) + .post(`/api/badge/${id}/print`) .send() .expect(401)) }) @@ -72,12 +74,12 @@ describe('Badges & barcodes', () => { it('get blank badge', () => anonymous - .get('/api/blank-badge') + .get('/api/badge/blank') .expect(200) .expect('Content-Type', pngType)) it("fail to get member's badge", () => - anonymous.get(`/api/people/${id}/badge`).expect(401)) + anonymous.get(`/api/badge/${id}`).expect(401)) it("get member's barcode with key as PNG", () => anonymous @@ -109,7 +111,7 @@ describe('Badges & barcodes', () => { it("get member's badge", () => admin - .get(`/api/people/${id}/badge`) + .get(`/api/badge/${id}`) .expect(200) .expect('Content-Type', pngType)) @@ -127,7 +129,7 @@ describe('Badges & barcodes', () => { it("log the member's badge as printed", () => admin - .post(`/api/people/${id}/print`) + .post(`/api/badge/${id}/print`) .send() .expect(200) .expect(res => assert.equal(res.body.status, 'success'))) diff --git a/modules/badge/lib/badge.js b/modules/badge/lib/badge.js new file mode 100644 index 0000000..53b67a8 --- /dev/null +++ b/modules/badge/lib/badge.js @@ -0,0 +1,102 @@ +const fetch = require('node-fetch') +const { matchesId } = require('@kansa/common/auth-user') +const config = require('@kansa/common/config') +const { InputError } = require('@kansa/common/errors') +const splitName = require('@kansa/common/split-name') + +const fetchBadge = ({ member_number, membership, names, subtitle }) => + fetch('http://tarra/label.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + format: 'png', + labeldata: [ + { + id: String(member_number) || 'number', + Class: membership || '', + FirstName: names[0], + Surname: names[1], + Info: subtitle || '' + } + ], + ltype: 'web' + }) + }) + +class Badge { + constructor(db) { + this.db = db + this.getBadge = this.getBadge.bind(this) + this.getBlank = this.getBlank.bind(this) + this.logPrint = this.logPrint.bind(this) + } + + getBlank(req, res, next) { + const { name, subtitle } = req.query + return fetchBadge({ names: splitName(name || ''), subtitle }) + .then(({ body, headers }) => { + res.setHeader( + 'Content-Disposition', + `inline; filename="${config.id}-badge.png"` + ) + res.setHeader('Content-Type', headers.get('content-type')) + res.setHeader('Content-Length', headers.get('content-length')) + body.pipe(res) + }) + .catch(next) + } + + getBadge(req, res, next) { + this.db + .task(async ts => { + const id = await matchesId(ts, req, ['member_admin', 'member_list']) + const data = await ts.oneOrNone( + `SELECT p.id, p.member_number, membership, + get_badge_name(p) AS name, + get_badge_subtitle(p) AS subtitle + FROM people p LEFT JOIN membership_types m USING (membership) + WHERE id = $1 AND m.badge = true`, + id + ) + if (!data) + throw new InputError('This member type is not eligible for a badge') + return data + }) + .then(async ({ id, member_number, membership, name, subtitle }) => { + const { body, headers } = await fetchBadge({ + member_number, + membership, + names: splitName(req.query.name || name || ''), + subtitle: req.query.subtitle || subtitle + }) + res.setHeader( + 'Content-Disposition', + `inline; filename="${config.id}-badge-${id}.png"` + ) + res.setHeader('Content-Type', headers.get('content-type')) + res.setHeader('Content-Length', headers.get('content-length')) + body.pipe(res) + }) + .catch(next) + } + + logPrint(req, res, next) { + const id = parseInt(req.params.id) + if (isNaN(id) || id < 0) return next(new InputError('Bad id number')) + this.db + .one( + `INSERT INTO badge_and_daypass_prints + (person, membership, member_number, daypass) + ( + SELECT p.id, p.membership, p.member_number, d.id + FROM people p LEFT JOIN daypasses d ON (p.id = d.person_id) + WHERE p.id = $1 + ) RETURNING timestamp`, + id + ) + .then(({ timestamp }) => res.json({ status: 'success', timestamp })) + .catch(next) + } +} + +module.exports = Badge diff --git a/modules/badge/lib/router.js b/modules/badge/lib/router.js new file mode 100644 index 0000000..23adbea --- /dev/null +++ b/modules/badge/lib/router.js @@ -0,0 +1,12 @@ +const express = require('express') +const { isSignedIn, hasRole } = require('@kansa/common/auth-user') +const Badge = require('./badge') + +module.exports = db => { + const badge = new Badge(db) + const router = express.Router() + router.get('/blank', badge.getBlank) + router.get('/:id', isSignedIn, badge.getBadge) + router.post('/:id/print', hasRole('member_admin'), badge.logPrint) + return router +} diff --git a/modules/badge/package.json b/modules/badge/package.json new file mode 100644 index 0000000..9d36872 --- /dev/null +++ b/modules/badge/package.json @@ -0,0 +1,14 @@ +{ + "name": "@kansa/badge", + "version": "1.0.0", + "description": "Badge generator for Kansa", + "private": true, + "license": "Apache-2.0", + "repository": "maailma/kansa", + "main": "lib/router.js", + "peerDependencies": { + "@kansa/common": "1.x", + "express": "4.x", + "node-fetch": "*" + } +} From 14650aecbc2ca6d387f38518b1d8c8ca6ef7c45f Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 9 Sep 2018 23:26:33 +0300 Subject: [PATCH 7/9] Split barcode tests from badge tests --- integration-tests/test/badge.spec.js | 40 +-------- integration-tests/test/barcode.spec.js | 109 +++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 39 deletions(-) create mode 100644 integration-tests/test/barcode.spec.js diff --git a/integration-tests/test/badge.spec.js b/integration-tests/test/badge.spec.js index ea5a22c..35483d8 100644 --- a/integration-tests/test/badge.spec.js +++ b/integration-tests/test/badge.spec.js @@ -9,18 +9,16 @@ if (!config.modules.badge) return const ca = fs.readFileSync('../proxy/ssl/localhost.cert', 'utf8') const host = 'localhost:4430' -let pdfType = 'application/pdf' let pngType = 'image/png' if (process.env.CI) { // Tarra requires fonts that are normally mounted from the file system, and // are not included in the build on the CI servers. So we hack around the // problem for now by expecting the responses to fail. -- Eemeli, 2018-09-09 - pdfType = 'text/html; charset=UTF-8' pngType = 'text/html; charset=UTF-8' } -describe('Badges & barcodes', () => { +describe('Badges', () => { const key = 'key' let id = null @@ -50,18 +48,6 @@ describe('Badges & barcodes', () => { it("fail to get other's badge", () => member.get(`/api/badge/${id - 1}`).expect(401)) - it('get own barcode with id as PNG', () => - member - .get(`/api/people/${id}/barcode.png`) - .expect(200) - .expect('Content-Type', pngType)) - - it('get own barcode with id as PDF', () => - member - .get(`/api/people/${id}/barcode.pdf`) - .expect(200) - .expect('Content-Type', pdfType)) - it('fail to log own badge as printed', () => member .post(`/api/badge/${id}/print`) @@ -80,18 +66,6 @@ describe('Badges & barcodes', () => { it("fail to get member's badge", () => anonymous.get(`/api/badge/${id}`).expect(401)) - - it("get member's barcode with key as PNG", () => - anonymous - .get(`/api/barcode/${key}/${id}.png`) - .expect(200) - .expect('Content-Type', pngType)) - - it("get member's barcode with key as PDF", () => - anonymous - .get(`/api/barcode/${key}/${id}.pdf`) - .expect(200) - .expect('Content-Type', pdfType)) }) describe('admin access', () => { @@ -115,18 +89,6 @@ describe('Badges & barcodes', () => { .expect(200) .expect('Content-Type', pngType)) - it("get member's barcode with id as PNG", () => - admin - .get(`/api/people/${id}/barcode.png`) - .expect(200) - .expect('Content-Type', pngType)) - - it("get member's barcode with id as PDF", () => - admin - .get(`/api/people/${id}/barcode.pdf`) - .expect(200) - .expect('Content-Type', pdfType)) - it("log the member's badge as printed", () => admin .post(`/api/badge/${id}/print`) diff --git a/integration-tests/test/barcode.spec.js b/integration-tests/test/barcode.spec.js new file mode 100644 index 0000000..cda4f24 --- /dev/null +++ b/integration-tests/test/barcode.spec.js @@ -0,0 +1,109 @@ +const assert = require('assert') +const fs = require('fs') +const request = require('supertest') +const YAML = require('yaml').default + +const config = YAML.parse(fs.readFileSync('../config/kansa.yaml', 'utf8')) +if (!config.modules.barcode) return + +const ca = fs.readFileSync('../proxy/ssl/localhost.cert', 'utf8') +const host = 'localhost:4430' + +let pdfType = 'application/pdf' +let pngType = 'image/png' + +if (process.env.CI) { + // Tarra requires fonts that are normally mounted from the file system, and + // are not included in the build on the CI servers. So we hack around the + // problem for now by expecting the responses to fail. -- Eemeli, 2018-09-09 + pdfType = 'text/html; charset=UTF-8' + pngType = 'text/html; charset=UTF-8' +} + +describe('Barcodes', () => { + const key = 'key' + let id = null + + describe('member access', () => { + const member = request.agent(`https://${host}`, { ca }) + + before(() => { + const email = 'member@example.com' + return member + .get('/api/login') + .query({ email, key }) + .expect('set-cookie', /w75/) + .expect(200, { status: 'success', email }) + .then(() => member.get('/api/user')) + .then(res => { + id = res.body.people[0].id + assert.equal(typeof id, 'number') + }) + }) + + it('get own barcode with id as PNG', () => + member + .get(`/api/people/${id}/barcode.png`) + .expect(200) + .expect('Content-Type', pngType)) + + it('get own barcode with id as PDF', () => + member + .get(`/api/people/${id}/barcode.pdf`) + .expect(200) + .expect('Content-Type', pdfType)) + + it("fail to get other's barcode", () => + member.get(`/api/people/${id - 1}/barcode.png`).expect(401)) + + it('fail to get own barcode with bad key', () => + member.get(`/api/barcode/${key + 'x'}/${id}.png`).expect(401)) + }) + + describe('anonymous access', () => { + const anonymous = request.agent(`https://${host}`, { ca }) + + it("get member's barcode with key as PNG", () => + anonymous + .get(`/api/barcode/${key}/${id}.png`) + .expect(200) + .expect('Content-Type', pngType)) + + it("get member's barcode with key as PDF", () => + anonymous + .get(`/api/barcode/${key}/${id}.pdf`) + .expect(200) + .expect('Content-Type', pdfType)) + + it('fail to get barcode with bad key', () => + anonymous.get(`/api/barcode/${key + 'x'}/${id}.png`).expect(401)) + }) + + describe('admin access', () => { + const admin = request.agent(`https://${host}`, { ca }) + before(() => { + const email = 'admin@example.com' + return admin + .get('/api/login') + .query({ email, key }) + .expect('set-cookie', /w75/) + .expect(200, { status: 'success', email }) + .then(() => admin.get('/api/user')) + .then(res => { + assert.notEqual(res.body.roles.indexOf('member_admin'), -1) + }) + }) + + it("get member's barcode with id as PNG", () => + admin + .get(`/api/people/${id}/barcode.png`) + .expect(200) + .expect('Content-Type', pngType)) + + it("get member's barcode with id as PDF", () => + admin + .get(`/api/people/${id}/barcode.pdf`) + .expect(200) + .expect('Content-Type', pdfType)) + }) +}) From 76fd28108dae43b5eaa43e869679cfde8f7f9b1e Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 9 Sep 2018 23:31:01 +0300 Subject: [PATCH 8/9] Add a default-disabled module for barcode generation --- config/kansa.yaml | 4 ++ integration-tests/test/barcode.spec.js | 10 ++--- modules/barcode/lib/barcode.js | 53 ++++++++++++++++++++++++++ modules/barcode/lib/router.js | 41 ++++++++++++++++++++ modules/barcode/package.json | 14 +++++++ 5 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 modules/barcode/lib/barcode.js create mode 100644 modules/barcode/lib/router.js create mode 100644 modules/barcode/package.json diff --git a/config/kansa.yaml b/config/kansa.yaml index bc3f81f..78a82c9 100644 --- a/config/kansa.yaml +++ b/config/kansa.yaml @@ -46,6 +46,10 @@ modules: # turn will need to have the proper fonts included. badge: false + # Barcodes to help speed up registration. Uses tarra, the badge-printer, which + # in turn will need to have the proper fonts included. + barcode: false + # Nomination and voting for the Hugo Awards hugo: true diff --git a/integration-tests/test/barcode.spec.js b/integration-tests/test/barcode.spec.js index cda4f24..6ec79c7 100644 --- a/integration-tests/test/barcode.spec.js +++ b/integration-tests/test/barcode.spec.js @@ -43,18 +43,18 @@ describe('Barcodes', () => { it('get own barcode with id as PNG', () => member - .get(`/api/people/${id}/barcode.png`) + .get(`/api/barcode/${id}.png`) .expect(200) .expect('Content-Type', pngType)) it('get own barcode with id as PDF', () => member - .get(`/api/people/${id}/barcode.pdf`) + .get(`/api/barcode/${id}.pdf`) .expect(200) .expect('Content-Type', pdfType)) it("fail to get other's barcode", () => - member.get(`/api/people/${id - 1}/barcode.png`).expect(401)) + member.get(`/api/barcode/${id - 1}.png`).expect(401)) it('fail to get own barcode with bad key', () => member.get(`/api/barcode/${key + 'x'}/${id}.png`).expect(401)) @@ -96,13 +96,13 @@ describe('Barcodes', () => { it("get member's barcode with id as PNG", () => admin - .get(`/api/people/${id}/barcode.png`) + .get(`/api/barcode/${id}.png`) .expect(200) .expect('Content-Type', pngType)) it("get member's barcode with id as PDF", () => admin - .get(`/api/people/${id}/barcode.pdf`) + .get(`/api/barcode/${id}.pdf`) .expect(200) .expect('Content-Type', pdfType)) }) diff --git a/modules/barcode/lib/barcode.js b/modules/barcode/lib/barcode.js new file mode 100644 index 0000000..fbeb16f --- /dev/null +++ b/modules/barcode/lib/barcode.js @@ -0,0 +1,53 @@ +const fetch = require('node-fetch') +const splitName = require('@kansa/common/split-name') + +module.exports = { getBarcodeId, getBarcodeData, fetchBarcode } + +function getBarcodeId(membership, member_number, id) { + const ch = membership.charAt(0) + const num = member_number || `i${id}` + return `${ch}-${num}` +} + +function getBarcodeData(db, id, key) { + return db.oneOrNone( + `SELECT p.id, member_number, membership, + get_badge_name(p) AS name, get_badge_subtitle(p) AS subtitle, + d.status AS daypass, daypass_days(d) AS days + FROM people p + JOIN keys k USING (email) + LEFT JOIN membership_types m USING (membership) + LEFT JOIN daypasses d ON (p.id = d.person_id) + WHERE p.id=$(id) AND + (m.badge = true OR d.status IS NOT NULL) + ${key ? 'AND key=$(key)' : ''}`, + { id, key } + ) +} + +function fetchBarcode( + dayNames, + format, + { daypass, days, id, member_number, membership, name, subtitle } +) { + const [FirstName, Surname] = splitName(name || '') + const Info = daypass + ? 'Daypass ' + dayNames.filter((_, i) => days[i]).join('/') + : subtitle || '' + return fetch('http://tarra/label.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + format, + labeldata: [ + { + id: getBarcodeId(membership, member_number, id), + FirstName, + Surname, + Info + } + ], + ltype: 'mail' + }) + }) +} diff --git a/modules/barcode/lib/router.js b/modules/barcode/lib/router.js new file mode 100644 index 0000000..43b3e17 --- /dev/null +++ b/modules/barcode/lib/router.js @@ -0,0 +1,41 @@ +const express = require('express') +const { isSignedIn, matchesId } = require('@kansa/common/auth-user') +const config = require('@kansa/common/config') +const { AuthError, InputError } = require('@kansa/common/errors') +const { getBarcodeData, fetchBarcode } = require('./barcode') + +// Used to form the subtitle for daypass holders +const dayNames = ['Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + +const getBarcode = db => (req, res, next) => { + const id = parseInt(req.params.id) + if (isNaN(id) || id < 0) return next(new InputError('Bad id number')) + const key = req.params.key + const format = req.params.fmt + if (format !== 'pdf' && format !== 'png') + return next(new InputError('Format must be either pdf or png')) + db.task(async ts => { + if (!key) await matchesId(ts, req, 'member_admin') + return getBarcodeData(ts, id, key) + }) + .then(async data => { + if (!data) throw new AuthError() + const { body, headers } = await fetchBarcode(dayNames, format, data) + res.setHeader( + 'Content-Disposition', + `inline; filename="${config.id}-barcode-${id}.${format}"` + ) + res.setHeader('Content-Type', headers.get('content-type')) + res.setHeader('Content-Length', headers.get('content-length')) + body.pipe(res) + }) + .catch(next) +} + +module.exports = db => { + const gb = getBarcode(db) + const router = express.Router() + router.get('/:key/:id.:fmt', gb) + router.get('/:id.:fmt', isSignedIn, gb) + return router +} diff --git a/modules/barcode/package.json b/modules/barcode/package.json new file mode 100644 index 0000000..de83a47 --- /dev/null +++ b/modules/barcode/package.json @@ -0,0 +1,14 @@ +{ + "name": "@kansa/barcode", + "version": "1.0.0", + "description": "Barcode generator for Kansa", + "private": true, + "license": "Apache-2.0", + "repository": "maailma/kansa", + "main": "lib/router.js", + "peerDependencies": { + "@kansa/common": "1.x", + "express": "4.x", + "node-fetch": "*" + } +} From b9ef06f2ddb0fc51bcf5d10d16763933db2b021d Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 9 Sep 2018 23:34:59 +0300 Subject: [PATCH 9/9] Remove badge.js from server --- server/lib/badge.js | 128 ------------------------------------ server/lib/people/router.js | 6 -- server/lib/router.js | 7 +- 3 files changed, 1 insertion(+), 140 deletions(-) delete mode 100644 server/lib/badge.js diff --git a/server/lib/badge.js b/server/lib/badge.js deleted file mode 100644 index fd6e18a..0000000 --- a/server/lib/badge.js +++ /dev/null @@ -1,128 +0,0 @@ -const fetch = require('node-fetch') -const config = require('@kansa/common/config') -const { AuthError } = require('@kansa/common/errors') -const splitName = require('@kansa/common/split-name') - -module.exports = { getBadge, getBarcode, logPrint } - -function getBadge(req, res, next) { - const id = parseInt(req.params.id || '0') - req.app.locals.db - .oneOrNone( - `SELECT - p.member_number, membership, - get_badge_name(p) AS name, get_badge_subtitle(p) AS subtitle - FROM people p - LEFT JOIN membership_types m USING (membership) - WHERE id = $1 AND m.badge = true`, - id - ) - .then(data => { - const { member_number, membership, name, subtitle } = data || {} - const [FirstName, Surname] = splitName(req.query.name || name || '') - return fetch('http://tarra/label.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - format: 'png', - labeldata: [ - { - id: String(member_number) || 'number', - Class: membership, - FirstName, - Surname, - Info: req.query.subtitle || subtitle || '' - } - ], - ltype: 'web' - }) - }).then(({ body, headers }) => { - res.setHeader( - 'Content-Disposition', - `inline; filename="${config.id}-badge-${id}.png"` - ) - res.setHeader('Content-Type', headers.get('content-type')) - res.setHeader('Content-Length', headers.get('content-length')) - body.pipe(res) - }) - }) - .catch(next) -} - -function getBarcode(req, res, next) { - const id = parseInt(req.params.id) - const key = req.params.key - const format = req.params.fmt === 'pdf' ? 'pdf' : 'png' - req.app.locals.db - .one( - ` - SELECT member_number, membership, - get_badge_name(p) AS name, get_badge_subtitle(p) AS subtitle, - d.status AS daypass, daypass_days(d) AS days - FROM people p - JOIN keys k USING (email) - LEFT JOIN membership_types m USING (membership) - LEFT JOIN daypasses d ON (p.id = d.person_id) - WHERE p.id=$(id) AND - (m.badge = true OR d.status IS NOT NULL) - ${key ? 'AND key=$(key)' : ''}`, - { id, key } - ) - .then(data => { - const { daypass, days, member_number, membership, name, subtitle } = data - const code = membership.charAt(0) + '-' + (member_number || `i${id}`) - const [FirstName, Surname] = splitName(name || '') - const Info = daypass - ? 'Daypass ' + - ['Wed', 'Thu', 'Fri', 'Sat', 'Sun'] - .filter((_, i) => days[i]) - .join('/') - : subtitle || '' - return fetch('http://tarra/label.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - format, - labeldata: [ - { - id: code, - FirstName, - Surname, - Info - } - ], - ltype: 'mail' - }) - }).then(({ body, headers }) => { - res.setHeader( - 'Content-Disposition', - `inline; filename="${config.id}-barcode-${id}.${format}"` - ) - res.setHeader('Content-Type', headers.get('content-type')) - res.setHeader('Content-Length', headers.get('content-length')) - body.pipe(res) - }) - }) - .catch(error => { - if (error.message === 'No data returned from the query.') { - error = new AuthError() - } - next(error) - }) -} - -function logPrint(req, res, next) { - req.app.locals.db - .one( - `INSERT INTO badge_and_daypass_prints - (person, membership, member_number, daypass) - ( - SELECT p.id, p.membership, p.member_number, d.id - FROM people p LEFT JOIN daypasses d ON (p.id = d.person_id) - WHERE p.id = $1 - ) RETURNING timestamp`, - parseInt(req.params.id) - ) - .then(({ timestamp }) => res.json({ status: 'success', timestamp })) - .catch(next) -} diff --git a/server/lib/people/router.js b/server/lib/people/router.js index 3d38f88..2c3297e 100644 --- a/server/lib/people/router.js +++ b/server/lib/people/router.js @@ -1,8 +1,6 @@ const express = require('express') const { isSignedIn, hasRole, matchesId } = require('@kansa/common/auth-user') -const badge = require('../badge') - const addPerson = require('./add') const { getPerson, getPrevNames, getPersonLog } = require('./get') const lookupPerson = require('./lookup') @@ -76,9 +74,5 @@ module.exports = (db, ctx) => { .catch(next) }) - router.get('/:id/badge', badge.getBadge) - router.get('/:id/barcode.:fmt', badge.getBarcode) - router.post('/:id/print', hasRole('member_admin'), badge.logPrint) - return router } diff --git a/server/lib/router.js b/server/lib/router.js index 2d9f9ec..2dffa5b 100644 --- a/server/lib/router.js +++ b/server/lib/router.js @@ -1,11 +1,9 @@ -const cors = require('cors') const express = require('express') const { isSignedIn, hasRole } = require('@kansa/common/auth-user') -const badge = require('./badge') -const peopleRouter = require('./people/router') const adminRouter = require('./admin/router') const getConfig = require('./get-config') +const peopleRouter = require('./people/router') const Purchase = require('./purchase') const userRouter = require('./user/router') @@ -20,9 +18,6 @@ module.exports = (db, ctx) => { router.use(userRouter(db, ctx)) - router.get('/barcode/:key/:id.:fmt', badge.getBarcode) - router.get('/blank-badge', badge.getBadge) - const purchase = new Purchase(db) router.post('/purchase', purchase.makeMembershipPurchase) router.get('/purchase/data', purchase.getPurchaseData)