From 6d7a890d5c337c2dc900b23f26432d4ef0df15ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Witold=20Wis=CC=81niewski?= Date: Tue, 13 Jan 2026 08:45:46 +0100 Subject: [PATCH 1/3] Added signed urls --- src/Dto/GenerateSignedScreenUrlsDto.php | 51 +++++++++++++ .../Controllers/Swagger/WebinarAPISwagger.php | 73 +++++++++++++++++++ src/Http/Controllers/WebinarAPIController.php | 10 +++ .../GenerateSignedScreenUrlsRequest.php | 19 +++++ .../Contracts/WebinarServiceContract.php | 2 + src/Services/WebinarService.php | 27 +++++++ src/routes.php | 1 + tests/APIs/WebinarApiTest.php | 53 ++++++++++++++ 8 files changed, 236 insertions(+) create mode 100644 src/Dto/GenerateSignedScreenUrlsDto.php create mode 100644 src/Http/Requests/GenerateSignedScreenUrlsRequest.php diff --git a/src/Dto/GenerateSignedScreenUrlsDto.php b/src/Dto/GenerateSignedScreenUrlsDto.php new file mode 100644 index 0000000..3359d20 --- /dev/null +++ b/src/Dto/GenerateSignedScreenUrlsDto.php @@ -0,0 +1,51 @@ +webinarId; + } + + public function setWebinarId(int $webinarId): void + { + $this->webinarId = $webinarId; + } + + public function getExecutedAt(): string + { + return $this->executedAt; + } + + public function setExecutedAt(string $executedAt): void + { + $this->executedAt = $executedAt; + } + + public function getFiles(): array + { + return $this->files; + } + + public function setFiles(array $files): void + { + $this->files = $files; + } + + public function getUserId(): int + { + return $this->userId; + } + + public function setUserId(int $userId): void + { + $this->userId = $userId; + } +} diff --git a/src/Http/Controllers/Swagger/WebinarAPISwagger.php b/src/Http/Controllers/Swagger/WebinarAPISwagger.php index b32eb30..bf38dde 100644 --- a/src/Http/Controllers/Swagger/WebinarAPISwagger.php +++ b/src/Http/Controllers/Swagger/WebinarAPISwagger.php @@ -1,6 +1,7 @@ except(['limit', 'skip', 'order', 'order_by']); $orderDto = OrderDto::instantiateFromRequest($listWebinarsRequest); @@ -94,4 +97,11 @@ public function stopLiveStream(int $id): void */ $this->webinarServiceContract->setStatusInLiveStreamInYt($id, 'complete'); } + + public function generateSignedScreenUrls(GenerateSignedScreenUrlsRequest $request): JsonResponse + { + $data = $this->webinarServiceContract->generateSignedScreenUrls(new GenerateSignedScreenUrlsDto($request->validated())); + + return $this->sendResponse($data, __('Urls generated successfully')); + } } diff --git a/src/Http/Requests/GenerateSignedScreenUrlsRequest.php b/src/Http/Requests/GenerateSignedScreenUrlsRequest.php new file mode 100644 index 0000000..2a52260 --- /dev/null +++ b/src/Http/Requests/GenerateSignedScreenUrlsRequest.php @@ -0,0 +1,19 @@ + ['required', 'integer'], + 'user_id' => ['required', 'integer'], + 'executed_at' => ['required'], + 'files' => ['array', 'min:1'], + 'files.*.filename' => ['required', 'string'], + ]; + } +} diff --git a/src/Services/Contracts/WebinarServiceContract.php b/src/Services/Contracts/WebinarServiceContract.php index c15320c..19b13ba 100644 --- a/src/Services/Contracts/WebinarServiceContract.php +++ b/src/Services/Contracts/WebinarServiceContract.php @@ -5,6 +5,7 @@ use Carbon\Carbon; use EscolaLms\Auth\Models\User; use EscolaLms\Core\Dtos\OrderDto; +use EscolaLms\Webinar\Dto\GenerateSignedScreenUrlsDto; use EscolaLms\Webinar\Dto\WebinarDto; use EscolaLms\Webinar\Models\Webinar; use Illuminate\Database\Eloquent\Builder; @@ -67,4 +68,5 @@ public function reminderAboutWebinar(string $reminderStatus): void; public function getWebinarEndDate(Webinar $webinar): ?Carbon; public function setStatusInLiveStreamInYt(int $webinarId, string $broadcastStatus): void; public function setReminderStatus(Webinar $webinar, string $status): void; + public function generateSignedScreenUrls(GenerateSignedScreenUrlsDto $dto): array; } diff --git a/src/Services/WebinarService.php b/src/Services/WebinarService.php index a58f3b2..2031674 100644 --- a/src/Services/WebinarService.php +++ b/src/Services/WebinarService.php @@ -9,6 +9,7 @@ use EscolaLms\Jitsi\Helpers\StringHelper; use EscolaLms\Jitsi\Services\Contracts\JitsiServiceContract; use EscolaLms\Webinar\Dto\FilterListDto; +use EscolaLms\Webinar\Dto\GenerateSignedScreenUrlsDto; use EscolaLms\Webinar\Dto\WebinarDto; use EscolaLms\Webinar\Enum\ConstantEnum; use EscolaLms\Webinar\Events\ReminderAboutTerm; @@ -24,6 +25,7 @@ use EscolaLms\Youtube\Services\Contracts\YoutubeServiceContract; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Storage; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class WebinarService implements WebinarServiceContract @@ -361,4 +363,29 @@ public function getWebinarEndDate(Webinar $webinar): ?Carbon return $webinar->active_to ? Carbon::make($webinar->active_to) : null; } + + public function generateSignedScreenUrls(GenerateSignedScreenUrlsDto $dto): array + { + if (config('filesystems.default') !== 's3') { + abort(400, 'The file driver does not support this method.'); + } + + $term = Carbon::make($dto->getExecutedAt()); + $directory = sprintf( + '%s/%s/%s/%s/', + ConstantEnum::DIRECTORY, + $dto->getWebinarId(), + $term->getTimestamp(), + $dto->getUserId() + ); + + return array_map(function ($file) use ($directory) { + $filename = $file['filename']; + + return array_merge( + ['filename' => $filename], + Storage::temporaryUploadUrl($directory . $filename, now()->addMinutes(5)) + ); + }, $dto->getFiles()); + } } diff --git a/src/routes.php b/src/routes.php index 129c068..996dcf1 100644 --- a/src/routes.php +++ b/src/routes.php @@ -23,4 +23,5 @@ Route::group(['prefix' => 'api/webinars'], function () { Route::get('/', [WebinarAPIController::class, 'index']); Route::get('/{id}', [WebinarAPIController::class, 'show']); + Route::post('/signed-screen-urls', [WebinarAPIController::class, 'generateSignedScreenUrls']); }); diff --git a/tests/APIs/WebinarApiTest.php b/tests/APIs/WebinarApiTest.php index a606f9d..80b9885 100644 --- a/tests/APIs/WebinarApiTest.php +++ b/tests/APIs/WebinarApiTest.php @@ -16,6 +16,7 @@ use EscolaLms\Youtube\Services\Contracts\YoutubeServiceContract; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Facades\Storage; class WebinarApiTest extends TestCase { @@ -390,4 +391,56 @@ public function testWebinarListOwn(): void ->getJson('/api/admin/webinars') ->assertJsonCount(4, 'data'); } + + public function testGenerateSignedUrls(): void + { + config(['filesystems.default' => 's3']); + + Storage::shouldReceive('temporaryUploadUrl') + ->withArgs(function ($path, $expiration) { + return true; + }) + ->andReturnUsing(function ($path, $expiration) { + return [ + 'upload_url' => "https://example.com/{$path}", + ]; + }); + + $this->response = $this->json('POST', '/api/webinars/signed-screen-urls', [ + 'webinar_id' => 1, + 'user_id' => 1, + 'executed_at' => now()->format('Y-m-d H:i:s'), + 'files' => [ + [ + 'filename' => now()->format('Y-m-d H:i:s'), + ], + ], + ]) + ->assertOk() + ->assertJsonStructure([ + 'data' => [ + [ + 'filename', + 'upload_url', + ] + ] + ]); + } + + public function testGenerateSignedUrlsNotSupported(): void + { + config(['filesystems.default' => 'local']); + + $this->response = $this->json('POST', '/api/webinars/signed-screen-urls', [ + 'webinar_id' => 1, + 'user_id' => 1, + 'executed_at' => now()->format('Y-m-d H:i:s'), + 'files' => [ + [ + 'filename' => now()->format('Y-m-d H:i:s'), + ], + ], + ]) + ->assertStatus(400); + } } From e0e1daabd51d7ec4e5bd6c7d4750ee6775800c0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Witold=20Wis=CC=81niewski?= Date: Tue, 13 Jan 2026 09:09:36 +0100 Subject: [PATCH 2/3] fix --- src/Http/Controllers/WebinarAPIController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Http/Controllers/WebinarAPIController.php b/src/Http/Controllers/WebinarAPIController.php index 6d2b7ab..a304e13 100644 --- a/src/Http/Controllers/WebinarAPIController.php +++ b/src/Http/Controllers/WebinarAPIController.php @@ -25,7 +25,6 @@ public function __construct( public function index(ListWebinarsRequest $listWebinarsRequest): JsonResponse { - dd('tuda'); $search = $listWebinarsRequest->except(['limit', 'skip', 'order', 'order_by']); $orderDto = OrderDto::instantiateFromRequest($listWebinarsRequest); From eb62b35f8846a25fc62d0e6bfa53d67b5d5d0ef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Witold=20Wis=CC=81niewski?= Date: Tue, 24 Mar 2026 15:05:56 +0100 Subject: [PATCH 3/3] fix --- .github/workflows/test-cc.yml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test-cc.yml b/.github/workflows/test-cc.yml index ddf672e..ffc06cd 100644 --- a/.github/workflows/test-cc.yml +++ b/.github/workflows/test-cc.yml @@ -55,19 +55,19 @@ jobs: - name: codecov upload uses: codecov/codecov-action@v1 - - name: Setup Code Climate test-reporter - run: | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - - - name: SafeDirFix - run: git config --global safe.directory '*' - - - name: Convert - run: ./cc-test-reporter format-coverage coverage.xml -t clover -o codeclimate.0.json - - - name: Upload - run: ./cc-test-reporter upload-coverage -i codeclimate.0.json - env: - CC_TEST_REPORTER_ID: 915c317f6988c594001f3f74d7e1b1c919c04fde6ff24421291c6f0f2a345ab0 +# - name: Setup Code Climate test-reporter +# run: | +# curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter +# chmod +x ./cc-test-reporter +# +# - name: SafeDirFix +# run: git config --global safe.directory '*' +# +# - name: Convert +# run: ./cc-test-reporter format-coverage coverage.xml -t clover -o codeclimate.0.json +# +# - name: Upload +# run: ./cc-test-reporter upload-coverage -i codeclimate.0.json +# env: +# CC_TEST_REPORTER_ID: 915c317f6988c594001f3f74d7e1b1c919c04fde6ff24421291c6f0f2a345ab0