From b939b9ceda944ea5c9bb864e56aae7b0d2ebc85e Mon Sep 17 00:00:00 2001 From: Ada Date: Tue, 12 May 2026 20:08:17 -0400 Subject: [PATCH] Add Modpack feature: bundle installed mods into a player-downloadable zip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New end-to-end feature: admin picks installed mods → names + versions a "pack" → builds a zip → publishes it. Once published, an anonymous download CTA appears top-right on the login page so players can grab matching client-side mods without needing a panel account. Spec (confirmed with Ada): 1. Single-server scope = exactly one modpack at a time 2. Latest version only — re-publishing overwrites the previous zip 3. Just the Mods/ folder contents — no config files, no other layers 4. Top-right CTA on the login page (not a full panel) 5. Three-state workflow: draft → published → archived Backend surface: Config/Migrations/010_modpack.sql Singleton 'modpack' table. status, filename, size, mod_count, mod_list (JSON array of folder names), description, download_count. Data/Entities/Modpack.cs Plain entity mirroring the table. Data/Repositories/ModpackRepository.cs GetCurrent / GetPublished / Upsert / IncrementDownloadCount / Delete. Repo enforces singleton semantics (the table can structurally hold many rows; this layer keeps it to one). Services/ModpackService.cs SaveDraft (validates mods exist on disk), BuildZip (atomic temp-file pattern so partial writes can't leak to public downloaders), Publish / Archive / Unarchive / Delete, OpenPublishedForDownload (FileStream for the controller to pipe through StreamContent). Zips land in /KitsuneModpacks/ — same convention as KitsuneBackups. Web/Controllers/ModpackController.cs [Authorize] for the admin endpoints, [AllowAnonymous] for the two public-facing ones (GET /api/modpack/public for metadata, GET /api/modpack/public/download for the zip stream). Core/ServiceRegistry.cs Registers ModpackService alongside BackupService. Frontend surface: api/modpack.ts REST client for the 7 admin endpoints + 1 public metadata fetch. Public download URL exported as a constant so anchor tags can hit it directly without going through axios. types/index.ts Modpack, ModpackState, PublicModpack, ModpackStatus, etc. views/ModsView.vue Third tab on the existing Mods page ("Modpack"). Form fields for name + version + mod picker (MultiSelect from installedMods) + description. Status badge, action buttons gated by current state (Save Draft / Build Zip / Publish / Archive / Unarchive / Delete). Reveals the public download URL when published so admin can share or smoke-test it. views/LoginView.vue Top-right CTA pill that renders only when getPublishedModpack() resolves to a record. Shows name + version, opens the public download endpoint in a new tab. Hidden cleanly when no pack is published (silent failure on the anonymous metadata call). i18n: full English in en.ts, native translations in de/fr/es, English placeholders in ja/ko/zh-CN/zh-TW (consistent with prior locale rollout pattern — translations TBD by community native speakers). Tested locally: full build clean, vue-tsc structural check across all 8 locales passes, 0 C# errors, all expected files in dist/. Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/api/modpack.ts | 60 ++++ frontend/src/i18n/locales/de.ts | 47 +++ frontend/src/i18n/locales/en.ts | 47 +++ frontend/src/i18n/locales/es.ts | 47 +++ frontend/src/i18n/locales/fr.ts | 47 +++ frontend/src/i18n/locales/ja.ts | 39 +++ frontend/src/i18n/locales/ko.ts | 39 +++ frontend/src/i18n/locales/zh-CN.ts | 39 +++ frontend/src/i18n/locales/zh-TW.ts | 39 +++ frontend/src/types/index.ts | 41 +++ frontend/src/views/LoginView.vue | 88 ++++- frontend/src/views/ModsView.vue | 323 +++++++++++++++++- .../Config/Migrations/010_modpack.sql | 29 ++ src/KitsuneCommand/Core/ServiceRegistry.cs | 1 + src/KitsuneCommand/Data/Entities/Modpack.cs | 30 ++ .../Data/Repositories/ModpackRepository.cs | 98 ++++++ src/KitsuneCommand/Services/ModpackService.cs | 257 ++++++++++++++ .../Web/Controllers/ModpackController.cs | 228 +++++++++++++ 18 files changed, 1497 insertions(+), 2 deletions(-) create mode 100644 frontend/src/api/modpack.ts create mode 100644 src/KitsuneCommand/Config/Migrations/010_modpack.sql create mode 100644 src/KitsuneCommand/Data/Entities/Modpack.cs create mode 100644 src/KitsuneCommand/Data/Repositories/ModpackRepository.cs create mode 100644 src/KitsuneCommand/Services/ModpackService.cs create mode 100644 src/KitsuneCommand/Web/Controllers/ModpackController.cs diff --git a/frontend/src/api/modpack.ts b/frontend/src/api/modpack.ts new file mode 100644 index 0000000..214b753 --- /dev/null +++ b/frontend/src/api/modpack.ts @@ -0,0 +1,60 @@ +import apiClient from './client' +import type { Modpack, ModpackState, PublicModpack } from '@/types' + +export async function getModpackState(): Promise { + const response = await apiClient.get('/api/modpack') + return response.data.data +} + +export async function saveModpackDraft(payload: { + name: string + version: string + modList: string[] + description: string | null +}): Promise { + const response = await apiClient.put('/api/modpack', payload) + return response.data.data +} + +export async function buildModpack(): Promise { + const response = await apiClient.post('/api/modpack/build') + return response.data.data +} + +export async function publishModpack(): Promise { + const response = await apiClient.post('/api/modpack/publish') + return response.data.data +} + +export async function archiveModpack(): Promise { + const response = await apiClient.post('/api/modpack/archive') + return response.data.data +} + +export async function unarchiveModpack(): Promise { + const response = await apiClient.post('/api/modpack/unarchive') + return response.data.data +} + +export async function deleteModpack(): Promise { + await apiClient.delete('/api/modpack') +} + +/** + * Public metadata for the currently-published modpack. Returns null if none + * is published. Doesn't require auth — used by the login page to decide + * whether to show the download CTA. + */ +export async function getPublishedModpack(): Promise { + try { + const response = await apiClient.get('/api/modpack/public') + const env = response.data + if (env?.code !== 200) return null + return env.data as PublicModpack + } catch { + return null + } +} + +/** Public download URL — anchor href, not fetched programmatically. */ +export const publicModpackDownloadUrl = '/api/modpack/public/download' diff --git a/frontend/src/i18n/locales/de.ts b/frontend/src/i18n/locales/de.ts index 043b583..7bf9e43 100644 --- a/frontend/src/i18n/locales/de.ts +++ b/frontend/src/i18n/locales/de.ts @@ -1037,6 +1037,53 @@ const de = { modsFound: 'Mods gefunden', }, + modpack: { + tabLabel: 'Modpack', + intro: 'Bündeln Sie installierte Mods in eine einzelne ZIP-Datei, die Spieler von der Anmeldeseite herunterladen können. Nur das veröffentlichte Paket ist öffentlich verfügbar — Entwürfe und archivierte Pakete bleiben privat.', + status: { + draft: 'Entwurf', + published: 'Veröffentlicht', + archived: 'Archiviert', + }, + modCount: '{n} Mod(s)', + downloadsCount: '{n} Download(s)', + publicDownloadLink: 'Öffentlichen Download-Link öffnen', + fields: { + name: 'Paketname', + version: 'Version', + modList: 'Enthaltene Mods', + description: 'Beschreibung', + }, + placeholders: { + name: 'z. B. KitsuneDen Client Pack', + modList: 'Mods zum Einschließen auswählen...', + description: 'Optional — was im Paket ist, für wen, Versions-Notizen.', + }, + modListHint: 'Nur Mods, die derzeit auf dem Server installiert sind, erscheinen hier. Fügen Sie zuerst ein Mod über die Registerkarte „Installiert“ hinzu, dann kehren Sie zurück.', + actions: { + saveDraft: 'Entwurf speichern', + build: 'ZIP erstellen', + publish: 'Veröffentlichen', + archive: 'Archivieren', + unarchive: 'Aus Archiv wiederherstellen', + delete: 'Paket löschen', + }, + confirmDelete: 'Modpack-Datensatz und ZIP-Datei löschen? Spieler verlieren den Download-Link, bis Sie ein neues veröffentlichen.', + ctaTooltip: '{size} · {n} Mod(s)', + saved: 'Modpack als Entwurf gespeichert', + built: 'Modpack-ZIP erstellt', + published: 'Modpack veröffentlicht — Download ist jetzt auf der Anmeldeseite verfügbar', + archived: 'Modpack archiviert', + unarchived: 'Modpack als Entwurf wiederhergestellt', + deleted: 'Modpack gelöscht', + failedToLoad: 'Modpack konnte nicht geladen werden', + failedToSave: 'Modpack konnte nicht gespeichert werden', + failedToBuild: 'Modpack-ZIP konnte nicht erstellt werden', + failedToPublish: 'Modpack konnte nicht veröffentlicht werden', + failedToArchive: 'Modpack konnte nicht archiviert werden', + failedToDelete: 'Modpack konnte nicht gelöscht werden', + }, + backups: { title: 'Sicherungen', createNow: 'Sicherung erstellen', diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 1fc4df3..a16434c 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -1034,6 +1034,53 @@ const en = { modsFound: 'mods found', }, + modpack: { + tabLabel: 'Modpack', + intro: 'Bundle installed mods into a single zip players can download from the login page. Only the published pack is publicly downloadable — drafts and archived packs stay private.', + status: { + draft: 'Draft', + published: 'Published', + archived: 'Archived', + }, + modCount: '{n} mod(s)', + downloadsCount: '{n} download(s)', + publicDownloadLink: 'Open public download link', + fields: { + name: 'Pack name', + version: 'Version', + modList: 'Included mods', + description: 'Description', + }, + placeholders: { + name: 'e.g. KitsuneDen Client Pack', + modList: 'Pick mods to include...', + description: 'Optional — what\'s in this pack, who it\'s for, version notes.', + }, + modListHint: 'Only mods currently installed on the server appear here. Add a mod via the Installed tab first, then come back to include it.', + actions: { + saveDraft: 'Save Draft', + build: 'Build Zip', + publish: 'Publish', + archive: 'Archive', + unarchive: 'Restore from Archive', + delete: 'Delete Pack', + }, + confirmDelete: 'Delete the modpack record and its zip file? Players will lose the download link until you publish a new one.', + ctaTooltip: '{size} · {n} mod(s)', + saved: 'Modpack saved as draft', + built: 'Modpack zip built', + published: 'Modpack published — download is now live on the login page', + archived: 'Modpack archived', + unarchived: 'Modpack restored to draft', + deleted: 'Modpack deleted', + failedToLoad: 'Failed to load modpack', + failedToSave: 'Failed to save modpack', + failedToBuild: 'Failed to build modpack zip', + failedToPublish: 'Failed to publish modpack', + failedToArchive: 'Failed to archive modpack', + failedToDelete: 'Failed to delete modpack', + }, + backups: { title: 'Backups', createNow: 'Create Backup', diff --git a/frontend/src/i18n/locales/es.ts b/frontend/src/i18n/locales/es.ts index 6f90f63..0a7c67d 100644 --- a/frontend/src/i18n/locales/es.ts +++ b/frontend/src/i18n/locales/es.ts @@ -1037,6 +1037,53 @@ const es = { modsFound: 'mods encontrados', }, + modpack: { + tabLabel: 'Modpack', + intro: 'Agrupe los mods instalados en un único archivo ZIP que los jugadores pueden descargar desde la página de inicio de sesión. Solo el pack publicado se puede descargar públicamente — los borradores y los packs archivados permanecen privados.', + status: { + draft: 'Borrador', + published: 'Publicado', + archived: 'Archivado', + }, + modCount: '{n} mod(s)', + downloadsCount: '{n} descarga(s)', + publicDownloadLink: 'Abrir enlace de descarga pública', + fields: { + name: 'Nombre del pack', + version: 'Versión', + modList: 'Mods incluidos', + description: 'Descripción', + }, + placeholders: { + name: 'p. ej. KitsuneDen Client Pack', + modList: 'Elija mods para incluir...', + description: 'Opcional — qué contiene este pack, a quién va dirigido, notas de versión.', + }, + modListHint: 'Solo aparecen aquí los mods actualmente instalados en el servidor. Añada primero un mod desde la pestaña Instalados, y luego regrese para incluirlo.', + actions: { + saveDraft: 'Guardar borrador', + build: 'Construir ZIP', + publish: 'Publicar', + archive: 'Archivar', + unarchive: 'Restaurar desde archivo', + delete: 'Eliminar pack', + }, + confirmDelete: '¿Eliminar el registro del modpack y su archivo ZIP? Los jugadores perderán el enlace de descarga hasta que publique uno nuevo.', + ctaTooltip: '{size} · {n} mod(s)', + saved: 'Modpack guardado como borrador', + built: 'ZIP del modpack construido', + published: 'Modpack publicado — la descarga está activa en la página de inicio de sesión', + archived: 'Modpack archivado', + unarchived: 'Modpack restaurado a borrador', + deleted: 'Modpack eliminado', + failedToLoad: 'No se pudo cargar el modpack', + failedToSave: 'No se pudo guardar el modpack', + failedToBuild: 'No se pudo construir el ZIP del modpack', + failedToPublish: 'No se pudo publicar el modpack', + failedToArchive: 'No se pudo archivar el modpack', + failedToDelete: 'No se pudo eliminar el modpack', + }, + backups: { title: 'Copias de seguridad', createNow: 'Crear copia de seguridad', diff --git a/frontend/src/i18n/locales/fr.ts b/frontend/src/i18n/locales/fr.ts index 4794ed2..321db8b 100644 --- a/frontend/src/i18n/locales/fr.ts +++ b/frontend/src/i18n/locales/fr.ts @@ -1037,6 +1037,53 @@ const fr = { modsFound: 'mods trouvés', }, + modpack: { + tabLabel: 'Modpack', + intro: 'Regroupez les mods installés dans une seule archive ZIP que les joueurs peuvent télécharger depuis la page de connexion. Seul le pack publié est téléchargeable publiquement — les brouillons et les packs archivés restent privés.', + status: { + draft: 'Brouillon', + published: 'Publié', + archived: 'Archivé', + }, + modCount: '{n} mod(s)', + downloadsCount: '{n} téléchargement(s)', + publicDownloadLink: 'Ouvrir le lien de téléchargement public', + fields: { + name: 'Nom du pack', + version: 'Version', + modList: 'Mods inclus', + description: 'Description', + }, + placeholders: { + name: 'p. ex. KitsuneDen Client Pack', + modList: 'Choisissez les mods à inclure...', + description: 'Optionnel — ce qui est dans ce pack, à qui il s\'adresse, notes de version.', + }, + modListHint: 'Seuls les mods actuellement installés sur le serveur apparaissent ici. Ajoutez d\'abord un mod via l\'onglet Installés, puis revenez pour l\'inclure.', + actions: { + saveDraft: 'Enregistrer le brouillon', + build: 'Construire le ZIP', + publish: 'Publier', + archive: 'Archiver', + unarchive: 'Restaurer depuis l\'archive', + delete: 'Supprimer le pack', + }, + confirmDelete: 'Supprimer l\'enregistrement du modpack et son fichier ZIP ? Les joueurs perdront le lien de téléchargement jusqu\'à ce que vous publiiez un nouveau pack.', + ctaTooltip: '{size} · {n} mod(s)', + saved: 'Modpack enregistré comme brouillon', + built: 'ZIP du modpack construit', + published: 'Modpack publié — le téléchargement est désormais actif sur la page de connexion', + archived: 'Modpack archivé', + unarchived: 'Modpack restauré en brouillon', + deleted: 'Modpack supprimé', + failedToLoad: 'Impossible de charger le modpack', + failedToSave: 'Impossible d\'enregistrer le modpack', + failedToBuild: 'Impossible de construire le ZIP du modpack', + failedToPublish: 'Impossible de publier le modpack', + failedToArchive: 'Impossible d\'archiver le modpack', + failedToDelete: 'Impossible de supprimer le modpack', + }, + backups: { title: 'Sauvegardes', createNow: 'Créer une sauvegarde', diff --git a/frontend/src/i18n/locales/ja.ts b/frontend/src/i18n/locales/ja.ts index e5ba366..05647a8 100644 --- a/frontend/src/i18n/locales/ja.ts +++ b/frontend/src/i18n/locales/ja.ts @@ -1022,6 +1022,45 @@ const ja: Messages = { modsFound: '件のMODが見つかりました', }, + modpack: { + // English placeholders — translations TBD + tabLabel: 'Modpack', + intro: 'Bundle installed mods into a single zip players can download from the login page. Only the published pack is publicly downloadable — drafts and archived packs stay private.', + status: { draft: 'Draft', published: 'Published', archived: 'Archived' }, + modCount: '{n} mod(s)', + downloadsCount: '{n} download(s)', + publicDownloadLink: 'Open public download link', + fields: { name: 'Pack name', version: 'Version', modList: 'Included mods', description: 'Description' }, + placeholders: { + name: 'e.g. KitsuneDen Client Pack', + modList: 'Pick mods to include...', + description: 'Optional — what\'s in this pack, who it\'s for, version notes.', + }, + modListHint: 'Only mods currently installed on the server appear here. Add a mod via the Installed tab first, then come back to include it.', + actions: { + saveDraft: 'Save Draft', + build: 'Build Zip', + publish: 'Publish', + archive: 'Archive', + unarchive: 'Restore from Archive', + delete: 'Delete Pack', + }, + confirmDelete: 'Delete the modpack record and its zip file? Players will lose the download link until you publish a new one.', + ctaTooltip: '{size} · {n} mod(s)', + saved: 'Modpack saved as draft', + built: 'Modpack zip built', + published: 'Modpack published — download is now live on the login page', + archived: 'Modpack archived', + unarchived: 'Modpack restored to draft', + deleted: 'Modpack deleted', + failedToLoad: 'Failed to load modpack', + failedToSave: 'Failed to save modpack', + failedToBuild: 'Failed to build modpack zip', + failedToPublish: 'Failed to publish modpack', + failedToArchive: 'Failed to archive modpack', + failedToDelete: 'Failed to delete modpack', + }, + backups: { title: 'バックアップ', createNow: 'バックアップを作成', diff --git a/frontend/src/i18n/locales/ko.ts b/frontend/src/i18n/locales/ko.ts index 19c7893..e5c668e 100644 --- a/frontend/src/i18n/locales/ko.ts +++ b/frontend/src/i18n/locales/ko.ts @@ -1038,6 +1038,45 @@ const ko: Messages = { modsFound: '개의 모드를 찾았습니다', }, + modpack: { + // English placeholders — translations TBD + tabLabel: 'Modpack', + intro: 'Bundle installed mods into a single zip players can download from the login page. Only the published pack is publicly downloadable — drafts and archived packs stay private.', + status: { draft: 'Draft', published: 'Published', archived: 'Archived' }, + modCount: '{n} mod(s)', + downloadsCount: '{n} download(s)', + publicDownloadLink: 'Open public download link', + fields: { name: 'Pack name', version: 'Version', modList: 'Included mods', description: 'Description' }, + placeholders: { + name: 'e.g. KitsuneDen Client Pack', + modList: 'Pick mods to include...', + description: 'Optional — what\'s in this pack, who it\'s for, version notes.', + }, + modListHint: 'Only mods currently installed on the server appear here. Add a mod via the Installed tab first, then come back to include it.', + actions: { + saveDraft: 'Save Draft', + build: 'Build Zip', + publish: 'Publish', + archive: 'Archive', + unarchive: 'Restore from Archive', + delete: 'Delete Pack', + }, + confirmDelete: 'Delete the modpack record and its zip file? Players will lose the download link until you publish a new one.', + ctaTooltip: '{size} · {n} mod(s)', + saved: 'Modpack saved as draft', + built: 'Modpack zip built', + published: 'Modpack published — download is now live on the login page', + archived: 'Modpack archived', + unarchived: 'Modpack restored to draft', + deleted: 'Modpack deleted', + failedToLoad: 'Failed to load modpack', + failedToSave: 'Failed to save modpack', + failedToBuild: 'Failed to build modpack zip', + failedToPublish: 'Failed to publish modpack', + failedToArchive: 'Failed to archive modpack', + failedToDelete: 'Failed to delete modpack', + }, + backups: { title: '백업', createNow: '백업 생성', diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index 5dbc766..175d091 100644 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -1038,6 +1038,45 @@ const zhCN: Messages = { modsFound: '个模组', }, + modpack: { + // English placeholders — translations TBD + tabLabel: 'Modpack', + intro: 'Bundle installed mods into a single zip players can download from the login page. Only the published pack is publicly downloadable — drafts and archived packs stay private.', + status: { draft: 'Draft', published: 'Published', archived: 'Archived' }, + modCount: '{n} mod(s)', + downloadsCount: '{n} download(s)', + publicDownloadLink: 'Open public download link', + fields: { name: 'Pack name', version: 'Version', modList: 'Included mods', description: 'Description' }, + placeholders: { + name: 'e.g. KitsuneDen Client Pack', + modList: 'Pick mods to include...', + description: 'Optional — what\'s in this pack, who it\'s for, version notes.', + }, + modListHint: 'Only mods currently installed on the server appear here. Add a mod via the Installed tab first, then come back to include it.', + actions: { + saveDraft: 'Save Draft', + build: 'Build Zip', + publish: 'Publish', + archive: 'Archive', + unarchive: 'Restore from Archive', + delete: 'Delete Pack', + }, + confirmDelete: 'Delete the modpack record and its zip file? Players will lose the download link until you publish a new one.', + ctaTooltip: '{size} · {n} mod(s)', + saved: 'Modpack saved as draft', + built: 'Modpack zip built', + published: 'Modpack published — download is now live on the login page', + archived: 'Modpack archived', + unarchived: 'Modpack restored to draft', + deleted: 'Modpack deleted', + failedToLoad: 'Failed to load modpack', + failedToSave: 'Failed to save modpack', + failedToBuild: 'Failed to build modpack zip', + failedToPublish: 'Failed to publish modpack', + failedToArchive: 'Failed to archive modpack', + failedToDelete: 'Failed to delete modpack', + }, + backups: { title: '备份', createNow: '创建备份', diff --git a/frontend/src/i18n/locales/zh-TW.ts b/frontend/src/i18n/locales/zh-TW.ts index 67bd012..0d32452 100644 --- a/frontend/src/i18n/locales/zh-TW.ts +++ b/frontend/src/i18n/locales/zh-TW.ts @@ -1038,6 +1038,45 @@ const zhTW: Messages = { modsFound: '個模組', }, + modpack: { + // English placeholders — translations TBD + tabLabel: 'Modpack', + intro: 'Bundle installed mods into a single zip players can download from the login page. Only the published pack is publicly downloadable — drafts and archived packs stay private.', + status: { draft: 'Draft', published: 'Published', archived: 'Archived' }, + modCount: '{n} mod(s)', + downloadsCount: '{n} download(s)', + publicDownloadLink: 'Open public download link', + fields: { name: 'Pack name', version: 'Version', modList: 'Included mods', description: 'Description' }, + placeholders: { + name: 'e.g. KitsuneDen Client Pack', + modList: 'Pick mods to include...', + description: 'Optional — what\'s in this pack, who it\'s for, version notes.', + }, + modListHint: 'Only mods currently installed on the server appear here. Add a mod via the Installed tab first, then come back to include it.', + actions: { + saveDraft: 'Save Draft', + build: 'Build Zip', + publish: 'Publish', + archive: 'Archive', + unarchive: 'Restore from Archive', + delete: 'Delete Pack', + }, + confirmDelete: 'Delete the modpack record and its zip file? Players will lose the download link until you publish a new one.', + ctaTooltip: '{size} · {n} mod(s)', + saved: 'Modpack saved as draft', + built: 'Modpack zip built', + published: 'Modpack published — download is now live on the login page', + archived: 'Modpack archived', + unarchived: 'Modpack restored to draft', + deleted: 'Modpack deleted', + failedToLoad: 'Failed to load modpack', + failedToSave: 'Failed to save modpack', + failedToBuild: 'Failed to build modpack zip', + failedToPublish: 'Failed to publish modpack', + failedToArchive: 'Failed to archive modpack', + failedToDelete: 'Failed to delete modpack', + }, + backups: { title: '備份', createNow: '建立備份', diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 76c3876..acce5e7 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -265,6 +265,47 @@ export interface ChatCommandSettings { voteCooldownSeconds: number } +// ─── Modpack Types ─────────────────────────────────── + +export type ModpackStatus = 'draft' | 'published' | 'archived' + +export interface Modpack { + id: number + name: string + version: string + status: ModpackStatus + filename: string | null + sizeBytes: number + modCount: number + modList: string // JSON string of mod folder names + description: string | null + createdAt: string + updatedAt: string + downloadCount: number +} + +export interface ModpackInstalledMod { + name: string // folder name + displayName: string + version: string +} + +/** Admin-facing state envelope returned by GET /api/modpack. */ +export interface ModpackState { + modpack: Modpack | null + modList: string[] // parsed-out folder names included in the pack + installedMods: ModpackInstalledMod[] +} + +/** Public-facing trimmed envelope returned by GET /api/modpack/public. */ +export interface PublicModpack { + name: string + version: string + sizeBytes: number + modCount: number + description: string | null +} + // ─── Vote Rewards Types ────────────────────────────── /** Reward delivery shape — mirrors the C# VoteRewardType static class. */ diff --git a/frontend/src/views/LoginView.vue b/frontend/src/views/LoginView.vue index 18fcd26..109eb0c 100644 --- a/frontend/src/views/LoginView.vue +++ b/frontend/src/views/LoginView.vue @@ -1,9 +1,11 @@