feat: auditing for bubble edits

This commit is contained in:
Ashhhleyyy 2022-07-26 22:02:44 +01:00
parent b51c7bded3
commit 3f28cfcfd8
Signed by: ash
GPG key ID: 83B789081A0878FB
10 changed files with 320 additions and 23 deletions

View file

@ -28,7 +28,7 @@ export default function Button({
'transition-colors'
);
if (!noDefaultColous) {
classes.push('bg-slate-600', 'text-slate-50', 'hover:bg-slate-500');
classes.push('bg-slate-600', 'text-slate-50', 'hover:bg-slate-500', 'disabled:bg-slate-900', 'disabled:text-slate-200');
}
return <button className={classes.join(' ')} {...props} />;
}

View file

@ -6,8 +6,6 @@ export default function NavBar() {
const { data, status } = useSession();
const router = useRouter();
console.log(data);
return (
<nav className='flex flex-row h-12 bg-gray-900 justify-end items-center p-4 gap-1 shadow mb-4'>
{status === 'loading' && <>...</>}

11
next-auth.d.ts vendored Normal file
View file

@ -0,0 +1,11 @@
import NextAuth, { DefaultSession } from "next-auth"
declare module "next-auth" {
interface Session {
user: {
/** The user's internal ID. */
id: string;
} & DefaultSession["user"]
}
}

View file

@ -17,6 +17,8 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@next-auth/prisma-adapter": "^1.0.4",
"@prisma/client": "^4.1.0",
"diff": "^5.1.0",
"discord.js": "^14.0.3",
"next": "12.2.2",
"next-auth": "^4.10.1",
"react": "18.2.0",
@ -26,6 +28,7 @@
"zod": "^3.17.10"
},
"devDependencies": {
"@types/diff": "^5.0.2",
"@types/node": "18.0.6",
"@types/react": "18.0.15",
"@types/react-dom": "18.0.6",

View file

@ -30,6 +30,12 @@ export const authOptions: NextAuthOptions = {
params.account['not-before-policy'] = undefined;
return true;
},
session(params) {
if (params.session.user) {
params.session.user.id = params.user.id;
}
return params.session;
},
},
};

View file

@ -2,6 +2,7 @@ import { ComicBubble, Prisma } from '@prisma/client';
import { NextApiRequest, NextApiResponse } from 'next';
import { unstable_getServerSession } from 'next-auth';
import { z } from 'zod';
import { auditPageUpdate } from '../../src/audit';
import { prisma } from '../../src/db';
import { authOptions } from './auth/[...nextauth]';
@ -23,6 +24,8 @@ const PageBubblesSchema = z.object({
pageId: z.number(),
});
export type PageBubbles = z.infer<typeof PageBubblesSchema>;
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
@ -31,7 +34,7 @@ export default async function handler(
return res.status(405).json({ error: 'method not allowed' });
const session = await unstable_getServerSession(req, res, authOptions);
if (!session)
if (!session || !session.user)
return res.status(401).json({ error: 'authentication required' });
const data = PageBubblesSchema.parse(req.body);
@ -41,6 +44,9 @@ export default async function handler(
comicId: data.comicId,
id: data.pageId,
},
include: {
bubbles: true,
},
});
if (!page) return res.status(404).json({ error: 'not found' });
@ -92,5 +98,22 @@ export default async function handler(
}
await prisma.$transaction(ops);
const after = await prisma.comicPage.update({
where: {
comicId_id: {
comicId: page.comicId,
id: page.id,
},
},
data: {
lastEditedById: session.user.id,
},
include: {
bubbles: true,
}
});
await auditPageUpdate(page, after, data, session.user);
res.status(201).json({});
}

View file

@ -7,11 +7,14 @@ specifiers:
'@fortawesome/react-fontawesome': ^0.2.0
'@next-auth/prisma-adapter': ^1.0.4
'@prisma/client': ^4.1.0
'@types/diff': ^5.0.2
'@types/node': 18.0.6
'@types/react': 18.0.15
'@types/react-dom': 18.0.6
'@typescript-eslint/eslint-plugin': ^5.31.0
autoprefixer: ^10.4.7
diff: ^5.1.0
discord.js: ^14.0.3
eslint: 8.20.0
eslint-config-next: 12.2.2
next: 12.2.2
@ -35,6 +38,8 @@ dependencies:
'@fortawesome/react-fontawesome': 0.2.0_990294e19fa18431d2a389426fd0d6bc
'@next-auth/prisma-adapter': 1.0.4_9818e1f55ecb9769742928f823304203
'@prisma/client': 4.1.0_prisma@4.1.0
diff: 5.1.0
discord.js: 14.0.3
next: 12.2.2_react-dom@18.2.0+react@18.2.0
next-auth: 4.10.1_react-dom@18.2.0+react@18.2.0
react: 18.2.0
@ -44,6 +49,7 @@ dependencies:
zod: 3.17.10
devDependencies:
'@types/diff': 5.0.2
'@types/node': 18.0.6
'@types/react': 18.0.15
'@types/react-dom': 18.0.6
@ -141,6 +147,35 @@ packages:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@discordjs/builders/1.0.0:
resolution: {integrity: sha512-8y91ZfpOHubiGJu5tVyGI9tQCEyHZDTeqUWVcJd0dq7B96xIf84S0L4fwmD1k9zTe1eqEFSk0gc7BpY+FKn7Ww==}
engines: {node: '>=16.9.0'}
dependencies:
'@sapphire/shapeshift': 3.5.1
discord-api-types: 0.36.3
fast-deep-equal: 3.1.3
ts-mixer: 6.0.1
tslib: 2.4.0
dev: false
/@discordjs/collection/1.0.0:
resolution: {integrity: sha512-nAxDQYE5dNAzEGQ7HU20sujDsG5vLowUKCEqZkKUIlrXERZFTt/60zKUj/g4+AVCGeq+pXC5hivMaNtiC+PY5Q==}
engines: {node: '>=16.9.0'}
dev: false
/@discordjs/rest/1.0.0:
resolution: {integrity: sha512-uDAvnE0P2a8axMdD4C51EGjvCRQ2HZk2Yxf6vHWZgIqG87D8DGKMPwmquIxrrB07MjV+rwci2ObU+mGhGP+bJg==}
engines: {node: '>=16.9.0'}
dependencies:
'@discordjs/collection': 1.0.0
'@sapphire/async-queue': 1.3.2
'@sapphire/snowflake': 3.2.2
discord-api-types: 0.36.3
file-type: 17.1.4
tslib: 2.4.0
undici: 5.8.0
dev: false
/@emotion/babel-plugin/11.9.2:
resolution: {integrity: sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==}
peerDependencies:
@ -497,12 +532,34 @@ packages:
resolution: {integrity: sha512-LwzQKA4vzIct1zNZzBmRKI9QuNpLgTQMEjsQLf3BXuGYb3QPTP4Yjf6mkdX+X1mYttZ808QpOwAzZjv28kq7DA==}
dev: true
/@sapphire/async-queue/1.3.2:
resolution: {integrity: sha512-rUpMLATsoAMnlN3gecAcr9Ecnw1vG7zi5Xr+IX22YzRzi1k9PF9vKzoT8RuEJbiIszjcimu3rveqUnvwDopz8g==}
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
dev: false
/@sapphire/shapeshift/3.5.1:
resolution: {integrity: sha512-7JFsW5IglyOIUQI1eE0g6h06D/Far6HqpcowRScgCiLSqTf3hhkPWCWotVTtVycnDCMYIwPeaw6IEPBomKC8pA==}
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
dependencies:
fast-deep-equal: 3.1.3
lodash.uniqwith: 4.5.0
dev: false
/@sapphire/snowflake/3.2.2:
resolution: {integrity: sha512-ula2O0kpSZtX9rKXNeQMrHwNd7E4jPDJYUXmEGTFdMRfyfMw+FPyh04oKMjAiDuOi64bYgVkOV3MjK+loImFhQ==}
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
dev: false
/@swc/helpers/0.4.2:
resolution: {integrity: sha512-556Az0VX7WR6UdoTn4htt/l3zPQ7bsQWK+HqdG4swV7beUCxo/BqmvbOpUkTIm/9ih86LIf1qsUnywNL3obGHw==}
dependencies:
tslib: 2.4.0
dev: false
/@tokenizer/token/0.3.0:
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
dev: false
/@tsconfig/node10/1.0.9:
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
dev: true
@ -519,6 +576,10 @@ packages:
resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==}
dev: true
/@types/diff/5.0.2:
resolution: {integrity: sha512-uw8eYMIReOwstQ0QKF0sICefSy8cNO/v7gOTiIy9SbwuHyEecJUm7qlgueOO5S1udZ5I/irVydHVwMchgzbKTg==}
dev: true
/@types/json-schema/7.0.11:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true
@ -529,7 +590,6 @@ packages:
/@types/node/18.0.6:
resolution: {integrity: sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw==}
dev: true
/@types/parse-json/4.0.0:
resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==}
@ -560,6 +620,12 @@ packages:
/@types/scheduler/0.16.2:
resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
/@types/ws/8.5.3:
resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==}
dependencies:
'@types/node': 18.0.6
dev: false
/@typescript-eslint/eslint-plugin/5.31.0_eslint@8.20.0+typescript@4.7.4:
resolution: {integrity: sha512-VKW4JPHzG5yhYQrQ1AzXgVgX8ZAJEvCz0QI6mLRX4tf7rnFfh5D8SKm0Pq6w5PyNfAWJk6sv313+nEt3ohWMBQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -1126,6 +1192,11 @@ packages:
engines: {node: '>=0.3.1'}
dev: true
/diff/5.1.0:
resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
engines: {node: '>=0.3.1'}
dev: false
/dir-glob/3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@ -1133,6 +1204,30 @@ packages:
path-type: 4.0.0
dev: true
/discord-api-types/0.36.3:
resolution: {integrity: sha512-bz/NDyG0KBo/tY14vSkrwQ/n3HKPf87a0WFW/1M9+tXYK+vp5Z5EksawfCWo2zkAc6o7CClc0eff1Pjrqznlwg==}
dev: false
/discord.js/14.0.3:
resolution: {integrity: sha512-wH/VQl4CqN8/+dcXEtYis1iurqxGlDpEe0O4CqH5FGqZGIjVpTdtK0STXXx7bVNX8MT/0GvLZLkmO/5gLDWZVg==}
engines: {node: '>=16.9.0'}
dependencies:
'@discordjs/builders': 1.0.0
'@discordjs/collection': 1.0.0
'@discordjs/rest': 1.0.0
'@sapphire/snowflake': 3.2.2
'@types/ws': 8.5.3
discord-api-types: 0.36.3
fast-deep-equal: 3.1.3
lodash.snakecase: 4.1.1
tslib: 2.4.0
undici: 5.8.0
ws: 8.8.1
transitivePeerDependencies:
- bufferutil
- utf-8-validate
dev: false
/dlv/1.1.3:
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
dev: true
@ -1483,7 +1578,6 @@ packages:
/fast-deep-equal/3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
dev: true
/fast-glob/3.2.11:
resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==}
@ -1522,6 +1616,15 @@ packages:
engines: {node: '>=8'}
dev: false
/file-type/17.1.4:
resolution: {integrity: sha512-3w/rJUUPBj6CYhVER3D5JCKwYJJiC36uj5dP+LnyubHI6H6FJo1TeWVCEA09YLVoZqV3/mLP26j9+Pz1GjAyjQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
readable-web-to-node-stream: 3.0.2
strtok3: 7.0.0-alpha.9
token-types: 5.0.0-alpha.2
dev: false
/fill-range/7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
@ -1706,6 +1809,10 @@ packages:
resolution: {integrity: sha512-slx8Q6oywCCSfKgPgL0sEsXtPVnSbTLWpyiDcu6msHOyKOLari1TD1qocXVCft80umnkk3/Qqh3lwoFt8T/BPQ==}
dev: false
/ieee754/1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
dev: false
/ignore/5.2.0:
resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==}
engines: {node: '>= 4'}
@ -1954,6 +2061,14 @@ packages:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
dev: true
/lodash.snakecase/4.1.1:
resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==}
dev: false
/lodash.uniqwith/4.5.0:
resolution: {integrity: sha512-7lYL8bLopMoy4CTICbxygAUq6CdRJ36vFc80DucPueUee+d5NBRxz3FdT9Pes/HEx5mPoT9jwnsEJWz1N7uq7Q==}
dev: false
/loose-envify/1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
@ -2276,6 +2391,11 @@ packages:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
/peek-readable/5.0.0-alpha.5:
resolution: {integrity: sha512-pJohF/tDwV3ntnT5+EkUo4E700q/j/OCDuPxtM+5/kFGjyOai/sK4/We4Cy1MB2OiTQliWU5DxPvYIKQAdPqAA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: false
/picocolors/1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
@ -2491,6 +2611,22 @@ packages:
pify: 2.3.0
dev: true
/readable-stream/3.6.0:
resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==}
engines: {node: '>= 6'}
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
dev: false
/readable-web-to-node-stream/3.0.2:
resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==}
engines: {node: '>=8'}
dependencies:
readable-stream: 3.6.0
dev: false
/readdirp/3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
@ -2563,6 +2699,10 @@ packages:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
dev: false
/safe-buffer/5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: false
/scheduler/0.23.0:
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
dependencies:
@ -2645,6 +2785,12 @@ packages:
es-abstract: 1.20.1
dev: true
/string_decoder/1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
dependencies:
safe-buffer: 5.2.1
dev: false
/strip-ansi/6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
@ -2662,6 +2808,14 @@ packages:
engines: {node: '>=8'}
dev: true
/strtok3/7.0.0-alpha.9:
resolution: {integrity: sha512-G8WxjBFjTZ77toVElv1i7k3jCXNkBB14FVaZ/6LIOka/WGo4La5XHLrU7neFVLdKbXESZf4BejVKZu5maOmocA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
'@tokenizer/token': 0.3.0
peek-readable: 5.0.0-alpha.5
dev: false
/styled-jsx/5.0.2_react@18.2.0:
resolution: {integrity: sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ==}
engines: {node: '>= 12.0.0'}
@ -2772,10 +2926,22 @@ packages:
is-number: 7.0.0
dev: true
/token-types/5.0.0-alpha.2:
resolution: {integrity: sha512-EsG9UxAW4M6VATrEEjhPFTKEUi1OiJqTUMIZOGBN49fGxYjZB36k0p7to3HZSmWRoHm1QfZgrg3e02fpqAt5fQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
'@tokenizer/token': 0.3.0
ieee754: 1.2.1
dev: false
/tr46/0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
dev: false
/ts-mixer/6.0.1:
resolution: {integrity: sha512-hvE+ZYXuINrx6Ei6D6hz+PTim0Uf++dYbK9FFifLNwQj+RwKquhQpn868yZsCtJYiclZF1u8l6WZxxKi+vv7Rg==}
dev: false
/ts-node/10.9.1_98dbbc3ccd2e3c16131a870f4ea5cdb2:
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true
@ -2861,6 +3027,11 @@ packages:
which-boxed-primitive: 1.0.2
dev: true
/undici/5.8.0:
resolution: {integrity: sha512-1F7Vtcez5w/LwH2G2tGnFIihuWUlc58YidwLiCv+jR2Z50x0tNXpRRw7eOIJ+GvqCqIkg9SB7NWAJ/T9TLfv8Q==}
engines: {node: '>=12.18'}
dev: false
/update-browserslist-db/1.0.5_browserslist@4.21.2:
resolution: {integrity: sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==}
hasBin: true
@ -2888,7 +3059,6 @@ packages:
/util-deprecate/1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
/uuid/8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
@ -2940,6 +3110,19 @@ packages:
/wrappy/1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
/ws/8.8.1:
resolution: {integrity: sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==}
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
dev: false
/xtend/4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}

View file

@ -0,0 +1,22 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_ComicPage" (
"id" INTEGER NOT NULL,
"comicId" INTEGER NOT NULL,
"title" TEXT NOT NULL,
"url" TEXT NOT NULL,
"imageUrl" TEXT NOT NULL,
"width" INTEGER NOT NULL,
"height" INTEGER NOT NULL,
"lastEditedById" TEXT,
PRIMARY KEY ("comicId", "id"),
CONSTRAINT "ComicPage_comicId_fkey" FOREIGN KEY ("comicId") REFERENCES "Comic" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "ComicPage_lastEditedById_fkey" FOREIGN KEY ("lastEditedById") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_ComicPage" ("comicId", "height", "id", "imageUrl", "title", "url", "width") SELECT "comicId", "height", "id", "imageUrl", "title", "url", "width" FROM "ComicPage";
DROP TABLE "ComicPage";
ALTER TABLE "new_ComicPage" RENAME TO "ComicPage";
CREATE UNIQUE INDEX "ComicPage_comicId_id_key" ON "ComicPage"("comicId", "id");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View file

@ -21,40 +21,43 @@ model Comic {
model ComicPage {
id Int
comic Comic @relation(fields: [comicId], references: [id])
comic Comic @relation(fields: [comicId], references: [id])
comicId Int
title String
url String
imageUrl String
width Int
height Int
bubbles ComicBubble[]
bubbles ComicBubble[]
lastEditedBy User? @relation(fields: [lastEditedById], references: [id])
lastEditedById String?
@@id([comicId, id])
@@unique([comicId, id])
}
model ComicCharacter {
id Int @id @default(autoincrement())
comic Comic @relation(fields: [comicId], references: [id])
id Int @id @default(autoincrement())
comic Comic @relation(fields: [comicId], references: [id])
comicId Int
shortName String
longName String @unique
bubbles ComicBubble[]
longName String @unique
bubbles ComicBubble[]
}
model ComicBubble {
comicId Int
page ComicPage @relation(fields: [comicId, pageId], references: [comicId, id])
pageId Int
idx Int
comicId Int
page ComicPage @relation(fields: [comicId, pageId], references: [comicId, id])
pageId Int
idx Int
character ComicCharacter @relation(fields: [characterId], references: [id])
character ComicCharacter @relation(fields: [characterId], references: [id])
characterId Int
areaX Int
areaY Int
areaWidth Int
areaX Int
areaY Int
areaWidth Int
areaHeight Int
content String
@ -94,11 +97,13 @@ model Session {
}
model User {
id String @id @default(cuid())
id String @id @default(cuid())
name String?
email String? @unique
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
lastEditedPages ComicPage[]
}

46
src/audit.ts Normal file
View file

@ -0,0 +1,46 @@
import { ComicBubble, ComicPage } from "@prisma/client";
import { WebhookClient } from "discord.js";
import { Session } from "next-auth";
import { PageBubbles } from "../pages/api/submit-page-bubbles";
import { createPatch } from 'diff';
type Page = ComicPage & { bubbles: ComicBubble[] };
const DJS_CLIENT = new WebhookClient({
url: process.env.AUDIT_WEBHOOK!,
}, {
allowedMentions: {
parse: [],
},
})
export async function auditPageUpdate(before: Page, after: Page, update: PageBubbles, user: Session['user']) {
const beforeS = JSON.stringify(before, null, 2);
const afterS = JSON.stringify(after, null, 2);
if (beforeS === afterS) {
return;
}
const patch = createPatch(`/comic/${update.comicId}/${update.pageId}`, beforeS, afterS);
await DJS_CLIENT.send({
embeds: [
{
title: 'Page update',
description: '```patch\n' + patch + '\n```',
fields: [
{
name: 'Comic',
value: update.comicId.toString(),
},
{
name: 'Page',
value: update.pageId.toString(),
},
],
footer: {
text: `${user.name} (${user.id})`,
},
}
]
})
}