Implement the majority of the API
This commit is contained in:
parent
e322f24457
commit
4b61e73573
15 changed files with 475 additions and 6 deletions
179
api/package-lock.json
generated
179
api/package-lock.json
generated
|
|
@ -13,6 +13,8 @@
|
|||
"glob": "^10.3.3",
|
||||
"joi": "^17.9.2",
|
||||
"module-alias": "^2.2.3",
|
||||
"mysql2": "^3.6.0",
|
||||
"tmi.js": "^1.8.5",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"tslog": "^4.8.2"
|
||||
},
|
||||
|
|
@ -20,6 +22,7 @@
|
|||
"@types/chai": "^4.3.5",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/node": "^20.4.7",
|
||||
"@types/tmi.js": "^1.8.3",
|
||||
"chai": "^4.3.7",
|
||||
"mocha": "^10.2.0",
|
||||
"ts-mocha": "^10.0.0",
|
||||
|
|
@ -521,6 +524,12 @@
|
|||
"integrity": "sha512-bUBrPjEry2QUTsnuEjzjbS7voGWCc30W0qzgMf90GPeDGFRakvrz47ju+oqDAKCXLUCe39u57/ORMl/O/04/9g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/tmi.js": {
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/tmi.js/-/tmi.js-1.8.3.tgz",
|
||||
"integrity": "sha512-piKPU1DF+Lxnh9BV0gVpbJIMnKOQT6zT0o6NLE0DZgHNW7fGxReRRSp987+Ph1aYiyNWXXbxAeQAovTlEYC1hw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
|
||||
|
|
@ -855,6 +864,14 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
|
||||
|
|
@ -967,6 +984,14 @@
|
|||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/generate-function": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
|
||||
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
|
||||
"dependencies": {
|
||||
"is-property": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
|
|
@ -1050,6 +1075,17 @@
|
|||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
|
|
@ -1125,6 +1161,11 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-property": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
||||
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="
|
||||
},
|
||||
"node_modules/is-unicode-supported": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
|
||||
|
|
@ -1238,6 +1279,11 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
|
||||
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="
|
||||
},
|
||||
"node_modules/loupe": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz",
|
||||
|
|
@ -1402,6 +1448,51 @@
|
|||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mysql2": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.6.0.tgz",
|
||||
"integrity": "sha512-EWUGAhv6SphezurlfI2Fpt0uJEWLmirrtQR7SkbTHFC+4/mJBrPiSzHESHKAWKG7ALVD6xaG/NBjjd1DGJGQQQ==",
|
||||
"dependencies": {
|
||||
"denque": "^2.1.0",
|
||||
"generate-function": "^2.3.1",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"long": "^5.2.1",
|
||||
"lru-cache": "^8.0.0",
|
||||
"named-placeholders": "^1.1.3",
|
||||
"seq-queue": "^0.0.5",
|
||||
"sqlstring": "^2.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mysql2/node_modules/lru-cache": {
|
||||
"version": "8.0.5",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz",
|
||||
"integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==",
|
||||
"engines": {
|
||||
"node": ">=16.14"
|
||||
}
|
||||
},
|
||||
"node_modules/named-placeholders": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
|
||||
"integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^7.14.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/named-placeholders/node_modules/lru-cache": {
|
||||
"version": "7.18.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
|
||||
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
|
||||
|
|
@ -1414,6 +1505,25 @@
|
|||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
|
|
@ -1574,6 +1684,16 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/seq-queue": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
|
||||
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
|
||||
},
|
||||
"node_modules/serialize-javascript": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
|
||||
|
|
@ -1632,6 +1752,14 @@
|
|||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sqlstring": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
|
||||
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
|
|
@ -1717,6 +1845,18 @@
|
|||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/tmi.js": {
|
||||
"version": "1.8.5",
|
||||
"resolved": "https://registry.npmjs.org/tmi.js/-/tmi.js-1.8.5.tgz",
|
||||
"integrity": "sha512-A9qrydfe1e0VWM9MViVhhxVgvLpnk7pFShVUWePsSTtoi+A1X+Zjdoa7OJd7/YsgHXGj3GkNEvnWop/1WwZuew==",
|
||||
"dependencies": {
|
||||
"node-fetch": "^2.6.1",
|
||||
"ws": "^8.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
|
|
@ -1729,6 +1869,11 @@
|
|||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/ts-mocha": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-10.0.0.tgz",
|
||||
|
|
@ -1920,6 +2065,20 @@
|
|||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
|
@ -1980,6 +2139,26 @@
|
|||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.13.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
|
||||
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
"glob": "^10.3.3",
|
||||
"joi": "^17.9.2",
|
||||
"module-alias": "^2.2.3",
|
||||
"mysql2": "^3.6.0",
|
||||
"tmi.js": "^1.8.5",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"tslog": "^4.8.2"
|
||||
},
|
||||
|
|
@ -20,6 +22,7 @@
|
|||
"@types/chai": "^4.3.5",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/node": "^20.4.7",
|
||||
"@types/tmi.js": "^1.8.3",
|
||||
"chai": "^4.3.7",
|
||||
"mocha": "^10.2.0",
|
||||
"ts-mocha": "^10.0.0",
|
||||
|
|
|
|||
37
api/src/endpoints/channels/watch.ts
Normal file
37
api/src/endpoints/channels/watch.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { ServerRoute } from "@hapi/hapi";
|
||||
import boom from "@hapi/boom";
|
||||
|
||||
const route: ServerRoute = {
|
||||
method: `POST`, path: `/{channel}`,
|
||||
async handler(request, h) {
|
||||
const { channel } = request.params;
|
||||
const { bot, db } = request.server.app;
|
||||
|
||||
let [ channels ] = await db.query<Array<any>>(
|
||||
`select * from tqna.twitchs where channel = ?`,
|
||||
[ channel ]
|
||||
);
|
||||
if (channels.length > 0) {
|
||||
return h.response().code(202);
|
||||
};
|
||||
|
||||
try {
|
||||
await bot.join(channel);
|
||||
} catch {
|
||||
throw boom.badRequest(`Could not connect to that channel`);
|
||||
};
|
||||
|
||||
try {
|
||||
await db.query(
|
||||
`insert into tqna.twitchs ( channel ) values ( ? )`,
|
||||
[ channel ]
|
||||
);
|
||||
} catch {
|
||||
await bot.part(channel);
|
||||
throw boom.badImplementation(`Failed to save the channel`);
|
||||
};
|
||||
|
||||
return h.response().code(204);
|
||||
},
|
||||
};
|
||||
export default route;
|
||||
56
api/src/endpoints/questions/add.ts
Normal file
56
api/src/endpoints/questions/add.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { Query } from "~/types/utilities";
|
||||
import { ServerRoute } from "@hapi/hapi";
|
||||
import Joi from "joi";
|
||||
|
||||
const route: ServerRoute = {
|
||||
method: `POST`, path: `/{channel}/questions`,
|
||||
options: {
|
||||
validate: {
|
||||
params: Joi.object({
|
||||
channel: Joi.string().required(),
|
||||
}),
|
||||
payload: Joi.object({
|
||||
question: Joi.string().required(),
|
||||
asker: Joi.string().required(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async handler(request, h) {
|
||||
const { channel } = request.params;
|
||||
const { db, log } = request.server.app;
|
||||
const payload = request.payload as any;
|
||||
log.debug(`Attempting to save a new question in channel: ${channel}`);
|
||||
|
||||
let errored = false;
|
||||
let question = null;
|
||||
let conn = await db.getConnection();
|
||||
await conn.beginTransaction();
|
||||
|
||||
try {
|
||||
await db.query(
|
||||
`insert into tqna.questions ( channel, asker, question ) values ( ?, ?, ?)`,
|
||||
[ channel, payload.asker, payload.question ]
|
||||
);
|
||||
|
||||
let [ questions ] = await db.query<Query<Question, false>>(
|
||||
`select * from tqna.questions where id = last_insert_id() limit 1`
|
||||
);
|
||||
question = questions[0];
|
||||
|
||||
await conn.commit();
|
||||
} catch {
|
||||
log.error(`Failed to add the question`);
|
||||
await conn.rollback();
|
||||
} finally {
|
||||
conn.release();
|
||||
};
|
||||
|
||||
if (errored) {
|
||||
throw new Error();
|
||||
};
|
||||
|
||||
log.debug(`Created new question in channel: ${channel}`);
|
||||
return question;
|
||||
},
|
||||
};
|
||||
export default route;
|
||||
25
api/src/endpoints/questions/list.ts
Normal file
25
api/src/endpoints/questions/list.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { ServerRoute } from "@hapi/hapi";
|
||||
import Joi from "joi";
|
||||
|
||||
const route: ServerRoute = {
|
||||
method: `GET`, path: `/{channel}/questions`,
|
||||
options: {
|
||||
validate: {
|
||||
params: Joi.object({
|
||||
channel: Joi.string().required(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async handler(request) {
|
||||
const { channel } = request.params;
|
||||
const { db, log } = request.server.app;
|
||||
log.debug(`Listing questions for channel: ${channel}`);
|
||||
|
||||
let [ questions ] = await db.query(
|
||||
`select * from tqna.questions as q where q.channel = ?`,
|
||||
[ channel ]
|
||||
);
|
||||
return questions;
|
||||
},
|
||||
};
|
||||
export default route;
|
||||
77
api/src/endpoints/questions/update.ts
Normal file
77
api/src/endpoints/questions/update.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { Query } from "~/types/utilities";
|
||||
import { ServerRoute } from "@hapi/hapi";
|
||||
import Joi from "joi";
|
||||
|
||||
const route: ServerRoute = {
|
||||
method: `POST`, path: `/{channel}/questions/{question_id}`,
|
||||
options: {
|
||||
validate: {
|
||||
params: Joi.object({
|
||||
channel: Joi
|
||||
.string()
|
||||
.required(),
|
||||
question_id: Joi
|
||||
.number()
|
||||
.positive()
|
||||
.integer()
|
||||
.not(0)
|
||||
.required(),
|
||||
}),
|
||||
payload: Joi.object({
|
||||
question: Joi.string().optional(),
|
||||
asker: Joi.string().optional(),
|
||||
answered: Joi.boolean().optional(),
|
||||
id: Joi.forbidden(),
|
||||
})
|
||||
.min(1),
|
||||
},
|
||||
},
|
||||
async handler(request, h) {
|
||||
const { channel, question_id } = request.params;
|
||||
const { db, log } = request.server.app;
|
||||
const payload = request.payload as any;
|
||||
let question = null;
|
||||
log.debug(`Updating question ${question_id} for channel ${channel}`);
|
||||
|
||||
let conn = await db.getConnection();
|
||||
await conn.beginTransaction();
|
||||
|
||||
try {
|
||||
let setters = [];
|
||||
let values = [];
|
||||
for (const key in payload) {
|
||||
let v = payload[key];
|
||||
if (v.startsWith(`__`)) {
|
||||
setters.push(`${key} = ${v}`);
|
||||
} else {
|
||||
setters.push(`${key} = ?`);
|
||||
values.push(v);
|
||||
};
|
||||
};
|
||||
|
||||
await db.query(
|
||||
`update tqna.questions
|
||||
( ${setters.join(`, `)} )
|
||||
where channel = ? and id = ?
|
||||
limit 1`,
|
||||
[...values, channel, question_id ]
|
||||
);
|
||||
|
||||
let [ questions ] = await db.query<Query<Question, false>>(
|
||||
`select * from tqna.questions where id = ? limit 1`,
|
||||
[ question_id ]
|
||||
);
|
||||
question = questions[0];
|
||||
|
||||
await conn.commit();
|
||||
} catch {
|
||||
log.error(`Failed to add the question`);
|
||||
await conn.rollback();
|
||||
} finally {
|
||||
conn.release();
|
||||
};
|
||||
|
||||
return question;
|
||||
},
|
||||
};
|
||||
export default route;
|
||||
|
|
@ -1,11 +1,15 @@
|
|||
import { Server } from "@hapi/hapi";
|
||||
import { globSync } from "glob";
|
||||
import path from "path";
|
||||
import { log } from "./main";
|
||||
|
||||
|
||||
const server = new Server({
|
||||
port: 6969,
|
||||
host: `0.0.0.0`,
|
||||
debug: {
|
||||
request: [ `*` ],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -17,6 +21,7 @@ async function registerRoutes() {
|
|||
for (const file of files) {
|
||||
let route = (await import(path.join(__dirname, file))).default;
|
||||
server.route(route);
|
||||
log.debug(`Registered route: ${route.method} ${route.path}`);
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@
|
|||
// at the top of this file as the first statement
|
||||
import "module-alias/register";
|
||||
|
||||
import { start as startBot } from "~/twitch-bot";
|
||||
import { start as startApi } from "~/hapi";
|
||||
import mysql from "mysql2/promise";
|
||||
import { Logger } from "tslog";
|
||||
import { start } from "~/hapi";
|
||||
|
||||
export const isDev = process.env.NODE_ENV?.startsWith(`dev`);
|
||||
export const isDev = !!process.env.NODE_ENV?.startsWith(`dev`);
|
||||
|
||||
let logLevel = 4;
|
||||
if (process.env.LOG_LEVEL) {
|
||||
|
|
@ -22,8 +24,26 @@ export const log = new Logger({
|
|||
|
||||
|
||||
async function main() {
|
||||
let server = await start();
|
||||
log.info(`Server listening`)
|
||||
|
||||
const db = mysql.createPool({
|
||||
host: process.env.MYSQL_HOST,
|
||||
user: process.env.MYSQL_USER,
|
||||
password: process.env.MYSQL_PASSWORD,
|
||||
database: `tqna`,
|
||||
namedPlaceholders: true,
|
||||
});
|
||||
|
||||
|
||||
let server = await startApi();
|
||||
server.app.log = log;
|
||||
server.app.isDev = isDev;
|
||||
server.app.db = db;
|
||||
log.info(`Server listening`);
|
||||
|
||||
|
||||
let tmi = await startBot(db, server);
|
||||
server.app.bot = tmi;
|
||||
log.info(`Bot listening`);
|
||||
};
|
||||
|
||||
if (require.main === module) {
|
||||
|
|
|
|||
34
api/src/twitch-bot.ts
Normal file
34
api/src/twitch-bot.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { Pool, RowDataPacket } from "mysql2/promise";
|
||||
import { Server } from "@hapi/hapi";
|
||||
import { log } from "./main";
|
||||
import tmi from "tmi.js";
|
||||
|
||||
export async function start(db: Pool, server: Server) {
|
||||
let [ channels ] = await db.query<Array<any>>({
|
||||
sql: `select channel from tqna.twitchs`,
|
||||
rowsAsArray: true
|
||||
});
|
||||
|
||||
const client = new tmi.Client({
|
||||
channels: [...new Set(channels.flat())] as string[],
|
||||
});
|
||||
|
||||
client.on(`message`, async (channel, user, message, self) => {
|
||||
log.silly(`Twitch message: [c:${channel}] [u:${user}] ${message}`);
|
||||
if (self) { return };
|
||||
|
||||
if (message.match(/^[Q]:/i)) {
|
||||
await server.inject({
|
||||
method: `POST`, url: `/${channel.slice(1)}/questions`,
|
||||
payload: {
|
||||
question: message.slice(2).trim(),
|
||||
asker: user["display-name"] ?? user.username,
|
||||
},
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
return client;
|
||||
};
|
||||
7
api/src/types/database.d.ts
vendored
Normal file
7
api/src/types/database.d.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
interface Question {
|
||||
readonly __id: number;
|
||||
channel: number;
|
||||
asker: string;
|
||||
question: string;
|
||||
answered: boolean;
|
||||
}
|
||||
12
api/src/types/hapi.d.ts
vendored
Normal file
12
api/src/types/hapi.d.ts
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import mysql from "mysql2/promise";
|
||||
import { Client } from "tmi.js";
|
||||
import { Logger } from "tslog";
|
||||
|
||||
declare module "@hapi/hapi" {
|
||||
interface ServerApplicationState {
|
||||
db: mysql.Pool;
|
||||
log: Logger<unknown>;
|
||||
isDev: boolean;
|
||||
bot: Client;
|
||||
}
|
||||
}
|
||||
6
api/src/types/utilities.ts
Normal file
6
api/src/types/utilities.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import type { RowDataPacket } from "mysql2/promise";
|
||||
|
||||
export type Query<T, P = true> =
|
||||
P extends true
|
||||
? (Partial<T> & RowDataPacket)[]
|
||||
: (T & RowDataPacket)[]
|
||||
0
api/src/utils/database.ts
Normal file
0
api/src/utils/database.ts
Normal file
|
|
@ -1,9 +1,14 @@
|
|||
create database if not exists tqna;
|
||||
|
||||
create table if not exists tqna.twitchs (
|
||||
`id` integer not null auto_increment primary key,
|
||||
`channel` text
|
||||
);
|
||||
|
||||
create table if not exists tqna.questions (
|
||||
`__id` integer not null auto_increment primary key,
|
||||
`id` integer not null auto_increment primary key,
|
||||
`channel` text,
|
||||
`asker` text,
|
||||
`question` text,
|
||||
`answered` boolean
|
||||
`answered` boolean not null default false
|
||||
);
|
||||
|
|
@ -11,6 +11,9 @@ services:
|
|||
- "6969:6969"
|
||||
environment:
|
||||
NODE_ENV: "dev"
|
||||
MYSQL_HOST: "db"
|
||||
MYSQL_USER: "root"
|
||||
MYSQL_PASSWORD: "root"
|
||||
healthcheck:
|
||||
disable: true
|
||||
volumes:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue