From 3bc5316f0c7b70cadb4af2b710331288831d379f Mon Sep 17 00:00:00 2001 From: trenc Date: Wed, 13 Nov 2024 13:52:15 +0100 Subject: [PATCH 1/7] refactor: rearrange route orders --- src/routes/api.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/routes/api.php b/src/routes/api.php index a398c22..02cc9c3 100644 --- a/src/routes/api.php +++ b/src/routes/api.php @@ -55,8 +55,6 @@ Route::get('/items/{id}/persons', [PersonController::class, 'showByItemId']); Route::get('/items/{id}/places', [PlaceController::class, 'showByItemId']); - Route::get('/users/{id}/htrdata', [HtrDataController::class, 'showByUserId']); - Route::get('/stories/campaigns', [StoryController::class, 'showCampaignsByStories']); Route::get('/stories', [StoryController::class, 'index']); Route::get('/stories/{id}', [StoryController::class, 'show']); @@ -84,6 +82,7 @@ Route::get('/users', [UserController::class, 'index']); Route::get('/users/{id}', [UserController::class, 'show']); Route::get('/users/{id}/statistics', [UserStatsController::class, 'show']); + Route::get('/users/{id}/htrdata', [HtrDataController::class, 'showByUserId']); Route::get('/scores', [ScoreController::class, 'index']); Route::post('/scores', [ScoreController::class, 'store']); From 8cc92265acdb9a3ac46b4b17aabd733081fee81c Mon Sep 17 00:00:00 2001 From: trenc Date: Tue, 26 Nov 2024 11:35:15 +0100 Subject: [PATCH 2/7] build: update vendors --- src/composer.json | 2 +- src/composer.lock | 82 +++++++++++++++++++++++------------------------ 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/composer.json b/src/composer.json index 894d82f..784b347 100644 --- a/src/composer.json +++ b/src/composer.json @@ -10,7 +10,7 @@ "doctrine/dbal": "^3.9", "fruitcake/laravel-cors": "^2.0", "guzzlehttp/guzzle": "^7.0.1", - "laravel/framework": "^9.0", + "laravel/framework": "^9.52", "laravel/sanctum": "^2.14", "laravel/tinker": "^2.5", "nesbot/carbon": "^2.72" diff --git a/src/composer.lock b/src/composer.lock index 7bb3c1c..061dfd4 100644 --- a/src/composer.lock +++ b/src/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c000b738a869e10bbd8d627be0dfe338", + "content-hash": "643f9850e1f0110293f974aceee22abd", "packages": [ { "name": "asm89/stack-cors", @@ -1689,16 +1689,16 @@ }, { "name": "laravel/framework", - "version": "v9.52.16", + "version": "v9.52.18", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "082345d76fc6a55b649572efe10b11b03e279d24" + "reference": "41c812bf83e00d0d3f4b6963b0d475b26cb6fbf7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/082345d76fc6a55b649572efe10b11b03e279d24", - "reference": "082345d76fc6a55b649572efe10b11b03e279d24", + "url": "https://api.github.com/repos/laravel/framework/zipball/41c812bf83e00d0d3f4b6963b0d475b26cb6fbf7", + "reference": "41c812bf83e00d0d3f4b6963b0d475b26cb6fbf7", "shasum": "" }, "require": { @@ -1883,7 +1883,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-10-03T13:02:30+00:00" + "time": "2024-11-20T15:56:00+00:00" }, { "name": "laravel/sanctum", @@ -1952,16 +1952,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v1.3.5", + "version": "v1.3.7", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c" + "reference": "4f48ade902b94323ca3be7646db16209ec76be3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", - "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/4f48ade902b94323ca3be7646db16209ec76be3d", + "reference": "4f48ade902b94323ca3be7646db16209ec76be3d", "shasum": "" }, "require": { @@ -2009,7 +2009,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2024-09-23T13:33:08+00:00" + "time": "2024-11-14T18:34:49+00:00" }, { "name": "laravel/tinker", @@ -2455,16 +2455,16 @@ }, { "name": "monolog/monolog", - "version": "2.9.3", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "a30bfe2e142720dfa990d0a7e573997f5d884215" + "reference": "5cf826f2991858b54d5c3809bee745560a1042a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/a30bfe2e142720dfa990d0a7e573997f5d884215", - "reference": "a30bfe2e142720dfa990d0a7e573997f5d884215", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/5cf826f2991858b54d5c3809bee745560a1042a7", + "reference": "5cf826f2991858b54d5c3809bee745560a1042a7", "shasum": "" }, "require": { @@ -2541,7 +2541,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.9.3" + "source": "https://github.com/Seldaek/monolog/tree/2.10.0" }, "funding": [ { @@ -2553,7 +2553,7 @@ "type": "tidelift" } ], - "time": "2024-04-12T20:52:51+00:00" + "time": "2024-11-12T12:43:37+00:00" }, { "name": "nesbot/carbon", @@ -6217,16 +6217,16 @@ }, { "name": "voku/portable-ascii", - "version": "2.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/voku/portable-ascii.git", - "reference": "b56450eed252f6801410d810c8e1727224ae0743" + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b56450eed252f6801410d810c8e1727224ae0743", - "reference": "b56450eed252f6801410d810c8e1727224ae0743", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", "shasum": "" }, "require": { @@ -6251,7 +6251,7 @@ "authors": [ { "name": "Lars Moelleken", - "homepage": "http://www.moelleken.org/" + "homepage": "https://www.moelleken.org/" } ], "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", @@ -6263,7 +6263,7 @@ ], "support": { "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/2.0.1" + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" }, "funding": [ { @@ -6287,7 +6287,7 @@ "type": "tidelift" } ], - "time": "2022-03-08T17:03:00+00:00" + "time": "2024-11-21T01:49:47+00:00" }, { "name": "webmozart/assert", @@ -6502,16 +6502,16 @@ }, { "name": "fakerphp/faker", - "version": "v1.24.0", + "version": "v1.24.1", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "a136842a532bac9ecd8a1c723852b09915d7db50" + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/a136842a532bac9ecd8a1c723852b09915d7db50", - "reference": "a136842a532bac9ecd8a1c723852b09915d7db50", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", "shasum": "" }, "require": { @@ -6559,9 +6559,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.24.0" + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" }, - "time": "2024-11-07T15:11:20+00:00" + "time": "2024-11-21T13:46:39+00:00" }, { "name": "filp/whoops", @@ -6753,16 +6753,16 @@ }, { "name": "laravel/sail", - "version": "v1.37.1", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "7efa151ea0d16f48233d6a6cd69f81270acc6e93" + "reference": "d17abae06661dd6c46d13627b1683a2924259145" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/7efa151ea0d16f48233d6a6cd69f81270acc6e93", - "reference": "7efa151ea0d16f48233d6a6cd69f81270acc6e93", + "url": "https://api.github.com/repos/laravel/sail/zipball/d17abae06661dd6c46d13627b1683a2924259145", + "reference": "d17abae06661dd6c46d13627b1683a2924259145", "shasum": "" }, "require": { @@ -6812,7 +6812,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2024-10-29T20:18:14+00:00" + "time": "2024-11-11T20:16:51+00:00" }, { "name": "mockery/mockery", @@ -8550,16 +8550,16 @@ }, { "name": "spatie/backtrace", - "version": "1.6.2", + "version": "1.6.3", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9" + "reference": "7c18db2bc667ac84e5d7c18e33f16c38ff2d8838" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/1a9a145b044677ae3424693f7b06479fc8c137a9", - "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/7c18db2bc667ac84e5d7c18e33f16c38ff2d8838", + "reference": "7c18db2bc667ac84e5d7c18e33f16c38ff2d8838", "shasum": "" }, "require": { @@ -8597,7 +8597,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/backtrace/tree/1.6.2" + "source": "https://github.com/spatie/backtrace/tree/1.6.3" }, "funding": [ { @@ -8609,7 +8609,7 @@ "type": "other" } ], - "time": "2024-07-22T08:21:24+00:00" + "time": "2024-11-18T14:58:58+00:00" }, { "name": "spatie/flare-client-php", From 56702f060ff94ae7b5619d555efb61eb583a34d4 Mon Sep 17 00:00:00 2001 From: trenc Date: Tue, 26 Nov 2024 13:46:16 +0100 Subject: [PATCH 3/7] tests: apply additional seeder data for UserItems test --- src/database/seeders/ItemDataSeeder.php | 9 + src/database/seeders/LanguageDataSeeder.php | 25 +++ src/database/seeders/ScoreDataSeeder.php | 18 +- src/database/seeders/StoryDataSeeder.php | 6 +- ...024_11_20_120500_create_language_table.php | 29 ++++ src/tests/Feature/UserItemsTest.php | 159 ++++++++++++++++++ 6 files changed, 242 insertions(+), 4 deletions(-) create mode 100644 src/database/seeders/LanguageDataSeeder.php create mode 100644 src/database/testMigrations/2024_11_20_120500_create_language_table.php create mode 100644 src/tests/Feature/UserItemsTest.php diff --git a/src/database/seeders/ItemDataSeeder.php b/src/database/seeders/ItemDataSeeder.php index f24e5be..5219e0c 100644 --- a/src/database/seeders/ItemDataSeeder.php +++ b/src/database/seeders/ItemDataSeeder.php @@ -34,6 +34,15 @@ class ItemDataSeeder extends Seeder 'LocaionStatusId' => 1, 'OrderIndex' => 1, 'ImageLink' => 'Imagelink Item 3' + ], + [ + 'ItemId' => 5, + 'StoryId' => 3, + 'CompletionStatusId' => 1, + 'TranscriptionStatusId' => 1, + 'LocaionStatusId' => 1, + 'OrderIndex' => 1, + 'ImageLink' => 'Imagelink Item 5' ] ]; diff --git a/src/database/seeders/LanguageDataSeeder.php b/src/database/seeders/LanguageDataSeeder.php new file mode 100644 index 0000000..78ed1ab --- /dev/null +++ b/src/database/seeders/LanguageDataSeeder.php @@ -0,0 +1,25 @@ + '1', + 'Name' => 'Deutsch', + 'NameEnglish' => 'German', + 'ShortName' => 'DE', + 'Code' => 'de', + 'Code3' => 'deu', + ], + ]; + + public function run(): void + { + DB::table('Language')->insert(self::$data); + } +} diff --git a/src/database/seeders/ScoreDataSeeder.php b/src/database/seeders/ScoreDataSeeder.php index 81ba52b..d4da015 100644 --- a/src/database/seeders/ScoreDataSeeder.php +++ b/src/database/seeders/ScoreDataSeeder.php @@ -31,7 +31,23 @@ class ScoreDataSeeder extends Seeder 'ScoreTypeId' => 3, 'Amount' => 10, 'Timestamp' => '2022-02-01T12:00:00.000000Z' - ] + ], + [ + 'ScoreId' => 4, + 'ItemId' => 1, + 'UserId' => 1, + 'ScoreTypeId' => 3, + 'Amount' => 100, + 'Timestamp' => '2023-02-01T12:00:00.000000Z' + ], + [ + 'ScoreId' => 5, + 'ItemId' => 5, + 'UserId' => 1, + 'ScoreTypeId' => 3, + 'Amount' => 100, + 'Timestamp' => '2023-03-01T12:00:00.000000Z' + ], ]; public function run(): void diff --git a/src/database/seeders/StoryDataSeeder.php b/src/database/seeders/StoryDataSeeder.php index d5d3bd3..7056b63 100644 --- a/src/database/seeders/StoryDataSeeder.php +++ b/src/database/seeders/StoryDataSeeder.php @@ -19,7 +19,7 @@ class StoryDataSeeder extends Seeder 'PlaceUserGenerated' => 1, 'Public' => 1, 'ImportName' => '', - 'ProjectId' => '', + 'ProjectId' => 1, 'RecordId' => '', 'PreviewImage' => '', 'DatasetId' => '', @@ -68,7 +68,7 @@ class StoryDataSeeder extends Seeder 'PlaceUserGenerated' => 1, 'Public' => 1, 'ImportName' => '', - 'ProjectId' => '', + 'ProjectId' => 1, 'RecordId' => '', 'PreviewImage' => '', 'DatasetId' => '', @@ -117,7 +117,7 @@ class StoryDataSeeder extends Seeder 'PlaceUserGenerated' => 1, 'Public' => 1, 'ImportName' => '', - 'ProjectId' => '', + 'ProjectId' => 2, 'RecordId' => '', 'PreviewImage' => '', 'DatasetId' => '', diff --git a/src/database/testMigrations/2024_11_20_120500_create_language_table.php b/src/database/testMigrations/2024_11_20_120500_create_language_table.php new file mode 100644 index 0000000..d0617b1 --- /dev/null +++ b/src/database/testMigrations/2024_11_20_120500_create_language_table.php @@ -0,0 +1,29 @@ +charset = 'utf8mb4'; + $table->collation = 'utf8mb4_unicode_ci'; + + $table->smallIncrements('LanguageId'); + $table->string('Name', 100); + $table->string('NameEnglish', 100); + $table->string('ShortName', 10); + $table->string('Code', 10); + $table->string('Code3', 10)->nullable(); + }); + } + + public function down(): void + { + Schema::dropIfExists('Language'); + } +}; + diff --git a/src/tests/Feature/UserItemsTest.php b/src/tests/Feature/UserItemsTest.php new file mode 100644 index 0000000..6d62377 --- /dev/null +++ b/src/tests/Feature/UserItemsTest.php @@ -0,0 +1,159 @@ + ProjectDataSeeder::class]); + Artisan::call('db:seed', ['--class' => StoryDataSeeder::class]); + Artisan::call('db:seed', ['--class' => ItemDataSeeder::class]); + Artisan::call('db:seed', ['--class' => ScoreTypeDataSeeder::class]); + Artisan::call('db:seed', ['--class' => ScoreDataSeeder::class]); + } + + public function testNonExistentUserReturnsEmpty(): void + { + $userId = 0; + $endpoint = '/users/' . $userId . '/items'; + $awaitedSuccess = ['success' => true]; + $awaitedData = ['data' => []]; + + $response = $this->get($endpoint); + + $response + ->assertOk() + ->assertJson($awaitedSuccess) + ->assertJson($awaitedData); + } + + public function testGetLatestItemsOfAnUser(): void + { + $userId = 1; + $endpoint = '/users/' . $userId . '/items'; + $queryParams = ''; + $awaitedSuccess = ['success' => true]; + $awaitedData = ['data' => [ + [ + 'ProjectName' => 'Project-1', + 'Items' => [ + [ + 'ItemId' => 1, + 'ItemTitle' => '', + 'ItemImageLink' => 'Imagelink Item 1', + 'CompletionStatus' => 'Not Started', + 'LastEdit' => '2023-02-01T12:00:00.000000Z', + 'Scores' => [ + [ + 'ScoreType' => 'Enrichment', + 'Amount' => 100 + ], + [ + 'ScoreType' => 'Transcription', + 'Amount' => 55 + ], + ], + ] + ], + ], + [ + 'ProjectName' => 'Project-2', + 'Items' => [ + [ + 'ItemId' => 5, + 'ItemTitle' => '', + 'ItemImageLink' => 'Imagelink Item 5', + 'CompletionStatus' => 'Not Started', + 'LastEdit' => '2023-03-01T12:00:00.000000Z', + 'Scores' => [ + [ + 'ScoreType' => 'Enrichment', + 'Amount' => 100 + ], + ], + ], + [ + 'ItemId' => 3, + 'ItemTitle' => '', + 'ItemImageLink' => 'Imagelink Item 3', + 'CompletionStatus' => 'Not Started', + 'LastEdit' => '2022-02-01T12:00:00.000000Z', + 'Scores' => [ + [ + 'ScoreType' => 'Enrichment', + 'Amount' => 10 + ], + ], + ], + ], + ], + ]]; + + $response = $this->get($endpoint . $queryParams); + + $response + ->assertOk() + ->assertJson($awaitedSuccess) + ->assertJson($awaitedData); + } + + public function testGetLatestItemsOfAnUserLimited(): void + { + $userId = 1; + $endpoint = '/users/' . $userId . '/items'; + $queryParams = '?limit=1&page=2&threshold=100,'; + $awaitedSuccess = ['success' => true]; + $awaitedData = ['data' => [ + [ + 'ProjectName' => 'Project-1', + 'Items' => [], + ], + [ + 'ProjectName' => 'Project-2', + 'Items' => [ + [ + 'ItemId' => 3, + 'ItemTitle' => '', + 'ItemImageLink' => 'Imagelink Item 3', + 'CompletionStatus' => 'Not Started', + 'LastEdit' => '2022-02-01T12:00:00.000000Z', + 'Scores' => [ + [ + 'ScoreType' => 'Enrichment', + 'Amount' => 10 + ], + ], + ], + ], + ], + ]]; + $awaitedMeta = ['meta' => [ + 'limit' => 1, + 'currentPage' => 2, + 'threshold' => 100, + ]]; + + $response = $this->get($endpoint . $queryParams); + + $response + ->assertOk() + ->assertJson($awaitedSuccess) + ->assertJson($awaitedData) + ->assertJson($awaitedMeta); + } +} From f314616fe634a7c7b8423c01915393b4688fcdec Mon Sep 17 00:00:00 2001 From: trenc Date: Tue, 26 Nov 2024 13:46:59 +0100 Subject: [PATCH 4/7] tests: modify tests to reflect new seeder data --- src/tests/Feature/ScoreTest.php | 2 +- src/tests/Feature/SummaryStatsTest.php | 35 +++++++++++++++++++++++++- src/tests/Feature/UserStatsTest.php | 8 +++--- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/tests/Feature/ScoreTest.php b/src/tests/Feature/ScoreTest.php index 750c9fb..61d39b4 100644 --- a/src/tests/Feature/ScoreTest.php +++ b/src/tests/Feature/ScoreTest.php @@ -47,7 +47,7 @@ public function testGetAllScoresLimitedAndSorted(): void { $queryParams = '?limit=1&page=1&orderBy=ScoreId&orderDir=desc'; $awaitedSuccess = ['success' => true]; - $awaitedData = ['data' => [ScoreDataSeeder::$data[2]]]; + $awaitedData = ['data' => [ScoreDataSeeder::$data[4]]]; $response = $this->get(self::$endpoint . $queryParams); diff --git a/src/tests/Feature/SummaryStatsTest.php b/src/tests/Feature/SummaryStatsTest.php index 7b5de13..f6deaf8 100644 --- a/src/tests/Feature/SummaryStatsTest.php +++ b/src/tests/Feature/SummaryStatsTest.php @@ -48,6 +48,28 @@ public function testGetAllMonthlyBasedStatistics(): void 'OverallItemsStarted' => 1, 'Amount' => 10 ], + [ + 'Year' => 2023, + 'Month' => 2, + 'ScoreTypeId' => 3, + 'UniqueUsersPerScoreType' => 1, + 'UniqueItemsPerScoreType' => 1, + 'OverallUniqueUsers' => 1, + 'OverallUniqueItems' => 1, + 'OverallItemsStarted' => 0, + 'Amount' => 100 + ], + [ + 'Year' => 2023, + 'Month' => 3, + 'ScoreTypeId' => 3, + 'UniqueUsersPerScoreType' => 1, + 'UniqueItemsPerScoreType' => 1, + 'OverallUniqueUsers' => 1, + 'OverallUniqueItems' => 1, + 'OverallItemsStarted' => 1, + 'Amount' => 100 + ], [ 'Year' => 2021, 'Month' => 0, @@ -69,7 +91,18 @@ public function testGetAllMonthlyBasedStatistics(): void 'OverallUniqueItems' => 1, 'OverallItemsStarted' => 1, 'Amount' => 10 - ] + ], + [ + 'Year' => 2023, + 'Month' => 0, + 'ScoreTypeId' => 3, + 'UniqueUsersPerScoreType' => 1, + 'UniqueItemsPerScoreType' => 2, + 'OverallUniqueUsers' => 1, + 'OverallUniqueItems' => 2, + 'OverallItemsStarted' => 1, + 'Amount' => 200 + ], ]; $response = $this->get(self::$endpoint . $queryParams); diff --git a/src/tests/Feature/UserStatsTest.php b/src/tests/Feature/UserStatsTest.php index af5d2d5..539089b 100644 --- a/src/tests/Feature/UserStatsTest.php +++ b/src/tests/Feature/UserStatsTest.php @@ -98,7 +98,7 @@ function rate (array $array, int $scoreTypeId): float ->assertJson($awaitedData); } - function testGetStatisiticsForAllUsers(): void + function testGetStatisticsForAllUsers(): void { $endpoint = '/users/statistics'; $queryParams = ''; @@ -107,13 +107,13 @@ function testGetStatisiticsForAllUsers(): void 'data' => [ [ 'UserId' => 1, - 'Items' => 2, + 'Items' => 3, 'Locations' => 0, 'ManualTranscriptions' => 55, - 'Enrichments' => 10, + 'Enrichments' => 210, 'Descriptions' => 0, 'HTRTranscriptions' => 0, - 'Miles' => 3, + 'Miles' => 43, ], [ 'UserId' => 2, From fb4ebe1251e8e69fe6041f11109cf044596f062d Mon Sep 17 00:00:00 2001 From: trenc Date: Tue, 26 Nov 2024 13:49:07 +0100 Subject: [PATCH 5/7] feat: provide new endpoint for latest enrichments of user items --- .../Http/Controllers/UserItemsController.php | 123 ++++++++++++++++++ src/app/Http/Resources/UserItemsResource.php | 13 ++ src/routes/api.php | 2 + 3 files changed, 138 insertions(+) create mode 100644 src/app/Http/Controllers/UserItemsController.php create mode 100644 src/app/Http/Resources/UserItemsResource.php diff --git a/src/app/Http/Controllers/UserItemsController.php b/src/app/Http/Controllers/UserItemsController.php new file mode 100644 index 0000000..5bf9a88 --- /dev/null +++ b/src/app/Http/Controllers/UserItemsController.php @@ -0,0 +1,123 @@ +query(); + + // set defaults + $queries['limit'] = intval($queries['limit'] ?? 9); + $queries['page'] = intval($queries['page'] ?? 1); + $queries['threshold'] = intval($queries['threshold'] ?? 500); + + $data = DB::table('Score as s') + ->join('Item as i', 's.ItemId', '=', 'i.ItemId') + ->join('Story as st', 'i.StoryId', '=', 'st.StoryId') + ->join('Project as p', 'st.ProjectId', '=', 'p.ProjectId') + ->join('ScoreType as stype', 's.ScoreTypeId', '=', 'stype.ScoreTypeId') + ->join('CompletionStatus as cs', 'i.CompletionStatusId', '=', 'cs.CompletionStatusId') + ->select( + 'p.Name as ProjectName', + 'i.ItemId', + 'i.Title AS ItemTitle', + 'i.ImageLink AS ItemImageLink', + 'cs.Name AS CompletionStatus', + DB::raw('MAX(s.Timestamp) AS LastEdit'), + 'stype.Name AS ScoreType', + DB::raw('SUM(s.Amount) AS Amount') + ) + ->where('s.UserId', $id) + ->groupBy('p.Name', 'i.ItemId', 's.ScoreTypeId') + ->orderBy('p.Name') + ->orderBy('LastEdit', 'desc') + ->limit($queries['threshold']) + ->get(); + + $grouped = $this->groupCollection($queries, $data); + + $collection = UserItemsResource::collection($grouped); + + return $this->sendResponseWithMeta($collection, 'Items fetched.'); + } catch(\Exception $exception) { + return $this->sendError('Invalid data', $exception->getMessage(), 400); + } + } + + private function groupCollection(array $queries, Collection $data): array + { + $limit = $queries['limit']; + $page = $queries['page']; + $offset = $limit * ($page - 1); + $threshold = $queries['threshold']; + + $this->meta = [ + 'limit' => $limit, + 'currentPage' => $page, + 'threshold' => $threshold, + ]; + + $groupedData = []; + + foreach ($data as $row) { + $projectName = $row->ProjectName; + $itemId = $row->ItemId; + $itemTitle = $row->ItemTitle; + $itemImageLink = $row->ItemImageLink; + $itemLastEdit = $row->LastEdit; + $completionStatus = $row->CompletionStatus; + $scoreType = $row->ScoreType; + + // initialize project group if it doesn't exist + if (!isset($groupedData[$projectName])) { + $groupedData[$projectName] = [ + 'ProjectName' => $projectName, + 'Items' => [], + ]; + } + + // initialize item group within the project if it doesn't exist + if (!isset($groupedData[$projectName]['Items'][$itemId])) { + $groupedData[$projectName]['Items'][$itemId] = [ + 'ItemId' => (int) $itemId, + 'ItemTitle' => $itemTitle ?? '', + 'ItemImageLink' => $itemImageLink, + 'CompletionStatus' => $completionStatus, + 'LastEdit' => $itemLastEdit, + 'Scores' => [], + ]; + } + + // add score details to the item's scores + $groupedData[$projectName]['Items'][$itemId]['Scores'][] = [ + 'ScoreType' => $scoreType, + 'Amount' => (int) $row->Amount, + ]; + } + + $groupedData = $this->limitAndOffsetItems($groupedData, $offset, $limit); + + return array_values($groupedData); + } + + private function limitAndOffsetItems(array $data, int $offset, int $limit): array + { + foreach ($data as $key => $project) { + if (isset($project['Items']) && is_array($project['Items'])) { + $data[$key]['Items'] = array_slice($project['Items'], $offset, $limit); + } + } + + return $data; + } +} diff --git a/src/app/Http/Resources/UserItemsResource.php b/src/app/Http/Resources/UserItemsResource.php new file mode 100644 index 0000000..902cb84 --- /dev/null +++ b/src/app/Http/Resources/UserItemsResource.php @@ -0,0 +1,13 @@ + Date: Wed, 27 Nov 2024 11:33:47 +0100 Subject: [PATCH 6/7] docs: describe new users items endpoint --- src/storage/api-docs/api-docs.yaml | 2 + src/storage/api-docs/users-items-schema.yaml | 50 +++++++++++++++ .../api-docs/users-userId-items-path.yaml | 61 +++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 src/storage/api-docs/users-items-schema.yaml create mode 100644 src/storage/api-docs/users-userId-items-path.yaml diff --git a/src/storage/api-docs/api-docs.yaml b/src/storage/api-docs/api-docs.yaml index 0dda8b7..ff2f1ce 100644 --- a/src/storage/api-docs/api-docs.yaml +++ b/src/storage/api-docs/api-docs.yaml @@ -123,6 +123,8 @@ paths: $ref: 'users-userId-path.yaml' /users/{UserId}/statistics: $ref: 'users-userId-statistics-path.yaml' + /users/{UserId}/items: + $ref: 'users-userId-items-path.yaml' /teams: $ref: 'teams-path.yaml' diff --git a/src/storage/api-docs/users-items-schema.yaml b/src/storage/api-docs/users-items-schema.yaml new file mode 100644 index 0000000..5dd6cb2 --- /dev/null +++ b/src/storage/api-docs/users-items-schema.yaml @@ -0,0 +1,50 @@ +UserItemsGetResponseSchema: + allOf: + - type: object + - description: The data object of a single response entry + properties: + ProjectName: + type: string + description: Name of the project + example: Europeana + Items: + type: array + description: Array of lastest edited items of this user in this project + items: + type: object + description: An item entry with limited properties + properties: + ItemId: + type: integer + description: ID of the item + example: 245354 + ItemTitle: + type: string + description: Title of the item + example: 'Parchment no. 2' + ItemImageLink: + type: string + description: IIIF JSON string with full image link + example: '{\"@id\":\"rhus-209.man.poznan.pl/fcgi-bin/iipsrv.fcgi?IIIF=11/5//2020601/https___1914_1918_europeana_eu_contributions_17441/17441.245184.original.tif/full/full/0/default.jpg\",\"@type\":\"dctypes:Image\",\"width\":1531,\"height\":2121,\"service\":{\"@id\":\"rhus-209.man.poznan.pl/fcgi-bin/iipsrv.fcgi?IIIF=11/5//2020601/https___1914_1918_europeana_eu_contributions_17441/17441.245184.original.tif\",\"@context\":\"http://iiif.io/api/image/2/context.json\",\"profile\":\"http://iiif.io/api/image/2/level1.json\"}}' + CompletionStatus: + type: string + description: Name of the completion status + example: 'Edited' + LastEdit: + type: string + description: Time of this user's last edit + example: '2022-02-01 12:00:00' + Scores: + type: array + description: Array with scores amount and type for this item and user + items: + type: object + description: Summarized scores amount per score type + properties: + ScoreType: + type: string + description: Name of the ScoreType + example: Enrichment + Amount: + type: integer + description: Summarized score amount of this ScoreType diff --git a/src/storage/api-docs/users-userId-items-path.yaml b/src/storage/api-docs/users-userId-items-path.yaml new file mode 100644 index 0000000..b9439af --- /dev/null +++ b/src/storage/api-docs/users-userId-items-path.yaml @@ -0,0 +1,61 @@ +get: + tags: + - items + - users + summary: Get latest entiched/translated items of an user + description: The returned data is an array + parameters: + - in: query + name: limit + description: The numbers of items to return per Project + default: 9 + schema: + type: integer + - $ref: 'basic-query-parameter.yaml#/PaginationParameters/page' + - in: path + name: UserId + description: Numeric ID of the user + type: integer + required: true + - in: query + name: threshold + description: Limits the numbers of scores entries to be queried. Prevents overused memory usage. Can be set higher if the resuling limit/page don't show item entries. + default: 500 + schema: + type: integer + responses: + 200: + description: Ok + content: + application/json: + schema: + allOf: + - $ref: 'responses.yaml#/BasicSuccessResponse' + - properties: + data: + type: array + description: Last edited items as array sorted in projects + items: + $ref: 'users-items-schema.yaml#/UserItemsGetResponseSchema' + meta: + type: object + description: Meta data with pagination details + properties: + limit: + type: integer + description: The limit of the entries per page + example: 3 + currentPage: + type: integer + description: Number of the current page + example: 2 + threshold: + type: integer + description: Number of the threshold used for this query + example: 500 + 400: + $ref: 'responses.yaml#/400ErrorResponse' + 401: + $ref: 'responses.yaml#/401ErrorResponse' + 404: + $ref: 'responses.yaml#/404ErrorResponse' From 9f334ba99944f8ce6e81958c656d808a3d7d5801 Mon Sep 17 00:00:00 2001 From: trenc Date: Wed, 27 Nov 2024 11:34:11 +0100 Subject: [PATCH 7/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 ff2f1ce..f16d0c4 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.53.0 + version: 1.54.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.