From 5f5ffca74c1eda293b0859d5f0f9f942eb5efecf Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 23 Jul 2022 20:28:48 +0100 Subject: [PATCH] Finally "fix" (hack) Prisma middleware bug Middleware runs twice on question upsert? --- .gitignore | 2 +- db/mysql/schema.prisma | 4 +- db/postgresql/schema.prisma | 4 +- db/sqlite/schema.prisma | 2 +- src/client.js | 4 +- src/lib/middleware/prisma-encryption.js | 34 +++++++--------- src/lib/middleware/prisma-sqlite.js | 39 +++++++++++++++++++ src/lib/middleware/prisma-types.js | 35 ----------------- .../admin/guilds/[guild]/categories/index.js | 3 +- 9 files changed, 63 insertions(+), 64 deletions(-) create mode 100644 src/lib/middleware/prisma-sqlite.js delete mode 100644 src/lib/middleware/prisma-types.js diff --git a/.gitignore b/.gitignore index 50a83bf..cba93c1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ prisma/ # files .env *.db +*.db-journal *.log -*.lock *-lock.* user/config.yml \ No newline at end of file diff --git a/db/mysql/schema.prisma b/db/mysql/schema.prisma index 6834ce1..46dde9c 100644 --- a/db/mysql/schema.prisma +++ b/db/mysql/schema.prisma @@ -194,7 +194,7 @@ model Ticket { closedAt DateTime? closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) closedById String @db.VarChar(19) - closedReason String? + closedReason String? @db.Text createdAt DateTime @default(now()) createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) createdById String @db.VarChar(19) @@ -216,7 +216,7 @@ model Ticket { referencesMessageId String @db.VarChar(19) referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) referencesTicketId String? @db.VarChar(19) - topic String? + topic String? @db.Text questionAnswers QuestionAnswer[] @@unique([guildId, number]) diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index 5511dbe..d093f69 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -194,7 +194,7 @@ model Ticket { closedAt DateTime? closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) closedById String @db.VarChar(19) - closedReason String? + closedReason String? @db.Text createdAt DateTime @default(now()) createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) createdById String @db.VarChar(19) @@ -216,7 +216,7 @@ model Ticket { referencesMessageId String @db.VarChar(19) referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) referencesTicketId String? @db.VarChar(19) - topic String? + topic String? @db.Text questionAnswers QuestionAnswer[] @@unique([guildId, number]) diff --git a/db/sqlite/schema.prisma b/db/sqlite/schema.prisma index 6f7542c..cf3337b 100644 --- a/db/sqlite/schema.prisma +++ b/db/sqlite/schema.prisma @@ -4,7 +4,7 @@ generator client { datasource db { provider = "sqlite" - url = env("DB_CONNECTION_URL") + url = "file:../user/database.db" } model ArchivedChannel { diff --git a/src/client.js b/src/client.js index 6cb7249..dc5a47d 100644 --- a/src/client.js +++ b/src/client.js @@ -7,7 +7,7 @@ const fs = require('fs'); const { join } = require('path'); const YAML = require('yaml'); const encryptionMiddleware = require('./lib/middleware/prisma-encryption'); -const typesMiddleware = require('./lib/middleware/prisma-types'); +const sqliteMiddleware = require('./lib/middleware/prisma-sqlite'); module.exports = class Client extends FrameworkClient { constructor(config, log) { @@ -39,7 +39,7 @@ module.exports = class Client extends FrameworkClient { /** @type {PrismaClient} */ this.prisma = new PrismaClient(); this.prisma.$use(encryptionMiddleware); - this.prisma.$use(typesMiddleware); + if (process.env.DB_PROVIDER === 'sqlite') this.prisma.$use(sqliteMiddleware); this.keyv = new Keyv(); return super.login(token); } diff --git a/src/lib/middleware/prisma-encryption.js b/src/lib/middleware/prisma-encryption.js index 138b793..87836e4 100644 --- a/src/lib/middleware/prisma-encryption.js +++ b/src/lib/middleware/prisma-encryption.js @@ -17,33 +17,29 @@ const encryptedFields = [ // 'regex', ]; -const encrypt = obj => { +const traverse = (obj, action) => { for (const prop in obj) { - if (typeof obj[prop] === 'string' && obj[prop].length !== 0 && encryptedFields.includes(prop)) { - obj[prop] = cryptr.encrypt(obj[prop]); + if (encryptedFields.includes(prop) && typeof obj[prop] === 'string' && obj[prop].length !== 0) { + try { + // prevent double encryption bug (from nested writes - notably upserting questions in category update). + // not sure why it happens + if (action === 'ENCRYPT' && cryptr.decrypt(obj[prop])) continue; // don't encrypt if it already encrypted + else obj[prop] = cryptr[action.toLowerCase()](obj[prop]); + } catch { + // do nothing + } } else if (typeof obj[prop] === 'object') { - obj[prop] = encrypt(obj[prop]); - } - } - return obj; -}; - -const decrypt = obj => { - for (const prop in obj) { - if (typeof obj[prop] === 'string' && obj[prop].length !== 0 && encryptedFields.includes(prop)) { - obj[prop] = cryptr.decrypt(obj[prop]); - } else if (typeof obj[prop] === 'object') { - obj[prop] = decrypt(obj[prop]); + obj[prop] = traverse(obj[prop], action); } } return obj; }; module.exports = async (params, next) => { - if (params.args.create) params.args.create = encrypt(params.args.create); - if (params.args.data) params.args.data = encrypt(params.args.data); - if (params.args.update) params.args.update = encrypt(params.args.update); + if (params.args.create) params.args.create = traverse(params.args.create, 'ENCRYPT'); + if (params.args.data) params.args.data = traverse(params.args.data, 'ENCRYPT'); + if (params.args.update) params.args.update = traverse(params.args.update, 'ENCRYPT'); let result = await next(params); - if (result) result = decrypt(result); + if (result) result = traverse(result, 'DECRYPT'); return result; }; \ No newline at end of file diff --git a/src/lib/middleware/prisma-sqlite.js b/src/lib/middleware/prisma-sqlite.js new file mode 100644 index 0000000..8af1ca1 --- /dev/null +++ b/src/lib/middleware/prisma-sqlite.js @@ -0,0 +1,39 @@ +const jsonFields = [ + 'pingRoles', + 'requiredRoles', + 'staffRoles', + 'autoTag', + 'blocklist', + 'workingHours', + 'options', + 'pinnedMessages', +]; + +const traverse = (obj, action) => { + for (let prop in obj) { + if (prop === 'createMany') { + obj.create = obj[prop].data; + delete obj[prop]; + prop = 'create'; + traverse(obj[prop], action); + } else if (jsonFields.includes(prop) && obj[prop] !== null && obj[prop] !== undefined) { + if (action === 'SERIALISE' && typeof obj[prop] !== 'string') { + obj[prop] = JSON.stringify(obj[prop]); + } else if (action === 'PARSE' && typeof obj[prop] === 'string') { + obj[prop] = JSON.parse(obj[prop]); + } + } else if (typeof obj[prop] === 'object' && obj[prop] !== null && obj[prop] !== undefined) { + traverse(obj[prop], action); + } + } + return obj; +}; + +module.exports = async (params, next) => { + if (params.args.create) params.args.create = traverse(params.args.create, 'SERIALISE'); + if (params.args.data) params.args.data = traverse(params.args.data, 'SERIALISE'); + if (params.args.update) params.args.update = traverse(params.args.update, 'SERIALISE'); + let result = await next(params); + if (result) result = traverse(result, 'PARSE'); + return result; +}; \ No newline at end of file diff --git a/src/lib/middleware/prisma-types.js b/src/lib/middleware/prisma-types.js deleted file mode 100644 index 0718f14..0000000 --- a/src/lib/middleware/prisma-types.js +++ /dev/null @@ -1,35 +0,0 @@ -const jsonFields = [ - 'pingRoles', - 'requiredRoles', - 'staffRoles', - 'autoTag', - 'blocklist', - 'workingHours', - 'options', - 'pinnedMessages', -]; - -const traverse = (obj, func) => { - for (const prop in obj) { - console.log(prop, typeof obj[prop], obj[prop]); - if (jsonFields.includes(prop) && obj[prop] !== null && obj[prop] !== undefined) { - obj[prop] = func(obj[prop]); - } else if (typeof obj[prop] === 'object') { - obj[prop] = traverse(obj[prop], func); - } - } - return obj; -}; - -module.exports = async (params, next) => { - if (process.env.DB_PROVIDER === 'sqlite') { - if (params.args.create) params.args.create = traverse(params.args.create, val => JSON.stringify(val)); - if (params.args.data) params.args.data = traverse(params.args.data, val => JSON.stringify(val)); - if (params.args.update) params.args.update = traverse(params.args.update, val => JSON.stringify(val)); - let result = await next(params); - if (result) result = traverse(result, val => JSON.parse(val)); - return result; - } else { - return await next(params); - } -}; \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index b95dee7..ec9a5a0 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -78,8 +78,7 @@ module.exports.post = fastify => ({ data: { guild: { connect: { id: guild.id } }, ...data, - // questions: { createMany: { data: data.questions ?? [] } }, - questions: { create: { data: data.questions ?? [] } }, // sqlite doesn't support createMany? + questions: { createMany: { data: data.questions ?? [] } }, }, });