From 3fb86482404e11942cd83c3500a297a3991db0e4 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Wed, 13 Sep 2017 16:28:53 +0200 Subject: Restructure project --- src/server/app.js | 62 +++++++++++++++++++++++++ src/server/bin/www | 60 +++++++++++++++++++++++++ src/server/lib/getMeetingpointData.js | 83 ++++++++++++++++++++++++++++++++++ src/server/lib/getURLOfUser.js | 8 ++++ src/server/lib/getUserIndex.js | 85 +++++++++++++++++++++++++++++++++++ src/server/routes/getSchedule.js | 55 +++++++++++++++++++++++ src/server/routes/index.js | 31 +++++++++++++ src/server/routes/manifest.js | 19 ++++++++ src/server/routes/opensearch.js | 12 +++++ 9 files changed, 415 insertions(+) create mode 100644 src/server/app.js create mode 100755 src/server/bin/www create mode 100644 src/server/lib/getMeetingpointData.js create mode 100644 src/server/lib/getURLOfUser.js create mode 100644 src/server/lib/getUserIndex.js create mode 100644 src/server/routes/getSchedule.js create mode 100644 src/server/routes/index.js create mode 100644 src/server/routes/manifest.js create mode 100644 src/server/routes/opensearch.js (limited to 'src/server') diff --git a/src/server/app.js b/src/server/app.js new file mode 100644 index 0000000..36d7e26 --- /dev/null +++ b/src/server/app.js @@ -0,0 +1,62 @@ +const express = require('express') +const path = require('path') +const logger = require('morgan') +const cookieParser = require('cookie-parser') +const bodyParser = require('body-parser') +const compression = require('compression') + +const routes = require('./routes/index') +const getSchedule = require('./routes/getSchedule') +const manifest = require('./routes/manifest') + +const app = express() + +app.use(compression()) + +// view engine setup +app.set('views', path.join(__dirname, '../client/views')) +app.set('view engine', 'jade') + +app.use(logger('dev')) +app.use(bodyParser.json()) +app.use(bodyParser.urlencoded({ extended: false })) +app.use(cookieParser()) + +app.use('/manifest.webmanifest', manifest) +app.use(express.static(path.join(__dirname, '../client/static'))) + +app.use('/', routes) +app.use('/get', getSchedule) + +// catch 404 and forward to error handler +app.use(function (req, res, next) { + const err = new Error('Not Found') + err.status = 404 + next(err) +}) + +// error handlers + +// development error handler +// will print stacktrace +if (app.get('env') === 'development') { + app.use(function (err, req, res, next) { + res.status(err.status || 500) + res.render('error', { + message: err.message, + error: err + }) + }) +} + +// production error handler +// no stacktraces leaked to user +app.use(function (err, req, res, next) { + res.status(err.status || 500) + res.render('error', { + message: err.message, + error: {} + }) +}) + +module.exports = app diff --git a/src/server/bin/www b/src/server/bin/www new file mode 100755 index 0000000..545db41 --- /dev/null +++ b/src/server/bin/www @@ -0,0 +1,60 @@ +#!/usr/bin/env node + +const app = require('../app') +const http = require('http') + +const port = normalizePort(process.env.PORT || '3000') +const server = http.createServer(app) + +server.listen(port) +server.on('error', error => onError(error, port)) +server.on('listening', _ => onListening(server)) + +function normalizePort (val) { + const port = parseInt(val, 10) + + if (isNaN(port)) { + // named pipe + return val + } + + if (port >= 0) { + // port number + return port + } + + return false +} + +function onError (error, port) { + if (error.syscall !== 'listen') { + throw error + } + + const bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges') + process.exit(1) + break + case 'EADDRINUSE': + console.error(bind + ' is already in use') + process.exit(1) + break + default: + throw error + } +} + +function onListening (server) { + const addr = server.address() + if (typeof addr === 'string') { + console.log(`Listening on pipe ${addr}`) + } else { + console.log(`Listening on http://localhost:${addr.port}/`) + } +} diff --git a/src/server/lib/getMeetingpointData.js b/src/server/lib/getMeetingpointData.js new file mode 100644 index 0000000..94cf36c --- /dev/null +++ b/src/server/lib/getMeetingpointData.js @@ -0,0 +1,83 @@ +'use strict' + +const Promise = require('bluebird') +const cheerio = require('cheerio') +const _ = require('lodash') +const request = Promise.promisify(require('request')) + +let meetingpointData +let lastUpdate + +function getUsers (page) { + const script = page('script').eq(1).text() + + const regexs = [/var classes = \[(.+)\];/, /var teachers = \[(.+)\];/, /var rooms = \[(.+)\];/, /var students = \[(.+)\];/] + const items = regexs.map(function (regex) { + return script.match(regex)[1].split(',').map(function (item) { + return item.replace(/"/g, '') + }) + }) + + return [] + .concat(items[0].map(function (item, index) { + return { + type: 'c', + value: item, + index: index + } + })) + .concat(items[1].map(function (item, index) { + return { + type: 't', + value: item, + index: index + } + })) + .concat(items[2].map(function (item, index) { + return { + type: 'r', + value: item, + index: index + } + })) + .concat(items[3].map(function (item, index) { + return { + type: 's', + value: item, + index: index + } + })) +} + +function getValidWeekNumbers(page) { + const weekSelector = page('select[name="week"]'); + const weekNumbers = _.map(weekSelector.children(), option => parseInt(option.attribs.value)) + + return weekNumbers; +} + +function requestData() { + lastUpdate = new Date() + + return request(`http://www.meetingpointmco.nl/Roosters-AL/doc/dagroosters/frames/navbar.htm`, { timeout: 5000 }).then((response) => { + const page = cheerio.load(response.body) + const users = getUsers(page) + const validWeekNumbers = getValidWeekNumbers(page) + + meetingpointData = { users, validWeekNumbers } + + return meetingpointData + }) +} + +function getMeetingpointData () { + if (lastUpdate == null || new Date() - lastUpdate > 10 * 60 * 1000) { // 10 minutes + return requestData() + } else if (!meetingpointData) { + return Promise.reject() + } else { + return Promise.resolve(meetingpointData) + } +} + +module.exports = getMeetingpointData diff --git a/src/server/lib/getURLOfUser.js b/src/server/lib/getURLOfUser.js new file mode 100644 index 0000000..2de48e6 --- /dev/null +++ b/src/server/lib/getURLOfUser.js @@ -0,0 +1,8 @@ +const leftPad = require('left-pad') // I imported this just to piss you off ;) + +function getURLOfUser (type, index, week) { + return `http://www.meetingpointmco.nl/Roosters-AL/doc/dagroosters/` + + `${leftPad(week, 2, '0')}/${type}/${type}${leftPad(index + 1, 5, '0')}.htm` +} + +module.exports = getURLOfUser diff --git a/src/server/lib/getUserIndex.js b/src/server/lib/getUserIndex.js new file mode 100644 index 0000000..db7daa8 --- /dev/null +++ b/src/server/lib/getUserIndex.js @@ -0,0 +1,85 @@ +'use strict' + +const Promise = require('bluebird') +const cheerio = require('cheerio') +const request = Promise.promisify(require('request')) + +let userIndex +let lastUpdate + +function updateUserIndex () { + return new Promise(function (resolve, reject) { + process.stdout.write('Updating user index... ') + request(`http://www.meetingpointmco.nl/Roosters-AL/doc/dagroosters/frames/navbar.htm`) + .then(function (page) { + lastUpdate = new Date() + page = page.body + + const $ = cheerio.load(page) + const $script = $('script').eq(1) + const scriptText = $script.text() + + const regexs = [/var classes = \[(.+)];/, /var teachers = \[(.+)];/, /var rooms = \[(.+)];/, /var students = \[(.+)];/] + const items = regexs.map(function (regex) { + return scriptText.match(regex)[1].split(',').map(function (item) { + return item.replace(/"/g, '') + }) + }) + + userIndex = ([] + .concat(items[0].map(function (item, index) { + return { + type: 'c', + value: item, + index: index + } + })) + .concat(items[1].map(function (item, index) { + return { + type: 't', + value: item, + index: index + } + })) + .concat(items[2].map(function (item, index) { + return { + type: 'r', + value: item, + index: index + } + })) + .concat(items[3].map(function (item, index) { + return { + type: 's', + value: item, + index: index + } + }))) + + process.stdout.write('done.\n') + + resolve(userIndex) + }) + .catch(error => { + process.stdout.write('failed.\n') + reject(error) + }) + }) +} + +function getUserIndex () { + return new Promise((resolve, reject) => { + if (lastUpdate == null) { + updateUserIndex().then(resolve, reject) + } else if (new Date() - lastUpdate > 10 * 60 * 1000) { // 10 minutes + updateUserIndex().then(resolve, function () { + console.warn('Unable to update userIndex, using cached.') + resolve(userIndex) + }) + } else { + resolve(userIndex) + } + }) +} + +module.exports = getUserIndex diff --git a/src/server/routes/getSchedule.js b/src/server/routes/getSchedule.js new file mode 100644 index 0000000..f6c3cb6 --- /dev/null +++ b/src/server/routes/getSchedule.js @@ -0,0 +1,55 @@ +const express = require('express') +const router = express.Router() +const request = require('request') +const iconv = require('iconv-lite') + +const getUserIndex = require('../lib/getUserIndex') +const getURLOfUser = require('../lib/getURLOfUser') + +// copied from http://www.meetingpointmco.nl/Roosters-AL/doc/dagroosters/untisscripts.js, +// were using the same code as they do to be sure that we always get the same +// week number. +function getWeekNumber (target) { + const dayNr = (target.getDay() + 6) % 7 + target.setDate(target.getDate() - dayNr + 3) + const firstThursday = target.valueOf() + target.setMonth(0, 1) + if (target.getDay() !== 4) { + target.setMonth(0, 1 + ((4 - target.getDay()) + 7) % 7) + } + + return 1 + Math.ceil((firstThursday - target) / 604800000) +} + +router.get('/:type/:value', function (req, res, next) { + getUserIndex().then(users => { + const { type, value } = req.params + let { week } = req.query + const user = + users.filter(user => user.type === type && user.value === value)[0] + + if (!user) { + next(new Error(`${type}${value} is not in the user index.`)) + } + + if (!week) { + week = getWeekNumber(new Date()) + } + + const { index } = user + + const url = getURLOfUser(type, index, week) + + request(url, { encoding: null }, function (err, data) { + if (err) { + next(err) + return + } + + const utf8Body = iconv.decode(data.body, 'ISO-8859-1') + res.status(data.statusCode).end(utf8Body) + }) + }) +}) + +module.exports = router diff --git a/src/server/routes/index.js b/src/server/routes/index.js new file mode 100644 index 0000000..d2267ba --- /dev/null +++ b/src/server/routes/index.js @@ -0,0 +1,31 @@ +'use strict' + +const express = require('express') +const router = express.Router() +const getMeetingpointData = require('../lib/getMeetingpointData') + +/* GET home page. */ +router.get(['/', '/s/*', '/t/*', '/r/*', '/c/*'], function (req, res, next) { + getMeetingpointData().then(data => { + const isBeta = process.env.BETA === '1' + + let flags = [] + if (isBeta) { + flags.push('BETA') + flags.push('NO_FEATURE_DETECT') + } else if (req.query.nfd != null) { + flags.push('NO_FEATURE_DETECT') + } + + const flagsStr = `var FLAGS = ${JSON.stringify(flags)};` + const usersStr = `var USERS = ${JSON.stringify(data.users)};` + const validWeekNumbersStr = `var VALID_WEEK_NUMBERS = ${JSON.stringify(data.validWeekNumbers)}` + + res.render('index', { flagsStr, usersStr, validWeekNumbersStr }) + }).catch(function () { + console.error('Unable to get user info, emergency redirect!') + res.render('redirect') + }) +}) + +module.exports = router diff --git a/src/server/routes/manifest.js b/src/server/routes/manifest.js new file mode 100644 index 0000000..b2ce55f --- /dev/null +++ b/src/server/routes/manifest.js @@ -0,0 +1,19 @@ +'use strict' + +const express = require('express') +const router = express.Router() +const path = require('path') + +router.get('/', function (req, res, next) { + console.log('got a request') + + const isBeta = process.env.BETA === '1' + + if (isBeta) { + res.sendFile('manifest.beta.webmanifest', { root: path.join(__dirname, '../public') }) + } else { + res.sendFile('manifest.webmanifest', { root: path.join(__dirname, '../public') }) + } +}) + +module.exports = router diff --git a/src/server/routes/opensearch.js b/src/server/routes/opensearch.js new file mode 100644 index 0000000..c3e2e57 --- /dev/null +++ b/src/server/routes/opensearch.js @@ -0,0 +1,12 @@ +'use strict' + +const express = require('express') +const router = express.Router() +const path = require('path') + +router.get('/', function (req, res, next) { + res.setHeader('content-type', 'application/opensearchdescription+xml') + res.sendFile('opensearch.xml', { root: path.join(__dirname, '../public') }) +}) + +module.exports = router -- cgit v1.1