mirror of
https://github.com/chenasraf/nextcloud-pantry.git
synced 2026-05-17 17:28:01 +00:00
fix: proxy images for shared access
This commit is contained in:
@@ -419,7 +419,7 @@ final class ChecklistController extends OCSController {
|
||||
$original = (string)($data['name'] ?? 'image.jpg');
|
||||
$fileId = $this->images->uploadForUser($uid, $houseId, $original, $bytes);
|
||||
|
||||
$updated = $this->lists->updateItem($itemId, ['imageFileId' => $fileId]);
|
||||
$updated = $this->lists->updateItem($itemId, ['imageFileId' => $fileId, 'imageUploadedBy' => $uid]);
|
||||
return new DataResponse($updated->jsonSerialize());
|
||||
});
|
||||
}
|
||||
@@ -447,7 +447,7 @@ final class ChecklistController extends OCSController {
|
||||
if ($item->getListId() !== $listId) {
|
||||
throw new NotFoundException('Item does not belong to this list');
|
||||
}
|
||||
$updated = $this->lists->updateItem($itemId, ['imageFileId' => null]);
|
||||
$updated = $this->lists->updateItem($itemId, ['imageFileId' => null, 'imageUploadedBy' => null]);
|
||||
return new DataResponse($updated->jsonSerialize());
|
||||
});
|
||||
}
|
||||
|
||||
133
lib/Controller/ImageController.php
Normal file
133
lib/Controller/ImageController.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace OCA\Pantry\Controller;
|
||||
|
||||
use OCA\Pantry\Db\PhotoMapper;
|
||||
use OCA\Pantry\Exception\ForbiddenException;
|
||||
use OCA\Pantry\Exception\NotFoundException;
|
||||
use OCA\Pantry\Service\HouseAuthService;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\ApiRoute;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\Http\FileDisplayResponse;
|
||||
use OCP\AppFramework\OCSController;
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IPreview;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserSession;
|
||||
|
||||
/**
|
||||
* Serve images from the owner's storage so any house member can view them,
|
||||
* regardless of Nextcloud sharing settings.
|
||||
*/
|
||||
final class ImageController extends OCSController {
|
||||
use TranslatesDomainExceptions;
|
||||
|
||||
public function __construct(
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
private HouseAuthService $auth,
|
||||
private PhotoMapper $photoMapper,
|
||||
private IRootFolder $rootFolder,
|
||||
private IPreview $previewManager,
|
||||
private IUserSession $userSession,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve a photo board image preview
|
||||
*
|
||||
* @param int $houseId House id.
|
||||
* @param int $photoId Photo record id.
|
||||
* @param int $size Preview size (longest edge).
|
||||
*
|
||||
* @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_NOT_FOUND, array{error: string}, array{}>
|
||||
*
|
||||
* 200: Preview returned
|
||||
* 404: Image not found
|
||||
*/
|
||||
#[ApiRoute(verb: 'GET', url: '/api/houses/{houseId}/photos/{photoId}/preview')]
|
||||
#[NoAdminRequired]
|
||||
#[NoCSRFRequired]
|
||||
public function photoPreview(int $houseId, int $photoId, int $size = 300): FileDisplayResponse|DataResponse {
|
||||
return $this->runAction(function () use ($houseId, $photoId, $size) {
|
||||
$this->auth->requireMember($houseId, $this->requireUid());
|
||||
|
||||
$photo = $this->photoMapper->findById($photoId);
|
||||
if ($photo->getHouseId() !== $houseId) {
|
||||
throw new NotFoundException('Photo does not belong to this house');
|
||||
}
|
||||
|
||||
return $this->servePreview($photo->getUploadedBy(), $photo->getFileId(), $size);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve a checklist item image preview
|
||||
*
|
||||
* @param int $houseId House id.
|
||||
* @param int $fileId Nextcloud file id.
|
||||
* @param string $owner File owner uid.
|
||||
* @param int $size Preview size (longest edge).
|
||||
*
|
||||
* @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_NOT_FOUND, array{error: string}, array{}>
|
||||
*
|
||||
* 200: Preview returned
|
||||
* 404: Image not found
|
||||
*/
|
||||
#[ApiRoute(verb: 'GET', url: '/api/houses/{houseId}/image-preview')]
|
||||
#[NoAdminRequired]
|
||||
#[NoCSRFRequired]
|
||||
public function itemImagePreview(int $houseId, int $fileId, string $owner, int $size = 300): FileDisplayResponse|DataResponse {
|
||||
return $this->runAction(function () use ($houseId, $fileId, $owner, $size) {
|
||||
$this->auth->requireMember($houseId, $this->requireUid());
|
||||
|
||||
return $this->servePreview($owner, $fileId, $size);
|
||||
});
|
||||
}
|
||||
|
||||
private function servePreview(string $ownerUid, int $fileId, int $size): FileDisplayResponse|DataResponse {
|
||||
$size = max(16, min($size, 2048));
|
||||
|
||||
$userFolder = $this->rootFolder->getUserFolder($ownerUid);
|
||||
$nodes = $userFolder->getById($fileId);
|
||||
if (empty($nodes)) {
|
||||
return new DataResponse(['error' => 'File not found'], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
$file = $nodes[0];
|
||||
if (!$file instanceof File) {
|
||||
return new DataResponse(['error' => 'Not a file'], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($this->previewManager->isAvailable($file)) {
|
||||
$preview = $this->previewManager->getPreview($file, $size, $size);
|
||||
$resp = new FileDisplayResponse($preview, Http::STATUS_OK, [
|
||||
'Content-Type' => $preview->getMimeType(),
|
||||
]);
|
||||
} else {
|
||||
$resp = new FileDisplayResponse($file, Http::STATUS_OK, [
|
||||
'Content-Type' => $file->getMimeType(),
|
||||
]);
|
||||
}
|
||||
$resp->cacheFor(3600);
|
||||
return $resp;
|
||||
}
|
||||
|
||||
private function requireUid(): string {
|
||||
$user = $this->userSession->getUser();
|
||||
if ($user === null) {
|
||||
throw new ForbiddenException('Not authenticated');
|
||||
}
|
||||
return $user->getUID();
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,8 @@ use OCP\AppFramework\Db\Entity;
|
||||
* @method void setNextDueAt(?int $nextDueAt)
|
||||
* @method int|null getImageFileId()
|
||||
* @method void setImageFileId(?int $imageFileId)
|
||||
* @method string|null getImageUploadedBy()
|
||||
* @method void setImageUploadedBy(?string $imageUploadedBy)
|
||||
* @method int getSortOrder()
|
||||
* @method void setSortOrder(int $sortOrder)
|
||||
* @method int getCreatedAt()
|
||||
@@ -51,6 +53,7 @@ class ChecklistItem extends Entity implements \JsonSerializable {
|
||||
protected bool $repeatFromCompletion = false;
|
||||
protected ?int $nextDueAt = null;
|
||||
protected ?int $imageFileId = null;
|
||||
protected ?string $imageUploadedBy = null;
|
||||
protected int $sortOrder = 0;
|
||||
protected int $createdAt = 0;
|
||||
protected int $updatedAt = 0;
|
||||
@@ -88,6 +91,7 @@ class ChecklistItem extends Entity implements \JsonSerializable {
|
||||
'repeatFromCompletion' => $this->repeatFromCompletion,
|
||||
'nextDueAt' => $this->nextDueAt,
|
||||
'imageFileId' => $this->imageFileId,
|
||||
'imageUploadedBy' => $this->imageUploadedBy,
|
||||
'sortOrder' => $this->sortOrder,
|
||||
'createdAt' => $this->createdAt,
|
||||
'updatedAt' => $this->updatedAt,
|
||||
|
||||
@@ -220,6 +220,10 @@ class Version1Date20260405000000 extends SimpleMigrationStep {
|
||||
'notnull' => false,
|
||||
'length' => 20,
|
||||
]);
|
||||
$table->addColumn('image_uploaded_by', Types::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 64,
|
||||
]);
|
||||
$table->addColumn('sort_order', Types::INTEGER, [
|
||||
'notnull' => true,
|
||||
'default' => 0,
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace OCA\Pantry;
|
||||
* repeatFromCompletion: bool,
|
||||
* nextDueAt: int|null,
|
||||
* imageFileId: int|null,
|
||||
* imageUploadedBy: string|null,
|
||||
* sortOrder: int,
|
||||
* createdAt: int,
|
||||
* updatedAt: int,
|
||||
|
||||
@@ -193,6 +193,10 @@ class ChecklistService {
|
||||
if (array_key_exists('imageFileId', $patch)) {
|
||||
$item->setImageFileId($this->intOrNull($patch['imageFileId']));
|
||||
}
|
||||
if (array_key_exists('imageUploadedBy', $patch)) {
|
||||
$v = $patch['imageUploadedBy'];
|
||||
$item->setImageUploadedBy(is_string($v) && $v !== '' ? $v : null);
|
||||
}
|
||||
// If already done and rrule or mode changed, recompute next due.
|
||||
if ($item->getDone() && $item->getRrule() !== null
|
||||
&& (array_key_exists('rrule', $patch) || array_key_exists('repeatFromCompletion', $patch))) {
|
||||
|
||||
Reference in New Issue
Block a user