From 329c74d3ce3a72ca2d4a0777a4e19ccb3e1556d9 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Mon, 14 Oct 2024 20:34:23 +1100 Subject: [PATCH] game version re-ordering --- package.json | 9 +- pages/admin/library/[id]/import.vue | 44 +++++++++- pages/admin/library/[id]/index.vue | 88 ++++++++++++++++--- plugins/vuedraggable.ts | 5 ++ .../migration.sql | 9 ++ .../migration.sql | 8 ++ prisma/schema.prisma | 4 +- server/api/v1/admin/game/index.get.ts | 13 +++ server/api/v1/admin/game/version.delete.ts | 26 ++++++ server/api/v1/admin/game/version.post.ts | 40 +++++++++ .../api/v1/admin/import/version/index.post.ts | 23 ++--- .../{endpoint.get.ts => endpoints.get.ts} | 0 server/api/v1/client/metadata/manifest.get.ts | 12 +++ server/api/v1/client/metadata/versions.get.ts | 8 ++ .../internal/{downloads/README.md => P2P.md} | 14 ++- server/internal/downloads/manifest.ts | 52 +++++++++++ server/internal/library/index.ts | 9 +- yarn.lock | 40 ++++++--- 18 files changed, 354 insertions(+), 50 deletions(-) create mode 100644 plugins/vuedraggable.ts create mode 100644 prisma/migrations/20241014052934_add_delta_and_order/migration.sql create mode 100644 prisma/migrations/20241014053941_remove_version_order/migration.sql create mode 100644 server/api/v1/admin/game/version.delete.ts create mode 100644 server/api/v1/admin/game/version.post.ts rename server/api/v1/client/metadata/{endpoint.get.ts => endpoints.get.ts} (100%) create mode 100644 server/api/v1/client/metadata/manifest.get.ts rename server/internal/{downloads/README.md => P2P.md} (54%) create mode 100644 server/internal/downloads/manifest.ts diff --git a/package.json b/package.json index deb17af..20b30bf 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "postinstall": "nuxt prepare" }, "dependencies": { - "@drop/droplet": "^0.4.4", + "@drop/droplet": "^0.5.0", "@headlessui/vue": "^1.7.23", "@heroicons/vue": "^2.1.5", "@prisma/client": "5.20.0", @@ -28,7 +28,8 @@ "turndown": "^7.2.0", "uuid": "^10.0.0", "vue": "latest", - "vue-router": "latest" + "vue-router": "latest", + "vuedraggable": "^4.1.0" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e", "devDependencies": { @@ -46,7 +47,7 @@ "tailwindcss": "^3.4.13" }, "optionalDependencies": { - "@drop/droplet-linux-x64-gnu": "^0.4.4", - "@drop/droplet-win32-x64-msvc": "^0.4.4" + "@drop/droplet-linux-x64-gnu": "^0.5.0", + "@drop/droplet-win32-x64-msvc": "^0.5.0" } } diff --git a/pages/admin/library/[id]/import.vue b/pages/admin/library/[id]/import.vue index 53bbe41..feaeea7 100644 --- a/pages/admin/library/[id]/import.vue +++ b/pages/admin/library/[id]/import.vue @@ -74,7 +74,7 @@ -
+
-
+
-
-

- Images -

-
-
+

+ Images +

+
+
+

+ Manage version order +

+
lowest
+ + + +
highest
+ {{ game.versions }} +
diff --git a/plugins/vuedraggable.ts b/plugins/vuedraggable.ts new file mode 100644 index 0000000..58d50a0 --- /dev/null +++ b/plugins/vuedraggable.ts @@ -0,0 +1,5 @@ +import draggable from "vuedraggable"; + +export default defineNuxtPlugin((nuxtApp) => { + nuxtApp.vueApp.component("draggable", draggable); +}); diff --git a/prisma/migrations/20241014052934_add_delta_and_order/migration.sql b/prisma/migrations/20241014052934_add_delta_and_order/migration.sql new file mode 100644 index 0000000..9619d00 --- /dev/null +++ b/prisma/migrations/20241014052934_add_delta_and_order/migration.sql @@ -0,0 +1,9 @@ +/* + Warnings: + + - Added the required column `versionIndex` to the `GameVersion` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "GameVersion" ADD COLUMN "delta" BOOLEAN NOT NULL DEFAULT false, +ADD COLUMN "versionIndex" INTEGER NOT NULL; diff --git a/prisma/migrations/20241014053941_remove_version_order/migration.sql b/prisma/migrations/20241014053941_remove_version_order/migration.sql new file mode 100644 index 0000000..8e05931 --- /dev/null +++ b/prisma/migrations/20241014053941_remove_version_order/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - You are about to drop the column `versionOrder` on the `Game` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "Game" DROP COLUMN "versionOrder"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9eb6e7b..fefe74b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -91,7 +91,6 @@ model Game { mCoverId String mImageLibrary String[] // linked to objects in s3 - versionOrder String[] versions GameVersion[] libraryBasePath String @unique // Base dir for all the game versions @@ -109,6 +108,9 @@ model GameVersion { setupCommand String // Command to setup game (dependencies and such) dropletManifest Json // Results from droplet + versionIndex Int + delta Boolean @default(false) + @@id([gameId, versionName]) } diff --git a/server/api/v1/admin/game/index.get.ts b/server/api/v1/admin/game/index.get.ts index 4afbd2e..06c7984 100644 --- a/server/api/v1/admin/game/index.get.ts +++ b/server/api/v1/admin/game/index.get.ts @@ -16,6 +16,19 @@ export default defineEventHandler(async (h3) => { where: { id: gameId, }, + include: { + versions: { + orderBy: { + versionIndex: "asc", + }, + select: { + versionIndex: true, + versionName: true, + platform: true, + delta: true, + } + }, + }, }); if (!game) diff --git a/server/api/v1/admin/game/version.delete.ts b/server/api/v1/admin/game/version.delete.ts new file mode 100644 index 0000000..a80da87 --- /dev/null +++ b/server/api/v1/admin/game/version.delete.ts @@ -0,0 +1,26 @@ +import prisma from "~/server/internal/db/database"; + +export default defineEventHandler(async (h3) => { + const user = await h3.context.session.getAdminUser(h3); + if (!user) throw createError({ statusCode: 403 }); + + const body = await readBody(h3); + const gameId = body.id.toString(); + const version = body.versionName.toString(); + if (!gameId || !version) + throw createError({ + statusCode: 400, + statusMessage: "Missing ID or versionName in body", + }); + + await prisma.gameVersion.delete({ + where: { + gameId_versionName: { + gameId: gameId, + versionName: version, + }, + }, + }); + + return {}; +}); diff --git a/server/api/v1/admin/game/version.post.ts b/server/api/v1/admin/game/version.post.ts new file mode 100644 index 0000000..bbb2fc3 --- /dev/null +++ b/server/api/v1/admin/game/version.post.ts @@ -0,0 +1,40 @@ +import prisma from "~/server/internal/db/database"; + +export default defineEventHandler(async (h3) => { + const user = await h3.context.session.getAdminUser(h3); + if (!user) throw createError({ statusCode: 403 }); + + const body = await readBody(h3); + const gameId = body.id?.toString(); + // We expect an array of the version names for this game + const versions: string[] | undefined = body.versions; + if (!gameId || !versions || !Array.isArray(versions)) + throw createError({ + statusCode: 400, + statusMessage: "Missing id, versions or versions is not an array", + }); + + const newVersions = await prisma.$transaction( + versions.map((versionName, versionIndex) => + prisma.gameVersion.update({ + where: { + gameId_versionName: { + gameId: gameId, + versionName: versionName, + }, + }, + data: { + versionIndex: versionIndex, + }, + select: { + versionIndex: true, + versionName: true, + platform: true, + delta: true, + } + }) + ) + ); + + return newVersions; +}); diff --git a/server/api/v1/admin/import/version/index.post.ts b/server/api/v1/admin/import/version/index.post.ts index 162d4a5..a107ea6 100644 --- a/server/api/v1/admin/import/version/index.post.ts +++ b/server/api/v1/admin/import/version/index.post.ts @@ -10,23 +10,24 @@ export default defineEventHandler(async (h3) => { const platform = body.platform; const startup = body.startup; const setup = body.setup ?? ""; - if ( - !gameId || - !versionName || - !platform || - !startup - ) + const delta = body.delta ?? false; + if (!gameId || !versionName || !platform || (!delta && !startup)) throw createError({ statusCode: 400, statusMessage: "Missing id, version, platform, setup or startup from body", }); - const taskId = await libraryManager.importVersion(gameId, versionName, { - platform, - startup, - setup, - }); + const taskId = await libraryManager.importVersion( + gameId, + versionName, + { + platform, + startup, + setup, + }, + delta + ); if (!taskId) throw createError({ statusCode: 400, diff --git a/server/api/v1/client/metadata/endpoint.get.ts b/server/api/v1/client/metadata/endpoints.get.ts similarity index 100% rename from server/api/v1/client/metadata/endpoint.get.ts rename to server/api/v1/client/metadata/endpoints.get.ts diff --git a/server/api/v1/client/metadata/manifest.get.ts b/server/api/v1/client/metadata/manifest.get.ts new file mode 100644 index 0000000..bc3df5b --- /dev/null +++ b/server/api/v1/client/metadata/manifest.get.ts @@ -0,0 +1,12 @@ +import { defineClientEventHandler } from "~/server/internal/clients/event-handler"; + +export default defineClientEventHandler(async (h3) => { + const query = getQuery(h3); + const id = query.id?.toString(); + const version = query.version?.toString(); + if (!id || !version) + throw createError({ + statusCode: 400, + statusMessage: "Missing id or version in query", + }); +}); diff --git a/server/api/v1/client/metadata/versions.get.ts b/server/api/v1/client/metadata/versions.get.ts index 5d49db0..d40423a 100644 --- a/server/api/v1/client/metadata/versions.get.ts +++ b/server/api/v1/client/metadata/versions.get.ts @@ -14,6 +14,14 @@ export default defineClientEventHandler(async (h3, {}) => { where: { gameId: id, }, + select: { + versionIndex: true, + versionName: true, + platform: true, + setupCommand: true, + launchCommand: true, + delta: true, + } }); return versions; diff --git a/server/internal/downloads/README.md b/server/internal/P2P.md similarity index 54% rename from server/internal/downloads/README.md rename to server/internal/P2P.md index f1251aa..341e345 100644 --- a/server/internal/downloads/README.md +++ b/server/internal/P2P.md @@ -1,8 +1,16 @@ -# Drop Download System -Drop downloads come in two types: +# Drop P2P System + +Drop clients have a variety of P2P or P2P-like methods of data transfer available ## Public (not quite) HTTPS downloads endpoints + These use public HTTPS certificate, and while are authenticated, are 'public' in the sense that they aren't P2P; anyone can connect to them ## Private mTLS P2P endpoints -Drop clients use P2P mTLS aided by the P2P co-ordinator to transfer chunks between themselves. \ No newline at end of file + +Drop clients use P2P mTLS aided by the P2P co-ordinator to transfer chunks between themselves. This happens over HTTP. + + +## Private mTLS Wireguard tunnels + +Drop clients can establish P2P Wireguard \ No newline at end of file diff --git a/server/internal/downloads/manifest.ts b/server/internal/downloads/manifest.ts new file mode 100644 index 0000000..d646d58 --- /dev/null +++ b/server/internal/downloads/manifest.ts @@ -0,0 +1,52 @@ +export type DropChunk = { + permissions: number; + ids: string[]; + checksums: string[]; + lengths: string[]; +}; + +export type DropManifest = { + [key: string]: DropChunk; +}; + +export type DropManifestMetadata = { + manifest: DropManifest; + versionName: string; +}; + +export type DropGeneratedManifest = DropManifest & { + [key: string]: { versionName: string }; +}; + +class ManifestGenerator { + static generateManifest( + rootManifest: DropManifestMetadata, + ...overlays: DropManifestMetadata[] + ): DropGeneratedManifest { + if (overlays.length == 0) { + return Object.fromEntries( + Object.entries(rootManifest.manifest).map(([key, value]) => [ + key, + Object.assign({}, value, { versionName: rootManifest.versionName }), + ]) + ); + } + + // Recurse in verse order through versions, skipping files that already exist. + const versions = [...overlays.reverse(), rootManifest]; + const manifest: DropGeneratedManifest = {}; + for (const version of versions) { + for (const [filename, chunk] of Object.entries(version.manifest)) { + if (manifest[filename]) continue; + manifest[filename] = Object.assign({}, chunk, { + versionName: version.versionName, + }); + } + } + + return manifest; + } +} + +export const manifestGenerator = new ManifestGenerator(); +export default manifestGenerator; diff --git a/server/internal/library/index.ts b/server/internal/library/index.ts index 70eddbb..8f1878b 100644 --- a/server/internal/library/index.ts +++ b/server/internal/library/index.ts @@ -195,7 +195,8 @@ class LibraryManager { async importVersion( gameId: string, versionName: string, - metadata: { platform: string; setup: string; startup: string } + metadata: { platform: string; setup: string; startup: string }, + delta = false ) { const taskId = `import:${gameId}:${versionName}`; @@ -238,6 +239,10 @@ class LibraryManager { log("Created manifest successfully!"); + const currentIndex = await prisma.gameVersion.count({ + where: { gameId: gameId }, + }); + // Then, create the database object const version = await prisma.gameVersion.create({ data: { @@ -247,6 +252,8 @@ class LibraryManager { setupCommand: metadata.setup, launchCommand: metadata.startup, dropletManifest: manifest, + versionIndex: currentIndex, + delta: delta, }, }); diff --git a/yarn.lock b/yarn.lock index c3d4b0d..6bc5dd5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -296,23 +296,23 @@ dependencies: mime "^3.0.0" -"@drop/droplet-linux-x64-gnu@0.4.4", "@drop/droplet-linux-x64-gnu@^0.4.4": - version "0.4.4" - resolved "https://lab.deepcore.dev/api/v4/projects/57/packages/npm/@drop/droplet-linux-x64-gnu/-/@drop/droplet-linux-x64-gnu-0.4.4.tgz#6678a0923bb13d37e20cae467f45c72bc5d9fe6e" - integrity sha1-ZnigkjuxPTfiDK5Gf0XHK8XZ/m4= +"@drop/droplet-linux-x64-gnu@0.5.0", "@drop/droplet-linux-x64-gnu@^0.5.0": + version "0.5.0" + resolved "https://lab.deepcore.dev/api/v4/projects/57/packages/npm/@drop/droplet-linux-x64-gnu/-/@drop/droplet-linux-x64-gnu-0.5.0.tgz#06643f7bc79de4b35a395295ef1e29fad46f32b5" + integrity sha1-BmQ/e8ed5LNaOVKV7x4p+tRvMrU= -"@drop/droplet-win32-x64-msvc@0.4.4", "@drop/droplet-win32-x64-msvc@^0.4.4": - version "0.4.4" - resolved "https://lab.deepcore.dev/api/v4/projects/57/packages/npm/@drop/droplet-win32-x64-msvc/-/@drop/droplet-win32-x64-msvc-0.4.4.tgz#10802bb36c6ec7d69aa17ea22081e5d5f0dac3c3" - integrity sha1-EIArs2xux9aaoX6iIIHl1fDaw8M= +"@drop/droplet-win32-x64-msvc@0.5.0", "@drop/droplet-win32-x64-msvc@^0.5.0": + version "0.5.0" + resolved "https://lab.deepcore.dev/api/v4/projects/57/packages/npm/@drop/droplet-win32-x64-msvc/-/@drop/droplet-win32-x64-msvc-0.5.0.tgz#abc02af2102f0faaf4561473b7a18395a0ba2b10" + integrity sha1-q8Aq8hAvD6r0VhRzt6GDlaC6KxA= -"@drop/droplet@^0.4.4": - version "0.4.4" - resolved "https://lab.deepcore.dev/api/v4/projects/57/packages/npm/@drop/droplet/-/@drop/droplet-0.4.4.tgz#a9b6e3a341e85703b25c7fee597261e1b239a280" - integrity sha1-qbbjo0HoVwOyXH/uWXJh4bI5ooA= +"@drop/droplet@^0.5.0": + version "0.5.0" + resolved "https://lab.deepcore.dev/api/v4/projects/57/packages/npm/@drop/droplet/-/@drop/droplet-0.5.0.tgz#27da4f7292c9b860d38bb785c0fb2bb3b9cd50a3" + integrity sha1-J9pPcpLJuGDTi7eFwPsrs7nNUKM= optionalDependencies: - "@drop/droplet-linux-x64-gnu" "0.4.4" - "@drop/droplet-win32-x64-msvc" "0.4.4" + "@drop/droplet-linux-x64-gnu" "0.5.0" + "@drop/droplet-win32-x64-msvc" "0.5.0" "@esbuild/aix-ppc64@0.20.2": version "0.20.2" @@ -4887,6 +4887,11 @@ smob@^1.0.0: resolved "https://registry.yarnpkg.com/smob/-/smob-1.5.0.tgz#85d79a1403abf128d24d3ebc1cdc5e1a9548d3ab" integrity sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig== +sortablejs@1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.14.0.tgz#6d2e17ccbdb25f464734df621d4f35d4ab35b3d8" + integrity sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w== + "source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.2.0, source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" @@ -5656,6 +5661,13 @@ vue@^3.5.5, vue@latest: "@vue/server-renderer" "3.5.10" "@vue/shared" "3.5.10" +vuedraggable@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-4.1.0.tgz#edece68adb8a4d9e06accff9dfc9040e66852270" + integrity sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww== + dependencies: + sortablejs "1.14.0" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"