diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..f87e9ec --- /dev/null +++ b/.env.example @@ -0,0 +1,22 @@ +APP_NAME=ProductsApi +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_URL=http://localhost +APP_TIMEZONE=UTC + +LOG_CHANNEL=stack +LOG_SLACK_WEBHOOK_URL= + +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=lu-ms1-products_api +DB_USERNAME= +DB_PASSWORD= + + +CACHE_DRIVER=file +QUEUE_CONNECTION=sync + +ALLOWED_SECRETS= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c1f4a1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/vendor +/.idea +Homestead.json +Homestead.yaml +.env +composer.lock \ No newline at end of file diff --git a/app/Console/Commands/.gitkeep b/app/Console/Commands/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php new file mode 100644 index 0000000..ad6e311 --- /dev/null +++ b/app/Console/Kernel.php @@ -0,0 +1,29 @@ +getStatusCode(); + $errorMessage = Response::$statusTexts[$errorCode]; + return $this->errorResponse($errorMessage, $errorCode); + } + + if ($exception instanceof ModelNotFoundException) { + $model = strtolower(class_basename($exception->getModel())); + return $this->errorResponse( + "Does not exist any instance of {$model} with a given id", + Response::HTTP_NOT_FOUND); + } + + if ($exception instanceof AuthorizationException) { + return $this->errorResponse( + $exception->getMessage(), + Response::HTTP_FORBIDDEN); + } + + if ($exception instanceof AuthenticationException) { + return $this->errorResponse( + $exception->getMessage(), + Response::HTTP_UNAUTHORIZED); + } + + if ($exception instanceof ValidationException) { + $errors = $exception->validator->errors()->getMessages(); + return $this->errorResponse( + $errors, + Response::HTTP_UNPROCESSABLE_ENTITY); + } + + if(env('APP_DEBUG',false)){ + return parent::render($request, $exception); + } + + return $this->errorResponse( + "Unexpected error. Try later!", + Response::HTTP_INTERNAL_SERVER_ERROR); + + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php new file mode 100644 index 0000000..72350ce --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,11 @@ +successResponse($products); + } + + public function show($product) + { + $product = Product::findOrFail($product); + return $this->successResponse($product); + } + + public function store(Request $request) + { + $rules = [ + 'name' => 'required|max:255', + 'price' => 'required|numeric|min:1|max:200' + ]; + + $this->validate($request, $rules); + $product = Product::create($request->all()); + return $this->successResponse($product); + + } + + public function update(Request $request, $product) + { + $rules = [ + 'name' => 'max:255', + 'price' => 'numeric|min:1|max:200' + ]; + + $this->validate($request, $rules); + $product = Product::findOrFail($product); + $product = $product->fill($request->all()); + + if ($product->isClean()) { + return $this->errorResponse('at least one value must be change', + Response::HTTP_UNPROCESSABLE_ENTITY); + } + + $product->save(); + return $this->successResponse($product); + } + + + public function destroy($product) + { + + $product = Product::findOrFail($product); + $product->delete(); + return $this->successResponse($product); + } + + +} \ No newline at end of file diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php new file mode 100644 index 0000000..361a11e --- /dev/null +++ b/app/Http/Middleware/Authenticate.php @@ -0,0 +1,44 @@ +auth = $auth; + } + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @param string|null $guard + * @return mixed + */ + public function handle($request, Closure $next, $guard = null) + { + if ($this->auth->guard($guard)->guest()) { + return response('Unauthorized.', 401); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/AuthenticateAccess.php b/app/Http/Middleware/AuthenticateAccess.php new file mode 100644 index 0000000..69f2411 --- /dev/null +++ b/app/Http/Middleware/AuthenticateAccess.php @@ -0,0 +1,27 @@ +header('Authorization'), $allowedSecrets)) { + return $next($request); + } + + abort(Response::HTTP_UNAUTHORIZED); + } +} diff --git a/app/Http/Middleware/ExampleMiddleware.php b/app/Http/Middleware/ExampleMiddleware.php new file mode 100644 index 0000000..166581c --- /dev/null +++ b/app/Http/Middleware/ExampleMiddleware.php @@ -0,0 +1,20 @@ +app['auth']->viaRequest('api', function ($request) { + if ($request->input('api_token')) { + return User::where('api_token', $request->input('api_token'))->first(); + } + }); + } +} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php new file mode 100644 index 0000000..a3d284f --- /dev/null +++ b/app/Providers/EventServiceProvider.php @@ -0,0 +1,19 @@ + [ + 'App\Listeners\ExampleListener', + ], + ]; +} diff --git a/app/Traits/ApiResponse.php b/app/Traits/ApiResponse.php new file mode 100644 index 0000000..2c7cf46 --- /dev/null +++ b/app/Traits/ApiResponse.php @@ -0,0 +1,19 @@ +json(['data' => $data], $statusCode); + } + + public function errorResponse($errorMessage, $statusCode) + { + return response()->json(['error' => $errorMessage, 'error_code' => $statusCode], $statusCode); + } +} \ No newline at end of file diff --git a/artisan b/artisan new file mode 100644 index 0000000..6a9d095 --- /dev/null +++ b/artisan @@ -0,0 +1,35 @@ +#!/usr/bin/env php +make( + 'Illuminate\Contracts\Console\Kernel' +); + +exit($kernel->handle(new ArgvInput, new ConsoleOutput)); diff --git a/bootstrap/app.php b/bootstrap/app.php new file mode 100644 index 0000000..9579100 --- /dev/null +++ b/bootstrap/app.php @@ -0,0 +1,103 @@ +load(); +} catch (Dotenv\Exception\InvalidPathException $e) { + // +} + +/* +|-------------------------------------------------------------------------- +| Create The Application +|-------------------------------------------------------------------------- +| +| Here we will load the environment and create the application instance +| that serves as the central piece of this framework. We'll use this +| application as an "IoC" container and router for this framework. +| +*/ + +$app = new Laravel\Lumen\Application( + dirname(__DIR__) +); + +$app->withFacades(); + +$app->withEloquent(); + +/* +|-------------------------------------------------------------------------- +| Register Container Bindings +|-------------------------------------------------------------------------- +| +| Now we will register a few bindings in the service container. We will +| register the exception handler and the console kernel. You may add +| your own bindings here if you like or you can make another file. +| +*/ + +$app->singleton( + Illuminate\Contracts\Debug\ExceptionHandler::class, + App\Exceptions\Handler::class +); + +$app->singleton( + Illuminate\Contracts\Console\Kernel::class, + App\Console\Kernel::class +); + +/* +|-------------------------------------------------------------------------- +| Register Middleware +|-------------------------------------------------------------------------- +| +| Next, we will register the middleware with the application. These can +| be global middleware that run before and after each request into a +| route or middleware that'll be assigned to some specific routes. +| +*/ + +$app->middleware([ + App\Http\Middleware\AuthenticateAccess::class +]); + +// $app->routeMiddleware([ +// 'auth' => App\Http\Middleware\Authenticate::class, +// ]); + +/* +|-------------------------------------------------------------------------- +| Register Service Providers +|-------------------------------------------------------------------------- +| +| Here we will register all of the application's service providers which +| are used to bind services into the container. Service providers are +| totally optional, so you are not required to uncomment this line. +| +*/ + +// $app->register(App\Providers\AppServiceProvider::class); +// $app->register(App\Providers\AuthServiceProvider::class); +// $app->register(App\Providers\EventServiceProvider::class); + +/* +|-------------------------------------------------------------------------- +| Load The Application Routes +|-------------------------------------------------------------------------- +| +| Next we will include the routes file so that they can all be added to +| the application. This will provide all of the URLs the application +| can respond to, as well as the controllers that may handle them. +| +*/ + +$app->router->group([ + 'namespace' => 'App\Http\Controllers', +], function ($router) { + require __DIR__ . '/../routes/web.php'; +}); + +return $app; diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..273a813 --- /dev/null +++ b/composer.json @@ -0,0 +1,49 @@ +{ + "name": "laravel/lumen", + "description": "The Laravel Lumen Framework.", + "keywords": ["framework", "laravel", "lumen"], + "license": "MIT", + "type": "project", + "require": { + "php": ">=7.1.3", + "dusterio/lumen-passport": "^0.2.15", + "guzzlehttp/guzzle": "^6.5", + "laravel/lumen-framework": "5.8.*", + "lcobucci/jwt": "3.3.3", + "vlucas/phpdotenv": "^3.3" + }, + "require-dev": { + "fzaninotto/faker": "~1.4", + "mockery/mockery": "~1.0", + "phpunit/phpunit": "~7.0" + }, + "autoload": { + "classmap": [ + "database/seeds", + "database/factories" + ], + "psr-4": { + "App\\": "app/" + } + }, + "autoload-dev": { + "classmap": [ + "tests/" + ] + }, + "scripts": { + "post-root-package-install": [ + "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" + ] + }, + "config": { + "preferred-install": "dist", + "sort-packages": true, + "optimize-autoloader": true, + "allow-plugins": { + "kylekatarnls/update-helper": true + } + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php new file mode 100644 index 0000000..e2b6317 --- /dev/null +++ b/database/factories/ModelFactory.php @@ -0,0 +1,19 @@ +define(App\Product::class, function (Faker\Generator $faker) { + return [ + 'name' => $faker->userName, + 'price' => $faker->numberBetween(10,200), + ]; +}); diff --git a/database/migrations/.gitkeep b/database/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/database/migrations/2020_03_14_083218_create_products_table.php b/database/migrations/2020_03_14_083218_create_products_table.php new file mode 100644 index 0000000..de8efc0 --- /dev/null +++ b/database/migrations/2020_03_14_083218_create_products_table.php @@ -0,0 +1,33 @@ +increments('id'); + $table->string('name')->nullable(); + $table->decimal('price')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('products'); + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php new file mode 100644 index 0000000..f18fe7e --- /dev/null +++ b/database/seeds/DatabaseSeeder.php @@ -0,0 +1,18 @@ +call('UsersTableSeeder'); + + factory(\App\Product::class, 50)->create(); + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..b8c2751 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,26 @@ + + + + + ./tests + + + + + ./app + + + + + + + + diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..b75525b --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,21 @@ + + + Options -MultiViews -Indexes + + + RewriteEngine On + + # Handle Authorization Header + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Redirect Trailing Slashes If Not A Folder... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} (.+)/$ + RewriteRule ^ %1 [L,R=301] + + # Handle Front Controller... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..04aa086 --- /dev/null +++ b/public/index.php @@ -0,0 +1,28 @@ +run(); diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..eb1216f --- /dev/null +++ b/readme.md @@ -0,0 +1,6 @@ +# ProductApi +for running a project please run following command on your terminal: +php -S 127.0.0.1:8100 -t public + +database creation: +create schema `lu-ms1-products_api` character set utf8 collate utf8_general_ci; \ No newline at end of file diff --git a/resources/views/.gitkeep b/resources/views/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/routes/web.php b/routes/web.php new file mode 100644 index 0000000..c1113f3 --- /dev/null +++ b/routes/web.php @@ -0,0 +1,23 @@ +group(['prefix' => 'api'], function () use ($router) { + $router->group(['prefix' => 'product'], function () use ($router) { + $router->get('/', ['uses' => 'ProductController@index']); + $router->post('/', ['uses' => 'ProductController@store']); + $router->get('/{product}', ['uses' => 'ProductController@show']); + $router->patch('/{product}', ['uses' => 'ProductController@update']); + $router->delete('/{product}', ['uses' => 'ProductController@destroy']); + }); +}); \ No newline at end of file diff --git a/storage/app/.gitignore b/storage/app/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/app/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/views/.gitignore b/storage/framework/views/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/views/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php new file mode 100644 index 0000000..1bad6ef --- /dev/null +++ b/tests/ExampleTest.php @@ -0,0 +1,21 @@ +get('/'); + + $this->assertEquals( + $this->app->version(), $this->response->getContent() + ); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..89a058d --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,14 @@ +