mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-22 04:31:15 +10:00
Compare commits
13 Commits
0ce1f3124f
...
small-fixe
| Author | SHA1 | Date | |
|---|---|---|---|
| 973c3efa18 | |||
| bcb88f8069 | |||
| b842d78b94 | |||
| b0bf1a2795 | |||
| 2d165bf997 | |||
| 650a3ca98d | |||
| 246c97ccc9 | |||
| f1fccd9bff | |||
| 2ae7f41be0 | |||
| beb824c8d9 | |||
| 8f41024be2 | |||
| 2420814862 | |||
| 41855bccd2 |
@ -1,54 +0,0 @@
|
|||||||
variables:
|
|
||||||
GIT_SUBMODULE_STRATEGY: recursive
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- build
|
|
||||||
|
|
||||||
services:
|
|
||||||
- docker:24.0.5-dind
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
|
|
||||||
|
|
||||||
build:
|
|
||||||
stage: build
|
|
||||||
image: docker:latest
|
|
||||||
variables:
|
|
||||||
IMAGE_NAME: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA
|
|
||||||
LATEST_IMAGE_NAME: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_NAME:latest
|
|
||||||
PUBLISH_IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
|
|
||||||
PUBLISH_LATEST_IMAGE_NAME: $CI_REGISTRY_IMAGE:latest
|
|
||||||
script:
|
|
||||||
- docker build -t $IMAGE_NAME .
|
|
||||||
- docker image tag $IMAGE_NAME $LATEST_IMAGE_NAME
|
|
||||||
- docker push $IMAGE_NAME
|
|
||||||
- docker push $LATEST_IMAGE_NAME
|
|
||||||
- |
|
|
||||||
if [ $CI_COMMIT_TAG ]; then
|
|
||||||
docker image tag $IMAGE_NAME $PUBLISH_IMAGE_NAME
|
|
||||||
docker image tag $IMAGE_NAME $PUBLISH_LATEST_IMAGE_NAME
|
|
||||||
docker push $PUBLISH_IMAGE_NAME $PUBLISH_LATEST_IMAGE_NAME
|
|
||||||
fi
|
|
||||||
|
|
||||||
build-arm64:
|
|
||||||
stage: build
|
|
||||||
image: arm64v8/docker:latest
|
|
||||||
tags:
|
|
||||||
- aarch64
|
|
||||||
variables:
|
|
||||||
IMAGE_NAME: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA-arm64
|
|
||||||
LATEST_IMAGE_NAME: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_NAME:latest-arm64
|
|
||||||
PUBLISH_IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG-arm64
|
|
||||||
PUBLISH_LATEST_IMAGE_NAME: $CI_REGISTRY_IMAGE:latest-arm64
|
|
||||||
script:
|
|
||||||
- docker build -t $IMAGE_NAME . --platform=linux/arm64
|
|
||||||
- docker image tag $IMAGE_NAME $LATEST_IMAGE_NAME
|
|
||||||
- docker push $IMAGE_NAME
|
|
||||||
- docker push $LATEST_IMAGE_NAME
|
|
||||||
- |
|
|
||||||
if [ $CI_COMMIT_TAG ]; then
|
|
||||||
docker image tag $IMAGE_NAME $PUBLISH_IMAGE_NAME
|
|
||||||
docker image tag $IMAGE_NAME $PUBLISH_LATEST_IMAGE_NAME
|
|
||||||
docker push $PUBLISH_IMAGE_NAME
|
|
||||||
docker push $PUBLISH_LATEST_IMAGE_NAME
|
|
||||||
fi
|
|
||||||
@ -45,12 +45,12 @@ ENV NODE_ENV=production
|
|||||||
ENV NUXT_TELEMETRY_DISABLED=1
|
ENV NUXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
# RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn add --network-timeout 1000000 --no-lockfile --ignore-scripts prisma@6.11.1
|
# RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn add --network-timeout 1000000 --no-lockfile --ignore-scripts prisma@6.11.1
|
||||||
RUN apk add --no-cache pnpm
|
RUN apk add --no-cache pnpm 7zip
|
||||||
RUN pnpm install prisma@6.11.1
|
RUN pnpm install prisma@6.11.1
|
||||||
# init prisma to download all required files
|
# init prisma to download all required files
|
||||||
RUN pnpm prisma init
|
RUN pnpm prisma init
|
||||||
|
|
||||||
COPY --from=build-system /app/package.json ./
|
COPY --from=build-system /app/prisma.config.ts ./
|
||||||
COPY --from=build-system /app/.output ./app
|
COPY --from=build-system /app/.output ./app
|
||||||
COPY --from=build-system /app/prisma ./prisma
|
COPY --from=build-system /app/prisma ./prisma
|
||||||
COPY --from=build-system /app/build ./startup
|
COPY --from=build-system /app/build ./startup
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import type { RouteLocationNormalized } from "vue-router";
|
|
||||||
import type { NavigationItem } from "./types";
|
import type { NavigationItem } from "./types";
|
||||||
|
|
||||||
export const useCurrentNavigationIndex = (
|
export const useCurrentNavigationIndex = (
|
||||||
@ -9,7 +8,7 @@ export const useCurrentNavigationIndex = (
|
|||||||
|
|
||||||
const currentNavigation = ref(-1);
|
const currentNavigation = ref(-1);
|
||||||
|
|
||||||
function calculateCurrentNavIndex(to: RouteLocationNormalized) {
|
function calculateCurrentNavIndex(to: typeof route) {
|
||||||
const validOptions = navigation
|
const validOptions = navigation
|
||||||
.map((e, i) => ({ ...e, index: i }))
|
.map((e, i) => ({ ...e, index: i }))
|
||||||
.filter((e) => to.fullPath.startsWith(e.prefix));
|
.filter((e) => to.fullPath.startsWith(e.prefix));
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
import withNuxt from "./.nuxt/eslint.config.mjs";
|
import withNuxt from "./.nuxt/eslint.config.mjs";
|
||||||
import eslintConfigPrettier from "eslint-config-prettier/flat";
|
import eslintConfigPrettier from "eslint-config-prettier/flat";
|
||||||
import vueI18n from "@intlify/eslint-plugin-vue-i18n";
|
import vueI18n from "@intlify/eslint-plugin-vue-i18n";
|
||||||
|
import noPrismaDelete from "./rules/no-prisma-delete.mts";
|
||||||
|
|
||||||
export default withNuxt([
|
export default withNuxt([
|
||||||
eslintConfigPrettier,
|
eslintConfigPrettier,
|
||||||
@ -19,6 +20,7 @@ export default withNuxt([
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"@intlify/vue-i18n/no-missing-keys": "error",
|
"@intlify/vue-i18n/no-missing-keys": "error",
|
||||||
|
"drop/no-prisma-delete": "error",
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
"vue-i18n": {
|
"vue-i18n": {
|
||||||
@ -29,5 +31,8 @@ export default withNuxt([
|
|||||||
messageSyntaxVersion: "^11.0.0",
|
messageSyntaxVersion: "^11.0.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
plugins: {
|
||||||
|
drop: { rules: { "no-prisma-delete": noPrismaDelete } },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -3,16 +3,16 @@
|
|||||||
"devices": {
|
"devices": {
|
||||||
"capabilities": "Capacités",
|
"capabilities": "Capacités",
|
||||||
"lastConnected": "Dernière Connexion",
|
"lastConnected": "Dernière Connexion",
|
||||||
"noDevices": "Aucun appareil connecté à vôtre compte.",
|
"noDevices": "Aucun appareil n'est connecté à vôtre compte.",
|
||||||
"platform": "Plateforme",
|
"platform": "Plateforme",
|
||||||
"revoke": "Révoquer",
|
"revoke": "Révoquer",
|
||||||
"subheader": "Gérer les appareils authorisés à accéder à votre compte Drop.",
|
"subheader": "Gérer les appareils authorisés à accéder à votre compte Drop.",
|
||||||
"title": "Appareils"
|
"title": "Appareils"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"all": "Voir tout {arrow}",
|
"all": "Tout voir {arrow}",
|
||||||
"desc": "Voir et gérer vos notifications.",
|
"desc": "Voir et gérer vos notifications.",
|
||||||
"markAllAsRead": "Marquer tout comme lu",
|
"markAllAsRead": "Tout marqué comme lu",
|
||||||
"markAsRead": "Marquer comme lu",
|
"markAsRead": "Marquer comme lu",
|
||||||
"none": "Pas de notification",
|
"none": "Pas de notification",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
@ -62,7 +62,6 @@
|
|||||||
"description": "Utiliser un code pour vous connecter à votre client Drop si vous ne pouvez pas ouvrir un navigateur web sur votre appareil.",
|
"description": "Utiliser un code pour vous connecter à votre client Drop si vous ne pouvez pas ouvrir un navigateur web sur votre appareil.",
|
||||||
"title": "Connecter votre client Drop"
|
"title": "Connecter votre client Drop"
|
||||||
},
|
},
|
||||||
"confirmPassword": "Confirmez @:auth.password",
|
|
||||||
"displayName": "Nom d'Affichage",
|
"displayName": "Nom d'Affichage",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"password": "Mot de passe",
|
"password": "Mot de passe",
|
||||||
@ -148,7 +147,6 @@
|
|||||||
"auth": {
|
"auth": {
|
||||||
"disabled": "Compte invalide or désactivé. Merci de contacter l'administrateur du serveur.",
|
"disabled": "Compte invalide or désactivé. Merci de contacter l'administrateur du serveur.",
|
||||||
"invalidInvite": "Invitation invalide ou expirée",
|
"invalidInvite": "Invitation invalide ou expirée",
|
||||||
"invalidPassState": "Le mot de passe enregistré est invalide. Merci de contacter l'administrateur du serveur.",
|
|
||||||
"invalidUserOrPass": "Nom d'utilisateur ou password invalide.",
|
"invalidUserOrPass": "Nom d'utilisateur ou password invalide.",
|
||||||
"inviteIdRequired": "id est requis pour récupérer l'invitation",
|
"inviteIdRequired": "id est requis pour récupérer l'invitation",
|
||||||
"method": {
|
"method": {
|
||||||
@ -157,10 +155,6 @@
|
|||||||
"usernameTaken": "Nom d'utilisateur déjà pris."
|
"usernameTaken": "Nom d'utilisateur déjà pris."
|
||||||
},
|
},
|
||||||
"backHome": "{arrow} Retour a l'accueil",
|
"backHome": "{arrow} Retour a l'accueil",
|
||||||
"externalUrl": {
|
|
||||||
"subtitle": "Ce message n'est visible qu'aux administrateurs.",
|
|
||||||
"title": "Accès via une EXTERNAL_URL différente. Veuillez consulter la documentation."
|
|
||||||
},
|
|
||||||
"game": {
|
"game": {
|
||||||
"banner": {
|
"banner": {
|
||||||
"description": "Drop a échoué a mettre à jour l'image de la bannière : {0}",
|
"description": "Drop a échoué a mettre à jour l'image de la bannière : {0}",
|
||||||
@ -221,7 +215,6 @@
|
|||||||
"revokeClient": "Échec de la révocation du client",
|
"revokeClient": "Échec de la révocation du client",
|
||||||
"revokeClientFull": "Échec de la revocation du client {0}",
|
"revokeClientFull": "Échec de la revocation du client {0}",
|
||||||
"signIn": "Se connecter {arrow}",
|
"signIn": "Se connecter {arrow}",
|
||||||
"support": "Assistance Discord",
|
|
||||||
"unknown": "Une erreur inconnue est survenue",
|
"unknown": "Une erreur inconnue est survenue",
|
||||||
"upload": {
|
"upload": {
|
||||||
"description": "Drop n'a pas pu uploader le fichier : {0}",
|
"description": "Drop n'a pas pu uploader le fichier : {0}",
|
||||||
@ -261,11 +254,7 @@
|
|||||||
"admin": {
|
"admin": {
|
||||||
"admin": "Administration",
|
"admin": "Administration",
|
||||||
"metadata": "Méta",
|
"metadata": "Méta",
|
||||||
"settings": {
|
"settings": "Paramètres",
|
||||||
"store": "Store",
|
|
||||||
"title": "Paramètres",
|
|
||||||
"tokens": "API tokens"
|
|
||||||
},
|
|
||||||
"tasks": "Tâches",
|
"tasks": "Tâches",
|
||||||
"users": "Utilisateurs"
|
"users": "Utilisateurs"
|
||||||
},
|
},
|
||||||
@ -338,8 +327,6 @@
|
|||||||
},
|
},
|
||||||
"withoutMetadata": "Importer sans les données méta"
|
"withoutMetadata": "Importer sans les données méta"
|
||||||
},
|
},
|
||||||
"libraryHint": "Pas de bibliothèque configurée.",
|
|
||||||
"libraryHintDocsLink": "Qu'est-ce que cela veut dire ? {arrow}",
|
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"companies": {
|
"companies": {
|
||||||
"action": "Gérer {arrow}",
|
"action": "Gérer {arrow}",
|
||||||
@ -353,25 +340,15 @@
|
|||||||
"description": "Les sociétés organisent les jeux par qui les a développer ou éditer.",
|
"description": "Les sociétés organisent les jeux par qui les a développer ou éditer.",
|
||||||
"editor": {
|
"editor": {
|
||||||
"action": "Ajouter un jeu {plus}",
|
"action": "Ajouter un jeu {plus}",
|
||||||
"descriptionPlaceholder": "{'<'}description{'>'}",
|
|
||||||
"developed": "Développé",
|
"developed": "Développé",
|
||||||
"libraryDescription": "Ajouter, supprimer ou personnaliser ce que cette société a développé et/ou publié.",
|
"libraryDescription": "Ajouter, supprimer ou personnaliser ce que cette société a développé et/ou publié.",
|
||||||
"libraryTitle": "Bibliothèque de jeux",
|
"libraryTitle": "Bibliothèque de jeux",
|
||||||
"noDescription": "(pas de description)",
|
"noDescription": "(pas de description)",
|
||||||
"published": "Publié",
|
"published": "Publié",
|
||||||
"uploadBanner": "Uploader bannière",
|
"uploadBanner": "Uploader bannière",
|
||||||
"uploadIcon": "Uplader icône",
|
"uploadIcon": "Uplader icône"
|
||||||
"websitePlaceholder": "{'<'}site web{'>'}"
|
|
||||||
},
|
},
|
||||||
"modals": {
|
"modals": {
|
||||||
"createDescription": "Créez une société pour mieux organizer vos jeux.",
|
|
||||||
"createFieldDescription": "Description de la Société",
|
|
||||||
"createFieldDescriptionPlaceholder": "Un petit studio indépendant qui...",
|
|
||||||
"createFieldName": "Nom de la société",
|
|
||||||
"createFieldNamePlaceholder": "Ma nouvelle société...",
|
|
||||||
"createFieldWebsite": "Site web de la société",
|
|
||||||
"createFieldWebsitePlaceholder": "https://exemple com/",
|
|
||||||
"createTitle": "Créer une société",
|
|
||||||
"nameDescription": "Éditer le nom de la société. Ce nom est utilisé pour trouver les jeux nouvellement importés.",
|
"nameDescription": "Éditer le nom de la société. Ce nom est utilisé pour trouver les jeux nouvellement importés.",
|
||||||
"nameTitle": "Éditer le nom de la société",
|
"nameTitle": "Éditer le nom de la société",
|
||||||
"shortDeckDescription": "Éditer la description de la company. Cela n'affecte pas la description longue (markdown).",
|
"shortDeckDescription": "Éditer la description de la company. Cela n'affecte pas la description longue (markdown).",
|
||||||
@ -407,15 +384,12 @@
|
|||||||
"create": "Créer une source",
|
"create": "Créer une source",
|
||||||
"createDesc": "Drop va utiliser cette source pour accéder à votre bibliothèque de jeux, et les rendre disponible.",
|
"createDesc": "Drop va utiliser cette source pour accéder à votre bibliothèque de jeux, et les rendre disponible.",
|
||||||
"desc": "Configurer vos sources de bibliothèques où Drop va regarder pour les nouveaux jeux et versions à importer.",
|
"desc": "Configurer vos sources de bibliothèques où Drop va regarder pour les nouveaux jeux et versions à importer.",
|
||||||
"documentationLink": "Documentation {arrow}",
|
|
||||||
"edit": "Éditer la source",
|
"edit": "Éditer la source",
|
||||||
"fsDesc": "Importe les jeux à partir d'un chemin d'accès sur le disque. Cela requière une structure des dossiers basées sur la version, et qui supporte les jeux archivés.",
|
"fsDesc": "Importe les jeux à partir d'un chemin d'accès sur le disque. Cela requière une structure des dossiers basées sur la version, et qui supporte les jeux archivés.",
|
||||||
"fsFlatDesc": "Importe les jeux à partir d'un chemin d’accès sur le disque, mais sans le sous-dossier version séparé. Utile pour migrer une bibliothèque vers Drop.",
|
"fsFlatDesc": "Importe les jeux à partir d'un chemin d’accès sur le disque, mais sans le sous-dossier version séparé. Utile pour migrer une bibliothèque vers Drop.",
|
||||||
"fsFlatTitle": "Compatibilité",
|
|
||||||
"fsPath": "Chemin d’accès",
|
"fsPath": "Chemin d’accès",
|
||||||
"fsPathDesc": "Un chemin d’accès absolu à votre bibliothèque de jeux.",
|
"fsPathDesc": "Un chemin d’accès absolu à votre bibliothèque de jeux.",
|
||||||
"fsPathPlaceholder": "/mnt/jeux",
|
"fsPathPlaceholder": "/mnt/jeux",
|
||||||
"fsTitle": "Drop-style",
|
|
||||||
"link": "Sources {arrow}",
|
"link": "Sources {arrow}",
|
||||||
"nameDesc": "Le nom de votre source, pour référence.",
|
"nameDesc": "Le nom de votre source, pour référence.",
|
||||||
"namePlaceholder": "Mes Nouvelle Source",
|
"namePlaceholder": "Mes Nouvelle Source",
|
||||||
@ -473,7 +447,6 @@
|
|||||||
"checkLater": "Vérifier plus tard pour les mises à jour.",
|
"checkLater": "Vérifier plus tard pour les mises à jour.",
|
||||||
"delete": "Supprimer l'Article",
|
"delete": "Supprimer l'Article",
|
||||||
"filter": {
|
"filter": {
|
||||||
"all": "Tous les temps",
|
|
||||||
"month": "Ce mois",
|
"month": "Ce mois",
|
||||||
"week": "Cette semaine",
|
"week": "Cette semaine",
|
||||||
"year": "Cette année"
|
"year": "Cette année"
|
||||||
@ -536,19 +509,15 @@
|
|||||||
"store": {
|
"store": {
|
||||||
"about": "À propos",
|
"about": "À propos",
|
||||||
"commingSoon": "prochainement",
|
"commingSoon": "prochainement",
|
||||||
"developers": "Développeurs | Développeur | Développeurs",
|
|
||||||
"exploreMore": "Explorer plus {arrow}",
|
"exploreMore": "Explorer plus {arrow}",
|
||||||
"featured": "Mis en avant",
|
"featured": "Mis en avant",
|
||||||
"images": "Images de Jeux",
|
"images": "Images de Jeux",
|
||||||
"lookAt": "Découvrez le maintenant",
|
|
||||||
"noDevelopers": "Pas de développeur",
|
"noDevelopers": "Pas de développeur",
|
||||||
"noFeatured": "PAS DE JEU MIS EN AVANT",
|
"noGame": "pas de jeu",
|
||||||
"noGame": "PAS DE JEU",
|
|
||||||
"noImages": "Pas d'image",
|
"noImages": "Pas d'image",
|
||||||
"noPublishers": "Pas d'éditeur.",
|
"noPublishers": "Pas d'éditeur.",
|
||||||
"noTags": "Pas de tag",
|
"noTags": "Pas de tag",
|
||||||
"openAdminDashboard": "Ouvrir dans le Tableau de Bord d'Administration",
|
"openAdminDashboard": "Ouvrir dans le Tableau de Bord d'Administration",
|
||||||
"openFeatured": "Mettez des étoiles aux jeux dans l'administration de la bibliothèque {arrow}",
|
|
||||||
"platform": "Plateforme | Plateforme | Plateformes",
|
"platform": "Plateforme | Plateforme | Plateformes",
|
||||||
"publishers": "Éditeurs | Éditeur | Éditeurs",
|
"publishers": "Éditeurs | Éditeur | Éditeurs",
|
||||||
"rating": "Note",
|
"rating": "Note",
|
||||||
@ -575,9 +544,7 @@
|
|||||||
"back": "{arrow} Retour aux Tâches",
|
"back": "{arrow} Retour aux Tâches",
|
||||||
"completedTasksTitle": "Tâches complétées",
|
"completedTasksTitle": "Tâches complétées",
|
||||||
"dailyScheduledTitle": "Tâches quotidiennes planifiées",
|
"dailyScheduledTitle": "Tâches quotidiennes planifiées",
|
||||||
"execute": "{arrow} Exécuter",
|
|
||||||
"noTasksRunning": "Pas de tâche en cours",
|
"noTasksRunning": "Pas de tâche en cours",
|
||||||
"progress": "{0]%",
|
|
||||||
"runningTasksTitle": "Tâches en cours d'exécution",
|
"runningTasksTitle": "Tâches en cours d'exécution",
|
||||||
"scheduled": {
|
"scheduled": {
|
||||||
"checkUpdateDescription": "Vérifier si Drop a une mise à jour.",
|
"checkUpdateDescription": "Vérifier si Drop a une mise à jour.",
|
||||||
@ -621,7 +588,6 @@
|
|||||||
"description": "Drop supporte une variété de \"mécanismes d'authentification\". Lorsque vous les activez ou les désactivez, ils sont affichés sur la page de connection pour que les utilisateurs puissent les sélectionner. Cliquer sur le menu à points pour configurer le mécanisme d'authentification.",
|
"description": "Drop supporte une variété de \"mécanismes d'authentification\". Lorsque vous les activez ou les désactivez, ils sont affichés sur la page de connection pour que les utilisateurs puissent les sélectionner. Cliquer sur le menu à points pour configurer le mécanisme d'authentification.",
|
||||||
"disabled": "Désactivé",
|
"disabled": "Désactivé",
|
||||||
"enabled": "Activé",
|
"enabled": "Activé",
|
||||||
"enabledKey": "Activée ?",
|
|
||||||
"oidc": "OpenID Connect",
|
"oidc": "OpenID Connect",
|
||||||
"simple": "Simple (nom d'utilisateur/mot de passe)",
|
"simple": "Simple (nom d'utilisateur/mot de passe)",
|
||||||
"srOpenOptions": "Ouvrir les options",
|
"srOpenOptions": "Ouvrir les options",
|
||||||
@ -639,7 +605,7 @@
|
|||||||
"createInvitation": "Créer invitation",
|
"createInvitation": "Créer invitation",
|
||||||
"description": "L'authentification simple utilise un système d'invitations pour créer les utilisateurs. Tu peux créer une invitation et optionnellement spécifier le nom d'utilisateur ou email de cet utilisateur, et un lien magique sera généré un lien magique qui peut être utilisé pour créer le compte.",
|
"description": "L'authentification simple utilise un système d'invitations pour créer les utilisateurs. Tu peux créer une invitation et optionnellement spécifier le nom d'utilisateur ou email de cet utilisateur, et un lien magique sera généré un lien magique qui peut être utilisé pour créer le compte.",
|
||||||
"expires": "Expire : {expiry}",
|
"expires": "Expire : {expiry}",
|
||||||
"invitationTitle": "Invitations",
|
"invitationTitle": "invitations",
|
||||||
"invite3Days": "3 jours",
|
"invite3Days": "3 jours",
|
||||||
"invite6Months": "6 mois",
|
"invite6Months": "6 mois",
|
||||||
"inviteAdminSwitchDescription": "Créer cet utilisateur en tant qu'adminstrateur",
|
"inviteAdminSwitchDescription": "Créer cet utilisateur en tant qu'adminstrateur",
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="!noWrapper" class="flex flex-col w-full min-h-screen bg-zinc-900">
|
<div v-if="!noWrapper" class="flex flex-col w-full min-h-screen bg-zinc-900">
|
||||||
<UserHeader class="z-50" hydrate-on-idle />
|
<LazyUserHeader class="z-50" hydrate-on-idle />
|
||||||
<div class="grow flex">
|
<div class="grow flex">
|
||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
</div>
|
</div>
|
||||||
<UserFooter class="z-50" hydrate-on-interaction />
|
<LazyUserFooter class="z-50" hydrate-on-interaction />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex w-full min-h-screen bg-zinc-900">
|
<div v-else class="flex w-full min-h-screen bg-zinc-900">
|
||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
|
|||||||
@ -175,6 +175,9 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
i18n: {
|
i18n: {
|
||||||
|
bundle: {
|
||||||
|
optimizeTranslationDirective: false,
|
||||||
|
},
|
||||||
defaultLocale: "en-us",
|
defaultLocale: "en-us",
|
||||||
strategy: "no_prefix",
|
strategy: "no_prefix",
|
||||||
experimental: {
|
experimental: {
|
||||||
|
|||||||
11
package.json
11
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "drop",
|
"name": "drop",
|
||||||
"version": "0.3.3",
|
"version": "0.3.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
@ -21,7 +21,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordapp/twemoji": "^16.0.1",
|
"@discordapp/twemoji": "^16.0.1",
|
||||||
"@drop-oss/droplet": "3.2.0",
|
"@drop-oss/droplet": "3.5.0",
|
||||||
"@headlessui/vue": "^1.7.23",
|
"@headlessui/vue": "^1.7.23",
|
||||||
"@heroicons/vue": "^2.1.5",
|
"@heroicons/vue": "^2.1.5",
|
||||||
"@lobomfz/prismark": "0.0.3",
|
"@lobomfz/prismark": "0.0.3",
|
||||||
@ -37,6 +37,7 @@
|
|||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"cheerio": "^1.0.0",
|
"cheerio": "^1.0.0",
|
||||||
"cookie-es": "^2.0.0",
|
"cookie-es": "^2.0.0",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
"fast-fuzzy": "^1.12.0",
|
"fast-fuzzy": "^1.12.0",
|
||||||
"file-type-mime": "^0.4.3",
|
"file-type-mime": "^0.4.3",
|
||||||
"jdenticon": "^3.3.0",
|
"jdenticon": "^3.3.0",
|
||||||
@ -47,7 +48,7 @@
|
|||||||
"nuxt-security": "2.2.0",
|
"nuxt-security": "2.2.0",
|
||||||
"pino": "^9.7.0",
|
"pino": "^9.7.0",
|
||||||
"pino-pretty": "^13.0.0",
|
"pino-pretty": "^13.0.0",
|
||||||
"prisma": "^6.11.1",
|
"prisma": "6.11.1",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.7.1",
|
||||||
"stream-mime-type": "^2.0.0",
|
"stream-mime-type": "^2.0.0",
|
||||||
@ -65,7 +66,6 @@
|
|||||||
"@nuxt/eslint": "^1.3.0",
|
"@nuxt/eslint": "^1.3.0",
|
||||||
"@tailwindcss/forms": "^0.5.9",
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"@types/bcryptjs": "^3.0.0",
|
|
||||||
"@types/luxon": "^3.6.2",
|
"@types/luxon": "^3.6.2",
|
||||||
"@types/node": "^22.13.16",
|
"@types/node": "^22.13.16",
|
||||||
"@types/semver": "^7.7.0",
|
"@types/semver": "^7.7.0",
|
||||||
@ -87,8 +87,5 @@
|
|||||||
"vue3-carousel": "^0.16.0"
|
"vue3-carousel": "^0.16.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"prisma": {
|
|
||||||
"schema": "./prisma"
|
|
||||||
},
|
|
||||||
"packageManager": "pnpm@10.15.0+sha512.486ebc259d3e999a4e8691ce03b5cac4a71cbeca39372a9b762cb500cfdf0873e2cb16abe3d951b1ee2cf012503f027b98b6584e4df22524e0c7450d9ec7aa7b"
|
"packageManager": "pnpm@10.15.0+sha512.486ebc259d3e999a4e8691ce03b5cac4a71cbeca39372a9b762cb500cfdf0873e2cb16abe3d951b1ee2cf012503f027b98b6584e4df22524e0c7450d9ec7aa7b"
|
||||||
}
|
}
|
||||||
|
|||||||
3691
pnpm-lock.yaml
generated
3691
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,10 @@
|
|||||||
|
onlyBuiltDependencies:
|
||||||
|
- "@prisma/client"
|
||||||
|
- "@prisma/engines"
|
||||||
|
- "@tailwindcss/oxide"
|
||||||
|
- esbuild
|
||||||
|
- prisma
|
||||||
|
|
||||||
overrides:
|
overrides:
|
||||||
droplet: link:../../.local/share/pnpm/global/5/node_modules/@drop-oss/droplet
|
droplet: link:../../.local/share/pnpm/global/5/node_modules/@drop-oss/droplet
|
||||||
|
|
||||||
|
|||||||
10
prisma.config.ts
Normal file
10
prisma.config.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { config } from "dotenv";
|
||||||
|
import type { PrismaConfig } from "prisma";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
config();
|
||||||
|
|
||||||
|
export default {
|
||||||
|
schema: path.join("prisma"),
|
||||||
|
earlyAccess: true,
|
||||||
|
} satisfies PrismaConfig;
|
||||||
34
rules/no-prisma-delete.mts
Normal file
34
rules/no-prisma-delete.mts
Normal file
@ -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">;
|
||||||
@ -17,6 +17,10 @@ export default defineEventHandler<{
|
|||||||
|
|
||||||
const body = await readDropValidatedBody(h3, DeleteInvite);
|
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 {};
|
return {};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export default defineEventHandler(async (h3) => {
|
|||||||
|
|
||||||
const gameId = getRouterParam(h3, "id")!;
|
const gameId = getRouterParam(h3, "id")!;
|
||||||
|
|
||||||
libraryManager.deleteGame(gameId);
|
await libraryManager.deleteGame(gameId);
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -18,11 +18,13 @@ export default defineEventHandler<{ body: typeof DeleteLibrarySource.infer }>(
|
|||||||
|
|
||||||
const body = await readDropValidatedBody(h3, DeleteLibrarySource);
|
const body = await readDropValidatedBody(h3, DeleteLibrarySource);
|
||||||
|
|
||||||
await prisma.library.delete({
|
const { count } = await prisma.library.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
id: body.id,
|
id: body.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (count == 0)
|
||||||
|
throw createError({ statusCode: 404, message: "Library not found." });
|
||||||
|
|
||||||
libraryManager.removeLibrary(body.id);
|
libraryManager.removeLibrary(body.id);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,10 +13,10 @@ export default defineEventHandler(async (h3) => {
|
|||||||
statusMessage: "No id in router params",
|
statusMessage: "No id in router params",
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleted = await prisma.aPIToken.delete({
|
const { count } = await prisma.aPIToken.deleteMany({
|
||||||
where: { id: id, mode: APITokenMode.System },
|
where: { id: id, mode: APITokenMode.System },
|
||||||
})!;
|
})!;
|
||||||
if (!deleted)
|
if (count == 0)
|
||||||
throw createError({ statusCode: 404, statusMessage: "Token not found" });
|
throw createError({ statusCode: 404, statusMessage: "Token not found" });
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export default defineEventHandler(async (h3) => {
|
|||||||
if (!user)
|
if (!user)
|
||||||
throw createError({ statusCode: 404, statusMessage: "User not found." });
|
throw createError({ statusCode: 404, statusMessage: "User not found." });
|
||||||
|
|
||||||
|
// eslint-disable-next-line drop/no-prisma-delete
|
||||||
await prisma.user.delete({ where: { id: userId } });
|
await prisma.user.delete({ where: { id: userId } });
|
||||||
await userStatsManager.deleteUser();
|
await userStatsManager.deleteUser();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
|
|||||||
@ -84,7 +84,7 @@ export default defineEventHandler<{
|
|||||||
user: true,
|
user: true,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
prisma.invitation.delete({ where: { id: user.invitation } }),
|
prisma.invitation.deleteMany({ where: { id: user.invitation } }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await userStatsManager.addUser();
|
await userStatsManager.addUser();
|
||||||
|
|||||||
@ -42,7 +42,7 @@ export default defineEventHandler(async (h3) => {
|
|||||||
)
|
)
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
statusMessage: "Invalid capabilities.",
|
message: "Invalid capabilities.",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@ -17,21 +17,10 @@ export default defineClientEventHandler(async (h3) => {
|
|||||||
orderBy: {
|
orderBy: {
|
||||||
versionIndex: "desc", // Latest one first
|
versionIndex: "desc", // Latest one first
|
||||||
},
|
},
|
||||||
|
omit: {
|
||||||
|
dropletManifest: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const mappedVersions = versions
|
return versions;
|
||||||
.map((version) => {
|
|
||||||
if (!version.dropletManifest) return undefined;
|
|
||||||
|
|
||||||
const newVersion = { ...version, dropletManifest: undefined };
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore idk why we delete an undefined object
|
|
||||||
delete newVersion.dropletManifest;
|
|
||||||
return {
|
|
||||||
...newVersion,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter((e) => e);
|
|
||||||
|
|
||||||
return mappedVersions;
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -38,16 +38,14 @@ export default defineClientEventHandler(
|
|||||||
if (!game)
|
if (!game)
|
||||||
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
|
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
|
||||||
|
|
||||||
const save = await prisma.saveSlot.delete({
|
const { count } = await prisma.saveSlot.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
id: {
|
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
gameId: gameId,
|
gameId: gameId,
|
||||||
index: slotIndex,
|
index: slotIndex,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
});
|
});
|
||||||
if (!save)
|
if (count == 0)
|
||||||
throw createError({ statusCode: 404, statusMessage: "Save not found" });
|
throw createError({ statusCode: 404, statusMessage: "Save not found" });
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -20,14 +20,14 @@ export default defineEventHandler(async (h3) => {
|
|||||||
userIds.push("system");
|
userIds.push("system");
|
||||||
}
|
}
|
||||||
|
|
||||||
const notification = await prisma.notification.delete({
|
const { count } = await prisma.notification.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
id: notificationId,
|
id: notificationId,
|
||||||
userId: { in: userIds },
|
userId: { in: userIds },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!notification)
|
if (count == 0)
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
statusMessage: "Invalid notification ID",
|
statusMessage: "Invalid notification ID",
|
||||||
|
|||||||
@ -13,10 +13,10 @@ export default defineEventHandler(async (h3) => {
|
|||||||
statusMessage: "No id in router params",
|
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 },
|
where: { id: id, userId: userId, mode: APITokenMode.User },
|
||||||
})!;
|
})!;
|
||||||
if (!deleted)
|
if (count == 0)
|
||||||
throw createError({ statusCode: 404, statusMessage: "Token not found" });
|
throw createError({ statusCode: 404, statusMessage: "Token not found" });
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -66,6 +66,7 @@ export class OIDCManager {
|
|||||||
|
|
||||||
async create() {
|
async create() {
|
||||||
const wellKnownUrl = process.env.OIDC_WELLKNOWN as string | undefined;
|
const wellKnownUrl = process.env.OIDC_WELLKNOWN as string | undefined;
|
||||||
|
const scopes = process.env.OIDC_SCOPES as string | undefined;
|
||||||
let configuration: OIDCWellKnown;
|
let configuration: OIDCWellKnown;
|
||||||
if (wellKnownUrl) {
|
if (wellKnownUrl) {
|
||||||
const response: OIDCWellKnown = await $fetch<OIDCWellKnown>(wellKnownUrl);
|
const response: OIDCWellKnown = await $fetch<OIDCWellKnown>(wellKnownUrl);
|
||||||
@ -77,6 +78,9 @@ export class OIDCManager {
|
|||||||
) {
|
) {
|
||||||
throw new Error("Well known response was invalid");
|
throw new Error("Well known response was invalid");
|
||||||
}
|
}
|
||||||
|
if (scopes) {
|
||||||
|
response.scopes_supported = scopes.split(",");
|
||||||
|
}
|
||||||
|
|
||||||
configuration = response;
|
configuration = response;
|
||||||
} else {
|
} else {
|
||||||
@ -85,7 +89,6 @@ export class OIDCManager {
|
|||||||
| undefined;
|
| undefined;
|
||||||
const tokenEndpoint = process.env.OIDC_TOKEN as string | undefined;
|
const tokenEndpoint = process.env.OIDC_TOKEN as string | undefined;
|
||||||
const userinfoEndpoint = process.env.OIDC_USERINFO as string | undefined;
|
const userinfoEndpoint = process.env.OIDC_USERINFO as string | undefined;
|
||||||
const scopes = process.env.OIDC_SCOPES as string | undefined;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!authorizationEndpoint ||
|
!authorizationEndpoint ||
|
||||||
|
|||||||
@ -185,15 +185,19 @@ export class ClientHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async removeClient(id: string) {
|
async removeClient(id: string) {
|
||||||
|
const client = await prisma.client.findUnique({ where: { id } });
|
||||||
|
if (!client) return false;
|
||||||
const ca = useCertificateAuthority();
|
const ca = useCertificateAuthority();
|
||||||
await ca.blacklistClient(id);
|
await ca.blacklistClient(id);
|
||||||
|
|
||||||
|
// eslint-disable-next-line drop/no-prisma-delete
|
||||||
await prisma.client.delete({
|
await prisma.client.delete({
|
||||||
where: {
|
where: {
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await userStatsManager.cacheUserStats();
|
await userStatsManager.cacheUserStats();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,8 @@ class SystemConfig {
|
|||||||
private libraryFolder = process.env.LIBRARY ?? "./.data/library";
|
private libraryFolder = process.env.LIBRARY ?? "./.data/library";
|
||||||
private dataFolder = process.env.DATA ?? "./.data/data";
|
private dataFolder = process.env.DATA ?? "./.data/data";
|
||||||
|
|
||||||
|
private metadataTimeout = parseInt(process.env.METADATA_TIMEOUT ?? "5000");
|
||||||
|
|
||||||
private externalUrl = normalizeUrl(
|
private externalUrl = normalizeUrl(
|
||||||
process.env.EXTERNAL_URL ?? "http://localhost:3000",
|
process.env.EXTERNAL_URL ?? "http://localhost:3000",
|
||||||
{ stripWWW: false },
|
{ stripWWW: false },
|
||||||
@ -28,6 +30,10 @@ class SystemConfig {
|
|||||||
return this.dataFolder;
|
return this.dataFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMetadataTimeout() {
|
||||||
|
return this.metadataTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
getDropVersion() {
|
getDropVersion() {
|
||||||
return this.dropVersion;
|
return this.dropVersion;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,7 +105,10 @@ class LibraryManager {
|
|||||||
if (!game) return undefined;
|
if (!game) return undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const versions = await provider.listVersions(libraryPath);
|
const versions = await provider.listVersions(
|
||||||
|
libraryPath,
|
||||||
|
game.versions.map((v) => v.versionName),
|
||||||
|
);
|
||||||
const unimportedVersions = versions.filter(
|
const unimportedVersions = versions.filter(
|
||||||
(e) =>
|
(e) =>
|
||||||
game.versions.findIndex((v) => v.versionName == e) == -1 &&
|
game.versions.findIndex((v) => v.versionName == e) == -1 &&
|
||||||
@ -375,25 +378,23 @@ class LibraryManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deleteGameVersion(gameId: string, version: string) {
|
async deleteGameVersion(gameId: string, version: string) {
|
||||||
await prisma.gameVersion.delete({
|
await prisma.gameVersion.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
gameId_versionName: {
|
|
||||||
gameId: gameId,
|
gameId: gameId,
|
||||||
versionName: version,
|
versionName: version,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await gameSizeManager.deleteGameVersion(gameId, version);
|
await gameSizeManager.deleteGameVersion(gameId, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteGame(gameId: string) {
|
async deleteGame(gameId: string) {
|
||||||
await prisma.game.delete({
|
await prisma.game.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
id: gameId,
|
id: gameId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
gameSizeManager.deleteGame(gameId);
|
await gameSizeManager.deleteGame(gameId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGameVersionSize(
|
async getGameVersionSize(
|
||||||
|
|||||||
@ -24,7 +24,10 @@ export abstract class LibraryProvider<CFG> {
|
|||||||
* @param game folder name of the game to list versions for
|
* @param game folder name of the game to list versions for
|
||||||
* @returns list of version folder names
|
* @returns list of version folder names
|
||||||
*/
|
*/
|
||||||
abstract listVersions(game: string): Promise<string[]>;
|
abstract listVersions(
|
||||||
|
game: string,
|
||||||
|
existingPaths?: string[],
|
||||||
|
): Promise<string[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param game folder name of the game
|
* @param game folder name of the game
|
||||||
|
|||||||
@ -54,11 +54,15 @@ export class FilesystemProvider
|
|||||||
return folderDirs;
|
return folderDirs;
|
||||||
}
|
}
|
||||||
|
|
||||||
async listVersions(game: string): Promise<string[]> {
|
async listVersions(
|
||||||
|
game: string,
|
||||||
|
ignoredVersions?: string[],
|
||||||
|
): Promise<string[]> {
|
||||||
const gameDir = path.join(this.config.baseDir, game);
|
const gameDir = path.join(this.config.baseDir, game);
|
||||||
if (!fs.existsSync(gameDir)) throw new GameNotFoundError();
|
if (!fs.existsSync(gameDir)) throw new GameNotFoundError();
|
||||||
const versionDirs = fs.readdirSync(gameDir);
|
const versionDirs = fs.readdirSync(gameDir);
|
||||||
const validVersionDirs = versionDirs.filter((e) => {
|
const validVersionDirs = versionDirs.filter((e) => {
|
||||||
|
if (ignoredVersions && ignoredVersions.includes(e)) return false;
|
||||||
const fullDir = path.join(this.config.baseDir, game, e);
|
const fullDir = path.join(this.config.baseDir, game, e);
|
||||||
return DROPLET_HANDLER.hasBackendForPath(fullDir);
|
return DROPLET_HANDLER.hasBackendForPath(fullDir);
|
||||||
});
|
});
|
||||||
@ -109,17 +113,12 @@ export class FilesystemProvider
|
|||||||
) {
|
) {
|
||||||
const filepath = path.join(this.config.baseDir, game, version);
|
const filepath = path.join(this.config.baseDir, game, version);
|
||||||
if (!fs.existsSync(filepath)) return undefined;
|
if (!fs.existsSync(filepath)) return undefined;
|
||||||
let stream;
|
const stream = DROPLET_HANDLER.readFile(
|
||||||
while (!(stream instanceof ReadableStream)) {
|
|
||||||
const v = DROPLET_HANDLER.readFile(
|
|
||||||
filepath,
|
filepath,
|
||||||
filename,
|
filename,
|
||||||
options?.start ? BigInt(options.start) : undefined,
|
options?.start ? BigInt(options.start) : undefined,
|
||||||
options?.end ? BigInt(options.end) : undefined,
|
options?.end ? BigInt(options.end) : undefined,
|
||||||
);
|
);
|
||||||
if (!v) return undefined;
|
|
||||||
stream = v.getStream() as ReadableStream<unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -112,7 +112,7 @@ export class FlatFilesystemProvider
|
|||||||
);
|
);
|
||||||
if (!stream) return undefined;
|
if (!stream) return undefined;
|
||||||
|
|
||||||
return stream.getStream();
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
fsStats() {
|
fsStats() {
|
||||||
|
|||||||
@ -82,6 +82,10 @@ export class MetadataHandler {
|
|||||||
// TODO: fix eslint error
|
// TODO: fix eslint error
|
||||||
// eslint-disable-next-line no-async-promise-executor
|
// eslint-disable-next-line no-async-promise-executor
|
||||||
>(async (resolve, reject) => {
|
>(async (resolve, reject) => {
|
||||||
|
setTimeout(
|
||||||
|
() => reject(new Error("Timeout while fetching results")),
|
||||||
|
systemConfig.getMetadataTimeout(),
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
const results = await provider.search(query);
|
const results = await provider.search(query);
|
||||||
const mappedResults: InternalGameMetadataResult[] = results.map(
|
const mappedResults: InternalGameMetadataResult[] = results.map(
|
||||||
|
|||||||
@ -117,6 +117,10 @@ interface SteamAppDetailsLarge extends SteamAppDetailsSmall {
|
|||||||
filename: string;
|
filename: string;
|
||||||
ordinal: number;
|
ordinal: number;
|
||||||
}[];
|
}[];
|
||||||
|
mature_content_screenshots: {
|
||||||
|
filename: string;
|
||||||
|
ordinal: number;
|
||||||
|
}[];
|
||||||
};
|
};
|
||||||
full_description: string;
|
full_description: string;
|
||||||
}
|
}
|
||||||
@ -689,16 +693,20 @@ export class SteamProvider implements MetadataProvider {
|
|||||||
context?.progress(40);
|
context?.progress(40);
|
||||||
|
|
||||||
const images = [cover, banner];
|
const images = [cover, banner];
|
||||||
const screenshotCount = game.screenshots?.all_ages_screenshots?.length || 0;
|
|
||||||
context?.logger.info(`Processing ${screenshotCount} screenshots...`);
|
|
||||||
|
|
||||||
for (const image of game.screenshots?.all_ages_screenshots || []) {
|
const screenshots = game.screenshots?.all_ages_screenshots || [];
|
||||||
|
screenshots.push(...(game.screenshots?.mature_content_screenshots || []));
|
||||||
|
screenshots.sort((a, b) => a.ordinal - b.ordinal);
|
||||||
|
|
||||||
|
context?.logger.info(`Processing ${screenshots.length} screenshots...`);
|
||||||
|
|
||||||
|
for (const image of screenshots) {
|
||||||
const imageUrl = this._getImageUrl(image.filename);
|
const imageUrl = this._getImageUrl(image.filename);
|
||||||
images.push(createObject(imageUrl));
|
images.push(createObject(imageUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
context?.logger.info(
|
context?.logger.info(
|
||||||
`Image processing complete: icon, cover, banner and ${screenshotCount} screenshots`,
|
`Image processing complete: icon, cover, banner and ${screenshots.length} screenshots`,
|
||||||
);
|
);
|
||||||
context?.progress(50);
|
context?.progress(50);
|
||||||
|
|
||||||
|
|||||||
@ -124,7 +124,10 @@ class NewsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: string) {
|
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 },
|
where: { id },
|
||||||
});
|
});
|
||||||
if (article.imageObjectId) {
|
if (article.imageObjectId) {
|
||||||
|
|||||||
@ -259,16 +259,10 @@ class FsHashStore {
|
|||||||
*/
|
*/
|
||||||
async delete(id: ObjectReference) {
|
async delete(id: ObjectReference) {
|
||||||
await this.cache.remove(id);
|
await this.cache.remove(id);
|
||||||
|
await prisma.objectHash.deleteMany({
|
||||||
try {
|
|
||||||
// need to catch in case the object doesn't exist
|
|
||||||
await prisma.objectHash.delete({
|
|
||||||
where: {
|
where: {
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch {
|
|
||||||
/* empty */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,12 +53,16 @@ class ScreenshotManager {
|
|||||||
* @param id
|
* @param id
|
||||||
*/
|
*/
|
||||||
async delete(id: string) {
|
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: {
|
where: {
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await objectHandler.deleteAsSystem(deletedScreenshot.objectId);
|
await objectHandler.deleteAsSystem(screenshot.objectId);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -43,12 +43,12 @@ export default function createDBSessionHandler(): SessionProvider {
|
|||||||
},
|
},
|
||||||
async removeSession(token) {
|
async removeSession(token) {
|
||||||
await cache.remove(token);
|
await cache.remove(token);
|
||||||
await prisma.session.delete({
|
const { count } = await prisma.session.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
token,
|
token,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return true;
|
return count > 0;
|
||||||
},
|
},
|
||||||
async cleanupSessions() {
|
async cleanupSessions() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|||||||
@ -101,9 +101,7 @@ class UserLibraryManager {
|
|||||||
|
|
||||||
async collectionRemove(gameId: string, collectionId: string, userId: string) {
|
async collectionRemove(gameId: string, collectionId: string, userId: string) {
|
||||||
// Delete if exists
|
// Delete if exists
|
||||||
return (
|
const { count } = await prisma.collectionEntry.deleteMany({
|
||||||
(
|
|
||||||
await prisma.collectionEntry.deleteMany({
|
|
||||||
where: {
|
where: {
|
||||||
collectionId,
|
collectionId,
|
||||||
gameId,
|
gameId,
|
||||||
@ -111,9 +109,8 @@ class UserLibraryManager {
|
|||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
).count > 0
|
return count > 0;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async collectionCreate(name: string, userId: string) {
|
async collectionCreate(name: string, userId: string) {
|
||||||
@ -133,12 +130,13 @@ class UserLibraryManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deleteCollection(collectionId: string) {
|
async deleteCollection(collectionId: string) {
|
||||||
await prisma.collection.delete({
|
const { count } = await prisma.collection.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
id: collectionId,
|
id: collectionId,
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
return count > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import fs from "fs";
|
import fs from "node:fs";
|
||||||
import nodePath from "path";
|
import nodePath from "node:path";
|
||||||
|
|
||||||
export function fsStats(folderPath: string) {
|
export function fsStats(folderPath: string) {
|
||||||
const stats = fs.statfsSync(folderPath);
|
const stats = fs.statfsSync(folderPath);
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
// https://nuxt.com/docs/guide/concepts/typescript
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
"extends": "./.nuxt/tsconfig.json",
|
"extends": "./.nuxt/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"exactOptionalPropertyTypes": false
|
"exactOptionalPropertyTypes": false,
|
||||||
|
"allowImportingTsExtensions": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user