diff --git a/server/.gitignore b/server/.gitignore
index 7188be16..9cbcd222 100644
--- a/server/.gitignore
+++ b/server/.gitignore
@@ -34,4 +34,6 @@ deploy-template/*
# generated prisma client
/prisma/client
-/prisma/validate
\ No newline at end of file
+/prisma/validate
+
+/server/internal/proto
diff --git a/server/.prettierignore b/server/.prettierignore
index 51b5f44b..ed27b4da 100644
--- a/server/.prettierignore
+++ b/server/.prettierignore
@@ -2,6 +2,6 @@ drop-base/
# file is fully managed by pnpm, no reason to break it
pnpm-lock.yaml
-torrential/
+/torrential/
.data/**
**/.data/**
diff --git a/server/.vscode/settings.json b/server/.vscode/settings.json
index 0d0624ee..0570fe39 100644
--- a/server/.vscode/settings.json
+++ b/server/.vscode/settings.json
@@ -33,7 +33,7 @@
"username": "drop"
}
],
- "typescript.experimental.useTsgo": true,
+ "typescript.experimental.useTsgo": false,
// prioritize ArkType's "type" for autoimports
"typescript.preferences.autoImportSpecifierExcludeRegexes": ["^(node:)?os$"]
}
diff --git a/server/Dockerfile b/server/Dockerfile
index 92506f15..7b84a110 100644
--- a/server/Dockerfile
+++ b/server/Dockerfile
@@ -68,6 +68,7 @@ COPY --from=torrential-build /build/target/release/torrential /usr/bin/
ENV LIBRARY="/library"
ENV DATA="/data"
ENV NGINX_CONFIG="/nginx.conf"
-ENV NUXT_PORT=4000
+# NGINX's port
+ENV PORT=4000
CMD ["sh", "/app/startup/launch.sh"]
diff --git a/server/buf.gen.yaml b/server/buf.gen.yaml
new file mode 100644
index 00000000..fb9d2f35
--- /dev/null
+++ b/server/buf.gen.yaml
@@ -0,0 +1,5 @@
+version: v1
+plugins:
+ - plugin: es
+ out: server/internal/proto
+ opt: target=ts
diff --git a/server/components/Auth/Simple.vue b/server/components/Auth/Simple.vue
index 565aae88..5454e88d 100644
--- a/server/components/Auth/Simple.vue
+++ b/server/components/Auth/Simple.vue
@@ -90,7 +90,7 @@ import {
startAuthentication,
browserSupportsWebAuthn,
} from "@simplewebauthn/browser";
-import type { FetchError } from "ofetch";
+import { FetchError } from "ofetch";
const username = ref("");
const password = ref("");
@@ -141,16 +141,19 @@ const router = useRouter();
const route = useRoute();
const { t } = useI18n();
-function signin_wrapper() {
+async function signin_wrapper() {
loading.value = true;
- signin()
- .catch((response) => {
- const message = response.message || t("errors.unknown");
- error.value = message;
- })
- .finally(() => {
- loading.value = false;
- });
+ try {
+ await signin();
+ } catch (e) {
+ if (e instanceof FetchError) {
+ error.value = e.data.message || t("errors.unknown");
+ } else {
+ error.value = e as string;
+ }
+ } finally {
+ loading.value = false;
+ }
}
async function signin() {
diff --git a/server/components/GameEditor/Version.vue b/server/components/GameEditor/Version.vue
index cf8f36a8..28165fad 100644
--- a/server/components/GameEditor/Version.vue
+++ b/server/components/GameEditor/Version.vue
@@ -3,9 +3,11 @@
-
Versions
+
+ {{ $t("library.admin.version.title") }}
+
- Versions versions version, versions versions. Versions.
+ {{ $t("library.admin.version.description") }}
@@ -36,28 +38,28 @@
scope="col"
class="py-3 pr-3 pl-4 text-left text-xs font-medium tracking-wide text-gray-400 uppercase sm:pl-0"
>
- Name (ID)
+ {{ $t("library.admin.version.table.name") }}
- Path
+ {{ $t("library.admin.version.table.path") }}
- Setup Configurations
+ {{ $t("library.admin.version.table.setup") }}
- Launch Configurations
+ {{ $t("library.admin.version.table.launch") }}
- Edit
+ {{ $t("common.edit") }}
@@ -100,13 +102,13 @@
v-if="version.setups.length == 0"
class="text-xs uppercase font-display text-zinc-700 font-semibold"
>
- No setups configured.
+ {{ $t("library.admin.version.noSetups") }}
- Version configured as in setup-only mode.
+ {{ $t("library.admin.version.setupOnly") }}
deleteVersion(version.versionId)"
>
- Delete,
- {{ version.displayName ?? version.versionPath }}
+ {{ $t("common.delete") }}
- The installation directory is set as the current directory when
- launching. It is not prepended to your command.
+ {{ $t("library.admin.launchRow.currentDirHint") }}
{{ $t("library.admin.import.version.installDir") }}
@@ -124,7 +123,7 @@
- '{{ launchProcessQuery }}'
+ {{ launchProcessQuery }}
- {executor}
- is replaced with the game's launch command for executors.
+
+ {{
+ // eslint-disable-next-line @intlify/vue-i18n/no-raw-text
+ "{executor}"
+ }}
+
+
@@ -174,7 +183,7 @@
- Executor
+ {{ $t("library.admin.launchRow.executorTitle") }}
@@ -182,12 +191,12 @@
v-else
class="font-bold uppercase font-display text-zinc-500 text-sm"
>
- No executor selected
+ {{ $t("library.admin.launchRow.noExecutorSelected") }}
-
Select new executor
+
{{
+ $t("library.admin.launchRow.executorSelect")
+ }}
- Auto-suggest extensions
+ {{ $t("library.admin.launchRow.autosuggestHint") }}
({ required: true });
-const { t } = useI18n();
const collectionName = ref("");
const createCollectionLoading = ref(false);
const collections = await useCollections();
@@ -74,6 +73,7 @@ async function createCollection() {
const response = await $dropFetch("/api/v1/collection", {
method: "POST",
body: { name: collectionName.value },
+ failTitle: "Failed to create collection",
});
// Add the game if provided
@@ -83,6 +83,7 @@ async function createCollection() {
>(`/api/v1/collection/${response.id}/entry`, {
method: "POST",
body: { id: props.gameId },
+ failTitle: "Failed to add game to collection",
});
response.entries.push(entry);
}
@@ -94,20 +95,6 @@ async function createCollection() {
open.value = false;
emit("created", response.id);
- } catch (error) {
- console.error("Failed to create collection:", error);
-
- const err = error as { statusMessage?: string };
- createModal(
- ModalType.Notification,
- {
- title: t("errors.library.collection.create.title"),
- description: t("errors.library.collection.create.desc", [
- err?.statusMessage ?? t("errors.unknown"),
- ]),
- },
- (_, c) => c(),
- );
} finally {
createCollectionLoading.value = false;
}
diff --git a/server/components/Modal/DeleteCollection.vue b/server/components/Modal/DeleteCollection.vue
index b96e4817..f69d92a2 100644
--- a/server/components/Modal/DeleteCollection.vue
+++ b/server/components/Modal/DeleteCollection.vue
@@ -22,7 +22,7 @@
class="bg-red-600 text-white hover:bg-red-500"
@click="() => deleteCollection()"
>
- {{ $t("delete") }}
+ {{ $t("common.delete") }}
deleteArticle()"
>
- {{ $t("delete") }}
+ {{ $t("common.delete") }}
deleteUser()"
>
- {{ $t("delete") }}
+ {{ $t("common.delete") }}
- Select a launch option
+ {{ $t("library.admin.launchSelector.title") }}
- Select a launch option as an executor for your new launch option.
+ {{ $t("library.admin.launchSelector.description") }}
- Only showing launches for:
+ {{ $t("library.admin.launchSelector.platformFilterHint") }}
- Search for an executor
+ {{ $t("library.admin.launchSelector.search") }}
- No versions imported.
+ {{ $t("library.admin.launchSelector.noVersions") }}
- Select a version
+ {{ $t("library.admin.launchSelector.selectVersions") }}
- Select a launch command
+ {{ $t("library.admin.launchSelector.selectCommand") }}
- Select
+ {{ $t("common.select") }}
- No results.
+ {{ $t("common.noResults") }}
removeFileExtension(extension)"
>
- Remove
+ {{ $t("common.remove") }}
-
No extensions selected.
+
{{
+ $t("library.admin.fileExtSelector.noSelected")
+ }}
- Add "{{ normalize(query) }}"
+
+ {{
+ $t("library.admin.fileExtSelector.add", [normalize(query)])
+ }}
- Type at least 4 characters to get results
+ {{ $t("library.admin.gameSelector.hint") }}
- No results
+ {{ $t("common.noResults") }}
deleteSource(sourceIdx)"
>
- {{ $t("delete") }}
+ {{ $t("common.delete") }}
{{ $t("chars.srComma", [source.name]) }}
diff --git a/server/components/UserHeader/StoreNav.vue b/server/components/UserHeader/StoreNav.vue
new file mode 100644
index 00000000..e93e3b82
--- /dev/null
+++ b/server/components/UserHeader/StoreNav.vue
@@ -0,0 +1,39 @@
+
+
+ router.back()"
+ >
+
+
+ router.forward()"
+ >
+
+
+
+ {{ title }}
+
+
+
+
diff --git a/server/dev-tools/compose.yml b/server/dev-tools/compose.yml
index c2d81883..0dbe5ea4 100644
--- a/server/dev-tools/compose.yml
+++ b/server/dev-tools/compose.yml
@@ -1,12 +1,14 @@
services:
postgres:
image: postgres:14-alpine
- user: "1000:1000"
ports:
- 5432:5432
volumes:
- - ../.data/db:/var/lib/postgresql/data
+ - postgres-data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=drop
- POSTGRES_USER=drop
- POSTGRES_DB=drop
+
+volumes:
+ postgres-data:
diff --git a/server/drop.session.sql b/server/drop.session.sql
deleted file mode 100644
index b12df243..00000000
--- a/server/drop.session.sql
+++ /dev/null
@@ -1 +0,0 @@
-DELETE FROM "Session" WHERE 1=1;
diff --git a/server/i18n/locales/de.json b/server/i18n/locales/de.json
index fc42fe9d..19c867af 100644
--- a/server/i18n/locales/de.json
+++ b/server/i18n/locales/de.json
@@ -70,7 +70,7 @@
"register": {
"confirmPasswordFormat": "Muss mit oben genanntem übereinstimmen",
"emailFormat": "Muss im Format nutzer{'@'}beispiel.de sein",
- "passwordFormat": "Muss mindestens 14 Zeichen enthalten",
+ "passwordFormat": "Muss mindestens 8 Zeichen enthalten",
"subheader": "Gebe unten deine Daten ein, um dein Konto zu erstellen.",
"title": "Erstelle dein Drop Konto",
"usernameFormat": "Muss mindestens 5 Zeichen enthalten und aus Kleinbuchstaben bestehen"
@@ -101,6 +101,7 @@
"close": "Schließen",
"create": "Erstellen",
"date": "Datum",
+ "delete": "Löschen",
"deleteConfirm": "Möchtest du \"{0}\" wirklich löschen?",
"divider": "{'|'}",
"edit": "Bearbeiten",
@@ -120,7 +121,6 @@
"tags": "Tags",
"today": "Heute"
},
- "delete": "Löschen",
"drop": {
"desc": "Eine Open-Source-Plattform für die Verteilung von Spielen, die auf Geschwindigkeit, Flexibilität und Ästhetik ausgelegt ist.",
"drop": "Drop"
diff --git a/server/i18n/locales/en_pirate.json b/server/i18n/locales/en_pirate.json
index 01f5c3e3..6b1554bd 100644
--- a/server/i18n/locales/en_pirate.json
+++ b/server/i18n/locales/en_pirate.json
@@ -56,7 +56,7 @@
"register": {
"confirmPasswordFormat": "Must be the same as above, savvy?",
"emailFormat": "Must be in the fashion of a true scallywag {'@'} example.com",
- "passwordFormat": "Must be 14 or more marks, ye landlubber!",
+ "passwordFormat": "Must be 8 or more marks, ye landlubber!",
"subheader": "Fill in yer details below to make yer mark.",
"title": "Forge yer Drop Mark",
"usernameFormat": "Must be 5 or more marks, and all lowercase, argh!"
@@ -87,6 +87,7 @@
"close": "Shut yer trap!",
"create": "Forge!",
"date": "Date",
+ "delete": "Scuttle!",
"deleteConfirm": "Are ye sure ye want to scuttle \"{0}\", ye rogue?",
"divider": "{'|'}",
"edit": "Amend",
@@ -104,7 +105,6 @@
"tags": "Marks",
"today": "Today"
},
- "delete": "Scuttle!",
"drop": {
"desc": "An open-source game distribution platform built for speed, flexibility and beauty, like a swift brigantine!",
"drop": "Drop"
diff --git a/server/i18n/locales/en_us.json b/server/i18n/locales/en_us.json
index ed7a803f..8163a380 100644
--- a/server/i18n/locales/en_us.json
+++ b/server/i18n/locales/en_us.json
@@ -9,7 +9,9 @@
"subheader": "Manage the devices authorized to access your Drop account.",
"title": "Devices"
},
- "home": { "title": "Home" },
+ "home": {
+ "title": "Home"
+ },
"notifications": {
"all": "View all {arrow}",
"clear": "Clear notifications",
@@ -21,7 +23,35 @@
"title": "Notifications",
"unread": "Unread Notifications"
},
- "security": { "title": "Security" },
+ "security": {
+ "2fa": {
+ "superlevelHint": {
+ "signin": "Sign in {arrow}",
+ "success": "You have access to these protected actions.",
+ "title": "Sign in again to access these settings."
+ },
+ "title": "Two-factor authentication",
+ "totp": {
+ "description": "TOTP generates one-time codes, completely offline. You can use any TOTP authenticator you like.",
+ "disableButton": "Disable",
+ "title": "TOTP"
+ },
+ "webauthn": {
+ "bypassHint": "Also lets you bypass signing in with compatible devices.",
+ "description": "Otherwise known as passkeys. Authenticate using biometrics, a device, YubiKeys, or any compatible FIDO2 device.",
+ "manage": "Manage",
+ "modal": {
+ "description": "Create new keys or remove existing keys from your account.",
+ "new": "New key",
+ "tableCreated": "Created",
+ "tableName": "Name",
+ "title": "WebAuthn Keys"
+ },
+ "title": "WebAuthn"
+ }
+ },
+ "title": "Security"
+ },
"settings": "Settings",
"title": "Account Settings",
"token": {
@@ -50,6 +80,31 @@
"adminTitle": "Admin Dashboard - Drop",
"adminTitleTemplate": "{0} - Admin - Drop",
"auth": {
+ "2fa": {
+ "backToOptions": "{arrow} Back to options",
+ "description": "Two-factor authentication is enabled on your account. Choose one of the options below to continue.",
+ "passkey": {
+ "createDescription": "WebAuthn, or passkeys, allow you to sign in or complete 2FA with biometrics or hardware security devices.",
+ "createTitle": "Create a passkey",
+ "description": "Use a passkey, like biometrics, a hardware security device, or other compatible device to sign in to your Drop account.",
+ "passkeyNameTag": "Name",
+ "signinButton": "Sign in with WebAuthn",
+ "title": "WebAuthn"
+ },
+ "success": {
+ "back": "{arrow} Back to account security",
+ "description": "Drop has successfully created and added your 2FA method. If this is your first time configuring 2FA, your account now requires it to sign in.",
+ "title": "Added your 2FA method!"
+ },
+ "title": "Two-factor authentication",
+ "totp": {
+ "createDescription": "Use your TOTP authenticator, like Google Authenticator, Aegis, or Bitwarden, to add 2FA to your Drop account.",
+ "createHint": "Enter the generated code to enable TOTP",
+ "createTitle": "Set up your authenticator",
+ "description": "Use a one-time code to sign in to your Drop account.",
+ "title": "TOTP"
+ }
+ },
"callback": {
"authClient": "Authorize client?",
"authorize": "Authorize",
@@ -72,7 +127,7 @@
"register": {
"confirmPasswordFormat": "Must be the same as above",
"emailFormat": "Must be in the format user{'@'}example.com",
- "passwordFormat": "Must be 14 or more characters",
+ "passwordFormat": "Must be 8 or more characters",
"subheader": "Fill in your details below to create your account.",
"title": "Create your Drop account",
"usernameFormat": "Must be 5 or more characters, and lowercase"
@@ -81,12 +136,14 @@
"externalProvider": "external provider",
"forgot": "Forgot password?",
"noAccount": "Don't have an account? Ask an admin to create one for you.",
+ "noAccountProtected": "We need you to sign in again for security reasons while attempting to access more sensitive actions.",
"or": "OR",
"pageTitle": "Sign in to Drop",
"rememberMe": "Remember me",
"signin": "Sign in",
"signinWithExternalProvider": "Sign in with {externalProvider} {arrow}",
- "title": "Sign in to your account"
+ "title": "Sign in to your account",
+ "titleProtected": "Sign in to access protected action"
},
"signout": "Signout",
"username": "Username"
@@ -105,6 +162,7 @@
"close": "Close",
"create": "Create",
"date": "Date",
+ "delete": "Delete",
"deleteConfirm": "Are you sure you want to delete \"{0}\"?",
"divider": "{'|'}",
"edit": "Edit",
@@ -119,12 +177,12 @@
"remove": "Remove",
"save": "Save",
"saved": "Saved",
+ "select": "Select",
"servers": "Servers",
"srLoading": "Loading…",
"tags": "Tags",
"today": "Today"
},
- "delete": "Delete",
"drop": {
"desc": "An open-source game distribution platform built for speed, flexibility and beauty.",
"drop": "Drop"
@@ -158,7 +216,9 @@
"invalidPassState": "Invalid password state. Please contact the server administrator.",
"invalidUserOrPass": "Invalid username or password.",
"inviteIdRequired": "id required in fetching invitation",
- "method": { "signinDisabled": "Sign in method not enabled" },
+ "method": {
+ "signinDisabled": "Sign in method not enabled"
+ },
"usernameTaken": "Username already taken."
},
"backHome": "{arrow} Back to home",
@@ -248,12 +308,18 @@
"aboutDrop": "About Drop",
"api": "API documentation",
"comparison": "Comparison",
- "docs": { "client": "Client Docs", "server": "Server Docs" },
+ "docs": {
+ "client": "Client Docs",
+ "server": "Server Docs"
+ },
"documentation": "Documentation",
"findGame": "Find a Game",
"footer": "Footer",
"games": "Games",
- "social": { "discord": "Discord", "github": "GitHub" },
+ "social": {
+ "discord": "Discord",
+ "github": "GitHub"
+ },
"topSellers": "Top Sellers",
"version": "Drop {version} {gitRef}"
},
@@ -303,6 +369,10 @@
"admin": {
"detectedGame": "Drop has detected you have new games to import.",
"detectedVersion": "Drop has detected you have new versions of this game to import.",
+ "fileExtSelector": {
+ "add": "Add \"{0}\"",
+ "noSelected": "No extensions selected."
+ },
"game": {
"addCarouselNoImages": "No images to add.",
"addDescriptionNoImages": "No images to add.",
@@ -323,10 +393,14 @@
"setCover": "Set as cover"
},
"gameLibrary": "Game Library",
+ "gameSelector": {
+ "hint": "Type at least 4 characters to get results"
+ },
"import": {
"bulkImportDescription": "When on this page, you won't be redirect to the import task, so you can import multiple games in succession.",
"bulkImportTitle": "Bulk import mode",
"import": "Import",
+ "importAs": "Import as",
"link": "Import {arrow}",
"loading": "Loading game results…",
"search": "Search",
@@ -344,6 +418,7 @@
"launchPlaceholder": "game.exe --args",
"loadingVersion": "Loading version metadata…",
"noLaunches": "No launch configurations added.",
+ "noNameProvided": "No name provided.",
"noSetups": "No setup configurations added.",
"noVersions": "No versions to import",
"platform": "Version platform",
@@ -357,6 +432,23 @@
},
"withoutMetadata": "Import without metadata"
},
+ "launchRow": {
+ "autosuggestHint": "Auto-suggest extensions",
+ "currentDirHint": "The installation directory is set as the current directory when launching. It is not prepended to your command.",
+ "executorHint": "{executor} is replaced with the game's launch command for emulators.",
+ "executorSelect": "Select new executor",
+ "executorTitle": "Executor",
+ "noExecutorSelected": "No executor selected"
+ },
+ "launchSelector": {
+ "description": "Select a launch option as an executor for your new launch option.",
+ "noVersions": "No versions imported.",
+ "platformFilterHint": "Only showing launches for:",
+ "search": "Search for an executor",
+ "selectCommand": "Select a launch command",
+ "selectVersions": "Select a version",
+ "title": "Select a launch option"
+ },
"libraryHint": "No libraries configured.",
"libraryHintDocsLink": "What does this mean? {arrow}",
"metadata": {
@@ -446,7 +538,17 @@
"subheader": "As you add folders to your library sources, Drop will detect it and prompt you to import it. Each game needs to be imported before you can import a version.",
"title": "Libraries",
"version": {
- "noVersions": "You have no versions of this game available."
+ "description": "All versions imported for your game.",
+ "noSetups": "No setups configured.",
+ "noVersions": "You have no versions of this game available.",
+ "setupOnly": "Version configured as in setup-only mode.",
+ "table": {
+ "launch": "Launch Configurations",
+ "name": "Name (ID)",
+ "path": "Path",
+ "setup": "Setup Configurations"
+ },
+ "title": "Versions"
}
},
"back": "Back to Library",
@@ -595,6 +697,7 @@
"completedTasksTitle": "Completed tasks",
"dailyScheduledTitle": "Daily scheduled tasks",
"execute": "{arrow} Execute",
+ "noActions": "No actions",
"noTasksRunning": "No tasks currently running",
"progress": "{0}%",
"runningTasksTitle": "Running tasks",
@@ -628,8 +731,15 @@
},
"userHeader": {
"closeSidebar": "Close sidebar",
- "links": { "community": "Community", "library": "Library", "news": "News" },
- "profile": { "admin": "Admin Dashboard", "settings": "Account settings" }
+ "links": {
+ "community": "Community",
+ "library": "Library",
+ "news": "News"
+ },
+ "profile": {
+ "admin": "Admin Dashboard",
+ "settings": "Account settings"
+ }
},
"users": {
"admin": {
diff --git a/server/i18n/locales/fr.json b/server/i18n/locales/fr.json
index f3d19e26..1764f52d 100644
--- a/server/i18n/locales/fr.json
+++ b/server/i18n/locales/fr.json
@@ -70,7 +70,7 @@
"register": {
"confirmPasswordFormat": "Doit être pareil qu'au dessus",
"emailFormat": "Doit être au format utilisateur{'@'}exemple.com",
- "passwordFormat": "Doit être au moins 14 caractères ou plus",
+ "passwordFormat": "Doit être au moins 8 caractères ou plus",
"subheader": "Remplissez vos coordonnées pour créer votre compte.",
"title": "Créer votre compte Drop",
"usernameFormat": "Doit être au moins 5 caractères et en minuscules"
@@ -101,6 +101,7 @@
"close": "Fermer",
"create": "Créer",
"date": "Date",
+ "delete": "Supprimer",
"deleteConfirm": "Êtes vous sûr de vouloir supprimer \"{0}\" ?",
"divider": "{'|'}",
"edit": "Éditer",
@@ -120,7 +121,6 @@
"tags": "Étiquettes",
"today": "Aujourd'hui"
},
- "delete": "Supprimer",
"drop": {
"desc": "Une plateforme de distribution libre conçue pour être rapide, flexible et belle.",
"drop": "Drop"
diff --git a/server/i18n/locales/pl.json b/server/i18n/locales/pl.json
index b7215ca5..b20de9bc 100644
--- a/server/i18n/locales/pl.json
+++ b/server/i18n/locales/pl.json
@@ -70,7 +70,7 @@
"register": {
"confirmPasswordFormat": "Musi być takie samo jak powyżej",
"emailFormat": "Musi być w formacie uzytkownik{'@'}example.com",
- "passwordFormat": "Musi mieć conajmniej 14 znaków",
+ "passwordFormat": "Musi mieć conajmniej 8 znaków",
"subheader": "Wpisz poniżej swoje dane, aby utworzyć swoje konto.",
"title": "Stwórz swoje konto Drop",
"usernameFormat": "Musi mieć co najmniej 5 znaków i małe litery"
@@ -101,6 +101,7 @@
"close": "Zamknij",
"create": "Utwórz",
"date": "Data",
+ "delete": "Usuń",
"deleteConfirm": "Czy jesteś pewny że chcesz usunąć \"{0}\"?",
"divider": "{'|'}",
"edit": "Edytuj",
@@ -120,7 +121,6 @@
"tags": "Tagi",
"today": "Dzisiaj"
},
- "delete": "Usuń",
"drop": {
"desc": "Platforma typu open source do dystrybucji gier, stworzona z myślą o szybkości, elastyczności i estetyce.",
"drop": "Drop"
diff --git a/server/i18n/locales/ru.json b/server/i18n/locales/ru.json
index c3f58151..dc1e4d2e 100644
--- a/server/i18n/locales/ru.json
+++ b/server/i18n/locales/ru.json
@@ -79,6 +79,7 @@
"close": "Закрыть",
"create": "Создать",
"date": "Дата",
+ "delete": "Удалить",
"deleteConfirm": "Вы точно хотите удалить \"{0}\"?",
"edit": "Редактировать",
"friends": "Друзья",
@@ -94,7 +95,6 @@
"tags": "Теги",
"today": "Сегодня"
},
- "delete": "Удалить",
"drop": {
"drop": "Уронить"
},
diff --git a/server/layouts/default.vue b/server/layouts/default.vue
index 5b453632..f344fd47 100644
--- a/server/layouts/default.vue
+++ b/server/layouts/default.vue
@@ -9,8 +9,9 @@
-
+
+
diff --git a/server/package.json b/server/package.json
index eef05ecf..570331a8 100644
--- a/server/package.json
+++ b/server/package.json
@@ -12,7 +12,7 @@
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
- "postinstall": "nuxt prepare && prisma generate",
+ "postinstall": "nuxt prepare && prisma generate && buf generate",
"typecheck": "nuxt typecheck",
"lint": "pnpm run lint:eslint && pnpm run lint:prettier",
"lint:eslint": "eslint .",
@@ -20,8 +20,8 @@
"lint:fix": "eslint . --fix && prettier --write --list-different ."
},
"dependencies": {
+ "@bufbuild/protobuf": "^2.11.0",
"@discordapp/twemoji": "^16.0.1",
- "@drop-oss/droplet": "5.3.1",
"@headlessui/vue": "^1.7.23",
"@heroicons/vue": "^2.1.5",
"@lobomfz/prismark": "0.0.3",
@@ -44,8 +44,9 @@
"fast-fuzzy": "^1.12.0",
"file-type-mime": "^0.4.3",
"jdenticon": "^3.3.0",
- "kjua": "^0.10.0",
"jose": "^6.1.3",
+ "jsonwebtoken": "^9.0.3",
+ "kjua": "^0.10.0",
"luxon": "^3.6.1",
"micromark": "^4.0.1",
"normalize-url": "^8.0.2",
@@ -68,10 +69,13 @@
"vuedraggable": "^4.1.0"
},
"devDependencies": {
+ "@bufbuild/buf": "^1.65.0",
+ "@bufbuild/protoc-gen-es": "^2.11.0",
"@intlify/eslint-plugin-vue-i18n": "^4.0.1",
"@nuxt/eslint": "^1.3.0",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
+ "@types/jsonwebtoken": "^9.0.10",
"@types/luxon": "^3.6.2",
"@types/node": "^22.13.16",
"@types/semver": "^7.7.0",
diff --git a/server/pages/account/notifications.vue b/server/pages/account/notifications.vue
index 04b83f13..3c25592a 100644
--- a/server/pages/account/notifications.vue
+++ b/server/pages/account/notifications.vue
@@ -85,7 +85,7 @@
@click="deleteNotification(notification.id)"
>
- {{ $t("delete") }}
+ {{ $t("common.delete") }}
diff --git a/server/pages/account/security.vue b/server/pages/account/security.vue
index 8d358c4a..83b64ece 100644
--- a/server/pages/account/security.vue
+++ b/server/pages/account/security.vue
@@ -13,13 +13,21 @@
- Sign in again to access these settings.
- {{ " " }}
+ {{ $t("account.security.2fa.superlevelHint.title") }}
Sign in →
+
+
+ {{ $t("chars.arrow") }}
+
+
+
@@ -31,7 +39,7 @@
- You have access to these protected actions.
+ {{ $t("account.security.2fa.superlevelHint.success") }}
@@ -40,7 +48,7 @@
- Two-factor authentication
+ {{ $t("account.security.2fa.title") }}
@@ -67,15 +75,16 @@
class="absolute inset-0"
aria-hidden="true"
>
- TOTP
+ {{ $t("account.security.2fa.totp.title") }}
- TOTP generates one-time codes, completely offline. You can use any
- TOTP authenticator you like.
+ {{ $t("account.security.2fa.totp.description") }}
- Disable
+ {{
+ $t("account.security.2fa.totp.disableButton")
+ }}
-
WebAuthn
+
+ {{ $t("account.security.2fa.webauthn.title") }}
+
- Otherwise known as passkeys. Authenticate using biometrics, a
- device, YubiKeys, or any compatible FIDO2 device.
+ {{ $t("account.security.2fa.webauthn.description") }}
- Also lets you bypass signing in with compatible devices.
+ {{ $t("account.security.2fa.webauthn.bypassHint") }}
(webAuthnOpen = true)"
- >Manage {{ $t("account.security.2fa.webauthn.manage") }}
@@ -130,9 +140,11 @@
-
WebAuthn Keys
+
+ {{ $t("account.security.2fa.webauthn.modal.title") }}
+
- Create new keys or remove existing keys from your account.
+ {{ $t("account.security.2fa.webauthn.modal.description") }}
@@ -140,7 +152,7 @@
to="/mfa/setup/webauthn"
class="block rounded-md bg-blue-500 px-3 py-2 text-center text-sm font-semibold text-white shadow-xs hover:bg-blue-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
>
- New key
+ {{ $t("account.security.2fa.webauthn.modal.new") }}
@@ -156,17 +168,19 @@
scope="col"
class="py-3.5 pr-3 pl-4 text-left text-sm font-semibold text-white sm:pl-0"
>
- Name
+ {{ $t("account.security.2fa.webauthn.modal.tableName") }}
- Created
+ {{
+ $t("account.security.2fa.webauthn.modal.tableCreated")
+ }}
- Delete
+ {{ $t("common.delete") }}
@@ -193,9 +207,12 @@
- Delete deletePasskey(mec.id)"
>
+ {{ $t("common.delete") }}
+
@@ -229,4 +246,12 @@ const superlevel = await $dropFetch("/api/v1/user/superlevel");
const mfa = await $dropFetch("/api/v1/user/mfa");
const webAuthnOpen = ref(false);
+
+async function deletePasskey(id: string) {
+ await $dropFetch("/api/v1/user/mfa/webauthn", {
+ method: "DELETE",
+ body: { id },
+ failTitle: "Failed to delete passkey",
+ });
+}
diff --git a/server/pages/admin/index.vue b/server/pages/admin/index.vue
index 8d28871c..fafc6527 100644
--- a/server/pages/admin/index.vue
+++ b/server/pages/admin/index.vue
@@ -173,7 +173,7 @@
:title="t('home.admin.biggestGamesToDownload')"
:subtitle="t('home.admin.latestVersionOnly')"
>
-
+
@@ -181,7 +181,7 @@
:title="t('home.admin.biggestGamesOnServer')"
:subtitle="t('home.admin.allVersionsCombined')"
>
-
+
@@ -196,8 +196,6 @@ import DropLogo from "~/components/DropLogo.vue";
import { ServerStackIcon, UserGroupIcon } from "@heroicons/vue/24/outline";
import { getPercentage } from "~/utils/utils";
import { getBarColor } from "~/utils/colors";
-import type { GameSize } from "~/server/internal/gamesize";
-import type { RankItem } from "~/components/RankingList.vue";
definePageMeta({
layout: "admin",
@@ -211,20 +209,8 @@ const { t } = useI18n();
const systemData = useSystemData();
-const {
- version,
- gameCount,
- sources,
- userStats,
- biggestGamesLatest,
- biggestGamesCombined,
-} = await $dropFetch("/api/v1/admin/home");
-
-const gameToRankItem = (game: GameSize, rank: number): RankItem => ({
- rank: rank + 1,
- name: game.gameName,
- value: formatBytes(game.size),
-});
+const { version, gameCount, sources, userStats } =
+ await $dropFetch("/api/v1/admin/home");
const pieChartData = [
{
diff --git a/server/pages/admin/library/[id]/import.vue b/server/pages/admin/library/[id]/import.vue
index 2c5befc9..4ead6519 100644
--- a/server/pages/admin/library/[id]/import.vue
+++ b/server/pages/admin/library/[id]/import.vue
@@ -191,9 +191,9 @@
{{
launch.name
}}
- No name provided.
+ {{
+ $t("library.admin.import.version.noNameProvided")
+ }}
diff --git a/server/pages/admin/library/import.vue b/server/pages/admin/library/import.vue
index ee528ee2..b27c79b8 100644
--- a/server/pages/admin/library/import.vue
+++ b/server/pages/admin/library/import.vue
@@ -115,13 +115,14 @@
diff --git a/server/pages/admin/metadata/companies/index.vue b/server/pages/admin/metadata/companies/index.vue
index 44ac5dd9..eeec5de6 100644
--- a/server/pages/admin/metadata/companies/index.vue
+++ b/server/pages/admin/metadata/companies/index.vue
@@ -78,7 +78,7 @@
class="w-fit rounded-md bg-red-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm transition-all duration-200 hover:bg-red-500 hover:scale-105 hover:shadow-lg hover:shadow-red-500/25 active:scale-95 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600"
@click="() => deleteCompany(company.id)"
>
- {{ $t("delete") }}
+ {{ $t("common.delete") }}
diff --git a/server/pages/admin/task/[id]/index.vue b/server/pages/admin/task/[id]/index.vue
index 26dc80b1..bcdb0a4f 100644
--- a/server/pages/admin/task/[id]/index.vue
+++ b/server/pages/admin/task/[id]/index.vue
@@ -11,39 +11,35 @@
-
-
-
-
-
- {{ task.error.title }}
-
-
-
- {{ task.error.description }}
-
-
-
-
-
-
+
{{ task.name }}
+
+
+
+
+
+
+
+ {{ task.error.title }}
+
+
+ {{ task.error.description }}
+
+
+
+
- No actions
+ {{ $t("tasks.admin.noActions") }}
@@ -95,8 +91,8 @@