diff --git a/api/src/endpoints/questions/update.ts b/api/src/endpoints/questions/update.ts
index e1cc557..a986f76 100644
--- a/api/src/endpoints/questions/update.ts
+++ b/api/src/endpoints/questions/update.ts
@@ -3,7 +3,7 @@ import { ServerRoute } from "@hapi/hapi";
import Joi from "joi";
const route: ServerRoute = {
- method: `POST`, path: `/{channel}/questions/{question_id}`,
+ method: `PATCH`, path: `/{channel}/questions/{question_id}`,
options: {
validate: {
params: Joi.object({
@@ -21,6 +21,7 @@ const route: ServerRoute = {
question: Joi.string().optional(),
asker: Joi.string().optional(),
answered: Joi.boolean().optional(),
+ hidden: Joi.boolean().optional(),
id: Joi.forbidden(),
})
.min(1),
@@ -41,17 +42,24 @@ const route: ServerRoute = {
let values = [];
for (const key in payload) {
let v = payload[key];
- if (v.startsWith(`__`)) {
+
+ let type = typeof v;
+ if (type == "boolean") {
setters.push(`${key} = ${v}`);
- } else {
- setters.push(`${key} = ?`);
- values.push(v);
+ }
+ else if (type == "string") {
+ if (v.startsWith(`__`)) {
+ setters.push(`${key} = ${v.slice(2)}`);
+ } else {
+ setters.push(`${key} = ?`);
+ values.push(v);
+ };
};
};
await db.query(
`update tqna.questions
- ( ${setters.join(`, `)} )
+ set ${setters.join(`, `)}
where channel = ? and id = ?
limit 1`,
[...values, channel, question_id ]
@@ -64,8 +72,9 @@ const route: ServerRoute = {
question = questions[0];
await conn.commit();
- } catch {
- log.error(`Failed to add the question`);
+ } catch (e) {
+ log.error(e)
+ log.error(`Failed to save the question`);
await conn.rollback();
} finally {
conn.release();
diff --git a/api/src/hapi.ts b/api/src/hapi.ts
index aaba203..47e6ddc 100644
--- a/api/src/hapi.ts
+++ b/api/src/hapi.ts
@@ -1,4 +1,5 @@
import { Server } from "@hapi/hapi";
+import { isBoom } from "@hapi/boom";
import { globSync } from "glob";
import path from "path";
import { log } from "./main";
@@ -10,6 +11,42 @@ const server = new Server({
debug: {
request: [ `*` ],
},
+ router: {
+ stripTrailingSlash: true,
+ },
+ routes: {
+ cors: {
+ origin: [ `*` ],
+ },
+ },
+});
+
+/*
+This event listener makes it so that the error that is returned from the system
+is more user-friendly when it's a validation error, and so that nothing gets
+leaked accidentally through allowing other data to make it out of the API.
+*/
+server.ext(`onPreResponse`, (req, h) => {
+ if (isBoom(req.response)) {
+ let oldResponse = req.response.output.payload as any;
+ let newResponse: any = {
+ statusCode: oldResponse.statusCode,
+ error: oldResponse.error,
+ message: oldResponse.message,
+ };
+
+ let deets = (req.response as any).details as any[];
+ if (deets) {
+ let messages = deets.map(e => e.message);
+ newResponse.message = (req.response as any).output.payload.validation.source + ` failed to validate`;
+ newResponse.violations = messages;
+ };
+
+ req.response.output.payload = newResponse;
+ return h.continue;
+ }
+
+ return h.continue;
});
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 15821b0..c941bef 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -8,6 +8,7 @@
"name": "frontend",
"version": "0.0.0",
"dependencies": {
+ "axios": "^1.5.0",
"svelte-i18n": "^3.7.0"
},
"devDependencies": {
@@ -599,6 +600,21 @@
"dequal": "^2.0.3"
}
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/axios": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
+ "integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
+ "dependencies": {
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/axobject-query": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
@@ -716,6 +732,17 @@
"periscopic": "^3.1.0"
}
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -768,6 +795,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -939,6 +974,38 @@
"node": ">=8"
}
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
+ "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -1180,6 +1247,25 @@
"node": ">=8.6"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
@@ -1354,6 +1440,11 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 8e5082b..1fc0422 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -19,6 +19,7 @@
"vite": "^4.4.5"
},
"dependencies": {
+ "axios": "^1.5.0",
"svelte-i18n": "^3.7.0"
}
}
diff --git a/frontend/public/OpenDyslexic3-Bold.ttf b/frontend/public/OpenDyslexic3-Bold.ttf
new file mode 100644
index 0000000..395dffc
Binary files /dev/null and b/frontend/public/OpenDyslexic3-Bold.ttf differ
diff --git a/frontend/public/OpenDyslexic3-Regular.ttf b/frontend/public/OpenDyslexic3-Regular.ttf
new file mode 100644
index 0000000..0ff4c0b
Binary files /dev/null and b/frontend/public/OpenDyslexic3-Regular.ttf differ
diff --git a/frontend/public/locales/en-CA.json b/frontend/public/locales/en-CA.json
index c3c0339..66deb08 100644
--- a/frontend/public/locales/en-CA.json
+++ b/frontend/public/locales/en-CA.json
@@ -1,3 +1,17 @@
{
- "docs.svelte": "Svelte Docs"
-}
\ No newline at end of file
+ "option.streamer-mode": "Streamer Mode",
+ "option.dyslexic-font": "Use Dyslexic Font",
+ "option.refresh-interval": "Refresh Interval",
+ "option.locale": "Language",
+ "option.join-channel": "Join Twitch Channel",
+ "button.refresh": "Refresh",
+ "button.extra-settings": "Extra Settings",
+ "display.question.button.answered": "Answered",
+ "display.question.button.hide-from-streamer": "Hide from Streamer",
+ "display.question.button.show-for-streamer": "Show to Streamer",
+ "display.question.asked-by": "asked by:",
+ "display.modal.settings.title": "Extra Settings",
+ "display.modal.settings.description": "Any changes made to these options are automatically saved to your browser and will be restored on page re-load.",
+ "display.modal.close": "Close",
+ "option.delete-questions": "Delete All Questions"
+}
diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte
index 3324498..841c7cb 100644
--- a/frontend/src/App.svelte
+++ b/frontend/src/App.svelte
@@ -1,19 +1,82 @@
-
{q.question}+