From 387778c9ee63841f5c26efd1d1c0e85b5a78e6e0 Mon Sep 17 00:00:00 2001 From: Manassarn Manoonchai Date: Thu, 1 Apr 2021 13:02:21 +0700 Subject: [PATCH 01/12] feat: init 2.0 skeleton, starting from db service --- .github/workflows/pull-test.yml | 60 +++++++++++++++++------------- 9armbot-2.0/.prettierrc | 6 +++ 9armbot-2.0/index.ts | 11 ++++++ 9armbot-2.0/jest.config.js | 5 +++ 9armbot-2.0/services/bot.ts | 1 + 9armbot-2.0/services/db.ts | 50 +++++++++++++++++++++++++ 9armbot-2.0/services/discord.ts | 19 ++++++++++ 9armbot-2.0/services/twitch.ts | 39 +++++++++++++++++++ 9armbot-2.0/test/db.test.ts | 66 +++++++++++++++++++++++++++++++++ package.json | 14 ++++++- tsconfig.json | 28 ++++++++++++++ 11 files changed, 271 insertions(+), 28 deletions(-) create mode 100644 9armbot-2.0/.prettierrc create mode 100644 9armbot-2.0/index.ts create mode 100644 9armbot-2.0/jest.config.js create mode 100644 9armbot-2.0/services/bot.ts create mode 100644 9armbot-2.0/services/db.ts create mode 100644 9armbot-2.0/services/discord.ts create mode 100644 9armbot-2.0/services/twitch.ts create mode 100644 9armbot-2.0/test/db.test.ts create mode 100644 tsconfig.json diff --git a/.github/workflows/pull-test.yml b/.github/workflows/pull-test.yml index 802dd13..e1950a9 100644 --- a/.github/workflows/pull-test.yml +++ b/.github/workflows/pull-test.yml @@ -1,26 +1,34 @@ -name: PR unit test - -on: - pull_request: - paths: - - '9armbot/**' - - 'core/**' - - '__test__/**' - branches: - - main - -jobs: - build: - runs-on: windows-latest - steps: - - uses: actions/checkout@v2 - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: '14' - architecture: 'x64' - check-latest: true - - name: Install dependencies - run: npm install - - name: Test - run: npm test +name: PR unit test + +on: + push: + branches: + - main + - v2.0.0 + pull_request: + paths: + - "9armbot/**" + - "9armbot-2.0/**" + - "core/**" + - "__test__/**" + branches: + - main + - v2.0.0 + +jobs: + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: "14" + architecture: "x64" + check-latest: true + - name: Install dependencies + run: npm install + - name: Test + run: npm test + - name: Test (2.0) + run: npm test-2.0 diff --git a/9armbot-2.0/.prettierrc b/9armbot-2.0/.prettierrc new file mode 100644 index 0000000..b04bde6 --- /dev/null +++ b/9armbot-2.0/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "all" +} diff --git a/9armbot-2.0/index.ts b/9armbot-2.0/index.ts new file mode 100644 index 0000000..09f5054 --- /dev/null +++ b/9armbot-2.0/index.ts @@ -0,0 +1,11 @@ +import { twitchService } from './services/twitch' +import { discordService } from './services/discord' + +async function main() { + await twitchService() + await discordService() + + console.log('9armbot 2.0 Running...') +} + +main() diff --git a/9armbot-2.0/jest.config.js b/9armbot-2.0/jest.config.js new file mode 100644 index 0000000..3a8d305 --- /dev/null +++ b/9armbot-2.0/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", + testMatch: ["**/test/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[tj]s?(x)"], +}; diff --git a/9armbot-2.0/services/bot.ts b/9armbot-2.0/services/bot.ts new file mode 100644 index 0000000..2f9b513 --- /dev/null +++ b/9armbot-2.0/services/bot.ts @@ -0,0 +1 @@ +export class Bot {} diff --git a/9armbot-2.0/services/db.ts b/9armbot-2.0/services/db.ts new file mode 100644 index 0000000..a6cefa9 --- /dev/null +++ b/9armbot-2.0/services/db.ts @@ -0,0 +1,50 @@ +import find from 'lodash/find' + +interface IDb { + players: IPlayer[] +} + +interface IPlayer { + uid: string + username: string +} + +export class Db { + // TODO: Fetch from json file instead + private db: IDb = { + players: [], + } + + public read() { + return this.db + } + + public createPlayer(username: string) { + // Don't re-create existing player + const player = find(this.db.players, (p) => { + return p.username === username + }) + + if (player) { + return player + } + + const newPlayer = { + uid: 'Changeme', + username, + } + + this.db.players.push(newPlayer) + + return newPlayer + } + + public getPlayerbyUsername(username: string) { + return find(this.db.players, (p) => { + return p.username === username + }) + } +} + +// Use singleton pattern to mitigate multiple readers/writers +export const dbService = new Db() diff --git a/9armbot-2.0/services/discord.ts b/9armbot-2.0/services/discord.ts new file mode 100644 index 0000000..29c40ee --- /dev/null +++ b/9armbot-2.0/services/discord.ts @@ -0,0 +1,19 @@ +import dotenv from 'dotenv' +import { dbService } from './db' + +dotenv.config() + +export async function discordService() { + // Test db reading + setInterval(() => { + console.log('discord read db from dbservice', dbService.read()) + }, 2500) + + setInterval(() => { + console.log( + 'discord', + 'test adding random players', + dbService.createPlayer(Math.random().toString()), + ) + }, 7000) +} diff --git a/9armbot-2.0/services/twitch.ts b/9armbot-2.0/services/twitch.ts new file mode 100644 index 0000000..4ebb103 --- /dev/null +++ b/9armbot-2.0/services/twitch.ts @@ -0,0 +1,39 @@ +import tmi from 'tmi.js' +import fs from 'fs' +import dotenv from 'dotenv' +import path from 'path' +import { dbService } from './db' + +dotenv.config() + +let oauth_token = fs.readFileSync( + path.resolve(__dirname, '../../9armbot/oauth_token'), + 'utf8', +) + +export async function twitchService() { + const client = new tmi.Client({ + options: { debug: true }, + connection: { reconnect: true }, + identity: { + username: process.env.tmi_username, + password: oauth_token, + }, + channels: [process.env.tmi_channel_name as string], + }) + + await client.connect() + + // Test db reading + setInterval(() => { + console.log('twitch read db from dbservice', dbService.read()) + }, 2000) + + setInterval(() => { + console.log( + 'twitch', + 'test adding random players', + dbService.createPlayer(Math.random().toString()), + ) + }, 6000) +} diff --git a/9armbot-2.0/test/db.test.ts b/9armbot-2.0/test/db.test.ts new file mode 100644 index 0000000..aca2dc4 --- /dev/null +++ b/9armbot-2.0/test/db.test.ts @@ -0,0 +1,66 @@ +import { Db } from '../services/db' + +let db: Db + +beforeEach(() => { + db = new Db() +}) + +describe('read db', () => { + describe('when db is not existed yet', () => { + it('returns default data', () => { + expect(db.read()).toEqual({ + players: [], + }) + }) + }) +}) + +describe('CRUD players', () => { + describe('createPlayer', () => { + it('can create new player by username (twitch for now)', () => { + db.createPlayer('foo') + + expect(db.read()).toEqual({ + players: [ + { + uid: expect.any(String), + username: 'foo', + }, + ], + }) + }) + + it('does not create new player if username is existed', () => { + db.createPlayer('foo') + db.createPlayer('foo') + db.createPlayer('foo') + + expect(db.read()).toEqual({ + players: [ + { + uid: expect.any(String), + username: 'foo', + }, + ], + }) + }) + }) + + describe('getPlayerbyUsername', () => { + it('returns undefined if player is not found', () => { + expect(db.getPlayerbyUsername('foo')).toBeUndefined + }) + + it('returns player if found by username', () => { + db.createPlayer('foo') + + expect(db.getPlayerbyUsername('foo')).toEqual({ + uid: expect.any(String), + username: 'foo', + }) + + expect(db.getPlayerbyUsername('bar')).toBeUndefined + }) + }) +}) diff --git a/package.json b/package.json index d300646..b6cb340 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "scripts": { "test": "jest --verbose", "start": "cd 9armbot && node 9armbot.js", - "dev": "cd 9armbot && nodemon 9armbot.js --ignore '*.json'" + "dev": "cd 9armbot && nodemon 9armbot.js --ignore '*.json'", + "dev-2.0": "ts-node-dev --respawn 9armbot-2.0", + "test-2.0": "jest --config 9armbot-2.0/jest.config.js" }, "repository": { "type": "git", @@ -20,15 +22,23 @@ "homepage": "https://github.com/thananon/twitch_tools#readme", "dependencies": { "axios": "^0.21.1", + "discord.js": "^12.5.2", "dotenv": "^8.2.0", "ejs": "^3.1.6", "express": "^4.17.1", + "lodash": "^4.17.21", "multer": "^1.4.2", "socket.io": "^3.1.1", "tmi.js": "^1.7.1" }, "devDependencies": { + "@types/jest": "^26.0.22", + "@types/lodash": "^4.14.168", + "@types/tmi.js": "^1.7.1", + "jest": "^26.6.3", "nodemon": "^2.0.7", - "jest": "^26.6.3" + "ts-jest": "^26.5.4", + "ts-node-dev": "^1.1.6", + "typescript": "^4.2.3" } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e611389 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "lib": ["dom", "es6", "es2017", "esnext.asynciterable"], + "sourceMap": true, + "outDir": "./dist", + "moduleResolution": "node", + "removeComments": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + "baseUrl": ".", + "allowJs": true + }, + "exclude": ["node_modules"], + "include": ["./9armbot-2.0/**/*.ts"] +} From e20ebe080c88e7ab13883cc1d087b791e620d348 Mon Sep 17 00:00:00 2001 From: Manassarn Manoonchai Date: Thu, 1 Apr 2021 23:16:40 +0700 Subject: [PATCH 02/12] feat: add db.load --- 9armbot-2.0/index.ts | 2 ++ 9armbot-2.0/services/db.ts | 15 ++++++++++--- 9armbot-2.0/test/db.test.ts | 44 ++++++++++++++++++++++++++++++------- package.json | 3 ++- 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/9armbot-2.0/index.ts b/9armbot-2.0/index.ts index 09f5054..5849710 100644 --- a/9armbot-2.0/index.ts +++ b/9armbot-2.0/index.ts @@ -1,7 +1,9 @@ import { twitchService } from './services/twitch' import { discordService } from './services/discord' +import { dbService } from './services/db' async function main() { + dbService.load() await twitchService() await discordService() diff --git a/9armbot-2.0/services/db.ts b/9armbot-2.0/services/db.ts index a6cefa9..6804c3a 100644 --- a/9armbot-2.0/services/db.ts +++ b/9armbot-2.0/services/db.ts @@ -1,20 +1,29 @@ import find from 'lodash/find' +import fs from 'fs' -interface IDb { +export interface IDb { players: IPlayer[] } -interface IPlayer { +export interface IPlayer { uid: string username: string } export class Db { - // TODO: Fetch from json file instead private db: IDb = { players: [], } + public load() { + try { + const playersJson = fs.readFileSync('./players.json', 'utf8') + this.db = JSON.parse(playersJson) + } catch (err) { + console.log('File not found, use default blank db.', err.message) + } + } + public read() { return this.db } diff --git a/9armbot-2.0/test/db.test.ts b/9armbot-2.0/test/db.test.ts index aca2dc4..bef1421 100644 --- a/9armbot-2.0/test/db.test.ts +++ b/9armbot-2.0/test/db.test.ts @@ -1,4 +1,9 @@ -import { Db } from '../services/db' +import { Db, IDb } from '../services/db' +import fs from 'fs' + +jest.mock('fs') + +const mockFs = fs as jest.Mocked let db: Db @@ -6,18 +11,41 @@ beforeEach(() => { db = new Db() }) -describe('read db', () => { - describe('when db is not existed yet', () => { - it('returns default data', () => { - expect(db.read()).toEqual({ - players: [], - }) +describe('#load', () => { + it('loads data from json file and initiate players', () => { + const dbFileContent: IDb = { + players: [ + { + uid: 'uid', + username: 'foo', + }, + ], + } + + mockFs.readFileSync.mockReturnValueOnce(JSON.stringify(dbFileContent)) + + db.load() + + expect(db.read()).toEqual(dbFileContent) + expect(mockFs.readFileSync).toHaveBeenCalledTimes(1) + }) + + it('falls back to default if file does not exist', () => { + const dbFileBlank: IDb = { + players: [], + } + + mockFs.readFileSync.mockImplementation(() => { + throw new Error('ENOENT: no such file or directory') }) + + expect(db.read()).toEqual(dbFileBlank) + expect(mockFs.readFileSync).toHaveBeenCalledTimes(1) }) }) describe('CRUD players', () => { - describe('createPlayer', () => { + describe('#createPlayer', () => { it('can create new player by username (twitch for now)', () => { db.createPlayer('foo') diff --git a/package.json b/package.json index b6cb340..ca9d92e 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "start": "cd 9armbot && node 9armbot.js", "dev": "cd 9armbot && nodemon 9armbot.js --ignore '*.json'", "dev-2.0": "ts-node-dev --respawn 9armbot-2.0", - "test-2.0": "jest --config 9armbot-2.0/jest.config.js" + "test-2.0": "jest --config 9armbot-2.0/jest.config.js", + "test-2.0:watch": "jest --config 9armbot-2.0/jest.config.js --watch" }, "repository": { "type": "git", From c11473e4f58f17515a79476f11d2f9f9247cb64a Mon Sep 17 00:00:00 2001 From: Manassarn Manoonchai Date: Thu, 1 Apr 2021 23:24:55 +0700 Subject: [PATCH 03/12] feat: use nanoid to generate unique id --- 9armbot-2.0/services/db.ts | 3 ++- package.json | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/9armbot-2.0/services/db.ts b/9armbot-2.0/services/db.ts index 6804c3a..f4021b6 100644 --- a/9armbot-2.0/services/db.ts +++ b/9armbot-2.0/services/db.ts @@ -1,5 +1,6 @@ import find from 'lodash/find' import fs from 'fs' +import { nanoid } from 'nanoid' export interface IDb { players: IPlayer[] @@ -39,7 +40,7 @@ export class Db { } const newPlayer = { - uid: 'Changeme', + uid: nanoid(), username, } diff --git a/package.json b/package.json index ca9d92e..0c26454 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "express": "^4.17.1", "lodash": "^4.17.21", "multer": "^1.4.2", + "nanoid": "^3.1.22", "socket.io": "^3.1.1", "tmi.js": "^1.7.1" }, From 302749915be6e74d63402ac3733b393c0df499be Mon Sep 17 00:00:00 2001 From: Manassarn Manoonchai Date: Thu, 1 Apr 2021 23:42:46 +0700 Subject: [PATCH 04/12] feat: db.save() --- 9armbot-2.0/index.ts | 6 ++++ 9armbot-2.0/services/db.ts | 20 ++++++++--- 9armbot-2.0/test/db.test.ts | 67 ++++++++++++++++++++++++------------- 3 files changed, 65 insertions(+), 28 deletions(-) diff --git a/9armbot-2.0/index.ts b/9armbot-2.0/index.ts index 5849710..7d8af7e 100644 --- a/9armbot-2.0/index.ts +++ b/9armbot-2.0/index.ts @@ -4,10 +4,16 @@ import { dbService } from './services/db' async function main() { dbService.load() + dbService.save() + await twitchService() await discordService() console.log('9armbot 2.0 Running...') + + setInterval(() => { + console.log('db', 'save database', dbService.save()) + }, 15000) } main() diff --git a/9armbot-2.0/services/db.ts b/9armbot-2.0/services/db.ts index f4021b6..ef930aa 100644 --- a/9armbot-2.0/services/db.ts +++ b/9armbot-2.0/services/db.ts @@ -2,6 +2,7 @@ import find from 'lodash/find' import fs from 'fs' import { nanoid } from 'nanoid' +const DB_PATH = './players-2.0.json' export interface IDb { players: IPlayer[] } @@ -16,16 +17,27 @@ export class Db { players: [], } - public load() { + public load(): void { try { - const playersJson = fs.readFileSync('./players.json', 'utf8') + const playersJson = fs.readFileSync(DB_PATH, 'utf8') this.db = JSON.parse(playersJson) + console.log(`Loaded ${this.db.players.length} players.`) } catch (err) { - console.log('File not found, use default blank db.', err.message) + console.log('[ERROR] File not found, use default blank db.', err.message) } } - public read() { + public save(): void { + try { + const data = JSON.stringify(this.read(), null, 2) + fs.writeFileSync(DB_PATH, data, 'utf8') + console.log(`Saved ${this.db.players.length} players.`) + } catch (err) { + console.log('[ERROR] Cannot write file.', err.message) + } + } + + public read(): IDb { return this.db } diff --git a/9armbot-2.0/test/db.test.ts b/9armbot-2.0/test/db.test.ts index bef1421..6e7c27e 100644 --- a/9armbot-2.0/test/db.test.ts +++ b/9armbot-2.0/test/db.test.ts @@ -1,5 +1,5 @@ -import { Db, IDb } from '../services/db' import fs from 'fs' +import { Db, IDb } from '../services/db' jest.mock('fs') @@ -11,36 +11,55 @@ beforeEach(() => { db = new Db() }) -describe('#load', () => { - it('loads data from json file and initiate players', () => { - const dbFileContent: IDb = { - players: [ - { - uid: 'uid', - username: 'foo', - }, - ], - } +describe('Database', () => { + describe('#load', () => { + it('loads data from json file and initiate players', () => { + const dbFileContent: IDb = { + players: [ + { + uid: 'uid', + username: 'foo', + }, + ], + } - mockFs.readFileSync.mockReturnValueOnce(JSON.stringify(dbFileContent)) + mockFs.readFileSync.mockReturnValueOnce(JSON.stringify(dbFileContent)) - db.load() + db.load() - expect(db.read()).toEqual(dbFileContent) - expect(mockFs.readFileSync).toHaveBeenCalledTimes(1) - }) + expect(db.read()).toEqual(dbFileContent) + expect(mockFs.readFileSync).toHaveBeenCalledTimes(1) + }) - it('falls back to default if file does not exist', () => { - const dbFileBlank: IDb = { - players: [], - } + it('falls back to default if file does not exist', () => { + const dbFileBlank: IDb = { + players: [], + } - mockFs.readFileSync.mockImplementation(() => { - throw new Error('ENOENT: no such file or directory') + mockFs.readFileSync.mockImplementation(() => { + throw new Error('ENOENT: no such file or directory') + }) + + expect(db.read()).toEqual(dbFileBlank) + expect(mockFs.readFileSync).toHaveBeenCalledTimes(1) }) + }) + + describe('save', () => { + it('saves the current database state to json file', () => { + db.createPlayer('foo') + + const expectedJson = JSON.stringify(db.read(), null, 2) - expect(db.read()).toEqual(dbFileBlank) - expect(mockFs.readFileSync).toHaveBeenCalledTimes(1) + db.save() + + expect(mockFs.writeFileSync).toHaveBeenCalledTimes(1) + expect(mockFs.writeFileSync).toHaveBeenCalledWith( + expect.any(String), + expectedJson, + 'utf8', + ) + }) }) }) From ef3d7d25f680a83730eb588d4b8792235cf41810 Mon Sep 17 00:00:00 2001 From: Manassarn Manoonchai Date: Thu, 1 Apr 2021 23:45:55 +0700 Subject: [PATCH 05/12] feat: show file path in console, and gitignore the file --- .gitignore | 1 + 9armbot-2.0/services/db.ts | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 8812074..b04b65f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ node_modules 9armbot/players.json package-lock.json yarn.lock +players-2.0.json diff --git a/9armbot-2.0/services/db.ts b/9armbot-2.0/services/db.ts index ef930aa..d6d3927 100644 --- a/9armbot-2.0/services/db.ts +++ b/9armbot-2.0/services/db.ts @@ -1,8 +1,9 @@ import find from 'lodash/find' import fs from 'fs' import { nanoid } from 'nanoid' +import path from 'path' -const DB_PATH = './players-2.0.json' +const DB_PATH = path.resolve(process.cwd(), './players-2.0.json') export interface IDb { players: IPlayer[] } @@ -21,7 +22,7 @@ export class Db { try { const playersJson = fs.readFileSync(DB_PATH, 'utf8') this.db = JSON.parse(playersJson) - console.log(`Loaded ${this.db.players.length} players.`) + console.log(`Loaded ${this.db.players.length} players from ${DB_PATH}.`) } catch (err) { console.log('[ERROR] File not found, use default blank db.', err.message) } @@ -31,7 +32,7 @@ export class Db { try { const data = JSON.stringify(this.read(), null, 2) fs.writeFileSync(DB_PATH, data, 'utf8') - console.log(`Saved ${this.db.players.length} players.`) + console.log(`Saved ${this.db.players.length} players to ${DB_PATH}.`) } catch (err) { console.log('[ERROR] Cannot write file.', err.message) } From 0e0e545a40688a1a04861e89c2491b8362aeb02a Mon Sep 17 00:00:00 2001 From: Manassarn Manoonchai Date: Fri, 2 Apr 2021 00:09:15 +0700 Subject: [PATCH 06/12] feat: add db console --- .gitignore | 1 + 9armbot-2.0/console.ts | 28 ++++++++++++++++++++++++++++ README.md | 6 ++++++ package.json | 3 ++- 4 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 9armbot-2.0/console.ts diff --git a/.gitignore b/.gitignore index b04b65f..d16d8a4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ node_modules package-lock.json yarn.lock players-2.0.json +.node_repl_history diff --git a/9armbot-2.0/console.ts b/9armbot-2.0/console.ts new file mode 100644 index 0000000..a135354 --- /dev/null +++ b/9armbot-2.0/console.ts @@ -0,0 +1,28 @@ +/** + * 9armbot Console + * Warning : Since this is not sharing the same process as the NodeJS server, + * writing to db will overwrite everything! (Singleton does not work across processes, damn!) + */ + +import repl from 'repl' +import { dbService } from './services/db' +import _ from 'lodash' + +const replServer = repl.start({ + prompt: `9armbot(${process.env.NODE_ENV || 'development'}) > `, +}) + +replServer.setupHistory('./.node_repl_history', () => {}) + +// Preload database +dbService.load() + +console.log('Database loaded, press enter to continue.') + +// Access db eg. `db.read()` +// Type `db.` then press Tab to see all available commands +replServer.context.db = dbService + +// Lodash (_ is reserved, use l or __ instead) +replServer.context.l = _ +replServer.context.__ = _ diff --git a/README.md b/README.md index b8792fb..fa3a0a6 100644 --- a/README.md +++ b/README.md @@ -60,3 +60,9 @@ $ npm install $ npm start ``` > More information about `oauth_token` [here](https://dev.twitch.tv/docs/irc). + +### Debug Console + +```bash +$ npm run console +``` \ No newline at end of file diff --git a/package.json b/package.json index 0c26454..660b838 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "dev": "cd 9armbot && nodemon 9armbot.js --ignore '*.json'", "dev-2.0": "ts-node-dev --respawn 9armbot-2.0", "test-2.0": "jest --config 9armbot-2.0/jest.config.js", - "test-2.0:watch": "jest --config 9armbot-2.0/jest.config.js --watch" + "test-2.0:watch": "jest --config 9armbot-2.0/jest.config.js --watch", + "console": "ts-node 9armbot-2.0/console" }, "repository": { "type": "git", From 55d6d7e6fd4267bafc5a8b3d2bd75a98d045a1d0 Mon Sep 17 00:00:00 2001 From: Manassarn Manoonchai Date: Fri, 2 Apr 2021 00:09:47 +0700 Subject: [PATCH 07/12] feat: add graceful shutdown for 2.0 process --- 9armbot-2.0/services/db.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/9armbot-2.0/services/db.ts b/9armbot-2.0/services/db.ts index d6d3927..85861da 100644 --- a/9armbot-2.0/services/db.ts +++ b/9armbot-2.0/services/db.ts @@ -69,5 +69,21 @@ export class Db { } } +// Graceful Shutdown +function gracefulShutdown() { + console.log('\nPre-Close') + dbService.save() + setTimeout(() => { + console.log('Closed') + process.exit(0) + }, 1000) +} +process.on('SIGINT', () => gracefulShutdown()) +process.on('SIGTERM', () => gracefulShutdown()) +process.on('uncaughtException', (err) => { + console.error(err) + gracefulShutdown() +}) + // Use singleton pattern to mitigate multiple readers/writers export const dbService = new Db() From c196651c52326669f7d804d5ee5a22ab27884a0e Mon Sep 17 00:00:00 2001 From: Manassarn Manoonchai Date: Fri, 2 Apr 2021 00:12:38 +0700 Subject: [PATCH 08/12] fix: scope old test script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 660b838..508d0a0 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Repo to keep my community's twitch gadgeties.", "main": "index.js", "scripts": { - "test": "jest --verbose", + "test": "jest --verbose ./__test__", "start": "cd 9armbot && node 9armbot.js", "dev": "cd 9armbot && nodemon 9armbot.js --ignore '*.json'", "dev-2.0": "ts-node-dev --respawn 9armbot-2.0", From c11fddf77e59fcb7b0d695c4536fc8c91892c61f Mon Sep 17 00:00:00 2001 From: Manassarn Manoonchai Date: Fri, 2 Apr 2021 00:17:38 +0700 Subject: [PATCH 09/12] ci: fix test-2.0 command --- .github/workflows/pull-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-test.yml b/.github/workflows/pull-test.yml index e1950a9..ac6048e 100644 --- a/.github/workflows/pull-test.yml +++ b/.github/workflows/pull-test.yml @@ -31,4 +31,4 @@ jobs: - name: Test run: npm test - name: Test (2.0) - run: npm test-2.0 + run: npm run test-2.0 From 752b25ba3e56627a3554cb9f3af6d89a93c2759c Mon Sep 17 00:00:00 2001 From: Manassarn Manoonchai Date: Fri, 2 Apr 2021 00:31:19 +0700 Subject: [PATCH 10/12] refactor: cleanup code --- 9armbot-2.0/console.ts | 4 ++-- 9armbot-2.0/services/db.ts | 13 ++++++------- 9armbot-2.0/test/db.test.ts | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/9armbot-2.0/console.ts b/9armbot-2.0/console.ts index a135354..fb3731e 100644 --- a/9armbot-2.0/console.ts +++ b/9armbot-2.0/console.ts @@ -1,12 +1,12 @@ /** * 9armbot Console - * Warning : Since this is not sharing the same process as the NodeJS server, + * Warning : Since this console toes not share the same process as the NodeJS server, * writing to db will overwrite everything! (Singleton does not work across processes, damn!) */ import repl from 'repl' -import { dbService } from './services/db' import _ from 'lodash' +import { dbService } from './services/db' const replServer = repl.start({ prompt: `9armbot(${process.env.NODE_ENV || 'development'}) > `, diff --git a/9armbot-2.0/services/db.ts b/9armbot-2.0/services/db.ts index 85861da..99b27a9 100644 --- a/9armbot-2.0/services/db.ts +++ b/9armbot-2.0/services/db.ts @@ -4,6 +4,7 @@ import { nanoid } from 'nanoid' import path from 'path' const DB_PATH = path.resolve(process.cwd(), './players-2.0.json') + export interface IDb { players: IPlayer[] } @@ -42,17 +43,15 @@ export class Db { return this.db } - public createPlayer(username: string) { + public createPlayer(username: string): IPlayer { // Don't re-create existing player - const player = find(this.db.players, (p) => { - return p.username === username - }) + const player = this.getPlayerbyUsername(username) if (player) { return player } - const newPlayer = { + const newPlayer: IPlayer = { uid: nanoid(), username, } @@ -62,7 +61,7 @@ export class Db { return newPlayer } - public getPlayerbyUsername(username: string) { + public getPlayerbyUsername(username: string): IPlayer | undefined { return find(this.db.players, (p) => { return p.username === username }) @@ -70,7 +69,7 @@ export class Db { } // Graceful Shutdown -function gracefulShutdown() { +function gracefulShutdown(): void { console.log('\nPre-Close') dbService.save() setTimeout(() => { diff --git a/9armbot-2.0/test/db.test.ts b/9armbot-2.0/test/db.test.ts index 6e7c27e..bc6773a 100644 --- a/9armbot-2.0/test/db.test.ts +++ b/9armbot-2.0/test/db.test.ts @@ -45,7 +45,7 @@ describe('Database', () => { }) }) - describe('save', () => { + describe('#save', () => { it('saves the current database state to json file', () => { db.createPlayer('foo') @@ -94,7 +94,7 @@ describe('CRUD players', () => { }) }) - describe('getPlayerbyUsername', () => { + describe('#getPlayerbyUsername', () => { it('returns undefined if player is not found', () => { expect(db.getPlayerbyUsername('foo')).toBeUndefined }) From 00d37fa34b3b4b4f92a5bab413204f3692bcfc1a Mon Sep 17 00:00:00 2001 From: Manassarn Manoonchai Date: Fri, 2 Apr 2021 00:35:28 +0700 Subject: [PATCH 11/12] feat: use nanoid to random username for testing --- 9armbot-2.0/services/discord.ts | 6 +++++- 9armbot-2.0/services/twitch.ts | 8 -------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/9armbot-2.0/services/discord.ts b/9armbot-2.0/services/discord.ts index 29c40ee..ecbd6e3 100644 --- a/9armbot-2.0/services/discord.ts +++ b/9armbot-2.0/services/discord.ts @@ -1,4 +1,5 @@ import dotenv from 'dotenv' +import { customAlphabet } from 'nanoid' import { dbService } from './db' dotenv.config() @@ -9,11 +10,14 @@ export async function discordService() { console.log('discord read db from dbservice', dbService.read()) }, 2500) + // Test db writing + const randomName = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 8) + setInterval(() => { console.log( 'discord', 'test adding random players', - dbService.createPlayer(Math.random().toString()), + dbService.createPlayer(randomName()), ) }, 7000) } diff --git a/9armbot-2.0/services/twitch.ts b/9armbot-2.0/services/twitch.ts index 4ebb103..fab56b7 100644 --- a/9armbot-2.0/services/twitch.ts +++ b/9armbot-2.0/services/twitch.ts @@ -28,12 +28,4 @@ export async function twitchService() { setInterval(() => { console.log('twitch read db from dbservice', dbService.read()) }, 2000) - - setInterval(() => { - console.log( - 'twitch', - 'test adding random players', - dbService.createPlayer(Math.random().toString()), - ) - }, 6000) } From 2772e7772a8899a4233df4614260b9ad46725068 Mon Sep 17 00:00:00 2001 From: Manassarn Manoonchai Date: Fri, 2 Apr 2021 02:07:04 +0700 Subject: [PATCH 12/12] feat: build & start --- .gitignore | 1 + package.json | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index d16d8a4..25ce7f7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ package-lock.json yarn.lock players-2.0.json .node_repl_history +dist diff --git a/package.json b/package.json index 508d0a0..331b7c8 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,8 @@ "start": "cd 9armbot && node 9armbot.js", "dev": "cd 9armbot && nodemon 9armbot.js --ignore '*.json'", "dev-2.0": "ts-node-dev --respawn 9armbot-2.0", + "build-2.0": "tsc", + "start-2.0": "node dist", "test-2.0": "jest --config 9armbot-2.0/jest.config.js", "test-2.0:watch": "jest --config 9armbot-2.0/jest.config.js --watch", "console": "ts-node 9armbot-2.0/console"