Skip to content

Commit 1a14345

Browse files
committed
init
1 parent d8bbada commit 1a14345

16 files changed

Lines changed: 3767 additions & 441 deletions

File tree

appinfo/routes.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,7 @@
2121
['name' => 'Whiteboard#show', 'url' => '{fileId}', 'verb' => 'GET'],
2222
/** @see SettingsController::update() */
2323
['name' => 'Settings#update', 'url' => 'settings', 'verb' => 'POST'],
24+
/** @see WhiteboardController::sync() */
25+
['name' => 'whiteboard#sync', 'url' => '{fileId}/sync', 'verb' => 'PUT']
2426
]
2527
];

bun.lock

Lines changed: 2357 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/Controller/WhiteboardController.php

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,13 @@
2424
use OCP\AppFramework\Http\Attribute\PublicPage;
2525
use OCP\AppFramework\Http\DataResponse;
2626
use OCP\IRequest;
27-
27+
use Psr\Log\LoggerInterface;
2828
/**
2929
* @psalm-suppress UndefinedClass
3030
* @psalm-suppress UndefinedDocblockClass
3131
*/
32-
final class WhiteboardController extends ApiController {
32+
final class WhiteboardController extends ApiController
33+
{
3334
public function __construct(
3435
$appName,
3536
IRequest $request,
@@ -39,14 +40,16 @@ public function __construct(
3940
private WhiteboardContentService $contentService,
4041
private ExceptionService $exceptionService,
4142
private ConfigService $configService,
43+
private LoggerInterface $logger,
4244
) {
4345
parent::__construct($appName, $request);
4446
}
4547

4648
#[NoAdminRequired]
4749
#[NoCSRFRequired]
4850
#[PublicPage]
49-
public function show(int $fileId): DataResponse {
51+
public function show(int $fileId): DataResponse
52+
{
5053
try {
5154
$jwt = $this->getJwtFromRequest();
5255

@@ -67,7 +70,8 @@ public function show(int $fileId): DataResponse {
6770
#[NoAdminRequired]
6871
#[NoCSRFRequired]
6972
#[PublicPage]
70-
public function update(int $fileId, array $data): DataResponse {
73+
public function update(int $fileId, array $data): DataResponse
74+
{
7175
try {
7276
$this->validateBackendSharedToken($fileId);
7377

@@ -85,29 +89,33 @@ public function update(int $fileId, array $data): DataResponse {
8589
}
8690
}
8791

88-
private function getJwtFromRequest(): string {
92+
private function getJwtFromRequest(): string
93+
{
8994
$authHeader = $this->request->getHeader('Authorization');
9095
if (sscanf($authHeader, 'Bearer %s', $jwt) !== 1) {
9196
throw new UnauthorizedException();
9297
}
93-
return (string)$jwt;
98+
return (string) $jwt;
9499
}
95100

96-
private function getUserIdFromRequest(): string {
101+
private function getUserIdFromRequest(): string
102+
{
97103
return $this->request->getHeader('X-Whiteboard-User');
98104
}
99105

100-
private function validateBackendSharedToken(int $fileId): void {
106+
private function validateBackendSharedToken(int $fileId): void
107+
{
101108
$backendSharedToken = $this->request->getHeader('X-Whiteboard-Auth');
102109
if (!$backendSharedToken || !$this->verifySharedToken($backendSharedToken, $fileId)) {
103110
throw new InvalidUserException('Invalid backend shared token');
104111
}
105112
}
106113

107-
private function verifySharedToken(string $token, int $fileId): bool {
114+
private function verifySharedToken(string $token, int $fileId): bool
115+
{
108116
[$roomId, $timestamp, $signature] = explode(':', $token);
109117

110-
if ($roomId !== (string)$fileId) {
118+
if ($roomId !== (string) $fileId) {
111119
return false;
112120
}
113121

@@ -117,4 +125,32 @@ private function verifySharedToken(string $token, int $fileId): bool {
117125

118126
return hash_equals($expectedSignature, $signature);
119127
}
128+
129+
130+
/**
131+
* Sync whiteboard data
132+
*/
133+
#[NoAdminRequired]
134+
#[NoCSRFRequired]
135+
#[PublicPage]
136+
public function sync(int $fileId, array $data): DataResponse
137+
{
138+
try {
139+
$jwt = $this->getJwtFromRequest();
140+
141+
$userId = $this->jwtService->getUserIdFromJWT($jwt);
142+
143+
$user = $this->getUserFromIdServiceFactory->create($userId)->getUser();
144+
145+
$file = $this->getFileServiceFactory->create($user, $fileId)->getFile();
146+
147+
$this->contentService->updateContent($file, $data);
148+
149+
return new DataResponse(['status' => 'success', 'version' => $data['version']]);
150+
} catch (Exception $e) {
151+
$this->logger->error('Error syncing whiteboard data: ' . $e->getMessage());
152+
153+
return $this->exceptionService->handleException($e);
154+
}
155+
}
120156
}

lib/Service/ExceptionService.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@
1616
use OCP\AppFramework\Http\DataResponse;
1717
use OCP\Files\NotFoundException;
1818
use OCP\Files\NotPermittedException;
19+
use Psr\Log\LoggerInterface;
1920

2021
/**
2122
* @psalm-suppress UndefinedClass
2223
*/
2324
final class ExceptionService {
25+
public function __construct(
26+
private LoggerInterface $logger,
27+
) {
28+
}
29+
2430
public function handleException(Exception $e): DataResponse {
2531
$statusCode = $this->getStatusCode($e);
2632
$message = $this->getMessage($e);

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@
3030
"@nextcloud/event-bus": "^3.3.2",
3131
"@nextcloud/files": "^3.10.2",
3232
"@nextcloud/initial-state": "^2.2.0",
33-
"@nextcloud/l10n": "^3.1.0",
33+
"@nextcloud/l10n": "^3.2.0",
3434
"@nextcloud/router": "^3.0.1",
3535
"@nextcloud/sharing": "^0.2.4",
3636
"@nextcloud/vue": "^8.23.1",
3737
"@socket.io/redis-streams-adapter": "^0.2.2",
3838
"axios": "^1.8.1",
39+
"dexie": "^4.0.11",
3940
"dotenv": "^16.4.7",
4041
"express": "^5.0.1",
4142
"jsonwebtoken": "^9.0.2",
@@ -58,11 +59,11 @@
5859
"@nextcloud/stylelint-config": "^3.0.1",
5960
"@nextcloud/vite-config": "^1.5.2",
6061
"@playwright/test": "^1.50.1",
61-
"@types/react-dom": "^18.3.1",
62+
"@types/react-dom": "^18.3.5",
6263
"@vitejs/plugin-react": "^4.3.4",
6364
"@vue/tsconfig": "^0.5.1",
6465
"nodemon": "^3.1.9",
65-
"prettier": "^3.5.2",
66+
"prettier": "^3.5.3",
6667
"stylelint-config-css-modules": "^4.4.0",
6768
"typescript": "^5.8.2",
6869
"typescript-plugin-css-modules": "^5.1.0",

src/App.scss

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,41 @@
7171
position: relative;
7272
height: 100%;
7373
width: 100%;
74+
75+
.App-container {
76+
width: 100%;
77+
height: 100%;
78+
position: relative;
79+
}
80+
81+
.App-loading,
82+
.App-error {
83+
display: flex;
84+
flex-direction: column;
85+
align-items: center;
86+
justify-content: center;
87+
height: 100%;
88+
width: 100%;
89+
color: var(--color-main-text);
90+
91+
.retry-button {
92+
margin-top: 16px;
93+
padding: 8px 16px;
94+
background-color: var(--color-primary);
95+
color: var(--color-primary-text);
96+
border: none;
97+
border-radius: 4px;
98+
cursor: pointer;
99+
100+
&:hover {
101+
background-color: var(--color-primary-element-hover);
102+
}
103+
}
104+
}
105+
106+
.App-error {
107+
color: var(--color-error);
108+
}
74109
}
75110

76111
.excalidraw-wrapper {

0 commit comments

Comments
 (0)