Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion cypress/e2e/api/SessionApi.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ describe('The session Api', function() {

it('returns connection', function() {
cy.openFileConnection({ fileId }).then(({ connection }) => {
cy.wrap(connection).its('documentId').should('be.greaterThan', 0)
cy.wrap(connection)
.its('documentId')
.then((id) => Number.parseInt(id))
.should('be.greaterThan', 1_000_000) // snowflake ids have more than 20 bit.
cy.closeConnection(connection)
})
})
Expand Down
5 changes: 4 additions & 1 deletion cypress/e2e/api/UsersApi.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ describe('The user mention API', function() {
})

it('has a valid connection', function() {
cy.get('@connection').its('documentId').should('be.greaterThan', 0)
cy.get('@connection')
.its('documentId')
.then((id) => Number.parseInt(id))
.should('be.greaterThan', 1_000_000) // snowflake ids have more than 20 bit.
cy.closeConnection(this.connection)
})

Expand Down
4 changes: 3 additions & 1 deletion lib/Command/ResetDocument.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
if ($all) {
$fileIds = [];
foreach ($this->documentService->getAll() as $document) {
$fileIds[] = $document->getId();
if ($document->getContextType() === 'file') {
$fileIds[] = $document->getContextId();
}
}
} else {
$fileIds = [$fileId];
Expand Down
80 changes: 55 additions & 25 deletions lib/Controller/AttachmentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
namespace OCA\Text\Controller;

use Exception;
use OCA\Text\Context\ContextManager;
use OCA\Text\Context\IContext;
use OCA\Text\Db\DocumentMapper;
use OCA\Text\Exception\InvalidSessionException;
use OCA\Text\Exception\UploadException;
use OCA\Text\Middleware\Attribute\RequireDocumentSession;
use OCA\Text\Middleware\Attribute\RequireDocumentSessionOrUserOrShareToken;
use OCA\Text\Service\AttachmentService;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
Expand All @@ -25,6 +29,7 @@
use OCP\Constants;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\InvalidPathException;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use OCP\IL10N;
use OCP\IRequest;
Expand Down Expand Up @@ -70,22 +75,24 @@ public function __construct(
private IMimeTypeDetector $mimeTypeDetector,
private AttachmentService $attachmentService,
private ShareManager $shareManager,
private DocumentMapper $documentMapper,
private ContextManager $contextManager,
) {
parent::__construct($appName, $request);
}

#[NoAdminRequired]
#[PublicPage]
#[RequireDocumentSessionOrUserOrShareToken]
public function getAttachmentList(string $shareToken = ''): DataResponse {
$documentId = $this->getDocumentId();
public function getAttachmentList(int $fileId = 0, string $shareToken = ''): DataResponse {
$auth = $this->getAuth($shareToken, false);
$context = $this->getContext($auth, $fileId);
try {
$session = $this->getSession();
} catch (InvalidSessionException) {
$session = null;
}
$auth = $this->getAuth($shareToken, false);
$attachments = $this->attachmentService->getAttachmentList($documentId, $auth, $session);
$attachments = $this->attachmentService->getAttachmentList($context, $auth, $session);
return new DataResponse($attachments);
}

Expand All @@ -94,9 +101,9 @@ public function getAttachmentList(string $shareToken = ''): DataResponse {
#[RequireDocumentSession]
public function insertAttachmentFile(string $filePath): DataResponse {
$user = $this->getUser();

$context = $this->getContext($user);
try {
$insertResult = $this->attachmentService->insertAttachmentFile($this->getSession()->getDocumentId(), $filePath, $user);
$insertResult = $this->attachmentService->insertAttachmentFile($context, $filePath, $user);
if (isset($insertResult['error'])) {
return new DataResponse($insertResult, Http::STATUS_BAD_REQUEST);
} else {
Expand All @@ -112,8 +119,6 @@ public function insertAttachmentFile(string $filePath): DataResponse {
#[PublicPage]
#[RequireDocumentSession]
public function uploadAttachment(string $token = ''): DataResponse {
$documentId = $this->getSession()->getDocumentId();

try {
$file = $this->getUploadedFile('file');
if (isset($file['tmp_name'], $file['name'], $file['type'])) {
Expand All @@ -123,7 +128,8 @@ public function uploadAttachment(string $token = ''): DataResponse {
}
$newFileName = $file['name'];
$auth = $this->getAuth($token);
$uploadResult = $this->attachmentService->uploadAttachment($documentId, $newFileName, $newFileResource, $auth);
$context = $this->getContext($auth);
$uploadResult = $this->attachmentService->uploadAttachment($context, $newFileName, $newFileResource, $auth);
if (isset($uploadResult['error'])) {
return new DataResponse($uploadResult, Http::STATUS_BAD_REQUEST);
} else {
Expand All @@ -145,11 +151,11 @@ public function uploadAttachment(string $token = ''): DataResponse {
#[PublicPage]
#[RequireDocumentSession]
public function createAttachment(): DataResponse {
$documentId = $this->getSession()->getDocumentId();
$user = $this->getUser();
$context = $this->getContext($user);
try {
$user = $this->getUser();
$newFileName = $this->request->getParam('fileName', 'text.md');
$createResult = $this->attachmentService->createAttachmentFile($documentId, $newFileName, $user);
$createResult = $this->attachmentService->createAttachmentFile($context, $newFileName, $user);
if (isset($createResult['error'])) {
return new DataResponse($createResult, Http::STATUS_BAD_REQUEST);
} else {
Expand Down Expand Up @@ -200,13 +206,16 @@ private function getUploadedFile(string $key): array {
#[PublicPage]
#[NoCSRFRequired]
#[RequireDocumentSessionOrUserOrShareToken]
public function getImageFile(string $imageFileName, string $shareToken = '',
int $preferRawImage = 0): DataResponse|DataDownloadResponse {
$documentId = $this->getDocumentId();

public function getImageFile(
string $imageFileName,
string $shareToken = '',
int $preferRawImage = 0,
int $fileId = 0,
): DataResponse|DataDownloadResponse {
try {
$auth = $this->getAuth($shareToken, false);
$imageFile = $this->attachmentService->getImageFile($documentId, $imageFileName, $auth, $preferRawImage === 1);
$context = $this->getContext($auth, $fileId);
$imageFile = $this->attachmentService->getImageFile($context, $imageFileName, $preferRawImage === 1);
if ($imageFile !== null) {
$response = new DataDownloadResponse(
$imageFile->getContent(),
Expand Down Expand Up @@ -235,12 +244,15 @@ public function getImageFile(string $imageFileName, string $shareToken = '',
#[PublicPage]
#[NoCSRFRequired]
#[RequireDocumentSessionOrUserOrShareToken]
public function getMediaFile(string $mediaFileName, string $shareToken = ''): DataResponse|DataDownloadResponse {
$documentId = $this->getDocumentId();

public function getMediaFile(
string $mediaFileName,
string $shareToken = '',
int $fileId = 0,
): DataResponse|DataDownloadResponse {
try {
$auth = $this->getAuth($shareToken, false);
$mediaFile = $this->attachmentService->getMediaFile($documentId, $mediaFileName, $auth);
$context = $this->getContext($auth, $fileId);
$mediaFile = $this->attachmentService->getMediaFile($context, $mediaFileName);
return $mediaFile !== null
? new DataDownloadResponse(
$mediaFile->getContent(),
Expand All @@ -262,12 +274,11 @@ public function getMediaFile(string $mediaFileName, string $shareToken = ''): Da
#[PublicPage]
#[NoCSRFRequired]
#[RequireDocumentSessionOrUserOrShareToken]
public function getMediaFilePreview(string $mediaFileName, string $shareToken = '') {
$documentId = $this->getDocumentId();

public function getMediaFilePreview(string $mediaFileName, string $shareToken = '', int $fileId = 0) {
try {
$auth = $this->getAuth($shareToken, false);
$preview = $this->attachmentService->getMediaFilePreview($documentId, $mediaFileName, $auth);
$context = $this->getContext($auth, $fileId);
$preview = $this->attachmentService->getMediaFilePreview($context, $mediaFileName);
if ($preview === null) {
return new DataResponse('', Http::STATUS_NOT_FOUND);
}
Expand All @@ -286,6 +297,25 @@ public function getMediaFilePreview(string $mediaFileName, string $shareToken =
return new DataResponse('', Http::STATUS_NOT_FOUND);
}

private function getContext(IShare|IUser $auth, int $fileId = 0): IContext {
try {
$documentId = $this->getDocumentId();
} catch (InvalidSessionException $e) {
// Fallback for scenarios without a session. (MarkdownContentEditor with fileId)
return $this->contextManager->getContext('file', $fileId, $auth);
}
try {
$document = $this->documentMapper->find($documentId);
} catch (DoesNotExistException $e) {
throw new NotFoundException('Text file for document '
. $documentId
. ' was not found.', 0, $e);
}
$type = $document->getContextType();
$id = $document->getContextId();
return $this->contextManager->getContext($type, $id, $auth);
}

private function getAuth(string $shareToken, bool $updatePermissionRequired = true): IShare|IUser {
if ($shareToken !== '') {
try {
Expand Down
4 changes: 2 additions & 2 deletions lib/Controller/ISessionAwareController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
interface ISessionAwareController {
public function getSession(): Session;
public function setSession(Session $session): void;
public function getDocumentId(): int;
public function setDocumentId(int $documentId): void;
public function getDocumentId(): string;
public function setDocumentId(string $documentId): void;
public function getDocument(): Document;
public function setDocument(Document $document): void;
public function getUser(): IUser;
Expand Down
2 changes: 1 addition & 1 deletion lib/Controller/PublicSessionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public function create(string $token, ?string $filePath = null, ?string $baseVer

#[NoAdminRequired]
#[PublicPage]
public function close(int $documentId, int $sessionId, string $sessionToken): DataResponse {
public function close(string $documentId, int $sessionId, string $sessionToken): DataResponse {
return $this->apiService->close($documentId, $sessionId, $sessionToken, $this->getShare());
}

Expand Down
4 changes: 2 additions & 2 deletions lib/Controller/SessionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public function create(string $type, int $id, ?string $baseVersionEtag = null):

#[NoAdminRequired]
#[PublicPage]
public function close(int $documentId, int $sessionId, string $sessionToken): DataResponse {
public function close(string $documentId, int $sessionId, string $sessionToken): DataResponse {
// We also want this to work with a session that has already been closed.
// So we cannot rely on RequireDocumentSession to retrieve the user.
$user = $this->userSession->getUser();
Expand Down Expand Up @@ -127,7 +127,7 @@ public function save(int $version, string $autosaveContent, string $documentStat
#[RequireDocumentSession]
#[UserRateLimit(limit: 5, period: 120)]
public function mention(string $mention): DataResponse {
if ($this->getSession()->isGuest() && !$this->sessionService->isUserInDocument($this->getDocument()->getId(), $mention)) {
if ($this->getSession()->isGuest() && !$this->sessionService->isUserInDocument($this->getDocumentId(), $mention)) {
return new DataResponse([], 403);
}

Expand Down
6 changes: 3 additions & 3 deletions lib/Controller/TSessionAwareController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@

trait TSessionAwareController {
private ?Session $textSession = null;
private ?int $documentId = null;
private ?string $documentId = null;
private ?Document $document = null;
private ?IUser $user = null;

public function setSession(?Session $session): void {
$this->textSession = $session;
}

public function setDocumentId(int $documentId): void {
public function setDocumentId(string $documentId): void {
$this->documentId = $documentId;
}

Expand All @@ -50,7 +50,7 @@ public function getSession(): Session {
/**
* @throws InvalidSessionException
*/
public function getDocumentId(): int {
public function getDocumentId(): string {
if ($this->documentId === null) {
throw new InvalidSessionException();
}
Expand Down
4 changes: 3 additions & 1 deletion lib/Cron/Cleanup.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ public function __construct(
protected function run($argument): void {
$this->logger->debug('Run cleanup job for text documents');
foreach ($this->documentService->getAllWithNoActiveSession() as $document) {
$this->attachmentService->cleanupAttachments($document->getId());
if ($document->getContextType() === 'file') {
$this->attachmentService->cleanupAttachments($document->getContextId());
}
}

$this->logger->debug('Run cleanup job for text sessions');
Expand Down
28 changes: 17 additions & 11 deletions lib/Db/Document.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Text\Db;

use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\SnowflakeAwareEntity;
use OCP\DB\Types;

/**
* @method getId(): int
* @method getCurrentVersion(): int
* @method setCurrentVersion(int $version): void
* @method getLastSavedVersion(): int
Expand All @@ -30,7 +32,10 @@
* @method getContextId(): int
* @method setContextId(int $contextId): void
*/
class Document extends Entity implements \JsonSerializable {
class Document extends SnowflakeAwareEntity implements \JsonSerializable {
/** @var ?string $id
* @psalm-suppress NonInvariantDocblockPropertyType
*/
public $id = null;
// TODO: Remove obsolete field `currentVersion`
protected int $currentVersion = 0;
Expand All @@ -44,18 +49,19 @@ class Document extends Entity implements \JsonSerializable {
protected int $contextId = 0;

public function __construct() {
$this->addType('currentVersion', 'integer');
$this->addType('lastSavedVersion', 'integer');
$this->addType('lastSavedVersionTime', 'integer');
$this->addType('initialVersion', 'integer');
$this->addType('checksum', 'string');
$this->addType('contextType', 'string');
$this->addType('contextId', 'integer');
$this->addType('id', Types::STRING);
$this->addType('currentVersion', Types::INTEGER);
$this->addType('lastSavedVersion', Types::INTEGER);
$this->addType('lastSavedVersionTime', Types::INTEGER);
$this->addType('initialVersion', Types::INTEGER);
$this->addType('checksum', Types::STRING);
$this->addType('contextType', Types::STRING);
$this->addType('contextId', Types::INTEGER);
}

public function jsonSerialize(): array {
return [
'id' => $this->getId(),
'id' => $this->id,
'lastSavedVersion' => $this->lastSavedVersion,
'lastSavedVersionTime' => $this->lastSavedVersionTime,
'baseVersionEtag' => $this->baseVersionEtag,
Expand Down
2 changes: 1 addition & 1 deletion lib/Db/DocumentMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public function __construct(IDBConnection $db) {
* @return Document
* @throws DoesNotExistException
*/
public function find(int $documentId): Document {
public function find(string $documentId): Document {

/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
Expand Down
11 changes: 6 additions & 5 deletions lib/Db/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use JsonSerializable;
use OCA\Text\Exception\InvalidSessionException;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;

/**
* @method void setUserId(?string $userId)
Expand All @@ -23,8 +24,8 @@
* @method void setLastAwarenessMessage(string $message)
* @method int getLastContact()
* @method void setLastContact(int $getTime)
* @method int getDocumentId()
* @method void setDocumentId(int $documentId)
* @method string getDocumentId()
* @method void setDocumentId(string $documentId)
*/
class Session extends Entity implements JsonSerializable {
public $id;
Expand All @@ -34,11 +35,11 @@ class Session extends Entity implements JsonSerializable {
protected ?string $guestName = null;
protected ?string $lastAwarenessMessage = '';
protected int $lastContact = 0;
protected int $documentId = 0;
protected string $documentId = '';

public function __construct() {
$this->addType('documentId', 'integer');
$this->addType('lastContact', 'integer');
$this->addType('documentId', Types::STRING);
$this->addType('lastContact', Types::INTEGER);
}

public function isGuest(): bool {
Expand Down
Loading
Loading