Finally "fix" (hack) Prisma middleware bug

Middleware runs twice on question upsert?
This commit is contained in:
Isaac 2022-07-23 20:28:48 +01:00
parent e00ff4e831
commit 5f5ffca74c
No known key found for this signature in database
GPG Key ID: F4EAABEB0FFCC06A
9 changed files with 63 additions and 64 deletions

2
.gitignore vendored
View File

@ -6,7 +6,7 @@ prisma/
# files # files
.env .env
*.db *.db
*.db-journal
*.log *.log
*.lock
*-lock.* *-lock.*
user/config.yml user/config.yml

View File

@ -194,7 +194,7 @@ model Ticket {
closedAt DateTime? closedAt DateTime?
closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id])
closedById String @db.VarChar(19) closedById String @db.VarChar(19)
closedReason String? closedReason String? @db.Text
createdAt DateTime @default(now()) createdAt DateTime @default(now())
createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id])
createdById String @db.VarChar(19) createdById String @db.VarChar(19)
@ -216,7 +216,7 @@ model Ticket {
referencesMessageId String @db.VarChar(19) referencesMessageId String @db.VarChar(19)
referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull)
referencesTicketId String? @db.VarChar(19) referencesTicketId String? @db.VarChar(19)
topic String? topic String? @db.Text
questionAnswers QuestionAnswer[] questionAnswers QuestionAnswer[]
@@unique([guildId, number]) @@unique([guildId, number])

View File

@ -194,7 +194,7 @@ model Ticket {
closedAt DateTime? closedAt DateTime?
closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id])
closedById String @db.VarChar(19) closedById String @db.VarChar(19)
closedReason String? closedReason String? @db.Text
createdAt DateTime @default(now()) createdAt DateTime @default(now())
createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id])
createdById String @db.VarChar(19) createdById String @db.VarChar(19)
@ -216,7 +216,7 @@ model Ticket {
referencesMessageId String @db.VarChar(19) referencesMessageId String @db.VarChar(19)
referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull)
referencesTicketId String? @db.VarChar(19) referencesTicketId String? @db.VarChar(19)
topic String? topic String? @db.Text
questionAnswers QuestionAnswer[] questionAnswers QuestionAnswer[]
@@unique([guildId, number]) @@unique([guildId, number])

View File

@ -4,7 +4,7 @@ generator client {
datasource db { datasource db {
provider = "sqlite" provider = "sqlite"
url = env("DB_CONNECTION_URL") url = "file:../user/database.db"
} }
model ArchivedChannel { model ArchivedChannel {

View File

@ -7,7 +7,7 @@ const fs = require('fs');
const { join } = require('path'); const { join } = require('path');
const YAML = require('yaml'); const YAML = require('yaml');
const encryptionMiddleware = require('./lib/middleware/prisma-encryption'); 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 { module.exports = class Client extends FrameworkClient {
constructor(config, log) { constructor(config, log) {
@ -39,7 +39,7 @@ module.exports = class Client extends FrameworkClient {
/** @type {PrismaClient} */ /** @type {PrismaClient} */
this.prisma = new PrismaClient(); this.prisma = new PrismaClient();
this.prisma.$use(encryptionMiddleware); this.prisma.$use(encryptionMiddleware);
this.prisma.$use(typesMiddleware); if (process.env.DB_PROVIDER === 'sqlite') this.prisma.$use(sqliteMiddleware);
this.keyv = new Keyv(); this.keyv = new Keyv();
return super.login(token); return super.login(token);
} }

View File

@ -17,33 +17,29 @@ const encryptedFields = [
// 'regex', // 'regex',
]; ];
const encrypt = obj => { const traverse = (obj, action) => {
for (const prop in obj) { for (const prop in obj) {
if (typeof obj[prop] === 'string' && obj[prop].length !== 0 && encryptedFields.includes(prop)) { if (encryptedFields.includes(prop) && typeof obj[prop] === 'string' && obj[prop].length !== 0) {
obj[prop] = cryptr.encrypt(obj[prop]); try {
} else if (typeof obj[prop] === 'object') { // prevent double encryption bug (from nested writes - notably upserting questions in category update).
obj[prop] = encrypt(obj[prop]); // 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
} }
}
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') { } else if (typeof obj[prop] === 'object') {
obj[prop] = decrypt(obj[prop]); obj[prop] = traverse(obj[prop], action);
} }
} }
return obj; return obj;
}; };
module.exports = async (params, next) => { module.exports = async (params, next) => {
if (params.args.create) params.args.create = encrypt(params.args.create); if (params.args.create) params.args.create = traverse(params.args.create, 'ENCRYPT');
if (params.args.data) params.args.data = encrypt(params.args.data); if (params.args.data) params.args.data = traverse(params.args.data, 'ENCRYPT');
if (params.args.update) params.args.update = encrypt(params.args.update); if (params.args.update) params.args.update = traverse(params.args.update, 'ENCRYPT');
let result = await next(params); let result = await next(params);
if (result) result = decrypt(result); if (result) result = traverse(result, 'DECRYPT');
return result; return result;
}; };

View File

@ -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;
};

View File

@ -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);
}
};

View File

@ -78,8 +78,7 @@ module.exports.post = fastify => ({
data: { data: {
guild: { connect: { id: guild.id } }, guild: { connect: { id: guild.id } },
...data, ...data,
// questions: { createMany: { data: data.questions ?? [] } }, questions: { createMany: { data: data.questions ?? [] } },
questions: { create: { data: data.questions ?? [] } }, // sqlite doesn't support createMany?
}, },
}); });