From 4691bb94ceb036799050f659d526eec572f991f5 Mon Sep 17 00:00:00 2001 From: YOUNG EUN Date: Mon, 6 Nov 2017 21:06:18 +0900 Subject: [PATCH] hi --- .env_sample | 2 + .eslintrc | 7 +- .gitignore | 4 +- .travis.yml | 6 +- README.md | 10 +- bin/bot.js | 92 ++++--- lib/controller/awardPoints.js | 58 ++--- lib/controller/deductPoints.js | 55 ++--- lib/controller/helpMessage.js | 9 +- lib/controller/replyWithDumbledore.js | 227 +++++------------- lib/controller/replyWithGithub.js | 19 +- lib/dumbledore.js | 329 +++++++++----------------- lib/helper/botCommand.js | 29 --- lib/helper/common.js | 9 + lib/helper/getPointsFromDatabase.js | 19 +- lib/helper/slackBot.js | 52 ++++ lib/parseMessage.js | 25 -- lib/word.js | 59 ++--- nodemon_sample.json => nodemon.json | 11 +- package.json | 65 +++-- page/src/components/Header.css | 3 + page/src/components/Header.js | 13 + page/src/components/NavBar.css | 10 + page/src/components/NavBar.js | 12 + page/src/components/Table.js | 61 +++++ page/src/containers/App.css | 7 + page/src/containers/App.js | 29 +++ page/src/containers/TableContainer.js | 56 +++++ page/src/helper/fetch.js | 42 ++++ page/src/index.js | 8 + page/static/pictogram.svg | 49 ++++ page/webpack.config.dev.js | 81 +++++++ parse-server/cloud/main.js | 4 + parse-server/parse.js | 81 +++++++ public/index.html | 12 + server/app.js | 26 ++ spec/botSpec.js | 7 +- spec/helperSpec.js | 74 ++++++ spec/helpers/helper.js | 45 ++++ spec/parseSpec.js | 26 ++ 40 files changed, 1120 insertions(+), 613 deletions(-) create mode 100644 .env_sample delete mode 100644 lib/helper/botCommand.js create mode 100644 lib/helper/common.js create mode 100644 lib/helper/slackBot.js delete mode 100644 lib/parseMessage.js rename nodemon_sample.json => nodemon.json (57%) create mode 100644 page/src/components/Header.css create mode 100644 page/src/components/Header.js create mode 100644 page/src/components/NavBar.css create mode 100644 page/src/components/NavBar.js create mode 100644 page/src/components/Table.js create mode 100644 page/src/containers/App.css create mode 100644 page/src/containers/App.js create mode 100644 page/src/containers/TableContainer.js create mode 100644 page/src/helper/fetch.js create mode 100644 page/src/index.js create mode 100644 page/static/pictogram.svg create mode 100644 page/webpack.config.dev.js create mode 100644 parse-server/cloud/main.js create mode 100644 parse-server/parse.js create mode 100644 public/index.html create mode 100644 server/app.js create mode 100644 spec/helperSpec.js create mode 100644 spec/helpers/helper.js create mode 100644 spec/parseSpec.js diff --git a/.env_sample b/.env_sample new file mode 100644 index 0000000..daf3e20 --- /dev/null +++ b/.env_sample @@ -0,0 +1,2 @@ +BOT_API_KEY= +BOT_NAME= diff --git a/.eslintrc b/.eslintrc index 66fea9b..f08e546 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,7 +1,8 @@ { "extends": ["airbnb", "plugin:react/recommended"], "env": { - "node": true + "node": true, + "browser": true }, "rules": { "new-cap": [2, { "capIsNewExceptions": ["List", "Map", "Set", "Router"] }], @@ -28,7 +29,9 @@ "no-unneeded-ternary": 0, "no-plusplus": 0, "prefer-template": 0, - "prefer-arrow-callback": 0 + "prefer-arrow-callback": 0, + "class-methods-use-this": 0, + "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], }, "plugins": [ "import", "react" diff --git a/.gitignore b/.gitignore index 4f3d3c2..6a37237 100644 --- a/.gitignore +++ b/.gitignore @@ -60,8 +60,8 @@ typings/ .env .idea -pm2.json -nodemon.json .DS_Store +pm2.json data/dumbledore.db package-lock.json +bundle.js diff --git a/.travis.yml b/.travis.yml index 37020b6..fa686c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,8 @@ cache: directories: - node_modules before_script: -- npm run travis + - npm run travis env: global: - - secure: xGKT41g2ntScqxUP1PzVlIx/s8MIVwyDLK7D93exHSc3XuQ2/hEJB1WqoToDeW/40NSAjl4NCLQ918wLLCWnRtxkA8bp8JgqBDPYlbWLCHfyAqXhF5RpS8Yq//hAYTseiX/6q/ZsU9v+z7lofm2xm2dXgI8AlOXbSYH0XXt+WayZVqaueXnGizNQ1q1NgXEvCPYQVVf9FgkM/wfBUptbCp8qbKdrYA7nrz+69QZIECAO3asvMcVFRRuDV0BLTXELT+J9nCFMa/q1ygl6Qpy6UMQvDWnljXtyhGRnqyISX1okACEfStl5ic0oLc6hYJgbdkB4CXSdJG9C/j7of8dDZTRN2QhYqjygs+7kCNyYGh4w2/F1trxvnuoNCp4UJo33FOTiYE4Mq8HSB4F8ecB/fxXvG6PK1Ir7q9h+ECm55nohRMadTGVMPLekTvk5l3zpPjfCuAwei4rps2ayoOvF452zkqsRvc4cGPBVwnUGnohTYJjtiMt+9T9HWyU34QD8C8SHyp+ty+544f0TH7f3a8xBULGdcUPWuIYazUwf0RsomOLAzZAvqj9K+ok5/+P2johQAJRmitn+XtbhSLjcLHvqzcUisOkwdT5SnlsFJ3I1Dl2WS6SCko2Omaf2E3oZI+SIRkswSKCVGUum8lgQavVLo0DVTvA9CvlaXqXNxGU= - - secure: n0tnMsGZZx+xVokW/jC3owFp+hP3XafqfLbgY/5AkFl4+ixKEz/9r8t1ykHjC4SkArNbaQHD4aS4udqQ8zguNUpX7LsoUOB85tKO6dnxXSFZTZ8IBAMs2YUfZaL2XHgnlZx4Dj7B7znnYNuqZECU/u13991HFeMM0XYd0r0GDDkRiy+WhOwt113xof8brqjj3XRCqn1eyOlHqPCcux+P8vGdietdPqLMolrksb4udF+gBHKu/bJUzwA0ALw9lIiUAQyyVkRVQp0rdHBU6WD1rX4Z3Jz5S4RECCQL1OPMpP6dbbvLZ3SX/iC2+/+OVDvfkfdyQ9EzjmopIPJLM8nhhvhl243rfJeUFyWTkaW3TMcFWu+QkYHvqmoRSIlABhhiwXwdeSqy37RzDX6LwVEiTqVN7yopthlaiTRVsSPT4HWGE10KUb6d3IKVE0PWdICPSDNvYrmf4Eip3Kp1/QhNhvqw9/JGEHUIoLAoUqwD+d3B0z8NFa/9K/jBrfOf7R4LSl5b/WZtWTDOTNtxJpvB81HHK7euyrnmQNTwMTXrZpr9k7jsYD703ClCwHh7HxjtjOZGD06Un2No7txlwMsUbmiMb6+9DDJCTP7sWbqMj3J/TWRib+dqbyyBeVsneOmCOaMJnYj1K4qmNCwKn+vhY1jacAf2ajlh/kxo1KuPvwE= + - BOT_API_KEY=eG94Yi0yNTQwNTc5Mjg5ODAtMDE0TU4zY2FrQmlhS3NXSnJJNEw5WGEw + - BOT_NAME=travis diff --git a/README.md b/README.md index 6cac8ee..d96eff1 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,8 @@ Dumbledore is configurable through environment variables. There are several vari ```bash $ npm install -g nodemon -$ cp ./nodemon_sample.json ./nodemon.json -// You need to change `nodemon.json` +$ cp ./.env_sample ./.env +// You need to change `.env` $ npm run dev ``` @@ -52,11 +52,11 @@ $ npm run dev * We use [jasmine](https://jasmine.github.io/): ```bash -$ BOT_API_KEY={key} BOT_NAME={name} npm test +$ cp ./.env_sample ./.env +// You need to change `.env` +$ npm test ``` -* If you are a Windows user, use [cross-env](https://www.npmjs.com/package/cross-env). - ## Production * We recommend the pm2: diff --git a/bin/bot.js b/bin/bot.js index 3dbd408..56abe92 100644 --- a/bin/bot.js +++ b/bin/bot.js @@ -1,10 +1,10 @@ -<<<<<<< HEAD -'use strict'; - -var Dumbledore = require('../lib/dumbledore');//Dumbledore변수에 dumbledore.js파일을 요청한다 -======= const Dumbledore = require('../lib/dumbledore'); ->>>>>>> young/master +const ParseInstance = require('../parse-server/parse'); +const WebInstance = require('../server/app'); +const Parse = require('parse/node'); +const { atob } = require('../lib/helper/common'); +const { DB } = require('../lib/word'); + /** * Environment variables used to configure the bot: @@ -16,31 +16,65 @@ const Dumbledore = require('../lib/dumbledore'); * BOT_GITHUB_CHANNEL_ID: If your team uses a github slack channel for alerts, The Gitub Channel Id goes here. */ -<<<<<<< HEAD -var token ='xoxb-248266143441-zgzDCId1tMbBG7GKaToXsUi2';// process.env.BOT_API_KEY;//token 변수에 api key값 저장 -var dbPath ;//= process.env.BOT_DB_PATH;//dbPath변수에 bot dbpath 저장 -var name = 'bot1';//name 변수에 bot이름 저장 -var githubChannel = '#song97';//process.env.BOT_GITHUB_CHANNEL_ID;//githubChannel변수에 channel id 저장 - -var dumbledore = new Dumbledore({//dumbledore변수에 Dumbledore객체 참조 - token: token, - dbPath: dbPath, - name: name, - githubChannel: githubChannel +const parseServer = new ParseInstance({ + databaseURI: process.env.DATABASE_URI, + cloud: process.env.CLOUD_CODE_MAIN, + appId: process.env.APP_ID, + masterKey: process.env.MASTER_KEY, + serverURL: process.env.SERVER_URL, + port: process.env.PARSE_PORT, + mountPath: process.env.MOUNT_PATH, + user: process.env.ADMIN_NAME, + pass: process.env.ADMIN_PASS, }); +// parse js sdk +Parse.initialize(process.env.APP_ID || 'myAppId', null, process.env.MASTER_KEY || 'masterKey'); +Parse.serverURL = process.env.SERVER_URL || 'http://localhost:1337/parse'; -console.log("Start +Dumbledore bot+ on your slack channel."); -dumbledore.run();//dumbledore run 호출 +async function startBot() { + try { + await parseServer.create(); -======= -const dumbledore = new Dumbledore({ - token: process.env.BOT_API_KEY, - dbPath: process.env.BOT_DB_PATH, - name: process.env.BOT_NAME, - githubChannel: process.env.BOT_GITHUB_CHANNEL_ID -}); + // web server + const webServer = new WebInstance({ + setting: process.env.WEB_PORT + }); + webServer.create(); + + if (process.env.NODE_ENV === 'development') { + const query = new Parse.Query(DB.BOT.CALL); + const botCount = await query.count(); + + if (!botCount) { + const name = process.env.BOT_NAME; + + // base64 encoded token + let token = process.env.BOT_API_KEY; + if (token.length > 42) token = atob(token); + + const obj = new Parse.Object(DB.BOT.CALL); + await obj.save({ + [DB.BOT.BOT_NAME]: name, + [DB.BOT.BOT_API_KEY]: token + }); + } + } + + // dumbledore bot + const query = new Parse.Query(DB.BOT.CALL); + await query.each((o) => { + const dumbledore = new Dumbledore({ + token: o.get(DB.BOT.BOT_API_KEY), + name: o.get(DB.BOT.BOT_NAME) + }); -console.log('Start +Dumbledore bot+ on your slack channel.'); -dumbledore.run(); ->>>>>>> young/master + dumbledore.run().then(() => { + console.log('Start *Dumbledore bot* on your slack channel.'); + }, console.error); + }); + } catch (error) { + return console.log(error); + } +} +startBot(); diff --git a/lib/controller/awardPoints.js b/lib/controller/awardPoints.js index 6dd8749..b0039f7 100644 --- a/lib/controller/awardPoints.js +++ b/lib/controller/awardPoints.js @@ -1,42 +1,26 @@ -const getPointsFromDatabase = require('../helper/getPointsFromDatabase'); -const botCommand = require('../helper/botCommand'); +const Parse = require('parse/node'); +const { DB } = require('../word'); -function awardPoints(originalMessage) { - let points = originalMessage.text.split(' ')[0].replace(/[^\d.]/g, ''); - if (points > 100) { points = 100; } - - if (originalMessage.text.toLowerCase().indexOf('gryffindor') > -1) { - this.db.run('UPDATE houses SET points = points + ? WHERE house = "gryffindor"', points); - this.db.run('UPDATE students SET points_given = (points_given + ?) WHERE user_id = ?', points, originalMessage.user); - getPointsFromDatabase.call(this, originalMessage, 'gryffindor', botCommand.awardPointsCallback); - } else if (originalMessage.text.toLowerCase().indexOf('hufflepuff') > -1) { - this.db.run('UPDATE houses SET points = points + ? WHERE house = "hufflepuff"', points); - this.db.run('UPDATE students SET points_given = (points_given + ?) WHERE user_id = ?', points, originalMessage.user); - getPointsFromDatabase.call(this, originalMessage, 'hufflepuff', botCommand.awardPointsCallback); - } else if (originalMessage.text.toLowerCase().indexOf('ravenclaw') > -1) { - this.db.run('UPDATE houses SET points = points + ? WHERE house = "ravenclaw"', points); - this.db.run('UPDATE students SET points_given = (points_given + ?) WHERE user_id = ?', points, originalMessage.user); - getPointsFromDatabase.call(this, originalMessage, 'ravenclaw', botCommand.awardPointsCallback); - } else if (originalMessage.text.toLowerCase().indexOf('slytherin') > -1) { - this.db.run('UPDATE houses SET points = points + ? WHERE house = "slytherin"', points); - this.db.run('UPDATE students SET points_given = (points_given + ?) WHERE user_id = ?', points, originalMessage.user); - getPointsFromDatabase.call(this, originalMessage, 'slytherin', botCommand.awardPointsCallback); - } else if (originalMessage.text.toLowerCase().indexOf('@') > -1) { - const student = originalMessage.text.substring(originalMessage.text.indexOf('@') + 1).split('>')[0]; - - this.db.get('SELECT user_id, username, house FROM students WHERE user_id = ?', student, (err, record) => { - if (err) { - return console.error('DATABASE ERROR:', err); - } - if (record !== undefined) { - const message = originalMessage; - message.text = points + ' ' + record.house; - this.awardPoints(message); - - this.db.run('UPDATE students SET points_earned = (points_earned + ?) WHERE user_id = ?', points, student); +function awardPoints(botId, userId, point) { + if (typeof botId === 'undefined' || typeof userId === 'undefined' || typeof point === 'undefined') return; + const Student = new Parse.Object(DB.STUDENT.CALL); + const query = new Parse.Query(Student); + query.equalTo(DB.STUDENT.BOT_ID, botId).equalTo(DB.STUDENT.USER_ID, userId).find({ + success(results) { + if (results.length === 0) { + Student.save({ + [DB.STUDENT.BOT_ID]: botId, + [DB.STUDENT.USER_ID]: userId, + [DB.STUDENT.POINT]: point + }); } - }); - } + results[0].increment(DB.STUDENT.POINT, point); + results[0].save(); + }, + error(error) { + console.log(error.message); + } + }); } module.exports = awardPoints; diff --git a/lib/controller/deductPoints.js b/lib/controller/deductPoints.js index 1c16cbc..4ac79b5 100644 --- a/lib/controller/deductPoints.js +++ b/lib/controller/deductPoints.js @@ -1,41 +1,26 @@ -const getPointsFromDatabase = require('../helper/getPointsFromDatabase'); -const botCommand = require('../helper/botCommand'); +const Parse = require('parse/node'); +const { DB } = require('../word'); -function deductPoints(originalMessage) { - let points = originalMessage.text.split(' ')[0].replace(/[^\d.]/g, ''); +function deductPoints(botId, userId, point) { + if (typeof botId === 'undefined' || typeof userId === 'undefined' || typeof point === 'undefined') return; + const Student = new Parse.Object(DB.STUDENT.CALL); + const query = new Parse.Query(Student); + query.equalTo(DB.STUDENT.BOT_ID, botId).equalTo(DB.STUDENT.USER_ID, userId).find({ + success(results) { + if (results.length === 0) { + Student.set(DB.STUDENT.BOT_ID, botId); + Student.set(DB.STUDENT.USER_ID, userId); + Student.set(DB.STUDENT.POINT, -point); - if (points > 100) { points = 100; } - if (originalMessage.text.toLowerCase().indexOf('gryffindor') > -1) { - this.db.run('UPDATE houses SET points = MAX(0, points - ?) WHERE house = "gryffindor"', points); - this.db.run('UPDATE students SET points_taken = (points_taken + ?) WHERE user_id = ?', points, originalMessage.user); - getPointsFromDatabase.call(this, originalMessage, 'gryffindor', botCommand.deductPointsCallback); - } else if (originalMessage.text.toLowerCase().indexOf('hufflepuff') > -1) { - this.db.run('UPDATE houses SET points = MAX(0, points - ?) WHERE house = "hufflepuff"', points); - getPointsFromDatabase.call(this, originalMessage, 'hufflepuff', botCommand.deductPointsCallback); - this.db.run('UPDATE students SET points_taken = (points_taken + ?) WHERE user_id = ?', points, originalMessage.user); - } else if (originalMessage.text.toLowerCase().indexOf('ravenclaw') > -1) { - this.db.run('UPDATE houses SET points = MAX(0, points - ?) WHERE house = "ravenclaw"', points); - getPointsFromDatabase.call(this, originalMessage, 'ravenclaw', botCommand.deductPointsCallback); - this.db.run('UPDATE students SET points_taken = (points_taken + ?) WHERE user_id = ?', points, originalMessage.user); - } else if (originalMessage.text.toLowerCase().indexOf('slytherin') > -1) { - this.db.run('UPDATE houses SET points = MAX(0, points - ?) WHERE house = "slytherin"', points); - getPointsFromDatabase.call(this, originalMessage, 'slytherin', botCommand.deductPointsCallback); - this.db.run('UPDATE students SET points_taken = (points_taken + ?) WHERE user_id = ?', points, originalMessage.user); - } else if (originalMessage.text.toLowerCase().indexOf('@') > -1) { - const student = originalMessage.text.substring(originalMessage.text.indexOf('@') + 1).split('>')[0]; - - this.db.get('SELECT user_id, username, house FROM students WHERE user_id = ?', student, (err, record) => { - if (err) { - return console.error('DATABASE ERROR:', err); - } - - if (record !== undefined) { - const message = originalMessage; - message.text = points + ' ' + record.house; - this.deductPoints(message); + Student.save(); } - }); - } + results[0].increment(DB.STUDENT.POINT, -point); + results[0].save(); + }, + error(error) { + console.log(error.message); + } + }); } module.exports = deductPoints; diff --git a/lib/controller/helpMessage.js b/lib/controller/helpMessage.js index 9c5fcb6..90daa98 100644 --- a/lib/controller/helpMessage.js +++ b/lib/controller/helpMessage.js @@ -1,4 +1,3 @@ -const botCommand = require('../helper/botCommand'); const { INPUT } = require('../word'); async function helpMessage(originalMessage) { @@ -6,19 +5,19 @@ async function helpMessage(originalMessage) { // for points to const pointTo = `\`Num\` ${POINTS_TO.CALL} \`house name\``; - await botCommand.announcePlainString(originalMessage, pointTo, this); + await this.slackBot.announcePlainString(originalMessage, pointTo); // for points from const pointFrom = `\`Num\` ${POINTS_FROM.CALL} \`house name\``; - await botCommand.announcePlainString(originalMessage, pointFrom, this); + await this.slackBot.announcePlainString(originalMessage, pointFrom); // for professor // todo: show details Object.keys(PROFESSOR).forEach(async k => { if (k !== 'CALL') { - const str = `\`${PROFESSOR.CALL}\` or \`${this.name}\` ${typeof PROFESSOR[k] === 'object' ? PROFESSOR[k].CALL : PROFESSOR[k]}`; + const str = `\`${PROFESSOR.CALL}\` or \`${this.slackBot.getName()}\` ${typeof PROFESSOR[k] === 'object' ? PROFESSOR[k].CALL : PROFESSOR[k]}`; - await botCommand.announcePlainString(originalMessage, str, this); + await this.slackBot.announcePlainString(originalMessage, str); } }); } diff --git a/lib/controller/replyWithDumbledore.js b/lib/controller/replyWithDumbledore.js index 85fc98b..5ee25d4 100644 --- a/lib/controller/replyWithDumbledore.js +++ b/lib/controller/replyWithDumbledore.js @@ -1,30 +1,11 @@ -const random = require('random-js')(); // uses the nativeMath engine -const getPointsFromDatabase = require('../helper/getPointsFromDatabase'); -const botCommand = require('../helper/botCommand'); -const { INPUT, OUTPUT, HOUSE } = require('../word'); - -async function getAllHouseScores(originalMessage, callback) { - const channel = await this.getChannelById(originalMessage.channel); - - this.postMessageToChannel(channel.name, 'The House Points are: \n', { as_user: true }, () => { - Object.values(HOUSE).forEach(house => { - getPointsFromDatabase.call(this, originalMessage, house, callback); - }); - }); -} - -function resetTheScores(originalMessage, callback) { - this.db.run('UPDATE houses SET points = 0'); - callback(originalMessage, OUTPUT.RESET_SCORE, this); -} - -function welcomeMessageManual(originalMessage) { - botCommand.announcePlainString(originalMessage, OUTPUT.SAY_HELLO, this); -} +const { + INPUT, OUTPUT, DB +} = require('../word'); // Slacks API converts an @username reference in a message to the userid this converts it back to username. -function convertToUserName(bot, key) { - return bot.users.find((userid) => { +async function convertToUserName(key) { + const users = await this.slackBot.getUsers(); + return users.find((userid) => { if (userid.id === key || userid.name === key) { return userid.name; } @@ -32,187 +13,101 @@ function convertToUserName(bot, key) { }); } -function greetNewStudent(originalMessage, student, house) { - botCommand.announcePlainString(originalMessage, OUTPUT.canIJoin(student, house), this); -} - -function addStudentToHouse(originalMessage, house) { - // const student = originalMessage.text.substring(originalMessage.text.indexOf('@') + 1).split('>')[0]; - const student = originalMessage.user; - const studentUsername = convertToUserName(this, student); - let notAlreadyStudent = true; - const text = originalMessage.text.toLowerCase(); - - this.db.all('SELECT * FROM students', (err, respond) => { - if (err) { - return console.error('DATABASE ERROR', err); - } - if (respond !== undefined) { - respond.find((dbUser) => { - if (dbUser.user_id === student) { - // botCommand.announcePlainString(originalMessage, studentUsername + ' You are already in a house. Once You have been sorted in that house you shall remain. But fear not, if you give it a chance you will see there is much to gain.', bot); - notAlreadyStudent = false; - return true; - } - return false; - }); - } - if (notAlreadyStudent === true) { - // current not allow multiple house since callback return true - Object.values(HOUSE).filter(t => text.includes(t) || house === t).some(v => { - this.db.run('INSERT INTO students (user_id, username, house) VALUES (?, ?, ?)', student, studentUsername, v, (_err) => { - if (_err) { - return console.error('DATABASE ERROR', _err); - } - greetNewStudent.call(this, originalMessage, studentUsername, v); - }); - return true; - }); - } - }); -} - -function rollTheDice(originalMessage) { - const house = random.integer(0, 3); - addStudentToHouse.call(this, originalMessage, Object.values(HOUSE)[house], null); -} - -function obliviate(originalMessage) { +async function studentStats(originalMessage) { const student = originalMessage.text.substring(originalMessage.text.indexOf('@') + 1).split('>')[0]; - if (student === convertToUserName(this, originalMessage.user)) { - this.db.run('DELETE FROM students WHERE user_id = ?', student, (err) => { - if (err) { - return console.error('DATABASE ERROR', err); - } - botCommand.announcePlainString(originalMessage, OUTPUT.OBLIVIATE, this); - }); - } -} - -function studentStats(originalMessage) { - const student = originalMessage.text.substring(originalMessage.text.indexOf('@') + 1).split('>')[0]; + if (convertToUserName(student) !== 'dumbledore') { + const Student = new Parse.Object(DB.STUDENT.CALL); + const query = new Parse.Query(Student); + query.equalTo('userId', student); - if (convertToUserName(this, student) !== 'dumbledore') { - this.db.get('SELECT * FROM students WHERE user_id = ?', student, (err, record) => { - if (err) { - return console.error('DATABASE ERROR', err); - } - if (record !== undefined) { - botCommand.announcePlainString(originalMessage, OUTPUT.TELL_ME_ABOUT.PERSON.student(record), this); + try { + const record = await query.first(); + if (record.attributes !== undefined) { + this.slackBot.announcePlainString(originalMessage, OUTPUT.TELL_ME_ABOUT.PERSON.student(record.attributes)); } else { - botCommand.announcePlainString(originalMessage, OUTPUT.TELL_ME_ABOUT.PERSON.NOT_FOUND, this); + this.slackBot.announcePlainString(originalMessage, OUTPUT.TELL_ME_ABOUT.PERSON.NOT_FOUND); } - }); + } catch (err) { + console.error('DATABASE ERROR', err); + } } else { - botCommand.announcePlainString(originalMessage, OUTPUT.TELL_ME_ABOUT.PERSON.DUMBLEDORE, this); + this.slackBot.announcePlainString(originalMessage, OUTPUT.TELL_ME_ABOUT.PERSON.DUMBLEDORE); } } -function bestStudent(originalMessage) { - Object.values(HOUSE).forEach((house) => { - this.db.get('SELECT * FROM students WHERE house = ? ORDER BY points_earned DESC', house, (err, record) => { - if (err) { - return console.error('DATABASE ERROR', err); - } - if (record !== undefined) { - botCommand.announcePlainString(originalMessage, OUTPUT.getBestStudent(record), this); - } - }); - }); - botCommand.announcePlainString(originalMessage, OUTPUT.ABOUT_FROGS, this); +async function bestStudentChannel(student) { + const Student = new Parse.Object(student); + const query = new Parse.Query(Student); + + query.descending(DB.STUDENT.POINT); + try { + const record = await query.first(); + return record; + } catch (err) { + console.error('DATABASE ERROR', err); + } } -function worstStudent(originalMessage) { - Object.values(HOUSE).forEach((house) => { - this.db.get('SELECT * FROM students WHERE house = ? ORDER BY points_taken DESC', house, (err, record) => { - if (err) { - return console.error('DATABASE ERROR', err); - } - if (record !== undefined) { - botCommand.announcePlainString(originalMessage, OUTPUT.getMeanestStudent(record), this); - } - }); - }); +async function bestStudent(originalMessage) { + const record = await bestStudentChannel(DB.STUDENT.CALL); + this.slackBot.announcePlainString(originalMessage, OUTPUT.getBestStudent(record.attributes)); } -function getAllStudentsFromHouse(originalMessage, house) { - this.db.all('SELECT * FROM students WHERE house = ? ', house, (err, record) => { - if (err) { - return console.error('DATABASE ERROR', err); - } +async function worstStudentChannel(student) { + const Student = new Parse.Object(student); + const query = new Parse.Query(Student); - botCommand.announcePlainString(originalMessage, OUTPUT.TELL_ME_ABOUT.fromHouse(house, record), this); - }); + query.ascending(DB.STUDENT.POINT); + try { + const record = await query.first(); + return record; + } catch (err) { + console.error('DATABASE ERROR', err); + } } -function tellMeAbout(originalMessage, house) { - const text = originalMessage.text.toLowerCase(); +async function worstStudent(originalMessage) { + const record = await worstStudentChannel(DB.STUDENT.CALL); + this.slackBot.announcePlainString(originalMessage, OUTPUT.getMeanestStudent(record.attributes)); +} +function tellMeAbout(originalMessage) { + const text = originalMessage.text.toLowerCase(); if (text.includes('@')) { studentStats.call(this, originalMessage); - } else if (house) { - getAllStudentsFromHouse.call(this, originalMessage, house); } else if (text.includes(INPUT.PROFESSOR.JASON_MOM)) { - botCommand.announcePlainString(originalMessage, OUTPUT.TELL_ME_ABOUT.JASON_MOM, this); + this.slackBot.announcePlainString(originalMessage, OUTPUT.TELL_ME_ABOUT.JASON_MOM); } } -function getAllStudents(originalMessage) { - this.db.all('SELECT * FROM students', (err) => { - if (err) { - return console.error('DATABASE ERROR', err); - } - Object.values(HOUSE).forEach((house) => { - getAllStudentsFromHouse.call(this, originalMessage, house); - }); - }); -} - -function explainSorting(originalMessage) { - botCommand.announcePlainString(originalMessage, OUTPUT.START_SORTING, this); -} - function saveGitName(originalMessage) { + const Student = new Parse.Object(DB.STUDENT.CALL); + const query = new Parse.Query(Student); const gitName = originalMessage.text.split('=')[1]; const { user } = originalMessage; - + query.equalTo('user_id', user); + Student.set('GITHUB_NAME', gitName); + this.slackBot.announcePlainString(originalMessage, OUTPUT.saveGitName(convertToUserName(user), gitName)); +/* this.db.run('UPDATE students SET github_name = ? WHERE user_id = ?', gitName, user, (err) => { if (err) { return console.error('DATABASE ERROR', err); } - botCommand.announcePlainString(originalMessage, OUTPUT.saveGitName(convertToUserName(this, user), gitName), this); - }); -} - -async function forceSortRemaining(originalMessage) { - const users = await this.getUsers(); - - users.members.forEach((user) => { - originalMessage.user = user.id; - rollTheDice.call(this, originalMessage); + this.slackBot.announcePlainString(originalMessage, OUTPUT.saveGitName(convertToUserName(user), gitName)); }); + */ } function replyWithDumbledore(originalMessage) { const text = originalMessage.text.toLowerCase(); - const house = Object.values(HOUSE).find(v => text.includes(v)); const parseCase = { - [INPUT.PROFESSOR.GET_WINNER]: () => getAllHouseScores.call(this, originalMessage, botCommand.getAllHousePointsCallback), - [INPUT.PROFESSOR.RESET_SCORE]: () => resetTheScores.call(this, botCommand.announcePlainString), - [INPUT.PROFESSOR.SAY_HELLO]: () => welcomeMessageManual.call(this, originalMessage), - [INPUT.PROFESSOR.CAN_I_JOIN]: () => addStudentToHouse.call(this, originalMessage, null, null /* this._studentJoinedHouse */), - [INPUT.PROFESSOR.SORTING_HAT]: () => rollTheDice.call(this, originalMessage), - [INPUT.PROFESSOR.OBLIVIATE]: () => obliviate.call(this, originalMessage), [INPUT.PROFESSOR.GET_BEST_STUDENT]: () => bestStudent.call(this, originalMessage), [INPUT.PROFESSOR.GET_MEANEST_STUDENT]: () => worstStudent.call(this, originalMessage), - [INPUT.PROFESSOR.TELL_ME_ABOUT]: () => tellMeAbout.call(this, originalMessage, house), - [INPUT.PROFESSOR.START_SORTING]: () => explainSorting.call(this, originalMessage), - [INPUT.PROFESSOR.GET_HOGWARTS_ROSTER]: () => getAllStudents.call(this, originalMessage), - [INPUT.PROFESSOR.SAVE_GIT_NAME]: () => saveGitName.call(this, originalMessage), - [INPUT.PROFESSOR.SORT_REST]: () => forceSortRemaining.call(this, originalMessage) + [INPUT.PROFESSOR.TELL_ME_ABOUT.CALL]: () => tellMeAbout.call(this, originalMessage), + [INPUT.PROFESSOR.SAVE_GIT_NAME]: () => saveGitName.call(this, originalMessage) }; Object.keys(parseCase).forEach(key => { diff --git a/lib/controller/replyWithGithub.js b/lib/controller/replyWithGithub.js index 8df522c..cbf9580 100644 --- a/lib/controller/replyWithGithub.js +++ b/lib/controller/replyWithGithub.js @@ -1,9 +1,25 @@ -function replyWithGithub(originalMessage) { +const { DB } = require('../word'); + +async function replyWithGithub(originalMessage) { + const point = 5; if (originalMessage.attachments[0].pretext !== undefined) { if (originalMessage.attachments[0].pretext.indexOf('New comment by ') > -1) { const gitUser = originalMessage.attachments[0].pretext.split('New comment by ')[1].split(' ')[0]; if (originalMessage.attachments[0].text.indexOf(':+1:') > -1) { + const Student = new Parse.Object(DB.STUDENT.CALL); + const query = new Parse.Query(Student); + query.equalTo(DB.STUDENT.BOT_ID, 'botId').equalTo(DB.STUDENT.USER_ID, 'userId').equalTo(DB.STUDENT.GITHUB_NAME, gitUser); + try { + const record = await query.first(); + if (record !== undefined) { + record.increment(DB.STUDENT.POINT, point); + record.save(); + } + } catch (err) { + console.error('DATABASE ERROR', err); + } + /* this.db.get('SELECT * FROM students WHERE github_name = ?', gitUser, function (err, record) { if (err) { return console.error('DATABASE ERROR', err); @@ -12,6 +28,7 @@ function replyWithGithub(originalMessage) { this.db.run('UPDATE houses SET points = points + ? WHERE house = ?', 5, record.house); } }); + */ } } } diff --git a/lib/dumbledore.js b/lib/dumbledore.js index dae6377..7efd147 100644 --- a/lib/dumbledore.js +++ b/lib/dumbledore.js @@ -1,280 +1,175 @@ const path = require('path'); -const fs = require('fs'); -<<<<<<< HEAD -const SQLite = require("sqlite3").verbose();// sqlite를 연결하는 객체를 만듭니다. -const Bot = require("slackbots"); -const random = require("random-js")(); // uses the nativeMath engine -const cron = require('cron').CronJob; // cron works -======= -const SQLite = require('sqlite3').verbose(); -const Bot = require('slackbots'); -// const random = require('random-js')(); // uses the nativeMath engine + const Cron = require('cron').CronJob; // cron works -const parseMessage = require('./parseMessage'); -const { OUTPUT } = require('./word'); ->>>>>>> young/master +const { INPUT, OUTPUT, DB } = require('./word'); +const SlackBot = require('./helper/slackBot'); -class Dumbledore extends Bot { //const Bot를 상속받음 - constructor(settings) { //settings구성 ? - settings.token = settings.token || 'your-token'; //'xoxb-248266143441-zgzDCId1tMbBG7GKaToXsUi2'; - settings.name = settings.name || 'your-bot-name'; //'bot1'; +class Dumbledore { + constructor(settings) { + const token = settings.token || 'your-token'; + const name = settings.name || 'your-bot-name'; - super(settings);//bot생성자 + this.slackBot = new SlackBot({ token, name }); this.githubChannel = settings.githubChannel || 'null'; this.dbPath = settings.dbPath || path.resolve(process.cwd(), 'data', 'dumbledore.db'); this.db = null; this.user = null; - this.params = { - link_names: 1 - }; + this.name = this.slackBot.getName(); + this.awardPoints = require('./controller/awardPoints').bind(this); + this.deductPoints = require('./controller/deductPoints').bind(this); + this.replyWithDumbledore = require('./controller/replyWithDumbledore').bind(this); + this.replyWithGithub = require('./controller/replyWithGithub').bind(this); + this.helpMessage = require('./controller/helpMessage').bind(this); + this.getPointsFromDatabase = require('./helper/getPointsFromDatabase').bind(this); } run() { -<<<<<<< HEAD - this.on('start', this._onStart); - this.on('message', this._onMessage); - }//실행 메소드 -======= return new Promise((resolve) => { - this.on('start', () => { - this._onStart(); - resolve(); + this.slackBot.on('start', () => { + this.onStart().then(() => { + resolve(); + }); + }); + this.slackBot.on('message', (message) => { + this.onMessage(message); }); - this.on('message', this._onMessage); }); } ->>>>>>> young/master - _onStart() { - this._loadBotUser(); - this._connectDb(); - this._firstRunCheck(); - // this._onCron(); + async onStart() { + this.checkBackend(); + await this.loadBotUser(); + await this.firstRunCheck(); + this.onCron(); } - _onCron() { - const cronMessage = { - type: 'message', - channel: 'your-channel-id', - text: `${this.name} who is winning the house cup` - }; - -<<<<<<< HEAD - new cron({ - cronTime: '00 59 23 * * 0',//초 분 시간 년 월 일 - onTick: () => self._getAllHouseScores(cronMessage, self, self._getAllHousePointsCallback), -======= - new Cron({ - cronTime: '00 59 23 * * 0', - onTick: () => this._getAllHouseScores(cronMessage, this, this._getAllHousePointsCallback), ->>>>>>> young/master + onCron() { + const doRegular = new Cron({ + cronTime: '00 00 00 * * 1', + onTick: () => this.welcomeMessage(), // set function to run something regularly start: false, timeZone: 'Asia/Seoul' - }).start(); + }); + + doRegular.start(); } - _loadBotUser() { - [this.user] = this.users.filter((user) => { - return user.name === this.name; - }); + async loadBotUser() { + this.user = await this.slackBot.getUser(this.name); } - _connectDb() { - if (!fs.existsSync(this.dbPath)) { - console.error('Database path "' + this.dbPath + '" does not exists or it\'s not readable.'); + checkBackend() { + if (!Parse || !Parse.serverURL || !Parse.applicationId) { + console.error('Parse Backend does not exists.'); process.exit(1); } - - this.db = new SQLite.Database(this.dbPath); } - _firstRunCheck() { - this.db.get('SELECT val FROM info WHERE name = "lastrun" LIMIT 1', (err, record) => { + async firstRunCheck() { + try { + const query = new Parse.Query(DB.INFO.CALL); + query.equalTo('name', DB.INFO.LASTRUN); + const doc = await query.first(); const currentTime = (new Date()).toJSON(); - if (err) { - return console.error('DATABASE ERROR:', err); - } - // this is a first run - if (!record) { - this._welcomeMessage(); - return this.db.run('INSERT INTO info(name, val) VALUES("lastrun", ?)', currentTime); + if (!doc) { + this.welcomeMessage(); + const _doc = new Parse.Object(DB.INFO.CALL); + return _doc.save({ name: DB.INFO.LASTRUN, val: currentTime }); } // updates with new last running time - this.db.run('UPDATE info SET val = ? WHERE name = "lastrun"', currentTime); - }); - } - - _welcomeMessage() { - this.postMessageToChannel(this.channels[0].name, OUTPUT.SAY_HELLO, { as_user: true }); + return doc.save({ val: currentTime }); + } catch (e) { + console.error(e); + throw e; + } } - _onMessage(message) { - if (this._isTargetEvent(message)) { - if ((message.text.indexOf('01100100 01110101 01101101 01100010 01101100 01100101 01100100 01101111 01110010 01100101') > -1) || (message.text.indexOf('01110000 01101111 01101001 01101110 01110100 01110011 00100000 01110100 01101111') > -1) || (message.text.indexOf('01110000 01101111 01101001 01101110 01110100 01110011 00100000 01100110 01110010 01101111 01101101') > -1)) { - const binaryArray = message.text.split(' '); - const decimalArray = binaryArray.map(function (x) { return parseInt(x, 2); }); - let finalString = String.fromCharCode.apply(this, decimalArray); - if (finalString.indexOf('@') > -1) { - const username = finalString.substring(finalString.indexOf('@') + 1).split(' ')[0]; - const userid = this.convertToUserID(username); - finalString = finalString.replace('@' + username, '<@' + userid + '>'); - } - message.text = finalString; + async welcomeMessage() { + const { channels } = await this.slackBot.getChannels(); + this.channels = channels.filter(channel => channel.is_member); + if (!this.channels.length) return; + return this.slackBot.postTo(this.channels[0].name, OUTPUT.SAY_HELLO, { as_user: true }); + } + + onMessage(message) { + if (this.isChatMessageEvent(message)) { + this.parseMessage(message); + } else if (this.isReplayEvent(message)) { + const { user } = message.message.replies[message.message.reply_count - 1]; + const point = 5; + this.awardPoints(this.name, user, point); + } else if (this.isReactionEvent(message)) { + const user = message.item_user; + const point = 10; + if (message.type === 'reaction_added') { + this.awardPoints(this.name, user, point); + } else { + this.awardPoints(this.name, user, -point); } - parseMessage.call(this, message); } } - _isTargetEvent(message) { + isChatMessageEvent(message) { const isChatMessage = message.type === 'message' && (Boolean(message.text) || Boolean(message.attachments)); - const isChannelConversation = typeof message.channel === 'string' && message.channel[0] === 'C'; + const isChannelConversation = (typeof message.channel === 'string' && message.channel[0] === 'C'); const isFromDumbledore = message.user === this.user.id; const isFromSlackbot = message.user === 'USLACKBOT'; return isChatMessage && isChannelConversation && !isFromDumbledore && !isFromSlackbot; } - convertToUserID(key) { - if (key in this.users) { - return key; - } -<<<<<<< HEAD - } - - _getAllStudents(originalMessage, bot) { - bot.db.all('SELECT * FROM students', function (err, record) { - if (err) { - return console.error('DATABASE ERROR', err); - } - for (var house of ['gryffindor', 'hufflepuff', 'ravenclaw', 'slytherin']) { - bot._getAllStudentsFromHouse(originalMessage, house, bot); - }; - }); - } + isReplayEvent(message) { + if (typeof message.subtype === 'undefined') return false; + const isReplayMessage = message.type === 'message' && message.subtype === 'message_replied'; + const isChannelConversation = (typeof message.channel === 'string' && message.channel[0] === 'C'); + const isSameUser = message.message.user === message.message.replies[message.message.reply_count - 1].user; + const isFromSlackbot = message.message.user === this.user.id; - _getAllStudentsFromHouse(originalMessage, house, bot) { - bot.db.all('SELECT * FROM students WHERE house = ? ', house, function(err, record) { - if (err) { - return console.error('DATABASE ERROR', err); - } - var students = 'The students of ' + house.capitalizeFirstLetter() + ' House are:\n'; - for (var name of record) { - students = students + name.username + '\n'; - } - bot._announcePlainString(originalMessage, students, bot); - }); + return isReplayMessage && isChannelConversation && !isSameUser && !isFromSlackbot; } - _getAllHouseScores(originalMessage, bot, callback) { - bot.postMessageToChannel(bot._getChannelById(originalMessage.channel).name, 'The House Points are: \n', {as_user: true}, function() { - bot._getPointsFromDatabase(originalMessage, "gryffindor", bot, callback); - bot._getPointsFromDatabase(originalMessage, "hufflepuff", bot, callback); - bot._getPointsFromDatabase(originalMessage, "ravenclaw", bot, callback); - bot._getPointsFromDatabase(originalMessage, "slytherin", bot, callback); - }); - }; - - _rollTheDice(originalMessage, bot) { - - var house = random.integer(0,3); - switch(house) { - case 0: - bot._addStudentToHouse(originalMessage, 'gryffindor', bot, null); - break; - case 1: - bot._addStudentToHouse(originalMessage, 'hufflepuff', bot, null); - break; - case 2: - bot._addStudentToHouse(originalMessage, 'ravenclaw', bot, null); - break; - case 3: - bot._addStudentToHouse(originalMessage, 'slytherin', bot, null); - break; - }; - } + isReactionEvent(message) { + if (typeof message.item === 'undefined') return false; + const isReactionMessage = message.type === 'reaction_added' || message.type === 'reaction_removed'; + const isScoreReaction = message.reaction === '100'; + const isChannelConversation = (typeof message.item.channel === 'string' && message.item.channel[0] === 'C'); + const isSameUser = message.user === message.item_user; + const isFromSlackbot = message.item_user === this.user.id; - _addStudentToHouse(originalMessage, house, bot, callback) { - var bot = bot; - //var student = originalMessage.text.substring(originalMessage.text.indexOf('@') + 1).split('>')[0]; - var student = originalMessage.user; - var studentUsername = bot.convertToUserName(bot, student); - var notAlreadyStudent = true; - bot.db.all('SELECT * FROM students', function (err, respond) { - if (err) { - return console.error('DATABASE ERROR', err); - } - if (respond !== undefined) { - for (var db_user of respond) { - if (db_user.user_id == student) { - // bot._announcePlainString(originalMessage, studentUsername + ' You are already in a house. Once You have been sorted in that house you shall remain. But fear not, if you give it a chance you will see there is much to gain.', bot); - notAlreadyStudent = false; - break; - } - } - } - if (notAlreadyStudent === true) { - if ((originalMessage.text.toLowerCase().indexOf('gryffindor') > -1) || house == 'gryffindor') { - bot.db.run('INSERT INTO students (user_id, username, house) VALUES (?, ?, "gryffindor")', student, studentUsername, function (err, respond) { - if (err) { - return console.error('DATABASE ERROR', err); - } - bot._greetNewStudent(originalMessage, studentUsername, 'gryffindor', bot); - }); - } else if ((originalMessage.text.toLowerCase().indexOf('hufflepuff') > -1) || house == 'hufflepuff') { - bot.db.run('INSERT INTO students (user_id, username, house) VALUES (?, ?, "hufflepuff")', student, studentUsername, function (err, respond) { - if (err) { - return console.error('DATABASE ERROR', err); - } - bot._greetNewStudent(originalMessage, studentUsername, 'hufflepuff', bot); - }); - } else if ((originalMessage.text.toLowerCase().indexOf('ravenclaw') > -1) || house == 'ravenclaw') { - bot.db.run('INSERT INTO students (user_id, username, house) VALUES (?, ?, "ravenclaw")', student, studentUsername, function (err, respond) { - if (err) { - return console.error('DATABASE ERROR', err); - } - bot._greetNewStudent(originalMessage, studentUsername, 'ravenclaw', bot); - }); - } else if ((originalMessage.text.toLowerCase().indexOf('slytherin') > -1) || house == 'slytherin') { - bot.db.run('INSERT INTO students (user_id, username, house) VALUES (?, ?, "slytherin")', student, studentUsername, function (err, respond) { - if (err) { - return console.error('DATABASE ERROR', err); - } - bot._greetNewStudent(originalMessage, studentUsername, 'slytherin', bot); - }); - } - } - }); + return isReactionMessage && isScoreReaction && isChannelConversation && !isSameUser && !isFromSlackbot; } - _greetNewStudent(originalMessage, student, house, bot) { - var bot = bot; - bot._announcePlainString(originalMessage, 'Welcome ' + student.capitalizeFirstLetter() + ' the house of ' + house.capitalizeFirstLetter() + ' expects great things from you!', bot); - } + parseMessage(message) { + /* todo: determine what if handle multiple messages? */ + const text = message.text.toLowerCase(); - //Slacks API converts an @username reference in a message to the userid this converts it back to username. - convertToUserName(bot, key) { - var bot = bot; - for (var userid of bot.users) { - if (userid.id == key || userid.name == key) { - return userid.name; - } + if (text.includes(INPUT.POINTS_TO.CALL)) { + const point = parseInt(message.text.split(' ')[0].replace(/[^\d.]/g, ''), 10); + const user = message.text.substring(message.text.indexOf('@') + 1).split('>')[0]; + this.awardPoints(this.name, user, point); + } else if (text.includes(INPUT.POINTS_FROM.CALL)) { + this.deductPoints(message); + } else if (text.includes(INPUT.PROFESSOR.CALL) || text.includes(this.name)) { + this.replyWithDumbledore(message); + } else if (text.includes(INPUT.HELP.CALL)) { + this.helpMessage(message); + } else if (message.channel != null && message.channel === this.githubChannel) { + this.replyWithGithub(message); } } - convertToUserID(bot, key) { - if (key in bot.users) { - return key + static async convertToUserID(key) { + const users = await this.slackBot.getUsers(); + + if (key in users) { + return key; } - for (var userid of bot.users) { - if (userid.name == key) { -======= - return this.users.find((userid) => { + return users.find((userid) => { if (userid.name === key) { ->>>>>>> young/master return userid.id; } return false; diff --git a/lib/helper/botCommand.js b/lib/helper/botCommand.js deleted file mode 100644 index 9240112..0000000 --- a/lib/helper/botCommand.js +++ /dev/null @@ -1,29 +0,0 @@ -const { OUTPUT } = require('../word'); - -const botCommand = { - async explainSorting(originalMessage, bot) { - await this.announcePlainString(originalMessage, OUTPUT.START_SORTING, bot); - }, - async awardPointsCallback(originalMessage, house, bot, result) { - const channel = await bot.getChannelById(originalMessage.channel); - - await bot.postMessageToChannel(channel.name, OUTPUT.pointTo(house, result), { as_user: true }); - }, - async deductPointsCallback(originalMessage, house, bot, result) { - const channel = await bot.getChannelById(originalMessage.channel); - - await bot.postMessageToChannel(channel.name, OUTPUT.pointFrom(house, result), { as_user: true }); - }, - async getAllHousePointsCallback(originalMessage, house, bot, result) { - const channel = await bot.getChannelById(originalMessage.channel); - - await bot.postMessageToChannel(channel.name, OUTPUT.getWinner(house, result), { as_user: true }); - }, - async announcePlainString(originalMessage, message, bot) { - const channel = await bot.getChannelById(originalMessage.channel); - - await bot.postMessageToChannel(channel.name, message, { as_user: true, link_names: 1 }); - } -}; - -module.exports = botCommand; diff --git a/lib/helper/common.js b/lib/helper/common.js new file mode 100644 index 0000000..049e3be --- /dev/null +++ b/lib/helper/common.js @@ -0,0 +1,9 @@ +function atob(str) { + return Buffer.from(str, 'base64').toString('binary'); +} +module.exports.atob = atob; + +function btoa(str) { + return Buffer.from(str, 'binary').toString('base64'); +} +module.exports.btoa = btoa; diff --git a/lib/helper/getPointsFromDatabase.js b/lib/helper/getPointsFromDatabase.js index f984350..d596103 100644 --- a/lib/helper/getPointsFromDatabase.js +++ b/lib/helper/getPointsFromDatabase.js @@ -1,13 +1,30 @@ +const { DB } = require('../word'); + function getPointsFromDatabase(originalMessage, house, callback) { + const query = new Parse.Query(DB.HOUSE.CALL); + + query.equalTo('name', house); + query.first({ + success: (o) => { + if (typeof callback === 'function') { + const point = o.get('val'); + callback(originalMessage, house, point); + } + }, + error: (err) => console.error('DATABASE ERROR', err) + }); + +/* this.db.get('SELECT points FROM houses WHERE house = ?', house, (err, record) => { if (err || record === undefined) { return console.error('DATABASE ERROR', err); } if (typeof callback === 'function') { - callback(originalMessage, house, this, record); + callback(originalMessage, house, record); } }); +*/ } module.exports = getPointsFromDatabase; diff --git a/lib/helper/slackBot.js b/lib/helper/slackBot.js new file mode 100644 index 0000000..d47dcd8 --- /dev/null +++ b/lib/helper/slackBot.js @@ -0,0 +1,52 @@ +const { OUTPUT } = require('../word'); +const Bot = require('slackbots'); + +class SlackBot extends Bot { + constructor({ token, name }) { + if (!token || !name) { + throw Error('please set token and name of bot'); + } + super({ token, name }); + + this.awardPointsCallback = this.awardPointsCallback.bind(this); + this.deductPointsCallback = this.deductPointsCallback.bind(this); + this.getAllHousePointsCallback = this.getAllHousePointsCallback.bind(this); + this.announcePlainString = this.announcePlainString.bind(this); + } + + async awardPointsCallback(originalMessage, result) { + const channel = await this.getChannelById(originalMessage.channel); + + await this.postMessageToChannel(channel.name, OUTPUT.pointTo(result.attributes), { as_user: true }); + } + + async deductPointsCallback(originalMessage, house, result) { + const channel = await this.getChannelById(originalMessage.channel); + + await this.postMessageToChannel(channel.name, OUTPUT.pointFrom(house, result), { as_user: true }); + } + + async getAllHousePointsCallback(originalMessage, house, result) { + const channel = await this.getChannelById(originalMessage.channel); + + await this.postMessageToChannel(channel.name, OUTPUT.getWinner(house, result), { as_user: true }); + } + + async announcePlainString(originalMessage, message) { + const channel = await this.getChannelById(originalMessage.channel); + + await this.postMessageToChannel(channel.name, message, { as_user: true, link_names: 1 }); + } + + async getUserList() { + const users = await this.getUsers(); + + return users; + } + + getName() { + return this.name; + } +} + +module.exports = SlackBot; diff --git a/lib/parseMessage.js b/lib/parseMessage.js deleted file mode 100644 index fe1a81e..0000000 --- a/lib/parseMessage.js +++ /dev/null @@ -1,25 +0,0 @@ -const { INPUT } = require('./word'); -const awardPoints = require('./controller/awardPoints'); -const deductPoints = require('./controller/deductPoints'); -const replyWithDumbledore = require('./controller/replyWithDumbledore'); -const replyWithGithub = require('./controller/replyWithGithub'); -const helpMessage = require('./controller/helpMessage'); - -function parseMessage(message) { - /* todo: determine what if handle multiple messages? */ - const text = message.text.toLowerCase(); - - if (text.includes(INPUT.POINTS_TO.CALL)) { - awardPoints.call(this, message); - } else if (text.includes(INPUT.POINTS_FROM.CALL)) { - deductPoints.call(this, message); - } else if (text.includes(INPUT.PROFESSOR.CALL) || text.includes(this.name)) { - replyWithDumbledore.call(this, message); - } else if (text.includes(INPUT.HELP.CALL)) { - helpMessage.call(this, message); - } else if (message.channel != null && message.channel === this.githubChannel) { - replyWithGithub.call(this, message); - } -} - -module.exports = parseMessage; diff --git a/lib/word.js b/lib/word.js index d005f78..cf90705 100644 --- a/lib/word.js +++ b/lib/word.js @@ -1,4 +1,3 @@ -const { capitalizeFirstLetter } = require('./helper/stringHandler'); module.exports = { INPUT: { @@ -10,61 +9,49 @@ module.exports = { }, PROFESSOR: { CALL: 'professor', - GET_WINNER: 'who is winning the house cup', - RESET_SCORE: 'reset the scores please', - SAY_HELLO: 'say hello to the students', - CAN_I_JOIN: 'can i please join', - SORTING_HAT: 'i would like to put my fate in the hands of the sorting hat', - OBLIVIATE: 'obliviate', GET_BEST_STUDENT: 'best student', GET_MEANEST_STUDENT: 'meanest student', TELL_ME_ABOUT: { CALL: 'tell me about', JASON_MOM: 'jason\'s mom', }, - START_SORTING: 'start the sorting ceremony', - GET_HOGWARTS_ROSTER: 'hogwarts roster', - SAVE_GIT_NAME: 'link my github name=', - SORT_REST: 'sort the rest' + SAVE_GIT_NAME: 'link my github name=' }, HELP: { CALL: 'help' } }, OUTPUT: { - pointTo: (house, result) => 'Congratulations ' + capitalizeFirstLetter(house) + '! ' + capitalizeFirstLetter(house) + ' house has ' + result.points + ' points!', - pointFrom: (house, result) => 'Alas ' + capitalizeFirstLetter(house) + '. ' + capitalizeFirstLetter(house) + ' house now only has ' + result.points + ' points. Do not dwell on your misdeeds, there is potential for greatness in all students!', - getWinner: (house, result) => capitalizeFirstLetter(house) + ' House: ' + result.points + '\n', - RESET_SCORE: 'The scores have been reset and we are ready for another great year at Hogwarts!', + pointTo: result => 'Congratulations ' + result.username + 'has ' + result.point + ' points!', + pointFrom: (result) => 'Alas house now only has ' + result.point + ' points. Do not dwell on your misdeeds, there is potential for greatness in all students!', SAY_HELLO: 'Welcome to Hogwarts everyone!\n Now, in a few moments you will pass through these doors and join your classmates, but before you take your seats, you must be sorted into your houses. They are Gryffindor, Hufflepuff, Ravenclaw, and Slytherin. Now while you\'re here, your house will be like your family. Your triumphs will earn you points. Any rule breaking, and you will lose points. At the end of the year, the house with the most points is awarded the house cup.\n I Albus Dumbledore will award points on your behalf. Just say `10 points to Gryffindor` + or `5 points to @benc` to award points', - canIJoin: (student, house) => 'Welcome ' + student.capitalizeFirstLetter() + ' the house of ' + house.capitalizeFirstLetter() + ' expects great things from you!', - OBLIVIATE: 'Poof, even a remembrall wont help you now Gilderoy', - getBestStudent: record => 'The head boy/girl of ' + capitalizeFirstLetter(record.house) + ' is @' + record.username + ' with ' + record.points_earned + ' points!', - ABOUT_FROGS: '\n I think they\'ve earned some Chocolate Frogs.', - getMeanestStudent: record => 'The student most likely to join the Inquisitorial Squad in ' + capitalizeFirstLetter(record.house) + ' is @' + record.username + ' who has taken a total of ' + record.points_taken + ' from their fellow students.', + getBestStudent: record => 'High score (boy/girl): ' + record.username + ' with ' + record.point + ' points!', + getMeanestStudent: record => 'Low score (boy/girl): ' + record.username + ' with ' + record.point + ' points!', TELL_ME_ABOUT: { PERSON: { DUMBLEDORE: 'Well my name is Albus Percival Wulfric Brian Dumbledore, I am Headmaster of Hogwarts, I am famous for discovering the 12 uses of Dragon\'s blood, and my favorite candy is Lemon Drops.', - student: record => record.username + ' belongs to ' + capitalizeFirstLetter(record.house) + ' House, they have: \n earned: ' + record.points_earned + ' points \n taken: ' + record.points_taken + ' points \n given: ' + record.points_given + ' points \nI\'m sure if you asked them in person they would tell you all this information themself. Good day.', + student: record => record.username + 'they have: ' + record.point + ' points \nI\'m sure if you asked them in person they would tell you all this information themself. Good day.', NOT_FOUND: 'I am unfamiliar with that student. In all my years I have never come across such a person. However, if they are over the age of 11 and possess magical abilities, I invite them to come to Hogwarts. Perhaps they are a muggle, or worse a Squib.' }, - fromHouse: (house, record) => { - let students = 'The students of ' + capitalizeFirstLetter(house) + ' House are:\n'; - - record.forEach((name) => { - students += name.username + '\n'; - }); - return students; - }, JASON_MOM: 'Not much is known about Jason\'s mom, except that she is thought to be responsible for the great internet unplugging of 2016', }, - START_SORTING: 'Students you have two choices for sorting. If you wish to choose your house you need only ask \'Professor can I please join Gryffindor\' For those more daring, you can let the Hat of Godric Gryffindor decide. Just say \'Professor I would like to put my fate in the hands of the Sorting Hat\' \n Good luck, let the sorting begin.', - saveGitName: (userName, gitName) => userName + '\'s github name is saved as ' + gitName, }, - HOUSE: { - GRYFFINDOR: 'gryffindor', - HUFFLEPUFF: 'hufflepuff', - RAVENCLAW: 'ravenclaw', - SLYTHRIN: 'slytherin' + DB: { + INFO: { + CALL: 'Info', + LASTRUN: 'lastrun' + }, + STUDENT: { + CALL: 'Student', + BOT_ID: 'botId', + USER_ID: 'userId', + POINT: 'point', + GITHUB_NAME: 'gitName' + }, + BOT: { + CALL: 'Bot', + BOT_API_KEY: 'botApi', + BOT_NAME: 'botName' + } } }; diff --git a/nodemon_sample.json b/nodemon.json similarity index 57% rename from nodemon_sample.json rename to nodemon.json index 610478d..7a2d685 100644 --- a/nodemon_sample.json +++ b/nodemon.json @@ -13,9 +13,12 @@ "lib/" ], "env": { - "NODE_ENV": "development", - "BOT_API_KEY": "", - "BOT_NAME": "bot1" + "NODE_ENV": "development" }, - "ext": "js json" + "ext": "js json", + "events": { + "start": "npm run lint", + "restart": "npm run lint", + "exit": "mongodb-runner stop" + } } diff --git a/package.json b/package.json index 743ae45..5b22be6 100644 --- a/package.json +++ b/package.json @@ -4,44 +4,79 @@ "description": "Dumbledore the Slackbot", "main": "lib/dumbledore.js", "scripts": { - "pretest": "npm run lint", - "test": "jasmine", + "pretest": "npm run lint && mongodb-runner start", + "test": "better-npm-run test", + "posttest": "mongodb-runner stop", "travis": "cp ./data/dumbledore_sample.db ./data/dumbledore.db", - "lint": "eslint -c .eslintrc lib", - "lintfix": "eslint -c .eslintrc lib --fix", + "lint": "eslint -c .eslintrc .", + "lintfix": "eslint -c .eslintrc . --fix", "start": "pm2 start ./pm2.json", "start-docker": "pm2 start ./pm2.json --no-daemon logs", "restart": "pm2 restart ./pm2.json", "stop": "pm2 stop ./pm2.json", - "dev": "nodemon ./bin/bot.js" + "predev": "mongodb-runner start", + "dev": "npm-run-all --parallel dev:*", + "dev:server": "better-npm-run dev:server", + "dev:client": "better-npm-run dev:client", + "build": "webpack --config ./page/webpack.config.prod.js" }, "repository": { "type": "git", "url": "https://github.com/kosslab-kr/Dumbledore" }, "license": "MIT", + "betterScripts": { + "test": { + "command": "jasmine" + }, + "dev:server": { + "command": "nodemon ./bin/bot.js --watch server --watch lib --watch bin" + }, + "dev:client": { + "command": "webpack-dev-server --config ./page/webpack.config.dev.js" + } + }, "dependencies": { "cron": "^1.3.0", + "express": "^4.16.2", + "kerberos": "0.0.23", + "parse": "^1.10.0", + "parse-dashboard": "^1.1.0", + "parse-server": "^2.6.3", + "prop-types": "^15.6.0", "random-js": "^1.0.8", - "slackbots": "^1.1.0", - "sqlite3": "^3.1.13" - }, -<<<<<<< HEAD - "bin": { - "dumbledore": "bot.js" + "react": "^16.0.0", + "react-dom": "^16.0.0", + "semantic-ui-css": "^2.2.12", + "semantic-ui-react": "^0.75.1", + "slackbots": "^1.1.0" }, - "keywords": [] -======= "engines": { "node": ">=7.0" }, "devDependencies": { + "@babel/plugin-transform-regenerator": "^7.0.0-beta.31", + "babel-core": "^6.26.0", + "babel-loader": "^7.1.2", + "babel-polyfill": "^6.26.0", + "babel-preset-es2015": "^6.24.1", + "babel-preset-es2017": "^6.24.1", + "babel-preset-react": "^6.24.1", + "better-npm-run": "^0.1.0", + "css-loader": "^0.28.7", "eslint": "^4.8.0", "eslint-config-airbnb": "^16.0.0", + "eslint-loader": "^1.9.0", "eslint-plugin-import": "^2.7.0", "eslint-plugin-jsx-a11y": "^6.0.2", "eslint-plugin-react": "^7.4.0", - "jasmine": "^2.8.0" + "file-loader": "^1.1.5", + "html-webpack-plugin": "^2.30.1", + "jasmine": "^2.8.0", + "mongodb-runner": "^3.6.1", + "npm-run-all": "^4.1.1", + "style-loader": "^0.19.0", + "webpack": "^3.8.0", + "webpack-dev-server": "^2.9.2" } ->>>>>>> young/master } diff --git a/page/src/components/Header.css b/page/src/components/Header.css new file mode 100644 index 0000000..5839bb3 --- /dev/null +++ b/page/src/components/Header.css @@ -0,0 +1,3 @@ +#header { + margin: 30px 0; +} \ No newline at end of file diff --git a/page/src/components/Header.js b/page/src/components/Header.js new file mode 100644 index 0000000..3af3a17 --- /dev/null +++ b/page/src/components/Header.js @@ -0,0 +1,13 @@ +import React from 'react'; +import pictogram from '../../static/pictogram.svg'; +import './Header.css'; + +const Header = () => { + return ( + + ); +}; + +export default Header; diff --git a/page/src/components/NavBar.css b/page/src/components/NavBar.css new file mode 100644 index 0000000..75be786 --- /dev/null +++ b/page/src/components/NavBar.css @@ -0,0 +1,10 @@ +#navbar { + background-color: black; + padding: 10px 20px; + display: flex; + box-sizing: border-box; + align-items: center; + height: 60px; + color: white; + font-size: 1.3rem; +} \ No newline at end of file diff --git a/page/src/components/NavBar.js b/page/src/components/NavBar.js new file mode 100644 index 0000000..b42a44a --- /dev/null +++ b/page/src/components/NavBar.js @@ -0,0 +1,12 @@ +import React from 'react'; +import './NavBar.css'; + +const NavBar = () => { + return ( + + ); +}; + +export default NavBar; diff --git a/page/src/components/Table.js b/page/src/components/Table.js new file mode 100644 index 0000000..c0aac8f --- /dev/null +++ b/page/src/components/Table.js @@ -0,0 +1,61 @@ +import React from 'react'; +import { Table as STable, Label, Icon } from 'semantic-ui-react'; +import PropTypes from 'prop-types'; + +const Table = ({ data }) => { + const [first, second, third] = data.slice(0, 3); + const firstRow = ( + + + {first.name} + {first.point} + + ); + const secondRow = ( + + + {second.name} + {second.point} + + ); + const thirdRow = ( + + + {third.name} + {third.point} + + ); + const Etc = data.slice(3).map(el => { + return ( + + {el.id + 1} + {el.name} + {el.point} + + ); + }); + + return ( + + + + Rank + Name + Point + + + + {firstRow} + {secondRow} + {thirdRow} + {Etc} + + + ); +}; + +Table.propTypes = { + data: PropTypes.arrayOf(PropTypes.object).isRequired +}; + +export default Table; diff --git a/page/src/containers/App.css b/page/src/containers/App.css new file mode 100644 index 0000000..770bf68 --- /dev/null +++ b/page/src/containers/App.css @@ -0,0 +1,7 @@ +body { + margin: 0; +} + +#container { + +} \ No newline at end of file diff --git a/page/src/containers/App.js b/page/src/containers/App.js new file mode 100644 index 0000000..0aa3f01 --- /dev/null +++ b/page/src/containers/App.js @@ -0,0 +1,29 @@ +import React, { Component } from 'react'; +import { Grid } from 'semantic-ui-react'; +import 'semantic-ui-css/semantic.min.css'; +import NavBar from '../components/NavBar'; +import Header from '../components/Header'; +import TableContainer from './TableContainer'; +import './App.css'; + +class App extends Component { + componentDidMount() { + console.log('cdm'); + } + + render() { + return ( +
+ + + +
+ + + +
+ ); + } +} + +export default App; diff --git a/page/src/containers/TableContainer.js b/page/src/containers/TableContainer.js new file mode 100644 index 0000000..8a96f26 --- /dev/null +++ b/page/src/containers/TableContainer.js @@ -0,0 +1,56 @@ +import React, { Component } from 'react'; +import { Grid, Loader } from 'semantic-ui-react'; +import Table from '../components/Table'; +import { getStudent } from '../helper/fetch'; + +class TableContainer extends Component { + constructor(props) { + super(props); + + this.state = { + bots: { isFetching: false, data: '' }, + students: { isFetching: false, data: '' } + }; + + this.fetchBots = this.fetchBots.bind(this); + } + + componentDidMount() { + this.fetchBots(); + } + + async fetchBots() { + await this.setState({ + bots: { + bots: this.state.bots, + isFetching: true + } + }); + + const students = await getStudent(); + + await this.setState({ + bots: { + data: students, + isFetching: false + } + }); + } + + render() { + const { bots, students } = this.state; + + return ( + + + {bots.isFetching || !bots.data ? : } + + + {students.isFetching || !students.data ? :
} + + + ); + } +} + +export default TableContainer; diff --git a/page/src/helper/fetch.js b/page/src/helper/fetch.js new file mode 100644 index 0000000..d7fec1b --- /dev/null +++ b/page/src/helper/fetch.js @@ -0,0 +1,42 @@ +async function fakeAsync(delayTime = 1000) { + await new Promise(resolve => setTimeout(resolve, delayTime)); +} + +export async function getBot() { + // Todo: create logic for get bot + const botDummy = new Array(10).fill(undefined).map((e, idx) => ( + { + id: idx, + name: `bot ${idx}`, + point: `${idx * 100}` + } + )).sort((a, b) => b.point - a.point); + + await fakeAsync(); + + return botDummy; +} + +export async function getStudent(id) { + console.log(id); + // Todo: create logic for get student + + const studentDummy = new Array(10).fill(undefined).map((e, idx) => ( + { + id: idx, + name: `student ${idx}`, + point: `${idx * 10}` + } + )).sort((a, b) => b.point - a.point); + + await fakeAsync(); + + return studentDummy; +} + +export function createBot(key, password) { + const response = { key, password }; + // Todo: create logic for add bot + return response; +} + diff --git a/page/src/index.js b/page/src/index.js new file mode 100644 index 0000000..bf8c03f --- /dev/null +++ b/page/src/index.js @@ -0,0 +1,8 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './containers/App'; + +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/page/static/pictogram.svg b/page/static/pictogram.svg new file mode 100644 index 0000000..322de9e --- /dev/null +++ b/page/static/pictogram.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/page/webpack.config.dev.js b/page/webpack.config.dev.js new file mode 100644 index 0000000..ab7cf1a --- /dev/null +++ b/page/webpack.config.dev.js @@ -0,0 +1,81 @@ +const webpack = require('webpack'); +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +require('babel-polyfill'); + +module.exports = { + entry: ['babel-polyfill', './page/src/index.js'], + output: { + path: path.resolve(__dirname, '/public'), + filename: 'bundle.js', + }, + devServer: { + filename: 'bundle.js', + port: 8080, + proxy: { + '/': 'http://localhost:1337' + }, + watchOptions: { + ignore: [path.resolve('lib/*.js'), path.resolve('server/*.js')], + aggregateTimeout: 300 + } + }, + module: { + rules: [ + { + enforce: 'pre', + test: /\.js$/, + exclude: /node_modules/, + loader: 'eslint-loader', + options: { + fix: true, + failOnWarning: true, + failOnError: true, + configFile: require.resolve('../.eslintrc'), + } + }, + { + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel-loader', + options: { + presets: [ + 'react', + 'es2015', + 'es2017' + ], + plugins: [ + 'transform-regenerator' + ], + } + }, + { + test: /\.css$/, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: { + importLoaders: 1, + } + } + ] + }, + { + test: /\.(png|ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, + loader: 'file-loader?name=fonts/[name].[ext]' + } + ] + }, + devtool: 'eval', + plugins: [ + new HtmlWebpackPlugin({ + inject: true, + filename: '../public/index.html', + template: path.resolve(__dirname, '../public/index.html'), + }), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('development') + }) + ] +}; diff --git a/parse-server/cloud/main.js b/parse-server/cloud/main.js new file mode 100644 index 0000000..4fd6c00 --- /dev/null +++ b/parse-server/cloud/main.js @@ -0,0 +1,4 @@ + +Parse.Cloud.define('hello', function (req, res) { + res.success('Hi'); +}); diff --git a/parse-server/parse.js b/parse-server/parse.js new file mode 100644 index 0000000..0df07b4 --- /dev/null +++ b/parse-server/parse.js @@ -0,0 +1,81 @@ +// parse server +const express = require('express'); +const { ParseServer } = require('parse-server'); +const path = require('path'); + +// parse dashboard +const ParseDashboard = require('parse-dashboard'); + +class ParseInstance { + constructor(settings) { + this.settings = settings; + } + + create() { + return new Promise((resolve, reject) => { + try { + const { settings } = this; + + // express + const app = express(); + + // Serve static assets from the /public folder + // app.use('/public', express.static(path.join(__dirname, '../public'))); + + // parse server + const api = new ParseServer({ + databaseURI: settings.databaseURI || 'mongodb://localhost/test', + cloud: settings.cloud || path.join(__dirname, '/cloud/main.js'), + appId: settings.appId || 'myAppId', + masterKey: settings.masterKey || 'masterKey', // Add your master key here. Keep it secret! + serverURL: settings.serverURL || 'http://localhost:1337/parse', // Don't forget to change to https if needed + }); + + // Serve the Parse API on the /parse URL prefix + const mountPath = settings.MOUNT_PATH || '/parse'; + app.use(mountPath, api); + + // parse dashboard + const dashboard = new ParseDashboard({ + apps: [ + { + serverURL: settings.serverURL || 'http://localhost:1337/parse', + appId: settings.appId || 'myAppId', + masterKey: settings.masterKey || 'masterKey', + appName: 'dumbledore', + } + ], + users: [ + { + user: settings.user || 'parseapp', + pass: settings.pass || 'parsepassword' + } + ] + }, true); // allowInsecureHTTP + + // make the Parse dashboard available at /dashboard + app.use('/dashboard', dashboard); + + // Parse Server plays nicely with the rest of your web routes + app.get('/', function (req, res) { + res.status(200).send('Make sure to star the parse-server repo on GitHub!'); + }); + + const port = settings.port || 1337; + this.server = app.listen(port, function () { + console.log('parse-server running on port ' + port + '.'); + resolve(); + }); + } catch (error) { + reject(error); + } + }); + } + + close() { + if (!this.server) return Promise.resolve(); + return this.server.close(); + } +} + +module.exports = ParseInstance; diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..cf03bb5 --- /dev/null +++ b/public/index.html @@ -0,0 +1,12 @@ + + + + + + + Dumbledore + + +
+ + \ No newline at end of file diff --git a/server/app.js b/server/app.js new file mode 100644 index 0000000..d5721a0 --- /dev/null +++ b/server/app.js @@ -0,0 +1,26 @@ +const express = require('express'); +const path = require('path'); + +class ServerInstance { + constructor(settings) { + this.settings = settings; + } + + create() { + return new Promise((resolve) => { + const { settings } = this; + const port = settings.port || 55555; + const app = express(); + + app.get('/', (req, res) => { + res.sendFile(path.join(__dirname, '../public/index.html')); + }); + this.server = app.listen(port, () => { + console.log('server run...' + port + '.'); + resolve(); + }); + }); + } +} + +module.exports = ServerInstance; diff --git a/spec/botSpec.js b/spec/botSpec.js index 021f199..2b18f13 100644 --- a/spec/botSpec.js +++ b/spec/botSpec.js @@ -1,9 +1,14 @@ describe('bot', function () { const Dumbledore = require('../lib/dumbledore'); + const { atob } = require('../lib/helper/common'); it('should be able to launch', (done) => { + // base64 encoded token + let token = process.env.BOT_API_KEY; + if (token.length > 42) token = atob(token); + const dumbledore = new Dumbledore({ - token: process.env.BOT_API_KEY, + token, dbPath: process.env.BOT_DB_PATH, name: process.env.BOT_NAME, githubChannel: process.env.BOT_GITHUB_CHANNEL_ID diff --git a/spec/helperSpec.js b/spec/helperSpec.js new file mode 100644 index 0000000..43685c7 --- /dev/null +++ b/spec/helperSpec.js @@ -0,0 +1,74 @@ +describe('In helper', function () { + const stringHandler = require('../lib/helper/stringHandler'); + const SlackBot = require('../lib/helper/slackBot'); + const { atob } = require('../lib/helper/common'); + const { OUTPUT } = require('../lib/word.js'); + let token; + let slackBot; + + const fake = { + type: 'message', + channel: 'C7A6B2CHF', + user: 'U7B970DBR', + text: '', + ts: '1509022008.000293', + source_team: 'T7B8QKJ8P', + team: 'T7B8QKJ8P' + }; + + beforeAll(() => { + token = process.env.BOT_API_KEY; + if (token.length > 42) token = atob(token); + + slackBot = new SlackBot({ token, name: process.env.BOT_NAME }); + }); + + it('First letter should be capitalized', () => { + const first = stringHandler.capitalizeFirstLetter('letter').charCodeAt(0); + let check = false; + + if (first < 91 && first > 64) check = true; + + expect(check).toBe(true); + }); + + it('Any functions should not be `undefined` in slackBot', () => { + expect(slackBot.awardPointsCallback).toBeDefined(); + expect(slackBot.deductPointsCallback).toBeDefined(); + expect(slackBot.getAllHousePointsCallback).toBeDefined(); + expect(slackBot.announcePlainString).toBeDefined(); + expect(slackBot.getUserList).toBeDefined(); + expect(slackBot.getName).toBeDefined(); + }); + + it('Slack bot should say hello', (done) => { + let check = false; + + slackBot.announcePlainString(fake, OUTPUT.SAY_HELLO).then(() => { + try { + check = true; + } catch (err) { + console.error(err); + } + expect(check).toBe(true); + done(); + }); + }); + + it('Slack bot should get its own name', () => { + expect(slackBot.getName()).toEqual(process.env.BOT_NAME); + }); + + it('Slack bot should get user list', (done) => { + slackBot.getUserList().then((o) => { + let check = false; + + if (o !== undefined) { + check = true; + } + + expect(check).toBe(true); + done(); + }); + }); +}); diff --git a/spec/helpers/helper.js b/spec/helpers/helper.js new file mode 100644 index 0000000..d3eaa9d --- /dev/null +++ b/spec/helpers/helper.js @@ -0,0 +1,45 @@ +const ParseInstance = require('../../parse-server/parse'); +const Parse = require('parse/node'); + +jasmine.DEFAULT_TIMEOUT_INTERVAL = process.env.PARSE_SERVER_TEST_TIMEOUT || 5000; + +let server; + +function createServer() { + return new Promise((resolve, reject) => { + if (server) { + return server.close(() => { + server = undefined; + createServer().then(resolve, reject); + }); + } + + try { + server = new ParseInstance({ + databaseURI: process.env.DATABASE_URI, + cloud: process.env.CLOUD_CODE_MAIN, + appId: process.env.APP_ID, + masterKey: process.env.MASTER_KEY, + serverURL: process.env.SERVER_URL, + port: process.env.PORT, + mountPath: process.env.MOUNT_PATH, + user: process.env.ADMIN_NAME, + pass: process.env.ADMIN_PASS, + }); + + server.create().then(() => { + // parse js sdk + Parse.initialize(process.env.APP_ID || 'myAppId', null, process.env.MASTER_KEY || 'masterKey'); + Parse.serverURL = process.env.SERVER_URL || 'http://localhost:1337/parse'; + resolve(); + }, reject); + } catch (error) { + reject(error); + } + }); +} + +beforeEach((done) => { + if (server) return done(); + createServer().then(done, done.fail); +}); diff --git a/spec/parseSpec.js b/spec/parseSpec.js new file mode 100644 index 0000000..60a23fd --- /dev/null +++ b/spec/parseSpec.js @@ -0,0 +1,26 @@ +describe('parse', function () { + it('parse application id', (done) => { + expect(Parse.applicationId).toEqual(process.env.APP_ID || 'myAppId'); + done(); + }); + + it('create object', (done) => { + let objId; + const _obj = new Parse.Object('AnObject'); + _obj.save() + .then(obj => { + objId = obj.id; + const q = new Parse.Query('AnObject'); + q.descending('createdAt'); + return q.first(); + }) + .then(obj => { + expect(obj.id).toEqual(objId); + done(); + }) + .catch((error) => { + fail(JSON.stringify(error)); + done(); + }); + }); +});