diff --git a/README.md b/README.md index 7e57dce..bb79256 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ through the CMS for creating/updating data. - [ ] create data - [ ] update data - [ ] websocket - - [ ] create ws server - - [ ] how do you wanna authenticate? -- [ ] authentication - - [ ] create collection - - [ ] cache tokens on startup - - [ ] allow create, update only on authentication \ No newline at end of file + - [x] create ws server + - [ ] handle events +- [x] authentication + - [x] create collection + - [x] figure out auth +- [ ] casters stuff \ No newline at end of file diff --git a/env.example b/env.example index 5a942fb..ac95e5f 100644 --- a/env.example +++ b/env.example @@ -1,3 +1,4 @@ NODE_ENV=development PORT=5000 -CMS_TOKEN= \ No newline at end of file +CMS_TOKEN= +MONGO_URI= \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index de844ab..6419b32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "helmet": "^4.1.0", "mongoose": "^5.11.19", "morgan": "^1.10.0", + "uuid": "^8.3.2", "ws": "^7.4.4" }, "devDependencies": { @@ -2068,6 +2069,14 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -3736,6 +3745,11 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 454f223..f15fec3 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "helmet": "^4.1.0", "mongoose": "^5.11.19", "morgan": "^1.10.0", + "uuid": "^8.3.2", "ws": "^7.4.4" }, "devDependencies": { diff --git a/src/api/index.js b/src/api/index.js index 1b2aa2f..d786458 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -1,5 +1,8 @@ const express = require('express') const axios = require('axios') +const { v4 } = require('uuid') + +const { push } = require('../uuids') const router = express.Router() @@ -16,12 +19,14 @@ router.post('/', async (req, res) => { }, }) if (r.data.entries.length === 1) { + const id = v4(); + push(id) res.json({ - message: r.data.entries.length + session: id, }) } else { res.status(403).json({ - message: r.data.entries, + message: 'Forbidden', }) } } catch(e) { diff --git a/src/index.js b/src/index.js index c7c795a..9b7e7d7 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,17 @@ +const mongoose = require('mongoose') const WebSocket = require('ws') const url = require('url') const app = require('./app') +const { get } = require('./uuids') +const handler = require('./ws/handler') + +mongoose.connect(process.env.MONGO_URI, { + useNewUrlParser: true, + useUnifiedTopology: true, +}) + +const db = mongoose.connection +db.once('open', () => console.log('Connected to mongodb instance!')) const port = process.env.PORT || 5000; @@ -8,11 +19,9 @@ wss = new WebSocket.Server({ noServer: true, }) -wss.on('connection', (ws) => { - ws.on('message', (msg) => { - console.log(`received ${msg}`); - }) - ws.send('something'); +wss.on('connection', (ws, req) => { + const id = req.id + handler(ws, id) }) const server = app.listen(port, () => { @@ -20,9 +29,11 @@ const server = app.listen(port, () => { }) server.on('upgrade', (req, socket, head) => { - const pathname = url.parse(req.url).pathname - if (pathname === '/ws') { + const pathname = url.parse(req.url).pathname.split('/') + const token = pathname[2] + if (pathname[1] === 'ws' && get(token)) { wss.handleUpgrade(req, socket, head, (socket) => { + req.id = token wss.emit('connection', socket, req); }) } diff --git a/src/models/casters.js b/src/models/casters.js new file mode 100644 index 0000000..112fa9d --- /dev/null +++ b/src/models/casters.js @@ -0,0 +1,24 @@ +const { Schema, model } = require('mongoose') + +const casters = new Schema({ + username: { + type: String, + required: true, + }, + name: { + type: String, + required: true, + }, + twitter: { + type: String, + required: true, + }, + image: { + type: String, + required: true, + }, +}); + +const Caster = model('Casters', casters) + +module.exports = Caster \ No newline at end of file diff --git a/src/models/events.js b/src/models/events.js index 5db3ccc..6a3ae5c 100644 --- a/src/models/events.js +++ b/src/models/events.js @@ -1,10 +1,13 @@ -import { Schema, model } from 'mongoose'; +const mongoose = require('mongoose'); -const events = new Schema({ +const events = new mongoose.Schema({ name: { type: String, required: true, + unique: true, }, }); -export default model('Events', events); \ No newline at end of file +const Event = mongoose.model('Events', events) + +module.exports = Event \ No newline at end of file diff --git a/src/models/hosts.js b/src/models/hosts.js new file mode 100644 index 0000000..72a05a0 --- /dev/null +++ b/src/models/hosts.js @@ -0,0 +1,24 @@ +const { Schema, model } = require('mongoose') + +const hosts = new Schema({ + username: { + type: String, + required: true, + }, + name: { + type: String, + required: true, + }, + twitter: { + type: String, + required: true, + }, + image: { + type: String, + required: true, + }, +}); + +const Host = model('Hosts', hosts) + +module.exports = Host \ No newline at end of file diff --git a/src/models/matches.js b/src/models/matches.js index c33fa8a..0fcb5f1 100644 --- a/src/models/matches.js +++ b/src/models/matches.js @@ -1,4 +1,4 @@ -import { Schema, model } from 'mongoose'; +const { Schema, model } = require('mongoose') const gameScores = new Schema({ orange: { @@ -9,16 +9,16 @@ const gameScores = new Schema({ type: Number, required: true, }, -}); +}) const matches = new Schema({ orange: { - type: Schema.types.ObjectId, + type: Schema.Types.ObjectId, refs: 'Rosters', required: true, }, blue: { - type: Schema.types.ObjectId, + type: Schema.Types.ObjectId, refs: 'Rosters', required: true, }, @@ -52,6 +52,8 @@ const matches = new Schema({ type: [gameScores], required: true, }, -}); +}) -export default model('Matches', matches); \ No newline at end of file +const Match = model('Matches', matches) + +module.exports = Match \ No newline at end of file diff --git a/src/models/rosters.js b/src/models/rosters.js index 55124ee..63c7210 100644 --- a/src/models/rosters.js +++ b/src/models/rosters.js @@ -1,4 +1,4 @@ -import { Schema, model } from 'mongoose'; +const { Schema, model } = require('mongoose') const stats = new Schema({ goals: { @@ -21,7 +21,7 @@ const stats = new Schema({ type: Number, required: true, }, -}); +}) const players = new Schema({ name: { @@ -36,11 +36,11 @@ const players = new Schema({ type: [stats], required: true, }, -}); +}) const rosters = new Schema({ event: { - type: Schema.types.ObjectId, + type: Schema.Types.ObjectId, ref: 'Events', required: true, }, @@ -56,6 +56,8 @@ const rosters = new Schema({ type: [players], required: true, }, -}); +}) -export default model('Rosters', rosters); \ No newline at end of file +const Roster = model('Rosters', rosters) + +module.exports = Roster \ No newline at end of file diff --git a/src/models/streams.js b/src/models/streams.js index fb9ed0f..6bbd74d 100644 --- a/src/models/streams.js +++ b/src/models/streams.js @@ -1,4 +1,4 @@ -import { Schema, model } from 'mongoose'; +const { Schema, model } = require('mongoose') const streams = new Schema({ name: { @@ -6,15 +6,17 @@ const streams = new Schema({ required: true, }, event: { - type: Schema.types.ObjectId, + type: Schema.Types.ObjectId, refs: 'Events', requried: true, }, matches: { - type: [Schema.types.ObjectId], + type: [Schema.Types.ObjectId], refs: 'matches', requried: true, }, -}); +}) -export default model('Streams', streams); \ No newline at end of file +const Stream = model('Streams', streams) + +module.exports = Stream \ No newline at end of file diff --git a/src/uuids.js b/src/uuids.js new file mode 100644 index 0000000..2bd224c --- /dev/null +++ b/src/uuids.js @@ -0,0 +1,21 @@ +let uuids = []; + +const get = (id) => { + if (uuids.indexOf(id) !== -1) + return true; + return false; +} + +const push = (id) => { + uuids.push(id) +} + +const pop = (id) => { + uuids = uuids.filter((x) => x !== id) +} + +module.exports = { + get, + push, + pop +}; \ No newline at end of file diff --git a/src/ws/channels.js b/src/ws/channels.js new file mode 100644 index 0000000..5fea53a --- /dev/null +++ b/src/ws/channels.js @@ -0,0 +1,29 @@ +const channels = [ + 'rosters', + 'matches', + 'streams', + 'events', + 'casters', + 'hosts', +]; + +const events = [ + 'create', + 'read', + 'update', + 'delete', +]; + +const channelEvents = []; + +channels.forEach((c) => { + events.forEach((e) => { + channelEvents.push(`${c}:${e}`); + }); +}) + +module.exports = { + channelEvents, + channels, + events, +}; \ No newline at end of file diff --git a/src/ws/crud.js b/src/ws/crud.js new file mode 100644 index 0000000..d8becd2 --- /dev/null +++ b/src/ws/crud.js @@ -0,0 +1,171 @@ +const events = require('../models/events') +const rosters = require('../models/rosters') +const matches = require('../models/matches') +const streams = require('../models/streams') +const casters = require('../models/casters') +const hosts = require('../models/hosts') + +const eventFns = { + getAll: async () => { + return await events.find().exec() + }, + getById: async (id) => { + return await events.findById(id).exec() + }, + update: async(id, data) => { + return await events.findByIdAndUpdate(id, data).exec() + }, + delete: async (id) => { + return await events.findByIdAndDelete(id).exec() + }, + create: async (data) => { + try { + const ev = new events(data) + ev.save((err) => { + if (err) + throw err; + }) + } catch (e) { + throw e; + } + } +} + +const rosterFns = { + getAll: async () => { + return await rosters.find().exec() + }, + getById: async (id) => { + return await rosters.findById(id).exec() + }, + update: async(id, data) => { + return await rosters.findByIdAndUpdate(id, data).exec() + }, + delete: async (id) => { + return await rosters.findByIdAndDelete(id).exec() + }, + create: async (data) => { + try { + const ev = new rosters(data) + ev.save((err) => { + if (err) + throw err; + }) + } catch (e) { + throw e; + } + } +} + +const matchFns = { + getAll: async () => { + return await matches.find().exec() + }, + getById: async (id) => { + return await matches.findById(id).exec() + }, + update: async(id, data) => { + return await matches.findByIdAndUpdate(id, data).exec() + }, + delete: async (id) => { + return await matches.findByIdAndDelete(id).exec() + }, + create: async (data) => { + try { + const ev = new matches(data) + ev.save((err) => { + if (err) + throw err; + }) + } catch (e) { + throw e; + } + } +} + +const streamFns = { + getAll: async () => { + return await streams.find().exec() + }, + getById: async (id) => { + return await streams.findById(id).exec() + }, + update: async(id, data) => { + return await streams.findByIdAndUpdate(id, data).exec() + }, + delete: async (id) => { + return await streams.findByIdAndDelete(id).exec() + }, + create: async (data) => { + try { + const ev = new streams(data) + ev.save((err) => { + if (err) + throw err; + }) + } catch (e) { + throw e; + } + } +} + +const casterFns = { + getAll: async () => { + return await casters.find().exec() + }, + getById: async (id) => { + return await casters.findById(id).exec() + }, + update: async(id, data) => { + return await casters.findByIdAndUpdate(id, data).exec() + }, + delete: async (id) => { + return await casters.findByIdAndDelete(id).exec() + }, + create: async (data) => { + try { + const ev = new casters(data) + ev.save((err) => { + if (err) + throw err; + }) + } catch (e) { + throw e; + } + } +} + +const hostFns = { + getAll: async () => { + return await hosts.find().exec() + }, + getById: async (id) => { + return await hosts.findById(id).exec() + }, + update: async(id, data) => { + return await hosts.findByIdAndUpdate(id, data).exec() + }, + delete: async (id) => { + return await hosts.findByIdAndDelete(id).exec() + }, + create: async (data) => { + try { + const ev = new hosts(data) + ev.save((err) => { + if (err) + throw err; + }) + } catch (e) { + throw e; + } + } +} + +module.exports = { + 'events': eventFns, + 'rosters': rosterFns, + 'matches': matchFns, + 'streams': streamFns, + 'casters': casterFns, + 'hosts': hostFns, +} \ No newline at end of file diff --git a/src/ws/handler.js b/src/ws/handler.js new file mode 100644 index 0000000..339507b --- /dev/null +++ b/src/ws/handler.js @@ -0,0 +1,65 @@ +const { pop } = require('../uuids') +const { channelEvents } = require('./channels') +const crud = require('./crud') + +const connections = {}; + +const handler = (ws, id) => { + connections[id] = { + connection: ws, + events: [], + } + + ws.send(JSON.stringify({ + event: 'info', + data: 'Welcome to APL Nuke v1.0.0!' + })) + + ws.on('message', (msg) => handleMsg(msg, id)) + ws.on('close', () => { + delete connections[id] + pop(id) + }) +} + +const handleMsg = async (msg, id) => { + try { + const d = JSON.parse(msg) + if (d.hasOwnProperty('subscribe') && channelEvents.indexOf(d.subscribe) !== -1) { + const channel = d.subscribe.split(':')[0] + connections[id].events.push(d.subscribe) + connections[id].connection.send(JSON.stringify(await crud[channel].getAll())) + } else if (d.hasOwnProperty('event') && channelEvents.indexOf(d.event) !== -1) { + const ev = d.event.split(':') + const channel = ev[0] + const event = ev[1] + console.log(`received event for ${channel} with data %s`, d.data) + switch (event) { + case 'create': + await crud[channel].create(d.data) + fanoutMsg(channel, await crud[channel].getAll()) + break; + case 'update': + await crud[channel].update(d.data.id, d.data.data) + fanoutMsg(channel, await crud[channel].getAll()) + break; + case 'delete': + await crud[channel].delete(d.data.id) + fanoutMsg(channel, await crud[channel].getAll()) + break; + } + } + } catch (e) { + console.log(e) + } +} + +const fanoutMsg = (channel, data) => { + Object.keys(connections).forEach((k) => { + if (connections[k].events.indexOf(`${channel}:read`) !== -1) { + connections[k].connection.send(JSON.stringify(data)) + } + }) +} + +module.exports = handler \ No newline at end of file diff --git a/src/ws/server.js b/src/ws/server.js deleted file mode 100644 index e7159de..0000000 --- a/src/ws/server.js +++ /dev/null @@ -1,5 +0,0 @@ -const WebSocket = require('ws') - -let wss = null - -module.exports = wss; \ No newline at end of file