mirror of
https://github.com/roleypoly/roleypoly-v1.git
synced 2025-06-15 01:49:10 +00:00
first sync
This commit is contained in:
parent
5d6f382c8a
commit
a4acc441ea
52 changed files with 28315 additions and 0 deletions
21
Server/.editorconfig
Normal file
21
Server/.editorconfig
Normal file
|
@ -0,0 +1,21 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# Change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# We recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
6
Server/.env
Normal file
6
Server/.env
Normal file
|
@ -0,0 +1,6 @@
|
|||
DB_URL=postgres://roleypoly:19216801@localhost:5432/roleypoly
|
||||
DEBUG=true
|
||||
NODE_ENV=development
|
||||
DISCORD_CLIENT_ID=363916474127220739
|
||||
DISCORD_CLIENT_SECRET=PZd3u4Rkok200o-5UcG3hnB8MVdALPz5
|
||||
APP_URL=http://localhost:6769
|
2
Server/.gitignore
vendored
Normal file
2
Server/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
.data
|
53
Server/Roleypoly.js
Normal file
53
Server/Roleypoly.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
const log = new (require('./logger'))('World')
|
||||
const Sequelize = require('sequelize')
|
||||
const fetchModels = require('./models')
|
||||
const fetchApis = require('./api')
|
||||
|
||||
class Roleypoly {
|
||||
constructor (router, io, app) {
|
||||
this.router = router
|
||||
this.io = io
|
||||
this.ctx = {}
|
||||
|
||||
this.ctx.config = {
|
||||
appUrl: process.env.APP_URL
|
||||
}
|
||||
|
||||
this.ctx.io = io
|
||||
this.__app = app
|
||||
|
||||
if (log.debugOn) log.warn('debug mode is on')
|
||||
|
||||
this.__initialized = this._mountServices()
|
||||
}
|
||||
|
||||
async awaitServices () {
|
||||
await this.__initialized
|
||||
}
|
||||
|
||||
async _mountServices () {
|
||||
const sequelize = new Sequelize(process.env.DB_URL, { logging: log.sql.bind(log, log) })
|
||||
this.ctx.sql = sequelize
|
||||
this.M = fetchModels(sequelize)
|
||||
this.ctx.M = this.M
|
||||
await sequelize.sync()
|
||||
|
||||
// this.ctx.redis = new (require('ioredis'))({
|
||||
// port: process.env.REDIS_PORT || '6379',
|
||||
// host: process.env.REDIS_HOST || 'localhost',
|
||||
// parser: 'hiredis',
|
||||
// dropBufferSupport: true,
|
||||
// enableReadyCheck: true,
|
||||
// enableOfflineQueue: true
|
||||
// })
|
||||
|
||||
this.ctx.discord = new (require('./services/discord'))(this.ctx)
|
||||
}
|
||||
|
||||
async mountRoutes () {
|
||||
fetchApis(this.router, this.ctx)
|
||||
this.__app.use(this.router.middleware())
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Roleypoly
|
3
Server/api/auth.js
Normal file
3
Server/api/auth.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = R => {
|
||||
|
||||
}
|
20
Server/api/index.js
Normal file
20
Server/api/index.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
const log = new (require('../logger'))('api/index')
|
||||
const glob = require('glob')
|
||||
|
||||
module.exports = async (router, ctx) => {
|
||||
const apis = glob.sync('./api/**/!(index).js')
|
||||
log.debug('found apis', apis)
|
||||
|
||||
for (let a of apis) {
|
||||
if (a.endsWith('_test.js') !== null && process.env.NODE_ENV !== 'development') {
|
||||
log.debug(`skipping ${a}`)
|
||||
continue
|
||||
}
|
||||
log.debug(`mounting ${a}`)
|
||||
try {
|
||||
require(a.replace('api/', ''))(router, ctx)
|
||||
} catch (e) {
|
||||
log.error(`couldn't mount ${a}`, e)
|
||||
}
|
||||
}
|
||||
}
|
6
Server/api/servers.js
Normal file
6
Server/api/servers.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
module.exports = (R, $) => {
|
||||
R.get('/api/servers', (ctx) => {
|
||||
ctx.body = 'hi'
|
||||
console.log($)
|
||||
})
|
||||
}
|
44
Server/index.js
Normal file
44
Server/index.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
require('dotenv').config({silent: true})
|
||||
const log = new (require('./logger'))('index')
|
||||
|
||||
const http = require('http')
|
||||
const Koa = require('koa')
|
||||
const app = new Koa()
|
||||
const _io = require('socket.io')
|
||||
const router = require('koa-better-router')().loadMethods()
|
||||
const Roleypoly = require('./Roleypoly')
|
||||
|
||||
// Create the server and socket.io server
|
||||
const server = http.createServer(app.callback())
|
||||
const io = _io(server, { transports: ['websocket'], path: '/api/socket.io', wsEngine: 'uws' })
|
||||
|
||||
const M = new Roleypoly(router, io, app) // eslint-disable-line no-unused-vars
|
||||
|
||||
async function start () {
|
||||
// body parser
|
||||
const bodyParser = require('koa-bodyparser')
|
||||
app.use(bodyParser({ types: ['json'] }))
|
||||
|
||||
|
||||
// Request logger
|
||||
app.use(async (ctx, next) => {
|
||||
let timeStart = new Date()
|
||||
await next()
|
||||
let timeElapsed = new Date() - timeStart
|
||||
|
||||
log.request(`${ctx.status} ${ctx.method} ${ctx.url} - ${ctx.ip} - took ${timeElapsed}ms`)
|
||||
return null
|
||||
})
|
||||
|
||||
// Construct the Roleypoly!
|
||||
await M.mountRoutes()
|
||||
|
||||
// Start it!
|
||||
await M.awaitServices()
|
||||
log.info(`starting HTTP server on ${process.env.APP_PORT || 6769}`)
|
||||
server.listen(process.env.APP_PORT || 6769)
|
||||
}
|
||||
|
||||
start().catch(e => {
|
||||
console.trace(e)
|
||||
})
|
56
Server/logger.js
Normal file
56
Server/logger.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
const chalk = require('chalk')
|
||||
// const { debug } = require('yargs').argv
|
||||
// process.env.DEBUG = process.env.DEBUG || debug
|
||||
// logger template//
|
||||
// const log = new (require('../logger'))('server/thing')
|
||||
|
||||
class Logger {
|
||||
constructor (name, debugOverride = false) {
|
||||
this.name = name
|
||||
this.debugOn = (process.env.DEBUG === 'true' || process.env.DEBUG === '*') || debugOverride
|
||||
}
|
||||
|
||||
fatal (text, ...data) {
|
||||
this.error(text, data)
|
||||
|
||||
if (typeof data[data.length - 1] === 'number') {
|
||||
process.exit(data[data.length - 1])
|
||||
}
|
||||
|
||||
throw text
|
||||
}
|
||||
|
||||
error (text, ...data) {
|
||||
console.error(chalk.red.bold(`ERR ${this.name}:`) + `\n ${text}`, data)
|
||||
}
|
||||
|
||||
warn (text, ...data) {
|
||||
console.warn(chalk.yellow.bold(`WARN ${this.name}:`) + `\n ${text}`, data)
|
||||
}
|
||||
|
||||
notice (text, ...data) {
|
||||
console.log(chalk.cyan.bold(`NOTICE ${this.name}:`) + `\n ${text}`, data)
|
||||
}
|
||||
|
||||
info (text, ...data) {
|
||||
console.info(chalk.blue.bold(`INFO ${this.name}:`) + `\n ${text}`, data)
|
||||
}
|
||||
|
||||
request (text, ...data) {
|
||||
console.info(chalk.green.bold(`HTTP ${this.name}:`) + `\n ${text}`)
|
||||
}
|
||||
|
||||
debug (text, ...data) {
|
||||
if (this.debugOn) {
|
||||
console.log(chalk.gray.bold(`DEBUG ${this.name}:`) + `\n ${text}`, data)
|
||||
}
|
||||
}
|
||||
|
||||
sql (logger, ...data) {
|
||||
if (logger.debugOn) {
|
||||
console.log(chalk.bold('DEBUG SQL:\n '), data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Logger
|
14
Server/models/Server.js
Normal file
14
Server/models/Server.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
module.exports = (sql, DataTypes) => {
|
||||
return sql.define('server', {
|
||||
id: { // discord snowflake
|
||||
type: DataTypes.TEXT,
|
||||
primaryKey: true
|
||||
},
|
||||
categories: {
|
||||
type: DataTypes.JSON
|
||||
},
|
||||
note: {
|
||||
type: DataTypes.TEXT
|
||||
}
|
||||
})
|
||||
}
|
7
Server/models/Session.js
Normal file
7
Server/models/Session.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
module.exports = (sequelize, DataTypes) => {
|
||||
return sequelize.define('session', {
|
||||
id: { type: DataTypes.TEXT, primaryKey: true },
|
||||
maxAge: DataTypes.BIGINT,
|
||||
data: DataTypes.JSONB
|
||||
})
|
||||
}
|
37
Server/models/index.js
Normal file
37
Server/models/index.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
const log = new (require('../logger'))('models/index')
|
||||
const glob = require('glob')
|
||||
const path = require('path')
|
||||
const util = require('../util/model-methods')
|
||||
|
||||
module.exports = (sql) => {
|
||||
const models = {}
|
||||
const modelFiles = glob.sync('./models/**/!(index).js')
|
||||
log.debug('found models', modelFiles)
|
||||
|
||||
modelFiles.forEach((v) => {
|
||||
let name = path.basename(v).replace('.js', '')
|
||||
if (v === './models/index.js') {
|
||||
log.debug('index.js hit, skipped')
|
||||
return
|
||||
}
|
||||
try {
|
||||
log.debug('importing..', v.replace('models/', ''))
|
||||
let model = sql.import(v.replace('models/', ''))
|
||||
models[name] = model
|
||||
} catch (err) {
|
||||
log.fatal('error importing model ' + v, err)
|
||||
process.exit(-1)
|
||||
}
|
||||
})
|
||||
|
||||
Object.keys(models).forEach((v) => {
|
||||
if (models[v].hasOwnProperty('__associations')) {
|
||||
models[v].__associations(models)
|
||||
}
|
||||
if (models[v].hasOwnProperty('__instanceMethods')) {
|
||||
models[v].__instanceMethods(models[v])
|
||||
}
|
||||
})
|
||||
|
||||
return models
|
||||
}
|
4669
Server/package-lock.json
generated
Normal file
4669
Server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
40
Server/package.json
Normal file
40
Server/package.json
Normal file
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "standard && node index.js",
|
||||
"fix": "standard --fix",
|
||||
"dev": "pm2 start index.js --watch",
|
||||
"pm2": "pm2"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^2.3.0",
|
||||
"commander": "^2.12.2",
|
||||
"discord.js": "^11.2.1",
|
||||
"dotenv": "^4.0.0",
|
||||
"erlpack": "github:discordapp/erlpack",
|
||||
"eventemitter3": "^3.0.0",
|
||||
"glob": "^7.1.2",
|
||||
"immutable": "^3.8.2",
|
||||
"inquirer": "^4.0.1",
|
||||
"koa": "^2.4.1",
|
||||
"koa-better-router": "^2.1.1",
|
||||
"koa-bodyparser": "^4.2.0",
|
||||
"koa-passport": "^4.0.1",
|
||||
"koa-session": "^5.5.1",
|
||||
"ksuid": "^0.4.0",
|
||||
"passport-discord": "^0.1.3",
|
||||
"passport-oauth2-refresh": "^1.0.0",
|
||||
"pg": "^7.4.0",
|
||||
"pg-hstore": "^2.3.2",
|
||||
"pm2": "^2.8.0",
|
||||
"sequelize": "^4.25.1",
|
||||
"socket.io": "^2.0.4",
|
||||
"standard": "^10.0.3",
|
||||
"superagent": "^3.8.1",
|
||||
"uuid": "^3.1.0",
|
||||
"uws": "^9.14.0",
|
||||
"yargs": "^10.0.3"
|
||||
}
|
||||
}
|
10
Server/services/Service.js
Normal file
10
Server/services/Service.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
const Logger = require('../logger')
|
||||
|
||||
class Service {
|
||||
constructor (ctx) {
|
||||
this.ctx = ctx
|
||||
this.log = new Logger(this.constructor.name)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Service
|
80
Server/services/discord.js
Normal file
80
Server/services/discord.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
const Service = require('./Service')
|
||||
const discord = require('discord.js')
|
||||
const superagent = require('superagent')
|
||||
|
||||
class DiscordService extends Service {
|
||||
constructor (ctx) {
|
||||
super(ctx)
|
||||
|
||||
this.botToken = process.env.DISCORD_BOT_TOKEN
|
||||
this.clientId = process.env.DISCORD_CLIENT_ID
|
||||
this.clientSecret = process.env.DISCORD_CLIENT_SECRET
|
||||
this.oauthCallback = process.env.OAUTH_AUTH_CALLBACK
|
||||
this.botCallback = `${ctx.config.appUrl}/oauth/bot/callback`
|
||||
|
||||
this.startBot()
|
||||
}
|
||||
|
||||
async startBot () {
|
||||
await discord.login(this.botToken)
|
||||
}
|
||||
|
||||
// oauth step 2 flow, grab the auth token via code
|
||||
async getAuthToken (code) {
|
||||
const url = 'https://discordapp.com/api/oauth2/token'
|
||||
try {
|
||||
const rsp =
|
||||
await superagent
|
||||
.post(url)
|
||||
.send({
|
||||
client_id: this.clientId,
|
||||
client_secret: this.clientSecret,
|
||||
grant_type: 'authorization_code',
|
||||
code: code,
|
||||
redirect_uri: this.oauthCallback
|
||||
})
|
||||
|
||||
return rsp.body
|
||||
} catch (e) {
|
||||
this.log.error('getAuthToken failed', e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
// on sign out, we revoke the token we had.
|
||||
async revokeAuthToken (code, state) {
|
||||
const url = 'https://discordapp.com/api/oauth2/revoke'
|
||||
try {
|
||||
const rsp =
|
||||
await superagent
|
||||
.post(url)
|
||||
.send({
|
||||
client_id: this.clientId,
|
||||
client_secret: this.clientSecret,
|
||||
grant_type: 'authorization_code',
|
||||
code: code,
|
||||
redirect_uri: this.oauthCallback
|
||||
})
|
||||
|
||||
return rsp.body
|
||||
} catch (e) {
|
||||
this.log.error('getAuthToken failed', e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
// returns oauth authorize url with IDENTIFY permission
|
||||
// we only need IDENTIFY because we only use it for matching IDs from the bot
|
||||
getAuthUrl (state) {
|
||||
return `https://discordapp.com/oauth2/authorize?client_id=${this.clientId}&redirect_uri=${this.oauthCallback}&response_type=code&scope=identify`
|
||||
}
|
||||
|
||||
// returns the bot join url with MANAGE_ROLES permission
|
||||
// MANAGE_ROLES is the only permission we really need.
|
||||
getBotJoinUrl () {
|
||||
return `https://discordapp.com/oauth2/authorize?client_id=${this.clientId}&scope=bot&permissions=268435456&redirect_uri=${this.botCallback}`
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = DiscordService
|
10
Server/util/model-methods.js
Normal file
10
Server/util/model-methods.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
const ksuid = require('ksuid')
|
||||
|
||||
module.exports = {
|
||||
ksuid (field = 'id') {
|
||||
return async function () {
|
||||
this.id = await ksuid.random()
|
||||
return this
|
||||
}
|
||||
}
|
||||
}
|
3294
Server/yarn.lock
Normal file
3294
Server/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue