diff --git a/app.js b/app.js index a3c952b..06ea0f6 100644 --- a/app.js +++ b/app.js @@ -1,36 +1,39 @@ require('dotenv').config(); + +const fs = require('fs'); const express = require('express'); const session = require('express-session'); const minifyHTML = require('express-minify-html-2'); const bodyParser = require('body-parser'); -const settings = require(__dirname + '/src/admin/settings'); const passport = require('passport'); -const passportSteam = require('passport-steam'); -const SteamStrategy = passportSteam.Strategy; +const SteamStrategy = require('passport-steam').Strategy; + +const github = 'https://github.com/TeamHypersomnia'; +const pressKit = `${github}/PressKit/blob/main/README.md#intro`; const app = express(); const port = 3000; -passport.serializeUser(function (user, done) { +// Passport +passport.serializeUser((user, done) => { done(null, user); }); -passport.deserializeUser(function (obj, done) { +passport.deserializeUser((obj, done) => { done(null, obj); }); passport.use(new SteamStrategy({ - returnURL: process.env.URL + 'auth/steam/return', - realm: process.env.URL, - apiKey: process.env.STEAM_APIKEY - }, - function (identifier, profile, done) { - process.nextTick(function () { - profile.identifier = identifier; - return done(null, profile); - }); - } -)); + returnURL: process.env.URL + 'auth/steam/return', + realm: process.env.URL, + apiKey: process.env.STEAM_APIKEY +}, (identifier, profile, done) => { + process.nextTick(() => { + profile.identifier = identifier; + return done(null, profile); + }); +})); +// Configuration app.set('views', __dirname + '/views'); app.set('view engine', 'ejs'); @@ -39,11 +42,13 @@ app.use(session({ resave: true, saveUninitialized: true })); + app.use(passport.initialize()); app.use(passport.session()); app.use(express.static(__dirname + '/public')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); + app.use(minifyHTML({ override: true, exceptionUrls: false, @@ -56,14 +61,64 @@ app.use(minifyHTML({ } })); -app.locals = { - arena: false, - version: 3 -}; -const obj = settings.load(); -app.locals.alert = obj.alert; +// Settings +const ts = Math.floor(Date.now() / 1000); +app.locals.version = ts; +app.locals.alert = ''; +try { + const content = fs.readFileSync(`./private/settings.json`, 'utf8'); + const obj = JSON.parse(content); + app.locals.version = obj.version ? obj.version : ts; + app.locals.alert = obj.alert ? obj.alert : ''; +} catch (error) { + console.error(error.message); +} + +// Middleware +function usr(req, res, next) { + if (!req.isAuthenticated()) { + return res.redirect('/auth/steam'); + } + return next(); +} + +function adm(req, res, next) { + if (!req.isAuthenticated()) { + return res.redirect('/auth/steam'); + } + const admins = process.env.ADMINS.split(','); + if (!admins.includes(req.user.id)) { + return res.redirect('/'); + } + return next(); +} -require(__dirname + '/src/routes')(app, passport); +// Routes +app.use('/', require('./src/index')); +app.use('/guide', require('./src/guide')); +app.use('/arenas', require('./src/arenas')); +app.use('/weapons', require('./src/weapons')); +app.use('/servers', require('./src/servers')); +app.use('/profile', usr, require('./src/profile')); +app.use('/logout', require('./src/logout')); +app.use('/auth', require('./src/auth')(passport)); +app.use('/disclaimer', require('./src/disclaimer')); +app.use('/cookie-policy', require('./src/cookie_policy')); +app.use('/contact', require('./src/contact')); +app.get('/press', (req, res) => res.redirect(pressKit)); +app.use('/upload', require('./src/upload')); +app.get('/admin', adm, (req, res) => res.redirect('/admin/system')); +app.use('/admin/system', adm, require('./src/admin/system')); +app.use('/admin/visitors', adm, require('./src/admin/visitors')); +app.use('/admin/users', adm, require('./src/admin/users')); +app.use('/admin/creators', adm, require('./src/admin/creators')); +app.use('/admin/settings', adm, require('./src/admin/settings')(app.locals)); +app.use((req, res) => res.status(404).render('404', { + page: 'Not Found', + user: req.user +})); + +// Start app.listen(port, () => { - console.log(`App listening on port ${port}`) -}) + console.log(`App listening on port ${port}`); +}); diff --git a/src/admin/creators.js b/src/admin/creators.js new file mode 100644 index 0000000..9a840e3 --- /dev/null +++ b/src/admin/creators.js @@ -0,0 +1,85 @@ +const express = require('express'); +const router = express.Router(); +const fs = require('fs'); +const path = `${__dirname}/../../private/authorized_mappers.json`; + +function loadCreators() { + try { + const d = fs.readFileSync(path, 'utf8'); + const obj = JSON.parse(d); + return obj; + } catch (error) { + console.error(error.message); + return {}; + } +} + +function saveCreators(obj) { + try { + const json = JSON.stringify(obj, null, 2); + fs.writeFileSync(path, json, 'utf8'); + } catch (error) { + console.error(error.message); + } +} + +function randomString(len) { + const c = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + for (let i = 0; i < len; i++) { + const randomIndex = Math.floor(Math.random() * c.length); + result += c.charAt(randomIndex); + } + return result; +} + +router.get('/', (req, res) => { + res.render('admin/creators', { + page: 'Creators', + user: req.user, + creators: loadCreators() + }); +}); + +router.post('/', (req, res) => { + const obj = loadCreators(); + obj[randomString(50)] = { + shorthand: req.body.shorthand, + allow_creating_new: false, + maps: [] + }; + saveCreators(obj); + res.redirect('/admin/creators'); +}); + +router.get('/:shorthand', (req, res) => { + const obj = loadCreators(); + const c = Object.values(obj).find((obj) => obj.shorthand === req.params.shorthand); + if (!c) { + return res.redirect('/admin/creators'); + } + res.render('admin/creator', { + page: 'Creators', + user: req.user, + c: c + }); +}); + +router.post('/:shorthand', (req, res) => { + const obj = loadCreators(); + const k = Object.keys(obj).find((k) => obj[k].shorthand === req.params.shorthand); + if (!k) { + return res.redirect('/admin/creators'); + } + if (req.body.delete === 'on') { + delete obj[k]; + } else { + obj[k].shorthand = req.body.shorthand; + obj[k].allow_creating_new = req.body.allow_creating_new === 'yes'; + obj[k].maps = req.body.arenas.split('\n').map((line) => line.trim()); + } + saveCreators(obj); + res.redirect('/admin/creators'); +}); + +module.exports = router; diff --git a/src/admin/settings.js b/src/admin/settings.js index 936a857..3ad2a30 100644 --- a/src/admin/settings.js +++ b/src/admin/settings.js @@ -1,25 +1,43 @@ +const express = require('express'); +const router = express.Router(); const fs = require('fs'); -const path = __dirname + '/../../private/settings.json'; +const path = `${__dirname}/../../private/settings.json`; -module.exports = { - load: function () { - try { - const fileContent = fs.readFileSync(path, 'utf8'); - const obj = JSON.parse(fileContent); - return obj; - } catch (error) { - console.error(`Error reading settings: ${error.message}`); - return null; - } - }, +function loadSettings() { + try { + const d = fs.readFileSync(path, 'utf8'); + const obj = JSON.parse(d); + return obj; + } catch (error) { + console.error(error.message); + return {}; + } +} - save: function (obj) { - try { - const json = JSON.stringify(obj, null, 2); - fs.writeFileSync(path, json, 'utf8'); - } catch (error) { - console.error(`Error writing settings ${error.message}`); - return null; - } +function saveSettings(obj) { + try { + const json = JSON.stringify(obj, null, 2); + fs.writeFileSync(path, json, 'utf8'); + } catch (error) { + console.error(error.message); } +} + +module.exports = function(locals) { + router.get('/', (req, res) => { + res.render('admin/settings', { + page: 'Settings', + user: req.user + }); + }); + + router.post('/', (req, res) => { + const obj = loadSettings(); + obj.alert = req.body.alert; + locals.alert = req.body.alert; + saveSettings(obj); + res.redirect('/admin/settings'); + }); + + return router; }; diff --git a/src/admin/system.js b/src/admin/system.js index 15ba9a6..acb678f 100644 --- a/src/admin/system.js +++ b/src/admin/system.js @@ -1,3 +1,5 @@ +const express = require('express'); +const router = express.Router(); const os = require('os'); function formatUptime(uptime) { @@ -11,18 +13,20 @@ function formatUptime(uptime) { return `${h}:${m}:${s}`; } -module.exports = { - getData: function () { - return { - hostname: os.hostname(), - loadavg: os.loadavg(), - machine: os.machine(), - type: os.type(), - release: os.release(), - uptime: formatUptime(os.uptime()), - usedmem: os.totalmem() - os.freemem(), - totalmem: os.totalmem(), - nodever: process.version, - }; - } -}; \ No newline at end of file +router.get('/', (req, res) => { + res.render('admin/system', { + page: 'System', + user: req.user, + hostname: os.hostname(), + loadavg: os.loadavg(), + machine: os.machine(), + type: os.type(), + release: os.release(), + uptime: formatUptime(os.uptime()), + usedmem: os.totalmem() - os.freemem(), + totalmem: os.totalmem(), + node: process.version + }); +}); + +module.exports = router; diff --git a/src/admin/users.js b/src/admin/users.js new file mode 100644 index 0000000..8d43e19 --- /dev/null +++ b/src/admin/users.js @@ -0,0 +1,36 @@ +const express = require('express'); +const router = express.Router(); +const moment = require('moment'); +const fs = require('fs'); +const path = `${__dirname}/../../private/users.json`; + +function loadUsers() { + try { + const d = fs.readFileSync(path, 'utf8'); + const obj = JSON.parse(d); + return obj; + } catch (error) { + console.error(error.message); + return {}; + } +} + +router.get('/', (req, res) => { + const users = loadUsers(); + const updatedUsers = Object.fromEntries( + Object.entries(users).map(([userId, user]) => [ + userId, + { + ...user, + lastLogin: moment(user.lastLogin * 1000).fromNow() + } + ]) + ); + res.render('admin/users', { + page: 'Users', + user: req.user, + users: updatedUsers + }); +}); + +module.exports = router; diff --git a/src/admin/visitors.js b/src/admin/visitors.js new file mode 100644 index 0000000..d01b944 --- /dev/null +++ b/src/admin/visitors.js @@ -0,0 +1,11 @@ +const express = require('express'); +const router = express.Router(); + +router.get('/', (req, res) => { + res.render('admin/visitors', { + page: 'Visitors', + user: req.user + }); +}); + +module.exports = router; diff --git a/src/arenas.js b/src/arenas.js index 542e741..b58722b 100644 --- a/src/arenas.js +++ b/src/arenas.js @@ -1,3 +1,5 @@ +const express = require('express'); +const router = express.Router(); const fs = require('fs'); const path = require('path'); const moment = require('moment'); @@ -25,79 +27,99 @@ function getFolderSize(folderPath) { } function loadArenas() { - try { - const files = fs.readdirSync(dirPath, { withFileTypes: true }); - const directories = files.filter(file => file.isDirectory()); - const arenas = []; - directories.forEach(d => { - const arenaPath = `${dirPath}/${d.name}/${d.name}.json`; - if (fs.existsSync(arenaPath)) { - const fileContent = fs.readFileSync(arenaPath, 'utf8'); - const obj = JSON.parse(fileContent); - - const format = 'YYYY-MM-DD HH:mm:ss.SSSSSS Z'; - const dateObject = moment.utc(obj.meta.version_timestamp, format); - const timeAgo = dateObject.fromNow(); - const size = getFolderSize(`${dirPath}/${d.name}`); - - let short_description = 'N/A'; - if (obj.about.short_description) { - short_description = obj.about.short_description; - } - - let full_description = 'N/A'; - if (obj.about.full_description) { - full_description = obj.about.full_description; - } - - arenas.push({ - name: obj.meta.name, - author: obj.about.author, - short_description: short_description, - full_description: full_description, - version_timestamp: obj.meta.version_timestamp, - updated: timeAgo, - size: size - }); + const files = fs.readdirSync(dirPath, { withFileTypes: true }); + const directories = files.filter(file => file.isDirectory()); + const arenas = []; + directories.forEach(d => { + const arenaPath = `${dirPath}/${d.name}/${d.name}.json`; + if (fs.existsSync(arenaPath)) { + const fileContent = fs.readFileSync(arenaPath, 'utf8'); + const obj = JSON.parse(fileContent); + + const format = 'YYYY-MM-DD HH:mm:ss.SSSSSS Z'; + const dateObject = moment.utc(obj.meta.version_timestamp, format); + const timeAgo = dateObject.fromNow(); + const size = getFolderSize(`${dirPath}/${d.name}`); + + let short_description = 'N/A'; + if (obj.about.short_description) { + short_description = obj.about.short_description; } - }); - console.log(`Loaded ${arenas.length} arenas successfully`); - return arenas; - } catch (err) { - console.error(`Error reading directory: ${err}`); - return []; - } + + let full_description = 'N/A'; + if (obj.about.full_description) { + full_description = obj.about.full_description; + } + + arenas.push({ + name: obj.meta.name, + author: obj.about.author, + short_description: short_description, + full_description: full_description, + version_timestamp: obj.meta.version_timestamp, + updated: timeAgo, + size: size + }); + } + }); + console.log(`Loaded ${arenas.length} arenas successfully`); + return arenas; } -module.exports = { - init: function () { - arenas = loadArenas(); +arenas = loadArenas(); - const watcher = chokidar.watch(`${dirPath}/**/*.json`, { - ignoreInitial: true, - persistent: true, - recursive: true, - ignored: '**/editor_view.json' - }); +const watcher = chokidar.watch(`${dirPath}/**/*.json`, { + ignoreInitial: true, + persistent: true, + recursive: true, + ignored: '**/editor_view.json' +}); - watcher.on('all', (event, path) => { - console.log(event, path); - arenas = loadArenas(); - }); - - watcher.on('error', (error) => { - console.error(`Watcher error: ${error}`); - }); - - process.on('SIGINT', () => { - watcher.close(); - process.exit(); - }); - - console.log(`Watching for changes in arenas...`); - }, +watcher.on('all', (event, path) => { + console.log(event, path); + arenas = loadArenas(); +}); - getArenas: function () { - return arenas; +watcher.on('error', (error) => { + console.error(`Watcher error: ${error}`); +}); + +process.on('SIGINT', () => { + watcher.close(); + process.exit(); +}); + +router.get('/', function (req, res) { + if (req.query.format !== undefined && req.query.format == 'json') { + return res.json(arenas); + } + res.render('arenas', { + page: 'Arenas', + user: req.user, + arenas: arenas + }); +}); + +router.get('/:arena', function (req, res) { + const index = arenas.findIndex(v => v.name === req.params.arena); + if (index === -1) { + return res.redirect('/arenas'); } -}; + let prev = arenas[arenas.length - 1].name; + if (arenas[index - 1] !== undefined) { + prev = arenas[index - 1].name; + } + let next = arenas[0].name; + if (arenas[index + 1] !== undefined) { + next = arenas[index + 1].name; + } + res.render('arena', { + page: arenas[index].name, + user: req.user, + arena: arenas[index], + prev: prev, + next: next + }); +}); + +module.exports = router; diff --git a/src/auth.js b/src/auth.js new file mode 100644 index 0000000..b3007ef --- /dev/null +++ b/src/auth.js @@ -0,0 +1,59 @@ +const express = require('express'); +const router = express.Router(); +const fs = require('fs'); +const path = `${__dirname}/../private/users.json`; + +function loadUsers() { + try { + const d = fs.readFileSync(path, 'utf8'); + const obj = JSON.parse(d); + return obj; + } catch (error) { + console.error(error.message); + return {}; + } +} + +function saveUsers(obj) { + try { + const json = JSON.stringify(obj, null, 2); + fs.writeFileSync(path, json, 'utf8'); + } catch (error) { + console.error(error.message); + } +} + +module.exports = function(passport) { + router.get('/steam', + passport.authenticate('steam', { + failureRedirect: '/' + }), + function (req, res) { + res.redirect('/'); + } + ); + + router.get('/steam/return', + passport.authenticate('steam', { + failureRedirect: '/' + }), + function (req, res) { + const users = loadUsers(); + const data = { + name: req.user.displayName, + lastLogin: Math.floor(Date.now() / 1000), + ip: req.headers['x-forwarded-for'] || req.socket.remoteAddress, + ua: req.get('User-Agent') + }; + if (users.hasOwnProperty(req.user.id)) { + Object.assign(users[req.user.id], data); + } else { + users[req.user.id] = data; + } + saveUsers(users); + res.redirect('/'); + } + ); + + return router; +}; diff --git a/src/commits.js b/src/commits.js deleted file mode 100644 index 654ae85..0000000 --- a/src/commits.js +++ /dev/null @@ -1,37 +0,0 @@ -const axios = require('axios'); -const moment = require('moment'); -let commits = []; - -function cutTitle(title, maxLength) { - return title.length > maxLength ? title.substring(0, maxLength) + '...' : title; -} - -module.exports = { - init: function () { - function fetchCommits(callback) { - axios.get('https://api.github.com/repos/TeamHypersomnia/Hypersomnia/commits') - .then(response => { - commits = response.data.slice(0, 10).map(commit => ({ - sha: commit.sha, - date: commit.commit.author.date, - msg: cutTitle(commit.commit.message, 35), - })); - }) - .catch(error => { - console.error(`Error fetching commits: ${error.message}`); - }); - } - fetchCommits(); - setInterval(fetchCommits, 600000); // 10 minutes - }, - - getCommits: function () { - const formattedData = commits.map(v => { - return { - ...v, - date: moment.utc(v.date).fromNow() - }; - }); - return formattedData; - } -}; diff --git a/src/contact.js b/src/contact.js new file mode 100644 index 0000000..a71f20a --- /dev/null +++ b/src/contact.js @@ -0,0 +1,11 @@ +const express = require('express'); +const router = express.Router(); + +router.get('/', (req, res) => { + res.render('contact', { + page: 'Contact', + user: req.user + }); +}) + +module.exports = router; diff --git a/src/cookie_policy.js b/src/cookie_policy.js new file mode 100644 index 0000000..f61b3d3 --- /dev/null +++ b/src/cookie_policy.js @@ -0,0 +1,11 @@ +const express = require('express'); +const router = express.Router(); + +router.get('/', (req, res) => { + res.render('cookie_policy', { + page: 'Cookie Policy', + user: req.user + }); +}) + +module.exports = router; diff --git a/src/disclaimer.js b/src/disclaimer.js new file mode 100644 index 0000000..0b955a8 --- /dev/null +++ b/src/disclaimer.js @@ -0,0 +1,11 @@ +const express = require('express'); +const router = express.Router(); + +router.get('/', (req, res) => { + res.render('disclaimer', { + page: 'Disclaimer', + user: req.user + }); +}) + +module.exports = router; diff --git a/src/guide.js b/src/guide.js new file mode 100644 index 0000000..2c13361 --- /dev/null +++ b/src/guide.js @@ -0,0 +1,11 @@ +const express = require('express'); +const router = express.Router(); + +router.get('/', (req, res) => { + res.render('guide', { + page: 'Guide', + user: req.user + }); +}) + +module.exports = router; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..f9944e7 --- /dev/null +++ b/src/index.js @@ -0,0 +1,41 @@ +const express = require('express'); +const router = express.Router(); +const axios = require('axios'); +const moment = require('moment'); +let commits = []; + +function cutTitle(title, maxLength) { + return title.length > maxLength ? title.substring(0, maxLength) + '...' : title; +} + +function fetchCommits() { + axios.get('https://api.github.com/repos/TeamHypersomnia/Hypersomnia/commits') + .then(response => { + commits = response.data.slice(0, 10).map(commit => ({ + sha: commit.sha, + date: commit.commit.author.date, + msg: cutTitle(commit.commit.message, 35), + })); + }) + .catch(error => { + console.error(error.message); + }); +} +fetchCommits(); +setInterval(fetchCommits, 600000); // 10 min + +router.get('/', (req, res) => { + const obj = commits.map(v => { + return { + ...v, + date: moment.utc(v.date).fromNow() + }; + }); + res.render('index', { + page: 'Index', + user: req.user, + commits: obj + }); +}) + +module.exports = router; diff --git a/src/logout.js b/src/logout.js new file mode 100644 index 0000000..4b79180 --- /dev/null +++ b/src/logout.js @@ -0,0 +1,13 @@ +const express = require('express'); +const router = express.Router(); + +router.post('/', function (req, res, next) { + req.logout(function (err) { + if (err) { + return next(err); + } + res.redirect('/'); + }); +}); + +module.exports = router; diff --git a/src/profile.js b/src/profile.js new file mode 100644 index 0000000..f5419b6 --- /dev/null +++ b/src/profile.js @@ -0,0 +1,11 @@ +const express = require('express'); +const router = express.Router(); + +router.get('/', function (req, res) { + res.render('profile', { + page: 'Profile', + user: req.user + }); +}); + +module.exports = router; diff --git a/src/routes.js b/src/routes.js deleted file mode 100644 index cf115f7..0000000 --- a/src/routes.js +++ /dev/null @@ -1,313 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const multer = require('multer'); -const storage = multer.memoryStorage(); -const upload = multer({ storage: storage }); - -const arenas = require(__dirname + '/arenas'); -const servers = require(__dirname + '/servers'); -const commits = require(__dirname + '/commits'); -const weapons = require(__dirname + '/weapons'); -const system = require(__dirname + '/admin/system'); -const settings = require(__dirname + '/admin/settings'); - -arenas.init(); -servers.init(); -commits.init(); - -function onlyUserAccess(req, res, next) { - if (!req.isAuthenticated()) { - return res.redirect('/auth/steam'); - } - return next(); -} - -function onlyAdminAccess(req, res, next) { - if (!req.isAuthenticated()) { - return res.redirect('/auth/steam'); - } - const admins = process.env.ADMINS.split(','); - if (!admins.includes(req.user.id)) { - return res.redirect('/'); - } - return next(); -} - -function writeFileWithDirectory(filePath, content) { - const directory = path.dirname(filePath); - if (!fs.existsSync(directory)) { - fs.mkdirSync(directory, { recursive: true }); - } - fs.writeFileSync(filePath, content); -} - -module.exports = function (app, passport) { - app.get('/', function (req, res) { - res.render('index', { - page: 'Index', - user: req.user, - commits: commits.getCommits() - }); - }); - - app.get('/guide', function (req, res) { - res.render('guide', { - page: 'Guide', - user: req.user - }); - }); - - app.get('/arenas', function (req, res) { - const obj = arenas.getArenas(); - if (req.query.format !== undefined && req.query.format == 'json') { - return res.json(obj); - } - res.render('arenas', { - page: 'Arenas', - user: req.user, - arenas: obj - }); - }); - - app.get('/arenas/:arena', function (req, res) { - const obj = arenas.getArenas(); - const index = obj.findIndex(v => v.name === req.params.arena); - if (index === -1) { - return res.redirect('/arenas'); - } - let prev = obj[obj.length - 1].name; - if (obj[index - 1] !== undefined) { - prev = obj[index - 1].name; - } - let next = obj[0].name; - if (obj[index + 1] !== undefined) { - next = obj[index + 1].name; - } - res.render('arena', { - page: obj[index].name, - user: req.user, - arena: obj[index], - prev: prev, - next: next - }); - }); - - app.get('/weapons', function (req, res) { - res.render('weapons', { - page: 'Weapons', - user: req.user, - firearms: weapons.getFirearms(), - melees: weapons.getMelees(), - explosives: weapons.getExplosives(), - spells: weapons.getSpells() - }); - }); - - app.get('/servers', function (req, res) { - res.render('servers', { - page: 'Servers', - user: req.user, - servers: servers.getServers() - }); - }); - - app.get('/servers/:address', function (req, res) { - const list = servers.getServers(); - const sv = list.find(v => v.ip === req.params.address); - if (sv === undefined) { - return res.redirect('/servers'); - } - res.render('server', { - page: sv.name, - user: req.user, - sv: sv - }); - }); - - app.get('/disclaimer', function (req, res) { - res.render('disclaimer', { - page: 'Disclaimer', - user: req.user - }); - }); - - app.get('/cookie-policy', function (req, res) { - res.render('cookie_policy', { - page: 'Cookie Policy', - user: req.user - }); - }); - - app.get('/press', function (req, res) { - res.redirect('https://github.com/TeamHypersomnia/PressKit/blob/main/README.md#intro'); - }); - - app.get('/contact', function (req, res) { - res.render('contact', { - page: 'Contact', - user: req.user - }); - }); - - app.get('/profile', onlyUserAccess, function (req, res) { - res.render('profile', { - page: 'Profile', - user: req.user - }); - }); - - app.get('/admin', onlyAdminAccess, function (req, res) { - res.redirect('/admin/system'); - }); - - app.get('/admin/system', onlyAdminAccess, function (req, res) { - res.render('admin/system', { - page: 'System', - user: req.user, - system: system.getData() - }); - }); - - app.get('/admin/visitors', onlyAdminAccess, function (req, res) { - res.render('admin/visitors', { - page: 'Visitors', - user: req.user - }); - }); - - app.get('/admin/users', onlyAdminAccess, function (req, res) { - res.render('admin/users', { - page: 'Users', - user: req.user - }); - }); - - app.get('/admin/creators', onlyAdminAccess, function (req, res) { - res.render('admin/creators', { - page: 'Creators', - user: req.user - }); - }); - - app.get('/admin/settings', onlyAdminAccess, function (req, res) { - res.render('admin/settings', { - page: 'Settings', - user: req.user, - alert: app.locals.alert - }); - }); - - app.post('/admin/settings', onlyAdminAccess, function (req, res) { - const alert = req.body.alert; - app.locals.alert = alert; - const obj = settings.load(); - obj.alert = alert; - settings.save(obj); - res.redirect('/admin/settings'); - }); - - app.post('/upload', upload.single('upload'), (req, res) => { - res.setHeader('Content-Type', 'application/json; charset=utf-8'); - - const apikey = req.body.apikey; - const arena = req.body.arena; - const filename = req.body.filename; - if (!apikey || !arena || !filename) { - return res.status(400).json({ - error: 'Missing required parameters' - }); - } - - const d = fs.readFileSync(__dirname + '/../private/authorized_mappers.json', { - encoding: 'utf8', - flag: 'r' - }); - const authorizedMappers = JSON.parse(d); - if (!authorizedMappers[apikey]) { - return res.status(400).json({ - error: 'You are not authorized to upload maps' - }); - } - - let allowCreatingNew = false; - if (authorizedMappers[apikey].allow_creating_new === 1) { - allowCreatingNew = true; - } - - const arenas = authorizedMappers[apikey]?.maps || []; - if (!allowCreatingNew && !arenas.includes(arena)) { - return res.status(400).json({ - error: 'You are not authorized to create new maps' - }); - } else if (allowCreatingNew) { - const owner = Object.keys(authorizedMappers).find( - (key) => authorizedMappers[key].maps && authorizedMappers[key].maps.includes(arena) - ); - if (owner && owner !== apikey) { - return res.status(400).json({ - error: 'You are not authorized to upload a map with this name' - }); - } - } - - const allowed = ['json', 'png', 'jpg', 'gif', 'ogg', 'wav']; - const ext = path.extname(req.file.originalname).slice(1); - const ext2 = path.extname(filename).slice(1); - if (!allowed.includes(ext) || !allowed.includes(ext2)) { - return res.status(400).json({ - error: 'You are not allowed to upload this file type' - }); - } - - // Avoid directory traversal attacks - const sanitizedFilename = filename.replace(/\\/g, '/'); - const pathComponents = sanitizedFilename.split('/'); - for (const component of pathComponents) { - if (component === '.' || component === '..') { - return res.status(400).json({ - error: 'Parameter filename is invalid' - }); - } - } - - const filePath = `public/arenas/${arena}/${sanitizedFilename}`; - writeFileWithDirectory(filePath, req.file.buffer); - console.log(`File saved to ${filePath}`); - - return res.json({ - success: 'The file has been uploaded' - }); - }); - - app.post('/logout', function (req, res, next) { - req.logout(function (err) { - if (err) { - return next(err); - } - res.redirect('/'); - }); - }); - - app.get('/auth/steam', - passport.authenticate('steam', { - failureRedirect: '/' - }), - function (req, res) { - res.redirect('/'); - }); - - app.get('/auth/steam/return', - passport.authenticate('steam', { - failureRedirect: '/' - }), - function (req, res) { - res.redirect('/'); - }); - - app.use((req, res, next) => { - res.status(404).render('404', { - page: 'Not Found', - user: req.user - }); - }); -} \ No newline at end of file diff --git a/src/servers.js b/src/servers.js index abfd170..1495978 100644 --- a/src/servers.js +++ b/src/servers.js @@ -1,30 +1,34 @@ +const express = require('express'); +const router = express.Router(); const axios = require('axios'); const moment = require('moment'); let servers = []; -module.exports = { - init: function () { - function fetchServers() { - axios.get('http://hypersomnia.xyz:8420/server_list_json') - .then(response => { - servers = response.data; - }) - .catch(error => { - console.error(`Error fetching servers: ${error.message}`); - }); - } - fetchServers(); - setInterval(fetchServers, 60000); // 1 minute - }, - - getServers: function () { - const formattedData = servers.map(v => { - return { - ...v, - time_hosted_ago: moment(v.time_hosted * 1000).fromNow(), - time_last_heartbeat_ago: moment(v.time_last_heartbeat * 1000).fromNow(), - }; +function fetchServers() { + axios.get('http://hypersomnia.xyz:8420/server_list_json') + .then(response => { + servers = response.data; + }) + .catch(error => { + console.error(error.message); }); - return formattedData; - } -}; +} +fetchServers(); +setInterval(fetchServers, 60000); // 1 min + +router.get('/', function (req, res) { + const data = servers.map(v => { + return { + ...v, + time_hosted_ago: moment(v.time_hosted * 1000).fromNow(), + time_last_heartbeat_ago: moment(v.time_last_heartbeat * 1000).fromNow() + }; + }); + res.render('servers', { + page: 'Servers', + user: req.user, + servers: data + }); +}); + +module.exports = router; diff --git a/src/upload.js b/src/upload.js new file mode 100644 index 0000000..1be0a4e --- /dev/null +++ b/src/upload.js @@ -0,0 +1,89 @@ +const express = require('express'); +const router = express.Router(); +const path = require('path'); +const multer = require('multer'); +const storage = multer.memoryStorage(); +const upload = multer({ storage: storage }); + +function writeFileWithDirectory(filePath, content) { + const directory = path.dirname(filePath); + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory, { recursive: true }); + } + fs.writeFileSync(filePath, content); +} + +router.post('/', upload.single('upload'), (req, res) => { + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + + const apikey = req.body.apikey; + const arena = req.body.arena; + const filename = req.body.filename; + if (!apikey || !arena || !filename) { + return res.status(400).json({ + error: 'Missing required parameters' + }); + } + + const d = fs.readFileSync(__dirname + '/../private/authorized_mappers.json', { + encoding: 'utf8', + flag: 'r' + }); + const authorizedMappers = JSON.parse(d); + if (!authorizedMappers[apikey]) { + return res.status(400).json({ + error: 'You are not authorized to upload maps' + }); + } + + let allowCreatingNew = false; + if (authorizedMappers[apikey].allow_creating_new === 1) { + allowCreatingNew = true; + } + + const arenas = authorizedMappers[apikey]?.maps || []; + if (!allowCreatingNew && !arenas.includes(arena)) { + return res.status(400).json({ + error: 'You are not authorized to create new maps' + }); + } else if (allowCreatingNew) { + const owner = Object.keys(authorizedMappers).find( + (key) => authorizedMappers[key].maps && authorizedMappers[key].maps.includes(arena) + ); + if (owner && owner !== apikey) { + return res.status(400).json({ + error: 'You are not authorized to upload a map with this name' + }); + } + } + + const allowed = ['json', 'png', 'jpg', 'gif', 'ogg', 'wav']; + const ext = path.extname(req.file.originalname).slice(1); + const ext2 = path.extname(filename).slice(1); + if (!allowed.includes(ext) || !allowed.includes(ext2)) { + return res.status(400).json({ + error: 'You are not allowed to upload this file type' + }); + } + + // Avoid directory traversal attacks + const sanitizedFilename = filename.replace(/\\/g, '/'); + const pathComponents = sanitizedFilename.split('/'); + for (const component of pathComponents) { + if (component === '.' || component === '..') { + return res.status(400).json({ + error: 'Parameter filename is invalid' + }); + } + } + + const filePath = `public/arenas/${arena}/${sanitizedFilename}`; + writeFileWithDirectory(filePath, req.file.buffer); + console.log(`File saved to ${filePath}`); + + return res.json({ + success: 'The file has been uploaded' + }); +}); + +module.exports = router; diff --git a/src/weapons.js b/src/weapons.js index 2be2c3c..130e320 100644 --- a/src/weapons.js +++ b/src/weapons.js @@ -1,35 +1,25 @@ +const express = require('express'); +const router = express.Router(); const fs = require('fs'); -module.exports = { - getFirearms: function () { - const d = fs.readFileSync(__dirname + '/../public/weapons/firearms.json', { - encoding: 'utf8', - flag: 'r' - }); - return JSON.parse(d); - }, +function getWeapons(file) { + const path = `${__dirname}/../public/weapons/${file}.json`; + const data = fs.readFileSync(path, { + encoding: 'utf8', + flag: 'r' + }); + return JSON.parse(data); +} - getMelees: function () { - const d = fs.readFileSync(__dirname + '/../public/weapons/melees.json', { - encoding: 'utf8', - flag: 'r' - }); - return JSON.parse(d); - }, +router.get('/', function (req, res) { + res.render('weapons', { + page: 'Weapons', + user: req.user, + firearms: getWeapons('firearms'), + melees: getWeapons('melees'), + explosives: getWeapons('explosives'), + spells: getWeapons('spells') + }); +}); - getExplosives: function () { - const d = fs.readFileSync(__dirname + '/../public/weapons/explosives.json', { - encoding: 'utf8', - flag: 'r' - }); - return JSON.parse(d); - }, - - getSpells: function () { - const d = fs.readFileSync(__dirname + '/../public/weapons/spells.json', { - encoding: 'utf8', - flag: 'r' - }); - return JSON.parse(d); - } -}; +module.exports = router; diff --git a/views/admin/creator.ejs b/views/admin/creator.ejs new file mode 100644 index 0000000..5c493a8 --- /dev/null +++ b/views/admin/creator.ejs @@ -0,0 +1,29 @@ +<%- include('../overall_header'); %> +<%- include('nav'); %> + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ +<%- include('../overall_footer'); %> diff --git a/views/admin/creators.ejs b/views/admin/creators.ejs index 157e426..f3ab56a 100644 --- a/views/admin/creators.ejs +++ b/views/admin/creators.ejs @@ -1,6 +1,38 @@ <%- include('../overall_header'); %> <%- include('nav'); %> -

TBA

+
+
+ + +
+
+ +
+
+ +
+ + + + + + + + + + + <% Object.keys(creators).forEach((k) => { %> + <% const v = creators[k]; %> + + + + + + + <% }); %> + +
ShorthandAllow creating newArenasAPI Key
<%= v.shorthand %><%= v.allow_creating_new ? 'Yes' : 'No' %><%= v.maps.length %><%= k %>
+
<%- include('../overall_footer'); %> diff --git a/views/admin/settings.ejs b/views/admin/settings.ejs index a5e6984..ef8e68c 100644 --- a/views/admin/settings.ejs +++ b/views/admin/settings.ejs @@ -4,7 +4,7 @@
- + Leave empty to disable.
diff --git a/views/admin/system.ejs b/views/admin/system.ejs index 1643d25..f6ea36e 100644 --- a/views/admin/system.ejs +++ b/views/admin/system.ejs @@ -7,38 +7,38 @@ - - + + - + - + - + - + - + - + - +
KeyValueKeyValue
Machine type<%= system.machine %><%= machine %>
Operating system<%= system.type %> <%= system.release %><%= type %> <%= release %>
Host name<%= system.hostname %><%= hostname %>
Uptime<%= system.uptime %><%= uptime %>
Load avg<%= system.loadavg.map(v => v.toFixed(2)).join(' ') %><%= loadavg.map(v => v.toFixed(2)).join(' ') %>
RAM usage<%= Math.floor(system.usedmem / (1024 * 1024)) %>/<%= Math.floor(system.totalmem / (1024 * 1024)) %> MB<%= Math.floor(usedmem / (1024 * 1024)) %>/<%= Math.floor(totalmem / (1024 * 1024)) %> MB
Node version<%= system.nodever %><%= node %>
diff --git a/views/admin/users.ejs b/views/admin/users.ejs index 157e426..021a83d 100644 --- a/views/admin/users.ejs +++ b/views/admin/users.ejs @@ -1,6 +1,28 @@ <%- include('../overall_header'); %> <%- include('nav'); %> -

TBA

+
+ + + + + + + + + + + <% Object.keys(users).forEach((k) => { %> + <% const v = users[k]; %> + + + + + + + <% }); %> + +
Steam IDDisplay nameIP addressLast login
<%= k %><%= v.name %><%= v.ip %><%= v.lastLogin %>
+
<%- include('../overall_footer'); %> diff --git a/views/overall_header.ejs b/views/overall_header.ejs index d8e8b5a..00b335a 100644 --- a/views/overall_header.ejs +++ b/views/overall_header.ejs @@ -3,12 +3,12 @@ - + - + - + <% if (page === 'Index') { %>Free Multiplayer Top-Down Shooter<% } else { %><%= page %><% } %> - Hypersomnia