From f39f1f9eee69586ec0bc925fb61bec3517561e5a Mon Sep 17 00:00:00 2001 From: Tim Slager Date: Thu, 1 Feb 2024 12:16:38 +0100 Subject: [PATCH 1/3] Added and fixed book apis. --- src/app/Http/Controllers/GrantController.php | 62 +++++++++++++ src/app/Http/Controllers/ItemController.php | 89 ++++++++++++++++++- src/app/Http/Kernel.php | 23 ++--- src/app/Http/Middleware/ApiAuthorize.php | 46 ++++++++++ src/app/Http/Requests/ApiStoreItemRequest.php | 32 +++++++ src/app/Http/Requests/GrantItemRequest.php | 28 ++++++ src/app/Http/Requests/SearchItemRequest.php | 4 +- src/app/Models/Author.php | 6 +- src/app/Models/Item.php | 7 +- src/app/Models/LibraryPass.php | 12 +++ src/app/Traits/CommonTrait.php | 30 ++++++- src/database/seeders/DatabaseSeeder.php | 15 +++- src/routes/api.php | 9 +- 13 files changed, 339 insertions(+), 24 deletions(-) create mode 100644 src/app/Http/Middleware/ApiAuthorize.php create mode 100644 src/app/Http/Requests/ApiStoreItemRequest.php create mode 100644 src/app/Http/Requests/GrantItemRequest.php diff --git a/src/app/Http/Controllers/GrantController.php b/src/app/Http/Controllers/GrantController.php index a7ce175..b82c083 100644 --- a/src/app/Http/Controllers/GrantController.php +++ b/src/app/Http/Controllers/GrantController.php @@ -3,11 +3,17 @@ namespace App\Http\Controllers; use App\Enums\ModifiedEnum; +use App\Enums\Permissions; use App\Enums\ResponseStatus; +use App\Http\Requests\GrantItemRequest; use App\Http\Requests\GrantRequest; use App\Models\Grant; +use App\Models\Item; +use App\Models\LibraryPass; +use App\Models\User; use Illuminate\Http\Request; use App\Traits\CommonTrait; +use Illuminate\Support\Env; class GrantController extends Controller { @@ -97,4 +103,60 @@ public function destroy(Grant $grant) 'Grant deleted successfully', null); } + + public function GrantItem(GrantItemRequest $request) { + $validated = $request->validated(); + + $item = Item::where('identifier', $validated['identifier'])->first(); + $library_pass = LibraryPass::where('barcode', $request->bearerToken())->first(); + $user = User::where('id', $library_pass->user_id)->first(); + + if (!isset($user)) { + return $this->CommonResponse( + ResponseStatus::notFound, 'User not found', null + ); + } + + if (!$user->can(Permissions::CREATE_GRANT->value) && Env::get('APP_ENV') != 'local') { + return $this->CommonResponse( + ResponseStatus::unauthorized, 'Unauthorized', null + ); + } + + $grant = Grant::where('user_id', $user->id) + ->where('item_id', $item->id) + ->where('modified_kind', '!=', ModifiedEnum::deleted) + ->first(); + + if (isset($grant)) { + + if ($grant->return_date != null) { + return $this->CommonResponse( + ResponseStatus::forbidden, 'Item already returned', null + ); + } + + $grant->return_date = now(); + $grant->modified_kind = ModifiedEnum::modified; + $grant->modified_user = $user->id; + $grant->save(); + + $message = 'Item returned successfully'; + } else { + $grant = Grant::create([ + 'user_id' => $user->id, + 'item_id' => $item->id, + 'borrowed_date' => now(), + 'return_date' => null, + 'modified_kind' => ModifiedEnum::inserted, + 'modified_user' => $user->id, + ]); + + $message = 'Item granted successfully'; + } + + return $this->CommonResponse( + ResponseStatus::success, $message, $grant + ); + } } diff --git a/src/app/Http/Controllers/ItemController.php b/src/app/Http/Controllers/ItemController.php index e7e70f4..6a7d4ce 100644 --- a/src/app/Http/Controllers/ItemController.php +++ b/src/app/Http/Controllers/ItemController.php @@ -2,16 +2,26 @@ namespace App\Http\Controllers; +use App\Enums\ItemTypes; use App\Enums\ModifiedEnum; +use App\Enums\Permissions; use App\Enums\ResponseStatus; +use App\Http\Requests\ApiStoreItemRequest; use App\Http\Requests\ItemRequest; use App\Http\Requests\SearchItemRequest; +use App\Models\Author; use App\Models\Item; +use App\Models\ItemImage; +use App\Models\LibraryPass; +use App\Models\User; use App\Traits\CommonTrait; +use App\Traits\CommonLibraryTrait; +use Illuminate\Support\Env; +use Illuminate\Support\Facades\Storage; class ItemController extends Controller { - use CommonTrait; + use CommonTrait, CommonLibraryTrait; /** * Display a listing of the resource. */ @@ -101,15 +111,90 @@ public function search(SearchItemRequest $request) { $validated = $request->validated(); $search = $validated['search']; + $library_pass = LibraryPass::where('barcode', $request->bearerToken())->first(); + $user = User::where('id', $library_pass->user_id)->first(); + + if (!$user->can(Permissions::VIEW_ITEM->value) && Env::get('APP_ENV') != 'local') { + return $this->CommonResponse( + ResponseStatus::unauthorized, + 'Permission denied', + null + ); + } + $items = Item::where('name', 'like', "%{$search}%") ->orWhere('description', 'like', "%{$search}%") ->orWhere('ISBN', 'like', "%{$search}%") ->get(); + $authors = Author::where('name', 'like', "%{$search}%")->get(); + + if (count($authors) >= 1) { + foreach ($authors as $author) { + + $author_items = $author->items; + foreach ($author_items as $author_item) { + + $item_exists = false; + foreach ($items as $item) { + if ($item->id == $author_item->id) { + $item_exists = true; + } + } + + if (!$item_exists) { + $items[] = $author_item; + } + + } + } + } + return $this->CommonResponse( ResponseStatus::success, 'Search results', - $items + $this->ReturnMultipleFullItems($items) + ); + } + + public function ApiStore(ApiStoreItemRequest $request) { + $validated = $request->validated(); + $library_pass = LibraryPass::where('barcode', $request->bearerToken())->first(); + $user = User::where('id', $library_pass->user_id)->first(); + + $title = $validated['title']; + $description = $validated['description']; + $ISBN = $validated['ISBN']; + $images = $validated['images']; + + $item = Item::create([ + 'name' => $title, + 'description' => $description, + 'ISBN' => $ISBN, + 'author_id' => 1, + 'identifier' => $this->generateItemIdentifier(), + 'type' => ItemTypes::book, + 'modified_user' => $user->id, + 'modified_kind' => ModifiedEnum::inserted, + ]); + + foreach ($images as $image) { + // store image in storage + $what = $image->storeAs('public/images/', $image->getClientOriginalName()); + + ItemImage::create([ + 'item_id' => $item->id, + 'filename' => $image->getClientOriginalName(), + 'path' => $what, + 'modified_user' => $user->id, + 'modified_kind' => ModifiedEnum::inserted, + ]); + } + + return $this->CommonResponse( + ResponseStatus::success, + 'Item created', + $this->ReturnFullItem($item) ); } } diff --git a/src/app/Http/Kernel.php b/src/app/Http/Kernel.php index fd469a6..1d442e4 100644 --- a/src/app/Http/Kernel.php +++ b/src/app/Http/Kernel.php @@ -54,16 +54,17 @@ class Kernel extends HttpKernel * @var array */ protected $middlewareAliases = [ - 'auth' => \App\Http\Middleware\Authenticate::class, - 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, - 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, - 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, - 'can' => \Illuminate\Auth\Middleware\Authorize::class, - 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, - 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, - 'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class, - 'signed' => \App\Http\Middleware\ValidateSignature::class, - 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, - 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + 'auth' => \App\Http\Middleware\Authenticate::class, + 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, + 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, + 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, + 'can' => \Illuminate\Auth\Middleware\Authorize::class, + 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, + 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, + 'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class, + 'signed' => \App\Http\Middleware\ValidateSignature::class, + 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + 'api.auth' => \App\Http\Middleware\ApiAuthorize::class, ]; } diff --git a/src/app/Http/Middleware/ApiAuthorize.php b/src/app/Http/Middleware/ApiAuthorize.php new file mode 100644 index 0000000..3b76198 --- /dev/null +++ b/src/app/Http/Middleware/ApiAuthorize.php @@ -0,0 +1,46 @@ +bearerToken(); + + if (!isset($pass)) { + return $this->CommonResponse(ResponseStatus::unauthorized, 'Unauthorized. Missing library pass.', null); + } + + $lib_pass = LibraryPass::where('barcode', $pass) + ->where('modified_kind', '!=', ModifiedEnum::deleted)->first(); + + if (!isset($lib_pass)) { + return $this->CommonResponse(ResponseStatus::unauthorized, 'Unauthorized. Invalid library pass.', null); + } + + $user = $lib_pass->user; + + if (!isset($user)) { + return $this->CommonResponse(ResponseStatus::unauthorized, 'Unauthorized. Invalid library pass.', null); + } + + return $next($request); + } +} diff --git a/src/app/Http/Requests/ApiStoreItemRequest.php b/src/app/Http/Requests/ApiStoreItemRequest.php new file mode 100644 index 0000000..865ae1e --- /dev/null +++ b/src/app/Http/Requests/ApiStoreItemRequest.php @@ -0,0 +1,32 @@ +|string> + */ + public function rules(): array + { + return [ + 'title' => 'required|string', + 'description' => 'required|string', + 'ISBN' => 'required|string', + 'images' => 'required|array', + ]; + } +} diff --git a/src/app/Http/Requests/GrantItemRequest.php b/src/app/Http/Requests/GrantItemRequest.php new file mode 100644 index 0000000..3a9a267 --- /dev/null +++ b/src/app/Http/Requests/GrantItemRequest.php @@ -0,0 +1,28 @@ +|string> + */ + public function rules(): array + { + return [ + 'identifier' => 'required|string|max:255', + ]; + } +} diff --git a/src/app/Http/Requests/SearchItemRequest.php b/src/app/Http/Requests/SearchItemRequest.php index 36fcb14..25b45a5 100644 --- a/src/app/Http/Requests/SearchItemRequest.php +++ b/src/app/Http/Requests/SearchItemRequest.php @@ -2,6 +2,7 @@ namespace App\Http\Requests; +use App\Enums\Permissions; use App\Models\Item; use Illuminate\Foundation\Http\FormRequest; @@ -12,8 +13,7 @@ class SearchItemRequest extends FormRequest */ public function authorize(): bool { - $user = auth()->user(); - return $user->can('search', Item::class); + return true; } /** diff --git a/src/app/Models/Author.php b/src/app/Models/Author.php index c686aab..d988ba2 100644 --- a/src/app/Models/Author.php +++ b/src/app/Models/Author.php @@ -7,6 +7,10 @@ class Author extends Model { - use HasFactory; protected $guarded = []; + + public function items() + { + return $this->hasMany(Item::class); + } } diff --git a/src/app/Models/Item.php b/src/app/Models/Item.php index 745befd..dd93bc5 100644 --- a/src/app/Models/Item.php +++ b/src/app/Models/Item.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; class Item extends Model @@ -16,10 +17,14 @@ public function author(): BelongsTo } public function category(): BelongsTo { - return $this->belongsTo(ItemCategory::class); + return $this->BelongsTo(ItemCategory::class, 'category_id'); } public function ageRating(): BelongsTo { return $this->belongsTo(ItemAgeRating::class, 'rating_id'); } + + public function images(): HasMany { + return $this->hasMany(ItemImage::class); + } } diff --git a/src/app/Models/LibraryPass.php b/src/app/Models/LibraryPass.php index f6e7d56..1e56cda 100644 --- a/src/app/Models/LibraryPass.php +++ b/src/app/Models/LibraryPass.php @@ -2,11 +2,23 @@ namespace App\Models; +use App\Enums\ModifiedEnum; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; class LibraryPass extends Model { use HasFactory; protected $guarded = []; + + public function User(): ?BelongsTo + { + $belongsTo = $this->belongsTo(User::class); + $user = $belongsTo->first(); + if ($user->modified_kind == ModifiedEnum::deleted) { + return null; + } + return $belongsTo; + } } diff --git a/src/app/Traits/CommonTrait.php b/src/app/Traits/CommonTrait.php index 35f198d..acc47d1 100644 --- a/src/app/Traits/CommonTrait.php +++ b/src/app/Traits/CommonTrait.php @@ -3,6 +3,8 @@ namespace App\Traits; use App\Enums\ResponseStatus; +use App\Models\Item; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -17,7 +19,7 @@ trait CommonTrait { * @param int $code http status code * @return JsonResponse response formatted in json */ - public function CommonResponse(ResponseStatus $status, string $message, array|null $data): JsonResponse + public function CommonResponse(ResponseStatus $status, string $message, mixed $data): JsonResponse { return response()->json([ 'status' => $status->label(), @@ -25,4 +27,30 @@ public function CommonResponse(ResponseStatus $status, string $message, array|nu 'data' => $data ], $status->getStatusCode()); } + + public function ReturnFullItem(Item $item) + { + $author = $item->author; + $category = $item->category; + $age_rating = $item->ageRating; + $images = $item->images; + + $item->author = $author; + $item->category = $category; + $item->age_rating = $age_rating; + $item->images = $images; + + + return $item; + } + + public function ReturnMultipleFullItems($items) { + $fullItems = []; + + foreach ($items as $item) { + $fullItems[] = $this->ReturnFullItem($item); + } + + return $fullItems; + } } diff --git a/src/database/seeders/DatabaseSeeder.php b/src/database/seeders/DatabaseSeeder.php index 7c31374..79c5fde 100644 --- a/src/database/seeders/DatabaseSeeder.php +++ b/src/database/seeders/DatabaseSeeder.php @@ -3,6 +3,8 @@ namespace Database\Seeders; // use Illuminate\Database\Console\Seeds\WithoutModelEvents; +use App\Enums\ModifiedEnum; +use App\Models\Author; use App\Models\LibraryPass; use App\Models\User; use Illuminate\Database\Seeder; @@ -26,7 +28,7 @@ public function run(): void 'street' => 'Straat', 'zip_code' => '1234AB', 'house_number' => '1', - 'modified_kind' => 'I', + 'modified_kind' => ModifiedEnum::inserted, 'modified_user' => 1, 'created_at' => now(), 'updated_at' => now(), @@ -36,7 +38,7 @@ public function run(): void 'user_id' => $superAdmin->id, 'barcode' => $this->generateValidLibraryPassBarCode(), 'is_active' => true, - 'modified_kind' => 'I', + 'modified_kind' => ModifiedEnum::inserted, 'modified_user' => $superAdmin->id, ]); @@ -59,8 +61,15 @@ public function run(): void 'user_id' => $admin->id, 'barcode' => $this->generateValidLibraryPassBarCode(), 'is_active' => true, - 'modified_kind' => 'I', + 'modified_kind' => ModifiedEnum::inserted, 'modified_user' => $superAdmin->id, ]); + + + Author::create([ + 'name' => 'Unknown', + 'modified_kind' => ModifiedEnum::inserted, + 'modified_user' => $superAdmin->id, + ]); } } diff --git a/src/routes/api.php b/src/routes/api.php index d6d7d4d..7d42822 100644 --- a/src/routes/api.php +++ b/src/routes/api.php @@ -1,8 +1,8 @@ middleware('api.auth'); +Route::post('/users/search', [UserController::class, 'search'])->middleware('api.auth'); + +Route::post('/item/store', [ItemController::class, 'ApiStore'])->middleware('api.auth'); +Route::post('/item/grant', [GrantController::class, 'GrantItem'])->middleware('api.auth'); From 55cac53df8609cd4e2356c7b32fbba73f88d0d5e Mon Sep 17 00:00:00 2001 From: Tim Slager Date: Thu, 1 Feb 2024 13:16:06 +0100 Subject: [PATCH 2/3] Fix: Did an oopsie :) --- src/app/Http/Controllers/GrantController.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/app/Http/Controllers/GrantController.php b/src/app/Http/Controllers/GrantController.php index b82c083..e6bb3eb 100644 --- a/src/app/Http/Controllers/GrantController.php +++ b/src/app/Http/Controllers/GrantController.php @@ -126,16 +126,11 @@ public function GrantItem(GrantItemRequest $request) { $grant = Grant::where('user_id', $user->id) ->where('item_id', $item->id) ->where('modified_kind', '!=', ModifiedEnum::deleted) + ->where('return_date', null) ->first(); if (isset($grant)) { - if ($grant->return_date != null) { - return $this->CommonResponse( - ResponseStatus::forbidden, 'Item already returned', null - ); - } - $grant->return_date = now(); $grant->modified_kind = ModifiedEnum::modified; $grant->modified_user = $user->id; From f5359f38ca2efc70d339547e9a4ae276c9e6faaf Mon Sep 17 00:00:00 2001 From: Tim Slager Date: Thu, 1 Feb 2024 13:23:21 +0100 Subject: [PATCH 3/3] added grants to items search --- src/app/Http/Controllers/ItemController.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/app/Http/Controllers/ItemController.php b/src/app/Http/Controllers/ItemController.php index 6a7d4ce..7c1a5aa 100644 --- a/src/app/Http/Controllers/ItemController.php +++ b/src/app/Http/Controllers/ItemController.php @@ -10,6 +10,7 @@ use App\Http\Requests\ItemRequest; use App\Http\Requests\SearchItemRequest; use App\Models\Author; +use App\Models\Grant; use App\Models\Item; use App\Models\ItemImage; use App\Models\LibraryPass; @@ -150,6 +151,13 @@ public function search(SearchItemRequest $request) { } } + foreach ($items as $item) { + $item->grants = Grant::where('item_id', $item->id) + ->where('modified_kind', '!=', ModifiedEnum::deleted) + ->where('return_date', null) + ->get(); + } + return $this->CommonResponse( ResponseStatus::success, 'Search results',