From c0ab7ff0f1f7ed95669e9a1d1669cf2a28287cb8 Mon Sep 17 00:00:00 2001 From: Rinaldo Peligrineli Date: Fri, 26 Jul 2024 13:54:08 -0300 Subject: [PATCH 1/6] refactor: change code for improved readability and maintainability --- .env.testing | 36 ++++++ ANSWER.md | 29 +++++ Dockerfile | 77 +++++++++++ .../Api/v1/Auth/AuthController.php | 113 +++++++++++++++++ .../{ => Api/v1/User}/UserController.php | 120 ++++++++++++++---- app/Http/Controllers/Controller.php | 0 app/Http/Requests/Auth/AuthFormRequest.php | 54 ++++++++ app/Http/Requests/User/UserStoreRequest.php | 57 +++++++++ app/Http/Requests/User/UserUpdateRequest.php | 53 ++++++++ app/Http/Resources/User/UserResource.php | 25 ++++ .../User/UserRepositoryInterface.php | 15 +++ app/Providers/RepositoryServiceProvider.php | 29 +++++ app/Repositories/User/UserRepository.php | 49 +++++++ config/app.php | 2 + makefile | 32 ++--- routes/api.php | 12 +- storage/api-docs/api-docs.json | 108 +++++++++++++++- storage/app/.gitignore | 0 storage/app/public/.gitignore | 0 storage/framework/.gitignore | 0 storage/framework/cache/.gitignore | 0 storage/framework/cache/data/.gitignore | 0 storage/framework/sessions/.gitignore | 0 storage/framework/testing/.gitignore | 0 storage/framework/views/.gitignore | 0 storage/logs/.gitignore | 0 tests/CreatesApplication.php | 0 tests/Feature/Auth/AuthTest.php | 57 +++++++++ tests/Feature/ExampleTest.php | 0 tests/Feature/User/UserTest.php | 95 ++++++++++++++ tests/TestCase.php | 33 +++++ tests/Unit/ExampleTest.php | 0 tests/Unit/Models/User/UserModelTest.php | 75 +++++++++++ .../Requests/Auth/AuthFormRequestTest.php | 51 ++++++++ .../Requests/User/UserStoreRequestTest.php | 63 +++++++++ 35 files changed, 1134 insertions(+), 51 deletions(-) create mode 100755 .env.testing create mode 100755 ANSWER.md create mode 100644 Dockerfile create mode 100755 app/Http/Controllers/Api/v1/Auth/AuthController.php rename app/Http/Controllers/{ => Api/v1/User}/UserController.php (58%) mode change 100644 => 100755 mode change 100644 => 100755 app/Http/Controllers/Controller.php create mode 100644 app/Http/Requests/Auth/AuthFormRequest.php create mode 100644 app/Http/Requests/User/UserStoreRequest.php create mode 100644 app/Http/Requests/User/UserUpdateRequest.php create mode 100755 app/Http/Resources/User/UserResource.php create mode 100755 app/Interfaces/User/UserRepositoryInterface.php create mode 100755 app/Providers/RepositoryServiceProvider.php create mode 100755 app/Repositories/User/UserRepository.php mode change 100644 => 100755 config/app.php mode change 100644 => 100755 makefile mode change 100644 => 100755 storage/api-docs/api-docs.json mode change 100644 => 100755 storage/app/.gitignore mode change 100644 => 100755 storage/app/public/.gitignore mode change 100644 => 100755 storage/framework/.gitignore mode change 100644 => 100755 storage/framework/cache/.gitignore mode change 100644 => 100755 storage/framework/cache/data/.gitignore mode change 100644 => 100755 storage/framework/sessions/.gitignore mode change 100644 => 100755 storage/framework/testing/.gitignore mode change 100644 => 100755 storage/framework/views/.gitignore mode change 100644 => 100755 storage/logs/.gitignore mode change 100644 => 100755 tests/CreatesApplication.php create mode 100755 tests/Feature/Auth/AuthTest.php mode change 100644 => 100755 tests/Feature/ExampleTest.php create mode 100755 tests/Feature/User/UserTest.php mode change 100644 => 100755 tests/TestCase.php mode change 100644 => 100755 tests/Unit/ExampleTest.php create mode 100755 tests/Unit/Models/User/UserModelTest.php create mode 100755 tests/Unit/Requests/Auth/AuthFormRequestTest.php create mode 100755 tests/Unit/Requests/User/UserStoreRequestTest.php diff --git a/.env.testing b/.env.testing new file mode 100755 index 0000000..0512e04 --- /dev/null +++ b/.env.testing @@ -0,0 +1,36 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY=base64:FwpumMyUcvG3z+MSQjaXBoJydVKC24+QqzN2VZxzJSc= +APP_DEBUG=true +APP_URL=http://localhost:8080 + +LOG_CHANNEL=stack +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=mysql +DB_HOST=mysql +DB_PORT=3306 +DB_DATABASE=testing +DB_USERNAME=sail +DB_PASSWORD=password + +BROADCAST_DRIVER=log +CACHE_DRIVER=file +FILESYSTEM_DISK=local +QUEUE_CONNECTION=sync +SESSION_DRIVER=file +SESSION_LIFETIME=120 + +MEMCACHED_HOST=127.0.0.1 + +MAIL_MAILER=smtp +MAIL_HOST=mailpit +MAIL_PORT=1025 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null +MAIL_FROM_ADDRESS="hello@example.com" +MAIL_FROM_NAME="${APP_NAME}" + +L5_SWAGGER_CONST_HOST=${APP_URL}/api diff --git a/ANSWER.md b/ANSWER.md new file mode 100755 index 0000000..b0f3d9d --- /dev/null +++ b/ANSWER.md @@ -0,0 +1,29 @@ +# Eleven Soft Backend Refactoring Test Answers + + Adicionado o Dockerfile para subir o ambiente através de containers docker + + Adicionado o arquivo .env.testing para a base de dados de teste + + No Arquivo README.md nos itens Instalation and Setup e Documentation o endereço esta com a porta errada ao invés de 8000 é 8080 + + Criado a pasta Api/v1 dentro de Controllers para criar tipo um versionamento das apis. + + Utilização do Laravel Passport para garantir maior segurança nas operações de Crud, permitindo que somente usuários devidamente logados possam inserir, recuperar, atualizarinserir, atualizar, deleta + + Utiliização de RepositoryPattern para separar lógica de acesso a dados e a lógica da camada de negócio. + + Utilização de FormRequests no Crud para validar os dados enviados pela requisição antes de processa-los, dentre os beneficios estão separação de responsabilidades, centralização das regras de validações, melhor segurança + + Método Index retorna a listagem de todos os usuários cadastrados, por esse motivo foi adicionada paginação para evitar possível sobrecarregamento da API, podendo causar um timeout + + Pensando na gestão dos dados do usuário, e que o mesmo poderá apenas alterar a senha, no update de usuários, não será permitido alterar a senha, a alteração da senha será feita em uma api especifica para alteração de senha. + + Utilização de Resources para estruturar e formatar os dados da Model a serem retornados + + No Método delete, foi adicionado a trait de Softdeletes, e adicionado a coluna deleted_at na migration, para evitar a exclusão fisica do usuário e apenas inativá-lo, pois para exclusão do usuário, caso tenha dados em outra tabela associado a ele, estes dados também deverão ser excluídos, e podem ser dados sensíveis que não poderam ser excluídos. + + Adicionado Testes Unitários e de Integração para garantir que o código alterado não afetem o comportamento da aplicação + + Adicionado blocos try catch para tratamento de erros na controller + Adicionado Códigos HTTP de acordo com as respostas das solicitações, + 201 para inserção, 200 para lista, edição e atulização e 500 para erros no bloco Catch diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..eb65786 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,77 @@ +FROM ubuntu:22.04 + +LABEL maintainer="Taylor Otwell" + +ARG WWWGROUP + +WORKDIR /var/www/html + +ENV DEBIAN_FRONTEND noninteractive +ENV TZ=UTC + +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + + +RUN apt-get update \ + && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python2 \ + && mkdir -p ~/.gnupg \ + && chmod 600 ~/.gnupg \ + && echo "disable-ipv6" >> ~/.gnupg/dirmngr.conf \ + && apt-key adv --homedir ~/.gnupg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys E5267A6C \ + && apt-key adv --homedir ~/.gnupg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C300EE8C \ + && echo "deb http://ppa.launchpad.net/ondrej/php/ubuntu focal main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ + && apt-get update \ + && apt-get install -y php82-cli php82-dev \ + php82-pgsql php82-sqlite3 php82-gd \ + php82-curl php82-memcached \ + php82-imap php82-mysql php82-mbstring \ + php82-xml php82-zip php82-bcmath php82-soap \ + php82-intl php82-readline \ + php82-msgpack php82-igbinary php82-ldap \ + php82-redis php82-mbstring \ + && php -r "readfile('http://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \ + && curl -sL https://deb.nodesource.com/setup_16.x | bash - \ + && apt-get install -y nodejs \ + && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ + && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ + && apt-get update \ + && apt-get install -y yarn \ + && apt-get install -y mysql-client \ + && apt-get install -y postgresql-client \ + && apt-get -y autoremove \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + + + +# RUN pecl channel-update https://pecl.php.net/channel.xml \ +# && pecl install swoole \ +# && pecl clear-cache \ +# && rm -rf /tmp/* /var/tmp/* + + + + +RUN setcap "cap_net_bind_service=+ep" /usr/bin/php82 + +RUN useradd -m sail +RUN usermod -aG sudo sail + +# RUN groupadd --force -g $WWWGROUP sail +# RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail + +RUN curl -fsSL https://deb.nodesource.com/setup_12.x | bash - +RUN apt-get install -y nodejs + +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + + +COPY . /var/www/html/ +COPY resources/docker/app/start-container /usr/local/bin/start-container +COPY resources/docker/app/supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY resources/docker/app/php.ini /etc/php/82/cli/conf.d/99-sail.ini +RUN chmod +x /usr/local/bin/start-container + +EXPOSE 80 + +ENTRYPOINT ["start-container"] diff --git a/app/Http/Controllers/Api/v1/Auth/AuthController.php b/app/Http/Controllers/Api/v1/Auth/AuthController.php new file mode 100755 index 0000000..9bc9efb --- /dev/null +++ b/app/Http/Controllers/Api/v1/Auth/AuthController.php @@ -0,0 +1,113 @@ + $request->email, + 'password' => $request->password + ]; + + if (auth()->attempt($data)) { + $token = auth()->user()->createToken('LaravelAuthApp')->accessToken; + $data = [ + 'message' => 'Login efetuado com sucesso', + 'user' => Auth::user(), + 'token' => $token + ]; + return response()->json($data, JsonResponse::HTTP_OK); + } else { + return response()->json(['error' => 'Unauthorised'], JsonResponse::HTTP_UNAUTHORIZED); + } + } + + /** + * Make a login + * + * @return JsonResponse + * + * @OA\Get( + * path="/v1/auth/me", + * operationId="Get User logged data", + * summary="Return infos of user logged", + * tags={"Auth"}, + * description="Get User logged data", + * security={ + * {"bearerAuth": {}} + * }, + * @OA\Response( + * response=200, + * description="Successful operation", + * @OA\JsonContent(ref="#/components/schemas/User") + * ), + * @OA\Response( + * response=401, + * description="Unauthenticated", + * ), + * @OA\Response( + * response=403, + * description="Forbidden" + * ) + * ) + */ + public function me(): JsonResponse + { + return response()->json(['user' => Auth::user()], JsonResponse::HTTP_OK); + } + + public function logout(Request $request): JsonResponse + { + $request->user()->tokens()->delete(); + + return response()->json(['Logout efetuado com sucesso'], JsonResponse::HTTP_OK); + } +} diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/Api/v1/User/UserController.php old mode 100644 new mode 100755 similarity index 58% rename from app/Http/Controllers/UserController.php rename to app/Http/Controllers/Api/v1/User/UserController.php index f4a5826..75acb0a --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/Api/v1/User/UserController.php @@ -1,17 +1,26 @@ user = $user; + } /** @@ -48,9 +57,23 @@ function __construct(User $user) * ) * ) */ - public function index(Request $request) + public function index(Request $request): JsonResponse { - return $this->user->get(); + try { + $perPage = $request->has('perPage') ? $request->per_page : 20; + $page = $request->has('page') ? $request->page : 1; + + $users = UserResource::collection($this->userRepository->getAllUsersPaginate($perPage, $page)); + return response()->json([ + 'data' => $users, + ], JsonResponse::HTTP_OK); + } catch (\Exception $e) { + return response()->json([ + 'data' => [], + 'errorMessage' => $e->getMessage(), + 'errorCode' => $e->getCode(), + ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); + } } /** @@ -88,15 +111,26 @@ public function index(Request $request) * ) * ) */ - public function show(User $user) + public function show($id) { - return $user; + try { + $users = new UserResource($this->userRepository->getUserById($id)); + return response()->json([ + 'data' => $users, + ], JsonResponse::HTTP_OK); + } catch (\Exception $e) { + return response()->json([ + 'data' => [], + 'errorMessage' => $e->getMessage(), + 'errorCode' => $e->getCode(), + ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); + } } /** * Store a newly created user in storage. * - * @return User + * @return JsonResponse * * @OA\Post( * path="/users", @@ -126,15 +160,22 @@ public function show(User $user) * ) * ) */ - public function store(Request $request) + public function store(UserStoreRequest $request): JsonResponse { - $data = $request->only([ - 'name', - 'email', - 'password', - ]); + try { + $user = new UserResource($this->userRepository->createUser($request->all())); - return $this->user->create($data); + return response()->json([ + 'message' => 'Usuario inserido com sucesso', + 'data' => $user, + ], JsonResponse::HTTP_CREATED); + } catch (\Exception $e) { + return response()->json([ + 'data' => [], + 'errorMessage' => $e->getMessage(), + 'errorCode' => $e->getCode(), + ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); + } } /** @@ -176,17 +217,30 @@ public function store(Request $request) * ) * ) */ - public function update(Request $request, User $user) + public function update(UserUpdateRequest $request, $id) { - $data = $request->only([ - 'name', - 'email', - 'password', - ]); + try { + + $data = $request->only([ + 'name', + 'email', + 'password', + ]); - $user->update($data); + $this->userRepository->updateUser($id, $data); + $user = $this->userRepository->getUserById($id); - return $user; + return response()->json([ + 'message' => "Usuário Alterado com sucesso", + 'data' => $user, + ], JsonResponse::HTTP_OK); + } catch (\Exception $e) { + return response()->json([ + 'data' => [], + 'errorMessage' => $e->getMessage(), + 'errorCode' => $e->getCode(), + ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); + } } /** @@ -224,11 +278,21 @@ public function update(Request $request, User $user) * ) * ) */ - public function destroy(User $user) + public function destroy($id) { - $user->delete(); + try { + $this->userRepository->deleteUser($id); - return $user; + return response()->json([ + 'message' => 'Usuario excluido com sucesso' + ], JsonResponse::HTTP_OK); + } catch (\Exception $e) { + return response()->json([ + 'data' => [], + 'errorMessage' => $e->getMessage(), + 'errorCode' => $e->getCode(), + ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); + } } } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php old mode 100644 new mode 100755 diff --git a/app/Http/Requests/Auth/AuthFormRequest.php b/app/Http/Requests/Auth/AuthFormRequest.php new file mode 100644 index 0000000..839e86e --- /dev/null +++ b/app/Http/Requests/Auth/AuthFormRequest.php @@ -0,0 +1,54 @@ + + */ + public function rules() + { + return [ + 'email' => 'required|email', + 'password' => 'required|min:8' + ]; + } + + public function failedValidation(Validator $validator) + { + throw new HttpResponseException(response()->json([ + 'success' => false, + 'message' => 'Errors found', + 'data' => $validator->errors() + ])); + } + + public function messages() + { + return [ + 'email.required' => 'O Campo Email é obrigatório', + 'email.email' => 'O Campo Email possui um formato invalido', + 'password.required' => 'O Campo senha é obrigatório', + 'password.min' => 'O Campo senha deve conter no mínimo 8 caracteres' + ]; + } + +} diff --git a/app/Http/Requests/User/UserStoreRequest.php b/app/Http/Requests/User/UserStoreRequest.php new file mode 100644 index 0000000..d7ccb5e --- /dev/null +++ b/app/Http/Requests/User/UserStoreRequest.php @@ -0,0 +1,57 @@ + + */ + public function rules() + { + return [ + 'name' => 'required', + 'email' => 'required|email|unique:users,email', + 'password' => 'required|min:8' + ]; + } + + public function failedValidation(Validator $validator) + { + throw new HttpResponseException(response()->json([ + 'success' => false, + 'message' => 'Errors found', + 'data' => $validator->errors() + ])); + } + + public function messages() + { + return [ + 'name.required' => 'O Campo Nome é obrigatório', + 'email.required' => 'O Campo Email é obrigatório', + 'email.email' => 'O Campo Email possui um formato invalido', + 'email.unique' => 'Email já cadastrado', + 'password.required' => 'O Campo senha é obrigatório', + 'password.min' => 'O Campo senha deve conter no mínimo 8 caracteres' + ]; + } + +} diff --git a/app/Http/Requests/User/UserUpdateRequest.php b/app/Http/Requests/User/UserUpdateRequest.php new file mode 100644 index 0000000..cf731c4 --- /dev/null +++ b/app/Http/Requests/User/UserUpdateRequest.php @@ -0,0 +1,53 @@ + + */ + public function rules() + { + + return [ + 'name' => 'required', + 'email' => 'required|email|unique:users,email,' . $this->id . ',id' + ]; + } + + public function failedValidation(Validator $validator) + { + throw new HttpResponseException(response()->json([ + 'success' => false, + 'message' => 'Errors found', + 'data' => $validator->errors() + ])); + } + + public function messages() + { + return [ + 'name.required' => 'O Campo Nome é obrigatório', + 'email.required' => 'O Campo Email é obrigatório', + 'email.email' => 'O Campo Email possui um formato invalido', + 'email.unique' => 'Email já cadastrado' + ]; + } +} diff --git a/app/Http/Resources/User/UserResource.php b/app/Http/Resources/User/UserResource.php new file mode 100755 index 0000000..ef4f7d3 --- /dev/null +++ b/app/Http/Resources/User/UserResource.php @@ -0,0 +1,25 @@ + + */ + public function toArray($request) + { + return [ + 'id' => $this->id, + 'name' => $this->name, + 'email' => $this->email, + 'created_at' => $this->created_at->format('Y-m-d H:i:s'), + 'updated_at' => $this->updated_at->format('Y-m-d H:i:s'), + ]; + } +} diff --git a/app/Interfaces/User/UserRepositoryInterface.php b/app/Interfaces/User/UserRepositoryInterface.php new file mode 100755 index 0000000..a17c7af --- /dev/null +++ b/app/Interfaces/User/UserRepositoryInterface.php @@ -0,0 +1,15 @@ +app->bind(UserRepositoryInterface::class, UserRepository::class); + + } + + /** + * Bootstrap services. + */ + public function boot(): void + { + // + } +} diff --git a/app/Repositories/User/UserRepository.php b/app/Repositories/User/UserRepository.php new file mode 100755 index 0000000..383d486 --- /dev/null +++ b/app/Repositories/User/UserRepository.php @@ -0,0 +1,49 @@ +first(); + } + + public function searchUserByColumn($column, $value) { + + return User::where($column, 'LIKE', "%{$value}%")->get(); + } + + public function deleteUser($userId) + { + User::find($userId)->delete(); + } + + public function createUser(array $userDetails) + { + return User::create($userDetails); + } + + public function updateUser($userId, array $newDetails) + { + return User::find($userId)->update($newDetails); + } +} diff --git a/config/app.php b/config/app.php old mode 100644 new mode 100755 index 1d662e6..e9675df --- a/config/app.php +++ b/config/app.php @@ -169,6 +169,8 @@ // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, + App\Providers\RepositoryServiceProvider::class, + Laravel\Passport\PassportServiceProvider::class, ])->toArray(), /* diff --git a/makefile b/makefile old mode 100644 new mode 100755 index 5d94f95..f488c7a --- a/makefile +++ b/makefile @@ -4,19 +4,19 @@ sleep: sleep 3 ps: - docker-compose ps + docker compose ps up: - docker-compose up -d + docker compose up -d up-recreate: - docker-compose up -d --force-recreate + docker compose up -d --force-recreate down: - docker-compose down + docker compose down forget: - docker-compose down --rmi all --volumes + docker compose down --rmi all --volumes docker volume rm backend-test_sail-mysql 2>/dev/null db-shell: @@ -26,26 +26,26 @@ api-build: USER_ID=$(shell id -u) GROUP_ID=$(shell id -g) docker-compose build --no-cache api-db: - docker-compose exec -it api php /var/www/html/artisan migrate:fresh - docker-compose exec -it api php /var/www/html/artisan db:seed + docker compose exec -it api php /var/www/html/artisan migrate:fresh + docker compose exec -it api php /var/www/html/artisan db:seed api-key: - docker-compose exec -it api php /var/www/html/artisan key:generate + docker compose exec -it api php /var/www/html/artisan key:generate api-env: cp .env.example .env api-config-cache: - docker-compose exec -it api php /var/www/html/artisan config:cache + docker compose exec -it api php /var/www/html/artisan config:cache api-composer-install: composer install --ignore-platform-reqs api-shell: - docker-compose exec -it api bash -c 'su sail' + docker compose exec -it api bash -c 'su sail' api-root-shell: - docker-compose exec -it api bash + docker compose exec -it api bash api-test: docker-compose exec -it api php /var/www/html/artisan test @@ -54,17 +54,17 @@ api-test-feature: docker-compose exec -it api php /var/www/html/artisan test --testsuite=Feature --stop-on-failure api-test-php-unit: - docker-compose exec -it api php /var/www/html/artisan phpunit + docker compose exec -it api php /var/www/html/artisan phpunit api-build-swagger: - docker-compose exec -it api php /var/www/html/artisan l5-swagger:generate + docker compose exec -it api php /var/www/html/artisan l5-swagger:generate api-passport-key: - docker-compose exec -it api php /var/www/html/artisan passport:keys --force + docker compose exec -it api php /var/www/html/artisan passport:keys --force api-passport-generate: - docker-compose exec -it api php /var/www/html/artisan passport:client --password --name='Laravel Password Grant Client' --provider=users > .passport + docker compose exec -it api php /var/www/html/artisan passport:client --password --name='Laravel Password Grant Client' --provider=users > .passport cat .passport fix-permissions: - docker-compose exec -it api bash -c 'chmod -R 777 /var/www/html/storage/logs && chmod -R 777 /var/www/html/storage/framework' + docker compose exec -it api bash -c 'chmod -R 777 /var/www/html/storage/logs && chmod -R 777 /var/www/html/storage/framework' diff --git a/routes/api.php b/routes/api.php index 03a44e1..592e88a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,7 +1,8 @@ group(function() { + Route::get('/v1/auth/me', [AuthController::class, 'me']); + Route::post('/v1/auth/logout', [AuthController::class, 'logout']); + Route::apiResource('users', UserController::class); +}); diff --git a/storage/api-docs/api-docs.json b/storage/api-docs/api-docs.json old mode 100644 new mode 100755 index 244d983..bde28b2 --- a/storage/api-docs/api-docs.json +++ b/storage/api-docs/api-docs.json @@ -15,6 +15,94 @@ } ], "paths": { + "/v1/auth/login": { + "post": { + "tags": [ + "Auth" + ], + "summary": "Autehticate with valid user", + "description": "Make a Login", + "operationId": "Login", + "requestBody": { + "description": "Provide All Info Below", + "required": true, + "content": { + "application/json": { + "schema": { + "required": [ + "email", + "password" + ], + "properties": { + "email": { + "type": "email", + "format": "text", + "example": "example@elevensoft.dev" + }, + "password": { + "type": "string", + "format": "text", + "example": "password" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "401": { + "description": "Unauthenticated" + }, + "403": { + "description": "Forbidden" + } + } + } + }, + "/v1/auth/me": { + "get": { + "tags": [ + "Auth" + ], + "summary": "Return infos of user logged", + "description": "Get User logged data", + "operationId": "Get User logged data", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "401": { + "description": "Unauthenticated" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, "/users": { "get": { "tags": [ @@ -293,13 +381,23 @@ "scheme": "https", "flows": { "password": { - "authorizationUrl": "http://localhost:8080/oauth/authorize", - "tokenUrl": "http://localhost:8080/oauth/token", - "refreshUrl": "http://localhost:8080/token/refresh", + "authorizationUrl": "http://localhost:8090/oauth/authorize", + "tokenUrl": "http://localhost:8090/oauth/token", + "refreshUrl": "http://localhost:8090/token/refresh", "scopes": [] } } } } - } -} \ No newline at end of file + }, + "tags": [ + { + "name": "Auth", + "description": "Auth" + }, + { + "name": "Users", + "description": "Users" + } + ] +} diff --git a/storage/app/.gitignore b/storage/app/.gitignore old mode 100644 new mode 100755 diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore old mode 100644 new mode 100755 diff --git a/storage/framework/.gitignore b/storage/framework/.gitignore old mode 100644 new mode 100755 diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore old mode 100644 new mode 100755 diff --git a/storage/framework/cache/data/.gitignore b/storage/framework/cache/data/.gitignore old mode 100644 new mode 100755 diff --git a/storage/framework/sessions/.gitignore b/storage/framework/sessions/.gitignore old mode 100644 new mode 100755 diff --git a/storage/framework/testing/.gitignore b/storage/framework/testing/.gitignore old mode 100644 new mode 100755 diff --git a/storage/framework/views/.gitignore b/storage/framework/views/.gitignore old mode 100644 new mode 100755 diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore old mode 100644 new mode 100755 diff --git a/tests/CreatesApplication.php b/tests/CreatesApplication.php old mode 100644 new mode 100755 diff --git a/tests/Feature/Auth/AuthTest.php b/tests/Feature/Auth/AuthTest.php new file mode 100755 index 0000000..bd767cd --- /dev/null +++ b/tests/Feature/Auth/AuthTest.php @@ -0,0 +1,57 @@ + 'example@elevensoft.dev', + 'password' => 'password' + ]; + + $response = $this->postJson('/api/v1/auth/login', $userData); + + $response->assertOK(); + $response->assertJsonStructure([ + 'message', + 'user' => [ + 'id', + 'name', + 'email', + 'email_verified_at', + 'created_at', + 'updated_at' + + ], + 'token' + ]); + + } + + public function testCannotMakeloginWithInvalidCredentials(): void + { + + $userData = [ + 'email' => 'admin@teste.com', + 'password' => 'admin@teste' + ]; + + $response = $this->postJson('/api/v1/auth/login', $userData); + + $response + ->assertUnauthorized(); + + } + + public function testCanMakeLogout(): void + { + $headers = $this->makeAuth(); + $response = $this->postJson('/api/v1/auth/logout', [], $headers); + $response->assertOK(); + } +} diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php old mode 100644 new mode 100755 diff --git a/tests/Feature/User/UserTest.php b/tests/Feature/User/UserTest.php new file mode 100755 index 0000000..2f47cbd --- /dev/null +++ b/tests/Feature/User/UserTest.php @@ -0,0 +1,95 @@ +raw(); + $header = $this->makeAuth(); + + $response = $this->postJson('/api/users', $userData, $header); + + $response + ->assertCreated() + ->assertJsonStructure([ + 'message', + 'data' => [ + 'id', + 'name', + 'email', + 'created_at', + 'updated_at' + ] + ]); + + } + + public function testWithoutAuthorizationCannotStoreUser(): void + { + $userData = User::factory()->raw(); + $header = $this->makeUnauthorizedAuth(); + + $response = $this->postJson('/api/users', $userData, $header); + + $response + ->assertUnauthorized(); + + } + + public function testCanListUsers(): void + { + + $header = $this->makeAuth(); + $response = $this->getJson('/api/users', $header); + + $response->assertOK(); + + } + + public function testCanEditUser(): void + { + $header = $this->makeAuth(); + $user = User::factory()->create(); + + $response = $this->getJson('/api/users/' . $user['id'], $header); + + $response->assertOk(); + $response->assertJsonCount(1); + $response->assertJsonStructure([ + 'data' => [ + 'id', + 'name', + 'email', + 'created_at', + 'updated_at' + ] + ]); + + } + + public function testCanDeleteUser(): void + { + $header = $this->makeAuth(); + $user = User::factory()->create(); + + $response = $this->deleteJson('/api/users/' . $user['id'], [], $header); + + $response->assertOk()->assertJsonCount(1); + + } + +} diff --git a/tests/TestCase.php b/tests/TestCase.php old mode 100644 new mode 100755 index 2932d4a..c79d03d --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -7,4 +7,37 @@ abstract class TestCase extends BaseTestCase { use CreatesApplication; + + public function makeAuth(): array + { + $userData = [ + 'email' => 'example@elevensoft.dev', + 'password' => 'password' + ]; + + $response = $this->postJson('/api/v1/auth/login', $userData); + $data = json_decode($response->getContent()); + + $headers = [ + 'Authorization' => 'Bearer ' . $data->token + ]; + + return $headers; + } + + + public function makeUnauthorizedAuth(): array + { + $userData = [ + 'email' => 'example@elevensoft.dev', + 'password' => 'teste' + ]; + + $response = $this->postJson('/api/v1/auth/login', $userData); + $data = json_decode($response->getContent()); + + $headers = []; + + return $headers; + } } diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php old mode 100644 new mode 100755 diff --git a/tests/Unit/Models/User/UserModelTest.php b/tests/Unit/Models/User/UserModelTest.php new file mode 100755 index 0000000..6345554 --- /dev/null +++ b/tests/Unit/Models/User/UserModelTest.php @@ -0,0 +1,75 @@ +raw(); + $user = User::create($userFactory); + $arrUser = json_decode($user,true); + + $this->assertArrayHasKey('id', $arrUser); + $this->assertArrayHasKey('name', $arrUser); + $this->assertArrayHasKey('email', $arrUser); + $this->assertEquals(5, count($arrUser)); + + } + + public function testForUserEditById(): void + { + $userFactory = User::factory()->raw(); + $userIns = User::create($userFactory); + $user = User::where('email', $userIns['email'])->first(); + $arrUser = json_decode($user,true); + + $this->assertArrayHasKey('id', $arrUser); + $this->assertArrayHasKey('name', $arrUser); + $this->assertArrayHasKey('email', $arrUser); + $this->assertEquals(7, count($arrUser)); + + } + + public function testForUserUpdate(): void + { + $userFactory = User::factory()->raw(); + + $userIns = User::create($userFactory); + $arrUserIns = json_decode($userIns,true); + + $user = User::find($arrUserIns['id']); + $arrUserIns['name'] = $userIns['name'] . ' - Update'; + unset($arrUserIns['id']); + $user->update($arrUserIns); + + $arrUser = json_decode($user,true); + + $this->assertArrayHasKey('id', $arrUser); + $this->assertArrayHasKey('name', $arrUser); + $this->assertArrayHasKey('email', $arrUser); + $this->assertEquals(7, count($arrUser)); + + } + + public function testForUserDelete(): void + { + $userFactory = User::factory()->raw(); + + $userIns = User::create($userFactory); + $arrUserIns = json_decode($userIns,true); + + $user = User::find($arrUserIns['id']); + $value = $user->delete(); + + $this->assertNotNull($value); + $this->assertTrue($value); + + } + +} diff --git a/tests/Unit/Requests/Auth/AuthFormRequestTest.php b/tests/Unit/Requests/Auth/AuthFormRequestTest.php new file mode 100755 index 0000000..721c74f --- /dev/null +++ b/tests/Unit/Requests/Auth/AuthFormRequestTest.php @@ -0,0 +1,51 @@ +rules = (new AuthFormRequest())->rules(); + $this->validator = $this->app['validator']; + } + + public function testForAuthIsValidEmail(): void + { + + $this->assertTrue($this->validateField('email', 'usern@elevensoft.dev')); + $this->assertTrue($this->validateField('email', 'usern@elevensoft')); + $this->assertFalse($this->validateField('email', 'userelevensoft.com')); + $this->assertFalse($this->validateField('email', '')); + + } + + public function testForAuthIsValidPassword(): void + { + + $this->assertTrue($this->validateField('password', 'inf41234')); + $this->assertFalse($this->validateField('password', 'inf123')); + $this->assertFalse($this->validateField('email', '')); + + } + + protected function validateField(string $field, $value): bool + { + return $this->validator->make( + [$field => $value], + [$field => $this->rules[$field]] + )->passes(); + } +} diff --git a/tests/Unit/Requests/User/UserStoreRequestTest.php b/tests/Unit/Requests/User/UserStoreRequestTest.php new file mode 100755 index 0000000..41df879 --- /dev/null +++ b/tests/Unit/Requests/User/UserStoreRequestTest.php @@ -0,0 +1,63 @@ +rules = (new UserStoreRequest())->rules(); + $this->validator = $this->app['validator']; + } + + public function testForUserIsValidName(): void + { + + $this->assertTrue($this->validateField('name', fake()->name())); + $this->assertFalse($this->validateField('name', '')); + + } + + public function testForUserIsValidEmail(): void + { + + $this->assertTrue($this->validateField('email', fake()->email())); + $this->assertTrue($this->validateField('email', 'user@elevensoft')); + $this->assertFalse($this->validateField('email', 'userelevensoft.com')); + $this->assertFalse($this->validateField('email', 'example@elevensoft.dev')); + $this->assertFalse($this->validateField('email', '')); + + } + + public function testForUserIsValidPassword(): void + { + + $this->assertTrue($this->validateField('password', Str::random())); + $this->assertFalse($this->validateField('password', Str::random(7))); + $this->assertFalse($this->validateField('password', '')); + + } + + protected function validateField(string $field, $value): bool + { + return $this->validator->make( + [$field => $value], + [$field => $this->rules[$field]] + )->passes(); + } +} From 4d3c1f7eba438419f90cbaac21781e8c7142c5da Mon Sep 17 00:00:00 2001 From: Rinaldo Peligrineli Date: Fri, 26 Jul 2024 15:14:12 -0300 Subject: [PATCH 2/6] fix auth and model to return passport token --- app/Models/User.php | 5 +++-- config/auth.php | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) mode change 100644 => 100755 app/Models/User.php mode change 100644 => 100755 config/auth.php diff --git a/app/Models/User.php b/app/Models/User.php old mode 100644 new mode 100755 index 8ceeafd..efab038 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,9 +4,10 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; -use Laravel\Sanctum\HasApiTokens; +use Laravel\Passport\HasApiTokens; /** * @OA\Schema( @@ -19,7 +20,7 @@ */ class User extends Authenticatable { - use HasApiTokens, HasFactory, Notifiable; + use HasApiTokens, HasFactory, Notifiable, SoftDeletes; /** * The attributes that are mass assignable. diff --git a/config/auth.php b/config/auth.php old mode 100644 new mode 100755 index 9548c15..ea85d10 --- a/config/auth.php +++ b/config/auth.php @@ -40,6 +40,12 @@ 'driver' => 'session', 'provider' => 'users', ], + 'api' => [ + 'driver' => 'passport', + 'provider' => 'users', + + 'hash' => true, + ], ], /* From 7d84a93d5756a4ebf715cf48c78438a692198cd4 Mon Sep 17 00:00:00 2001 From: Rinaldo Peligrineli Date: Fri, 26 Jul 2024 15:49:15 -0300 Subject: [PATCH 3/6] fix some files that need to run app --- .../Api/v1/Auth/AuthController.php | 113 +++++++++++++++++ .../{ => Api/v1/User}/UserController.php | 120 ++++++++++++++---- app/Http/Requests/Auth/AuthFormRequest.php | 54 ++++++++ app/Http/Requests/User/UserStoreRequest.php | 57 +++++++++ app/Http/Requests/User/UserUpdateRequest.php | 53 ++++++++ app/Http/Resources/User/UserResource.php | 25 ++++ .../User/UserRepositoryInterface.php | 15 +++ app/Models/User.php | 5 +- app/Repositories/User/UserRepository.php | 49 +++++++ config/auth.php | 5 + .../2014_10_12_000000_create_users_table.php | 22 ++-- routes/api.php | 12 +- storage/api-docs/api-docs.json | 100 ++++++++++++++- 13 files changed, 588 insertions(+), 42 deletions(-) create mode 100755 app/Http/Controllers/Api/v1/Auth/AuthController.php rename app/Http/Controllers/{ => Api/v1/User}/UserController.php (58%) mode change 100644 => 100755 create mode 100644 app/Http/Requests/Auth/AuthFormRequest.php create mode 100644 app/Http/Requests/User/UserStoreRequest.php create mode 100644 app/Http/Requests/User/UserUpdateRequest.php create mode 100755 app/Http/Resources/User/UserResource.php create mode 100755 app/Interfaces/User/UserRepositoryInterface.php mode change 100644 => 100755 app/Models/User.php create mode 100755 app/Repositories/User/UserRepository.php mode change 100644 => 100755 database/migrations/2014_10_12_000000_create_users_table.php mode change 100644 => 100755 routes/api.php mode change 100644 => 100755 storage/api-docs/api-docs.json diff --git a/app/Http/Controllers/Api/v1/Auth/AuthController.php b/app/Http/Controllers/Api/v1/Auth/AuthController.php new file mode 100755 index 0000000..9bc9efb --- /dev/null +++ b/app/Http/Controllers/Api/v1/Auth/AuthController.php @@ -0,0 +1,113 @@ + $request->email, + 'password' => $request->password + ]; + + if (auth()->attempt($data)) { + $token = auth()->user()->createToken('LaravelAuthApp')->accessToken; + $data = [ + 'message' => 'Login efetuado com sucesso', + 'user' => Auth::user(), + 'token' => $token + ]; + return response()->json($data, JsonResponse::HTTP_OK); + } else { + return response()->json(['error' => 'Unauthorised'], JsonResponse::HTTP_UNAUTHORIZED); + } + } + + /** + * Make a login + * + * @return JsonResponse + * + * @OA\Get( + * path="/v1/auth/me", + * operationId="Get User logged data", + * summary="Return infos of user logged", + * tags={"Auth"}, + * description="Get User logged data", + * security={ + * {"bearerAuth": {}} + * }, + * @OA\Response( + * response=200, + * description="Successful operation", + * @OA\JsonContent(ref="#/components/schemas/User") + * ), + * @OA\Response( + * response=401, + * description="Unauthenticated", + * ), + * @OA\Response( + * response=403, + * description="Forbidden" + * ) + * ) + */ + public function me(): JsonResponse + { + return response()->json(['user' => Auth::user()], JsonResponse::HTTP_OK); + } + + public function logout(Request $request): JsonResponse + { + $request->user()->tokens()->delete(); + + return response()->json(['Logout efetuado com sucesso'], JsonResponse::HTTP_OK); + } +} diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/Api/v1/User/UserController.php old mode 100644 new mode 100755 similarity index 58% rename from app/Http/Controllers/UserController.php rename to app/Http/Controllers/Api/v1/User/UserController.php index f4a5826..75acb0a --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/Api/v1/User/UserController.php @@ -1,17 +1,26 @@ user = $user; + } /** @@ -48,9 +57,23 @@ function __construct(User $user) * ) * ) */ - public function index(Request $request) + public function index(Request $request): JsonResponse { - return $this->user->get(); + try { + $perPage = $request->has('perPage') ? $request->per_page : 20; + $page = $request->has('page') ? $request->page : 1; + + $users = UserResource::collection($this->userRepository->getAllUsersPaginate($perPage, $page)); + return response()->json([ + 'data' => $users, + ], JsonResponse::HTTP_OK); + } catch (\Exception $e) { + return response()->json([ + 'data' => [], + 'errorMessage' => $e->getMessage(), + 'errorCode' => $e->getCode(), + ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); + } } /** @@ -88,15 +111,26 @@ public function index(Request $request) * ) * ) */ - public function show(User $user) + public function show($id) { - return $user; + try { + $users = new UserResource($this->userRepository->getUserById($id)); + return response()->json([ + 'data' => $users, + ], JsonResponse::HTTP_OK); + } catch (\Exception $e) { + return response()->json([ + 'data' => [], + 'errorMessage' => $e->getMessage(), + 'errorCode' => $e->getCode(), + ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); + } } /** * Store a newly created user in storage. * - * @return User + * @return JsonResponse * * @OA\Post( * path="/users", @@ -126,15 +160,22 @@ public function show(User $user) * ) * ) */ - public function store(Request $request) + public function store(UserStoreRequest $request): JsonResponse { - $data = $request->only([ - 'name', - 'email', - 'password', - ]); + try { + $user = new UserResource($this->userRepository->createUser($request->all())); - return $this->user->create($data); + return response()->json([ + 'message' => 'Usuario inserido com sucesso', + 'data' => $user, + ], JsonResponse::HTTP_CREATED); + } catch (\Exception $e) { + return response()->json([ + 'data' => [], + 'errorMessage' => $e->getMessage(), + 'errorCode' => $e->getCode(), + ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); + } } /** @@ -176,17 +217,30 @@ public function store(Request $request) * ) * ) */ - public function update(Request $request, User $user) + public function update(UserUpdateRequest $request, $id) { - $data = $request->only([ - 'name', - 'email', - 'password', - ]); + try { + + $data = $request->only([ + 'name', + 'email', + 'password', + ]); - $user->update($data); + $this->userRepository->updateUser($id, $data); + $user = $this->userRepository->getUserById($id); - return $user; + return response()->json([ + 'message' => "Usuário Alterado com sucesso", + 'data' => $user, + ], JsonResponse::HTTP_OK); + } catch (\Exception $e) { + return response()->json([ + 'data' => [], + 'errorMessage' => $e->getMessage(), + 'errorCode' => $e->getCode(), + ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); + } } /** @@ -224,11 +278,21 @@ public function update(Request $request, User $user) * ) * ) */ - public function destroy(User $user) + public function destroy($id) { - $user->delete(); + try { + $this->userRepository->deleteUser($id); - return $user; + return response()->json([ + 'message' => 'Usuario excluido com sucesso' + ], JsonResponse::HTTP_OK); + } catch (\Exception $e) { + return response()->json([ + 'data' => [], + 'errorMessage' => $e->getMessage(), + 'errorCode' => $e->getCode(), + ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); + } } } diff --git a/app/Http/Requests/Auth/AuthFormRequest.php b/app/Http/Requests/Auth/AuthFormRequest.php new file mode 100644 index 0000000..839e86e --- /dev/null +++ b/app/Http/Requests/Auth/AuthFormRequest.php @@ -0,0 +1,54 @@ + + */ + public function rules() + { + return [ + 'email' => 'required|email', + 'password' => 'required|min:8' + ]; + } + + public function failedValidation(Validator $validator) + { + throw new HttpResponseException(response()->json([ + 'success' => false, + 'message' => 'Errors found', + 'data' => $validator->errors() + ])); + } + + public function messages() + { + return [ + 'email.required' => 'O Campo Email é obrigatório', + 'email.email' => 'O Campo Email possui um formato invalido', + 'password.required' => 'O Campo senha é obrigatório', + 'password.min' => 'O Campo senha deve conter no mínimo 8 caracteres' + ]; + } + +} diff --git a/app/Http/Requests/User/UserStoreRequest.php b/app/Http/Requests/User/UserStoreRequest.php new file mode 100644 index 0000000..d7ccb5e --- /dev/null +++ b/app/Http/Requests/User/UserStoreRequest.php @@ -0,0 +1,57 @@ + + */ + public function rules() + { + return [ + 'name' => 'required', + 'email' => 'required|email|unique:users,email', + 'password' => 'required|min:8' + ]; + } + + public function failedValidation(Validator $validator) + { + throw new HttpResponseException(response()->json([ + 'success' => false, + 'message' => 'Errors found', + 'data' => $validator->errors() + ])); + } + + public function messages() + { + return [ + 'name.required' => 'O Campo Nome é obrigatório', + 'email.required' => 'O Campo Email é obrigatório', + 'email.email' => 'O Campo Email possui um formato invalido', + 'email.unique' => 'Email já cadastrado', + 'password.required' => 'O Campo senha é obrigatório', + 'password.min' => 'O Campo senha deve conter no mínimo 8 caracteres' + ]; + } + +} diff --git a/app/Http/Requests/User/UserUpdateRequest.php b/app/Http/Requests/User/UserUpdateRequest.php new file mode 100644 index 0000000..cf731c4 --- /dev/null +++ b/app/Http/Requests/User/UserUpdateRequest.php @@ -0,0 +1,53 @@ + + */ + public function rules() + { + + return [ + 'name' => 'required', + 'email' => 'required|email|unique:users,email,' . $this->id . ',id' + ]; + } + + public function failedValidation(Validator $validator) + { + throw new HttpResponseException(response()->json([ + 'success' => false, + 'message' => 'Errors found', + 'data' => $validator->errors() + ])); + } + + public function messages() + { + return [ + 'name.required' => 'O Campo Nome é obrigatório', + 'email.required' => 'O Campo Email é obrigatório', + 'email.email' => 'O Campo Email possui um formato invalido', + 'email.unique' => 'Email já cadastrado' + ]; + } +} diff --git a/app/Http/Resources/User/UserResource.php b/app/Http/Resources/User/UserResource.php new file mode 100755 index 0000000..ef4f7d3 --- /dev/null +++ b/app/Http/Resources/User/UserResource.php @@ -0,0 +1,25 @@ + + */ + public function toArray($request) + { + return [ + 'id' => $this->id, + 'name' => $this->name, + 'email' => $this->email, + 'created_at' => $this->created_at->format('Y-m-d H:i:s'), + 'updated_at' => $this->updated_at->format('Y-m-d H:i:s'), + ]; + } +} diff --git a/app/Interfaces/User/UserRepositoryInterface.php b/app/Interfaces/User/UserRepositoryInterface.php new file mode 100755 index 0000000..a17c7af --- /dev/null +++ b/app/Interfaces/User/UserRepositoryInterface.php @@ -0,0 +1,15 @@ +first(); + } + + public function searchUserByColumn($column, $value) { + + return User::where($column, 'LIKE', "%{$value}%")->get(); + } + + public function deleteUser($userId) + { + User::find($userId)->delete(); + } + + public function createUser(array $userDetails) + { + return User::create($userDetails); + } + + public function updateUser($userId, array $newDetails) + { + return User::find($userId)->update($newDetails); + } +} diff --git a/config/auth.php b/config/auth.php index 9548c15..f2db043 100644 --- a/config/auth.php +++ b/config/auth.php @@ -40,6 +40,11 @@ 'driver' => 'session', 'provider' => 'users', ], + + 'api' => [ + 'driver' => 'passport', + '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 old mode 100644 new mode 100755 index 444fafb..50c4f96 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -11,15 +11,19 @@ */ public function up(): void { - Schema::create('users', function (Blueprint $table) { - $table->id(); - $table->string('name'); - $table->string('email')->unique(); - $table->timestamp('email_verified_at')->nullable(); - $table->string('password'); - $table->rememberToken(); - $table->timestamps(); - }); + if ( ! Schema::hasTable('users')) + { + Schema::create('users', function (Blueprint $table) { + $table->id(); + $table->string('name'); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + $table->softDeletes(); + }); + } } /** diff --git a/routes/api.php b/routes/api.php old mode 100644 new mode 100755 index 03a44e1..592e88a --- a/routes/api.php +++ b/routes/api.php @@ -1,7 +1,8 @@ group(function() { + Route::get('/v1/auth/me', [AuthController::class, 'me']); + Route::post('/v1/auth/logout', [AuthController::class, 'logout']); + Route::apiResource('users', UserController::class); +}); diff --git a/storage/api-docs/api-docs.json b/storage/api-docs/api-docs.json old mode 100644 new mode 100755 index 244d983..ba9035c --- a/storage/api-docs/api-docs.json +++ b/storage/api-docs/api-docs.json @@ -15,6 +15,94 @@ } ], "paths": { + "/v1/auth/login": { + "post": { + "tags": [ + "Auth" + ], + "summary": "Autehticate with valid user", + "description": "Make a Login", + "operationId": "Login", + "requestBody": { + "description": "Provide All Info Below", + "required": true, + "content": { + "application/json": { + "schema": { + "required": [ + "email", + "password" + ], + "properties": { + "email": { + "type": "email", + "format": "text", + "example": "example@elevensoft.dev" + }, + "password": { + "type": "string", + "format": "text", + "example": "password" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "401": { + "description": "Unauthenticated" + }, + "403": { + "description": "Forbidden" + } + } + } + }, + "/v1/auth/me": { + "get": { + "tags": [ + "Auth" + ], + "summary": "Return infos of user logged", + "description": "Get User logged data", + "operationId": "Get User logged data", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "401": { + "description": "Unauthenticated" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, "/users": { "get": { "tags": [ @@ -301,5 +389,15 @@ } } } - } + }, + "tags": [ + { + "name": "Auth", + "description": "Auth" + }, + { + "name": "Users", + "description": "Users" + } + ] } \ No newline at end of file From a877626a751036d0d473fb8011db15ad611b8be6 Mon Sep 17 00:00:00 2001 From: Rinaldo Peligrineli Date: Fri, 26 Jul 2024 15:59:05 -0300 Subject: [PATCH 4/6] Adjust name of tests controllers --- tests/Feature/Auth/{AuthTest.php => AuthControllerTest.php} | 3 ++- tests/Feature/User/{UserTest.php => UserControllerTest.php} | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) rename tests/Feature/Auth/{AuthTest.php => AuthControllerTest.php} (96%) rename tests/Feature/User/{UserTest.php => UserControllerTest.php} (97%) diff --git a/tests/Feature/Auth/AuthTest.php b/tests/Feature/Auth/AuthControllerTest.php similarity index 96% rename from tests/Feature/Auth/AuthTest.php rename to tests/Feature/Auth/AuthControllerTest.php index bd767cd..57e2f3b 100755 --- a/tests/Feature/Auth/AuthTest.php +++ b/tests/Feature/Auth/AuthControllerTest.php @@ -3,7 +3,8 @@ namespace Tests\Feature; use Tests\TestCase; -class AuthTest extends TestCase + +class AuthControllerTest extends TestCase { public function testCanBeMakeLogin(): void diff --git a/tests/Feature/User/UserTest.php b/tests/Feature/User/UserControllerTest.php similarity index 97% rename from tests/Feature/User/UserTest.php rename to tests/Feature/User/UserControllerTest.php index 2f47cbd..6e14545 100755 --- a/tests/Feature/User/UserTest.php +++ b/tests/Feature/User/UserControllerTest.php @@ -5,7 +5,7 @@ use Tests\TestCase; use App\Models\User; -class UserTest extends TestCase +class UserControllerTest extends TestCase { public function setUp(): void From f3255badb0c59d57bd1180629d3f3893dd99d373 Mon Sep 17 00:00:00 2001 From: Rinaldo Peligrineli Date: Fri, 26 Jul 2024 17:44:22 -0300 Subject: [PATCH 5/6] Adicionado rota para alteracao de senha --- .../Api/v1/User/UserController.php | 65 ++++++++++++++++- .../User/UserPasswordUpdateRequest.php | 50 +++++++++++++ app/Repositories/User/UserRepository.php | 1 + database/seeders/UserTableSeeder.php | 3 +- routes/api.php | 1 + storage/api-docs/api-docs.json | 71 +++++++++++++++++-- tests/Feature/User/UserControllerTest.php | 29 ++++++++ 7 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 app/Http/Requests/User/UserPasswordUpdateRequest.php diff --git a/app/Http/Controllers/Api/v1/User/UserController.php b/app/Http/Controllers/Api/v1/User/UserController.php index 75acb0a..a86b17f 100755 --- a/app/Http/Controllers/Api/v1/User/UserController.php +++ b/app/Http/Controllers/Api/v1/User/UserController.php @@ -11,7 +11,7 @@ use App\Http\Resources\User\UserResource; use App\Http\Requests\User\UserStoreRequest; use App\Http\Requests\User\UserUpdateRequest; - +use App\Http\Requests\User\UserPasswordUpdateRequest; class UserController extends Controller @@ -294,5 +294,68 @@ public function destroy($id) ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); } } + + /** + * Make a login + * + * @return JsonResponse + * + * @OA\Put( + * path="/v1/users/alterar-senha/{id}", + * operationId="updatePassword", + * summary="Update password from user", + * tags={"Users"}, + * description="Update password from user", + * security={ + * {"bearerAuth": {}} + * }, + * @OA\Parameter( + * name="id", + * description="User ID", + * required=true, + * in="path", + * ), + * @OA\RequestBody( + * required=true, + * description="Provide All Info Below", + * @OA\JsonContent( + * required={"email","password"}, + * @OA\Property(property="password", type="string", format="text", example="password"), + * ), + * ), + * @OA\Response( + * response=200, + * description="Successful operation", + * @OA\JsonContent(ref="#/components/schemas/User") + * ), + * @OA\Response( + * response=401, + * description="Unauthenticated", + * ), + * @OA\Response( + * response=403, + * description="Forbidden" + * ) + * ) + */ + public function updatePassword(UserPasswordUpdateRequest $request, $id) { + try { + + $password = bcrypt($request->password); + $this->userRepository->updateUser($id, array('password' => $password)); + $user = $this->userRepository->getUserById($id); + + return response()->json([ + 'message' => "Senha Alterada com sucesso", + 'data' => $user, + ], JsonResponse::HTTP_OK); + } catch (\Exception $e) { + return response()->json([ + 'data' => [], + 'errorMessage' => $e->getMessage(), + 'errorCode' => $e->getCode(), + ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); + } + } } diff --git a/app/Http/Requests/User/UserPasswordUpdateRequest.php b/app/Http/Requests/User/UserPasswordUpdateRequest.php new file mode 100644 index 0000000..4de7472 --- /dev/null +++ b/app/Http/Requests/User/UserPasswordUpdateRequest.php @@ -0,0 +1,50 @@ + + */ + public function rules() + { + + return [ + 'password' => 'required|min:8' + ]; + } + + public function failedValidation(Validator $validator) + { + throw new HttpResponseException(response()->json([ + 'success' => false, + 'message' => 'Errors found', + 'data' => $validator->errors() + ])); + } + + public function messages() + { + return [ + 'password.required' => 'O Campo senha é obrigatório', + 'password.min' => 'O Campo senha deve conter no mínimo 8 caracteres' + ]; + } +} diff --git a/app/Repositories/User/UserRepository.php b/app/Repositories/User/UserRepository.php index 383d486..b097f82 100755 --- a/app/Repositories/User/UserRepository.php +++ b/app/Repositories/User/UserRepository.php @@ -44,6 +44,7 @@ public function createUser(array $userDetails) public function updateUser($userId, array $newDetails) { + return User::find($userId)->update($newDetails); } } diff --git a/database/seeders/UserTableSeeder.php b/database/seeders/UserTableSeeder.php index 00fec09..b51dd2a 100644 --- a/database/seeders/UserTableSeeder.php +++ b/database/seeders/UserTableSeeder.php @@ -3,6 +3,7 @@ namespace Database\Seeders; use Illuminate\Database\Seeder; +use App\Models\User; class UserTableSeeder extends Seeder { @@ -11,7 +12,7 @@ class UserTableSeeder extends Seeder */ public function run(): void { - \App\Models\User::factory()->create([ + User::create([ 'name' => 'example', 'email' => 'example@elevensoft.dev', 'password' => bcrypt('password') diff --git a/routes/api.php b/routes/api.php index 592e88a..7cd9a17 100755 --- a/routes/api.php +++ b/routes/api.php @@ -20,5 +20,6 @@ Route::middleware('auth:api')->group(function() { Route::get('/v1/auth/me', [AuthController::class, 'me']); Route::post('/v1/auth/logout', [AuthController::class, 'logout']); + Route::put('/v1/users/alterar-senha/{id}', [UserController::class, 'updatePassword']); Route::apiResource('users', UserController::class); }); diff --git a/storage/api-docs/api-docs.json b/storage/api-docs/api-docs.json index bde28b2..d75f5ff 100755 --- a/storage/api-docs/api-docs.json +++ b/storage/api-docs/api-docs.json @@ -308,6 +308,69 @@ } ] } + }, + "/v1/users/alterar-senha/{id}": { + "put": { + "tags": [ + "Users" + ], + "summary": "Update password from user", + "description": "Update password from user", + "operationId": "updatePassword", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "User ID", + "required": true + } + ], + "requestBody": { + "description": "Provide All Info Below", + "required": true, + "content": { + "application/json": { + "schema": { + "required": [ + "email", + "password" + ], + "properties": { + "password": { + "type": "string", + "format": "text", + "example": "password" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "401": { + "description": "Unauthenticated" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } } }, "components": { @@ -381,9 +444,9 @@ "scheme": "https", "flows": { "password": { - "authorizationUrl": "http://localhost:8090/oauth/authorize", - "tokenUrl": "http://localhost:8090/oauth/token", - "refreshUrl": "http://localhost:8090/token/refresh", + "authorizationUrl": "http://localhost:8080/oauth/authorize", + "tokenUrl": "http://localhost:8080/oauth/token", + "refreshUrl": "http://localhost:8080/token/refresh", "scopes": [] } } @@ -400,4 +463,4 @@ "description": "Users" } ] -} +} \ No newline at end of file diff --git a/tests/Feature/User/UserControllerTest.php b/tests/Feature/User/UserControllerTest.php index 6e14545..1461f95 100755 --- a/tests/Feature/User/UserControllerTest.php +++ b/tests/Feature/User/UserControllerTest.php @@ -92,4 +92,33 @@ public function testCanDeleteUser(): void } + public function testCanUpdateUser(): void + { + $header = $this->makeAuth(); + $user = User::factory()->create(); + $data = [ + "name" => fake()->name(), + "email" => fake()->email() + ]; + $response = $this->putJson('/api/users/' . $user['id'], $data, $header); + + $response->assertOk()->assertJsonCount(2); + + } + + public function testCanUpdatePassword(): void + { + $header = $this->makeAuth(); + $user = User::factory()->create(); + + $data = [ + "password" => fake()->password() + ]; + + $response = $this->putJson('/api/v1/users/alterar-senha/' . $user['id'], $data, $header); + + $response->assertOk()->assertJsonCount(2); + + } + } From 664e41d62073ef54e2727ef06defe68663fea33a Mon Sep 17 00:00:00 2001 From: Rinaldo Peligrineli Date: Mon, 29 Jul 2024 10:01:23 -0300 Subject: [PATCH 6/6] Using resource in updates --- app/Http/Controllers/Api/v1/User/UserController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/v1/User/UserController.php b/app/Http/Controllers/Api/v1/User/UserController.php index a86b17f..65af7ac 100755 --- a/app/Http/Controllers/Api/v1/User/UserController.php +++ b/app/Http/Controllers/Api/v1/User/UserController.php @@ -228,7 +228,7 @@ public function update(UserUpdateRequest $request, $id) ]); $this->userRepository->updateUser($id, $data); - $user = $this->userRepository->getUserById($id); + $user = new UserResource($this->userRepository->getUserById($id)); return response()->json([ 'message' => "Usuário Alterado com sucesso", @@ -343,7 +343,7 @@ public function updatePassword(UserPasswordUpdateRequest $request, $id) { $password = bcrypt($request->password); $this->userRepository->updateUser($id, array('password' => $password)); - $user = $this->userRepository->getUserById($id); + $user = new UserResource($this->userRepository->getUserById($id)); return response()->json([ 'message' => "Senha Alterada com sucesso",