├── README.md
├── database
├── migrations
│ ├── .gitkeep
│ └── 2020_02_28_164547_create_users_table.php
├── seeds
│ └── DatabaseSeeder.php
└── factories
│ └── ModelFactory.php
├── resources
└── views
│ └── .gitkeep
├── app
├── Console
│ ├── Commands
│ │ └── .gitkeep
│ └── Kernel.php
├── Events
│ ├── Event.php
│ ├── ExampleEvent.php
│ └── UserLoggedIn.php
├── Jobs
│ ├── ExampleJob.php
│ └── Job.php
├── Http
│ ├── Middleware
│ │ ├── ExampleMiddleware.php
│ │ ├── CheckAge.php
│ │ └── Authenticate.php
│ └── Controllers
│ │ ├── AuthorsController.php
│ │ ├── BooksController.php
│ │ └── UsersController.php
├── Providers
│ ├── EventServiceProvider.php
│ ├── AppServiceProvider.php
│ └── AuthServiceProvider.php
├── Listeners
│ ├── ExampleListener.php
│ └── SendIpAddress.php
├── Users.php
├── Traits
│ ├── ConsumeExternalService.php
│ └── ApiResponder.php
├── Services
│ ├── AuthorService.php
│ └── BookService.php
└── Exceptions
│ └── Handler.php
├── storage
├── app
│ └── .gitignore
├── logs
│ └── .gitignore
└── framework
│ ├── cache
│ └── .gitignore
│ └── views
│ └── .gitignore
├── file.png
├── .gitignore
├── .editorconfig
├── config
├── services.php
├── auth.php
└── mail.php
├── tests
├── TestCase.php
└── ExampleTest.php
├── .env.example
├── public
├── .htaccess
└── index.php
├── phpunit.xml
├── artisan
├── composer.json
├── routes
└── web.php
├── bootstrap
└── app.php
├── README.adoc
└── Gateway.postman_collection.json
/README.md:
--------------------------------------------------------------------------------
1 | ApiGateWay
2 |
--------------------------------------------------------------------------------
/database/migrations/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/views/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/Console/Commands/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/storage/app/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/views/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedalaahagag/api-gateway-php/HEAD/file.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | /.idea
3 | Homestead.json
4 | Homestead.yaml
5 | .env
6 | storage/*.key
7 |
--------------------------------------------------------------------------------
/app/Events/Event.php:
--------------------------------------------------------------------------------
1 | [
5 | 'base_uri' => env('AUTHORS_SERVICE_BASE_URI'),
6 | 'secret' => env('AUTHORS_SERVICE_SECRET'),
7 | ],
8 | 'books' => [
9 | 'base_uri' => env('BOOKS_SERVICE_BASE_URI'),
10 | 'secret' => env('BOOKS_SERVICE_SECRET')
11 | ]
12 | ];
13 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | request = $request;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/Jobs/ExampleJob.php:
--------------------------------------------------------------------------------
1 | [
13 | 'App\Listeners\SendIpAddress',
14 | ],
15 | ];
16 | }
17 |
--------------------------------------------------------------------------------
/tests/ExampleTest.php:
--------------------------------------------------------------------------------
1 | get('/');
16 |
17 | $this->assertEquals(
18 | $this->app->version(), $this->response->getContent()
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Providers/AppServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->singleton('mailer', function ($app) {
17 | $app->configure('services');
18 | return $app->loadComponent('mail', 'Illuminate\Mail\MailServiceProvider', 'mailer');
19 | });
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Http/Middleware/CheckAge.php:
--------------------------------------------------------------------------------
1 | input('age') >= 80) {
17 | return 'You do not satisfy middleware security check and hence cannot proceed';
18 | }
19 | return $next($request);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Listeners/ExampleListener.php:
--------------------------------------------------------------------------------
1 | $this->baseUri
13 | ]);
14 | if(isset($this->secret)) {
15 | $headers['Authorization'] = $this->secret;
16 | }
17 | $response = $client->request($method, $requestUrl, [
18 | 'form_params' => $formParams,
19 | 'headers' => $headers
20 | ]);
21 | return $response->getBody()->getContents();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | APP_NAME=
2 | APP_ENV=local
3 | APP_KEY=
4 | APP_DEBUG=true
5 | APP_URL=http://localhost
6 | APP_TIMEZONE=UTC
7 |
8 | LOG_CHANNEL=stack
9 | LOG_SLACK_WEBHOOK_URL=
10 |
11 | DB_CONNECTION=mysql
12 | DB_HOST=mysql
13 | DB_PORT=3306
14 | DB_DATABASE=
15 | DB_USERNAME=
16 | DB_PASSWORD=
17 |
18 | CACHE_DRIVER=file
19 | QUEUE_CONNECTION=sync
20 |
21 | MAIL_DRIVER=smtp
22 | MAIL_HOST=smtp.mailtrap.io
23 | MAIL_PORT=2525
24 | MAIL_USERNAME=
25 | MAIL_PASSWORD=
26 | MAIL_ENCRYPTION=tls
27 | MAIL_FROM_ADDRESS=hello@example.com
28 | MAIL_FROM_NAME="Exampleapp"
29 |
30 | MICRO_SERVICE_BASE_URI=
31 | MICRO_SERVICE_SECRET=
32 | MICRO_SERVICE_BASE_URI_2=
33 | MICRO_SERVICE_SECRET_2=
34 |
--------------------------------------------------------------------------------
/app/Console/Kernel.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | Options -MultiViews -Indexes
4 |
5 |
6 | RewriteEngine On
7 |
8 | # Handle Authorization Header
9 | RewriteCond %{HTTP:Authorization} .
10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
11 |
12 | # Redirect Trailing Slashes If Not A Folder...
13 | RewriteCond %{REQUEST_FILENAME} !-d
14 | RewriteCond %{REQUEST_URI} (.+)/$
15 | RewriteRule ^ %1 [L,R=301]
16 |
17 | # Handle Front Controller...
18 | RewriteCond %{REQUEST_FILENAME} !-d
19 | RewriteCond %{REQUEST_FILENAME} !-f
20 | RewriteRule ^ index.php [L]
21 |
22 |
--------------------------------------------------------------------------------
/database/factories/ModelFactory.php:
--------------------------------------------------------------------------------
1 | define(App\Author::class, function (Faker\Generator $faker) {
14 | return [
15 | 'gender' => $gender = $faker->randomElement(['male','female']),
16 | 'name' => $faker->name($gender),
17 | 'country' => $faker->country,
18 | ];
19 | });
20 |
--------------------------------------------------------------------------------
/app/Jobs/Job.php:
--------------------------------------------------------------------------------
1 | to([$event->request->email]); $msg->from(['blog@blog.com']); });
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/database/migrations/2020_02_28_164547_create_users_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->string('name');
19 | $table->string('email')->unique();
20 | $table->string('password');
21 | $table->rememberToken();
22 | $table->timestamps();
23 | });
24 | }
25 |
26 | /**
27 | * Reverse the migrations.
28 | *
29 | * @return void
30 | */
31 | public function down()
32 | {
33 | Schema::dropIfExists('users');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 | ./tests
14 |
15 |
16 |
17 |
18 | ./app
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/Providers/AuthServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->router);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/Traits/ApiResponder.php:
--------------------------------------------------------------------------------
1 | json($data, $code)->header('Content-Type', 'application/json');
14 | }
15 |
16 | public function validResponse($data, $code = Response::HTTP_OK) :\Illuminate\Http\JsonResponse
17 | {
18 | return \response()->json(['data' => $data], $code)->header('Content-Type', 'application/json');
19 | }
20 |
21 | public function errorResponse($message, $code) :\Illuminate\Http\JsonResponse
22 | {
23 | return \response()->json(['message' => $message], $code);
24 | }
25 |
26 | public function errorMessage($message, $code) :\Illuminate\Http\JsonResponse
27 | {
28 | return \response()->json($message, $code)->header('Content-Type', 'application/json');
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | run();
28 |
--------------------------------------------------------------------------------
/app/Services/AuthorService.php:
--------------------------------------------------------------------------------
1 | baseUri = config('services.authors.base_uri');
16 | $this->secret = config('services.authors.secret');
17 | }
18 |
19 | public function getAuthors(){
20 | return $this->performRequest('GET','/authors');
21 | }
22 | public function getAuthor($author){
23 | return $this->performRequest('GET',"/authors/{$author}");
24 | }
25 | public function createAuthor($data) {
26 | return $this->performRequest('POST','/authors',$data);
27 | }
28 | public function editAuthor($data, $author) {
29 | return $this->performRequest('PUT',"/authors/{$author}",$data);
30 | }
31 | public function deleteAuthor($author) {
32 | return $this->performRequest('DELETE',"/authors/{$author}");
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/Services/BookService.php:
--------------------------------------------------------------------------------
1 | baseUri = config('services.books.base_uri');
19 | $this->secret = config('services.authors.secret');
20 | }
21 | public function getBooks(){
22 | return $this->performRequest('GET','/books');
23 | }
24 | public function getBook($book){
25 | return $this->performRequest('GET',"/books/{$book}");
26 | }
27 | public function createBook($data) {
28 | return $this->performRequest('POST','/books',$data);
29 | }
30 | public function editBook($data, $book) {
31 | return $this->performRequest('PUT',"/books/{$book}",$data);
32 | }
33 | public function deleteBook($book) {
34 | return $this->performRequest('DELETE',"/books/{$book}");
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/Http/Middleware/Authenticate.php:
--------------------------------------------------------------------------------
1 | auth = $auth;
26 | }
27 |
28 | /**
29 | * Handle an incoming request.
30 | *
31 | * @param \Illuminate\Http\Request $request
32 | * @param \Closure $next
33 | * @param string|null $guard
34 | * @return mixed
35 | */
36 | public function handle($request, Closure $next, $guard = null)
37 | {
38 | if ($this->auth->guard($guard)->guest()) {
39 | return response('Unauthorized.', 401);
40 | }
41 |
42 | return $next($request);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/artisan:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | make(
32 | 'Illuminate\Contracts\Console\Kernel'
33 | );
34 |
35 | exit($kernel->handle(new ArgvInput, new ConsoleOutput));
36 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel/lumen",
3 | "description": "The Laravel Lumen Framework.",
4 | "keywords": ["framework", "laravel", "lumen"],
5 | "license": "MIT",
6 | "type": "project",
7 | "require": {
8 | "php": ">=7.1.3",
9 | "dusterio/lumen-passport": "^0.2.15",
10 | "guzzlehttp/guzzle": "^6.3",
11 | "illuminate/mail": "^5.7",
12 | "laravel/lumen-framework": "5.8.*",
13 | "tymon/jwt-auth": "^1.0",
14 | "vlucas/phpdotenv": "^3.3"
15 | },
16 | "require-dev": {
17 | "fzaninotto/faker": "^1.4",
18 | "phpunit/phpunit": "^7.0",
19 | "mockery/mockery": "^1.0"
20 | },
21 | "autoload": {
22 | "classmap": [
23 | "database/seeds",
24 | "database/factories"
25 | ],
26 | "psr-4": {
27 | "App\\": "app/"
28 | }
29 | },
30 | "autoload-dev": {
31 | "classmap": [
32 | "tests/"
33 | ]
34 | },
35 | "scripts": {
36 | "post-root-package-install": [
37 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
38 | ]
39 | },
40 | "config": {
41 | "preferred-install": "dist",
42 | "sort-packages": true,
43 | "optimize-autoloader": true
44 | },
45 | "minimum-stability": "dev",
46 | "prefer-stable": true
47 | }
48 |
--------------------------------------------------------------------------------
/app/Http/Controllers/AuthorsController.php:
--------------------------------------------------------------------------------
1 | authorService = $authorService;
25 | }
26 |
27 | public function index() :JsonResponse
28 | {
29 | return $this->validResponse($this->authorService->getAuthors());
30 | }
31 |
32 | public function store(Request $request) :JsonResponse
33 | {
34 | return $this->validResponse($this->authorService->createAuthor($request->all()));
35 | }
36 |
37 | public function show($author) :JsonResponse
38 | {
39 | return $this->validResponse($this->authorService->getAuthor($author));
40 | }
41 |
42 | public function update(Request $request, $author) :JsonResponse
43 | {
44 | return $this->validResponse($this->authorService->editAuthor(($request->all()),
45 | $author));
46 | }
47 |
48 | public function destroy($author) :JsonResponse
49 | {
50 | return $this->validResponse($this->authorService->deleteAuthor($author));
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/Http/Controllers/BooksController.php:
--------------------------------------------------------------------------------
1 | bookService = $bookService;
28 | $this->authorService = $authorService;
29 | }
30 |
31 | public function index() :JsonResponse
32 | {
33 | return $this->successResponse($this->bookService->getBooks());
34 | }
35 |
36 | public function store(Request $request) :JsonResponse
37 | {
38 | $this->authorService->getAuthor($request->author_id);
39 |
40 | return $this->successResponse($this->bookService->createBook($request->all()));
41 | }
42 |
43 | public function show($book) :JsonResponse
44 | {
45 | return $this->successResponse($this->bookService->getBook($book));
46 | }
47 |
48 | public function update(Request $request, $book) :JsonResponse
49 | {
50 | return $this->successResponse($this->bookService->editBook(($request->all()),
51 | $book));
52 | }
53 |
54 | public function destroy($book) :JsonResponse
55 | {
56 | return $this->successResponse($this->bookService->deleteBook($book));
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/routes/web.php:
--------------------------------------------------------------------------------
1 | group(['middleware' => 'auth:api'], function () use ($router){
15 | $router->get('/users/me','UsersController@me');
16 | });
17 |
18 | $router->group(['middleware' => 'client.credentials'], function () use ($router){
19 | $router->get('/authors','AuthorsController@index');
20 | $router->post('/authors','AuthorsController@store');
21 | $router->get('/authors/{author}','AuthorsController@show');
22 | $router->put('/authors/{author}','AuthorsController@update');
23 | $router->patch('/authors/{author}','AuthorsController@update');
24 | $router->delete('/authors/{author}','AuthorsController@destroy');
25 |
26 | $router->get('/books','BooksController@index');
27 | $router->post('/books','BooksController@store');
28 | $router->get('/books/{book}','BooksController@show');
29 | $router->put('/books/{book}','BooksController@update');
30 | $router->patch('/books/{book}','BooksController@update');
31 | $router->delete('/books/{book}','BooksController@destroy');
32 |
33 | $router->get('/users','UsersController@index');
34 | $router->post('/users','UsersController@store');
35 | $router->get('/users/{user}','UsersController@show');
36 | $router->put('/users/{user}','UsersController@update');
37 | $router->patch('/users/{user}','UsersController@update');
38 | $router->delete('/users/{user}','UsersController@destroy');
39 | });
40 |
--------------------------------------------------------------------------------
/app/Http/Controllers/UsersController.php:
--------------------------------------------------------------------------------
1 | validResponse($users, Response::HTTP_OK);
32 | }
33 |
34 | public function store(Request $request) :string
35 | {
36 |
37 | $rules = [
38 | 'name' => 'required|max:255',
39 | 'email' => 'required|email|unique:users,email',
40 | 'password' => 'required|confirmed|min:8',
41 | ];
42 |
43 | $this->validate($request, $rules);
44 | $fields = $request->all();
45 | $fields['password'] = Hash::make($request->password);
46 |
47 | $user = Users::create($fields);
48 |
49 | return $this->validResponse($user, Response::HTTP_CREATED);
50 | }
51 |
52 | public function show($book) :string
53 | {
54 | $user = Users::findOrFail($book);
55 |
56 | return $this->validResponse($book);
57 | }
58 |
59 | public function update(Request $request, $user) :string
60 | {
61 | $rules = [
62 | 'name' => 'max:255',
63 | 'email' => 'email|unique:user,email,'.$user,
64 | 'password' => 'confirmed|min:8',
65 | ];
66 |
67 | $this->validate($request, $rules);
68 | $user = Users::findOrFail($user);
69 |
70 | if ($request->has('password')) {
71 | $user->password = Hash::make($request->password);
72 | }
73 |
74 | $user->fill($request->all());
75 |
76 | if ($user->isClean()) {
77 | return $this->errorResponse('At least one value should change', Response::HTTP_UNPROCESSABLE_ENTITY);
78 | }
79 |
80 | $user->save();
81 |
82 | return $this->validResponse($user, Response::HTTP_CREATED);
83 | }
84 |
85 | public function destroy($user) :string
86 | {
87 | $user = Users::findOrFail($user);
88 | $user->delete();
89 |
90 | return $this->validResponse($user);
91 | }
92 |
93 | public function me(Request $request) :JsonResponse
94 | {
95 | return $this->validResponse($request->user());
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/app/Exceptions/Handler.php:
--------------------------------------------------------------------------------
1 | getStatusCode();
55 | $message = Response::$statusTexts[$code];
56 | return $this->errorResponse($message, $code);
57 | }
58 | if($exception instanceof ModelNotFoundException){
59 | $model = strtolower(class_basename($exception->getModel()));
60 | return $this->errorResponse("Dose exist instance of {$model} with this id", Response::HTTP_NOT_FOUND);
61 | }
62 | if($exception instanceof AuthorizationException){
63 | return $this->errorResponse($exception->getMessage(), Response::HTTP_FORBIDDEN);
64 | }
65 | if($exception instanceof AuthenticationException){
66 | return $this->errorResponse($exception->getMessage(), Response::HTTP_UNAUTHORIZED);
67 | }
68 | if($exception instanceof ValidationException){
69 | $errors = $exception->errors();
70 | return $this->errorResponse($errors, Response::HTTP_UNPROCESSABLE_ENTITY);
71 | }
72 | if($exception instanceof ClientException){
73 | $message = $exception->getResponse()->getBody();
74 | $code = $exception->getCode();
75 | return $this->errorMessage($message, $code);
76 | }
77 | if(env('APP_DEBUG', false)) {
78 | return parent::render($request, $exception);
79 | }
80 | return $this->errorResponse('unexpected error', Response::HTTP_INTERNAL_SERVER_ERROR);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/config/auth.php:
--------------------------------------------------------------------------------
1 | [
17 | 'guard' => env('AUTH_GUARD', 'api'),
18 | ],
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Authentication Guards
23 | |--------------------------------------------------------------------------
24 | |
25 | | Next, you may define every authentication guard for your application.
26 | | Of course, a great default configuration has been defined for you
27 | | here which uses session storage and the Eloquent user provider.
28 | |
29 | | All authentication drivers have a user provider. This defines how the
30 | | users are actually retrieved out of your database or other storage
31 | | mechanisms used by this application to persist your user's data.
32 | |
33 | | Supported: "session", "token"
34 | |
35 | */
36 |
37 | 'guards' => [
38 | 'api' => [
39 | 'driver' => 'passport',
40 | 'provider' => 'users'
41 | ],
42 | ],
43 |
44 | /*
45 | |--------------------------------------------------------------------------
46 | | User Providers
47 | |--------------------------------------------------------------------------
48 | |
49 | | All authentication drivers have a user provider. This defines how the
50 | | users are actually retrieved out of your database or other storage
51 | | mechanisms used by this application to persist your user's data.
52 | |
53 | | If you have multiple user tables or models you may configure multiple
54 | | sources which represent each model / table. These sources may then
55 | | be assigned to any extra authentication guards you have defined.
56 | |
57 | | Supported: "database", "eloquent"
58 | |
59 | */
60 |
61 | 'providers' => [
62 | 'users' => [
63 | 'driver' => 'eloquent',
64 | 'model' => \App\Users::class,
65 | ],
66 | ],
67 |
68 | /*
69 | |--------------------------------------------------------------------------
70 | | Resetting Passwords
71 | |--------------------------------------------------------------------------
72 | |
73 | | Here you may set the options for resetting passwords including the view
74 | | that is your password reset e-mail. You may also set the name of the
75 | | table that maintains all of the reset tokens for your application.
76 | |
77 | | You may specify multiple password reset configurations if you have more
78 | | than one user table or model in the application and you want to have
79 | | separate password reset settings based on the specific user types.
80 | |
81 | | The expire time is the number of minutes that the reset token should be
82 | | considered valid. This security feature keeps tokens short-lived so
83 | | they have less time to be guessed. You may change this as needed.
84 | |
85 | */
86 |
87 | 'passwords' => [
88 | //
89 | ],
90 |
91 | ];
92 |
--------------------------------------------------------------------------------
/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | bootstrap();
8 |
9 | /*
10 | |--------------------------------------------------------------------------
11 | | Create The Application
12 | |--------------------------------------------------------------------------
13 | |
14 | | Here we will load the environment and create the application instance
15 | | that serves as the central piece of this framework. We'll use this
16 | | application as an "IoC" container and router for this framework.
17 | |
18 | */
19 |
20 | $app = new Laravel\Lumen\Application(
21 | dirname(__DIR__)
22 | );
23 |
24 | $app->withFacades();
25 |
26 | $app->withEloquent();
27 |
28 | $app->configure('services');
29 | $app->configure('auth');
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Register Container Bindings
34 | |--------------------------------------------------------------------------
35 | |
36 | | Now we will register a few bindings in the service container. We will
37 | | register the exception handler and the console kernel. You may add
38 | | your own bindings here if you like or you can make another file.
39 | |
40 | */
41 |
42 | $app->singleton(
43 | Illuminate\Contracts\Debug\ExceptionHandler::class,
44 | App\Exceptions\Handler::class
45 | );
46 |
47 | $app->singleton(
48 | Illuminate\Contracts\Console\Kernel::class,
49 | App\Console\Kernel::class
50 | );
51 |
52 | /*
53 | |--------------------------------------------------------------------------
54 | | Register Middleware
55 | |--------------------------------------------------------------------------
56 | |
57 | | Next, we will register the middleware with the application. These can
58 | | be global middleware that run before and after each request into a
59 | | route or middleware that'll be assigned to some specific routes.
60 | |
61 | */
62 |
63 | $app->middleware([
64 | ]);
65 |
66 | $app->routeMiddleware([
67 | 'auth' => \App\Http\Middleware\Authenticate::class,
68 | 'client.credentials' => \Laravel\Passport\Http\Middleware\CheckClientCredentials::class,
69 | ]);
70 |
71 | // configure mail aliases
72 | $app->configure('mail');
73 | $app->alias('mailer', Illuminate\Mail\Mailer::class);
74 | $app->alias('mailer', Illuminate\Contracts\Mail\Mailer::class);
75 | $app->alias('mailer', Illuminate\Contracts\Mail\MailQueue::class);
76 |
77 | /*
78 | |--------------------------------------------------------------------------
79 | | Register Service Providers
80 | |--------------------------------------------------------------------------
81 | |
82 | | Here we will register all of the application's service providers which
83 | | are used to bind services into the container. Service providers are
84 | | totally optional, so you are not required to uncomment this line.
85 | |
86 | */
87 |
88 | // $app->register(App\Providers\AppServiceProvider::class);
89 | $app->register(App\Providers\AuthServiceProvider::class);
90 | $app->register(Laravel\Passport\PassportServiceProvider::class);
91 | $app->register(Dusterio\LumenPassport\PassportServiceProvider::class);
92 | $app->register(Tymon\JWTAuth\Providers\LumenServiceProvider::class);
93 | $app->register(App\Providers\EventServiceProvider::class);
94 | $app->register(Illuminate\Mail\MailServiceProvider::class);
95 |
96 | /*
97 | |--------------------------------------------------------------------------
98 | | Load The Application Routes
99 | |--------------------------------------------------------------------------
100 | |
101 | | Next we will include the routes file so that they can all be added to
102 | | the application. This will provide all of the URLs the application
103 | | can respond to, as well as the controllers that may handle them.
104 | |
105 | */
106 |
107 | $app->router->group([
108 | 'namespace' => 'App\Http\Controllers',
109 | ], function ($router) {
110 | require __DIR__.'/../routes/web.php';
111 | });
112 |
113 | return $app;
114 |
--------------------------------------------------------------------------------
/config/mail.php:
--------------------------------------------------------------------------------
1 | env('MAIL_DRIVER', 'smtp'),
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | SMTP Host Address
24 | |--------------------------------------------------------------------------
25 | |
26 | | Here you may provide the host address of the SMTP server used by your
27 | | applications. A default option is provided that is compatible with
28 | | the Mailgun mail service which will provide reliable deliveries.
29 | |
30 | */
31 |
32 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | SMTP Host Port
37 | |--------------------------------------------------------------------------
38 | |
39 | | This is the SMTP port used by your application to deliver e-mails to
40 | | users of the application. Like the host we have set this value to
41 | | stay compatible with the Mailgun e-mail application by default.
42 | |
43 | */
44 |
45 | 'port' => env('MAIL_PORT', 587),
46 |
47 | /*
48 | |--------------------------------------------------------------------------
49 | | Global "From" Address
50 | |--------------------------------------------------------------------------
51 | |
52 | | You may wish for all e-mails sent by your application to be sent from
53 | | the same address. Here, you may specify a name and address that is
54 | | used globally for all e-mails that are sent by your application.
55 | |
56 | */
57 |
58 | 'from' => [
59 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
60 | 'name' => env('MAIL_FROM_NAME', 'Example'),
61 | ],
62 |
63 | /*
64 | |--------------------------------------------------------------------------
65 | | E-Mail Encryption Protocol
66 | |--------------------------------------------------------------------------
67 | |
68 | | Here you may specify the encryption protocol that should be used when
69 | | the application send e-mail messages. A sensible default using the
70 | | transport layer security protocol should provide great security.
71 | |
72 | */
73 |
74 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
75 |
76 | /*
77 | |--------------------------------------------------------------------------
78 | | SMTP Server Username
79 | |--------------------------------------------------------------------------
80 | |
81 | | If your SMTP server requires a username for authentication, you should
82 | | set it here. This will get used to authenticate with your server on
83 | | connection. You may also set the "password" value below this one.
84 | |
85 | */
86 |
87 | 'username' => env('MAIL_USERNAME'),
88 |
89 | 'password' => env('MAIL_PASSWORD'),
90 |
91 | /*
92 | |--------------------------------------------------------------------------
93 | | Sendmail System Path
94 | |--------------------------------------------------------------------------
95 | |
96 | | When using the "sendmail" driver to send e-mails, we will need to know
97 | | the path to where Sendmail lives on this server. A default path has
98 | | been provided here, which will work well on most of your systems.
99 | |
100 | */
101 |
102 | 'sendmail' => '/usr/sbin/sendmail -bs',
103 |
104 | /*
105 | |--------------------------------------------------------------------------
106 | | Markdown Mail Settings
107 | |--------------------------------------------------------------------------
108 | |
109 | | If you are using Markdown based email rendering, you may configure your
110 | | theme and component paths here, allowing you to customize the design
111 | | of the emails. Or, you may simply stick with the Laravel defaults!
112 | |
113 | */
114 |
115 | 'markdown' => [
116 | 'theme' => 'default',
117 |
118 | 'paths' => [
119 | resource_path('views/vendor/mail'),
120 | ],
121 | ],
122 |
123 | ];
124 |
--------------------------------------------------------------------------------
/README.adoc:
--------------------------------------------------------------------------------
1 | = Lumen Api Gateway Microservice
2 |
3 | This project was done as a part of 3 *Microservices* project using *Lumen* and *Laradock*
4 | Can be used as a stand a lone project and part of the *Microservices* project.
5 |
6 | = What is an API Gateway
7 |
8 | An API gateway takes all API calls from clients, then routes them to the appropriate microservice with request routing, composition, and protocol translation. Typically it handles a request by invoking multiple microservices and aggregating the results, to determine the best path. It can translate between web protocols and web‑unfriendly protocols that are used internally.
9 | This part is the first point that the client connect to when trying to use the microserivces of
10 | a given system also provides auth services for the client and manges access to the microservices.
11 |
12 | image::file.png[]
13 | vices.io/i/apigateway.jpg
14 | ____
15 |
16 | Lumen is designed for building lightning fast micro-services and APIs
17 |
18 | ____
19 |
20 | *Laradock* is a full PHP development environment for Docker that Includes prepackaged Docker Images, all preconfigured to provide a wonderful PHP development environment. Laradock is well known in the Laravel/lumen community, as the project started with single focus on running Laravel projects on Docker.
21 |
22 | == Usefull Links
23 |
24 | * https://laradock.io/[Laradock]
25 | * https://lumen.laravel.com/[Lumen]
26 |
27 | == Requirements
28 |
29 | To be able to run this project one needs the following technologies:
30 |
31 | * https://www.docker.com/[Docker] -> Laradock uses Docker
32 | * https://getcomposer.org/[Composer] -> No need if one uses Laradock/Docker
33 |
34 | if one want to start Lumen from scratch Composer is need as a package manager.
35 |
36 | == Instructions
37 |
38 | Following the instructions one should be able to run this project or at least have a good base how to start a Lumen project using Laradock.
39 |
40 | . `git clone git@github.com:ahmedalaahagag/api-gateway-php.git`
41 | . Rename `.env.example` file to `.env`. The
42 | .env file is the environment file that deals with project configurations like database credentials, api keys, debug mode, application keys etc and this file is out of version control.
43 | . Set your application key to a random string. Typically, this string should be 32 characters long. In .env file it is called eg
44 | `APP_KEY=akkfjvlakengoemvgkcgelapchyekci`
45 | . Add `MICRO_SERVICE_BASE_URI` which should point to the address of your microserivce
46 | ex.`AUTHORS_SERVICE_BASE_URI="http://IP_ADDRESS"` if you are using localy with laradock use your local network ip address which you can get from
47 | Linux or MacOs `ifconfig` or Windows from `network adapter properties`.
48 | . Add `MICRO_SERVICE_SECRET` which should be added to your microservice to secure the connection between the api gateway
49 | and the microservice ex.`AUTHORS_SERVICE_SECRET=ECvSZ5O6P9x1GP1fvbtEVktoN358BofH`
50 | should be sent with every api call to this microservice using the header `Authorization:`
51 | ex.`Authorization:U0ZyuUhUrNgmLu9TIWJJay30gCT8UOyd`
52 | . Laradock clone it inside the project folder `git clone https://github.com/laradock/laradock.git`
53 |
54 | == To run this project as a standalone project
55 |
56 | . `cd laradock`
57 | . `cp env-example .env`
58 | . `docker-compose up -d nginx mysql phpmyadmin workspace` => To start the server
59 | . `docker-compose exec workspace bash` => to get access to virtual machine and here one can execute any artisan command
60 | . Run `composer install` => to install all php dependencies. This will create a vendor folder which is the core lumen framework
61 | . Inside `.env` file in the project root update `DB_HOST=mysql`
62 | . With SQL tool as `PhpMyAdmin` which is already provided by *Laradock* at port `localhost:8080` or similar connect to the MySQL to create a new DB. Or use the Docker MySQL workspace bash to use commands instead.
63 | . Default values `host:mysql username:root password:root`
64 | . Update database name and else in `.env`
65 |
66 | Remember all the *Docker* commands have to be run it under *Laradock* folder as there the Docker files are placed.
67 |
68 | If one wants to run this project as it is after `composer install` run migration as `php artisan migrate` to update the DB with the right tables. Then seed with `php artisan db:seed` to populate the DB with some fake data.
69 |
70 | == Securing the Api gateway
71 | This gateway is secured using `lumen/passport` is a lumen package which authorizes and authenticates users
72 | to start using the gateway your client should first request a token using
73 | `localhost/oauth/token` using provided `client_id` and `client_secret`
74 | to generate those you need to run `docker-compose exec workspace bash` and use the following command after installing passport
75 | `php artisan passport:install`
76 | which a typical Oauth process.
77 | If you are not familier with the process refer to
78 | https://github.com/dusterio/lumen-passport
79 |
80 | == Adding a new microservice
81 | After writing your microservice code
82 |
83 | . obtain the address for the microservice.
84 | . in `.env` file add
85 |
86 | MICRO_SERVICE_BASE_URI="http://IP_ADDRESS:PORT"
87 | MICRO_SERVICE_SECRET=
88 |
89 | Which is how your api gateway gets to your microservice
90 |
91 | . in `app/Http/Controllers` dir add the oprations that you want to perform with your api gatway
92 | . in `app/Http/Services` dir add the api call using `ConsumeExternalService` trait or write your own
93 | `ConsumeExternalService` is a guzzle wrapper
94 | http://docs.guzzlephp.org/en/stable/
95 | . in `routes/web.php` add your new routes
96 |
97 | And like this you've added a new microservice.
98 |
99 | Note : This is not the best practice though read more on service discovery
100 | https://www.nginx.com/blog/service-discovery-in-a-microservices-architecture/
101 |
102 | == Troubleshoot some possible issues
103 |
104 | It is possible one has issues with connecting to MySQL image of Docker. A possible solution as follows:
105 |
106 | From terminal
107 |
108 | ----
109 | $ docker-compose exec mysql bash
110 | $ mysql -u root -p
111 |
112 | # Execute commands
113 | ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';
114 | ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root';
115 | ALTER USER 'default'@'%' IDENTIFIED WITH mysql_native_password BY 'secret';
116 | ----
117 |
118 | May be need to restart the container after the changes
119 |
120 | ----
121 | $ docker-compose down
122 | $ docker-compose up -d nginx mysql
123 | ----
124 |
125 | == Tutorial
126 |
127 | How to create a *Simple Gateway*. Step by step explanations to get start with *Lumen*
128 |
129 | *1) Create a Lumen project*
130 |
131 | First one need to install Lumen via Composer:
132 |
133 | ----
134 | composer global require "laravel/lumen-installer"
135 | ----
136 |
137 | Then can run:
138 |
139 | ----
140 | lumen new ApiGateWay
141 | ----
142 |
143 | *2) Clone Laradock inside the ApiGateWay folder project*
144 |
145 | The steps above shows what to do with Laradock and Docker parts.
146 |
147 | *3) Connect to the MySQL container*
148 | One can connect via a program like PhpMyAdmin or MysqlWorkBench or else to the MySQL container. Then need to create the DB.
149 |
150 | Example of a connection set up:
151 | image:doc/Edit_Connection_Laradock__MySQL.png[connection_db]
152 |
153 | Remember the name of the DB need to be put inside the `.env` file along with the credetials of it.
154 |
155 | ----
156 | DB_CONNECTION=mysql
157 | DB_HOST=mysql -> This need to be in this way and if any connection issues please refer to the troubleshoot section above
158 | DB_PORT=3306 -> Change this if you use a different port
159 | DB_DATABASE= < DB_NAME > < ex. gateway >
160 | DB_USERNAME= < DB_USERNAME > < ex. root >
161 | DB_PASSWORD= < DB_PASSWORD > < ex. root >
162 | ----
163 |
164 | After one done the first preliminary set up steps, then is the time to move forward creating the API itself.
165 |
166 | *4) Eloquent*
167 |
168 | In simple words allows calling built-in functions instead of writing complex queries.
169 | The Eloquent ORM includes *Laravel/Lumen* which provides a beautiful, simple *ActiveRecord* implementation for working with the database.
170 | Each database table has a corresponding *Model* which is used to interact with that table. Models allow you to query for data in your tables, as well as insert new records into the table. For example, one can say `Users::all()` to get all the users inside users table rather than writing `select * from users`.
171 | Where Users in `Users::all() is a`model`.
172 |
173 | Then to use *Eloquent* uncomment the `$app->withEloquent()`
174 | in your `bootstrap/app.php`
175 |
176 | *5) Facades*
177 |
178 | A *facade* class is a wrapper around a few classes serving a particular purpose to make a series of steps required to perform a task in a single function call.
179 |
180 | Then uncomment the `$app->withFacades()` call inside `bootstrap/app.php` file to use *Laravel Facade*.
181 |
182 | *6) Users*
183 | Then inside the *app* folder, will create `Users.php`. It is called a model in *MVC framework*.
184 | It will reflect *users table* inside database which has not been created yet Inside this model will have set some fillable `fields =>name` and `gender` and `country`
185 | as all *Eloquent models* protect against mass-assignment by default. A *mass-assignment* vulnerability occurs when a user passes an unexpected HTTP parameter through a request, and that parameter changes a column in your database you did not expect
186 |
187 | See more at https://laravel.com/docs/5.7/eloquent#mass-assignment[mass-assignment]
188 |
189 | *7)Create a migration*
190 |
191 | To create a migration one need to be inside the *Docker container workspace*:
192 |
193 | ----
194 | docker-compose exec workspace bash
195 | ----
196 |
197 | Then:
198 |
199 | ----
200 | root@688df818e9b7:/var/www# php artisan make:migration create_users_table
201 | ----
202 |
203 | This will create migration file inside `database/migrations`
204 |
205 | Example: `2020_02_27_153519_create_users_table.php`
206 |
207 | A migration file usually defines the schema of the database table.
208 |
209 | See more at https://laravel.com/docs/5.7/migrations[migrations]
210 |
211 | Then run command
212 |
213 | ----
214 | php artisan migrate
215 | ----
216 |
217 | This will migrate schema to database according to what is present in migration file. Now your database will have *users table*.
218 | This is how Eloquent makes it so easy to create tables, share this schema with the team and use its simple functions to generate complex sql queries.
219 |
220 | *8) Fake data to use for the test of the API*
221 |
222 | Now the issue how we test the API if we do not have any data to
223 | test actually.
224 |
225 | *Lumen* has a very fine way to create dummy data. It is called *Model Factories*. That uses https://github.com/fzaninotto/Faker[Faker] package behind the scenes. Let's dive into.
226 |
227 | Inside `database/factories/ModelFactory.php` will define a factory for each table (1 only for users table in this case). A factory is a suitable word because a factory creates object based on rules defined inside the factory.
228 |
229 | Now we need the a *seeder class* to call this factory to start creating objects and tell it a number to produce as well. So command `php artisan make:seeder UsersTableSeeder.
230 |
231 | Alternatively you can create a factory as in *database/factories/ModelFactory.php* to create objects of Users +
232 | Will ask it to create 50 objects whenever it is called. Inside
233 | `database/seeds/DatabaseSeeder.php` call `UsersFactory`.
234 | Now we will run `php artisan db:seed` command to seed the database. Which will call `run()` in `DatabaseSeeder.php` and seed all listed seeders.
235 | We now have 50 dummy records inside users table.
236 |
237 | Example:
238 | 
239 |
240 | *9) API end points*
241 |
242 | If we go to `routes/web.php` here is we define our endpoints/routes. For example if one wants to get all books one will set an endpoint with `url books/all`.
243 | See the file. One used `Users::all()` (in a callback function)
244 | which is *Eloquent* way to fetch all the results for a
245 | model which has also been discussed earlier.
246 |
247 | Now if one hit the endpoint through *Postman*
248 | Attached is a collection that can be imported to *Postman*
249 | `Gateway.postman_collection.json`
250 | or visit in browser`localhost/users` one should see all users in json.
251 |
252 | = What to expect with the code
253 |
254 | * Standardized response format `ApiResponder.php`
255 | * Standardized Api requests `ConsumeExternalService.php`
256 | * Independent services for microservices `AuthorService.php` and `BookService.php`
257 | * `Secret Key` protected endpoints `AuthenticateAccess.php`
258 | * Standardized exception response format `Handler.php`
259 | * RESTful Based API format `web.php`
260 |
261 | = Response Example
262 |
263 | [source]
264 | ----
265 | All APIs should be calls with Authorization header
266 | `Authorization`
267 | Which is the key provided in the .env file with the key `ACCEPTED_SECRETS`
268 |
269 | Get example
270 |
271 | API : POST localhost/oauth/token
272 | Response :
273 | {
274 | "token_type": "Bearer",
275 | "expires_in": 31622400,
276 | "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjAxYzdjYzIwYWE2ZWM2ODg1NzQxNjVjYTc1ZDJhMzk2MjkyOTVkYTAwNTM2OTQ4OGI4MjNkOGE5OTdjYmYyMjc2NGQwZTdjZjM4MGJkOWJlIn0.eyJhdWQiOiI0IiwianRpIjoiMDFjN2NjMjBhYTZlYzY4ODU3NDE2NWNhNzVkMmEzOTYyOTI5NWRhMDA1MzY5NDg4YjgyM2Q4YTk5N2NiZjIyNzY0ZDBlN2NmMzgwYmQ5YmUiLCJpYXQiOjE1ODI5ODQzODcsIm5iZiI6MTU4Mjk4NDM4NywiZXhwIjoxNjE0NjA2Nzg3LCJzdWIiOiIxIiwic2NvcGVzIjpbIioiXX0.b8OwcPVlfoYDtWBoBE64A-5Aur0FEkTyv0vBLjWqA1mjOkRXW1EyCHNzxEZsE4e3oPgjrsZcN37YlxetlnQfOtaxfpSTF9lSL7xXmm2nKhA46XcnyFgSeV3H8X-Msn22q0hiouSXdMcuQiXyWFTn3vDKDleq5s7kXUoI4MTeFScXnf_L8HQG2di7WCOKQppWTPtZA0ArnDoIBesoUxSu0J8CFg7qP1DdjxeYx0HKNU4FKytuJT-_i-aRAsPG5k-Z7b7AaSwmS5EDmmDjcnwApRLFcerBApaWJx9L26Rg7m0RT6xGCXL31k_454YIvmRMJVMbbwo-2zBPjrbWgKowoQ29MvVjy4BDuzM900QW3W3KjpiRDO7ZV066s0xe8wFTZrq7g4qK0DpG64Sf1k1mZYL3IoMzUvNPrPVhiLSHhtmUE3aumYt90euaVcafPTREUN3DYbvS5BY3RApt1nJPe3c2KfP7ARqi12NKsX3N8RkekS6KXwq1tPZnIcHFip2UJSwpu0kgDJzZAYVfrdkH7UtEmssEriRm__59IcSLQedyHiq2ROMjHHr4VoWdTLpd4sGHTYGqQIn0jj2RdKEmscl0rlxYNWC2krVgEWBTrvY42NcuzkQGoasMTt1oIf-Kcnf-x_UXU2cP4Q7Hi8aQUuQ0a_Q1CntZWvyvFFe2A5o",
277 | "refresh_token": "def50200a5d83b16a4a238488ba574d6fb45c8e2196ded5c57d47709e2b8dd827ea071ece4f8ba01e7749a96712e43bb4e1360f566ba5933fada03a6253d29b5d001e7d6c430177cba538b053afbf75b7c263390e7f0ee519f1f9acb398a35dac6c9dd678029f3f190019e5037a9e9ae685a4afef4cc777883948f02fbff755a21b38ec28364b71ee0c3a72174d83aa574c8df17b0e1a6f65e1417ed085d383690999a53336ea2e33e19f7c1e9f7d9d74b0d37203e5284fa2ac6b6cf9bef509182fb9645a62b7dbbd8ddf44a39c340755ad2474c2c95428e24fbd850ce4e76dbf94195db39033e57cc002d73b847864373f09cc3c9c6a5fc7644a7edbfd0aa596ae001fbf5a106327f10f69995ec3b13cd7ad9dd6ca12858cdf41b98d8d4b55e5670a01422edf75ab321546f41ea8de75727b53bc4d5ad3f960d00ddefcc8c823ade553e548803fdc1e21557481dfd3c164684c91df4262eec67f3e1284f61db952a4cc5"
278 | }
279 |
280 | Error example
281 |
282 | API : GET localhost/users
283 | Response :
284 | {
285 | "error": "Unauthenticated."
286 | }
287 | ----
288 |
--------------------------------------------------------------------------------
/Gateway.postman_collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "info": {
3 | "_postman_id": "8a406aa6-865d-4bb1-9cfc-bd254b49d608",
4 | "name": "Gateway",
5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
6 | },
7 | "item": [
8 | {
9 | "name": "Authors",
10 | "request": {
11 | "method": "GET",
12 | "header": [
13 | {
14 | "key": "Authorization",
15 | "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6Ijk4ZmZlMDFhYWFkNzM0ZDNiYWIyMDJlYmRkYjE3NDEwMTgwZmE5ZDVmYWM3MWMzODlmZTcxYzliODFlMzBiMzBiOGIwNjQ5MTNiOTRkNGRhIn0.eyJhdWQiOiIxIiwianRpIjoiOThmZmUwMWFhYWQ3MzRkM2JhYjIwMmViZGRiMTc0MTAxODBmYTlkNWZhYzcxYzM4OWZlNzFjOWI4MWUzMGIzMGI4YjA2NDkxM2I5NGQ0ZGEiLCJpYXQiOjE1ODI5MDc2NDcsIm5iZiI6MTU4MjkwNzY0NywiZXhwIjoxNjE0NTMwMDQ3LCJzdWIiOiIiLCJzY29wZXMiOltdfQ.ftafh-ZJ8f2U20K8xaeLW9knDAMQ4SohZbSb4gi-RMZ-QDuxiJwdIHnhSkl01j5qBjHkixo5H5SErlTzJGccamq7K88cLIIORc6qHf14g9fSxzf_M5BzKeypiepLY9m3Lns-6HSHjgQX_zStimj6pEWh8fhRl7X1BbOQO98rCYO_8ML75wkU4EKzZrKi19ZncrGhDTg5eAG7a7_wTWMeHT4C6ROG_dQiTwjraEeCDV3ooxm6Yivw0he7D2YXrmKtVOWdnnHUyxrHTSNEcDzmDAYDtiqRobwX1eTBpvxkbyZi3skfk2VkWNIsEaZS-HDK-QeYo-16-rOP7w7dYfm0bXp42Be_HFCF2drxJzwLKbBTwrDodKw46knf1EbL_t7I2WWLymde0U1ndR3ugI2c51iBs5-d6YM0bFaTGhHrnUXkqDRAcCEvOP77iGA-HAsA3NgY0w9sKSP5c3HdLrqQ9vGmQG6tZMIE5XLzxqnO4U7-Bg-27rSldt1wXB-qYibbec_SmlBZsUpZkXZNusO6PhF1DKWDZz6DBAYpy-fc3nBP0RuUrsO8joToY_GJGUZ77Wy7J6Tn9vA-zIH3e1w4Y8Y3vc7g2QTanZ0lKrfOALl7cZA8y9ItLyZXgSVwaDqzwtTD4WwLueQe9nlhBUTjM9W7tmHMN3hMnvuWtNZ1g4k",
16 | "type": "text"
17 | }
18 | ],
19 | "url": {
20 | "raw": "localhost/authors",
21 | "host": [
22 | "localhost"
23 | ],
24 | "path": [
25 | "authors"
26 | ]
27 | }
28 | },
29 | "response": []
30 | },
31 | {
32 | "name": "Create Author",
33 | "request": {
34 | "method": "POST",
35 | "header": [
36 | {
37 | "key": "Content-Type",
38 | "name": "Content-Type",
39 | "value": "application/x-www-form-urlencoded",
40 | "type": "text"
41 | }
42 | ],
43 | "body": {
44 | "mode": "urlencoded",
45 | "urlencoded": [
46 | {
47 | "key": "name",
48 | "value": "Test 3",
49 | "type": "text"
50 | },
51 | {
52 | "key": "gender",
53 | "value": "male",
54 | "type": "text"
55 | },
56 | {
57 | "key": "country",
58 | "value": "Germany",
59 | "type": "text"
60 | }
61 | ]
62 | },
63 | "url": {
64 | "raw": "localhost/authors",
65 | "host": [
66 | "localhost"
67 | ],
68 | "path": [
69 | "authors"
70 | ]
71 | }
72 | },
73 | "response": []
74 | },
75 | {
76 | "name": "Get Author",
77 | "request": {
78 | "method": "GET",
79 | "header": [],
80 | "url": {
81 | "raw": "localhost/authors/1",
82 | "host": [
83 | "localhost"
84 | ],
85 | "path": [
86 | "authors",
87 | "1"
88 | ]
89 | }
90 | },
91 | "response": []
92 | },
93 | {
94 | "name": "Update Author",
95 | "request": {
96 | "method": "PUT",
97 | "header": [
98 | {
99 | "key": "Content-Type",
100 | "name": "Content-Type",
101 | "value": "application/x-www-form-urlencoded",
102 | "type": "text"
103 | }
104 | ],
105 | "body": {
106 | "mode": "urlencoded",
107 | "urlencoded": [
108 | {
109 | "key": "name",
110 | "value": "Test 4",
111 | "type": "text"
112 | },
113 | {
114 | "key": "gender",
115 | "value": "male",
116 | "type": "text"
117 | },
118 | {
119 | "key": "country",
120 | "value": "Germany",
121 | "type": "text"
122 | }
123 | ]
124 | },
125 | "url": {
126 | "raw": "localhost/authors/20",
127 | "host": [
128 | "localhost"
129 | ],
130 | "path": [
131 | "authors",
132 | "20"
133 | ]
134 | }
135 | },
136 | "response": []
137 | },
138 | {
139 | "name": "Delete Author",
140 | "request": {
141 | "method": "DELETE",
142 | "header": [
143 | {
144 | "key": "Content-Type",
145 | "name": "Content-Type",
146 | "value": "application/x-www-form-urlencoded",
147 | "type": "text",
148 | "disabled": true
149 | }
150 | ],
151 | "body": {
152 | "mode": "urlencoded",
153 | "urlencoded": []
154 | },
155 | "url": {
156 | "raw": "localhost/authors/21",
157 | "host": [
158 | "localhost"
159 | ],
160 | "path": [
161 | "authors",
162 | "21"
163 | ]
164 | }
165 | },
166 | "response": []
167 | },
168 | {
169 | "name": "Books",
170 | "request": {
171 | "method": "GET",
172 | "header": [],
173 | "url": {
174 | "raw": "localhost/books",
175 | "host": [
176 | "localhost"
177 | ],
178 | "path": [
179 | "books"
180 | ]
181 | }
182 | },
183 | "response": []
184 | },
185 | {
186 | "name": "Create Book",
187 | "request": {
188 | "method": "POST",
189 | "header": [],
190 | "body": {
191 | "mode": "formdata",
192 | "formdata": [
193 | {
194 | "key": "title",
195 | "value": "The new book",
196 | "type": "text"
197 | },
198 | {
199 | "key": "description",
200 | "value": "The new book",
201 | "type": "text"
202 | },
203 | {
204 | "key": "price",
205 | "value": "2",
206 | "type": "text"
207 | },
208 | {
209 | "key": "author_id",
210 | "value": "1",
211 | "type": "text"
212 | }
213 | ]
214 | },
215 | "url": {
216 | "raw": "localhost/books",
217 | "host": [
218 | "localhost"
219 | ],
220 | "path": [
221 | "books"
222 | ]
223 | }
224 | },
225 | "response": []
226 | },
227 | {
228 | "name": "Get Book",
229 | "protocolProfileBehavior": {
230 | "disableBodyPruning": true
231 | },
232 | "request": {
233 | "method": "GET",
234 | "header": [],
235 | "body": {
236 | "mode": "formdata",
237 | "formdata": [
238 | {
239 | "key": "name",
240 | "value": "Ahmed",
241 | "type": "text"
242 | },
243 | {
244 | "key": "gender",
245 | "value": "male",
246 | "type": "text"
247 | },
248 | {
249 | "key": "country",
250 | "value": "Germany",
251 | "type": "text"
252 | }
253 | ]
254 | },
255 | "url": {
256 | "raw": "localhost/books/151",
257 | "host": [
258 | "localhost"
259 | ],
260 | "path": [
261 | "books",
262 | "151"
263 | ]
264 | }
265 | },
266 | "response": []
267 | },
268 | {
269 | "name": "Update Book",
270 | "request": {
271 | "method": "PUT",
272 | "header": [
273 | {
274 | "key": "Content-Type",
275 | "name": "Content-Type",
276 | "type": "text",
277 | "value": "application/x-www-form-urlencoded"
278 | }
279 | ],
280 | "body": {
281 | "mode": "urlencoded",
282 | "urlencoded": [
283 | {
284 | "key": "title",
285 | "value": "The new book",
286 | "type": "text"
287 | },
288 | {
289 | "key": "description",
290 | "value": "The new book",
291 | "type": "text"
292 | },
293 | {
294 | "key": "price",
295 | "value": "50",
296 | "type": "text"
297 | },
298 | {
299 | "key": "author_id",
300 | "value": "1",
301 | "type": "text"
302 | }
303 | ]
304 | },
305 | "url": {
306 | "raw": "localhost/books/151",
307 | "host": [
308 | "localhost"
309 | ],
310 | "path": [
311 | "books",
312 | "151"
313 | ]
314 | }
315 | },
316 | "response": []
317 | },
318 | {
319 | "name": "Delete Book",
320 | "request": {
321 | "method": "DELETE",
322 | "header": [],
323 | "body": {
324 | "mode": "formdata",
325 | "formdata": [
326 | {
327 | "key": "name",
328 | "value": "AhmedAlaa",
329 | "type": "text"
330 | },
331 | {
332 | "key": "gender",
333 | "value": "male",
334 | "type": "text"
335 | },
336 | {
337 | "key": "country",
338 | "value": "Germany",
339 | "type": "text"
340 | }
341 | ]
342 | },
343 | "url": {
344 | "raw": "localhost/books/151",
345 | "host": [
346 | "localhost"
347 | ],
348 | "path": [
349 | "books",
350 | "151"
351 | ]
352 | }
353 | },
354 | "response": []
355 | },
356 | {
357 | "name": "Issue Token",
358 | "request": {
359 | "method": "POST",
360 | "header": [
361 | {
362 | "key": "Content-Type",
363 | "name": "Content-Type",
364 | "value": "application/x-www-form-urlencoded",
365 | "type": "text"
366 | }
367 | ],
368 | "body": {
369 | "mode": "urlencoded",
370 | "urlencoded": [
371 | {
372 | "key": "grant_type",
373 | "value": "password",
374 | "type": "text"
375 | },
376 | {
377 | "key": "client_id",
378 | "value": "2",
379 | "type": "text"
380 | },
381 | {
382 | "key": "client_secret",
383 | "value": "client_secret",
384 | "type": "text"
385 | },
386 | {
387 | "key": "scope",
388 | "value": "*",
389 | "type": "text"
390 | },
391 | {
392 | "key": "username",
393 | "value": "test@test.com",
394 | "type": "text"
395 | },
396 | {
397 | "key": "password",
398 | "value": "Germany123",
399 | "type": "text"
400 | }
401 | ]
402 | },
403 | "url": {
404 | "raw": "localhost/oauth/token",
405 | "host": [
406 | "localhost"
407 | ],
408 | "path": [
409 | "oauth",
410 | "token"
411 | ]
412 | }
413 | },
414 | "response": []
415 | },
416 | {
417 | "name": "Create User",
418 | "request": {
419 | "method": "POST",
420 | "header": [
421 | {
422 | "key": "",
423 | "value": "",
424 | "type": "text"
425 | }
426 | ],
427 | "body": {
428 | "mode": "formdata",
429 | "formdata": [
430 | {
431 | "key": "name",
432 | "value": "test",
433 | "type": "text"
434 | },
435 | {
436 | "key": "email",
437 | "value": "test@test.com",
438 | "type": "text"
439 | },
440 | {
441 | "key": "password",
442 | "value": "Germany123",
443 | "type": "text"
444 | },
445 | {
446 | "key": "password_confirmation",
447 | "value": "Germany123",
448 | "type": "text"
449 | }
450 | ]
451 | },
452 | "url": {
453 | "raw": "localhost/users",
454 | "host": [
455 | "localhost"
456 | ],
457 | "path": [
458 | "users"
459 | ]
460 | }
461 | },
462 | "response": []
463 | },
464 | {
465 | "name": "Users",
466 | "request": {
467 | "auth": {
468 | "type": "bearer",
469 | "bearer": [
470 | {
471 | "key": "token",
472 | "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImRhMjU0NTA3MDU3MWU4OTc0OGJiNjQ4Mzk0YWFmMmRlZTcwNjhhYWM1MTEwMmRiNTg4ZDM0YzUyZDRkNTgyOGM5ZGFhMTJkNTc1YmFlZjBlIn0.eyJhdWQiOiIyIiwianRpIjoiZGEyNTQ1MDcwNTcxZTg5NzQ4YmI2NDgzOTRhYWYyZGVlNzA2OGFhYzUxMTAyZGI1ODhkMzRjNTJkNGQ1ODI4YzlkYWExMmQ1NzViYWVmMGUiLCJpYXQiOjE1ODI5MTIxNDcsIm5iZiI6MTU4MjkxMjE0NywiZXhwIjoxNjE0NTM0NTQ3LCJzdWIiOiIxIiwic2NvcGVzIjpbIioiXX0.bbhcN2OLO3y_pVIfhtdiHxNI1myDGiObWC_5NND4duJkkGmUzZCTjHEI4pMKXIMJMnADBGulTxB9HybqaxN0rNiqE99X1_tJcwHt3OZwx32_gg9Sg9nGJv4mgRylkESJIW7j1aLwPZxQaPP_5P_0ZIYVaQKRMNRkrsaKcLXJPjK90Uqrj2uvoHorfG1-rKyJVo7iu-6fjojbR1-05yf0LASLphcQSoP2GUYSFNhTtweLosflaPo_rE8OUsvgBF7yE9PojVqf4xbt8ffzNUYZF5sr-jao72ZHkHxPdhNHTZknWvuSdCnDuJhTnRausNbK_PDawam3TGnEihZId3VhnnHL65MV6K4x_hmqYDoKr8u-X7yrSnFUh8xQejoCuDITsG5tN8BFUW1n_1_GJp5AkJxFsThMNfjUEV76BLdngFusxTPo5Ke0oW0nRhnhcR4cbpvNmIZ-Eb8WDx8L3Aj5XIH-dexV_brcPMCWgen7HCQ-7HS_XBDbdtehtkMMdxYvJu7Y4bpmm-JW7LxPQkMWy3eruw_S3ZGQAD3zvY2JmxaqARxFL2qqpBGsByEZo_SQPUZwwJGAey2WISR0eQiOOvQ2wJIax6Z8r8DBeV4DXPUJfOsYD22pByEtliopKMwdeKwRAxgD5EppaYdCpSrRv6P0PfpOiHqKRcrzdkd30L4",
473 | "type": "string"
474 | }
475 | ]
476 | },
477 | "method": "GET",
478 | "header": [],
479 | "url": {
480 | "raw": "localhost/users",
481 | "host": [
482 | "localhost"
483 | ],
484 | "path": [
485 | "users"
486 | ]
487 | }
488 | },
489 | "response": []
490 | },
491 | {
492 | "name": "Get User Request",
493 | "protocolProfileBehavior": {
494 | "disableBodyPruning": true
495 | },
496 | "request": {
497 | "auth": {
498 | "type": "bearer",
499 | "bearer": [
500 | {
501 | "key": "token",
502 | "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjgxNmM4NGJkOWMwNTYwMjhkMDM1YjM4ZjgxMmIxNjI3NDk2YWRiYjg5YzJhNTZiZDU0ODU0OGU1YjRkMTRjM2I5ZWM3ODY4OGRjZmE0ZjhhIn0.eyJhdWQiOiIyIiwianRpIjoiODE2Yzg0YmQ5YzA1NjAyOGQwMzViMzhmODEyYjE2Mjc0OTZhZGJiODljMmE1NmJkNTQ4NTQ4ZTViNGQxNGMzYjllYzc4Njg4ZGNmYTRmOGEiLCJpYXQiOjE1ODI5MTIxNjksIm5iZiI6MTU4MjkxMjE2OSwiZXhwIjoxNjE0NTM0NTY5LCJzdWIiOiIxIiwic2NvcGVzIjpbIioiXX0.XQocaFqIsKKRErLTXTA98lNZ0tcAc2WhYt639EWxgwhrVqG4JtaVfDU5FVQBU2wp3KpLFLOvram2z1H7yA7VODcb6K7vNMonamrdfr8WaVmuDWPzcMBcLxuqjp3JgHVoV2ifwFdPziUKI-7N-y6TiVkU512IAOakSpMK98I1jvpBt7_NCmi5zfDsCusXSPJ_bhNaQ55lW9T5DTaAGdWIgnYPgRQt9f-VBPV9QgsPs-d83UGGXAD50q-4l2w5OKzA5egDs5o64rxudplVyteJZD1xrhM43qr-Li9ewp-IdVpKb66tVuER6PVZamxrWpRTKxQg-vrQnmR1vvQfNy4zY3A0dfVgahggwPD4gaBhS_VAa3-PhVO5XyY9asVnuickrdgFS-iYUkBsYW1Bts3qnrt3E0GwyUWzXbTS6np_RGKZ5yEQLjTiYFEbIz5bAvPpqx5Xa35xhEesonrVBcAW0Gx_qGprxpxuJdPSZpEKWap33rWNaf6W2N0Pa3oBUJTbDyiEWfVJnxheQf715mrzzJKNKhWORtSRlQdNA_eFIFBz9XDyKdegkjrMytr1CQIptosBXb_5pL-GEu4Sb6N6S5ygSNclETyI5jqzt_PIkZFq0KKrpz5lZzKgJLe9DQi1v0f2qVZfxE6V_EC17emrsmPLQgw_nu-NJqVD1GciOXg",
503 | "type": "string"
504 | }
505 | ]
506 | },
507 | "method": "GET",
508 | "header": [
509 | {
510 | "warning": "This is a duplicate header and will be overridden by the Authorization header generated by Postman.",
511 | "key": "Authorization",
512 | "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImRhMjU0NTA3MDU3MWU4OTc0OGJiNjQ4Mzk0YWFmMmRlZTcwNjhhYWM1MTEwMmRiNTg4ZDM0YzUyZDRkNTgyOGM5ZGFhMTJkNTc1YmFlZjBlIn0.eyJhdWQiOiIyIiwianRpIjoiZGEyNTQ1MDcwNTcxZTg5NzQ4YmI2NDgzOTRhYWYyZGVlNzA2OGFhYzUxMTAyZGI1ODhkMzRjNTJkNGQ1ODI4YzlkYWExMmQ1NzViYWVmMGUiLCJpYXQiOjE1ODI5MTIxNDcsIm5iZiI6MTU4MjkxMjE0NywiZXhwIjoxNjE0NTM0NTQ3LCJzdWIiOiIxIiwic2NvcGVzIjpbIioiXX0.bbhcN2OLO3y_pVIfhtdiHxNI1myDGiObWC_5NND4duJkkGmUzZCTjHEI4pMKXIMJMnADBGulTxB9HybqaxN0rNiqE99X1_tJcwHt3OZwx32_gg9Sg9nGJv4mgRylkESJIW7j1aLwPZxQaPP_5P_0ZIYVaQKRMNRkrsaKcLXJPjK90Uqrj2uvoHorfG1-rKyJVo7iu-6fjojbR1-05yf0LASLphcQSoP2GUYSFNhTtweLosflaPo_rE8OUsvgBF7yE9PojVqf4xbt8ffzNUYZF5sr-jao72ZHkHxPdhNHTZknWvuSdCnDuJhTnRausNbK_PDawam3TGnEihZId3VhnnHL65MV6K4x_hmqYDoKr8u-X7yrSnFUh8xQejoCuDITsG5tN8BFUW1n_1_GJp5AkJxFsThMNfjUEV76BLdngFusxTPo5Ke0oW0nRhnhcR4cbpvNmIZ-Eb8WDx8L3Aj5XIH-dexV_brcPMCWgen7HCQ-7HS_XBDbdtehtkMMdxYvJu7Y4bpmm-JW7LxPQkMWy3eruw_S3ZGQAD3zvY2JmxaqARxFL2qqpBGsByEZo_SQPUZwwJGAey2WISR0eQiOOvQ2wJIax6Z8r8DBeV4DXPUJfOsYD22pByEtliopKMwdeKwRAxgD5EppaYdCpSrRv6P0PfpOiHqKRcrzdkd30L4",
513 | "type": "text",
514 | "disabled": true
515 | }
516 | ],
517 | "body": {
518 | "mode": "formdata",
519 | "formdata": []
520 | },
521 | "url": {
522 | "raw": "localhost/users/me",
523 | "host": [
524 | "localhost"
525 | ],
526 | "path": [
527 | "users",
528 | "me"
529 | ]
530 | }
531 | },
532 | "response": []
533 | }
534 | ],
535 | "protocolProfileBehavior": {}
536 | }
--------------------------------------------------------------------------------