From 3dff5e5129866400dc23042b92aedb2d5b54be65 Mon Sep 17 00:00:00 2001 From: trenc Date: Thu, 19 Sep 2024 13:25:44 +0200 Subject: [PATCH 1/7] feat: add TranscriptionProvider property to HtrData --- src/app/Models/HtrData.php | 16 ++++++++- src/app/Models/TranscriptionProvider.php | 21 +++++++++++ ...00_create_transcription_provider_table.php | 30 ++++++++++++++++ ...scription_provider_id_to_htrdata_table.php | 35 +++++++++++++++++++ 4 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/app/Models/TranscriptionProvider.php create mode 100644 src/database/migrations/2024_09_19_115300_create_transcription_provider_table.php create mode 100644 src/database/migrations/2024_09_19_121100_add_transcription_provider_id_to_htrdata_table.php diff --git a/src/app/Models/HtrData.php b/src/app/Models/HtrData.php index 840fde2..4a49881 100644 --- a/src/app/Models/HtrData.php +++ b/src/app/Models/HtrData.php @@ -26,13 +26,21 @@ class HtrData extends Model 'Language' ]; + protected $hidden = ['TranscriptionProviderId']; + protected $appends = [ 'UserId', 'TranscriptionData', 'TranscriptionText', - 'Language' + 'Language', + 'TranscriptionProvider', ]; + public function transcriptionProvider(): BelongsTo + { + return $this->belongsTo(TranscriptionProvider::class, 'TranscriptionProviderId'); + } + public function item(): BelongsTo { return $this->belongsTo(Item::class, 'ItemId'); @@ -59,6 +67,12 @@ public function language(): BelongsToMany // we make usage of some custom accessors and mutators + public function getTranscriptionProviderAttribute(): string + { + $transcriptionProvider = $this->transcriptionProvider()->first(); + return $transcriptionProvider ? $transcriptionProvider['Name'] : 'No provider'; + } + public function getLanguageAttribute(): Collection { return $this->language()->get(); diff --git a/src/app/Models/TranscriptionProvider.php b/src/app/Models/TranscriptionProvider.php new file mode 100644 index 0000000..0c0e3d5 --- /dev/null +++ b/src/app/Models/TranscriptionProvider.php @@ -0,0 +1,21 @@ +hasMany(HtrData::class, 'TranscriptionProviderId'); + } +} diff --git a/src/database/migrations/2024_09_19_115300_create_transcription_provider_table.php b/src/database/migrations/2024_09_19_115300_create_transcription_provider_table.php new file mode 100644 index 0000000..5d778b2 --- /dev/null +++ b/src/database/migrations/2024_09_19_115300_create_transcription_provider_table.php @@ -0,0 +1,30 @@ +charset = 'utf8mb4'; + $table->collation = 'utf8mb4_unicode_ci'; + + $table->smallIncrements('TranscriptionProviderId'); + $table->string('Name'); + }); + + DB::table('TranscriptionProvider')->insert([ + ['Name' => 'ReadCoop-Transkribus'], + ['Name' => 'CrossLang-Occam'] + ]); + } + + public function down() + { + Schema::dropIfExists('TranscriptionProvider'); + } +}; diff --git a/src/database/migrations/2024_09_19_121100_add_transcription_provider_id_to_htrdata_table.php b/src/database/migrations/2024_09_19_121100_add_transcription_provider_id_to_htrdata_table.php new file mode 100644 index 0000000..072e495 --- /dev/null +++ b/src/database/migrations/2024_09_19_121100_add_transcription_provider_id_to_htrdata_table.php @@ -0,0 +1,35 @@ +unsignedSmallInteger('TranscriptionProviderId') + ->after('EuropeanaAnnotationId') + ->nullable(); + + $table->foreign('TranscriptionProviderId') + ->references('TranscriptionProviderId') + ->on('TranscriptionProvider') + ->nullOnDelete(); + }); + + DB::table('HtrData')->update(['TranscriptionProviderId' => 1]); + } + + public function down() + { + Schema::table('HtrData', function (Blueprint $table) { + $table->dropForeign(['TranscriptionProviderId']); + $table->dropColumn('TranscriptionProviderId'); + }); + } +}; From 06d3e3d0f0a6aedf47df4936655bfa55f1454203 Mon Sep 17 00:00:00 2001 From: trenc Date: Thu, 19 Sep 2024 13:26:05 +0200 Subject: [PATCH 2/7] docs: explain TranscritpionProvider and TranscriptionProviderId --- src/storage/api-docs/htrdata-schema.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/storage/api-docs/htrdata-schema.yaml b/src/storage/api-docs/htrdata-schema.yaml index 25912f3..30594ed 100644 --- a/src/storage/api-docs/htrdata-schema.yaml +++ b/src/storage/api-docs/htrdata-schema.yaml @@ -54,6 +54,11 @@ HtrDataPostRequestSchema: type: integer description: Array of LanguageId associated with this data example: 4 + TranscriptionProviderId: + type: integer + nullable: true + description: ID of the TranscriptionProvider + example: 1 HtrDataGetResponseSchema: allOf: @@ -80,6 +85,10 @@ HtrDataGetResponseSchema: type: array items: $ref: 'language-reference-schema.yaml#/LanguageReferenceSchema' + TranscriptionProvider: + type: string + description: Name TranscriptionProvider + example: ReadCoop-Transkribus - $ref: '#/HtrDataMinimalReferenceSchema' HtrDataPutRequestSchema: @@ -93,4 +102,9 @@ HtrDataPutRequestSchema: type: integer description: Array of LanguageId associated with this data example: 4 + TranscriptionProviderId: + type: integer + nullable: true + description: ID of the TranscriptionProvider + example: 1 - $ref: '#/HtrDataMinimalReferenceSchema' From 6dfaa11d8be19edf78276485e4f018886ca27b4a Mon Sep 17 00:00:00 2001 From: trenc Date: Thu, 19 Sep 2024 13:26:34 +0200 Subject: [PATCH 3/7] build: bump version --- src/storage/api-docs/api-docs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storage/api-docs/api-docs.yaml b/src/storage/api-docs/api-docs.yaml index 4f771a0..98d3f02 100644 --- a/src/storage/api-docs/api-docs.yaml +++ b/src/storage/api-docs/api-docs.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: - version: 1.50.0 + version: 1.51.0 title: Transcribathon Platform API v2 description: This is the documentation of the Transcribathon API v2 used by [https:transcribathon.eu](https://transcribathon.eu/).
For authorization you can use the the bearer token you are provided with. From fbf4c28b1b38a2d27ad93d103c94ee5e71e36fc9 Mon Sep 17 00:00:00 2001 From: trenc Date: Fri, 20 Sep 2024 10:16:07 +0200 Subject: [PATCH 4/7] tests: tests for the new transcription provider endpoint --- .../Feature/TranscriptionProviderTest.php | 129 ++++++++++++++++++ src/tests/TestCase.php | 3 +- 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 src/tests/Feature/TranscriptionProviderTest.php diff --git a/src/tests/Feature/TranscriptionProviderTest.php b/src/tests/Feature/TranscriptionProviderTest.php new file mode 100644 index 0000000..e8838c8 --- /dev/null +++ b/src/tests/Feature/TranscriptionProviderTest.php @@ -0,0 +1,129 @@ + 1, + 'Name' => 'ReadCoop-Transkribus', + ] , + [ + 'TranscriptionProviderId' => 2, + 'Name' => 'CrossLang-Occam', + ] , + ]; + + public function setUp(): void + { + parent::setUp(); + } + + public function testGetAllTranscriptionProvider(): void + { + $awaitedSuccess = ['success' => true]; + $awaitedData = ['data' => self::$data]; + + $response = $this->get(self::$endpoint); + + $response + ->assertOk() + ->assertJson($awaitedSuccess) + ->assertJson($awaitedData); + } + + public function testGetASingleTranscriptionProvider(): void + { + $queryParams = '/'. self::$data[1]['TranscriptionProviderId']; + $awaitedSuccess = ['success' => true]; + $awaitedData = ['data' => self::$data[1]]; + + $response = $this->get(self::$endpoint . $queryParams); + + $response + ->assertOk() + ->assertJson($awaitedSuccess) + ->assertJson($awaitedData); + } + + public function testCreateATranscriptionProvider(): void + { + $createData = [ + 'Name' => 'TestTranscriptionProvider', + ]; + $awaitedSuccess = ['success' => true]; + $awaitedData = ['data' => $createData]; + + $response = $this->post(self::$endpoint, $createData); + + $response + ->assertOk() + ->assertJson($awaitedSuccess) + ->assertJson($awaitedData); + } + + public function testUpdateATranscriptionProvider(): void + { + $updateData = [ + 'Name' => 'TestTranscriptionProviderUpdate', + ]; + $id = self::$data[1]['TranscriptionProviderId']; + $queryParams = '/' . $id; + $awaitedSuccess = ['success' => true]; + $awaitedData = ['data' => $updateData]; + $awaitedData['data']['TranscriptionProviderId'] = $id; + + $response = $this->put(self::$endpoint . $queryParams, $updateData); + + $response + ->assertOk() + ->assertJson($awaitedSuccess) + ->assertJson($awaitedData); + } + + public function testUpdateANonExistentTranscriptionProvider(): void + { + $queryParams = '/999999'; + $updateData = []; + $awaitedSuccess = ['success' => false]; + + $response = $this->put(self::$endpoint . $queryParams, $updateData); + + $response + ->assertNotFound() + ->assertJson($awaitedSuccess); + } + + public function testDeleteATranscriptionProvider(): void + { + $id = self::$data[1]['TranscriptionProviderId']; + $queryParams = '/' . $id; + $awaitedSuccess = ['success' => true]; + $awaitedData = ['data' => self::$data[1]]; + + $response = $this->delete(self::$endpoint . $queryParams); + + $response + ->assertOk() + ->assertJson($awaitedSuccess) + ->assertJson($awaitedData); + } + + public function testDeleteANonExistentProject(): void + { + $queryParams = '/999999'; + $updateData = []; + $awaitedSuccess = ['success' => false]; + + $response = $this->delete(self::$endpoint . $queryParams); + + $response + ->assertNotFound() + ->assertJson($awaitedSuccess); + } +} diff --git a/src/tests/TestCase.php b/src/tests/TestCase.php index 9554905..b55b8da 100644 --- a/src/tests/TestCase.php +++ b/src/tests/TestCase.php @@ -23,7 +23,8 @@ protected function setUp(): void $additionalMigrations = [ '2024_03_18_103600_create_user_stats_view.php', '2024_03_22_150100_create_campaign_stats_view.php', - '2024_07_31_094900_add_manifest_to_story_table.php' + '2024_07_31_094900_add_manifest_to_story_table.php', + '2024_09_19_115300_create_transcription_provider_table.php', ]; foreach ($additionalMigrations as $migration) { From 3a66229f3937d49f7eec254bc1b449b0c580bbcc Mon Sep 17 00:00:00 2001 From: trenc Date: Fri, 20 Sep 2024 10:16:45 +0200 Subject: [PATCH 5/7] feat: introduce new transcription providers endpoint --- .../TranscriptionProviderController.php | 94 +++++++++++++++++++ .../TranscriptionProviderResource.php | 13 +++ src/app/Models/TranscriptionProvider.php | 2 + src/routes/api.php | 7 ++ 4 files changed, 116 insertions(+) create mode 100644 src/app/Http/Controllers/TranscriptionProviderController.php create mode 100644 src/app/Http/Resources/TranscriptionProviderResource.php diff --git a/src/app/Http/Controllers/TranscriptionProviderController.php b/src/app/Http/Controllers/TranscriptionProviderController.php new file mode 100644 index 0000000..295f5fb --- /dev/null +++ b/src/app/Http/Controllers/TranscriptionProviderController.php @@ -0,0 +1,94 @@ +getDataByRequest($request, $model, $queryColumns, $initialSortColumn); + + if (!$data) { + return $this->sendError('Invalid data', $request . ' not valid', 400); + } + + $collection = TranscriptionProviderResource::collection($data); + + return $this->sendResponseWithMeta($collection, 'TranscriptionProviders fetched.'); + } + + public function show(int $id): JsonResponse + { + try { + $data = TranscriptionProvider::findOrFail($id); + $resource = new TranscriptionProviderResource($data); + + return $this->sendResponse($resource, 'TranscriptionProvider fetched.'); + } catch (\Exception $exception) { + return $this->sendError('Not found', $exception->getMessage()); + } + } + + public function store(Request $request): JsonResponse + { + try { + $data = new TranscriptionProvider(); + $data->fill($request->all()); + $data->save(); + + return $this->sendResponse(new TranscriptionProviderResource($data), 'Transcription provider inserted.'); + } catch (\Exception $exception) { + return $this->sendError('Invalid data', $exception->getMessage(), 400); + } + } + + + public function update(Request $request, int $id): JsonResponse + { + try { + $data = TranscriptionProvider::findOrfail($id); + } catch(\Exception $exception) { + return $this->sendError('Not found', $exception->getMessage(), 404); + } + + try { + $data->fill($request->all()); + $data->save(); + + return $this->sendResponse(new TranscriptionProviderResource($data), 'Transcription provider updated.'); + } catch(\Exception $exception) { + return $this->sendError('Invalid data', $exception->getMessage(), 400); + } + } + + public function destroy(int $id): JsonResponse + { + try { + $data = TranscriptionProvider::findOrfail($id); + } catch(\Exception $exception) { + return $this->sendError('Not found', $exception->getMessage(), 404); + } + + try { + $resource = $data->toArray(); + $resource = new TranscriptionProviderResource($resource); + $data->delete(); + + return $this->sendResponse($resource, 'Transcription provider deleted.'); + } catch(\Exception $exception) { + return $this->sendError('Invalid data', $exception->getMessage(), 400); + } + } +} diff --git a/src/app/Http/Resources/TranscriptionProviderResource.php b/src/app/Http/Resources/TranscriptionProviderResource.php new file mode 100644 index 0000000..97818b8 --- /dev/null +++ b/src/app/Http/Resources/TranscriptionProviderResource.php @@ -0,0 +1,13 @@ +hasMany(HtrData::class, 'TranscriptionProviderId'); diff --git a/src/routes/api.php b/src/routes/api.php index 5e9493b..9d3ccff 100644 --- a/src/routes/api.php +++ b/src/routes/api.php @@ -22,6 +22,7 @@ use App\Http\Controllers\UserStatsController; use App\Http\Controllers\ScoreController; use App\Http\Controllers\StatisticsController; +use App\Http\Controllers\TranscriptionProviderController; /* |-------------------------------------------------------------------------- @@ -127,5 +128,11 @@ Route::put('/places/{id}', [PlaceController::class, 'update']); Route::delete('/places/{id}', [PlaceController::class, 'destroy']); + Route::get('/transcription-providers', [TranscriptionProviderController::class, 'index']); + Route::post('/transcription-providers', [TranscriptionProviderController::class, 'store']); + Route::get('/transcription-providers/{id}', [TranscriptionProviderController::class, 'show']); + Route::put('/transcription-providers/{id}', [TranscriptionProviderController::class, 'update']); + Route::delete('/transcription-providers/{id}', [TranscriptionProviderController::class, 'destroy']); + Route::post('/import', [ImportController::class, 'store']); }); From f7ed9cb0340b6647a216581b47694288ca9f2abe Mon Sep 17 00:00:00 2001 From: trenc Date: Fri, 20 Sep 2024 10:17:06 +0200 Subject: [PATCH 6/7] docs: describe new transcription providers endpoint --- src/storage/api-docs/api-docs.yaml | 5 + .../transcription-providers-path.yaml | 62 +++++++++++++ .../transcription-providers-schema.yaml | 29 ++++++ ...roviders-transcriptionProviderId-path.yaml | 93 +++++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 src/storage/api-docs/transcription-providers-path.yaml create mode 100644 src/storage/api-docs/transcription-providers-schema.yaml create mode 100644 src/storage/api-docs/transcription-providers-transcriptionProviderId-path.yaml diff --git a/src/storage/api-docs/api-docs.yaml b/src/storage/api-docs/api-docs.yaml index 98d3f02..92d2c8d 100644 --- a/src/storage/api-docs/api-docs.yaml +++ b/src/storage/api-docs/api-docs.yaml @@ -159,6 +159,11 @@ paths: /scores: $ref: 'scores-path.yaml' + /transcription-providers: + $ref: 'transcription-providers-path.yaml' + /transcription-providers/{TranscriptionProviderId}: + $ref: 'transcription-providers-transcriptionProviderId-path.yaml' + /stories: $ref: 'stories-path.yaml' /stories/campaigns: diff --git a/src/storage/api-docs/transcription-providers-path.yaml b/src/storage/api-docs/transcription-providers-path.yaml new file mode 100644 index 0000000..041e418 --- /dev/null +++ b/src/storage/api-docs/transcription-providers-path.yaml @@ -0,0 +1,62 @@ +get: + tags: + - htrdata + summary: Get all stored transcription providers + description: The index endpoint can be used to get all teams. The output is limited to 100 but can be filtered, sorted and paginated. The returned data is always an array even if the filter criteria is a unique identifier. + parameters: + - $ref: 'basic-query-parameter.yaml#/PaginationParameters/limit' + - $ref: 'basic-query-parameter.yaml#/PaginationParameters/page' + - in: query + name: orderBy + default: TranscriptionProviderId + description: Table column to order the return + schema: + type: string + - $ref: 'basic-query-parameter.yaml#/SortParameters/orderDir' + responses: + 200: + description: Ok + content: + application/json: + schema: + allOf: + - $ref: 'responses.yaml#/BasicSuccessResponse' + - properties: + meta: + type: object + description: Meta data with pagination details + $ref: 'meta-responses.yaml#MetaPaginationResponse' + data: + type: array + description: A transcription provider entry objects as array + items: + $ref: 'transcription-providers-schema.yaml#/TranscriptionProvidersGetResponseSchema' + 401: + $ref: 'responses.yaml#/401ErrorResponse' +post: + tags: + - htrdata + summary: Store a new transcription provider entry + requestBody: + required: true + content: + application/json: + schema: + $ref: 'transcription-providers-schema.yaml#/TranscriptionProvidersPostRequestSchema' + responses: + 200: + description: Ok + content: + application/json: + schema: + allOf: + - $ref: 'responses.yaml#/BasicSuccessResponse' + - properties: + message: + example: Transcription provider inserted. + data: + $ref: 'transcription-providers-schema.yaml#/TranscriptionProvidersGetResponseSchema' + 401: + $ref: 'responses.yaml#/401ErrorResponse' + 400: + $ref: 'responses.yaml#/400ErrorResponse' diff --git a/src/storage/api-docs/transcription-providers-schema.yaml b/src/storage/api-docs/transcription-providers-schema.yaml new file mode 100644 index 0000000..a17d119 --- /dev/null +++ b/src/storage/api-docs/transcription-providers-schema.yaml @@ -0,0 +1,29 @@ +TranscriptionProvidersMinimalReferenceSchema: + properties: + Name: + type: string + description: Name of the transcription provider + example: ReadCoop-Transkribus + +TranscriptionProvidersGetResponseSchema: + allOf: + - type: object + - description: The data object of a single response entry + - $ref: '#/TranscriptionProvidersMinimalReferenceSchema' + - properties: + TranscriptionProviderId: + type: integer + description: ID of the entry + example: 2 + +TranscriptionProvidersPostRequestSchema: + allOf: + - required: ['Name'] + - description: The data object of a POST request body + - $ref: '#/TranscriptionProvidersMinimalReferenceSchema' + +TranscriptionProvidersPutRequestSchema: + allOf: + - required: ['Name'] + - description: The data object of a PUT request body + - $ref: '#/TranscriptionProvidersMinimalReferenceSchema' diff --git a/src/storage/api-docs/transcription-providers-transcriptionProviderId-path.yaml b/src/storage/api-docs/transcription-providers-transcriptionProviderId-path.yaml new file mode 100644 index 0000000..5911143 --- /dev/null +++ b/src/storage/api-docs/transcription-providers-transcriptionProviderId-path.yaml @@ -0,0 +1,93 @@ +get: + tags: + - htrdata + summary: Get stored transcription provider data of an entry + description: The returned data is single object + parameters: + - in: path + name: TranscriptionProviderId + description: Numeric ID of the entry + type: integer + required: true + responses: + 200: + description: Ok + content: + application/json: + schema: + allOf: + - $ref: 'responses.yaml#/BasicSuccessResponse' + - properties: + data: + $ref: 'transcription-providers-schema.yaml#/TranscriptionProvidersGetResponseSchema' + 400: + $ref: 'responses.yaml#/400ErrorResponse' + 401: + $ref: 'responses.yaml#/401ErrorResponse' + 404: + $ref: 'responses.yaml#/404ErrorResponse' +put: + tags: + - htrdata + summary: Updates data of a transcription provider + parameters: + - in: path + name: TranscriptionProviderId + description: Numeric ID of the entry + type: integer + required: true + requestBody: + description: Data to be stored + required: true + content: + application/json: + schema: + $ref: 'transcription-providers-schema.yaml#/TranscriptionProvidersPutRequestSchema' + responses: + 200: + description: Ok + content: + application/json: + schema: + allOf: + - $ref: 'responses.yaml#/BasicSuccessResponse' + - properties: + data: + $ref: 'transcription-providers-schema.yaml#/TranscriptionProvidersGetResponseSchema' + message: + example: 'Transcription provider updated.' + 400: + $ref: 'responses.yaml#/400ErrorResponse' + 401: + $ref: 'responses.yaml#/401ErrorResponse' + 404: + $ref: 'responses.yaml#/404ErrorResponse' +delete: + tags: + - htrdata + summary: delete a transcription provider data entry + parameters: + - in: path + name: TranscriptionProviderId + description: Numeric ID of the entry + type: integer + required: true + responses: + 200: + description: Ok + content: + application/json: + schema: + allOf: + - $ref: 'responses.yaml#/BasicSuccessResponse' + - properties: + data: + $ref: 'transcription-providers-schema.yaml#/TranscriptionProvidersGetResponseSchema' + message: + example: 'Project deleted.' + 400: + $ref: 'responses.yaml#/400ErrorResponse' + 401: + $ref: 'responses.yaml#/401ErrorResponse' + 404: + $ref: 'responses.yaml#/404ErrorResponse' From abaf2dfc919b6d075f16d339bf4057402916db06 Mon Sep 17 00:00:00 2001 From: trenc Date: Fri, 20 Sep 2024 10:17:27 +0200 Subject: [PATCH 7/7] buld: bump version --- src/storage/api-docs/api-docs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storage/api-docs/api-docs.yaml b/src/storage/api-docs/api-docs.yaml index 92d2c8d..c6dc6ef 100644 --- a/src/storage/api-docs/api-docs.yaml +++ b/src/storage/api-docs/api-docs.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: - version: 1.51.0 + version: 1.52.0 title: Transcribathon Platform API v2 description: This is the documentation of the Transcribathon API v2 used by [https:transcribathon.eu](https://transcribathon.eu/).
For authorization you can use the the bearer token you are provided with.