diff --git a/components/NotificationItem.vue b/components/NotificationItem.vue index 243f4da..b81ee30 100644 --- a/components/NotificationItem.vue +++ b/components/NotificationItem.vue @@ -44,9 +44,12 @@ diff --git a/composables/notifications.ts b/composables/notifications.ts index ede125b..637fe7e 100644 --- a/composables/notifications.ts +++ b/composables/notifications.ts @@ -1,12 +1,16 @@ +import type { SerializeObject } from "nitropack"; import type { NotificationModel } from "~/prisma/client/models"; const ws = new WebSocketHandler("/api/v1/notifications/ws"); export const useNotifications = () => - useState>("notifications", () => []); + useState>>( + "notifications", + () => [], + ); ws.listen((e) => { - const notification = JSON.parse(e) as NotificationModel; + const notification = JSON.parse(e) as SerializeObject; const notifications = useNotifications(); notifications.value.push(notification); }); diff --git a/eslint.config.mjs b/eslint.config.mjs index 0576c43..a287ae9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -2,6 +2,7 @@ import withNuxt from "./.nuxt/eslint.config.mjs"; import eslintConfigPrettier from "eslint-config-prettier/flat"; import vueI18n from "@intlify/eslint-plugin-vue-i18n"; +import noPrismaDelete from "./rules/no-prisma-delete.mts"; export default withNuxt([ eslintConfigPrettier, @@ -19,6 +20,7 @@ export default withNuxt([ }, ], "@intlify/vue-i18n/no-missing-keys": "error", + "drop/no-prisma-delete": "error", }, settings: { "vue-i18n": { @@ -29,5 +31,8 @@ export default withNuxt([ messageSyntaxVersion: "^11.0.0", }, }, + plugins: { + drop: { rules: { "no-prisma-delete": noPrismaDelete } }, + }, }, ]); diff --git a/i18n/locales/en_us.json b/i18n/locales/en_us.json index 02ff541..d167034 100644 --- a/i18n/locales/en_us.json +++ b/i18n/locales/en_us.json @@ -13,6 +13,7 @@ "all": "View all {arrow}", "desc": "View and manage your notifications.", "markAllAsRead": "Mark all as read", + "clear": "Clear notifications", "markAsRead": "Mark as read", "none": "No notifications", "notifications": "Notifications", diff --git a/package.json b/package.json index 3e632df..bc18b7b 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@discordapp/twemoji": "^16.0.1", - "@drop-oss/droplet": "3.4.0", + "@drop-oss/droplet": "3.5.0", "@headlessui/vue": "^1.7.23", "@heroicons/vue": "^2.1.5", "@lobomfz/prismark": "0.0.3", diff --git a/pages/account/notifications.vue b/pages/account/notifications.vue index f625ab4..04b83f1 100644 --- a/pages/account/notifications.vue +++ b/pages/account/notifications.vue @@ -1,20 +1,32 @@ - + {{ $t("account.notifications.notifications") }} - - - {{ $t("account.notifications.markAllAsRead") }} - + + + + {{ $t("account.notifications.markAllAsRead") }} + + + + {{ $t("account.notifications.clear") }} + + - + {{ notification.title }} @@ -52,7 +64,9 @@ - + @@ -106,22 +120,12 @@ useHead({ }); // Fetch notifications -const notifications = ref[]>([]); - -async function fetchNotifications() { - const { data } = await useFetch("/api/v1/notifications"); - notifications.value = data.value || []; -} - -// Initial fetch -await fetchNotifications(); +const notifications = useNotifications(); // Mark a notification as read async function markAsRead(id: string) { await $dropFetch(`/api/v1/notifications/${id}/read`, { method: "POST" }); - const notification = notifications.value.find( - (n: SerializeObject) => n.id === id, - ); + const notification = notifications.value.find((n) => n.id === id); if (notification) { notification.read = true; } @@ -129,12 +133,21 @@ async function markAsRead(id: string) { // Mark all notifications as read async function markAllAsRead() { - await $dropFetch("/api/v1/notifications/readall", { method: "POST" }); - notifications.value.forEach( - (notification: SerializeObject) => { - notification.read = true; - }, - ); + await $dropFetch("/api/v1/notifications/readall", { + method: "POST", + failTitle: "Failed to read all notifications", + }); + notifications.value.forEach((notification) => { + notification.read = true; + }); +} + +async function clearNotifications() { + await $dropFetch("/api/v1/notifications/clear", { + method: "POST", + failTitle: "Failed to clear notifications", + }); + notifications.value = []; } // Delete a notification diff --git a/pages/admin/settings.vue b/pages/admin/settings.vue index 6d637d1..14870c6 100644 --- a/pages/admin/settings.vue +++ b/pages/admin/settings.vue @@ -2,7 +2,7 @@ - + = 10'} cpu: [arm64] os: [darwin] - '@drop-oss/droplet-darwin-universal@3.4.0': - resolution: {integrity: sha512-OCSdsX1gV0108IoGWxQ0GmkhOHkPsdteHb/QGTYVQlu0niJTGy6BHYCR6RPyq0T+xgY10zTeZDis3UFC/aslhQ==} + '@drop-oss/droplet-darwin-universal@3.5.0': + resolution: {integrity: sha512-FSjTKKUL0+eM1DWxFW969n3kbV6yNFjPU/F1NLwXL9xNoEKyN/A2tJOdSYvlHZNR6IaGL2O1QfBB4L6raADV+A==} engines: {node: '>= 10'} os: [darwin] - '@drop-oss/droplet-darwin-x64@3.4.0': - resolution: {integrity: sha512-AtlKKYtq3iikVi8cqywnrW5FZN90rlt4SzU9Eq/zhPCD+4qIlOOyfA8hX5Wj+qBoCexqWJfnwrkq3Ne+8t+f7A==} + '@drop-oss/droplet-darwin-x64@3.5.0': + resolution: {integrity: sha512-Sgy/UyM7NRWdJY2lpNo1sD0iYx1fPaEQZTgGREXZPNPUkG2uVSlqcl8rsdopFI9ZFc7GD/aSGgXNy+jWhOi0DQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@drop-oss/droplet-linux-arm64-gnu@3.4.0': - resolution: {integrity: sha512-oQMRTlzFW3TE/V7jWHFPYSA9UDSyqBJ1o9xWUg6ScJwkDy4vaGD9gdlndyuoQeD+ninM4AHYSrE94t8V7ptKFw==} + '@drop-oss/droplet-linux-arm64-gnu@3.5.0': + resolution: {integrity: sha512-+eP8W9Hea6koV3XyotBN/iUrmRu9zb9QIHujdDpxmmkp511sKoWEIjKqV+/sC9cR3J3OGILureaXjb39k35nfg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@drop-oss/droplet-linux-arm64-musl@3.4.0': - resolution: {integrity: sha512-DmhTqoxvzggB451wdqGMROvvTGMcyPz3xWc9XT5O/ibJ2EYRYH6D15A0CBGxWjr1TmxMrXrRuU4K+q8lJfq62w==} + '@drop-oss/droplet-linux-arm64-musl@3.5.0': + resolution: {integrity: sha512-zEHEm9PdXncxlAJkafLn8yykpGOr+AfDsjhzTH7yVxBVGl0U8L31nS2BuxKposLD6gKIuzRpFj4mQ+AtOIn+XA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@drop-oss/droplet-linux-riscv64-gnu@3.4.0': - resolution: {integrity: sha512-LbzT3z//Q03KJfLeRprjb7ICiAzBv+mNlrKKlZ1Zisjzgnqk7kx32sz5dStGDL+P+26yUt6inLZBhS56cWIyvA==} + '@drop-oss/droplet-linux-riscv64-gnu@3.5.0': + resolution: {integrity: sha512-SBo5A02oQ/qBWgVrSoE82Lbi5neS6CwlNKEKahG1dmkId/ZPQ9vMxwo5Cdgq3Oa4Lyo9l3RtRQWYFnw6HdG/rw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - '@drop-oss/droplet-linux-x64-gnu@3.4.0': - resolution: {integrity: sha512-U9BIyHxmtDIobyReuaXanYQ/TJOsRGQJ4WlXk+amZbMeAp8dyVawP0pmXbQO/hny/+VlxtAlObeCZ5T3nK+etQ==} + '@drop-oss/droplet-linux-x64-gnu@3.5.0': + resolution: {integrity: sha512-ddJv4UqzVr3GS7W6T9pcKjsY3qv+B+ahdPKP6cIwfL5EMLrKKfFBE7+pbXWEbE0t4q7Qhmj8GxSliCHA5TgCOg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@drop-oss/droplet-linux-x64-musl@3.4.0': - resolution: {integrity: sha512-TQtLGSk6M2fu9K85rS02Yv3Etb5mk77tGIDLY8KAl/fLJOWRxuRfwtTnlIA5IuNnsO/3rOmwCOvpF6tVI010mA==} + '@drop-oss/droplet-linux-x64-musl@3.5.0': + resolution: {integrity: sha512-pDc85qzA4UHvQopnA8nRFg20spDi4gL4yCwlYllJfoDUmXThPIHSQnQ/DurLPwqvJTURwkrfJboBQ93Z+Hnr9A==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@drop-oss/droplet-win32-arm64-msvc@3.4.0': - resolution: {integrity: sha512-z0NTFVefyXgFeeH8NHdM54PFxPctcLDWiQ/UZzsTMhahXj+yQwsAiM2q41GSeBIEBkR9r1IEuqMRhgXeCfyBLg==} + '@drop-oss/droplet-win32-arm64-msvc@3.5.0': + resolution: {integrity: sha512-ZEYCBhRD5VMrbG6p0TFj/TUUskZxQOf0plFFkSqYxeK2BZkwh2cxEw2y977BPo0pfzV5QxbASzXo6I17cJIztg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@drop-oss/droplet-win32-x64-msvc@3.4.0': - resolution: {integrity: sha512-NNbIchHnnwcAPtmwBAY3Y2glhFjndGg+FO0MjvajQIf5ts+b+ss5SSEjDDRw/KkPjlQAxFymRAhBbP7ObwdWQg==} + '@drop-oss/droplet-win32-x64-msvc@3.5.0': + resolution: {integrity: sha512-WloxGl6hJb2mn1N2TFQrrDEmjppDGHLUwegP/6M4FKT63i/SxhIoenCsL2e7qWdhEC7XZZYtl18e6iLt80cl3g==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@drop-oss/droplet@3.4.0': - resolution: {integrity: sha512-hGNoSBUg/tWxYESqHvV5AXBIcra9LXvkFg7WyCdRK9o1xhwTjAOmCaoivG1eSpjXEbWsFB69aI0sdvH/4nHzKg==} + '@drop-oss/droplet@3.5.0': + resolution: {integrity: sha512-FaTwGRl9uWdA1aw/WnbZfyZ7W/b2nEPdBLkdauonQ8OPKqK0k8KgolgyZ87yPFWw0BPyzUyCz4imrmdIIKhFYw==} engines: {node: '>= 10'} '@dxup/nuxt@0.2.2': @@ -7110,48 +7110,48 @@ snapshots: jsonfile: 5.0.0 universalify: 0.1.2 - '@drop-oss/droplet-darwin-arm64@3.4.0': + '@drop-oss/droplet-darwin-arm64@3.5.0': optional: true - '@drop-oss/droplet-darwin-universal@3.4.0': + '@drop-oss/droplet-darwin-universal@3.5.0': optional: true - '@drop-oss/droplet-darwin-x64@3.4.0': + '@drop-oss/droplet-darwin-x64@3.5.0': optional: true - '@drop-oss/droplet-linux-arm64-gnu@3.4.0': + '@drop-oss/droplet-linux-arm64-gnu@3.5.0': optional: true - '@drop-oss/droplet-linux-arm64-musl@3.4.0': + '@drop-oss/droplet-linux-arm64-musl@3.5.0': optional: true - '@drop-oss/droplet-linux-riscv64-gnu@3.4.0': + '@drop-oss/droplet-linux-riscv64-gnu@3.5.0': optional: true - '@drop-oss/droplet-linux-x64-gnu@3.4.0': + '@drop-oss/droplet-linux-x64-gnu@3.5.0': optional: true - '@drop-oss/droplet-linux-x64-musl@3.4.0': + '@drop-oss/droplet-linux-x64-musl@3.5.0': optional: true - '@drop-oss/droplet-win32-arm64-msvc@3.4.0': + '@drop-oss/droplet-win32-arm64-msvc@3.5.0': optional: true - '@drop-oss/droplet-win32-x64-msvc@3.4.0': + '@drop-oss/droplet-win32-x64-msvc@3.5.0': optional: true - '@drop-oss/droplet@3.4.0': + '@drop-oss/droplet@3.5.0': optionalDependencies: - '@drop-oss/droplet-darwin-arm64': 3.4.0 - '@drop-oss/droplet-darwin-universal': 3.4.0 - '@drop-oss/droplet-darwin-x64': 3.4.0 - '@drop-oss/droplet-linux-arm64-gnu': 3.4.0 - '@drop-oss/droplet-linux-arm64-musl': 3.4.0 - '@drop-oss/droplet-linux-riscv64-gnu': 3.4.0 - '@drop-oss/droplet-linux-x64-gnu': 3.4.0 - '@drop-oss/droplet-linux-x64-musl': 3.4.0 - '@drop-oss/droplet-win32-arm64-msvc': 3.4.0 - '@drop-oss/droplet-win32-x64-msvc': 3.4.0 + '@drop-oss/droplet-darwin-arm64': 3.5.0 + '@drop-oss/droplet-darwin-universal': 3.5.0 + '@drop-oss/droplet-darwin-x64': 3.5.0 + '@drop-oss/droplet-linux-arm64-gnu': 3.5.0 + '@drop-oss/droplet-linux-arm64-musl': 3.5.0 + '@drop-oss/droplet-linux-riscv64-gnu': 3.5.0 + '@drop-oss/droplet-linux-x64-gnu': 3.5.0 + '@drop-oss/droplet-linux-x64-musl': 3.5.0 + '@drop-oss/droplet-win32-arm64-msvc': 3.5.0 + '@drop-oss/droplet-win32-x64-msvc': 3.5.0 '@dxup/nuxt@0.2.2(magicast@0.5.1)': dependencies: diff --git a/rules/no-prisma-delete.mts b/rules/no-prisma-delete.mts new file mode 100644 index 0000000..aa8546f --- /dev/null +++ b/rules/no-prisma-delete.mts @@ -0,0 +1,34 @@ +import type { TSESLint } from "@typescript-eslint/utils"; + +export default { + meta: { + type: "problem", + docs: { + description: "Don't use Prisma error-prone .delete function", + }, + messages: { + noPrismaDelete: + "Prisma .delete(...) function is used. Use .deleteMany(..) and check count instead.", + }, + schema: [], + }, + create(context) { + return { + CallExpression: function (node) { + // @ts-expect-error It ain't typing properly + const funcId = node.callee.property; + if (!funcId || funcId.name !== "delete") return; + // @ts-expect-error It ain't typing properly + const tableExpr = node.callee.object; + if (!tableExpr) return; + const prismaExpr = tableExpr.object; + if (!prismaExpr || prismaExpr.name !== "prisma") return; + context.report({ + node, + messageId: "noPrismaDelete", + }); + }, + }; + }, + defaultOptions: [], +} satisfies TSESLint.RuleModule<"noPrismaDelete">; diff --git a/server/api/v1/admin/auth/invitation/index.delete.ts b/server/api/v1/admin/auth/invitation/index.delete.ts index 195aae5..43177ad 100644 --- a/server/api/v1/admin/auth/invitation/index.delete.ts +++ b/server/api/v1/admin/auth/invitation/index.delete.ts @@ -17,6 +17,10 @@ export default defineEventHandler<{ const body = await readDropValidatedBody(h3, DeleteInvite); - await prisma.invitation.delete({ where: { id: body.id } }); + const { count } = await prisma.invitation.deleteMany({ + where: { id: body.id }, + }); + if (count == 0) + throw createError({ statusCode: 404, message: "Invitation not found." }); return {}; }); diff --git a/server/api/v1/admin/game/[id]/index.delete.ts b/server/api/v1/admin/game/[id]/index.delete.ts index 66e7027..1b32550 100644 --- a/server/api/v1/admin/game/[id]/index.delete.ts +++ b/server/api/v1/admin/game/[id]/index.delete.ts @@ -7,7 +7,7 @@ export default defineEventHandler(async (h3) => { const gameId = getRouterParam(h3, "id")!; - libraryManager.deleteGame(gameId); + await libraryManager.deleteGame(gameId); return {}; }); diff --git a/server/api/v1/admin/library/sources/index.delete.ts b/server/api/v1/admin/library/sources/index.delete.ts index 165a9b2..b4594e2 100644 --- a/server/api/v1/admin/library/sources/index.delete.ts +++ b/server/api/v1/admin/library/sources/index.delete.ts @@ -18,11 +18,13 @@ export default defineEventHandler<{ body: typeof DeleteLibrarySource.infer }>( const body = await readDropValidatedBody(h3, DeleteLibrarySource); - await prisma.library.delete({ + const { count } = await prisma.library.deleteMany({ where: { id: body.id, }, }); + if (count == 0) + throw createError({ statusCode: 404, message: "Library not found." }); libraryManager.removeLibrary(body.id); }, diff --git a/server/api/v1/admin/token/[id]/index.delete.ts b/server/api/v1/admin/token/[id]/index.delete.ts index 1e33730..dd9fac5 100644 --- a/server/api/v1/admin/token/[id]/index.delete.ts +++ b/server/api/v1/admin/token/[id]/index.delete.ts @@ -13,10 +13,10 @@ export default defineEventHandler(async (h3) => { statusMessage: "No id in router params", }); - const deleted = await prisma.aPIToken.delete({ + const { count } = await prisma.aPIToken.deleteMany({ where: { id: id, mode: APITokenMode.System }, })!; - if (!deleted) + if (count == 0) throw createError({ statusCode: 404, statusMessage: "Token not found" }); return; diff --git a/server/api/v1/admin/users/[id]/index.delete.ts b/server/api/v1/admin/users/[id]/index.delete.ts index 4b5b141..dc8f790 100644 --- a/server/api/v1/admin/users/[id]/index.delete.ts +++ b/server/api/v1/admin/users/[id]/index.delete.ts @@ -27,6 +27,7 @@ export default defineEventHandler(async (h3) => { if (!user) throw createError({ statusCode: 404, statusMessage: "User not found." }); + // eslint-disable-next-line drop/no-prisma-delete await prisma.user.delete({ where: { id: userId } }); await userStatsManager.deleteUser(); return { success: true }; diff --git a/server/api/v1/auth/signup/simple.post.ts b/server/api/v1/auth/signup/simple.post.ts index 9f11f92..c9e4c94 100644 --- a/server/api/v1/auth/signup/simple.post.ts +++ b/server/api/v1/auth/signup/simple.post.ts @@ -84,7 +84,7 @@ export default defineEventHandler<{ user: true, }, }), - prisma.invitation.delete({ where: { id: user.invitation } }), + prisma.invitation.deleteMany({ where: { id: user.invitation } }), ]); await userStatsManager.addUser(); diff --git a/server/api/v1/client/saves/[gameid]/[slotindex]/index.delete.ts b/server/api/v1/client/saves/[gameid]/[slotindex]/index.delete.ts index 0f36008..a055dd4 100644 --- a/server/api/v1/client/saves/[gameid]/[slotindex]/index.delete.ts +++ b/server/api/v1/client/saves/[gameid]/[slotindex]/index.delete.ts @@ -38,16 +38,14 @@ export default defineClientEventHandler( if (!game) throw createError({ statusCode: 400, statusMessage: "Invalid game ID" }); - const save = await prisma.saveSlot.delete({ + const { count } = await prisma.saveSlot.deleteMany({ where: { - id: { - userId: user.id, - gameId: gameId, - index: slotIndex, - }, + userId: user.id, + gameId: gameId, + index: slotIndex, }, }); - if (!save) + if (count == 0) throw createError({ statusCode: 404, statusMessage: "Save not found" }); }, ); diff --git a/server/api/v1/notifications/[id]/index.delete.ts b/server/api/v1/notifications/[id]/index.delete.ts index ac60839..c47b106 100644 --- a/server/api/v1/notifications/[id]/index.delete.ts +++ b/server/api/v1/notifications/[id]/index.delete.ts @@ -20,14 +20,14 @@ export default defineEventHandler(async (h3) => { userIds.push("system"); } - const notification = await prisma.notification.delete({ + const { count } = await prisma.notification.deleteMany({ where: { id: notificationId, userId: { in: userIds }, }, }); - if (!notification) + if (count == 0) throw createError({ statusCode: 400, statusMessage: "Invalid notification ID", diff --git a/server/api/v1/notifications/clear.post.ts b/server/api/v1/notifications/clear.post.ts new file mode 100644 index 0000000..091d387 --- /dev/null +++ b/server/api/v1/notifications/clear.post.ts @@ -0,0 +1,25 @@ +import aclManager from "~/server/internal/acls"; +import prisma from "~/server/internal/db/database"; + +export default defineEventHandler(async (h3) => { + const userId = await aclManager.getUserIdACL(h3, ["notifications:mark"]); + if (!userId) throw createError({ statusCode: 403 }); + + const acls = await aclManager.fetchAllACLs(h3); + if (!acls) + throw createError({ + statusCode: 500, + statusMessage: "Got userId but no ACLs - what?", + }); + + await prisma.notification.deleteMany({ + where: { + userId, + acls: { + hasSome: acls, + }, + }, + }); + + return; +}); diff --git a/server/api/v1/user/token/[id]/index.delete.ts b/server/api/v1/user/token/[id]/index.delete.ts index 54d1be1..2f0d473 100644 --- a/server/api/v1/user/token/[id]/index.delete.ts +++ b/server/api/v1/user/token/[id]/index.delete.ts @@ -13,10 +13,10 @@ export default defineEventHandler(async (h3) => { statusMessage: "No id in router params", }); - const deleted = await prisma.aPIToken.delete({ + const { count } = await prisma.aPIToken.deleteMany({ where: { id: id, userId: userId, mode: APITokenMode.User }, })!; - if (!deleted) + if (count == 0) throw createError({ statusCode: 404, statusMessage: "Token not found" }); return; diff --git a/server/internal/auth/oidc/index.ts b/server/internal/auth/oidc/index.ts index 4508d48..2d55ad4 100644 --- a/server/internal/auth/oidc/index.ts +++ b/server/internal/auth/oidc/index.ts @@ -66,6 +66,7 @@ export class OIDCManager { async create() { const wellKnownUrl = process.env.OIDC_WELLKNOWN as string | undefined; + const scopes = process.env.OIDC_SCOPES as string | undefined; let configuration: OIDCWellKnown; if (wellKnownUrl) { const response: OIDCWellKnown = await $fetch(wellKnownUrl); @@ -77,6 +78,9 @@ export class OIDCManager { ) { throw new Error("Well known response was invalid"); } + if (scopes) { + response.scopes_supported = scopes.split(","); + } configuration = response; } else { @@ -85,7 +89,6 @@ export class OIDCManager { | undefined; const tokenEndpoint = process.env.OIDC_TOKEN as string | undefined; const userinfoEndpoint = process.env.OIDC_USERINFO as string | undefined; - const scopes = process.env.OIDC_SCOPES as string | undefined; if ( !authorizationEndpoint || diff --git a/server/internal/clients/handler.ts b/server/internal/clients/handler.ts index 960dd55..6b7da5f 100644 --- a/server/internal/clients/handler.ts +++ b/server/internal/clients/handler.ts @@ -185,15 +185,19 @@ export class ClientHandler { } async removeClient(id: string) { + const client = await prisma.client.findUnique({ where: { id } }); + if (!client) return false; const ca = useCertificateAuthority(); await ca.blacklistClient(id); + // eslint-disable-next-line drop/no-prisma-delete await prisma.client.delete({ where: { id, }, }); await userStatsManager.cacheUserStats(); + return true; } } diff --git a/server/internal/library/index.ts b/server/internal/library/index.ts index 0e6f0de..4c5b54c 100644 --- a/server/internal/library/index.ts +++ b/server/internal/library/index.ts @@ -378,12 +378,10 @@ class LibraryManager { } async deleteGameVersion(gameId: string, version: string) { - await prisma.gameVersion.delete({ + await prisma.gameVersion.deleteMany({ where: { - gameId_versionName: { - gameId: gameId, - versionName: version, - }, + gameId: gameId, + versionName: version, }, }); @@ -391,12 +389,12 @@ class LibraryManager { } async deleteGame(gameId: string) { - await prisma.game.delete({ + await prisma.game.deleteMany({ where: { id: gameId, }, }); - gameSizeManager.deleteGame(gameId); + await gameSizeManager.deleteGame(gameId); } async getGameVersionSize( diff --git a/server/internal/news/index.ts b/server/internal/news/index.ts index 00725eb..694ff26 100644 --- a/server/internal/news/index.ts +++ b/server/internal/news/index.ts @@ -124,7 +124,10 @@ class NewsManager { } async delete(id: string) { - const article = await prisma.article.delete({ + const article = await prisma.article.findUnique({ where: { id } }); + if (!article) return false; + // eslint-disable-next-line drop/no-prisma-delete + await prisma.article.delete({ where: { id }, }); if (article.imageObjectId) { diff --git a/server/internal/objects/fsBackend.ts b/server/internal/objects/fsBackend.ts index aedab6a..e6f58ec 100644 --- a/server/internal/objects/fsBackend.ts +++ b/server/internal/objects/fsBackend.ts @@ -259,16 +259,10 @@ class FsHashStore { */ async delete(id: ObjectReference) { await this.cache.remove(id); - - try { - // need to catch in case the object doesn't exist - await prisma.objectHash.delete({ - where: { - id, - }, - }); - } catch { - /* empty */ - } + await prisma.objectHash.deleteMany({ + where: { + id, + }, + }); } } diff --git a/server/internal/screenshots/index.ts b/server/internal/screenshots/index.ts index b41ac03..6035d69 100644 --- a/server/internal/screenshots/index.ts +++ b/server/internal/screenshots/index.ts @@ -53,12 +53,16 @@ class ScreenshotManager { * @param id */ async delete(id: string) { - const deletedScreenshot = await prisma.screenshot.delete({ + const screenshot = await prisma.screenshot.findUnique({ where: { id } }); + if (!screenshot) return false; + // eslint-disable-next-line drop/no-prisma-delete + await prisma.screenshot.delete({ where: { id, }, }); - await objectHandler.deleteAsSystem(deletedScreenshot.objectId); + await objectHandler.deleteAsSystem(screenshot.objectId); + return true; } /** diff --git a/server/internal/session/db.ts b/server/internal/session/db.ts index 5b8fd2e..de23a4c 100644 --- a/server/internal/session/db.ts +++ b/server/internal/session/db.ts @@ -43,12 +43,12 @@ export default function createDBSessionHandler(): SessionProvider { }, async removeSession(token) { await cache.remove(token); - await prisma.session.delete({ + const { count } = await prisma.session.deleteMany({ where: { token, }, }); - return true; + return count > 0; }, async cleanupSessions() { const now = new Date(); diff --git a/server/internal/userlibrary/index.ts b/server/internal/userlibrary/index.ts index 16db484..996ad3f 100644 --- a/server/internal/userlibrary/index.ts +++ b/server/internal/userlibrary/index.ts @@ -101,19 +101,16 @@ class UserLibraryManager { async collectionRemove(gameId: string, collectionId: string, userId: string) { // Delete if exists - return ( - ( - await prisma.collectionEntry.deleteMany({ - where: { - collectionId, - gameId, - collection: { - userId, - }, - }, - }) - ).count > 0 - ); + const { count } = await prisma.collectionEntry.deleteMany({ + where: { + collectionId, + gameId, + collection: { + userId, + }, + }, + }); + return count > 0; } async collectionCreate(name: string, userId: string) { @@ -133,12 +130,13 @@ class UserLibraryManager { } async deleteCollection(collectionId: string) { - await prisma.collection.delete({ + const { count } = await prisma.collection.deleteMany({ where: { id: collectionId, isDefault: false, }, }); + return count > 0; } } diff --git a/tsconfig.json b/tsconfig.json index 71aa8b8..3d75e0f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ // https://nuxt.com/docs/guide/concepts/typescript "extends": "./.nuxt/tsconfig.json", "compilerOptions": { - "exactOptionalPropertyTypes": false + "exactOptionalPropertyTypes": false, + "allowImportingTsExtensions": true } }