├── .env.example
├── .env.travis
├── .gitignore
├── .travis.yml
├── app
├── Console
│ ├── Commands
│ │ └── .gitkeep
│ └── Kernel.php
├── Events
│ ├── Event.php
│ ├── ExampleEvent.php
│ └── UserEvents
│ │ └── UserCreatedEvent.php
├── Exceptions
│ └── Handler.php
├── Http
│ ├── Controllers
│ │ ├── AccessTokenController.php
│ │ ├── Controller.php
│ │ ├── ResponseTrait.php
│ │ └── UserController.php
│ └── Middleware
│ │ ├── Authenticate.php
│ │ ├── ExampleMiddleware.php
│ │ └── ThrottleRequests.php
├── Jobs
│ ├── ExampleJob.php
│ └── Job.php
├── Listeners
│ ├── ExampleListener.php
│ └── UserEventsListener.php
├── Mails
│ └── WelcomeEmail.php
├── Models
│ └── User.php
├── Policies
│ └── UserPolicy.php
├── Providers
│ ├── AppServiceProvider.php
│ ├── AuthServiceProvider.php
│ ├── EventServiceProvider.php
│ └── RepositoriesServiceProvider.php
├── Repositories
│ ├── AbstractEloquentRepository.php
│ ├── Contracts
│ │ ├── BaseRepository.php
│ │ └── UserRepository.php
│ └── EloquentUserRepository.php
└── Transformers
│ └── UserTransformer.php
├── artisan
├── bootstrap
└── app.php
├── composer.json
├── composer.lock
├── config
├── auth.php
├── cors.php
├── database.php
└── mail.php
├── database
├── factories
│ ├── ModelFactory.php
│ └── UserFactory.php
├── migrations
│ ├── .gitkeep
│ └── 2017_03_01_155453_create_users_table.php
└── seeds
│ ├── DatabaseSeeder.php
│ └── UsersTableSeeder.php
├── develop
├── docker-compose.yml
├── docker
├── Dockerfile
├── default
├── php-fpm.conf
├── start-container
├── supervisord.conf
└── xdebug.ini
├── phpunit.xml
├── public
├── .htaccess
├── images
│ └── accessTokenCreation.png
└── index.php
├── readme.md
├── resources
└── views
│ ├── .gitkeep
│ └── emails
│ └── welcome.blade.php
├── routes
└── web.php
├── storage
├── app
│ └── .gitignore
├── framework
│ ├── cache
│ │ └── .gitignore
│ └── views
│ │ └── .gitignore
├── logs
│ └── .gitignore
├── oauth-private.key
└── oauth-public.key
└── tests
├── Endpoints
└── UsersTest.php
├── ExampleTest.php
├── Http
└── Middleware
│ └── ThrottleRequestsTest.php
├── Repositories
└── EloquentUserRepositoryTest.php
└── TestCase.php
/.env.example:
--------------------------------------------------------------------------------
1 | APP_ENV=local
2 | APP_DEBUG=true
3 | APP_KEY=Gfl7u2OcTQjPIPDMoi4ckoS4jTpGXqlE
4 | APP_TIMEZONE=UTC
5 |
6 | DB_CONNECTION=mysql
7 | DB_HOST=127.0.0.1
8 | DB_PORT=3306
9 | DB_DATABASE=restapi
10 | DB_USERNAME=homestead
11 | DB_PASSWORD=secret
12 |
13 | DB_TEST_DATABASE=restapi_test
14 | DB_TEST_USERNAME=homestead
15 | DB_TEST_PASSWORD=secret
16 |
17 | CACHE_DRIVER=file
18 | QUEUE_DRIVER=sync
19 | REDIS_HOST=restapi-redis
20 |
21 | MAIL_DRIVER=smtp
22 | MAIL_HOST=mailtrap.io
23 | MAIL_PORT=2525
24 | MAIL_FROM_ADDRESS=info@yourfrom.com
25 | MAIL_FROM_NAME=YourFrom
26 | MAIL_USERNAME=yourusername
27 | MAIL_PASSWORD=yourpassword
28 | MAIL_ENCRYPTION=tls
29 |
--------------------------------------------------------------------------------
/.env.travis:
--------------------------------------------------------------------------------
1 | APP_ENV=testing
2 | APP_KEY=Gfl7u2OcTQjPIPDMoi4ckoS4jTpGXqlE
3 |
4 | DB_CONNECTION=testing
5 | DB_TEST_USERNAME=root
6 | DB_TEST_PASSWORD=
7 |
8 | CACHE_DRIVER=array
9 | SESSION_DRIVER=array
10 | QUEUE_DRIVER=sync
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | Homestead.json
2 | Homestead.yaml
3 | .env
4 | /.vagrant/
5 | /vendor/
6 | /.idea/
7 | /storage/*.key
8 | composer.lock
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | services:
4 | - mysql
5 | php:
6 | - 7.2
7 | - 7.1
8 | - 7.0
9 | - hhvm
10 |
11 | before_script:
12 | - cp .env.travis .env
13 | - mysql -e 'CREATE DATABASE restapi_test;'
14 | - composer self-update
15 | - composer install --no-interaction
16 |
17 | script:
18 | - vendor/bin/phpunit
--------------------------------------------------------------------------------
/app/Console/Commands/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hasib32/rest-api-with-lumen/18a8dc72f54c69ffa38c45352d5d303d313e0352/app/Console/Commands/.gitkeep
--------------------------------------------------------------------------------
/app/Console/Kernel.php:
--------------------------------------------------------------------------------
1 | user = $user;
23 | }
24 | }
--------------------------------------------------------------------------------
/app/Exceptions/Handler.php:
--------------------------------------------------------------------------------
1 | json((['status' => 403, 'message' => 'Insufficient privileges to perform this action']), 403);
52 | }
53 |
54 | if ($e instanceof MethodNotAllowedHttpException) {
55 | return response()->json((['status' => 405, 'message' => 'Method Not Allowed']), 405);
56 | }
57 |
58 | if ($e instanceof NotFoundHttpException) {
59 | return response()->json((['status' => 404, 'message' => 'The requested resource was not found']), 404);
60 | }
61 |
62 | return parent::render($request, $e);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/Http/Controllers/AccessTokenController.php:
--------------------------------------------------------------------------------
1 | userRepository = $userRepository;
26 |
27 | parent::__construct();
28 | }
29 |
30 | /**
31 | * Since, with Laravel|Lumen passport doesn't restrict
32 | * a client requesting any scope. we have to restrict it.
33 | * http://stackoverflow.com/questions/39436509/laravel-passport-scopes
34 | *
35 | * @param Request $request
36 | * @return \Illuminate\Http\Response
37 | */
38 | public function createAccessToken(Request $request)
39 | {
40 | $inputs = $request->all();
41 |
42 | //Set default scope with full access
43 | if (!isset($inputs['scope']) || empty($inputs['scope'])) {
44 | $inputs['scope'] = "*";
45 | }
46 |
47 | $tokenRequest = $request->create('/oauth/token', 'post', $inputs);
48 |
49 | // forward the request to the oauth token request endpoint
50 | return app()->dispatch($tokenRequest);
51 | }
52 | }
--------------------------------------------------------------------------------
/app/Http/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | setFractal($fractal);
22 | }
23 |
24 | /**
25 | * Validate HTTP request against the rules
26 | *
27 | * @param Request $request
28 | * @param array $rules
29 | * @return bool|array
30 | */
31 | protected function validateRequest(Request $request, array $rules)
32 | {
33 | // Perform Validation
34 | $validator = \Validator::make($request->all(), $rules);
35 |
36 | if ($validator->fails()) {
37 | $errorMessages = $validator->errors()->messages();
38 |
39 | // crete error message by using key and value
40 | foreach ($errorMessages as $key => $value) {
41 | $errorMessages[$key] = $value[0];
42 | }
43 |
44 | return $errorMessages;
45 | }
46 |
47 | return true;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/Http/Controllers/ResponseTrait.php:
--------------------------------------------------------------------------------
1 | fractal = $fractal;
38 | }
39 |
40 | /**
41 | * Getter for statusCode
42 | *
43 | * @return mixed
44 | */
45 | public function getStatusCode()
46 | {
47 | return $this->statusCode;
48 | }
49 |
50 | /**
51 | * Setter for statusCode
52 | *
53 | * @param int $statusCode Value to set
54 | *
55 | * @return self
56 | */
57 | public function setStatusCode($statusCode)
58 | {
59 | $this->statusCode = $statusCode;
60 |
61 | return $this;
62 | }
63 |
64 | /**
65 | * Send custom data response
66 | *
67 | * @param $status
68 | * @param $message
69 | * @return \Illuminate\Http\JsonResponse
70 | */
71 | public function sendCustomResponse($status, $message)
72 | {
73 | return response()->json(['status' => $status, 'message' => $message], $status);
74 | }
75 |
76 | /**
77 | * Send this response when api user provide fields that doesn't exist in our application
78 | *
79 | * @param $errors
80 | * @return mixed
81 | */
82 | public function sendUnknownFieldResponse($errors)
83 | {
84 | return response()->json((['status' => 400, 'unknown_fields' => $errors]), 400);
85 | }
86 |
87 | /**
88 | * Send this response when api user provide filter that doesn't exist in our application
89 | *
90 | * @param $errors
91 | * @return mixed
92 | */
93 | public function sendInvalidFilterResponse($errors)
94 | {
95 | return response()->json((['status' => 400, 'invalid_filters' => $errors]), 400);
96 | }
97 |
98 | /**
99 | * Send this response when api user provide incorrect data type for the field
100 | *
101 | * @param $errors
102 | * @return mixed
103 | */
104 | public function sendInvalidFieldResponse($errors)
105 | {
106 | return response()->json((['status' => 400, 'invalid_fields' => $errors]), 400);
107 | }
108 |
109 | /**
110 | * Send this response when a api user try access a resource that they don't belong
111 | *
112 | * @return string
113 | */
114 | public function sendForbiddenResponse()
115 | {
116 | return response()->json(['status' => 403, 'message' => 'Forbidden'], 403);
117 | }
118 |
119 | /**
120 | * Send 404 not found response
121 | *
122 | * @param string $message
123 | * @return string
124 | */
125 | public function sendNotFoundResponse($message = '')
126 | {
127 | if ($message === '') {
128 | $message = 'The requested resource was not found';
129 | }
130 |
131 | return response()->json(['status' => 404, 'message' => $message], 404);
132 | }
133 |
134 | /**
135 | * Send empty data response
136 | *
137 | * @return string
138 | */
139 | public function sendEmptyDataResponse()
140 | {
141 | return response()->json(['data' => new \StdClass()]);
142 | }
143 |
144 | /**
145 | * Return collection response from the application
146 | *
147 | * @param array|LengthAwarePaginator|\Illuminate\Database\Eloquent\Collection $collection
148 | * @param \Closure|TransformerAbstract $callback
149 | * @return \Illuminate\Http\JsonResponse
150 | */
151 | protected function respondWithCollection($collection, $callback)
152 | {
153 | $resource = new Collection($collection, $callback);
154 |
155 | //set empty data pagination
156 | if (empty($collection)) {
157 | $collection = new \Illuminate\Pagination\LengthAwarePaginator([], 0, 10);
158 | $resource = new Collection($collection, $callback);
159 | }
160 | $resource->setPaginator(new IlluminatePaginatorAdapter($collection));
161 |
162 | $rootScope = $this->fractal->createData($resource);
163 |
164 | return $this->respondWithArray($rootScope->toArray());
165 | }
166 |
167 | /**
168 | * Return single item response from the application
169 | *
170 | * @param Model $item
171 | * @param \Closure|TransformerAbstract $callback
172 | * @return \Illuminate\Http\JsonResponse
173 | */
174 | protected function respondWithItem($item, $callback)
175 | {
176 | $resource = new Item($item, $callback);
177 | $rootScope = $this->fractal->createData($resource);
178 |
179 | return $this->respondWithArray($rootScope->toArray());
180 | }
181 |
182 | /**
183 | * Return a json response from the application
184 | *
185 | * @param array $array
186 | * @param array $headers
187 | * @return \Illuminate\Http\JsonResponse
188 | */
189 | protected function respondWithArray(array $array, array $headers = [])
190 | {
191 | return response()->json($array, $this->statusCode, $headers);
192 | }
193 | }
--------------------------------------------------------------------------------
/app/Http/Controllers/UserController.php:
--------------------------------------------------------------------------------
1 | userRepository = $userRepository;
35 | $this->userTransformer = $userTransformer;
36 |
37 | parent::__construct();
38 | }
39 |
40 | /**
41 | * Display a listing of the resource.
42 | *
43 | * @param Request $request
44 | * @return \Illuminate\Http\JsonResponse
45 | */
46 | public function index(Request $request)
47 | {
48 | $users = $this->userRepository->findBy($request->all());
49 |
50 | return $this->respondWithCollection($users, $this->userTransformer);
51 | }
52 |
53 | /**
54 | * Display the specified resource.
55 | *
56 | * @param $id
57 | * @return \Illuminate\Http\JsonResponse|string
58 | */
59 | public function show($id)
60 | {
61 | $user = $this->userRepository->findOne($id);
62 |
63 | if (!$user instanceof User) {
64 | return $this->sendNotFoundResponse("The user with id {$id} doesn't exist");
65 | }
66 |
67 | // Authorization
68 | $this->authorize('show', $user);
69 |
70 | return $this->respondWithItem($user, $this->userTransformer);
71 | }
72 |
73 | /**
74 | * Store a newly created resource in storage.
75 | *
76 | * @param Request $request
77 | * @return \Illuminate\Http\JsonResponse|string
78 | */
79 | public function store(Request $request)
80 | {
81 | // Validation
82 | $validatorResponse = $this->validateRequest($request, $this->storeRequestValidationRules($request));
83 |
84 | // Send failed response if validation fails
85 | if ($validatorResponse !== true) {
86 | return $this->sendInvalidFieldResponse($validatorResponse);
87 | }
88 |
89 | $user = $this->userRepository->save($request->all());
90 |
91 | if (!$user instanceof User) {
92 | return $this->sendCustomResponse(500, 'Error occurred on creating User');
93 | }
94 |
95 | return $this->setStatusCode(201)->respondWithItem($user, $this->userTransformer);
96 | }
97 |
98 | /**
99 | * Update the specified resource in storage.
100 | *
101 | * @param Request $request
102 | * @param $id
103 | * @return \Illuminate\Http\JsonResponse
104 | */
105 | public function update(Request $request, $id)
106 | {
107 | // Validation
108 | $validatorResponse = $this->validateRequest($request, $this->updateRequestValidationRules($request));
109 |
110 | // Send failed response if validation fails
111 | if ($validatorResponse !== true) {
112 | return $this->sendInvalidFieldResponse($validatorResponse);
113 | }
114 |
115 | $user = $this->userRepository->findOne($id);
116 |
117 | if (!$user instanceof User) {
118 | return $this->sendNotFoundResponse("The user with id {$id} doesn't exist");
119 | }
120 |
121 | // Authorization
122 | $this->authorize('update', $user);
123 |
124 |
125 | $user = $this->userRepository->update($user, $request->all());
126 |
127 | return $this->respondWithItem($user, $this->userTransformer);
128 | }
129 |
130 | /**
131 | * Remove the specified resource from storage.
132 | *
133 | * @param $id
134 | * @return \Illuminate\Http\JsonResponse|string
135 | */
136 | public function destroy($id)
137 | {
138 | $user = $this->userRepository->findOne($id);
139 |
140 | if (!$user instanceof User) {
141 | return $this->sendNotFoundResponse("The user with id {$id} doesn't exist");
142 | }
143 |
144 | // Authorization
145 | $this->authorize('destroy', $user);
146 |
147 | $this->userRepository->delete($user);
148 |
149 | return response()->json(null, 204);
150 | }
151 |
152 | /**
153 | * Store Request Validation Rules
154 | *
155 | * @param Request $request
156 | * @return array
157 | */
158 | private function storeRequestValidationRules(Request $request)
159 | {
160 | $rules = [
161 | 'email' => 'email|required|unique:users',
162 | 'firstName' => 'required|max:100',
163 | 'middleName' => 'max:50',
164 | 'lastName' => 'required|max:100',
165 | 'username' => 'max:50',
166 | 'address' => 'max:255',
167 | 'zipCode' => 'max:10',
168 | 'phone' => 'max:20',
169 | 'mobile' => 'max:20',
170 | 'city' => 'max:100',
171 | 'state' => 'max:100',
172 | 'country' => 'max:100',
173 | 'password' => 'min:5'
174 | ];
175 |
176 | $requestUser = $request->user();
177 |
178 | // Only admin user can set admin role.
179 | if ($requestUser instanceof User && $requestUser->role === User::ADMIN_ROLE) {
180 | $rules['role'] = 'in:BASIC_USER,ADMIN_USER';
181 | } else {
182 | $rules['role'] = 'in:BASIC_USER';
183 | }
184 |
185 | return $rules;
186 | }
187 |
188 | /**
189 | * Update Request validation Rules
190 | *
191 | * @param Request $request
192 | * @return array
193 | */
194 | private function updateRequestValidationRules(Request $request)
195 | {
196 | $userId = $request->segment(2);
197 | $rules = [
198 | 'email' => 'email|unique:users,email,'. $userId,
199 | 'firstName' => 'max:100',
200 | 'middleName' => 'max:50',
201 | 'lastName' => 'max:100',
202 | 'username' => 'max:50',
203 | 'address' => 'max:255',
204 | 'zipCode' => 'max:10',
205 | 'phone' => 'max:20',
206 | 'mobile' => 'max:20',
207 | 'city' => 'max:100',
208 | 'state' => 'max:100',
209 | 'country' => 'max:100',
210 | 'password' => 'min:5'
211 | ];
212 |
213 | $requestUser = $request->user();
214 |
215 | // Only admin user can update admin role.
216 | if ($requestUser instanceof User && $requestUser->role === User::ADMIN_ROLE) {
217 | $rules['role'] = 'in:BASIC_USER,ADMIN_USER';
218 | } else {
219 | $rules['role'] = 'in:BASIC_USER';
220 | }
221 |
222 | return $rules;
223 | }
224 | }
--------------------------------------------------------------------------------
/app/Http/Middleware/Authenticate.php:
--------------------------------------------------------------------------------
1 | auth = $auth;
28 | }
29 |
30 | /**
31 | * Handle an incoming request.
32 | *
33 | * @param \Illuminate\Http\Request $request
34 | * @param \Closure $next
35 | * @param string|null $guard
36 | * @return mixed
37 | */
38 | public function handle($request, Closure $next, $guard = null)
39 | {
40 | // First, check if the access_token created by the password grant is valid
41 | if ($this->auth->guard($guard)->guest()) {
42 |
43 | // Then check, access_token created by the client_credentials grant is valid.
44 | // We need this checking because we could use either password grant or client_credentials grant.
45 | try {
46 | app(CheckClientCredentials::class)->handle($request, function(){});
47 | } catch (AuthenticationException $e) {
48 | return response()->json((['status' => 401, 'message' => 'Unauthorized']), 401);
49 | }
50 | }
51 |
52 | return $next($request);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/Http/Middleware/ExampleMiddleware.php:
--------------------------------------------------------------------------------
1 | limiter = $limiter;
29 | }
30 |
31 | /**
32 | * Handle an incoming request.
33 | *
34 | * @param \Illuminate\Http\Request $request
35 | * @param \Closure $next
36 | * @param int $maxAttempts
37 | * @param int $decayMinutes
38 | *
39 | * @return mixed
40 | */
41 | public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
42 | {
43 | $key = $this->resolveRequestSignature($request);
44 |
45 | if ($this->limiter->tooManyAttempts($key, $maxAttempts, $decayMinutes)) {
46 | return $this->buildResponse($key, $maxAttempts);
47 | }
48 |
49 | $this->limiter->hit($key, $decayMinutes);
50 |
51 | $response = $next($request);
52 |
53 | return $this->addHeaders(
54 | $response,
55 | $maxAttempts,
56 | $this->calculateRemainingAttempts($key, $maxAttempts)
57 | );
58 | }
59 |
60 | /**
61 | * Resolve request signature.
62 | *
63 | * @param \Illuminate\Http\Request $request
64 | *
65 | * @return string
66 | */
67 | protected function resolveRequestSignature($request)
68 | {
69 | return sha1(
70 | $request->method() .
71 | '|' . $request->getHost() .
72 | '|' . $request->ip()
73 | );
74 | }
75 |
76 | /**
77 | * Create a 'too many attempts' response.
78 | *
79 | * @param string $key
80 | * @param int $maxAttempts
81 | *
82 | * @return \Illuminate\Http\Response
83 | */
84 | protected function buildResponse($key, $maxAttempts)
85 | {
86 | $response = new JsonResponse(['status' => 429, 'message' => 'Too Many Attempts.'], 429);
87 |
88 | return $this->addHeaders(
89 | $response,
90 | $maxAttempts,
91 | $this->calculateRemainingAttempts($key, $maxAttempts),
92 | $this->limiter->availableIn($key)
93 | );
94 | }
95 |
96 | /**
97 | * Add the limit header information to the given response.
98 | *
99 | * @param \Illuminate\Http\Response $response
100 | * @param int $maxAttempts
101 | * @param int $remainingAttempts
102 | * @param int|null $retryAfter
103 | *
104 | * @return \Illuminate\Http\Response
105 | */
106 | protected function addHeaders(\Symfony\Component\HttpFoundation\Response $response, $maxAttempts, $remainingAttempts, $retryAfter = null)
107 | {
108 | $headers = [
109 | 'X-RateLimit-Limit' => $maxAttempts,
110 | 'X-RateLimit-Remaining' => $remainingAttempts,
111 | ];
112 |
113 | if (!is_null($retryAfter)) {
114 | $headers['Retry-After'] = $retryAfter;
115 | }
116 |
117 | $response->headers->add($headers);
118 |
119 | return $response;
120 | }
121 |
122 | /**
123 | * Calculate the number of remaining attempts.
124 | *
125 | * @param string $key
126 | * @param int $maxAttempts
127 | *
128 | * @return int
129 | */
130 | protected function calculateRemainingAttempts($key, $maxAttempts)
131 | {
132 | return $this->limiter->retriesLeft($key, $maxAttempts);
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/app/Jobs/ExampleJob.php:
--------------------------------------------------------------------------------
1 | user;
20 |
21 | //send welcome email to the user
22 | Mail::to($user)->send(new WelcomeEmail($user));
23 | }
24 |
25 | /**
26 | * Register the listeners for the subscriber.
27 | *
28 | * @param Dispatcher $events
29 | */
30 | public function subscribe($events)
31 | {
32 | $events->listen(
33 | UserCreatedEvent::class,
34 | 'App\Listeners\UserEventsListener@onUserCreatedEvent'
35 | );
36 | }
37 | }
--------------------------------------------------------------------------------
/app/Mails/WelcomeEmail.php:
--------------------------------------------------------------------------------
1 | user = $user;
29 | }
30 |
31 | /**
32 | * Build the message.
33 | *
34 | * @return $this
35 | */
36 | public function build()
37 | {
38 | return $this->view('emails.welcome');
39 | }
40 | }
--------------------------------------------------------------------------------
/app/Models/User.php:
--------------------------------------------------------------------------------
1 | role) ? $this->role : self::BASIC_ROLE) == self::ADMIN_ROLE;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/Policies/UserPolicy.php:
--------------------------------------------------------------------------------
1 | isAdmin() && (!$currentUser->tokenCan('basic') || $currentUser->tokenCan('undefined'))) {
20 | return true;
21 | }
22 | }
23 |
24 | /**
25 | * Determine if a given user has permission to show
26 | *
27 | * @param User $currentUser
28 | * @param User $user
29 | * @return bool
30 | */
31 | public function show(User $currentUser, User $user)
32 | {
33 | return $currentUser->id === $user->id;
34 | }
35 |
36 | /**
37 | * Determine if a given user can update
38 | *
39 | * @param User $currentUser
40 | * @param User $user
41 | * @return bool
42 | */
43 | public function update(User $currentUser, User $user)
44 | {
45 | return $currentUser->id === $user->id;
46 | }
47 |
48 | /**
49 | * Determine if a given user can delete
50 | *
51 | * @param User $currentUser
52 | * @param User $user
53 | * @return bool
54 | */
55 | public function destroy(User $currentUser, User $user)
56 | {
57 | return $currentUser->id === $user->id;
58 | }
59 | }
--------------------------------------------------------------------------------
/app/Providers/AppServiceProvider.php:
--------------------------------------------------------------------------------
1 | app['auth']->viaRequest('api', function ($request) {
36 | if ($request->input('api_token')) {
37 | return User::where('api_token', $request->input('api_token'))->first();
38 | }
39 | });
40 |
41 | Passport::tokensCan([
42 | 'admin' => 'Admin user scope',
43 | 'basic' => 'Basic user scope',
44 | 'users' => 'Users scope',
45 | 'users:list' => 'Users scope',
46 | 'users:read' => 'Users scope for reading records',
47 | 'users:write' => 'Users scope for writing records',
48 | 'users:create' => 'Users scope for creating records',
49 | 'users:delete' => 'Users scope for deleting records',
50 | ]);
51 |
52 | //Register all policies here
53 | Gate::policy(User::class, UserPolicy::class);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/Providers/EventServiceProvider.php:
--------------------------------------------------------------------------------
1 | [
17 | 'App\Listeners\EventListener',
18 | ],
19 | ];
20 |
21 | /**
22 | * The subscriber classes to register.
23 | *
24 | * @var array
25 | */
26 | protected $subscribe = [
27 | UserEventsListener::class,
28 | ];
29 | }
30 |
--------------------------------------------------------------------------------
/app/Providers/RepositoriesServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->bind(UserRepository::class, function () {
27 | return new EloquentUserRepository(new User());
28 | });
29 | }
30 |
31 | /**
32 | * Get the services provided by the provider.
33 | *
34 | * @return array
35 | */
36 | public function provides()
37 | {
38 | return [
39 | UserRepository::class
40 | ];
41 | }
42 | }
--------------------------------------------------------------------------------
/app/Repositories/AbstractEloquentRepository.php:
--------------------------------------------------------------------------------
1 | model = $model;
39 | $this->loggedInUser = $this->getLoggedInUser();
40 | }
41 |
42 | /**
43 | * Get Model instance
44 | *
45 | * @return Model
46 | */
47 | public function getModel()
48 | {
49 | return $this->model;
50 | }
51 |
52 | /**
53 | * @inheritdoc
54 | */
55 | public function findOne($id)
56 | {
57 | return $this->findOneBy(['uid' => $id]);
58 | }
59 |
60 | /**
61 | * @inheritdoc
62 | */
63 | public function findOneBy(array $criteria)
64 | {
65 | return $this->model->where($criteria)->first();
66 | }
67 |
68 | /**
69 | * @inheritdoc
70 | */
71 | public function findBy(array $searchCriteria = [])
72 | {
73 | $limit = !empty($searchCriteria['per_page']) ? (int)$searchCriteria['per_page'] : 15; // it's needed for pagination
74 |
75 | $queryBuilder = $this->model->where(function ($query) use ($searchCriteria) {
76 |
77 | $this->applySearchCriteriaInQueryBuilder($query, $searchCriteria);
78 | }
79 | );
80 |
81 | return $queryBuilder->paginate($limit);
82 | }
83 |
84 |
85 | /**
86 | * Apply condition on query builder based on search criteria
87 | *
88 | * @param Object $queryBuilder
89 | * @param array $searchCriteria
90 | * @return mixed
91 | */
92 | protected function applySearchCriteriaInQueryBuilder($queryBuilder, array $searchCriteria = [])
93 | {
94 |
95 | foreach ($searchCriteria as $key => $value) {
96 |
97 | //skip pagination related query params
98 | if (in_array($key, ['page', 'per_page'])) {
99 | continue;
100 | }
101 |
102 | //we can pass multiple params for a filter with commas
103 | $allValues = explode(',', $value);
104 |
105 | if (count($allValues) > 1) {
106 | $queryBuilder->whereIn($key, $allValues);
107 | } else {
108 | $operator = '=';
109 | $queryBuilder->where($key, $operator, $value);
110 | }
111 | }
112 |
113 | return $queryBuilder;
114 | }
115 |
116 | /**
117 | * @inheritdoc
118 | */
119 | public function save(array $data)
120 | {
121 | // generate uid
122 | $data['uid'] = Uuid::uuid4();
123 |
124 | return $this->model->create($data);
125 | }
126 |
127 | /**
128 | * @inheritdoc
129 | */
130 | public function update(Model $model, array $data)
131 | {
132 | $fillAbleProperties = $this->model->getFillable();
133 |
134 | foreach ($data as $key => $value) {
135 |
136 | // update only fillAble properties
137 | if (in_array($key, $fillAbleProperties)) {
138 | $model->$key = $value;
139 | }
140 | }
141 |
142 | // update the model
143 | $model->save();
144 |
145 | // get updated model from database
146 | $model = $this->findOne($model->uid);
147 |
148 | return $model;
149 | }
150 |
151 | /**
152 | * @inheritdoc
153 | */
154 | public function findIn($key, array $values)
155 | {
156 | return $this->model->whereIn($key, $values)->get();
157 | }
158 |
159 | /**
160 | * @inheritdoc
161 | */
162 | public function delete(Model $model)
163 | {
164 | return $model->delete();
165 | }
166 |
167 | /**
168 | * get loggedIn user
169 | *
170 | * @return User
171 | */
172 | protected function getLoggedInUser()
173 | {
174 | $user = \Auth::user();
175 |
176 | if ($user instanceof User) {
177 | return $user;
178 | } else {
179 | return new User();
180 | }
181 | }
182 | }
--------------------------------------------------------------------------------
/app/Repositories/Contracts/BaseRepository.php:
--------------------------------------------------------------------------------
1 | loggedInUser->role !== User::ADMIN_ROLE) {
53 | $searchCriteria['id'] = $this->loggedInUser->id;
54 | }
55 |
56 | return parent::findBy($searchCriteria);
57 | }
58 |
59 | /**
60 | * @inheritdoc
61 | */
62 | public function findOne($id)
63 | {
64 | if ($id === 'me') {
65 | return $this->getLoggedInUser();
66 | }
67 |
68 | return parent::findOne($id);
69 | }
70 | }
--------------------------------------------------------------------------------
/app/Transformers/UserTransformer.php:
--------------------------------------------------------------------------------
1 | $user->uid,
14 | 'firstName' => $user->firstName,
15 | 'lastName' => $user->lastName,
16 | 'middleName' => $user->middleName,
17 | 'username' => $user->username,
18 | 'email' => $user->email,
19 | 'address' => $user->address,
20 | 'zipCode' => $user->zipCode,
21 | 'city' => $user->city,
22 | 'state' => $user->state,
23 | 'country' => $user->country,
24 | 'phone' => $user->phone,
25 | 'mobile' => $user->mobile,
26 | 'role' => $user->role,
27 | 'profileImage' => $user->profileImage,
28 | 'createdAt' => (string) $user->created_at,
29 | 'updatedAt' => (string) $user->updated_at
30 | ];
31 |
32 | return $formattedUser;
33 | }
34 | }
--------------------------------------------------------------------------------
/artisan:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | make(
32 | 'Illuminate\Contracts\Console\Kernel'
33 | );
34 |
35 | exit($kernel->handle(new ArgvInput, new ConsoleOutput));
36 |
--------------------------------------------------------------------------------
/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | load();
9 | } catch (Dotenv\Exception\InvalidPathException $e) {
10 | //
11 | }
12 |
13 | /*
14 | |--------------------------------------------------------------------------
15 | | Create The Application
16 | |--------------------------------------------------------------------------
17 | |
18 | | Here we will load the environment and create the application instance
19 | | that serves as the central piece of this framework. We'll use this
20 | | application as an "IoC" container and router for this framework.
21 | |
22 | */
23 |
24 | $app = new Laravel\Lumen\Application(
25 | realpath(__DIR__.'/../')
26 | );
27 |
28 | $app->withFacades();
29 |
30 | $app->withEloquent();
31 |
32 | /*
33 | |--------------------------------------------------------------------------
34 | | Register Container Bindings
35 | |--------------------------------------------------------------------------
36 | |
37 | | Now we will register a few bindings in the service container. We will
38 | | register the exception handler and the console kernel. You may add
39 | | your own bindings here if you like or you can make another file.
40 | |
41 | */
42 |
43 | $app->singleton(
44 | Illuminate\Contracts\Debug\ExceptionHandler::class,
45 | App\Exceptions\Handler::class
46 | );
47 |
48 | $app->singleton(
49 | Illuminate\Contracts\Console\Kernel::class,
50 | App\Console\Kernel::class
51 | );
52 |
53 | // load cors configurations
54 | $app->configure('cors');
55 |
56 | // load mail configurations
57 | $app->configure('mail');
58 |
59 | // load database configurations
60 | $app->configure('database');
61 |
62 | /*
63 | |--------------------------------------------------------------------------
64 | | Register Middleware
65 | |--------------------------------------------------------------------------
66 | |
67 | | Next, we will register the middleware with the application. These can
68 | | be global middleware that run before and after each request into a
69 | | route or middleware that'll be assigned to some specific routes.
70 | |
71 | */
72 |
73 | $app->middleware([
74 | \Barryvdh\Cors\HandleCors::class,
75 | ]);
76 |
77 | $app->routeMiddleware([
78 | 'auth' => App\Http\Middleware\Authenticate::class,
79 | 'throttle' => App\Http\Middleware\ThrottleRequests::class,
80 | 'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
81 | 'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class
82 | ]);
83 |
84 | /*
85 | |--------------------------------------------------------------------------
86 | | Register Service Providers
87 | |--------------------------------------------------------------------------
88 | |
89 | | Here we will register all of the application's service providers which
90 | | are used to bind services into the container. Service providers are
91 | | totally optional, so you are not required to uncomment this line.
92 | |
93 | */
94 |
95 | // $app->register(App\Providers\AppServiceProvider::class);
96 | $app->register(App\Providers\AuthServiceProvider::class);
97 | $app->register(App\Providers\EventServiceProvider::class);
98 | $app->register(App\Providers\RepositoriesServiceProvider::class);
99 | $app->register(Laravel\Passport\PassportServiceProvider::class);
100 | $app->register(Dusterio\LumenPassport\PassportServiceProvider::class);
101 | $app->register(Barryvdh\Cors\ServiceProvider::class);
102 | $app->register(\Illuminate\Mail\MailServiceProvider::class);
103 |
104 | LumenPassport::routes($app);
105 |
106 | /*
107 | |--------------------------------------------------------------------------
108 | | Load The Application Routes
109 | |--------------------------------------------------------------------------
110 | |
111 | | Next we will include the routes file so that they can all be added to
112 | | the application. This will provide all of the URLs the application
113 | | can respond to, as well as the controllers that may handle them.
114 | |
115 | */
116 |
117 | $app->router->group([
118 | 'namespace' => 'App\Http\Controllers'
119 | ], function ($router) {
120 | require __DIR__.'/../routes/web.php';
121 | });
122 |
123 | return $app;
124 |
--------------------------------------------------------------------------------
/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.0.0",
9 | "laravel/lumen-framework": "5.5.*",
10 | "vlucas/phpdotenv": "~2.4",
11 | "ramsey/uuid": "^3.7",
12 | "league/fractal": "^0.17.0",
13 | "dusterio/lumen-passport": "^0.2.4",
14 | "barryvdh/laravel-cors": "^0.11.0",
15 | "illuminate/mail": "5.5.*"
16 | },
17 | "require-dev": {
18 | "fzaninotto/faker": "~1.7",
19 | "phpunit/phpunit": "~6.5",
20 | "mockery/mockery": "~1.0"
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "App\\": "app/"
25 | }
26 | },
27 | "autoload-dev": {
28 | "classmap": [
29 | "tests/",
30 | "database/"
31 | ]
32 | },
33 | "scripts": {
34 | "post-root-package-install": [
35 | "php -r \"copy('.env.example', '.env');\""
36 | ]
37 | },
38 | "minimum-stability": "dev",
39 | "prefer-stable": true
40 | }
41 |
--------------------------------------------------------------------------------
/config/auth.php:
--------------------------------------------------------------------------------
1 | [
5 | 'guard' => 'api',
6 | 'passwords' => 'users',
7 | ],
8 |
9 | 'guards' => [
10 | 'api' => [
11 | 'driver' => 'passport',
12 | 'provider' => 'users',
13 | ],
14 | ],
15 |
16 | 'providers' => [
17 | 'users' => [
18 | 'driver' => 'eloquent',
19 | 'model' => \App\Models\User::class
20 | ]
21 | ]
22 | ];
--------------------------------------------------------------------------------
/config/cors.php:
--------------------------------------------------------------------------------
1 | true,
14 | 'allowedOrigins' => ['*'],
15 | 'allowedHeaders' => ['*'],
16 | 'allowedMethods' => ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
17 | 'exposedHeaders' => [],
18 | 'maxAge' => 0,
19 | 'hosts' => [],
20 | ];
21 |
--------------------------------------------------------------------------------
/config/database.php:
--------------------------------------------------------------------------------
1 | PDO::FETCH_CLASS,
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Default Database Connection Name
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may specify which of the database connections below you wish
24 | | to use as your default connection for all database work. Of course
25 | | you may use many connections at once using the Database library.
26 | |
27 | */
28 |
29 | 'default' => env('DB_CONNECTION', 'mysql'),
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Database Connections
34 | |--------------------------------------------------------------------------
35 | |
36 | | Here are each of the database connections setup for your application.
37 | | Of course, examples of configuring each database platform that is
38 | | supported by Laravel is shown below to make development simple.
39 | |
40 | |
41 | | All database work in Laravel is done through the PHP PDO facilities
42 | | so make sure you have the driver for your particular database of
43 | | choice installed on your machine before you begin development.
44 | |
45 | */
46 |
47 | 'connections' => [
48 |
49 | 'testing' => [
50 | 'driver' => 'mysql',
51 | 'host' => env('DB_TEST_HOST', 'localhost'),
52 | 'database' => env('DB_TEST_DATABASE', 'restapi_test'),
53 | 'username' => env('DB_TEST_USERNAME', ''),
54 | 'password' => env('DB_TEST_PASSWORD', ''),
55 | 'charset' => env('DB_CHARSET', 'utf8'),
56 | 'collation' => env('DB_COLLATION', 'utf8_unicode_ci'),
57 | 'prefix' => env('DB_PREFIX', ''),
58 | 'timezone' => env('DB_TIMEZONE', '+00:00'),
59 | 'strict' => env('DB_STRICT_MODE', false),
60 | ],
61 |
62 | 'sqlite' => [
63 | 'driver' => 'sqlite',
64 | 'database' => env('DB_DATABASE', base_path('database/database.sqlite')),
65 | 'prefix' => env('DB_PREFIX', ''),
66 | ],
67 |
68 | 'mysql' => [
69 | 'driver' => 'mysql',
70 | 'host' => env('DB_HOST', 'localhost'),
71 | 'port' => env('DB_PORT', 3306),
72 | 'database' => env('DB_DATABASE', 'forge'),
73 | 'username' => env('DB_USERNAME', 'forge'),
74 | 'password' => env('DB_PASSWORD', ''),
75 | 'charset' => env('DB_CHARSET', 'utf8'),
76 | 'collation' => env('DB_COLLATION', 'utf8_unicode_ci'),
77 | 'prefix' => env('DB_PREFIX', ''),
78 | 'timezone' => env('DB_TIMEZONE', '+00:00'),
79 | 'strict' => env('DB_STRICT_MODE', false),
80 | ],
81 |
82 | 'pgsql' => [
83 | 'driver' => 'pgsql',
84 | 'host' => env('DB_HOST', 'localhost'),
85 | 'port' => env('DB_PORT', 5432),
86 | 'database' => env('DB_DATABASE', 'forge'),
87 | 'username' => env('DB_USERNAME', 'forge'),
88 | 'password' => env('DB_PASSWORD', ''),
89 | 'charset' => env('DB_CHARSET', 'utf8'),
90 | 'prefix' => env('DB_PREFIX', ''),
91 | 'schema' => env('DB_SCHEMA', 'public'),
92 | ],
93 |
94 | 'sqlsrv' => [
95 | 'driver' => 'sqlsrv',
96 | 'host' => env('DB_HOST', 'localhost'),
97 | 'database' => env('DB_DATABASE', 'forge'),
98 | 'username' => env('DB_USERNAME', 'forge'),
99 | 'password' => env('DB_PASSWORD', ''),
100 | 'charset' => env('DB_CHARSET', 'utf8'),
101 | 'prefix' => env('DB_PREFIX', ''),
102 | ],
103 |
104 | ],
105 |
106 | /*
107 | |--------------------------------------------------------------------------
108 | | Migration Repository Table
109 | |--------------------------------------------------------------------------
110 | |
111 | | This table keeps track of all the migrations that have already run for
112 | | your application. Using this information, we can determine which of
113 | | the migrations on disk haven't actually been run in the database.
114 | |
115 | */
116 |
117 | 'migrations' => 'migrations',
118 |
119 | /*
120 | |--------------------------------------------------------------------------
121 | | Redis Databases
122 | |--------------------------------------------------------------------------
123 | |
124 | | Redis is an open source, fast, and advanced key-value store that also
125 | | provides a richer set of commands than a typical key-value systems
126 | | such as APC or Memcached. Laravel makes it easy to dig right in.
127 | |
128 | */
129 |
130 | 'redis' => [
131 |
132 | 'cluster' => env('REDIS_CLUSTER', false),
133 |
134 | 'default' => [
135 | 'host' => env('REDIS_HOST', '127.0.0.1'),
136 | 'port' => env('REDIS_PORT', 6379),
137 | 'database' => env('REDIS_DATABASE', 0),
138 | 'password' => env('REDIS_PASSWORD', null),
139 | ],
140 |
141 | ],
142 |
143 | ];
144 |
--------------------------------------------------------------------------------
/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', 'info@yoursite.com'),
60 | 'name' => env('MAIL_FROM_NAME', 'Your Name'),
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 | /*
90 | |--------------------------------------------------------------------------
91 | | SMTP Server Password
92 | |--------------------------------------------------------------------------
93 | |
94 | | Here you may set the password required by your SMTP server to send out
95 | | messages from your application. This will be given to the server on
96 | | connection so that the application will be able to send messages.
97 | |
98 | */
99 |
100 | 'password' => env('MAIL_PASSWORD'),
101 |
102 | /*
103 | |--------------------------------------------------------------------------
104 | | Sendmail System Path
105 | |--------------------------------------------------------------------------
106 | |
107 | | When using the "sendmail" driver to send e-mails, we will need to know
108 | | the path to where Sendmail lives on this server. A default path has
109 | | been provided here, which will work well on most of your systems.
110 | |
111 | */
112 |
113 | 'sendmail' => '/usr/sbin/sendmail -bs',
114 |
115 | ];
--------------------------------------------------------------------------------
/database/factories/ModelFactory.php:
--------------------------------------------------------------------------------
1 | define(App\Models\User::class, function (Faker\Generator $faker) {
4 | return [
5 | 'uid' => str_random(32),
6 | 'firstName' => $faker->firstName,
7 | 'lastName' => $faker->lastName,
8 | 'email' => $faker->email,
9 | 'middleName' => $faker->lastName,
10 | 'password' => \Illuminate\Support\Facades\Hash::make('test-password'),
11 | 'address' => $faker->address,
12 | 'zipCode' => $faker->postcode,
13 | 'username' => $faker->userName,
14 | 'city' => $faker->city,
15 | 'state' => $faker->state,
16 | 'country' => $faker->country,
17 | 'phone' => $faker->phoneNumber,
18 | 'mobile' => $faker->phoneNumber,
19 | 'role' => \App\Models\User::BASIC_ROLE,
20 | 'isActive' => rand(0,1),
21 | 'profileImage' => $faker->imageUrl('100')
22 | ];
23 | });
--------------------------------------------------------------------------------
/database/migrations/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hasib32/rest-api-with-lumen/18a8dc72f54c69ffa38c45352d5d303d313e0352/database/migrations/.gitkeep
--------------------------------------------------------------------------------
/database/migrations/2017_03_01_155453_create_users_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->string('uid', 36)->unique();
19 | $table->string('firstName', '100')->nullable();
20 | $table->string('lastName', '100')->nullable();
21 | $table->string('middleName', '50')->nullable();
22 | $table->string('username', '50')->nullable();
23 | $table->string('email')->unique();
24 | $table->string('password')->nullable();
25 | $table->string('address')->nullable();
26 | $table->string('zipCode')->nullable();
27 | $table->string('phone')->nullable();
28 | $table->string('mobile')->nullable();
29 | $table->string('city', '100')->nullable();
30 | $table->string('state', '100')->nullable();
31 | $table->string('country', '100')->nullable();
32 | $table->enum('role', ['BASIC_USER', 'ADMIN_USER'])->default('BASIC_USER');
33 | $table->tinyInteger('isActive');
34 | $table->string('profileImage')->nullable();
35 | $table->timestamps();
36 | $table->softDeletes();
37 | });
38 | }
39 |
40 | /**
41 | * Reverse the migrations.
42 | *
43 | * @return void
44 | */
45 | public function down()
46 | {
47 | Schema::dropIfExists('users');
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/database/seeds/DatabaseSeeder.php:
--------------------------------------------------------------------------------
1 | call(UsersTableSeeder::class);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/database/seeds/UsersTableSeeder.php:
--------------------------------------------------------------------------------
1 | create();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/develop:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Set environment variables for dev
4 | export APP_PORT=${APP_PORT:-80}
5 | export DB_PORT=${DB_PORT:-3306}
6 | export XDEBUG_HOST=$(ipconfig getifaddr en0) # Specific to Macintosh
7 |
8 | COMPOSE="docker-compose"
9 |
10 | # If we pass any arguments
11 | if [ $# -gt 0 ];then
12 |
13 | # If "art" or "artisan" is used, pass-thru to "artisan"
14 | # inside a new container
15 | if [ "$1" == "art" ] || [ "$1" == "artisan" ]; then
16 | shift 1
17 | $COMPOSE run --rm \
18 | -w /var/www/html \
19 | restapi-app \
20 | php artisan "$@"
21 |
22 | # If "composer" is used, pass-thru to "composer"
23 | # inside a new container
24 | elif [ "$1" == "composer" ];then
25 | shift 1
26 | $COMPOSE run --rm \
27 | -w /var/www/html \
28 | restapi-app \
29 | composer "$@"
30 |
31 | # If "test" is used, run unit tests,
32 | # pass-thru any extra arguments to php-unit
33 | elif [ "$1" == "test" ];then
34 | shift 1
35 | $COMPOSE run --rm \
36 | -w /var/www/html \
37 | restapi-app \
38 | ./vendor/bin/phpunit "$@"
39 | else
40 | $COMPOSE "$@"
41 | fi
42 | else
43 | $COMPOSE ps
44 | fi
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | restapi-app:
4 | depends_on:
5 | - restapi-mysql
6 | build:
7 | context: ./docker
8 | dockerfile: Dockerfile
9 | image: restapi/php-nginx
10 | environment:
11 | XDEBUG_HOST : ${XDEBUG_HOST}
12 | ports:
13 | - "${APP_PORT}:80"
14 | volumes:
15 | - .:/var/www/html
16 | networks:
17 | - restapi_docker
18 | restapi-redis:
19 | image: redis:alpine
20 | networks:
21 | - restapi_docker
22 | restapi-mysql:
23 | image: mysql:5.7
24 | ports:
25 | - "${DB_PORT}:3306"
26 | environment:
27 | MYSQL_ROOT_PASSWORD: root
28 | MYSQL_DATABASE: restapi
29 | MYSQL_USER: homestead
30 | MYSQL_PASSWORD: secret
31 | volumes:
32 | - rapimsqldata:/var/lib/mysql
33 | networks:
34 | - restapi_docker
35 | networks:
36 | restapi_docker:
37 | driver: "bridge"
38 | volumes:
39 | rapimsqldata:
40 | driver: "local"
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:16.04
2 |
3 | LABEL maintainer="Hasan Hasibul"
4 |
5 | RUN apt-get update \
6 | && apt-get install -y locales \
7 | && locale-gen en_US.UTF-8
8 |
9 | ENV LANG en_US.UTF-8
10 | ENV LANGUAGE en_US:en
11 | ENV LC_ALL en_US.UTF-8
12 |
13 | WORKDIR /var/www/html
14 |
15 | RUN apt-get update \
16 | && apt-get install -y nginx curl zip unzip git vim software-properties-common supervisor sqlite3 \
17 | && add-apt-repository -y ppa:ondrej/php \
18 | && apt-get update \
19 | && apt-get install -y php7.1-fpm php7.1-cli php7.1-mcrypt php7.1-gd php7.1-mysql \
20 | php7.1-pgsql php7.1-imap php-memcached php7.1-mbstring php7.1-xml php7.1-curl \
21 | php7.1-imagick php7.1-zip php7.1-bcmath php7.1-sqlite3 php7.1-xdebug php7.1-mongodb \
22 | && php -r "readfile('http://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \
23 | && mkdir /run/php \
24 | && apt-get -y autoremove \
25 | && apt-get clean \
26 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
27 | && echo "daemon off;" >> /etc/nginx/nginx.conf
28 |
29 | RUN ln -sf /dev/stdout /var/log/nginx/access.log \
30 | && ln -sf /dev/stderr /var/log/nginx/error.log
31 |
32 | COPY default /etc/nginx/sites-available/default
33 | COPY php-fpm.conf /etc/php/7.1/fpm/php-fpm.conf
34 | COPY xdebug.ini /etc/php/7.1/mods-available/xdebug.ini
35 |
36 | COPY start-container /usr/local/bin/start-container
37 | RUN chmod +x /usr/local/bin/start-container
38 |
39 | EXPOSE 80
40 |
41 | COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
42 |
43 | CMD ["start-container"]
--------------------------------------------------------------------------------
/docker/default:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80 default_server;
3 | listen [::]:80 default_server ipv6only=on;
4 |
5 | root /var/www/html/public;
6 |
7 | index index.html index.htm index.php;
8 |
9 | server_name restapi.app;
10 |
11 | charset utf-8;
12 |
13 | location = /favicon.ico { log_not_found off; access_log off; }
14 | location = /robots.txt { log_not_found off; access_log off; }
15 |
16 |
17 | location / {
18 | try_files $uri $uri/ /index.php$is_args$args;
19 | }
20 |
21 | location ~ \.php$ {
22 | include snippets/fastcgi-php.conf;
23 | fastcgi_pass unix:/run/php/php7.1-fpm.sock;
24 | }
25 |
26 | error_page 404 /index.php;
27 |
28 | location ~ /\.ht {
29 | deny all;
30 | }
31 | }
--------------------------------------------------------------------------------
/docker/php-fpm.conf:
--------------------------------------------------------------------------------
1 | ;;;;;;;;;;;;;;;;;;;;;
2 | ; FPM Configuration ;
3 | ;;;;;;;;;;;;;;;;;;;;;
4 |
5 | ; All relative paths in this configuration file are relative to PHP's install
6 | ; prefix (/usr). This prefix can be dynamically changed by using the
7 | ; '-p' argument from the command line.
8 |
9 | ;;;;;;;;;;;;;;;;;;
10 | ; Global Options ;
11 | ;;;;;;;;;;;;;;;;;;
12 |
13 | [global]
14 | ; Pid file
15 | ; Note: the default prefix is /var
16 | ; Default Value: none
17 | pid = /run/php/php7.1-fpm.pid
18 |
19 | ; Error log file
20 | ; If it's set to "syslog", log is sent to syslogd instead of being written
21 | ; in a local file.
22 | ; Note: the default prefix is /var
23 | ; Default Value: log/php-fpm.log
24 | error_log = /proc/self/fd/2
25 |
26 | ; syslog_facility is used to specify what type of program is logging the
27 | ; message. This lets syslogd specify that messages from different facilities
28 | ; will be handled differently.
29 | ; See syslog(3) for possible values (ex daemon equiv LOG_DAEMON)
30 | ; Default Value: daemon
31 | ;syslog.facility = daemon
32 |
33 | ; syslog_ident is prepended to every message. If you have multiple FPM
34 | ; instances running on the same server, you can change the default value
35 | ; which must suit common needs.
36 | ; Default Value: php-fpm
37 | ;syslog.ident = php-fpm
38 |
39 | ; Log level
40 | ; Possible Values: alert, error, warning, notice, debug
41 | ; Default Value: notice
42 | ;log_level = notice
43 |
44 | ; If this number of child processes exit with SIGSEGV or SIGBUS within the time
45 | ; interval set by emergency_restart_interval then FPM will restart. A value
46 | ; of '0' means 'Off'.
47 | ; Default Value: 0
48 | ;emergency_restart_threshold = 0
49 |
50 | ; Interval of time used by emergency_restart_interval to determine when
51 | ; a graceful restart will be initiated. This can be useful to work around
52 | ; accidental corruptions in an accelerator's shared memory.
53 | ; Available Units: s(econds), m(inutes), h(ours), or d(ays)
54 | ; Default Unit: seconds
55 | ; Default Value: 0
56 | ;emergency_restart_interval = 0
57 |
58 | ; Time limit for child processes to wait for a reaction on signals from master.
59 | ; Available units: s(econds), m(inutes), h(ours), or d(ays)
60 | ; Default Unit: seconds
61 | ; Default Value: 0
62 | ;process_control_timeout = 0
63 |
64 | ; The maximum number of processes FPM will fork. This has been design to control
65 | ; the global number of processes when using dynamic PM within a lot of pools.
66 | ; Use it with caution.
67 | ; Note: A value of 0 indicates no limit
68 | ; Default Value: 0
69 | ; process.max = 128
70 |
71 | ; Specify the nice(2) priority to apply to the master process (only if set)
72 | ; The value can vary from -19 (highest priority) to 20 (lower priority)
73 | ; Note: - It will only work if the FPM master process is launched as root
74 | ; - The pool process will inherit the master process priority
75 | ; unless it specified otherwise
76 | ; Default Value: no set
77 | ; process.priority = -19
78 |
79 | ; Send FPM to background. Set to 'no' to keep FPM in foreground for debugging.
80 | ; Default Value: yes
81 | daemonize = no
82 |
83 | ; Set open file descriptor rlimit for the master process.
84 | ; Default Value: system defined value
85 | ;rlimit_files = 1024
86 |
87 | ; Set max core size rlimit for the master process.
88 | ; Possible Values: 'unlimited' or an integer greater or equal to 0
89 | ; Default Value: system defined value
90 | ;rlimit_core = 0
91 |
92 | ; Specify the event mechanism FPM will use. The following is available:
93 | ; - select (any POSIX os)
94 | ; - poll (any POSIX os)
95 | ; - epoll (linux >= 2.5.44)
96 | ; - kqueue (FreeBSD >= 4.1, OpenBSD >= 2.9, NetBSD >= 2.0)
97 | ; - /dev/poll (Solaris >= 7)
98 | ; - port (Solaris >= 10)
99 | ; Default Value: not set (auto detection)
100 | ;events.mechanism = epoll
101 |
102 | ; When FPM is build with systemd integration, specify the interval,
103 | ; in second, between health report notification to systemd.
104 | ; Set to 0 to disable.
105 | ; Available Units: s(econds), m(inutes), h(ours)
106 | ; Default Unit: seconds
107 | ; Default value: 10
108 | ;systemd_interval = 10
109 |
110 | ;;;;;;;;;;;;;;;;;;;;
111 | ; Pool Definitions ;
112 | ;;;;;;;;;;;;;;;;;;;;
113 |
114 | ; Multiple pools of child processes may be started with different listening
115 | ; ports and different management options. The name of the pool will be
116 | ; used in logs and stats. There is no limitation on the number of pools which
117 | ; FPM can handle. Your system will tell you anyway :)
118 |
119 | ; Include one or more files. If glob(3) exists, it is used to include a bunch of
120 | ; files from a glob(3) pattern. This directive can be used everywhere in the
121 | ; file.
122 | ; Relative path can also be used. They will be prefixed by:
123 | ; - the global prefix if it's been set (-p argument)
124 | ; - /usr otherwise
125 | include=/etc/php/7.1/fpm/pool.d/*.conf
126 |
127 | ; Clear environment in FPM workers. Prevents arbitrary environment variables from
128 | ; reaching FPM worker processes by clearing the environment in workers before env
129 | ; vars specified in this pool configuration are added.
130 | clear_env=false
--------------------------------------------------------------------------------
/docker/start-container:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | sed -i "s/xdebug\.remote_host\=.*/xdebug\.remote_host\=$XDEBUG_HOST/g" /etc/php/7.1/mods-available/xdebug.ini
4 |
5 | /usr/bin/supervisord
--------------------------------------------------------------------------------
/docker/supervisord.conf:
--------------------------------------------------------------------------------
1 | [supervisord]
2 | nodaemon=true
3 |
4 | [program:nginx]
5 | command=nginx
6 | stdout_logfile=/dev/stdout
7 | stdout_logfile_maxbytes=0
8 | stderr_logfile=/dev/stderr
9 | stderr_logfile_maxbytes=0
10 |
11 | [program:php-fpm]
12 | command=php-fpm7.1
13 | stdout_logfile=/dev/stdout
14 | stdout_logfile_maxbytes=0
15 | stderr_logfile=/dev/stderr
16 | stderr_logfile_maxbytes=0
--------------------------------------------------------------------------------
/docker/xdebug.ini:
--------------------------------------------------------------------------------
1 | zend_extension=xdebug.so
2 | xdebug.remote_enable=1
3 | xdebug.remote_handler=dbgp
4 | xdebug.remote_port=9000
5 | xdebug.remote_autostart=1
6 | xdebug.remote_connect_back=0
7 | xdebug.idekey=docker
8 | xdebug.remote_host=??
9 | xdebug.max_nesting_level=500
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
Hi, {{ ucwords($user->firstName) }} Welcome to yoursite.com. Thank you for sign-up with us..
9 | 10 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | get('/', function () { 16 | return app()->version(); 17 | }); 18 | 19 | // Generate random string 20 | $router->get('appKey', function () { 21 | return str_random('32'); 22 | }); 23 | 24 | // route for creating access_token 25 | $router->post('accessToken', 'AccessTokenController@createAccessToken'); 26 | 27 | $router->group(['middleware' => ['auth:api', 'throttle:60']], function () use ($router) { 28 | $router->post('users', [ 29 | 'uses' => 'UserController@store', 30 | 'middleware' => "scope:users,users:create" 31 | ]); 32 | $router->get('users', [ 33 | 'uses' => 'UserController@index', 34 | 'middleware' => "scope:users,users:list" 35 | ]); 36 | $router->get('users/{id}', [ 37 | 'uses' => 'UserController@show', 38 | 'middleware' => "scope:users,users:read" 39 | ]); 40 | $router->put('users/{id}', [ 41 | 'uses' => 'UserController@update', 42 | 'middleware' => "scope:users,users:write" 43 | ]); 44 | $router->delete('users/{id}', [ 45 | 'uses' => 'UserController@destroy', 46 | 'middleware' => "scope:users,users:delete" 47 | ]); 48 | }); 49 | 50 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/oauth-private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /storage/oauth-public.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuxY4f9qMcoqQNjS4R3N8 3 | gFsV1dsIKzIMjyjTHQ/itMJKSvlIzdUc335H3WJU4qTUL9A0DDpwgZsyOjJInMmy 4 | ZOvtwXI2RSMocDmQ5mr7Nsv5QDt9DKSPceMq0ex1BKsLtLCGMwHTWbSg3idZYjmF 5 | gmEa15YGpVAc0WyE9xb0Y+KHN8qKvprg7PQPzKmRiF3XHMshhKPp7A6RO57T5olA 6 | Wj/QvCz/aeOW0xrnaaxOgZwABAllY+VKBiCtK3tRRfVPqUf2e89Kdooz/yKFse7j 7 | OdKJNuU+EagD3CQ+55xH+A8zpMk4HL2mqF4PhQVrt+9Noq9lMB+AszqQUNDy5WQp 8 | 5ChZpac7muxI5hP02hdw5i7vq/vCTr8OOZkvoXSDMk0CKGN+IzSIbWYJ0PIWlnIX 9 | Numx7U0S53SNxWH8wov0k5O0BZmAxyL1ufiawRDkVZjcFGkLJTd6Bh88DUtC6eb4 10 | cJIG2/1GCVHDnKc2LuwpCnXcjMCTfaU51GieolItHBX+W1s3tzg7rV4Dwd3ydqt/ 11 | QffdA3rPUkvjT3R5/ZaHb1PWSXnw/G+rz/JpbtWcdrSprf1LUa7NAV54FA01eWA6 12 | VzPH6y2Xvt1T+nG6TjSg+NgOhdblG3I4CpXQVN9R1+3ebqeMiiNGsq+ZNm7kGoN7 13 | nrVjrD5suWykcgnRijhHy4kCAwEAAQ== 14 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /tests/Endpoints/UsersTest.php: -------------------------------------------------------------------------------- 1 | call('GET', '/users'); 17 | $this->assertResponseStatus(401); 18 | 19 | $user = factory(User::class)->create(); 20 | $user->withAccessToken(new Token(['scopes' => ['*']])); 21 | $this->actingAs($user); 22 | 23 | $this->call('GET', '/users'); 24 | $this->assertResponseOk(); 25 | 26 | // test json response 27 | $this->seeJson(['email' => $user->email]); 28 | } 29 | 30 | public function testGettingSpecificUser() 31 | { 32 | 33 | // without authentication should give 401 34 | $this->call('GET', '/users/12345'); 35 | $this->assertResponseStatus(401); 36 | 37 | $user = factory(User::class)->create(); 38 | 39 | // authenticate 40 | $user->withAccessToken(new Token(['scopes' => ['*']])); 41 | $this->actingAs($user); 42 | 43 | // should work 44 | $this->call('GET', '/users/'.$user->uid); 45 | $this->assertResponseStatus(200); 46 | 47 | // test json response 48 | $this->seeJson(['email' => $user->email]); 49 | 50 | // accessing invalid user should give 404 51 | $this->call('GET', '/users/13232323'); 52 | $this->assertResponseStatus(404); 53 | } 54 | 55 | public function testCreatingUser() 56 | { 57 | // without authentication should give 401 Unauthorized 58 | $this->call('POST', '/users', []); 59 | $this->assertResponseStatus(401); 60 | 61 | $user = factory(User::class)->make(); 62 | $user->withAccessToken(new Token(['scopes' => ['*']])); 63 | $this->actingAs($user); 64 | 65 | // empty data should give 400 invalid fields error 66 | $this->call('POST', '/users', []); 67 | $this->assertResponseStatus(400); 68 | 69 | // should work now 70 | $this->call('POST', '/users', [ 71 | 'email' => 'test@test.com', 72 | 'firstName' => 'first', 73 | 'lastName' => 'last' 74 | ]); 75 | $this->assertResponseStatus(201); 76 | 77 | // same email should give 400 invalid 78 | $this->call('POST', '/users', [ 79 | 'email' => 'test@test.com', 80 | 'firstName' => 'first2', 81 | 'lastName' => 'last2' 82 | ]); 83 | $this->assertResponseStatus(400); 84 | } 85 | 86 | public function testUpdatingUser() 87 | { 88 | $user = factory(User::class)->create(); 89 | 90 | // without authentication should give 401 Unauthorized 91 | $this->call('PUT', '/users/'.$user->uid, []); 92 | $this->assertResponseStatus(401); 93 | 94 | // authenticate 95 | $user->withAccessToken(new Token(['scopes' => ['*']])); 96 | $this->actingAs($user); 97 | 98 | $this->call('PUT', '/users/'.$user->uid, [ 99 | 'firstName' => 'updated_first' 100 | ]); 101 | $this->assertResponseOk(); 102 | 103 | $this->call('PUT', '/users/234324', [ 104 | 'firstName' => 'updated_first' 105 | ]); 106 | $this->assertResponseStatus(404); 107 | } 108 | 109 | public function testDeletingUser() 110 | { 111 | 112 | // without authentication should give 401 113 | $this->call('DELETE', '/users/12345'); 114 | $this->assertResponseStatus(401); 115 | 116 | $user = factory(User::class)->create(); 117 | 118 | // authenticate 119 | $user->withAccessToken(new Token(['scopes' => ['*']])); 120 | $this->actingAs($user); 121 | 122 | // should work 123 | $this->call('DELETE', '/users/'.$user->uid); 124 | $this->assertResponseStatus(204); 125 | 126 | // deleting invalid user should give 404 127 | $this->call('GET', '/users/13232323'); 128 | $this->assertResponseStatus(404); 129 | } 130 | } -------------------------------------------------------------------------------- /tests/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 16 | 17 | $this->assertResponseOk(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Http/Middleware/ThrottleRequestsTest.php: -------------------------------------------------------------------------------- 1 | create(); 19 | $this->actingAs($user); 20 | 21 | $request = Request::create($this->prepareUrlForRequest('/users/' . $user->uid)); 22 | $middleware = app(ThrottleRequests::class); 23 | 24 | $totalNumberOfRequest = 60; 25 | for ($i = 1; $i <= 65; $i++) { 26 | $response = $middleware->handle($request, function () { 27 | return response()->json(['message' => 'success'], 200); 28 | }, $totalNumberOfRequest); 29 | 30 | $totalRateLimit = $response->headers->get('X-RateLimit-Limit'); 31 | $rateLimitRemaining = $response->headers->get('X-RateLimit-Remaining'); 32 | $this->assertEquals($totalRateLimit, $totalNumberOfRequest); 33 | 34 | if ($totalRateLimit >= $i) { 35 | $this->assertEquals($totalRateLimit - $i, $rateLimitRemaining); 36 | $this->assertTrue($response->isOk()); 37 | $this->assertEquals('{"message":"success"}', $response->getContent()); 38 | } else { 39 | // for greater than 60 (default limit), it will throttle 40 | $this->assertNotEquals($totalRateLimit - $i, $rateLimitRemaining); 41 | $this->assertFalse($response->isOk()); 42 | $this->assertNotEquals($totalRateLimit - $i, $rateLimitRemaining); 43 | $this->assertEquals('{"status":429,"message":"Too Many Attempts."}', $response->getContent()); 44 | } 45 | } 46 | } 47 | 48 | public function testThrottleWorksInEndpointRequest() 49 | { 50 | $user = factory(User::class)->create(); 51 | 52 | // authenticate 53 | $user->withAccessToken(new Token(['scopes' => ['*']])); 54 | $this->actingAs($user); 55 | 56 | for ($i = 1; $i <= 65 ; $i++) { 57 | $this->call('GET', '/users/' . $user->uid); 58 | 59 | // for greater than 60 (default limit), it will throttle 60 | if ($i > 60) { 61 | $this->assertResponseStatus(429); 62 | } else { 63 | $this->assertResponseStatus(200); 64 | $this->seeJson(['email' => $user->email]); 65 | } 66 | 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/Repositories/EloquentUserRepositoryTest.php: -------------------------------------------------------------------------------- 1 | eloquentUserRepository = new EloquentUserRepository(new User()); 24 | } 25 | 26 | public function testCreateUser() 27 | { 28 | $testUserArray = factory(User::class)->make()->toArray(); 29 | $user = $this->eloquentUserRepository->save($testUserArray); 30 | 31 | $this->assertInstanceOf(User::class, $user); 32 | $this->assertEquals($testUserArray['email'], $user->email); 33 | } 34 | 35 | public function testFindOne() 36 | { 37 | $testUser = factory(User::class)->create(); 38 | 39 | //first, check if it returns valid user 40 | $user = $this->eloquentUserRepository->findOne($testUser->uid); 41 | $this->assertInstanceOf(User::class, $user); 42 | 43 | //now check it returns null for gibberish data 44 | $user = $this->eloquentUserRepository->findOne('giberish'); 45 | $this->assertNull($user); 46 | } 47 | 48 | public function testFindOneBy() 49 | { 50 | $testUser = factory(User::class)->create(); 51 | 52 | //first, check if it returns valid user 53 | $user = $this->eloquentUserRepository->findOneBy(['uid' => $testUser->uid]); 54 | $this->assertInstanceOf(User::class, $user); 55 | $this->assertEquals($testUser->email, $user->email); 56 | 57 | //check if it returns valid user, for multiple criteria 58 | $user = $this->eloquentUserRepository->findOneBy([ 59 | 'email' => $testUser->email, 60 | 'firstName' => $testUser->firstName 61 | ]); 62 | $this->assertInstanceOf(User::class, $user); 63 | $this->assertEquals($testUser->firstName, $user->firstName); 64 | 65 | //now check it returns null for gibberish data 66 | $user = $this->eloquentUserRepository->findOneBy(['lastName' => 'Test Last']); 67 | $this->assertNull($user); 68 | } 69 | 70 | public function testFindBy() 71 | { 72 | // when instantiate the repo, logged in as Admin user. So, that we can search any user 73 | $adminUser = factory(User::class)->make(['role' => User::ADMIN_ROLE]); 74 | Auth::shouldReceive('user')->andReturn($adminUser); 75 | $eloquentUserRepository = new EloquentUserRepository(new User()); 76 | 77 | //get total users of this resource 78 | $totalUsers = User::all()->count(); 79 | 80 | //first, check if it returns all users without criteria 81 | $users = $eloquentUserRepository->findBy([]); 82 | $this->assertCount($totalUsers, $users); 83 | 84 | //create a user and findBy that using user's firstName 85 | factory(User::class)->create(['firstName' => 'Pappu']); 86 | $users = $eloquentUserRepository->findBy(['firstName' => 'Pappu']); 87 | //test instanceof 88 | $this->assertInstanceOf(LengthAwarePaginator::class, $users); 89 | $this->assertNotEmpty($users); 90 | 91 | //check with multiple criteria 92 | $searchCriteria = ['zipCode' => '11121', 'username' => 'jobberAli']; 93 | $previousTotalUsers = $eloquentUserRepository->findBy($searchCriteria)->count(); 94 | $this->assertEmpty($previousTotalUsers); 95 | 96 | factory(User::class)->create($searchCriteria); 97 | $newTotalUsers = $eloquentUserRepository->findBy($searchCriteria)->count(); 98 | $this->assertNotEmpty($newTotalUsers); 99 | 100 | //with basic user's permission, create a user and findBy using that user's firstName 101 | factory(User::class)->create(['firstName' => 'Jobber']); 102 | $users = $this->eloquentUserRepository->findBy(['firstName' => 'Jobber']); 103 | $this->assertEmpty($users); 104 | } 105 | 106 | public function testUpdate() 107 | { 108 | $testUser = factory(User::class)->create([ 109 | 'firstName' => 'test_first', 110 | 'lastName' => 'test_last' 111 | ]); 112 | 113 | // First, test user instance 114 | $user = $this->eloquentUserRepository->findOne($testUser->uid); 115 | $this->assertInstanceOf(User::class, $user); 116 | 117 | // Update user 118 | $this->eloquentUserRepository->update($testUser, [ 119 | 'firstName' => 'updated first_name', 120 | 'lastName' => 'updated last_name' 121 | ]); 122 | 123 | // Fetch the user again 124 | $user = $this->eloquentUserRepository->findOne($testUser->uid); 125 | $this->assertEquals('updated first_name', $user->firstName); 126 | $this->assertEquals('updated last_name', $user->lastName); 127 | $this->assertNotEquals('test_first', $user->firstName); 128 | } 129 | 130 | public function testDelete() 131 | { 132 | $testUser = factory(User::class)->create(); 133 | 134 | $isDeleted = $this->eloquentUserRepository->delete($testUser); 135 | $this->assertTrue($isDeleted); 136 | 137 | // confirm deleted 138 | $user = $this->eloquentUserRepository->findOne($testUser->uid); 139 | $this->assertNull($user); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 |