From 2aecd4cc6e850c715fafcf6ff69cfe1164bff8b3 Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Mon, 6 Apr 2026 10:59:06 +0300 Subject: [PATCH] feat: account settings per house --- lib/Controller/PrefsController.php | 25 +++++++++++++--------- lib/Controller/ShoppingListController.php | 2 +- lib/Service/ImageService.php | 12 +++++------ lib/Service/PrefsService.php | 8 +++---- openapi.json | 26 ++++++++++++++++++++--- src/api/prefs.ts | 10 +++++---- src/components/HouseSettingsDialog.vue | 9 ++++++++ src/components/PantrySettingsDialog.vue | 11 +++++----- src/views/SideNavigation.vue | 8 +++++-- 9 files changed, 76 insertions(+), 35 deletions(-) diff --git a/lib/Controller/PrefsController.php b/lib/Controller/PrefsController.php index 7834379..f24bb2e 100644 --- a/lib/Controller/PrefsController.php +++ b/lib/Controller/PrefsController.php @@ -85,36 +85,41 @@ final class PrefsController extends OCSController { } /** - * Get the user's preferred image upload folder + * Get the user's preferred image upload folder for a house + * + * @param int $houseId House id. * * @return DataResponse * * 200: Folder returned */ - #[ApiRoute(verb: 'GET', url: '/api/prefs/image-folder')] + #[ApiRoute(verb: 'GET', url: '/api/houses/{houseId}/prefs/image-folder')] #[NoAdminRequired] - public function getImageFolder(): DataResponse { - return $this->runAction(function (): DataResponse { + public function getImageFolder(int $houseId): DataResponse { + return $this->runAction(function () use ($houseId): DataResponse { $uid = $this->requireUid(); - return new DataResponse(['folder' => $this->prefs->getImageFolder($uid)]); + $this->auth->requireMember($houseId, $uid); + return new DataResponse(['folder' => $this->prefs->getImageFolder($uid, $houseId)]); }); } /** - * Set the user's preferred image upload folder + * Set the user's preferred image upload folder for a house * + * @param int $houseId House id. * @param string $folder Absolute path within the user's files. * * @return DataResponse * * 200: Folder updated */ - #[ApiRoute(verb: 'PUT', url: '/api/prefs/image-folder')] + #[ApiRoute(verb: 'PUT', url: '/api/houses/{houseId}/prefs/image-folder')] #[NoAdminRequired] - public function setImageFolder(string $folder): DataResponse { - return $this->runAction(function () use ($folder): DataResponse { + public function setImageFolder(int $houseId, string $folder): DataResponse { + return $this->runAction(function () use ($houseId, $folder): DataResponse { $uid = $this->requireUid(); - $stored = $this->prefs->setImageFolder($uid, $folder); + $this->auth->requireMember($houseId, $uid); + $stored = $this->prefs->setImageFolder($uid, $houseId, $folder); return new DataResponse(['folder' => $stored]); }); } diff --git a/lib/Controller/ShoppingListController.php b/lib/Controller/ShoppingListController.php index c746a94..bd57598 100644 --- a/lib/Controller/ShoppingListController.php +++ b/lib/Controller/ShoppingListController.php @@ -406,7 +406,7 @@ final class ShoppingListController extends OCSController { throw new \RuntimeException('Could not read uploaded file'); } $original = (string)($data['name'] ?? 'image.jpg'); - $fileId = $this->images->uploadForUser($uid, $original, $bytes); + $fileId = $this->images->uploadForUser($uid, $houseId, $original, $bytes); $updated = $this->lists->updateItem($itemId, ['imageFileId' => $fileId]); return new DataResponse($updated->jsonSerialize()); diff --git a/lib/Service/ImageService.php b/lib/Service/ImageService.php index b68dd66..94fefd4 100644 --- a/lib/Service/ImageService.php +++ b/lib/Service/ImageService.php @@ -24,11 +24,11 @@ class ImageService { * Upload image bytes to the user's configured pantry image folder, returning * the Nextcloud file id on success. */ - public function uploadForUser(string $uid, string $originalName, string $data): int { + public function uploadForUser(string $uid, int $houseId, string $originalName, string $data): int { if ($data === '') { throw new \InvalidArgumentException('Empty file'); } - $folder = $this->resolveShoppingItemsFolder($uid); + $folder = $this->resolveShoppingItemsFolder($uid, $houseId); $filename = $this->uniqueName($folder, $originalName); try { $file = $folder->newFile($filename, $data); @@ -38,14 +38,14 @@ class ImageService { return $file->getId(); } - private function resolveShoppingItemsFolder(string $uid): Folder { - $base = $this->resolveBaseFolder($uid); + private function resolveShoppingItemsFolder(string $uid, int $houseId): Folder { + $base = $this->resolveBaseFolder($uid, $houseId); return $this->getOrCreateSubFolder($base, self::SHOPPING_ITEMS_SUBDIR); } - private function resolveBaseFolder(string $uid): Folder { + private function resolveBaseFolder(string $uid, int $houseId): Folder { $userFolder = $this->rootFolder->getUserFolder($uid); - $path = $this->prefs->getImageFolder($uid); + $path = $this->prefs->getImageFolder($uid, $houseId); $relative = ltrim($path, '/'); if ($relative === '') { return $userFolder; diff --git a/lib/Service/PrefsService.php b/lib/Service/PrefsService.php index 3f5a80b..b3e2c22 100644 --- a/lib/Service/PrefsService.php +++ b/lib/Service/PrefsService.php @@ -36,19 +36,19 @@ class PrefsService { $this->config->setUserValue($uid, Application::APP_ID, self::KEY_LAST_HOUSE, (string)$houseId); } - public function getImageFolder(string $uid): string { + public function getImageFolder(string $uid, int $houseId): string { $value = $this->config->getUserValue( $uid, Application::APP_ID, - self::KEY_IMAGE_FOLDER, + self::KEY_IMAGE_FOLDER . '_' . $houseId, self::DEFAULT_IMAGE_FOLDER, ); return $this->normalizeFolder($value); } - public function setImageFolder(string $uid, string $folder): string { + public function setImageFolder(string $uid, int $houseId, string $folder): string { $normalized = $this->normalizeFolder($folder); - $this->config->setUserValue($uid, Application::APP_ID, self::KEY_IMAGE_FOLDER, $normalized); + $this->config->setUserValue($uid, Application::APP_ID, self::KEY_IMAGE_FOLDER . '_' . $houseId, $normalized); return $normalized; } diff --git a/openapi.json b/openapi.json index 9d2206f..82488ed 100644 --- a/openapi.json +++ b/openapi.json @@ -2157,10 +2157,10 @@ } } }, - "/ocs/v2.php/apps/pantry/api/prefs/image-folder": { + "/ocs/v2.php/apps/pantry/api/houses/{houseId}/prefs/image-folder": { "get": { "operationId": "prefs-get-image-folder", - "summary": "Get the user's preferred image upload folder", + "summary": "Get the user's preferred image upload folder for a house", "tags": [ "prefs" ], @@ -2173,6 +2173,16 @@ } ], "parameters": [ + { + "name": "houseId", + "in": "path", + "description": "House id.", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, { "name": "OCS-APIRequest", "in": "header", @@ -2247,7 +2257,7 @@ }, "put": { "operationId": "prefs-set-image-folder", - "summary": "Set the user's preferred image upload folder", + "summary": "Set the user's preferred image upload folder for a house", "tags": [ "prefs" ], @@ -2279,6 +2289,16 @@ } }, "parameters": [ + { + "name": "houseId", + "in": "path", + "description": "House id.", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, { "name": "OCS-APIRequest", "in": "header", diff --git a/src/api/prefs.ts b/src/api/prefs.ts index d25cf2d..da5802b 100644 --- a/src/api/prefs.ts +++ b/src/api/prefs.ts @@ -9,12 +9,14 @@ export async function setLastHouse(houseId: number | null): Promise { await ocs.put('/prefs/last-house', { houseId }) } -export async function getImageFolder(): Promise { - const resp = await ocs.get<{ folder: string }>('/prefs/image-folder') +export async function getImageFolder(houseId: number): Promise { + const resp = await ocs.get<{ folder: string }>(`/houses/${houseId}/prefs/image-folder`) return resp.data?.folder ?? '/Pantry' } -export async function setImageFolder(folder: string): Promise { - const resp = await ocs.put<{ folder: string }>('/prefs/image-folder', { folder }) +export async function setImageFolder(houseId: number, folder: string): Promise { + const resp = await ocs.put<{ folder: string }>(`/houses/${houseId}/prefs/image-folder`, { + folder, + }) return resp.data?.folder ?? folder } diff --git a/src/components/HouseSettingsDialog.vue b/src/components/HouseSettingsDialog.vue index 906b102..0a2b1f8 100644 --- a/src/components/HouseSettingsDialog.vue +++ b/src/components/HouseSettingsDialog.vue @@ -301,6 +301,15 @@ const strings = { descriptionPlaceholder: t('pantry', 'A short description'), save: t('pantry', 'Save changes'), saving: t('pantry', 'Saving …'), + saved: t('pantry', 'Saved.'), + imagesSection: t('pantry', 'Images'), + imagesHint: t( + 'pantry', + 'Pick the base folder where Pantry will store uploaded images for this house. Shopping list item images go into a "Shopping list items" subfolder inside it, created automatically.', + ), + folderLabel: t('pantry', 'Upload folder'), + browse: t('pantry', 'Browse …'), + pickerTitle: t('pantry', 'Pick an upload folder'), membersSection: t('pantry', 'Members'), addMember: t('pantry', 'Add member'), removeMember: t('pantry', 'Remove member'), diff --git a/src/components/PantrySettingsDialog.vue b/src/components/PantrySettingsDialog.vue index 926e81e..51c7201 100644 --- a/src/components/PantrySettingsDialog.vue +++ b/src/components/PantrySettingsDialog.vue @@ -39,7 +39,7 @@ import { getFilePickerBuilder } from '@nextcloud/dialogs' import FolderIcon from '@icons/Folder.vue' import { getImageFolder, setImageFolder } from '@/api/prefs' -const props = defineProps<{ open: boolean }>() +const props = defineProps<{ open: boolean; houseId: number | null }>() const emit = defineEmits<{ 'update:open': [value: boolean] }>() const folder = ref('/Pantry') @@ -47,8 +47,9 @@ const saving = ref(false) const saved = ref(false) async function loadFolder() { + if (props.houseId === null) return try { - folder.value = await getImageFolder() + folder.value = await getImageFolder(props.houseId) } catch { // Keep default. } @@ -87,11 +88,11 @@ async function browseFolder() { async function save() { const value = folder.value.trim() - if (!value) return + if (!value || props.houseId === null) return saving.value = true saved.value = false try { - folder.value = await setImageFolder(value) + folder.value = await setImageFolder(props.houseId, value) saved.value = true } finally { saving.value = false @@ -99,7 +100,7 @@ async function save() { } const strings = { - title: t('pantry', 'Pantry settings'), + title: t('pantry', 'Account settings'), imagesSection: t('pantry', 'Images'), imagesHint: t( 'pantry', diff --git a/src/views/SideNavigation.vue b/src/views/SideNavigation.vue index 88936f3..0e7eca1 100644 --- a/src/views/SideNavigation.vue +++ b/src/views/SideNavigation.vue @@ -53,7 +53,11 @@ - + @@ -106,7 +110,7 @@ - +