diff --git a/.dockerignore b/.dockerignore index 6f1b388..42de543 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,9 @@ .docker/nginx.conf -/coverage \ No newline at end of file +.env +.gitattributes +.gitignore +.phpunit.result.cache +/coverage +/vendor +LICENSE +README.md \ No newline at end of file diff --git a/.env.docker b/.env.docker index 1730ba0..7e32e93 100644 --- a/.env.docker +++ b/.env.docker @@ -10,7 +10,7 @@ LOG_LEVEL=debug DB_CONNECTION=mysql DB_HOST=db -DB_PORT=3306 +DB_PORT=3307 DB_DATABASE=web_blog DB_USERNAME=laraveluser DB_PASSWORD=laraveluserpass diff --git a/Dockerfile b/Dockerfile index 967fc9d..9c099b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,4 +9,5 @@ RUN docker-php-ext-enable pdo pdo_mysql mysqli RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer -COPY . . \ No newline at end of file +COPY . . +COPY .env.docker .env \ No newline at end of file diff --git a/app/Helpers/AppHelper.php b/app/Helpers/AppHelper.php index db2315a..fadf86f 100644 --- a/app/Helpers/AppHelper.php +++ b/app/Helpers/AppHelper.php @@ -2,9 +2,11 @@ namespace App\Helpers; +use Image; + class AppHelper { - public static function randStr($length = 10): string + public static function randStr($length = 15): string { $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $randomString = ''; @@ -20,4 +22,13 @@ public static function randEmail(): string { return self::randStr() . '@gmail.com'; } + + public static function uploadImage($image, $folder): string + { + $imageName = $folder . '/' . self::randStr() . '.' . $image->getClientOriginalExtension(); + $imageDir = public_path('storage/' . $imageName); + Image::make($image)->resize(980, 980)->save($imageDir); + + return $imageName; + } } diff --git a/app/Http/Controllers/Api/V1/AuthController.php b/app/Http/Controllers/Api/V1/AuthController.php index 7732b38..8ee4787 100644 --- a/app/Http/Controllers/Api/V1/AuthController.php +++ b/app/Http/Controllers/Api/V1/AuthController.php @@ -2,20 +2,21 @@ namespace App\Http\Controllers\Api\V1; -use App\Http\Controllers\Controller; +use App\Http\Controllers\BaseController; use App\Http\Requests\LoginAuthRequest; use App\Http\Requests\RegisterAuthRequest; -use App\Http\Resources\V1\UserResource; +use App\Http\Resources\V1\AuthResource; use App\Models\User; -use Illuminate\Support\Facades\Auth; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; -class AuthController extends Controller +class AuthController extends BaseController { /** * Register a new user. */ - public function register(RegisterAuthRequest $request): UserResource + public function register(RegisterAuthRequest $request): JsonResponse { $user = User::create([ 'name' => $request->name, @@ -23,48 +24,47 @@ public function register(RegisterAuthRequest $request): UserResource 'password'=> Hash::make($request->password), ]); - Auth::login($user); + auth()->login($user); - return new UserResource([ + return $this->sendResponse(new AuthResource([ 'user' => $user, 'token' => $user->createToken('user_token', [ - 'blog:store', - 'blog:update', - 'blog:delete', - 'category:store', - 'category:update', - 'category:delete', + 'blog:store', 'blog:update', 'blog:delete', + 'category:store', 'category:update', 'category:delete', ])->plainTextToken, - ]); + ]), 'Registered successfully'); } /** * Login a user. */ - public function login(LoginAuthRequest $request) + public function login(LoginAuthRequest $request): JsonResponse { $user = User::where('email', $request->email)->first(); - if ($user && Hash::check($request->password, $user->password)) { - return new UserResource([ - 'user' => $user, - 'token' => $user->createToken('user_token', [ - 'blog:store', - 'blog:update', - 'blog:delete', - 'category:store', - 'category:update', - 'category:delete', - ])->plainTextToken, - ]); + if (!$user || !Hash::check($request->password, $user->password)) { + return $this->sendError('The provided email or password are incorrect.', 401); } + + auth()->login($user); + + return $this->sendResponse(new AuthResource([ + 'user' => $user, + 'token' => $user->createToken('user_token', [ + 'blog:store', 'blog:update', 'blog:delete', + 'category:store', 'category:update', 'category:delete', + ])->plainTextToken, + ]), 'Logged in successfully'); } /** * Logout a user. */ - public function logout(): void + public function logout(Request $request): JsonResponse { - auth()->user()->tokens()->delete(); + auth('sanctum')->user()->tokens()->delete(); + $request->session()->invalidate(); + + return $this->sendResponse([], 'Logged out successfully'); } } diff --git a/app/Http/Controllers/Api/V1/BlogController.php b/app/Http/Controllers/Api/V1/BlogController.php index 0faa9e6..3ac00bb 100644 --- a/app/Http/Controllers/Api/V1/BlogController.php +++ b/app/Http/Controllers/Api/V1/BlogController.php @@ -3,7 +3,7 @@ namespace App\Http\Controllers\Api\V1; use App\Filters\V1\BlogFilter; -use App\Http\Controllers\Controller; +use App\Http\Controllers\BaseController; use App\Http\Requests\BulkStoreBlogRequest; use App\Http\Requests\DeleteBlogRequest; use App\Http\Requests\StoreBlogRequest; @@ -12,31 +12,35 @@ use App\Http\Resources\V1\BlogResource; use App\Models\Blog; use Elastic\Elasticsearch\ClientBuilder; -use Exception; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Illuminate\Support\Facades\DB; -class BlogController extends Controller +class BlogController extends BaseController { /** * Display a listing of the resource. */ - public function index(Request $request): BlogCollection + public function index(Request $request): JsonResponse { $query = (new BlogFilter())->transform($request); - $blogs = Blog::where($query)->with('category'); + $blogs = Blog::where($query)->with(['user', 'category']) + ->paginate(5)->appends($request->query()); - return new BlogCollection($blogs->paginate(5)->appends($request->query())); + return $this->sendResponse( + new BlogCollection($blogs), + 'Blogs retrieved successfully'); } /** * Search a listing of the resource. */ - public function search(Request $request, string $keyword) + public function search(Request $request) { $elastic = $request->test ?? true; + $keyword = $request->keyword; + $ids = []; - if ($elastic) { + if ($elastic && isset($keyword)) { $client = ClientBuilder::create() ->setHosts([env('ELASTICSEARCH_HOST')]) ->build(); @@ -53,22 +57,26 @@ public function search(Request $request, string $keyword) ]; $response = $client->search($params); - return $response->asArray(); + foreach ($response['hits']['hits'] as $hit) { + $ids[] = $hit['_id']; + } } - } - /** - * Show the form for creating a new resource. - */ - public function create(): void - { - // + $blogs = Blog::whereIn('id', $ids)->with(['user', 'category']) + ->paginate(5)->appends($request->query()) + ->sortBy(function($model) use ($ids) { + return array_search($model->getKey(), $ids); + }); + + return $this->sendResponse( + new BlogCollection($blogs), + 'Blogs retrieved successfully'); } /** * Store a newly created resource in storage. */ - public function store(StoreBlogRequest $request): BlogResource + public function store(StoreBlogRequest $request): JsonResponse { $blog = Blog::create($request->all()); @@ -90,13 +98,15 @@ public function store(StoreBlogRequest $request): BlogResource $client->index($params); } - return new BlogResource($blog); + return $this->sendResponse( + new BlogResource($blog), + 'Blog created successfully', 201); } /** * Store some newly created resource in storage. */ - public function bulkStore(BulkStoreBlogRequest $request): void + public function bulkStore(BulkStoreBlogRequest $request): JsonResponse { $bulk = collect($request->all())->map(function ($arr, $key) { return [ @@ -108,28 +118,24 @@ public function bulkStore(BulkStoreBlogRequest $request): void }); Blog::insert($bulk->toArray()); - } - /** - * Display the specified resource. - */ - public function show(Blog $blog): BlogResource - { - return new BlogResource($blog->loadMissing('category')); + return $this->sendResponse([], 'Blog created successfully', 201); } /** - * Show the form for editing the specified resource. + * Display the specified resource. */ - public function edit(Blog $blog): void + public function show(Blog $blog): JsonResponse { - // + return $this->sendResponse( + new BlogResource($blog->loadMissing('category')), + 'Blog retrieved successfully'); } /** * Update the specified resource in storage. */ - public function update(UpdateBlogRequest $request, Blog $blog): BlogResource + public function update(UpdateBlogRequest $request, Blog $blog): JsonResponse { $blog->update($request->all()); @@ -153,13 +159,15 @@ public function update(UpdateBlogRequest $request, Blog $blog): BlogResource $client->update($params); } - return new BlogResource($blog->loadMissing('category')); + return $this->sendResponse( + new BlogResource($blog->loadMissing('category')), + 'Blog updated successfully'); } /** * Remove the specified resource from storage. */ - public function destroy(DeleteBlogRequest $request, Blog $blog): void + public function destroy(DeleteBlogRequest $request, Blog $blog): JsonResponse { $elastic = $request->test ?? true; @@ -177,5 +185,7 @@ public function destroy(DeleteBlogRequest $request, Blog $blog): void } $blog->delete(); + + return $this->sendResponse([], 'Blog deleted successfully'); } } diff --git a/app/Http/Controllers/Api/V1/CategoryController.php b/app/Http/Controllers/Api/V1/CategoryController.php index 0d38496..6770532 100644 --- a/app/Http/Controllers/Api/V1/CategoryController.php +++ b/app/Http/Controllers/Api/V1/CategoryController.php @@ -3,75 +3,70 @@ namespace App\Http\Controllers\Api\V1; use App\Filters\V1\CategoryFilter; -use App\Http\Controllers\Controller; +use App\Http\Controllers\BaseController; use App\Http\Requests\DeleteCategoryRequest; use App\Http\Requests\StoreCategoryRequest; use App\Http\Requests\UpdateCategoryRequest; use App\Http\Resources\V1\CategoryCollection; use App\Http\Resources\V1\CategoryResource; use App\Models\Category; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -class CategoryController extends Controller +class CategoryController extends BaseController { /** * Display a listing of the resource. */ - public function index(Request $request): CategoryCollection + public function index(Request $request): JsonResponse { $query = (new CategoryFilter())->transform($request); $categories = Category::where($query); - return new CategoryCollection($categories->paginate(5)->appends($request->query())); - } - - /** - * Show the form for creating a new resource. - */ - public function create(): void - { - // + return $this->sendResponse( + new CategoryCollection($categories->paginate(5)->appends($request->query())), + 'Categories retrieved successfully'); } /** * Store a newly created resource in storage. */ - public function store(StoreCategoryRequest $request): CategoryResource + public function store(StoreCategoryRequest $request): JsonResponse { - return new CategoryResource(Category::create($request->all())); + return $this->sendResponse( + new CategoryResource(Category::create($request->all())), + 'Category created successfully', 201); } /** * Display the specified resource. */ - public function show(Category $category): CategoryResource + public function show(Category $category): JsonResponse { - return new CategoryResource($category); - } - - /** - * Show the form for editing the specified resource. - */ - public function edit(Category $category): void - { - // + return $this->sendResponse( + new CategoryResource($category), + 'Category retrieved successfully'); } /** * Update the specified resource in storage. */ - public function update(UpdateCategoryRequest $request, Category $category): CategoryResource + public function update(UpdateCategoryRequest $request, Category $category): JsonResponse { $category->update($request->all()); - return new CategoryResource($category); + return $this->sendResponse( + new CategoryResource($category), + 'Category updated successfully'); } /** * Remove the specified resource from storage. */ - public function destroy(DeleteCategoryRequest $request, Category $category): void + public function destroy(DeleteCategoryRequest $request, Category $category): JsonResponse { $category->delete(); + + return $this->sendResponse([], 'Category deleted successfully'); } } diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php new file mode 100644 index 0000000..5eeb54c --- /dev/null +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -0,0 +1,24 @@ +update($request->all()); + + return $this->sendResponse( + new UserResource($user), + 'User updated successfully'); + } +} diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php new file mode 100644 index 0000000..318dfed --- /dev/null +++ b/app/Http/Controllers/BaseController.php @@ -0,0 +1,25 @@ +json([ + 'success' => true, + 'message' => $message, + 'data' => $result, + ], $code); + } + + public function sendError($error, $code = 404): JsonResponse + { + return response()->json([ + 'status' => false, + 'message' => $error, + ], $code); + } +} diff --git a/app/Http/Controllers/Web/V1/AuthController.php b/app/Http/Controllers/Web/V1/AuthController.php new file mode 100644 index 0000000..bd403c3 --- /dev/null +++ b/app/Http/Controllers/Web/V1/AuthController.php @@ -0,0 +1,33 @@ + $blog, + ]); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Blog $blog): View + { + return view('blog.edit', [ + 'blog' => $blog, + ]); + } +} diff --git a/app/Http/Controllers/Web/V1/CategoryController.php b/app/Http/Controllers/Web/V1/CategoryController.php new file mode 100644 index 0000000..91171cc --- /dev/null +++ b/app/Http/Controllers/Web/V1/CategoryController.php @@ -0,0 +1,36 @@ + $category, + ]); + } +} diff --git a/app/Http/Controllers/Web/V1/UserController.php b/app/Http/Controllers/Web/V1/UserController.php new file mode 100644 index 0000000..e3669d7 --- /dev/null +++ b/app/Http/Controllers/Web/V1/UserController.php @@ -0,0 +1,20 @@ + $user, + ]); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 7a3930b..3e39d7e 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -21,6 +21,7 @@ class Kernel extends HttpKernel \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + \Illuminate\Session\Middleware\StartSession::class, ]; /** diff --git a/app/Http/Requests/DeleteCategoryRequest.php b/app/Http/Requests/DeleteCategoryRequest.php index ede57f1..45bde86 100644 --- a/app/Http/Requests/DeleteCategoryRequest.php +++ b/app/Http/Requests/DeleteCategoryRequest.php @@ -13,8 +13,7 @@ public function authorize(): bool { $user = $this->user(); - return isset($user) - && $user->tokenCan('category:delete'); + return isset($user) && $user->tokenCan('category:delete'); } /** diff --git a/app/Http/Requests/LoginAuthRequest.php b/app/Http/Requests/LoginAuthRequest.php index e6ed5d5..bdae0b4 100644 --- a/app/Http/Requests/LoginAuthRequest.php +++ b/app/Http/Requests/LoginAuthRequest.php @@ -24,7 +24,6 @@ public function rules(): array return [ 'email' => ['required', 'email'], 'password' => ['required', 'min:6', 'max:20'], - 'remember' => ['nullable', 'boolean'], ]; } diff --git a/app/Http/Requests/UpdateCategoryRequest.php b/app/Http/Requests/UpdateCategoryRequest.php index 716dee6..577828b 100644 --- a/app/Http/Requests/UpdateCategoryRequest.php +++ b/app/Http/Requests/UpdateCategoryRequest.php @@ -13,8 +13,7 @@ public function authorize(): bool { $user = $this->user(); - return isset($user) - && $user->tokenCan('category:update'); + return isset($user) && $user->tokenCan('category:update'); } /** diff --git a/app/Http/Requests/UpdateUserRequest.php b/app/Http/Requests/UpdateUserRequest.php new file mode 100644 index 0000000..cd46b8e --- /dev/null +++ b/app/Http/Requests/UpdateUserRequest.php @@ -0,0 +1,49 @@ +all()); + $user = $this->user(); + + return isset($user) && $user->id == $this->route('user')->id; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + 'name' => ['sometimes'], + 'password' => ['nullable', 'min:6', 'max:20', 'confirmed'], + 'avatarFile' => ['sometimes', 'image', 'mimes:jpeg,png,jpg,gif,svg', 'max:2048'], + ]; + } + + protected function prepareForValidation(): void + { + dd($this->all()); + if ($this->hasFile('avatarFile')) { + $this->merge([ + 'avatar' => AppHelper::uploadImage($this->file('avatarFile'), 'users'), + ]); + } + foreach ($this->all() as $key => $value) { + if (is_null($value) || $value == '') { + unset($this[$key]); + } + } + } +} diff --git a/app/Http/Resources/V1/AuthResource.php b/app/Http/Resources/V1/AuthResource.php new file mode 100644 index 0000000..f1c78fa --- /dev/null +++ b/app/Http/Resources/V1/AuthResource.php @@ -0,0 +1,22 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'user' => new UserResource($this['user']), + 'token' => $this['token'], + ]; + } +} diff --git a/app/Http/Resources/V1/BlogResource.php b/app/Http/Resources/V1/BlogResource.php index 49b97af..835679a 100644 --- a/app/Http/Resources/V1/BlogResource.php +++ b/app/Http/Resources/V1/BlogResource.php @@ -14,14 +14,12 @@ class BlogResource extends JsonResource */ public function toArray(Request $request): array { - // return parent::toArray($request); - return [ 'id' => $this->id, 'title' => $this->title, 'body' => $this->body, - 'user_id' => $this->user_id, - 'category_id' => $this->category_id, + 'image' => $this->image, + 'user' => new UserResource($this->whenLoaded('user')), 'category' => new CategoryResource($this->whenLoaded('category')), ]; } diff --git a/app/Http/Resources/V1/CategoryResource.php b/app/Http/Resources/V1/CategoryResource.php index 93587e3..76ffb11 100644 --- a/app/Http/Resources/V1/CategoryResource.php +++ b/app/Http/Resources/V1/CategoryResource.php @@ -14,8 +14,6 @@ class CategoryResource extends JsonResource */ public function toArray(Request $request): array { - // return parent::toArray($request); - return [ 'id' => $this->id, 'name' => $this->name, diff --git a/app/Http/Resources/V1/UserResource.php b/app/Http/Resources/V1/UserResource.php index b340c44..5ca34b9 100644 --- a/app/Http/Resources/V1/UserResource.php +++ b/app/Http/Resources/V1/UserResource.php @@ -14,15 +14,11 @@ class UserResource extends JsonResource */ public function toArray(Request $request): array { - // return parent::toArray($request); - return [ - 'user' => [ - 'id' => $this['user']->id, - 'name' => $this['user']->name, - 'email' => $this['user']->email, - ], - 'token' => $this['token'], + 'id' => $this->id, + 'name' => $this->name, + 'email' => $this->email, + 'avatar' => $this->avatar, ]; } } diff --git a/app/Models/User.php b/app/Models/User.php index 8b2a157..d8874f6 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -22,6 +22,7 @@ class User extends Authenticatable 'name', 'email', 'password', + 'avatar', ]; /** diff --git a/composer.json b/composer.json index 7a8aa7d..c3de16a 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "elasticsearch/elasticsearch": "^8.10", "guzzlehttp/guzzle": "^7.2", "laravel/framework": "^10.10", - "laravel/sanctum": "^3.2", + "laravel/sanctum": "^3.3", "laravel/tinker": "^2.8" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 920fe02..ae0b33e 100644 --- a/composer.lock +++ b/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": "a4478607b7c989b5ea96436c58458f3d", + "content-hash": "afa6a1ba04472c032a71c84c4a47571b", "packages": [ { "name": "brick/math", diff --git a/config/auth.php b/config/auth.php index 9548c15..160f0bb 100644 --- a/config/auth.php +++ b/config/auth.php @@ -40,6 +40,10 @@ 'driver' => 'session', 'provider' => 'users', ], + 'api' => [ + 'driver' => 'sanctum', + 'provider' => 'users', + ], ], /* diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index 444fafb..c39e9b3 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -17,6 +17,7 @@ public function up(): void $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); + $table->string('avatar')->default('users/default_avatar.jpg'); $table->rememberToken(); $table->timestamps(); }); diff --git a/database/migrations/2023_10_04_030255_create_blogs_table.php b/database/migrations/2023_10_04_030255_create_blogs_table.php index 65acd47..0f642fe 100644 --- a/database/migrations/2023_10_04_030255_create_blogs_table.php +++ b/database/migrations/2023_10_04_030255_create_blogs_table.php @@ -15,6 +15,7 @@ public function up(): void $table->id(); $table->string('title'); $table->text('body'); + $table->string('image')->default('blogs/default_blog_image.png'); $table->integer('user_id'); $table->integer('category_id'); $table->timestamps(); diff --git a/docker-compose.yml b/docker-compose.yml index 3b54033..7627fe1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,8 +13,8 @@ services: networks: - laravel working_dir: /var/www/html - volumes: - - ./:/var/www/html + # volumes: + # - ./:/var/www/html depends_on: - db @@ -30,7 +30,7 @@ services: - .docker/nginx.conf:/etc/nginx/conf.d/default.conf db: - image: mysql:5.7 + image: mysql:8.1 restart: unless-stopped ports: - "${DB_PORT}:${DB_PORT}" diff --git a/phpunit.xml b/phpunit.xml index 5478043..f112c0c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -9,9 +9,7 @@ tests/Unit - tests/Feature/Http/Controllers/Api/V1/CategoryTest.php - tests/Feature/Http/Controllers/Api/V1/BlogTest.php - tests/Feature/Http/Controllers/Api/V1/AuthTest.php + tests/Feature diff --git a/public/css/styles.css b/public/css/styles.css index b5793a4..5e79519 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -1,5 +1,6 @@ a { text-decoration: none; + color: inherit; } ul { diff --git a/resources/views/auth/dashboard.blade.php b/resources/views/auth/dashboard.blade.php index 4c88fe5..06f0800 100644 --- a/resources/views/auth/dashboard.blade.php +++ b/resources/views/auth/dashboard.blade.php @@ -2,6 +2,6 @@ @section('content')

{{ config('app.name') }}

-

This awesome blog has many posts, click the button below to see them

+

Welcome to my blog website. Enjoy your time with my awesome blogs.

@endsection \ No newline at end of file diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index d247896..ba51bfc 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -4,7 +4,7 @@
Login
-
+ @csrf
@@ -45,4 +45,24 @@
+@endsection +@section('scripts') + @endsection \ No newline at end of file diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php index 806ca88..d9dabda 100644 --- a/resources/views/auth/register.blade.php +++ b/resources/views/auth/register.blade.php @@ -4,7 +4,7 @@
Register
- + @csrf
@@ -36,6 +36,16 @@

+
+ +
+ + @if ($errors->has('password_confirmation')) + {{ $errors->first('password_confirmation') }} + @endif +
+
+
@@ -55,4 +65,24 @@
+@endsection +@section('scripts') + @endsection \ No newline at end of file diff --git a/resources/views/blog/create.blade.php b/resources/views/blog/create.blade.php new file mode 100644 index 0000000..52dcdc2 --- /dev/null +++ b/resources/views/blog/create.blade.php @@ -0,0 +1,82 @@ +@extends('layouts.app') +@section('content') + Go back +
+

Create Blog

+

Fill and submit this form to create a blog

+
+ + @csrf +
+
+ + + @if ($errors->has('title')) + {{ $errors->first('title') }} + @endif +
+
+ + + @if ($errors->has('body')) + {{ $errors->first('body') }} + @endif +
+
+ + + @if ($errors->has('category')) + {{ $errors->first('category') }} + @endif +
+
+
+
+ +
+
+ +
+@endsection +@section('scripts') + +@endsection \ No newline at end of file diff --git a/resources/views/blog/edit.blade.php b/resources/views/blog/edit.blade.php new file mode 100644 index 0000000..e0174c6 --- /dev/null +++ b/resources/views/blog/edit.blade.php @@ -0,0 +1,95 @@ +@extends('layouts.app') +@section('content') + Go back +
+

Edit Blog

+

Edit and submit this form to update a blog

+
+
+ @csrf + @method('PUT') +
+
+ + + @if ($errors->has('title')) + {{ $errors->first('title') }} + @endif +
+
+ + + @if ($errors->has('body')) + {{ $errors->first('body') }} + @endif +
+
+ + + @if ($errors->has('category')) + {{ $errors->first('category') }} + @endif +
+
+
+
+ +
+
+
+
+@endsection +@section('scripts') + +@endsection \ No newline at end of file diff --git a/resources/views/blog/index.blade.php b/resources/views/blog/index.blade.php new file mode 100644 index 0000000..2526d65 --- /dev/null +++ b/resources/views/blog/index.blade.php @@ -0,0 +1,96 @@ +@extends('layouts.app') +@section('content') +
+
+

Blogs

+
+ @auth +
+ Add Blog +
+ @endauth +
+ @csrf +
+ +
+ +
+
+
+
+
+@endsection +@section('scripts') + +@endsection \ No newline at end of file diff --git a/resources/views/blog/show.blade.php b/resources/views/blog/show.blade.php new file mode 100644 index 0000000..5b7df86 --- /dev/null +++ b/resources/views/blog/show.blade.php @@ -0,0 +1,48 @@ +@extends('layouts.app') +@section('content') + Go back +
+
+ @if (auth('sanctum')->user() && auth('sanctum')->user()->id == $blog->user_id) + Edit Post +

+
+ @method('DELETE') + @csrf + +
+ @endif +@endsection +@section('scripts') + +@endsection \ No newline at end of file diff --git a/resources/views/category/create.blade.php b/resources/views/category/create.blade.php index 65546c4..fba51ea 100644 --- a/resources/views/category/create.blade.php +++ b/resources/views/category/create.blade.php @@ -5,7 +5,7 @@

Create Category

Fill and submit this form to create a category


-
+ @csrf
@@ -19,11 +19,31 @@
-
+@endsection +@section('scripts') + @endsection \ No newline at end of file diff --git a/resources/views/category/edit.blade.php b/resources/views/category/edit.blade.php index 2dde121..96a2726 100644 --- a/resources/views/category/edit.blade.php +++ b/resources/views/category/edit.blade.php @@ -5,14 +5,14 @@

Edit Category

Edit and submit this form to update a category


-
+ @csrf @method('PUT')
- + @if ($errors->has('name')) {{ $errors->first('name') }} @endif @@ -28,7 +28,7 @@
-
+ @method('DELETE') @csrf @@ -36,4 +36,50 @@
+@endsection +@section('scripts') + @endsection \ No newline at end of file diff --git a/resources/views/category/index.blade.php b/resources/views/category/index.blade.php index 4020f21..4057e24 100644 --- a/resources/views/category/index.blade.php +++ b/resources/views/category/index.blade.php @@ -1,24 +1,44 @@ @extends('layouts.app') @section('content')
-
-

List of Categories

-
-
-

Create new Category

- Add Category +
+

Categories

+ @auth + + @endauth
- @foreach($categories as $category) -
    -
  • - {{ ucfirst($category->name) }} - - - -
  • -
- @empty -

No Categories available

- @endforeach +
+@endsection +@section('scripts') + @endsection \ No newline at end of file diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index a77e02e..c4ecc0d 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -4,6 +4,7 @@ + {{ config('app.name') }} - + + @include('layouts.navbar') @@ -26,4 +28,5 @@ + @yield('scripts') \ No newline at end of file diff --git a/resources/views/layouts/navbar.blade.php b/resources/views/layouts/navbar.blade.php index c9a4086..c09ea47 100644 --- a/resources/views/layouts/navbar.blade.php +++ b/resources/views/layouts/navbar.blade.php @@ -7,23 +7,44 @@ - @if (Auth::guest()) - Login - Register - @else - Hello {{ Auth::user()->name }}, - - Log out - - + @auth + User Avatar + Hello {{ auth('sanctum')->user()->name }}, + Profile + @csrf + - @endif + @else + Login + Register + @endauth
- \ No newline at end of file + + \ No newline at end of file diff --git a/resources/views/post/create.blade.php b/resources/views/post/create.blade.php deleted file mode 100644 index 56dd5b7..0000000 --- a/resources/views/post/create.blade.php +++ /dev/null @@ -1,51 +0,0 @@ -@extends('layouts.app') -@section('content') - Go back -
-

Create Post

-

Fill and submit this form to create a post

-
-
- @csrf -
-
- - - @if ($errors->has('title')) - {{ $errors->first('title') }} - @endif -
-
- - - @if ($errors->has('body')) - {{ $errors->first('body') }} - @endif -
-
- - - @if ($errors->has('category')) - {{ $errors->first('category') }} - @endif -
-
-
-
- -
-
-
-
-@endsection \ No newline at end of file diff --git a/resources/views/post/edit.blade.php b/resources/views/post/edit.blade.php deleted file mode 100644 index 86d07f6..0000000 --- a/resources/views/post/edit.blade.php +++ /dev/null @@ -1,58 +0,0 @@ -@extends('layouts.app') -@section('content') - Go back -
-

Edit Post

-

Edit and submit this form to update a post

-
-
- @csrf - @method('PUT') -
-
- - - @if ($errors->has('title')) - {{ $errors->first('title') }} - @endif -
-
- - - @if ($errors->has('body')) - {{ $errors->first('body') }} - @endif -
-
- - - @if ($errors->has('category')) - {{ $errors->first('category') }} - @endif -
-
-
-
- -
-
-
-
-@endsection \ No newline at end of file diff --git a/resources/views/post/index.blade.php b/resources/views/post/index.blade.php deleted file mode 100644 index 2a923f1..0000000 --- a/resources/views/post/index.blade.php +++ /dev/null @@ -1,19 +0,0 @@ -@extends('layouts.app') -@section('content') -
-
-

List of Blogs

-
-
-

Create new Post

- Add Post -
-
- @foreach($posts as $post) - - @empty -

No blog Posts available

- @endforeach -@endsection \ No newline at end of file diff --git a/resources/views/post/show.blade.php b/resources/views/post/show.blade.php deleted file mode 100644 index 2a072ae..0000000 --- a/resources/views/post/show.blade.php +++ /dev/null @@ -1,14 +0,0 @@ -@extends('layouts.app') -@section('content') - Go back -

{{ ucfirst($post->title) }}

-

{!! $post->body !!}

-
- Edit Post -

-
- @method('DELETE') - @csrf - -
-@endsection \ No newline at end of file diff --git a/resources/views/user/show.blade.php b/resources/views/user/show.blade.php new file mode 100644 index 0000000..1d0324e --- /dev/null +++ b/resources/views/user/show.blade.php @@ -0,0 +1,115 @@ +@extends('layouts.app') +@section('content') + Dashboard +
+

Info User {{ $user->name }}

+
+
+ @csrf + @method('PATCH') +
+
+ User Avatar + + @if (auth('sanctum')->user() && auth('sanctum')->user()->id == $user->id) + + @else + + @endif + @if ($errors->has('avatar')) + {{ $errors->first('avatar') }} + @endif +
+
+
+ + @if (auth('sanctum')->user() && auth('sanctum')->user()->id == $user->id) + + @else + + @endif + @if ($errors->has('name')) + {{ $errors->first('name') }} + @endif +
+
+ + + @if ($errors->has('email')) + {{ $errors->first('email') }} + @endif +
+
+ + @if (auth('sanctum')->user() && auth('sanctum')->user()->id == $user->id) + + @else + + @endif + @if ($errors->has('password')) + {{ $errors->first('password') }} + @endif +
+
+ + @if (auth('sanctum')->user() && auth('sanctum')->user()->id == $user->id) + + @else + + @endif + @if ($errors->has('confirm_password')) + {{ $errors->first('confirm_password') }} + @endif +
+
+
+ @if (auth('sanctum')->user() && auth('sanctum')->user()->id == $user->id) +
+
+ +
+
+ @endif +
+
+@endsection +@section('scripts') + +@endsection \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index aa70d5d..c40ab68 100644 --- a/routes/api.php +++ b/routes/api.php @@ -3,6 +3,7 @@ use App\Http\Controllers\Api\V1\AuthController; use App\Http\Controllers\Api\V1\BlogController; use App\Http\Controllers\Api\V1\CategoryController; +use App\Http\Controllers\Api\V1\UserController; use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; @@ -21,38 +22,43 @@ return $request->user(); }); -// , 'middleware' => 'auth:sanctum' -Route::group(['prefix' => 'v1', 'namespace' => 'App\Http\Controllers\Api\V1'], function() { - Route::post('/register', [AuthController::class, 'register']); - Route::post('/login', [AuthController::class, 'login']); +Route::prefix('v1')->name('api.')->group(function() { + Route::post('/register', [AuthController::class, 'register'])->name('register'); + Route::post('/login', [AuthController::class, 'login'])->name('login'); Route::group(['middleware' => 'auth:sanctum'], function () { - Route::post('/logout', [AuthController::class, 'logout']); + Route::post('/logout', [AuthController::class, 'logout'])->name('logout'); + }); + + Route::prefix('/user')->group(function () { + Route::group(['middleware' => 'auth:sanctum'], function () { + Route::patch('/{user}', [UserController::class, 'update'])->name('user.update'); + }); }); Route::prefix('/blog')->group(function () { - Route::get('/', [BlogController::class, 'index']); - Route::get('/search/{keyword}', [BlogController::class, 'search']); - Route::get('/{blog}', [BlogController::class, 'show']); + Route::get('/', [BlogController::class, 'index'])->name('blog.index'); + Route::get('/search', [BlogController::class, 'search'])->name('blog.search'); + Route::get('/{blog}', [BlogController::class, 'show'])->name('blog.show'); Route::group(['middleware' => 'auth:sanctum'], function () { - Route::post('/', [BlogController::class, 'store']); - Route::post('/bulk', [BlogController::class, 'bulkStore']); - Route::put('/{blog}', [BlogController::class, 'update']); - Route::patch('/{blog}', [BlogController::class, 'update']); - Route::delete('/{blog}', [BlogController::class, 'destroy']); + Route::post('/', [BlogController::class, 'store'])->name('blog.store'); + Route::post('/bulk', [BlogController::class, 'bulkStore'])->name('blog.bulk-store'); + Route::put('/{blog}', [BlogController::class, 'update'])->name('blog.update'); + Route::patch('/{blog}', [BlogController::class, 'update'])->name('blog.update'); + Route::delete('/{blog}', [BlogController::class, 'destroy'])->name('blog.destroy'); }); }); Route::prefix('/category')->group(function() { - Route::get('/', [CategoryController::class, 'index']); - Route::get('/{category}', [CategoryController::class, 'show']); + Route::get('/', [CategoryController::class, 'index'])->name('category.index'); + Route::get('/{category}', [CategoryController::class, 'show'])->name('category.show'); Route::group(['middleware' => 'auth:sanctum'], function () { - Route::post('/', [CategoryController::class, 'store']); - Route::put('/{category}', [CategoryController::class, 'update']); - Route::patch('/{category}', [CategoryController::class, 'update']); - Route::delete('/{category}', [CategoryController::class, 'destroy']); + Route::post('/', [CategoryController::class, 'store'])->name('category.store'); + Route::put('/{category}', [CategoryController::class, 'update'])->name('category.update'); + Route::patch('/{category}', [CategoryController::class, 'update'])->name('category.update'); + Route::delete('/{category}', [CategoryController::class, 'destroy'])->name('category.destroy'); }); }); }); diff --git a/routes/web.php b/routes/web.php index 2a3bf1f..7f634dc 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,8 +1,9 @@ name('dashboard'); -Route::get('/login', [AuthController::class, 'loginForm'])->name('login-form'); -Route::post('/login', [AuthController::class, 'login'])->name('login'); -Route::post('/logout', [AuthController::class, 'logout'])->name('logout'); -Route::get('/register', [AuthController::class, 'registerForm'])->name('register-form'); -Route::post('/register', [AuthController::class, 'register'])->name('register'); +Route::get('/login', [AuthController::class, 'login'])->name('login'); +Route::get('/register', [AuthController::class, 'register'])->name('register'); -// Route::prefix('/post')->name('post.')->group(function () { -// Route::get('/', [PostController::class, 'index'])->name('index'); -// Route::get('/create', [PostController::class, 'create'])->name('create'); -// Route::post('/create', [PostController::class, 'store'])->name('store'); -// Route::get('/{post}', [PostController::class, 'show'])->name('show'); -// Route::get('/edit/{post}', [PostController::class, 'edit'])->name('edit'); -// Route::put('/edit/{post}', [PostController::class, 'update'])->name('update'); -// Route::delete('/{post}', [PostController::class, 'destroy'])->name('destroy'); -// }); +Route::prefix('/user')->name('user.')->group(function () { + Route::get('/{user}', [UserController::class, 'show'])->name('show'); +}); -// Route::prefix('category')->name('category.')->group(function () { -// Route::get('/', [CategoryController::class, 'index'])->name('index'); -// Route::get('/create', [CategoryController::class, 'create'])->name('create'); -// Route::post('/create', [CategoryController::class, 'store'])->name('store'); -// Route::get('/edit/{category}', [CategoryController::class, 'edit'])->name('edit'); -// Route::put('/edit/{category}', [CategoryController::class, 'update'])->name('update'); -// Route::delete('/{category}', [CategoryController::class, 'destroy'])->name('destroy'); -// }); \ No newline at end of file +Route::prefix('/blog')->name('blog.')->group(function () { + Route::get('/', [BlogController::class, 'index'])->name('index'); + Route::get('/create', [BlogController::class, 'create'])->name('create'); + Route::get('/{blog}', [BlogController::class, 'show'])->name('show'); + Route::get('/edit/{blog}', [BlogController::class, 'edit'])->name('edit'); +}); + +Route::prefix('category')->name('category.')->group(function () { + Route::get('/', [CategoryController::class, 'index'])->name('index'); + Route::get('/create', [CategoryController::class, 'create'])->name('create'); + Route::get('/edit/{category}', [CategoryController::class, 'edit'])->name('edit'); +}); \ No newline at end of file diff --git a/tests/Feature/Http/Controllers/Api/V1/AuthTest.php b/tests/Feature/Http/Controllers/Api/V1/AuthTest.php index 895f42c..a9915d2 100644 --- a/tests/Feature/Http/Controllers/Api/V1/AuthTest.php +++ b/tests/Feature/Http/Controllers/Api/V1/AuthTest.php @@ -17,11 +17,11 @@ public function test_sanctum_authentication(): void { Sanctum::actingAs( User::factory()->create(), - ['*'] + ['*'], ); $response = $this->get('/api/user'); - $response->assertStatus(200); + $response->assertOk(); } public function test_register_return_successful(): void @@ -64,11 +64,23 @@ public function test_login_return_successful(): void ]); } + public function test_login_return_failed(): void + { + $response = $this->post('/api/v1/login', [ + 'email' => AppHelper::randEmail(), + 'password' => 'password', + ]); + + $response->assertJsonStructure([ + 'message', + ]); + } + public function test_logout_return_successful(): void { Sanctum::actingAs( User::factory()->create(), - ['*'] + ['*'], ); $response = $this->post('/api/v1/logout'); diff --git a/tests/Feature/Http/Controllers/Api/V1/BlogTest.php b/tests/Feature/Http/Controllers/Api/V1/BlogTest.php index 77c2028..ee75379 100644 --- a/tests/Feature/Http/Controllers/Api/V1/BlogTest.php +++ b/tests/Feature/Http/Controllers/Api/V1/BlogTest.php @@ -27,7 +27,7 @@ public function test_blog_search_return_successful(): void { Blog::factory()->count(10)->create(); - $response = $this->get('/api/v1/blog/search/a?test=0'); + $response = $this->get('/api/v1/blog/search?keyword=a&test=0'); $response->assertOk(); } @@ -35,7 +35,7 @@ public function test_blog_store_return_successful(): void { Sanctum::actingAs( User::factory()->create(), - ['blog:store'] + ['blog:store'], ); $response = $this->post('/api/v1/blog', [ @@ -51,7 +51,7 @@ public function test_blog_bulk_store_return_successful(): void { Sanctum::actingAs( User::factory()->create(), - ['blog:store'] + ['blog:store'], ); $response = $this->post('/api/v1/blog/bulk', [ @@ -66,7 +66,7 @@ public function test_blog_bulk_store_return_successful(): void 'categoryId' => Category::factory()->create()->id, ], ]); - $response->assertOk(); + $response->assertCreated(); } public function test_blog_show_return_successful(): void @@ -82,7 +82,7 @@ public function test_blog_update_put_return_successful(): void $user = User::factory()->create(); Sanctum::actingAs( $user, - ['blog:update'] + ['blog:update'], ); $blog = Blog::factory()->create(['user_id' => $user->id]); @@ -96,12 +96,31 @@ public function test_blog_update_put_return_successful(): void $response->assertOk(); } + public function test_blog_update_patch_return_successful(): void + { + $user = User::factory()->create(); + Sanctum::actingAs( + $user, + ['blog:update'], + ); + + $blog = Blog::factory()->create(['user_id' => $user->id]); + + $response = $this->patch('/api/v1/blog/' . $blog->id, [ + 'title' => AppHelper::randStr(), + 'body' => AppHelper::randStr(), + 'categoryId' => Category::factory()->create()->id, + 'test' => false, + ]); + $response->assertOk(); + } + public function test_blog_delete_return_successful(): void { $user = User::factory()->create(); Sanctum::actingAs( $user, - ['blog:delete'] + ['blog:delete'], ); $blog = Blog::factory()->create(['user_id' => $user->id]); diff --git a/tests/Feature/Http/Controllers/Api/V1/CategoryTest.php b/tests/Feature/Http/Controllers/Api/V1/CategoryTest.php index be8c200..9ef0920 100644 --- a/tests/Feature/Http/Controllers/Api/V1/CategoryTest.php +++ b/tests/Feature/Http/Controllers/Api/V1/CategoryTest.php @@ -26,7 +26,7 @@ public function test_category_store_return_successful(): void { Sanctum::actingAs( User::factory()->create(), - ['category:store'] + ['category:store'], ); $response = $this->post('/api/v1/category', [ @@ -47,7 +47,7 @@ public function test_category_update_return_successful(): void { Sanctum::actingAs( User::factory()->create(), - ['category:update'] + ['category:update'], ); $category = Category::factory()->create(); @@ -62,7 +62,7 @@ public function test_category_delete_return_successful(): void { Sanctum::actingAs( User::factory()->create(), - ['category:delete'] + ['category:delete'], ); $category = Category::factory()->create(); diff --git a/tests/Feature/Http/Controllers/Web/V1/AuthTest.php b/tests/Feature/Http/Controllers/Web/V1/AuthTest.php new file mode 100644 index 0000000..96878dd --- /dev/null +++ b/tests/Feature/Http/Controllers/Web/V1/AuthTest.php @@ -0,0 +1,30 @@ +get('/'); + $response->assertOk(); + } + + public function test_login_return_successful(): void + { + $response = $this->get('/login'); + $response->assertOk(); + } + + public function test_register_return_successful(): void + { + $response = $this->get('/register'); + $response->assertOk(); + } +} diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php index 5773b0c..e29cce1 100644 --- a/tests/Unit/ExampleTest.php +++ b/tests/Unit/ExampleTest.php @@ -2,7 +2,7 @@ namespace Tests\Unit; -use PHPUnit\Framework\TestCase; +use Tests\TestCase; class ExampleTest extends TestCase { diff --git a/tests/Unit/Models/BlogTest.php b/tests/Unit/Models/BlogTest.php index 56a78c1..c2deb5b 100644 --- a/tests/Unit/Models/BlogTest.php +++ b/tests/Unit/Models/BlogTest.php @@ -17,7 +17,7 @@ class BlogTest extends TestCase public function test_blogs_table_has_expected_columns(): void { $this->assertTrue(Schema::hasColumns('blogs', [ - 'id', 'title', 'body', 'user_id', 'category_id' + 'id', 'title', 'body', 'image', 'user_id', 'category_id', ]), 1); } diff --git a/tests/Unit/Models/CategoryTest.php b/tests/Unit/Models/CategoryTest.php index 321ce3c..10d6157 100644 --- a/tests/Unit/Models/CategoryTest.php +++ b/tests/Unit/Models/CategoryTest.php @@ -17,7 +17,7 @@ class CategoryTest extends TestCase public function test_categories_table_has_expected_columns(): void { $this->assertTrue(Schema::hasColumns('categories', [ - 'id', 'name' + 'id', 'name', ]), 1); } diff --git a/tests/Unit/Models/UserTest.php b/tests/Unit/Models/UserTest.php index e75b2a5..791a6e2 100644 --- a/tests/Unit/Models/UserTest.php +++ b/tests/Unit/Models/UserTest.php @@ -17,7 +17,8 @@ class UserTest extends TestCase public function test_users_table_has_expected_columns(): void { $this->assertTrue(Schema::hasColumns('users', [ - 'id', 'name', 'email', 'email_verified_at', 'password' + 'id', 'name', 'email', 'email_verified_at', + 'password', 'avatar', 'remember_token' ]), 1); }