├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── README.md ├── app ├── Enums │ ├── ApiStatus.php │ ├── Messages.php │ └── MySQLError.php ├── Http │ ├── Controllers │ │ ├── AuthController.php │ │ ├── Controller.php │ │ ├── IPController.php │ │ └── RoleController.php │ ├── Middleware │ │ ├── IPAuthorizationMiddleware.php │ │ └── JWTTokenMiddleware.php │ ├── Requests │ │ ├── IpRequest.php │ │ ├── LoginRequest.php │ │ ├── RoleRequest.php │ │ └── StoreUserRequest.php │ └── Resources │ │ ├── IPResource.php │ │ ├── LoginResource.php │ │ ├── RoleResource.php │ │ └── UserResource.php ├── Models │ ├── IPList.php │ ├── Role.php │ └── User.php ├── Providers │ ├── AppServiceProvider.php │ └── TelescopeServiceProvider.php ├── Services │ └── CrudController.php ├── Traits │ ├── ApiResponseFormatTrait.php │ ├── Filterable.php │ ├── Ownerable.php │ ├── ResponseFormatterTrait.php │ └── ResponseHandlerTrait.php └── Utils │ └── APIResponse.php ├── artisan ├── bootstrap ├── app.php ├── cache │ └── .gitignore └── providers.php ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── cache.php ├── database.php ├── filesystems.php ├── jwt.php ├── logging.php ├── mail.php ├── queue.php ├── sanctum.php ├── services.php ├── session.php └── telescope.php ├── database ├── .gitignore ├── factories │ └── UserFactory.php ├── migrations │ ├── 0001_01_01_000000_create_users_table.php │ ├── 0001_01_01_000001_create_cache_table.php │ ├── 0001_01_01_000002_create_jobs_table.php │ ├── 2024_03_13_090743_create_personal_access_tokens_table.php │ ├── 2024_03_15_080025_create_roles_table.php │ ├── 2024_03_15_083732_create_ip_lists_table.php │ └── 2024_03_16_072410_create_telescope_entries_table.php └── seeders │ ├── DatabaseSeeder.php │ ├── IPListSeeder.php │ └── RoleSeeder.php ├── documents ├── Laravel Rest APIs.postman_collection.json └── laravel.sql ├── package-lock.json ├── package.json ├── phpunit.xml ├── public ├── .htaccess ├── favicon.ico ├── index.php ├── robots.txt └── vendor │ └── telescope │ ├── app-dark.css │ ├── app.css │ ├── app.js │ ├── favicon.ico │ └── mix-manifest.json ├── resources ├── css │ └── app.css ├── js │ ├── app.js │ └── bootstrap.js └── views │ └── welcome.blade.php ├── routes ├── api.php ├── console.php └── web.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tests ├── Feature │ └── ExampleTest.php ├── TestCase.php └── Unit │ └── ExampleTest.php └── vite.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [docker-compose.yml] 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_TIMEZONE=UTC 6 | APP_URL=http://localhost 7 | 8 | APP_LOCALE=en 9 | APP_FALLBACK_LOCALE=en 10 | APP_FAKER_LOCALE=en_US 11 | 12 | APP_MAINTENANCE_DRIVER=file 13 | APP_MAINTENANCE_STORE=database 14 | 15 | BCRYPT_ROUNDS=12 16 | 17 | LOG_CHANNEL=stack 18 | LOG_STACK=single 19 | LOG_DEPRECATIONS_CHANNEL=null 20 | LOG_LEVEL=debug 21 | 22 | DB_CONNECTION=sqlite 23 | # DB_HOST=127.0.0.1 24 | # DB_PORT=3306 25 | # DB_DATABASE=laravel 26 | # DB_USERNAME=root 27 | # DB_PASSWORD= 28 | 29 | SESSION_DRIVER=database 30 | SESSION_LIFETIME=120 31 | SESSION_ENCRYPT=false 32 | SESSION_PATH=/ 33 | SESSION_DOMAIN=null 34 | 35 | BROADCAST_CONNECTION=log 36 | FILESYSTEM_DISK=local 37 | QUEUE_CONNECTION=database 38 | 39 | CACHE_STORE=database 40 | CACHE_PREFIX= 41 | 42 | MEMCACHED_HOST=127.0.0.1 43 | 44 | REDIS_CLIENT=phpredis 45 | REDIS_HOST=127.0.0.1 46 | REDIS_PASSWORD=null 47 | REDIS_PORT=6379 48 | 49 | MAIL_MAILER=log 50 | MAIL_HOST=127.0.0.1 51 | MAIL_PORT=2525 52 | MAIL_USERNAME=null 53 | MAIL_PASSWORD=null 54 | MAIL_ENCRYPTION=null 55 | MAIL_FROM_ADDRESS="hello@example.com" 56 | MAIL_FROM_NAME="${APP_NAME}" 57 | 58 | AWS_ACCESS_KEY_ID= 59 | AWS_SECRET_ACCESS_KEY= 60 | AWS_DEFAULT_REGION=us-east-1 61 | AWS_BUCKET= 62 | AWS_USE_PATH_STYLE_ENDPOINT=false 63 | 64 | VITE_APP_NAME="${APP_NAME}" 65 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.blade.php diff=html 4 | *.css diff=css 5 | *.html diff=html 6 | *.md diff=markdown 7 | *.php diff=php 8 | 9 | /.github export-ignore 10 | CHANGELOG.md export-ignore 11 | .styleci.yml export-ignore 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /vendor 8 | .env 9 | .env.backup 10 | .env.production 11 | .phpunit.result.cache 12 | Homestead.json 13 | Homestead.yaml 14 | auth.json 15 | npm-debug.log 16 | yarn-error.log 17 | /.fleet 18 | /.idea 19 | /.vscode 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Project Details 2 | 3 | This project is built using Laravel version 11 and focuses on REST API design. It implements best practices and emphasizes separating requests and responses while adhering to various design principles such as DRY (Don't Repeat Yourself), SOLID, and Separation of Concerns. 4 | 5 | ### Features Implemented 6 | 7 | - Two complete CRUD operations for models: `Role` and `IpList` 8 | - Usage of: 9 | - Traits 10 | - Services 11 | - Enums 12 | - Custom Middleware 13 | - JWT Token Authentication 14 | - Object-Oriented Programming (OOP) principles 15 | - DRY (Don't Repeat Yourself) approach 16 | - Request handling 17 | - Resource management 18 | 19 | ## Setting Up The Project 20 | 21 | 1. **Clone the Repository**: 22 | - Open your terminal or command prompt. 23 | - Navigate to the directory where you want to clone the project. 24 | - Run the following command: 25 | ```bash 26 | git clone https://github.com/sMmominur/restapi-laravel.git 27 | ``` 28 | 29 | 2. **Install Dependencies**: 30 | - Navigate into the cloned project directory: 31 | ```bash 32 | cd project-name 33 | ``` 34 | - Install Composer dependencies: 35 | ```bash 36 | composer install 37 | ``` 38 | 39 | 3. **Create Environment File**: 40 | - Make a copy of the `.env.example` file and rename it to `.env`: 41 | ```bash 42 | cp .env.example .env 43 | ``` 44 | - Generate an application key: 45 | ```bash 46 | php artisan key:generate 47 | ``` 48 | 49 | 4. **Database Configuration**: 50 | - Open the `.env` file and configure your database connection settings. 51 | - Set the database name, username, password, and other relevant settings. 52 | 53 | 5. **Run Migrations and Seeders**: 54 | - Run database migrations to create tables: 55 | ```bash 56 | php artisan migrate 57 | ``` 58 | - Run the seeders to populate the database with sample data: 59 | ```bash 60 | php artisan db:seed 61 | ``` 62 | 63 | 6. **Serve the Application**: 64 | - Start the Laravel development server: 65 | ```bash 66 | php artisan serve 67 | ``` 68 | 69 | 7. **Access the Application**: 70 | - Open your web browser and go to `http://localhost:8000` or the URL provided by the `php artisan serve` command. 71 | 72 | 8. **Test APIs**: 73 | - Utilize the provided CRUD operations for testing the REST APIs as per the project's documentation or instructions. 74 | 75 | 9. **Explore Additional Features**: 76 | - Explore and test additional features such as Traits, Services, Enums, Custom Middleware, JWT Token Authentication, Object-Oriented Programming (OOP) principles, DRY approach, Request handling, and Resource management as provided in the project. 77 | 78 | 10. **Start Developing**: 79 | - With the project set up, you can now start developing your application or explore further customization as per your requirements. 80 | 81 | ## API Sample and Documentation 82 | 83 | Details of requests and responses are provided in the following Postman documentation. Please follow the document for comprehensive API usage and understanding. 84 | 85 | [Postman Documentation](https://www.postman.com/mominur23/workspace/laravel-rest/collection/29494647-b8fb4f44-8602-40ba-99ec-573daf0a1996?action=share&creator=29494647) 86 | 87 | Make sure to consult the provided documentation for information on how to interact with the APIs, including request formats, available endpoints, authentication mechanisms (if any), and sample responses. 88 | 89 | This documentation serves as a guide for developers to effectively utilize and integrate the API endpoints into their applications. 90 | -------------------------------------------------------------------------------- /app/Enums/ApiStatus.php: -------------------------------------------------------------------------------- 1 | validated(); 23 | if (!$token = auth()->attempt($credentials)) { 24 | return $this->unauthorizedResponse(); 25 | } 26 | return new LoginResource($token); 27 | } catch (QueryException $queryException) { 28 | return $this->queryExceptionResponse($queryException); 29 | } catch (Exception $exception) { 30 | $this->recordException($exception); 31 | return $this->serverErrorResponse($exception); 32 | } 33 | } 34 | 35 | public function storeUser(StoreUserRequest $request) 36 | { 37 | try { 38 | $user = User::create($request->validated()); 39 | return (new UserResource($user))->additional($this->preparedResponse('store')); 40 | } catch (QueryException $queryException) { 41 | return $this->queryExceptionResponse($queryException); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | ip(), $allowedIPs)) { 27 | 28 | return response([ 29 | 'response' => [ 30 | 'status' => ApiStatus::ERROR, 31 | 'status_code' => Response::HTTP_UNAUTHORIZED, 32 | 'error' => [ 33 | 'message' => Messages::UNAUTHORIZED_DOMAIN_OR_IP, 34 | 'timestamp' => Carbon::now(), 35 | ], 36 | ] 37 | ], Response::HTTP_UNAUTHORIZED); 38 | } 39 | 40 | return $next($request); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Http/Middleware/JWTTokenMiddleware.php: -------------------------------------------------------------------------------- 1 | authenticate(); 26 | if (!$user) { 27 | return $this->JWTCustomResponse(Messages::INVALID_USER); 28 | } 29 | } catch (JWTException $e) { 30 | return $this->JWTCustomResponse($e->getMessage()); 31 | } 32 | 33 | return $next($request); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Http/Requests/IpRequest.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | public function rules(): array 28 | { 29 | $rules = [ 30 | 'ip_address' => 'required|string|max:40', 31 | 'status' => 'required|in:active,inactive', 32 | 'ip_type' => 'required|in:IPv4,IPv6', 33 | 'remarks' => 'required|string|max:50' 34 | ]; 35 | 36 | return $rules; 37 | } 38 | 39 | /** 40 | * Get custom error messages for validation rules. 41 | * 42 | * @return array 43 | */ 44 | public function messages(): array 45 | { 46 | return [ 47 | 'ip_address.required' => 'IP address field is required.', 48 | 'ip_address.string' => 'IP address field must be a string.', 49 | 'ip_address.max' => 'IP address field may not be greater than 40 characters.', 50 | 'status.required' => 'The status field is required.', 51 | 'status.in' => 'The selected status is invalid.Use active or inactive', 52 | 'ip_type.required' => 'IP type field is required.', 53 | 'ip_type.in' => 'The selected IP type is invalid.Use IPv4 or IPv6', 54 | 'remarks.required' => 'The remarks field is required.', 55 | 'remarks.string' => 'The remarks field must be a string.', 56 | 'remarks.max' => 'The remarks field may not be greater than 50 characters.', 57 | ]; 58 | } 59 | 60 | /** 61 | * Handle a failed validation attempt. 62 | * 63 | * @param \Illuminate\Contracts\Validation\Validator $validator 64 | * @return void 65 | * 66 | * @throws Illuminate\Http\Exceptions\HttpResponseException 67 | */ 68 | 69 | protected function failedValidation(Validator $validator) 70 | { 71 | throw new HttpResponseException(response()->json($this->validationFailedResponse($validator->errors()), Response::HTTP_UNPROCESSABLE_ENTITY)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/Http/Requests/LoginRequest.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | public function rules(): array 29 | { 30 | return [ 31 | 'email' => 'required|email:rfc,dns|max:100', 32 | 'password' => 'required|max:30', 33 | ]; 34 | } 35 | 36 | 37 | /** 38 | * Get custom error messages for validation rules. 39 | * 40 | * @return array 41 | */ 42 | public function messages(): array 43 | { 44 | return [ 45 | 'email.required' => 'The email address field is required.', 46 | 'email.email' => 'The email address must be a valid email address', 47 | 'email.max' => 'The email address field may not be greater than 100 characters.', 48 | 'password.required' => 'The password field is required.', 49 | 'password.max' => 'The password field may not be greater than 30 characters.', 50 | ]; 51 | } 52 | 53 | 54 | protected function failedValidation(Validator $validator) 55 | { 56 | throw new HttpResponseException(response()->json($this->validationFailedResponse($validator->errors()), Response::HTTP_UNPROCESSABLE_ENTITY)); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/Http/Requests/RoleRequest.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | public function rules(): array 29 | { 30 | $rules = [ 31 | 'name' => 'required|string|max:30', 32 | 'status' => 'required|in:active,inactive,deleted', 33 | 'description' => 'required|string|max:100' 34 | ]; 35 | 36 | $requetMethod = $this->getMethod(); 37 | if ($requetMethod !== 'PUT' && $requetMethod !== 'PATCH') { 38 | $rules['name'] .= '|unique:roles'; 39 | } 40 | 41 | return $rules; 42 | } 43 | 44 | /** 45 | * Get custom error messages for validation rules. 46 | * 47 | * @return array 48 | */ 49 | public function messages(): array 50 | { 51 | return [ 52 | 'name.required' => 'The role name field is required.', 53 | 'name.string' => 'The role name field must be a string.', 54 | 'name.max' => 'The role name field may not be greater than 30 characters.', 55 | 'name.unique' => 'The role name has already been taken.', 56 | 'status.required' => 'The status field is required.', 57 | 'status.in' => 'The selected status is invalid.Use active or inactive', 58 | 'description.required' => 'The role description field is required.', 59 | 'description.string' => 'The role description field must be a string.', 60 | 'description.max' => 'The role description field may not be greater than 100 characters.', 61 | ]; 62 | } 63 | 64 | /** 65 | * Prepare the data for validation. 66 | * 67 | * @return void 68 | */ 69 | protected function prepareForValidation(): void 70 | { 71 | // Generate slug from the 'name' field and replace spaces with hyphens 72 | $this->merge([ 73 | 'slug' => Str::slug($this->name) 74 | ]); 75 | } 76 | 77 | /** 78 | * Handle a failed validation attempt. 79 | * 80 | * @param \Illuminate\Contracts\Validation\Validator $validator 81 | * @return void 82 | * 83 | * @throws Illuminate\Http\Exceptions\HttpResponseException 84 | */ 85 | 86 | protected function failedValidation(Validator $validator) 87 | { 88 | throw new HttpResponseException(response()->json($this->validationFailedResponse($validator->errors()), Response::HTTP_UNPROCESSABLE_ENTITY)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/Http/Requests/StoreUserRequest.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | public function rules(): array 29 | { 30 | return [ 31 | 'name' => 'required|max:100', 32 | 'email' => 'required|email:rfc,dns|max:100', 33 | 'password' => 'required|max:30', 34 | ]; 35 | } 36 | 37 | 38 | /** 39 | * Get custom error messages for validation rules. 40 | * 41 | * @return array 42 | */ 43 | public function messages(): array 44 | { 45 | return [ 46 | 'name.required' => 'The name field is required.', 47 | 'name.max' => 'The name field may not be greater than 100 characters.', 48 | 'email.required' => 'The email address field is required.', 49 | 'email.email' => 'The email address must be a valid email address', 50 | 'email.max' => 'The email address field may not be greater than 100 characters.', 51 | 'password.required' => 'The password field is required.', 52 | 'password.max' => 'The password field may not be greater than 30 characters.', 53 | ]; 54 | } 55 | 56 | 57 | protected function failedValidation(Validator $validator) 58 | { 59 | throw new HttpResponseException(response()->json($this->validationFailedResponse($validator->errors()), Response::HTTP_UNPROCESSABLE_ENTITY)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/Http/Resources/IPResource.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | public function toArray(Request $request): array 16 | { 17 | return [ 18 | 'id' => $this->id, 19 | 'ip_address' => $this->ip_address, 20 | 'status' => $this->status, 21 | 'ip_type' => $this->ip_type, 22 | 'remarks' => $this->remarks, 23 | 'created_at' => $this->created_at->format('d M Y h:i A'), 24 | 'updated_at' => $this->updated_at->format('d M Y h:i A'), 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Http/Resources/LoginResource.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | public function toArray(Request $request): array 22 | { 23 | return [ 24 | 'response' => [ 25 | 'status' => ApiStatus::SUCCESS, 26 | 'status_code' => Response::HTTP_OK, 27 | 'message' => Messages::LOGIN_SUCCESSFUL, 28 | 'data' => [ 29 | 'token_type' => 'bearer', 30 | 'access_token' => $this->resource, 31 | 'expires_in' => auth()->factory()->getTTL() * 60 32 | ], 33 | ] 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Http/Resources/RoleResource.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | public function toArray(Request $request): array 16 | { 17 | return [ 18 | 'id' => $this->id, 19 | 'name' => $this->name, 20 | 'slug' => $this->slug, 21 | 'status' => $this->status, 22 | 'description' => $this->description, 23 | 'created_at' => $this->created_at->format('d M Y h:i A'), 24 | 'updated_at' => $this->updated_at->format('d M Y h:i A'), 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Http/Resources/UserResource.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | public function toArray(Request $request): array 17 | { 18 | return [ 19 | 'id' => $this->id, 20 | 'name' => $this->name, 21 | 'email' => $this->email, 22 | 'created_at' => $this->created_at->format('d M Y h:i A'), 23 | 'updated_at' => $this->updated_at->format('d M Y h:i A'), 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Models/IPList.php: -------------------------------------------------------------------------------- 1 | 'ip_address', 'status' => 'status', 'ip_type' => 'ip_type']; 19 | } 20 | -------------------------------------------------------------------------------- /app/Models/Role.php: -------------------------------------------------------------------------------- 1 | 'slug', 'status' => 'status', 'name' => 'name']; 19 | 20 | public static $isDataFilterAuthorizationEnabled = false; 21 | public static $isEnableResourceOwnerCheck = false; 22 | } 23 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | protected $fillable = [ 19 | 'name', 20 | 'email', 21 | 'password', 22 | ]; 23 | 24 | /** 25 | * The attributes that should be hidden for serialization. 26 | * 27 | * @var array 28 | */ 29 | protected $hidden = [ 30 | 'password', 31 | 'remember_token', 32 | ]; 33 | 34 | /** 35 | * Get the attributes that should be cast. 36 | * 37 | * @return array 38 | */ 39 | protected function casts(): array 40 | { 41 | return [ 42 | 'email_verified_at' => 'datetime', 43 | 'password' => 'hashed', 44 | ]; 45 | } 46 | 47 | /** 48 | * Get the identifier that will be stored in the subject claim of the JWT. 49 | * 50 | * @return mixed 51 | */ 52 | public function getJWTIdentifier() 53 | { 54 | return $this->getKey(); 55 | } 56 | 57 | /** 58 | * Return a key value array, containing any custom claims to be added to the JWT. 59 | * 60 | * @return array 61 | */ 62 | public function getJWTCustomClaims() 63 | { 64 | return []; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | hideSensitiveRequestDetails(); 20 | 21 | $isLocal = $this->app->environment('local'); 22 | 23 | Telescope::filter(function (IncomingEntry $entry) use ($isLocal) { 24 | return $isLocal || 25 | $entry->isReportableException() || 26 | $entry->isFailedRequest() || 27 | $entry->isFailedJob() || 28 | $entry->isScheduledTask() || 29 | $entry->hasMonitoredTag(); 30 | }); 31 | } 32 | 33 | /** 34 | * Prevent sensitive request details from being logged by Telescope. 35 | */ 36 | protected function hideSensitiveRequestDetails(): void 37 | { 38 | if ($this->app->environment('local')) { 39 | return; 40 | } 41 | 42 | Telescope::hideRequestParameters(['_token']); 43 | 44 | Telescope::hideRequestHeaders([ 45 | 'cookie', 46 | 'x-csrf-token', 47 | 'x-xsrf-token', 48 | ]); 49 | } 50 | 51 | /** 52 | * Register the Telescope gate. 53 | * 54 | * This gate determines who can access Telescope in non-local environments. 55 | */ 56 | protected function gate(): void 57 | { 58 | Gate::define('viewTelescope', function ($user) { 59 | return in_array($user->email, [ 60 | // 61 | ]); 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/Services/CrudController.php: -------------------------------------------------------------------------------- 1 | model::filterRecords($request); 24 | return $this->resource::collection($items)->additional($this->preparedResponse('index')); 25 | } catch (Exception $e) { 26 | $this->recordException($e); 27 | return $this->serverErrorResponse($e); 28 | } 29 | } 30 | 31 | public function store() 32 | { 33 | try { 34 | $request = app($this->requestClass); 35 | $item = $this->model::create($request->all()); 36 | return (new $this->resource($item))->additional($this->preparedResponse('store')); 37 | } catch (QueryException $queryException) { 38 | return $this->queryExceptionResponse($queryException); 39 | } 40 | } 41 | 42 | public function show($id) 43 | { 44 | try { 45 | $item = $this->model::findOrFail($id); 46 | 47 | if (isset($this->model::$isEnableResourceOwnerCheck) && $this->model::$isEnableResourceOwnerCheck === true) { 48 | if (!$this->isOwner($item)) { 49 | return $this->forbiddenAccessResponse(); 50 | } 51 | } 52 | 53 | return (new $this->resource($item))->additional($this->preparedResponse('show')); 54 | } catch (ModelNotFoundException $modelException) { 55 | return $this->recordNotFoundResponse($modelException); 56 | } catch (Exception $e) { 57 | return $this->serverErrorResponse($e); 58 | } 59 | } 60 | 61 | public function update($id) 62 | { 63 | try { 64 | $request = app($this->requestClass); 65 | $item = $this->model::findOrFail($id); 66 | 67 | if (isset($this->model::$isEnableResourceOwnerCheck) && $this->model::$isEnableResourceOwnerCheck === true) { 68 | if (!$this->isOwner($item)) { 69 | return $this->forbiddenAccessResponse(); 70 | } 71 | } 72 | 73 | $item->update($request->all()); 74 | return (new $this->resource($item))->additional($this->preparedResponse('update')); 75 | } catch (ModelNotFoundException $modelException) { 76 | return $this->recordNotFoundResponse($modelException); 77 | } catch (QueryException $queryException) { 78 | return $this->queryExceptionResponse($queryException); 79 | } 80 | } 81 | 82 | public function destroy($id) 83 | { 84 | try { 85 | $item = $this->model::findOrFail($id); 86 | 87 | if (isset($this->model::$isEnableResourceOwnerCheck) && $this->model::$isEnableResourceOwnerCheck === true) { 88 | if (!$this->isOwner($item)) { 89 | return $this->forbiddenAccessResponse(); 90 | } 91 | } 92 | 93 | $item->status = 'inactive'; 94 | $item->save(); 95 | return (new $this->resource($item))->additional($this->preparedResponse('destroy')); 96 | } catch (ModelNotFoundException $modelException) { 97 | return $this->recordNotFoundResponse($modelException); 98 | } catch (QueryException $queryException) { 99 | return $this->queryExceptionResponse($queryException); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/Traits/ApiResponseFormatTrait.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'status' => $status, 19 | 'status_code' => $statusCode, 20 | 'message' => $message, 21 | ] 22 | ]; 23 | 24 | if ($data !== null) { 25 | $response['data'] = $data; 26 | } 27 | 28 | return response()->json($response, $statusCode); 29 | } 30 | 31 | private function validationFailedResponse($errorDetails = null) 32 | { 33 | $response = [ 34 | 'response' => [ 35 | 'status' => ApiStatus::ERROR, 36 | 'status_code' => Response::HTTP_UNPROCESSABLE_ENTITY, 37 | 'error' => [ 38 | 'message' => Messages::VALIDATION_FAILED, 39 | 'timestamp' => Carbon::now(), 40 | ], 41 | ] 42 | ]; 43 | if ($errorDetails !== null) { 44 | $response['response']['error']['details'] = $errorDetails; 45 | } 46 | return $response; 47 | } 48 | 49 | private function errorResponse($status, $statusCode, $message, $errorDetails = null) 50 | { 51 | $response = [ 52 | 'response' => [ 53 | 'status' => $status, 54 | 'status_code' => $statusCode, 55 | 'error' => [ 56 | 'message' => $message, 57 | 'timestamp' => Carbon::now(), 58 | ], 59 | ] 60 | ]; 61 | if ($errorDetails !== null) { 62 | $response['response']['error']['details'] = $errorDetails; 63 | } 64 | return $response; 65 | } 66 | 67 | 68 | private function recordNotFoundResponse($exception) 69 | { 70 | return $this->errorResponse(ApiStatus::ERROR, Response::HTTP_NOT_FOUND, Messages::NO_QUERY_RESULTS, 'Record id ' . $exception->getIds()[0] . ' is invalid'); 71 | } 72 | 73 | private function serverErrorResponse() 74 | { 75 | return $this->errorResponse(ApiStatus::ERROR, Response::HTTP_INTERNAL_SERVER_ERROR, Messages::INTERNAL_SERVER_ERROR_MESSAGE); 76 | } 77 | 78 | private function invalidIdDataTypeResponse() 79 | { 80 | return $this->errorResponse(ApiStatus::ERROR, Response::HTTP_BAD_REQUEST, Messages::NON_NUMERIC_ID, 'Please provide a numeric id'); 81 | } 82 | 83 | private function queryExceptionResponse($exception) 84 | { 85 | $errorCode = $exception->errorInfo[1]; 86 | if ($errorCode == MySQLError::ER_DUP_ENTRY) { 87 | return $this->jsonResponse(ApiStatus::ERROR, Response::HTTP_INTERNAL_SERVER_ERROR, $exception->errorInfo[2]); 88 | } 89 | } 90 | 91 | private function forbiddenAccessResponse() 92 | { 93 | return $this->errorResponse(ApiStatus::ERROR, Response::HTTP_FORBIDDEN, Messages::FORBIDDEN); 94 | } 95 | 96 | private function unauthorizedResponse() 97 | { 98 | return $this->errorResponse(ApiStatus::ERROR, Response::HTTP_UNAUTHORIZED, Messages::UNAUTHORIZED); 99 | } 100 | 101 | private function logoutResponse() 102 | { 103 | return $this->jsonResponse(ApiStatus::SUCCESS, Response::HTTP_OK, Messages::LOGGED_OUT_SUCCESSFULLY); 104 | } 105 | 106 | private function preparedResponse($actionName) 107 | { 108 | $actions = [ 109 | 'index' => [ApiStatus::SUCCESS, Response::HTTP_OK, Messages::RETRIEVED_SUCCESSFULLY], 110 | 'store' => [ApiStatus::SUCCESS, Response::HTTP_CREATED, Messages::CREATED_SUCCESSFULLY], 111 | 'show' => [ApiStatus::SUCCESS, Response::HTTP_OK, Messages::FETCHED_SUCCESSFULLY], 112 | 'update' => [ApiStatus::SUCCESS, Response::HTTP_OK, Messages::UPDATED_SUCCESSFULLY], 113 | 'destroy' => [ApiStatus::SUCCESS, Response::HTTP_OK, Messages::TRASHED_SUCCESSFULLY] 114 | ]; 115 | 116 | if (array_key_exists($actionName, $actions)) { 117 | return [ 118 | 'response' => [ 119 | 'status' => $actions[$actionName][0], 120 | 'status_code' => $actions[$actionName][1], 121 | 'message' => $actions[$actionName][2] 122 | ] 123 | ]; 124 | } 125 | } 126 | 127 | private function recordException($e) 128 | { 129 | Log::error($e->getMessage() . ' in file ' . $e->getFile() . ' at line ' . $e->getLine()); 130 | } 131 | 132 | private function JWTCustomResponse($message) 133 | { 134 | $response = [ 135 | 'response' => [ 136 | 'status' => ApiStatus::ERROR, 137 | 'status_code' => Response::HTTP_INTERNAL_SERVER_ERROR, 138 | 'error' => [ 139 | 'message' => $message, 140 | 'timestamp' => Carbon::now(), 141 | ], 142 | ] 143 | ]; 144 | 145 | return response()->json($response, Response::HTTP_INTERNAL_SERVER_ERROR); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /app/Traits/Filterable.php: -------------------------------------------------------------------------------- 1 | merge(['user_id' => Auth::id()]); 26 | } 27 | 28 | foreach ($conditions as $key => $column) { 29 | if ($request->filled($key)) { 30 | $query->where($column, $request->input($key)); 31 | } 32 | } 33 | 34 | $items = $query->get(); 35 | $totalItems = $items->count(); 36 | $perPage = self::query()->getModel()->getPerPage(); 37 | 38 | if ($totalItems > $perPage) { 39 | return $query->paginate($perPage); 40 | } else { 41 | return $items; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Traits/Ownerable.php: -------------------------------------------------------------------------------- 1 | {$key} === Auth::id(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Traits/ResponseFormatterTrait.php: -------------------------------------------------------------------------------- 1 | createBaseResponse( 10 | $this->getStatusType($responseDetails['status']), 11 | $responseDetails['status_code'], 12 | $responseDetails['message'] 13 | ); 14 | 15 | $response = $this->addOptionalData($response, $responseDetails['data']); 16 | $response = $this->addOptionalError($response, $responseDetails['error']); 17 | 18 | return $response; 19 | } 20 | 21 | private function createBaseResponse(string $statusType, int $statusCode, ?string $message): array 22 | { 23 | return [ 24 | 'response' => [ 25 | 'status' => $statusType, 26 | 'status_code' => $statusCode, 27 | 'message' => $message, 28 | ] 29 | ]; 30 | } 31 | 32 | private function addOptionalData(array $response, $data): array 33 | { 34 | if ($data !== null) { 35 | $response['data'] = $data; 36 | } 37 | return $response; 38 | } 39 | 40 | private function addOptionalError(array $response, $error): array 41 | { 42 | if ($error !== null) { 43 | $response['response']['error']['details'] = $error; 44 | } 45 | return $response; 46 | } 47 | 48 | private function getStatusType(string $status): string 49 | { 50 | return $status === 'success' ? 'success' : 'error'; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/Traits/ResponseHandlerTrait.php: -------------------------------------------------------------------------------- 1 | 'success', 15 | 'status_code' => 200, 16 | 'message' => $message, 17 | 'data' => $data, 18 | 'error' => null, 19 | ]; 20 | 21 | return $this->formatResponse($responseDetails); 22 | } 23 | 24 | public function errorResponse(string $message, int $statusCode = 400, array $errorDetails = null): array 25 | { 26 | $responseDetails = [ 27 | 'status' => 'error', 28 | 'status_code' => $statusCode, 29 | 'message' => $message, 30 | 'data' => null, 31 | 'error' => $errorDetails, 32 | ]; 33 | 34 | return $this->formatResponse($responseDetails); 35 | } 36 | 37 | public function validationFailedResponse(array $errorDetails = null): array 38 | { 39 | return $this->errorResponse('Validation failed', 422, $errorDetails); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Utils/APIResponse.php: -------------------------------------------------------------------------------- 1 | Messages::RESOURCE_NOT_FOUND, 17 | Response::HTTP_METHOD_NOT_ALLOWED => Messages::METHOD_NOT_ALLOWED_MSG, 18 | Response::HTTP_INTERNAL_SERVER_ERROR => Messages::INTERNAL_SERVER_ERROR_MESSAGE, 19 | Response::HTTP_NOT_ACCEPTABLE => Messages::NOT_ACCEPTABLE_MSG, 20 | Response::HTTP_TOO_MANY_REQUESTS => Messages::TOO_MANY_ATTEMPT, 21 | ]; 22 | 23 | /** 24 | * Convert the default Laravel web response to a formatted API response. 25 | * Handles HTTP error status codes such as 404, 405, 406, 500, 429, etc. 26 | * 27 | */ 28 | private static function prepareErrorResponse(int $statusCode): JsonResponse 29 | { 30 | $message = self::$errorMessages[$statusCode] ?? 'Unknown Error'; 31 | 32 | $response = [ 33 | 'response' => [ 34 | 'status' => self::DEFAULT_ERROR_STATUS, 35 | 'status_code' => $statusCode, 36 | 'error' => [ 37 | 'message' => $message, 38 | 'timestamp' => Carbon::now(), 39 | ], 40 | ] 41 | ]; 42 | 43 | return new JsonResponse($response, $statusCode); 44 | } 45 | 46 | /** 47 | * Show an error response for the given HTTP status code and message. 48 | * 49 | */ 50 | public static function showErrorResponse(int $statusCode): JsonResponse 51 | { 52 | return self::prepareErrorResponse($statusCode); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | handleCommand(new ArgvInput); 14 | 15 | exit($status); 16 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | withRouting( 21 | web: __DIR__ . '/../routes/web.php', 22 | api: __DIR__ . '/../routes/api.php', 23 | commands: __DIR__ . '/../routes/console.php', 24 | health: '/up', 25 | ) 26 | ->withMiddleware(function (Middleware $middleware) { 27 | $middleware->appendToGroup('ip-whitelist', [IPAuthorizationMiddleware::class,]); 28 | $middleware->appendToGroup('jwt-verify', [JWTTokenMiddleware::class,]); 29 | }) 30 | ->withExceptions(function (Exceptions $exceptions) { 31 | 32 | # Handles cases where a requested resource is not found. 33 | $exceptions->render(function (NotFoundHttpException $e, Request $request) { 34 | if ($request->is('api/*')) { 35 | return APIResponse::showErrorResponse(Response::HTTP_NOT_FOUND); 36 | } 37 | }); 38 | 39 | # Handles instances where the request method is not allowed for the specified route. 40 | $exceptions->render(function (MethodNotAllowedHttpException $e, Request $request) { 41 | if ($request->is('api/*')) { 42 | return APIResponse::showErrorResponse(Response::HTTP_METHOD_NOT_ALLOWED); 43 | } 44 | }); 45 | 46 | # Catches general HTTP-related exceptions 47 | $exceptions->render(function (HttpException $e, Request $request) { 48 | if ($request->is('api/*')) { 49 | return APIResponse::showErrorResponse(Response::HTTP_INTERNAL_SERVER_ERROR); 50 | } 51 | }); 52 | 53 | # Captures exceptions related to database queries 54 | $exceptions->render(function (QueryException $e, Request $request) { 55 | if ($request->is('api/*')) { 56 | return APIResponse::showErrorResponse(Response::HTTP_INTERNAL_SERVER_ERROR); 57 | } 58 | }); 59 | 60 | # Custom handling for NotAcceptableHttpException rendering. 61 | $exceptions->render(function (NotAcceptableHttpException $e, Request $request) { 62 | if ($request->is('api/*')) { 63 | return APIResponse::showErrorResponse(Response::HTTP_NOT_ACCEPTABLE); 64 | } 65 | }); 66 | 67 | # Customizes the rendering of TooManyRequestsHttpException. 68 | $exceptions->render(function (TooManyRequestsHttpException $e, Request $request) { 69 | if ($request->is('api/*')) { 70 | return APIResponse::showErrorResponse(Response::HTTP_TOO_MANY_REQUESTS); 71 | } 72 | }); 73 | })->create(); 74 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /bootstrap/providers.php: -------------------------------------------------------------------------------- 1 | env('APP_NAME', 'Laravel'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Application Environment 21 | |-------------------------------------------------------------------------- 22 | | 23 | | This value determines the "environment" your application is currently 24 | | running in. This may determine how you prefer to configure various 25 | | services the application utilizes. Set this in your ".env" file. 26 | | 27 | */ 28 | 29 | 'env' => env('APP_ENV', 'production'), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Application Debug Mode 34 | |-------------------------------------------------------------------------- 35 | | 36 | | When your application is in debug mode, detailed error messages with 37 | | stack traces will be shown on every error that occurs within your 38 | | application. If disabled, a simple generic error page is shown. 39 | | 40 | */ 41 | 42 | 'debug' => (bool) env('APP_DEBUG', false), 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Application URL 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This URL is used by the console to properly generate URLs when using 50 | | the Artisan command line tool. You should set this to the root of 51 | | the application so that it's available within Artisan commands. 52 | | 53 | */ 54 | 55 | 'url' => env('APP_URL', 'http://localhost'), 56 | 57 | /* 58 | |-------------------------------------------------------------------------- 59 | | Application Timezone 60 | |-------------------------------------------------------------------------- 61 | | 62 | | Here you may specify the default timezone for your application, which 63 | | will be used by the PHP date and date-time functions. The timezone 64 | | is set to "UTC" by default as it is suitable for most use cases. 65 | | 66 | */ 67 | 68 | 'timezone' => env('APP_TIMEZONE', 'UTC'), 69 | 70 | /* 71 | |-------------------------------------------------------------------------- 72 | | Application Locale Configuration 73 | |-------------------------------------------------------------------------- 74 | | 75 | | The application locale determines the default locale that will be used 76 | | by Laravel's translation / localization methods. This option can be 77 | | set to any locale for which you plan to have translation strings. 78 | | 79 | */ 80 | 81 | 'locale' => env('APP_LOCALE', 'en'), 82 | 83 | 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), 84 | 85 | 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), 86 | 87 | /* 88 | |-------------------------------------------------------------------------- 89 | | Encryption Key 90 | |-------------------------------------------------------------------------- 91 | | 92 | | This key is utilized by Laravel's encryption services and should be set 93 | | to a random, 32 character string to ensure that all encrypted values 94 | | are secure. You should do this prior to deploying the application. 95 | | 96 | */ 97 | 98 | 'cipher' => 'AES-256-CBC', 99 | 100 | 'key' => env('APP_KEY'), 101 | 102 | 'previous_keys' => [ 103 | ...array_filter( 104 | explode(',', env('APP_PREVIOUS_KEYS', '')) 105 | ), 106 | ], 107 | 108 | /* 109 | |-------------------------------------------------------------------------- 110 | | Maintenance Mode Driver 111 | |-------------------------------------------------------------------------- 112 | | 113 | | These configuration options determine the driver used to determine and 114 | | manage Laravel's "maintenance mode" status. The "cache" driver will 115 | | allow maintenance mode to be controlled across multiple machines. 116 | | 117 | | Supported drivers: "file", "cache" 118 | | 119 | */ 120 | 121 | 'maintenance' => [ 122 | 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), 123 | 'store' => env('APP_MAINTENANCE_STORE', 'database'), 124 | ], 125 | 126 | ]; 127 | -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'guard' => env('AUTH_GUARD', 'api'), //Default was web 18 | 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), 19 | ], 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Authentication Guards 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Next, you may define every authentication guard for your application. 27 | | Of course, a great default configuration has been defined for you 28 | | which utilizes session storage plus the Eloquent user provider. 29 | | 30 | | All authentication guards have a user provider, which defines how the 31 | | users are actually retrieved out of your database or other storage 32 | | system used by the application. Typically, Eloquent is utilized. 33 | | 34 | | Supported: "session" 35 | | 36 | */ 37 | 38 | 'guards' => [ 39 | 'web' => [ 40 | 'driver' => 'session', 41 | 'provider' => 'users', 42 | ], 43 | 44 | 'api' => [ 45 | 'driver' => 'jwt', 46 | 'provider' => 'users', 47 | ], 48 | ], 49 | 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | User Providers 53 | |-------------------------------------------------------------------------- 54 | | 55 | | All authentication guards have a user provider, which defines how the 56 | | users are actually retrieved out of your database or other storage 57 | | system used by the application. Typically, Eloquent is utilized. 58 | | 59 | | If you have multiple user tables or models you may configure multiple 60 | | providers to represent the model / table. These providers may then 61 | | be assigned to any extra authentication guards you have defined. 62 | | 63 | | Supported: "database", "eloquent" 64 | | 65 | */ 66 | 67 | 'providers' => [ 68 | 'users' => [ 69 | 'driver' => 'eloquent', 70 | 'model' => env('AUTH_MODEL', App\Models\User::class), 71 | ], 72 | 73 | // 'users' => [ 74 | // 'driver' => 'database', 75 | // 'table' => 'users', 76 | // ], 77 | ], 78 | 79 | /* 80 | |-------------------------------------------------------------------------- 81 | | Resetting Passwords 82 | |-------------------------------------------------------------------------- 83 | | 84 | | These configuration options specify the behavior of Laravel's password 85 | | reset functionality, including the table utilized for token storage 86 | | and the user provider that is invoked to actually retrieve users. 87 | | 88 | | The expiry time is the number of minutes that each reset token will be 89 | | considered valid. This security feature keeps tokens short-lived so 90 | | they have less time to be guessed. You may change this as needed. 91 | | 92 | | The throttle setting is the number of seconds a user must wait before 93 | | generating more password reset tokens. This prevents the user from 94 | | quickly generating a very large amount of password reset tokens. 95 | | 96 | */ 97 | 98 | 'passwords' => [ 99 | 'users' => [ 100 | 'provider' => 'users', 101 | 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), 102 | 'expire' => 60, 103 | 'throttle' => 60, 104 | ], 105 | ], 106 | 107 | /* 108 | |-------------------------------------------------------------------------- 109 | | Password Confirmation Timeout 110 | |-------------------------------------------------------------------------- 111 | | 112 | | Here you may define the amount of seconds before a password confirmation 113 | | window expires and users are asked to re-enter their password via the 114 | | confirmation screen. By default, the timeout lasts for three hours. 115 | | 116 | */ 117 | 118 | 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), 119 | 120 | ]; 121 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_STORE', 'database'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Cache Stores 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the cache "stores" for your application as 26 | | well as their drivers. You may even define multiple stores for the 27 | | same cache driver to group types of items stored in your caches. 28 | | 29 | | Supported drivers: "apc", "array", "database", "file", "memcached", 30 | | "redis", "dynamodb", "octane", "null" 31 | | 32 | */ 33 | 34 | 'stores' => [ 35 | 36 | 'array' => [ 37 | 'driver' => 'array', 38 | 'serialize' => false, 39 | ], 40 | 41 | 'database' => [ 42 | 'driver' => 'database', 43 | 'table' => env('DB_CACHE_TABLE', 'cache'), 44 | 'connection' => env('DB_CACHE_CONNECTION', null), 45 | 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION', null), 46 | ], 47 | 48 | 'file' => [ 49 | 'driver' => 'file', 50 | 'path' => storage_path('framework/cache/data'), 51 | 'lock_path' => storage_path('framework/cache/data'), 52 | ], 53 | 54 | 'memcached' => [ 55 | 'driver' => 'memcached', 56 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 57 | 'sasl' => [ 58 | env('MEMCACHED_USERNAME'), 59 | env('MEMCACHED_PASSWORD'), 60 | ], 61 | 'options' => [ 62 | // Memcached::OPT_CONNECT_TIMEOUT => 2000, 63 | ], 64 | 'servers' => [ 65 | [ 66 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 67 | 'port' => env('MEMCACHED_PORT', 11211), 68 | 'weight' => 100, 69 | ], 70 | ], 71 | ], 72 | 73 | 'redis' => [ 74 | 'driver' => 'redis', 75 | 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), 76 | 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), 77 | ], 78 | 79 | 'dynamodb' => [ 80 | 'driver' => 'dynamodb', 81 | 'key' => env('AWS_ACCESS_KEY_ID'), 82 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 83 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 84 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 85 | 'endpoint' => env('DYNAMODB_ENDPOINT'), 86 | ], 87 | 88 | 'octane' => [ 89 | 'driver' => 'octane', 90 | ], 91 | 92 | ], 93 | 94 | /* 95 | |-------------------------------------------------------------------------- 96 | | Cache Key Prefix 97 | |-------------------------------------------------------------------------- 98 | | 99 | | When utilizing the APC, database, memcached, Redis, and DynamoDB cache 100 | | stores, there might be other applications using the same cache. For 101 | | that reason, you may prefix every cache key to avoid collisions. 102 | | 103 | */ 104 | 105 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), 106 | 107 | ]; 108 | -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | env('DB_CONNECTION', 'sqlite'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Database Connections 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Below are all of the database connections defined for your application. 27 | | An example configuration is provided for each database system which 28 | | is supported by Laravel. You're free to add / remove connections. 29 | | 30 | */ 31 | 32 | 'connections' => [ 33 | 34 | 'sqlite' => [ 35 | 'driver' => 'sqlite', 36 | 'url' => env('DB_URL'), 37 | 'database' => env('DB_DATABASE', database_path('database.sqlite')), 38 | 'prefix' => '', 39 | 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), 40 | ], 41 | 42 | 'mysql' => [ 43 | 'driver' => 'mysql', 44 | 'url' => env('DB_URL'), 45 | 'host' => env('DB_HOST', '127.0.0.1'), 46 | 'port' => env('DB_PORT', '3306'), 47 | 'database' => env('DB_DATABASE', 'laravel'), 48 | 'username' => env('DB_USERNAME', 'root'), 49 | 'password' => env('DB_PASSWORD', ''), 50 | 'unix_socket' => env('DB_SOCKET', ''), 51 | 'charset' => env('DB_CHARSET', 'utf8mb4'), 52 | 'collation' => env('DB_COLLATION', 'utf8mb4_general_ci'), 53 | 'prefix' => '', 54 | 'prefix_indexes' => true, 55 | 'strict' => true, 56 | 'engine' => null, 57 | 'options' => extension_loaded('pdo_mysql') ? array_filter([ 58 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), 59 | ]) : [], 60 | ], 61 | 62 | 'mariadb' => [ 63 | 'driver' => 'mariadb', 64 | 'url' => env('DB_URL'), 65 | 'host' => env('DB_HOST', '127.0.0.1'), 66 | 'port' => env('DB_PORT', '3306'), 67 | 'database' => env('DB_DATABASE', 'laravel'), 68 | 'username' => env('DB_USERNAME', 'root'), 69 | 'password' => env('DB_PASSWORD', ''), 70 | 'unix_socket' => env('DB_SOCKET', ''), 71 | 'charset' => env('DB_CHARSET', 'utf8mb4'), 72 | 'collation' => env('DB_COLLATION', 'utf8mb4_uca1400_ai_ci'), 73 | 'prefix' => '', 74 | 'prefix_indexes' => true, 75 | 'strict' => true, 76 | 'engine' => null, 77 | 'options' => extension_loaded('pdo_mysql') ? array_filter([ 78 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), 79 | ]) : [], 80 | ], 81 | 82 | 'pgsql' => [ 83 | 'driver' => 'pgsql', 84 | 'url' => env('DB_URL'), 85 | 'host' => env('DB_HOST', '127.0.0.1'), 86 | 'port' => env('DB_PORT', '5432'), 87 | 'database' => env('DB_DATABASE', 'laravel'), 88 | 'username' => env('DB_USERNAME', 'root'), 89 | 'password' => env('DB_PASSWORD', ''), 90 | 'charset' => env('DB_CHARSET', 'utf8'), 91 | 'prefix' => '', 92 | 'prefix_indexes' => true, 93 | 'search_path' => 'public', 94 | 'sslmode' => 'prefer', 95 | ], 96 | 97 | 'sqlsrv' => [ 98 | 'driver' => 'sqlsrv', 99 | 'url' => env('DB_URL'), 100 | 'host' => env('DB_HOST', 'localhost'), 101 | 'port' => env('DB_PORT', '1433'), 102 | 'database' => env('DB_DATABASE', 'laravel'), 103 | 'username' => env('DB_USERNAME', 'root'), 104 | 'password' => env('DB_PASSWORD', ''), 105 | 'charset' => env('DB_CHARSET', 'utf8'), 106 | 'prefix' => '', 107 | 'prefix_indexes' => true, 108 | // 'encrypt' => env('DB_ENCRYPT', 'yes'), 109 | // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), 110 | ], 111 | 112 | ], 113 | 114 | /* 115 | |-------------------------------------------------------------------------- 116 | | Migration Repository Table 117 | |-------------------------------------------------------------------------- 118 | | 119 | | This table keeps track of all the migrations that have already run for 120 | | your application. Using this information, we can determine which of 121 | | the migrations on disk haven't actually been run on the database. 122 | | 123 | */ 124 | 125 | 'migrations' => [ 126 | 'table' => 'migrations', 127 | 'update_date_on_publish' => true, 128 | ], 129 | 130 | /* 131 | |-------------------------------------------------------------------------- 132 | | Redis Databases 133 | |-------------------------------------------------------------------------- 134 | | 135 | | Redis is an open source, fast, and advanced key-value store that also 136 | | provides a richer body of commands than a typical key-value system 137 | | such as Memcached. You may define your connection settings here. 138 | | 139 | */ 140 | 141 | 'redis' => [ 142 | 143 | 'client' => env('REDIS_CLIENT', 'phpredis'), 144 | 145 | 'options' => [ 146 | 'cluster' => env('REDIS_CLUSTER', 'redis'), 147 | 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), 148 | ], 149 | 150 | 'default' => [ 151 | 'url' => env('REDIS_URL'), 152 | 'host' => env('REDIS_HOST', '127.0.0.1'), 153 | 'username' => env('REDIS_USERNAME'), 154 | 'password' => env('REDIS_PASSWORD'), 155 | 'port' => env('REDIS_PORT', '6379'), 156 | 'database' => env('REDIS_DB', '0'), 157 | ], 158 | 159 | 'cache' => [ 160 | 'url' => env('REDIS_URL'), 161 | 'host' => env('REDIS_HOST', '127.0.0.1'), 162 | 'username' => env('REDIS_USERNAME'), 163 | 'password' => env('REDIS_PASSWORD'), 164 | 'port' => env('REDIS_PORT', '6379'), 165 | 'database' => env('REDIS_CACHE_DB', '1'), 166 | ], 167 | 168 | ], 169 | 170 | ]; 171 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DISK', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Filesystem Disks 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Below you may configure as many filesystem disks as necessary, and you 24 | | may even configure multiple disks for the same driver. Examples for 25 | | most supported storage drivers are configured here for reference. 26 | | 27 | | Supported Drivers: "local", "ftp", "sftp", "s3" 28 | | 29 | */ 30 | 31 | 'disks' => [ 32 | 33 | 'local' => [ 34 | 'driver' => 'local', 35 | 'root' => storage_path('app'), 36 | 'throw' => false, 37 | ], 38 | 39 | 'public' => [ 40 | 'driver' => 'local', 41 | 'root' => storage_path('app/public'), 42 | 'url' => env('APP_URL').'/storage', 43 | 'visibility' => 'public', 44 | 'throw' => false, 45 | ], 46 | 47 | 's3' => [ 48 | 'driver' => 's3', 49 | 'key' => env('AWS_ACCESS_KEY_ID'), 50 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 51 | 'region' => env('AWS_DEFAULT_REGION'), 52 | 'bucket' => env('AWS_BUCKET'), 53 | 'url' => env('AWS_URL'), 54 | 'endpoint' => env('AWS_ENDPOINT'), 55 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 56 | 'throw' => false, 57 | ], 58 | 59 | ], 60 | 61 | /* 62 | |-------------------------------------------------------------------------- 63 | | Symbolic Links 64 | |-------------------------------------------------------------------------- 65 | | 66 | | Here you may configure the symbolic links that will be created when the 67 | | `storage:link` Artisan command is executed. The array keys should be 68 | | the locations of the links and the values should be their targets. 69 | | 70 | */ 71 | 72 | 'links' => [ 73 | public_path('storage') => storage_path('app/public'), 74 | ], 75 | 76 | ]; 77 | -------------------------------------------------------------------------------- /config/jwt.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | return [ 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | JWT Authentication Secret 17 | |-------------------------------------------------------------------------- 18 | | 19 | | Don't forget to set this in your .env file, as it will be used to sign 20 | | your tokens. A helper command is provided for this: 21 | | `php artisan jwt:secret` 22 | | 23 | | Note: This will be used for Symmetric algorithms only (HMAC), 24 | | since RSA and ECDSA use a private/public key combo (See below). 25 | | 26 | */ 27 | 28 | 'secret' => env('JWT_SECRET'), 29 | 30 | /* 31 | |-------------------------------------------------------------------------- 32 | | JWT Authentication Keys 33 | |-------------------------------------------------------------------------- 34 | | 35 | | The algorithm you are using, will determine whether your tokens are 36 | | signed with a random string (defined in `JWT_SECRET`) or using the 37 | | following public & private keys. 38 | | 39 | | Symmetric Algorithms: 40 | | HS256, HS384 & HS512 will use `JWT_SECRET`. 41 | | 42 | | Asymmetric Algorithms: 43 | | RS256, RS384 & RS512 / ES256, ES384 & ES512 will use the keys below. 44 | | 45 | */ 46 | 47 | 'keys' => [ 48 | 49 | /* 50 | |-------------------------------------------------------------------------- 51 | | Public Key 52 | |-------------------------------------------------------------------------- 53 | | 54 | | A path or resource to your public key. 55 | | 56 | | E.g. 'file://path/to/public/key' 57 | | 58 | */ 59 | 60 | 'public' => env('JWT_PUBLIC_KEY'), 61 | 62 | /* 63 | |-------------------------------------------------------------------------- 64 | | Private Key 65 | |-------------------------------------------------------------------------- 66 | | 67 | | A path or resource to your private key. 68 | | 69 | | E.g. 'file://path/to/private/key' 70 | | 71 | */ 72 | 73 | 'private' => env('JWT_PRIVATE_KEY'), 74 | 75 | /* 76 | |-------------------------------------------------------------------------- 77 | | Passphrase 78 | |-------------------------------------------------------------------------- 79 | | 80 | | The passphrase for your private key. Can be null if none set. 81 | | 82 | */ 83 | 84 | 'passphrase' => env('JWT_PASSPHRASE'), 85 | 86 | ], 87 | 88 | /* 89 | |-------------------------------------------------------------------------- 90 | | JWT time to live 91 | |-------------------------------------------------------------------------- 92 | | 93 | | Specify the length of time (in minutes) that the token will be valid for. 94 | | Defaults to 1 hour. 95 | | 96 | | You can also set this to null, to yield a never expiring token. 97 | | Some people may want this behaviour for e.g. a mobile app. 98 | | This is not particularly recommended, so make sure you have appropriate 99 | | systems in place to revoke the token if necessary. 100 | | Notice: If you set this to null you should remove 'exp' element from 'required_claims' list. 101 | | 102 | */ 103 | 104 | 'ttl' => env('JWT_TTL', 60), 105 | 106 | /* 107 | |-------------------------------------------------------------------------- 108 | | Refresh time to live 109 | |-------------------------------------------------------------------------- 110 | | 111 | | Specify the length of time (in minutes) that the token can be refreshed 112 | | within. I.E. The user can refresh their token within a 2 week window of 113 | | the original token being created until they must re-authenticate. 114 | | Defaults to 2 weeks. 115 | | 116 | | You can also set this to null, to yield an infinite refresh time. 117 | | Some may want this instead of never expiring tokens for e.g. a mobile app. 118 | | This is not particularly recommended, so make sure you have appropriate 119 | | systems in place to revoke the token if necessary. 120 | | 121 | */ 122 | 123 | 'refresh_ttl' => env('JWT_REFRESH_TTL', 20160), 124 | 125 | /* 126 | |-------------------------------------------------------------------------- 127 | | JWT hashing algorithm 128 | |-------------------------------------------------------------------------- 129 | | 130 | | Specify the hashing algorithm that will be used to sign the token. 131 | | 132 | */ 133 | 134 | 'algo' => env('JWT_ALGO', Tymon\JWTAuth\Providers\JWT\Provider::ALGO_HS256), 135 | 136 | /* 137 | |-------------------------------------------------------------------------- 138 | | Required Claims 139 | |-------------------------------------------------------------------------- 140 | | 141 | | Specify the required claims that must exist in any token. 142 | | A TokenInvalidException will be thrown if any of these claims are not 143 | | present in the payload. 144 | | 145 | */ 146 | 147 | 'required_claims' => [ 148 | 'iss', 149 | 'iat', 150 | 'exp', 151 | 'nbf', 152 | 'sub', 153 | 'jti', 154 | ], 155 | 156 | /* 157 | |-------------------------------------------------------------------------- 158 | | Persistent Claims 159 | |-------------------------------------------------------------------------- 160 | | 161 | | Specify the claim keys to be persisted when refreshing a token. 162 | | `sub` and `iat` will automatically be persisted, in 163 | | addition to the these claims. 164 | | 165 | | Note: If a claim does not exist then it will be ignored. 166 | | 167 | */ 168 | 169 | 'persistent_claims' => [ 170 | // 'foo', 171 | // 'bar', 172 | ], 173 | 174 | /* 175 | |-------------------------------------------------------------------------- 176 | | Lock Subject 177 | |-------------------------------------------------------------------------- 178 | | 179 | | This will determine whether a `prv` claim is automatically added to 180 | | the token. The purpose of this is to ensure that if you have multiple 181 | | authentication models e.g. `App\User` & `App\OtherPerson`, then we 182 | | should prevent one authentication request from impersonating another, 183 | | if 2 tokens happen to have the same id across the 2 different models. 184 | | 185 | | Under specific circumstances, you may want to disable this behaviour 186 | | e.g. if you only have one authentication model, then you would save 187 | | a little on token size. 188 | | 189 | */ 190 | 191 | 'lock_subject' => true, 192 | 193 | /* 194 | |-------------------------------------------------------------------------- 195 | | Leeway 196 | |-------------------------------------------------------------------------- 197 | | 198 | | This property gives the jwt timestamp claims some "leeway". 199 | | Meaning that if you have any unavoidable slight clock skew on 200 | | any of your servers then this will afford you some level of cushioning. 201 | | 202 | | This applies to the claims `iat`, `nbf` and `exp`. 203 | | 204 | | Specify in seconds - only if you know you need it. 205 | | 206 | */ 207 | 208 | 'leeway' => env('JWT_LEEWAY', 0), 209 | 210 | /* 211 | |-------------------------------------------------------------------------- 212 | | Blacklist Enabled 213 | |-------------------------------------------------------------------------- 214 | | 215 | | In order to invalidate tokens, you must have the blacklist enabled. 216 | | If you do not want or need this functionality, then set this to false. 217 | | 218 | */ 219 | 220 | 'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true), 221 | 222 | /* 223 | | ------------------------------------------------------------------------- 224 | | Blacklist Grace Period 225 | | ------------------------------------------------------------------------- 226 | | 227 | | When multiple concurrent requests are made with the same JWT, 228 | | it is possible that some of them fail, due to token regeneration 229 | | on every request. 230 | | 231 | | Set grace period in seconds to prevent parallel request failure. 232 | | 233 | */ 234 | 235 | 'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0), 236 | 237 | /* 238 | |-------------------------------------------------------------------------- 239 | | Cookies encryption 240 | |-------------------------------------------------------------------------- 241 | | 242 | | By default Laravel encrypt cookies for security reason. 243 | | If you decide to not decrypt cookies, you will have to configure Laravel 244 | | to not encrypt your cookie token by adding its name into the $except 245 | | array available in the middleware "EncryptCookies" provided by Laravel. 246 | | see https://laravel.com/docs/master/responses#cookies-and-encryption 247 | | for details. 248 | | 249 | | Set it to true if you want to decrypt cookies. 250 | | 251 | */ 252 | 253 | 'decrypt_cookies' => false, 254 | 255 | /* 256 | |-------------------------------------------------------------------------- 257 | | Providers 258 | |-------------------------------------------------------------------------- 259 | | 260 | | Specify the various providers used throughout the package. 261 | | 262 | */ 263 | 264 | 'providers' => [ 265 | 266 | /* 267 | |-------------------------------------------------------------------------- 268 | | JWT Provider 269 | |-------------------------------------------------------------------------- 270 | | 271 | | Specify the provider that is used to create and decode the tokens. 272 | | 273 | */ 274 | 275 | 'jwt' => Tymon\JWTAuth\Providers\JWT\Lcobucci::class, 276 | 277 | /* 278 | |-------------------------------------------------------------------------- 279 | | Authentication Provider 280 | |-------------------------------------------------------------------------- 281 | | 282 | | Specify the provider that is used to authenticate users. 283 | | 284 | */ 285 | 286 | 'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class, 287 | 288 | /* 289 | |-------------------------------------------------------------------------- 290 | | Storage Provider 291 | |-------------------------------------------------------------------------- 292 | | 293 | | Specify the provider that is used to store tokens in the blacklist. 294 | | 295 | */ 296 | 297 | 'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class, 298 | 299 | ], 300 | 301 | ]; 302 | -------------------------------------------------------------------------------- /config/logging.php: -------------------------------------------------------------------------------- 1 | env('LOG_CHANNEL', 'stack'), 22 | 23 | /* 24 | |-------------------------------------------------------------------------- 25 | | Deprecations Log Channel 26 | |-------------------------------------------------------------------------- 27 | | 28 | | This option controls the log channel that should be used to log warnings 29 | | regarding deprecated PHP and library features. This allows you to get 30 | | your application ready for upcoming major versions of dependencies. 31 | | 32 | */ 33 | 34 | 'deprecations' => [ 35 | 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), 36 | 'trace' => env('LOG_DEPRECATIONS_TRACE', false), 37 | ], 38 | 39 | /* 40 | |-------------------------------------------------------------------------- 41 | | Log Channels 42 | |-------------------------------------------------------------------------- 43 | | 44 | | Here you may configure the log channels for your application. Laravel 45 | | utilizes the Monolog PHP logging library, which includes a variety 46 | | of powerful log handlers and formatters that you're free to use. 47 | | 48 | | Available Drivers: "single", "daily", "slack", "syslog", 49 | | "errorlog", "monolog", "custom", "stack" 50 | | 51 | */ 52 | 53 | 'channels' => [ 54 | 55 | 'stack' => [ 56 | 'driver' => 'stack', 57 | 'channels' => explode(',', env('LOG_STACK', 'single')), 58 | 'ignore_exceptions' => false, 59 | ], 60 | 61 | 'single' => [ 62 | 'driver' => 'single', 63 | 'path' => storage_path('logs/laravel.log'), 64 | 'level' => env('LOG_LEVEL', 'debug'), 65 | 'replace_placeholders' => true, 66 | ], 67 | 68 | 'daily' => [ 69 | 'driver' => 'daily', 70 | 'path' => storage_path('logs/laravel.log'), 71 | 'level' => env('LOG_LEVEL', 'debug'), 72 | 'days' => env('LOG_DAILY_DAYS', 14), 73 | 'replace_placeholders' => true, 74 | ], 75 | 76 | 'slack' => [ 77 | 'driver' => 'slack', 78 | 'url' => env('LOG_SLACK_WEBHOOK_URL'), 79 | 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'), 80 | 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), 81 | 'level' => env('LOG_LEVEL', 'critical'), 82 | 'replace_placeholders' => true, 83 | ], 84 | 85 | 'papertrail' => [ 86 | 'driver' => 'monolog', 87 | 'level' => env('LOG_LEVEL', 'debug'), 88 | 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), 89 | 'handler_with' => [ 90 | 'host' => env('PAPERTRAIL_URL'), 91 | 'port' => env('PAPERTRAIL_PORT'), 92 | 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), 93 | ], 94 | 'processors' => [PsrLogMessageProcessor::class], 95 | ], 96 | 97 | 'stderr' => [ 98 | 'driver' => 'monolog', 99 | 'level' => env('LOG_LEVEL', 'debug'), 100 | 'handler' => StreamHandler::class, 101 | 'formatter' => env('LOG_STDERR_FORMATTER'), 102 | 'with' => [ 103 | 'stream' => 'php://stderr', 104 | ], 105 | 'processors' => [PsrLogMessageProcessor::class], 106 | ], 107 | 108 | 'syslog' => [ 109 | 'driver' => 'syslog', 110 | 'level' => env('LOG_LEVEL', 'debug'), 111 | 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER), 112 | 'replace_placeholders' => true, 113 | ], 114 | 115 | 'errorlog' => [ 116 | 'driver' => 'errorlog', 117 | 'level' => env('LOG_LEVEL', 'debug'), 118 | 'replace_placeholders' => true, 119 | ], 120 | 121 | 'null' => [ 122 | 'driver' => 'monolog', 123 | 'handler' => NullHandler::class, 124 | ], 125 | 126 | 'emergency' => [ 127 | 'path' => storage_path('logs/laravel.log'), 128 | ], 129 | 130 | ], 131 | 132 | ]; 133 | -------------------------------------------------------------------------------- /config/mail.php: -------------------------------------------------------------------------------- 1 | env('MAIL_MAILER', 'log'), 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Mailer Configurations 22 | |-------------------------------------------------------------------------- 23 | | 24 | | Here you may configure all of the mailers used by your application plus 25 | | their respective settings. Several examples have been configured for 26 | | you and you are free to add your own as your application requires. 27 | | 28 | | Laravel supports a variety of mail "transport" drivers that can be used 29 | | when delivering an email. You may specify which one you're using for 30 | | your mailers below. You may also add additional mailers if needed. 31 | | 32 | | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", 33 | | "postmark", "log", "array", "failover", "roundrobin" 34 | | 35 | */ 36 | 37 | 'mailers' => [ 38 | 39 | 'smtp' => [ 40 | 'transport' => 'smtp', 41 | 'url' => env('MAIL_URL'), 42 | 'host' => env('MAIL_HOST', '127.0.0.1'), 43 | 'port' => env('MAIL_PORT', 2525), 44 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 45 | 'username' => env('MAIL_USERNAME'), 46 | 'password' => env('MAIL_PASSWORD'), 47 | 'timeout' => null, 48 | 'local_domain' => env('MAIL_EHLO_DOMAIN'), 49 | ], 50 | 51 | 'ses' => [ 52 | 'transport' => 'ses', 53 | ], 54 | 55 | 'postmark' => [ 56 | 'transport' => 'postmark', 57 | // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), 58 | // 'client' => [ 59 | // 'timeout' => 5, 60 | // ], 61 | ], 62 | 63 | 'sendmail' => [ 64 | 'transport' => 'sendmail', 65 | 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), 66 | ], 67 | 68 | 'log' => [ 69 | 'transport' => 'log', 70 | 'channel' => env('MAIL_LOG_CHANNEL'), 71 | ], 72 | 73 | 'array' => [ 74 | 'transport' => 'array', 75 | ], 76 | 77 | 'failover' => [ 78 | 'transport' => 'failover', 79 | 'mailers' => [ 80 | 'smtp', 81 | 'log', 82 | ], 83 | ], 84 | 85 | ], 86 | 87 | /* 88 | |-------------------------------------------------------------------------- 89 | | Global "From" Address 90 | |-------------------------------------------------------------------------- 91 | | 92 | | You may wish for all emails sent by your application to be sent from 93 | | the same address. Here you may specify a name and address that is 94 | | used globally for all emails that are sent by your application. 95 | | 96 | */ 97 | 98 | 'from' => [ 99 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 100 | 'name' => env('MAIL_FROM_NAME', 'Example'), 101 | ], 102 | 103 | ]; 104 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_CONNECTION', 'database'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Queue Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure the connection options for every queue backend 24 | | used by your application. An example configuration is provided for 25 | | each backend supported by Laravel. You're also free to add more. 26 | | 27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'connection' => env('DB_QUEUE_CONNECTION', null), 40 | 'table' => env('DB_QUEUE_TABLE', 'jobs'), 41 | 'queue' => env('DB_QUEUE', 'default'), 42 | 'retry_after' => env('DB_QUEUE_RETRY_AFTER', 90), 43 | 'after_commit' => false, 44 | ], 45 | 46 | 'beanstalkd' => [ 47 | 'driver' => 'beanstalkd', 48 | 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), 49 | 'queue' => env('BEANSTALKD_QUEUE', 'default'), 50 | 'retry_after' => env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), 51 | 'block_for' => 0, 52 | 'after_commit' => false, 53 | ], 54 | 55 | 'sqs' => [ 56 | 'driver' => 'sqs', 57 | 'key' => env('AWS_ACCESS_KEY_ID'), 58 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 59 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 60 | 'queue' => env('SQS_QUEUE', 'default'), 61 | 'suffix' => env('SQS_SUFFIX'), 62 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 63 | 'after_commit' => false, 64 | ], 65 | 66 | 'redis' => [ 67 | 'driver' => 'redis', 68 | 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), 69 | 'queue' => env('REDIS_QUEUE', 'default'), 70 | 'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90), 71 | 'block_for' => null, 72 | 'after_commit' => false, 73 | ], 74 | 75 | ], 76 | 77 | /* 78 | |-------------------------------------------------------------------------- 79 | | Job Batching 80 | |-------------------------------------------------------------------------- 81 | | 82 | | The following options configure the database and table that store job 83 | | batching information. These options can be updated to any database 84 | | connection and table which has been defined by your application. 85 | | 86 | */ 87 | 88 | 'batching' => [ 89 | 'database' => env('DB_CONNECTION', 'sqlite'), 90 | 'table' => 'job_batches', 91 | ], 92 | 93 | /* 94 | |-------------------------------------------------------------------------- 95 | | Failed Queue Jobs 96 | |-------------------------------------------------------------------------- 97 | | 98 | | These options configure the behavior of failed queue job logging so you 99 | | can control how and where failed jobs are stored. Laravel ships with 100 | | support for storing failed jobs in a simple file or in a database. 101 | | 102 | | Supported drivers: "database-uuids", "dynamodb", "file", "null" 103 | | 104 | */ 105 | 106 | 'failed' => [ 107 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), 108 | 'database' => env('DB_CONNECTION', 'sqlite'), 109 | 'table' => 'failed_jobs', 110 | ], 111 | 112 | ]; 113 | -------------------------------------------------------------------------------- /config/sanctum.php: -------------------------------------------------------------------------------- 1 | explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( 19 | '%s%s', 20 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', 21 | Sanctum::currentApplicationUrlWithPort() 22 | ))), 23 | 24 | /* 25 | |-------------------------------------------------------------------------- 26 | | Sanctum Guards 27 | |-------------------------------------------------------------------------- 28 | | 29 | | This array contains the authentication guards that will be checked when 30 | | Sanctum is trying to authenticate a request. If none of these guards 31 | | are able to authenticate the request, Sanctum will use the bearer 32 | | token that's present on an incoming request for authentication. 33 | | 34 | */ 35 | 36 | 'guard' => ['web'], 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Expiration Minutes 41 | |-------------------------------------------------------------------------- 42 | | 43 | | This value controls the number of minutes until an issued token will be 44 | | considered expired. This will override any values set in the token's 45 | | "expires_at" attribute, but first-party sessions are not affected. 46 | | 47 | */ 48 | 49 | 'expiration' => null, 50 | 51 | /* 52 | |-------------------------------------------------------------------------- 53 | | Token Prefix 54 | |-------------------------------------------------------------------------- 55 | | 56 | | Sanctum can prefix new tokens in order to take advantage of numerous 57 | | security scanning initiatives maintained by open source platforms 58 | | that notify developers if they commit tokens into repositories. 59 | | 60 | | See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning 61 | | 62 | */ 63 | 64 | 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''), 65 | 66 | /* 67 | |-------------------------------------------------------------------------- 68 | | Sanctum Middleware 69 | |-------------------------------------------------------------------------- 70 | | 71 | | When authenticating your first-party SPA with Sanctum you may need to 72 | | customize some of the middleware Sanctum uses while processing the 73 | | request. You may change the middleware listed below as required. 74 | | 75 | */ 76 | 77 | 'middleware' => [ 78 | 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, 79 | 'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class, 80 | 'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, 81 | ], 82 | 83 | ]; 84 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'token' => env('POSTMARK_TOKEN'), 19 | ], 20 | 21 | 'ses' => [ 22 | 'key' => env('AWS_ACCESS_KEY_ID'), 23 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 24 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 25 | ], 26 | 27 | 'slack' => [ 28 | 'notifications' => [ 29 | 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), 30 | 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), 31 | ], 32 | ], 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /config/session.php: -------------------------------------------------------------------------------- 1 | env('SESSION_DRIVER', 'database'), 22 | 23 | /* 24 | |-------------------------------------------------------------------------- 25 | | Session Lifetime 26 | |-------------------------------------------------------------------------- 27 | | 28 | | Here you may specify the number of minutes that you wish the session 29 | | to be allowed to remain idle before it expires. If you want them 30 | | to expire immediately when the browser is closed then you may 31 | | indicate that via the expire_on_close configuration option. 32 | | 33 | */ 34 | 35 | 'lifetime' => env('SESSION_LIFETIME', 120), 36 | 37 | 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false), 38 | 39 | /* 40 | |-------------------------------------------------------------------------- 41 | | Session Encryption 42 | |-------------------------------------------------------------------------- 43 | | 44 | | This option allows you to easily specify that all of your session data 45 | | should be encrypted before it's stored. All encryption is performed 46 | | automatically by Laravel and you may use the session like normal. 47 | | 48 | */ 49 | 50 | 'encrypt' => env('SESSION_ENCRYPT', false), 51 | 52 | /* 53 | |-------------------------------------------------------------------------- 54 | | Session File Location 55 | |-------------------------------------------------------------------------- 56 | | 57 | | When utilizing the "file" session driver, the session files are placed 58 | | on disk. The default storage location is defined here; however, you 59 | | are free to provide another location where they should be stored. 60 | | 61 | */ 62 | 63 | 'files' => storage_path('framework/sessions'), 64 | 65 | /* 66 | |-------------------------------------------------------------------------- 67 | | Session Database Connection 68 | |-------------------------------------------------------------------------- 69 | | 70 | | When using the "database" or "redis" session drivers, you may specify a 71 | | connection that should be used to manage these sessions. This should 72 | | correspond to a connection in your database configuration options. 73 | | 74 | */ 75 | 76 | 'connection' => env('SESSION_CONNECTION'), 77 | 78 | /* 79 | |-------------------------------------------------------------------------- 80 | | Session Database Table 81 | |-------------------------------------------------------------------------- 82 | | 83 | | When using the "database" session driver, you may specify the table to 84 | | be used to store sessions. Of course, a sensible default is defined 85 | | for you; however, you're welcome to change this to another table. 86 | | 87 | */ 88 | 89 | 'table' => env('SESSION_TABLE', 'sessions'), 90 | 91 | /* 92 | |-------------------------------------------------------------------------- 93 | | Session Cache Store 94 | |-------------------------------------------------------------------------- 95 | | 96 | | When using one of the framework's cache driven session backends, you may 97 | | define the cache store which should be used to store the session data 98 | | between requests. This must match one of your defined cache stores. 99 | | 100 | | Affects: "apc", "dynamodb", "memcached", "redis" 101 | | 102 | */ 103 | 104 | 'store' => env('SESSION_STORE'), 105 | 106 | /* 107 | |-------------------------------------------------------------------------- 108 | | Session Sweeping Lottery 109 | |-------------------------------------------------------------------------- 110 | | 111 | | Some session drivers must manually sweep their storage location to get 112 | | rid of old sessions from storage. Here are the chances that it will 113 | | happen on a given request. By default, the odds are 2 out of 100. 114 | | 115 | */ 116 | 117 | 'lottery' => [2, 100], 118 | 119 | /* 120 | |-------------------------------------------------------------------------- 121 | | Session Cookie Name 122 | |-------------------------------------------------------------------------- 123 | | 124 | | Here you may change the name of the session cookie that is created by 125 | | the framework. Typically, you should not need to change this value 126 | | since doing so does not grant a meaningful security improvement. 127 | | 128 | | 129 | */ 130 | 131 | 'cookie' => env( 132 | 'SESSION_COOKIE', 133 | Str::slug(env('APP_NAME', 'laravel'), '_').'_session' 134 | ), 135 | 136 | /* 137 | |-------------------------------------------------------------------------- 138 | | Session Cookie Path 139 | |-------------------------------------------------------------------------- 140 | | 141 | | The session cookie path determines the path for which the cookie will 142 | | be regarded as available. Typically, this will be the root path of 143 | | your application, but you're free to change this when necessary. 144 | | 145 | */ 146 | 147 | 'path' => env('SESSION_PATH', '/'), 148 | 149 | /* 150 | |-------------------------------------------------------------------------- 151 | | Session Cookie Domain 152 | |-------------------------------------------------------------------------- 153 | | 154 | | This value determines the domain and subdomains the session cookie is 155 | | available to. By default, the cookie will be available to the root 156 | | domain and all subdomains. Typically, this shouldn't be changed. 157 | | 158 | */ 159 | 160 | 'domain' => env('SESSION_DOMAIN'), 161 | 162 | /* 163 | |-------------------------------------------------------------------------- 164 | | HTTPS Only Cookies 165 | |-------------------------------------------------------------------------- 166 | | 167 | | By setting this option to true, session cookies will only be sent back 168 | | to the server if the browser has a HTTPS connection. This will keep 169 | | the cookie from being sent to you when it can't be done securely. 170 | | 171 | */ 172 | 173 | 'secure' => env('SESSION_SECURE_COOKIE'), 174 | 175 | /* 176 | |-------------------------------------------------------------------------- 177 | | HTTP Access Only 178 | |-------------------------------------------------------------------------- 179 | | 180 | | Setting this value to true will prevent JavaScript from accessing the 181 | | value of the cookie and the cookie will only be accessible through 182 | | the HTTP protocol. It's unlikely you should disable this option. 183 | | 184 | */ 185 | 186 | 'http_only' => env('SESSION_HTTP_ONLY', true), 187 | 188 | /* 189 | |-------------------------------------------------------------------------- 190 | | Same-Site Cookies 191 | |-------------------------------------------------------------------------- 192 | | 193 | | This option determines how your cookies behave when cross-site requests 194 | | take place, and can be used to mitigate CSRF attacks. By default, we 195 | | will set this value to "lax" to permit secure cross-site requests. 196 | | 197 | | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value 198 | | 199 | | Supported: "lax", "strict", "none", null 200 | | 201 | */ 202 | 203 | 'same_site' => env('SESSION_SAME_SITE', 'lax'), 204 | 205 | /* 206 | |-------------------------------------------------------------------------- 207 | | Partitioned Cookies 208 | |-------------------------------------------------------------------------- 209 | | 210 | | Setting this value to true will tie the cookie to the top-level site for 211 | | a cross-site context. Partitioned cookies are accepted by the browser 212 | | when flagged "secure" and the Same-Site attribute is set to "none". 213 | | 214 | */ 215 | 216 | 'partitioned' => env('SESSION_PARTITIONED_COOKIE', false), 217 | 218 | ]; 219 | -------------------------------------------------------------------------------- /config/telescope.php: -------------------------------------------------------------------------------- 1 | env('TELESCOPE_DOMAIN'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Telescope Path 24 | |-------------------------------------------------------------------------- 25 | | 26 | | This is the URI path where Telescope will be accessible from. Feel free 27 | | to change this path to anything you like. Note that the URI will not 28 | | affect the paths of its internal API that aren't exposed to users. 29 | | 30 | */ 31 | 32 | 'path' => env('TELESCOPE_PATH', 'telescope'), 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Telescope Storage Driver 37 | |-------------------------------------------------------------------------- 38 | | 39 | | This configuration options determines the storage driver that will 40 | | be used to store Telescope's data. In addition, you may set any 41 | | custom options as needed by the particular driver you choose. 42 | | 43 | */ 44 | 45 | 'driver' => env('TELESCOPE_DRIVER', 'database'), 46 | 47 | 'storage' => [ 48 | 'database' => [ 49 | 'connection' => env('DB_CONNECTION', 'mysql'), 50 | 'chunk' => 1000, 51 | ], 52 | ], 53 | 54 | /* 55 | |-------------------------------------------------------------------------- 56 | | Telescope Master Switch 57 | |-------------------------------------------------------------------------- 58 | | 59 | | This option may be used to disable all Telescope watchers regardless 60 | | of their individual configuration, which simply provides a single 61 | | and convenient way to enable or disable Telescope data storage. 62 | | 63 | */ 64 | 65 | 'enabled' => env('TELESCOPE_ENABLED', true), 66 | 67 | /* 68 | |-------------------------------------------------------------------------- 69 | | Telescope Route Middleware 70 | |-------------------------------------------------------------------------- 71 | | 72 | | These middleware will be assigned to every Telescope route, giving you 73 | | the chance to add your own middleware to this list or change any of 74 | | the existing middleware. Or, you can simply stick with this list. 75 | | 76 | */ 77 | 78 | 'middleware' => [ 79 | 'web', 80 | Authorize::class, 81 | ], 82 | 83 | /* 84 | |-------------------------------------------------------------------------- 85 | | Allowed / Ignored Paths & Commands 86 | |-------------------------------------------------------------------------- 87 | | 88 | | The following array lists the URI paths and Artisan commands that will 89 | | not be watched by Telescope. In addition to this list, some Laravel 90 | | commands, like migrations and queue commands, are always ignored. 91 | | 92 | */ 93 | 94 | 'only_paths' => [ 95 | // 'api/*' 96 | ], 97 | 98 | 'ignore_paths' => [ 99 | 'livewire*', 100 | 'nova-api*', 101 | 'pulse*', 102 | ], 103 | 104 | 'ignore_commands' => [ 105 | // 106 | ], 107 | 108 | /* 109 | |-------------------------------------------------------------------------- 110 | | Telescope Watchers 111 | |-------------------------------------------------------------------------- 112 | | 113 | | The following array lists the "watchers" that will be registered with 114 | | Telescope. The watchers gather the application's profile data when 115 | | a request or task is executed. Feel free to customize this list. 116 | | 117 | */ 118 | 119 | 'watchers' => [ 120 | Watchers\BatchWatcher::class => env('TELESCOPE_BATCH_WATCHER', true), 121 | 122 | Watchers\CacheWatcher::class => [ 123 | 'enabled' => env('TELESCOPE_CACHE_WATCHER', true), 124 | 'hidden' => [], 125 | ], 126 | 127 | Watchers\ClientRequestWatcher::class => env('TELESCOPE_CLIENT_REQUEST_WATCHER', true), 128 | 129 | Watchers\CommandWatcher::class => [ 130 | 'enabled' => env('TELESCOPE_COMMAND_WATCHER', true), 131 | 'ignore' => [], 132 | ], 133 | 134 | Watchers\DumpWatcher::class => [ 135 | 'enabled' => env('TELESCOPE_DUMP_WATCHER', true), 136 | 'always' => env('TELESCOPE_DUMP_WATCHER_ALWAYS', false), 137 | ], 138 | 139 | Watchers\EventWatcher::class => [ 140 | 'enabled' => env('TELESCOPE_EVENT_WATCHER', true), 141 | 'ignore' => [], 142 | ], 143 | 144 | Watchers\ExceptionWatcher::class => env('TELESCOPE_EXCEPTION_WATCHER', true), 145 | 146 | Watchers\GateWatcher::class => [ 147 | 'enabled' => env('TELESCOPE_GATE_WATCHER', true), 148 | 'ignore_abilities' => [], 149 | 'ignore_packages' => true, 150 | 'ignore_paths' => [], 151 | ], 152 | 153 | Watchers\JobWatcher::class => env('TELESCOPE_JOB_WATCHER', true), 154 | 155 | Watchers\LogWatcher::class => [ 156 | 'enabled' => env('TELESCOPE_LOG_WATCHER', true), 157 | 'level' => 'error', 158 | ], 159 | 160 | Watchers\MailWatcher::class => env('TELESCOPE_MAIL_WATCHER', true), 161 | 162 | Watchers\ModelWatcher::class => [ 163 | 'enabled' => env('TELESCOPE_MODEL_WATCHER', true), 164 | 'events' => ['eloquent.*'], 165 | 'hydrations' => true, 166 | ], 167 | 168 | Watchers\NotificationWatcher::class => env('TELESCOPE_NOTIFICATION_WATCHER', true), 169 | 170 | Watchers\QueryWatcher::class => [ 171 | 'enabled' => env('TELESCOPE_QUERY_WATCHER', true), 172 | 'ignore_packages' => true, 173 | 'ignore_paths' => [], 174 | 'slow' => 100, 175 | ], 176 | 177 | Watchers\RedisWatcher::class => env('TELESCOPE_REDIS_WATCHER', true), 178 | 179 | Watchers\RequestWatcher::class => [ 180 | 'enabled' => env('TELESCOPE_REQUEST_WATCHER', true), 181 | 'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 64), 182 | 'ignore_http_methods' => [], 183 | 'ignore_status_codes' => [], 184 | ], 185 | 186 | Watchers\ScheduleWatcher::class => env('TELESCOPE_SCHEDULE_WATCHER', true), 187 | Watchers\ViewWatcher::class => env('TELESCOPE_VIEW_WATCHER', true), 188 | ], 189 | ]; 190 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class UserFactory extends Factory 13 | { 14 | /** 15 | * The current password being used by the factory. 16 | */ 17 | protected static ?string $password; 18 | 19 | /** 20 | * Define the model's default state. 21 | * 22 | * @return array 23 | */ 24 | public function definition(): array 25 | { 26 | return [ 27 | 'name' => fake()->name(), 28 | 'email' => fake()->unique()->safeEmail(), 29 | 'email_verified_at' => now(), 30 | 'password' => static::$password ??= Hash::make('password'), 31 | 'remember_token' => Str::random(10), 32 | ]; 33 | } 34 | 35 | /** 36 | * Indicate that the model's email address should be unverified. 37 | */ 38 | public function unverified(): static 39 | { 40 | return $this->state(fn (array $attributes) => [ 41 | 'email_verified_at' => null, 42 | ]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('email')->unique(); 18 | $table->timestamp('email_verified_at')->nullable(); 19 | $table->string('password'); 20 | $table->rememberToken(); 21 | $table->timestamps(); 22 | }); 23 | 24 | Schema::create('password_reset_tokens', function (Blueprint $table) { 25 | $table->string('email')->primary(); 26 | $table->string('token'); 27 | $table->timestamp('created_at')->nullable(); 28 | }); 29 | 30 | Schema::create('sessions', function (Blueprint $table) { 31 | $table->string('id')->primary(); 32 | $table->foreignId('user_id')->nullable()->index(); 33 | $table->string('ip_address', 45)->nullable(); 34 | $table->text('user_agent')->nullable(); 35 | $table->longText('payload'); 36 | $table->integer('last_activity')->index(); 37 | }); 38 | } 39 | 40 | /** 41 | * Reverse the migrations. 42 | */ 43 | public function down(): void 44 | { 45 | Schema::dropIfExists('users'); 46 | Schema::dropIfExists('password_reset_tokens'); 47 | Schema::dropIfExists('sessions'); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000001_create_cache_table.php: -------------------------------------------------------------------------------- 1 | string('key')->primary(); 16 | $table->mediumText('value'); 17 | $table->integer('expiration'); 18 | }); 19 | 20 | Schema::create('cache_locks', function (Blueprint $table) { 21 | $table->string('key')->primary(); 22 | $table->string('owner'); 23 | $table->integer('expiration'); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('cache'); 33 | Schema::dropIfExists('cache_locks'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000002_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('queue')->index(); 17 | $table->longText('payload'); 18 | $table->unsignedTinyInteger('attempts'); 19 | $table->unsignedInteger('reserved_at')->nullable(); 20 | $table->unsignedInteger('available_at'); 21 | $table->unsignedInteger('created_at'); 22 | }); 23 | 24 | Schema::create('job_batches', function (Blueprint $table) { 25 | $table->string('id')->primary(); 26 | $table->string('name'); 27 | $table->integer('total_jobs'); 28 | $table->integer('pending_jobs'); 29 | $table->integer('failed_jobs'); 30 | $table->longText('failed_job_ids'); 31 | $table->mediumText('options')->nullable(); 32 | $table->integer('cancelled_at')->nullable(); 33 | $table->integer('created_at'); 34 | $table->integer('finished_at')->nullable(); 35 | }); 36 | 37 | Schema::create('failed_jobs', function (Blueprint $table) { 38 | $table->id(); 39 | $table->string('uuid')->unique(); 40 | $table->text('connection'); 41 | $table->text('queue'); 42 | $table->longText('payload'); 43 | $table->longText('exception'); 44 | $table->timestamp('failed_at')->useCurrent(); 45 | }); 46 | } 47 | 48 | /** 49 | * Reverse the migrations. 50 | */ 51 | public function down(): void 52 | { 53 | Schema::dropIfExists('jobs'); 54 | Schema::dropIfExists('job_batches'); 55 | Schema::dropIfExists('failed_jobs'); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /database/migrations/2024_03_13_090743_create_personal_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->morphs('tokenable'); 17 | $table->string('name'); 18 | $table->string('token', 64)->unique(); 19 | $table->text('abilities')->nullable(); 20 | $table->timestamp('last_used_at')->nullable(); 21 | $table->timestamp('expires_at')->nullable(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('personal_access_tokens'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/migrations/2024_03_15_080025_create_roles_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name', 30); 17 | $table->string('slug', 30)->comment('system will generate from name'); 18 | $table->enum('status', ['active', 'inactive', 'deleted'])->default('active'); 19 | $table->string('description', 100)->nullable(); 20 | $table->unique('slug'); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('roles'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/migrations/2024_03_15_083732_create_ip_lists_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('ip_address', 40)->nullable(); 17 | $table->enum('status', ['Whitelist', 'Blocklist'])->default('Whitelist'); 18 | $table->enum('ip_type', ['IPv4', 'IPv6'])->default('IPv4'); 19 | $table->string('remarks', 50)->nullable(); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::dropIfExists('ip_lists'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /database/migrations/2024_03_16_072410_create_telescope_entries_table.php: -------------------------------------------------------------------------------- 1 | getConnection()); 23 | 24 | $schema->create('telescope_entries', function (Blueprint $table) { 25 | $table->bigIncrements('sequence'); 26 | $table->uuid('uuid'); 27 | $table->uuid('batch_id'); 28 | $table->string('family_hash')->nullable(); 29 | $table->boolean('should_display_on_index')->default(true); 30 | $table->string('type', 20); 31 | $table->longText('content'); 32 | $table->dateTime('created_at')->nullable(); 33 | 34 | $table->unique('uuid'); 35 | $table->index('batch_id'); 36 | $table->index('family_hash'); 37 | $table->index('created_at'); 38 | $table->index(['type', 'should_display_on_index']); 39 | }); 40 | 41 | $schema->create('telescope_entries_tags', function (Blueprint $table) { 42 | $table->uuid('entry_uuid'); 43 | $table->string('tag'); 44 | 45 | $table->primary(['entry_uuid', 'tag']); 46 | $table->index('tag'); 47 | 48 | $table->foreign('entry_uuid') 49 | ->references('uuid') 50 | ->on('telescope_entries') 51 | ->onDelete('cascade'); 52 | }); 53 | 54 | $schema->create('telescope_monitoring', function (Blueprint $table) { 55 | $table->string('tag')->primary(); 56 | }); 57 | } 58 | 59 | /** 60 | * Reverse the migrations. 61 | */ 62 | public function down(): void 63 | { 64 | $schema = Schema::connection($this->getConnection()); 65 | 66 | $schema->dropIfExists('telescope_entries_tags'); 67 | $schema->dropIfExists('telescope_entries'); 68 | $schema->dropIfExists('telescope_monitoring'); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | create(); 17 | 18 | User::factory()->create([ 19 | 'name' => 'Test User', 20 | 'email' => 'test@example.com', 21 | ]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /database/seeders/IPListSeeder.php: -------------------------------------------------------------------------------- 1 | '127.0.0.1', 22 | 'status' => 'Whitelist', 23 | 'ip_type' => 'IPv4', 24 | 'remarks' => 'Local Host IP', 25 | 'created_at' => Carbon::now(), 26 | 'updated_at' => Carbon::now(), 27 | ], 28 | [ 29 | 'ip_address' => '::1', 30 | 'status' => 'Whitelist', 31 | 'ip_type' => 'IPv4', 32 | 'remarks' => 'Local Host IP', 33 | 'created_at' => Carbon::now(), 34 | 'updated_at' => Carbon::now(), 35 | ], 36 | 37 | [ 38 | 'ip_address' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', 39 | 'status' => 'Blocklist', 40 | 'ip_type' => 'IPv6', 41 | 'remarks' => 'Sample IPv6 address', 42 | 'created_at' => Carbon::now(), 43 | 'updated_at' => Carbon::now(), 44 | ], 45 | ]; 46 | 47 | // Insert data into the ip_lists table 48 | DB::table('ip_lists')->insert($ipLists); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /database/seeders/RoleSeeder.php: -------------------------------------------------------------------------------- 1 | 'Super Admin', 20 | 'slug' => 'super-admin', 21 | 'status' => 'active', 22 | 'description' => 'Super Administrator Role', 23 | 'created_at' => Carbon::now(), 24 | 'updated_at' => Carbon::now(), 25 | ], 26 | [ 27 | 'name' => 'Admin', 28 | 'slug' => 'admin', 29 | 'status' => 'active', 30 | 'description' => 'Administrator Role', 31 | 'created_at' => Carbon::now(), 32 | 'updated_at' => Carbon::now(), 33 | ], 34 | [ 35 | 'name' => 'Teacher', 36 | 'slug' => 'teacher', 37 | 'status' => 'active', 38 | 'description' => 'Teacher Role', 39 | 'created_at' => Carbon::now(), 40 | 'updated_at' => Carbon::now(), 41 | ], 42 | [ 43 | 'name' => 'Principal', 44 | 'slug' => 'principal', 45 | 'status' => 'active', 46 | 'description' => 'Principal Role', 47 | 'created_at' => Carbon::now(), 48 | 'updated_at' => Carbon::now(), 49 | ], 50 | [ 51 | 'name' => 'Student', 52 | 'slug' => 'student', 53 | 'status' => 'active', 54 | 'description' => 'Student Role', 55 | 'created_at' => Carbon::now(), 56 | 'updated_at' => Carbon::now(), 57 | ], 58 | [ 59 | 'name' => 'Guest', 60 | 'slug' => 'guest', 61 | 'status' => 'active', 62 | 'description' => 'Guest Role', 63 | 'created_at' => Carbon::now(), 64 | 'updated_at' => Carbon::now(), 65 | ], 66 | ]; 67 | 68 | // Insert data into the roles table 69 | DB::table('roles')->insert($roles); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /documents/Laravel Rest APIs.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "b8fb4f44-8602-40ba-99ec-573daf0a1996", 4 | "name": "Laravel Rest APIs", 5 | "description": "StartFragmentThis project is built using Laravel version 11 and focuses on REST API design. It implements best practices and emphasizes separating requests and responses while adhering to various design principles such as DRY (Don't Repeat Yourself), SOLID, and Separation of Concerns.EndFragment", 6 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 7 | "_exporter_id": "29494647", 8 | "_collection_link": "https://www.postman.com/mominur23/workspace/laravel-rest/collection/29494647-b8fb4f44-8602-40ba-99ec-573daf0a1996?action=share&source=collection_link&creator=29494647" 9 | }, 10 | "item": [ 11 | { 12 | "name": "Roles", 13 | "item": [ 14 | { 15 | "name": "index", 16 | "event": [ 17 | { 18 | "listen": "prerequest", 19 | "script": { 20 | "exec": [ 21 | "" 22 | ], 23 | "type": "text/javascript", 24 | "packages": {} 25 | } 26 | } 27 | ], 28 | "request": { 29 | "auth": { 30 | "type": "bearer", 31 | "bearer": [ 32 | { 33 | "key": "token", 34 | "value": "{{token}}", 35 | "type": "string" 36 | } 37 | ] 38 | }, 39 | "method": "GET", 40 | "header": [], 41 | "url": { 42 | "raw": "{{base_url}}/api/v1/roles", 43 | "host": [ 44 | "{{base_url}}" 45 | ], 46 | "path": [ 47 | "api", 48 | "v1", 49 | "roles" 50 | ] 51 | } 52 | }, 53 | "response": [ 54 | { 55 | "name": "response-1", 56 | "originalRequest": { 57 | "method": "GET", 58 | "header": [], 59 | "url": { 60 | "raw": "{{base_url}}/api/v1/roles", 61 | "host": [ 62 | "{{base_url}}" 63 | ], 64 | "path": [ 65 | "api", 66 | "v1", 67 | "roles" 68 | ] 69 | } 70 | }, 71 | "status": "OK", 72 | "code": 200, 73 | "_postman_previewlanguage": "json", 74 | "header": [ 75 | { 76 | "key": "Host", 77 | "value": "127.0.0.1:8000" 78 | }, 79 | { 80 | "key": "Date", 81 | "value": "Sun, 17 Mar 2024 14:26:17 GMT" 82 | }, 83 | { 84 | "key": "Date", 85 | "value": "Sun, 17 Mar 2024 14:26:17 GMT" 86 | }, 87 | { 88 | "key": "Connection", 89 | "value": "close" 90 | }, 91 | { 92 | "key": "X-Powered-By", 93 | "value": "PHP/8.2.4" 94 | }, 95 | { 96 | "key": "Cache-Control", 97 | "value": "no-cache, private" 98 | }, 99 | { 100 | "key": "Content-Type", 101 | "value": "application/json" 102 | }, 103 | { 104 | "key": "Access-Control-Allow-Origin", 105 | "value": "*" 106 | } 107 | ], 108 | "cookie": [], 109 | "body": "{\n \"data\": [\n {\n \"id\": 1,\n \"name\": \"Super Admin\",\n \"slug\": \"super-admin\",\n \"status\": \"active\",\n \"description\": \"Super Administrator Role\",\n \"created_at\": \"15 Mar 2024 08:19 AM\",\n \"updated_at\": \"15 Mar 2024 08:19 AM\"\n },\n {\n \"id\": 2,\n \"name\": \"Admin\",\n \"slug\": \"admin\",\n \"status\": \"active\",\n \"description\": \"Administrator Role\",\n \"created_at\": \"15 Mar 2024 08:19 AM\",\n \"updated_at\": \"15 Mar 2024 08:19 AM\"\n },\n {\n \"id\": 3,\n \"name\": \"Teacher\",\n \"slug\": \"teacher\",\n \"status\": \"active\",\n \"description\": \"Teacher Role\",\n \"created_at\": \"15 Mar 2024 08:19 AM\",\n \"updated_at\": \"15 Mar 2024 08:19 AM\"\n },\n {\n \"id\": 4,\n \"name\": \"Principal\",\n \"slug\": \"principal\",\n \"status\": \"active\",\n \"description\": \"Principal Role\",\n \"created_at\": \"15 Mar 2024 08:19 AM\",\n \"updated_at\": \"15 Mar 2024 08:19 AM\"\n },\n {\n \"id\": 5,\n \"name\": \"Student\",\n \"slug\": \"student\",\n \"status\": \"active\",\n \"description\": \"Student Role\",\n \"created_at\": \"15 Mar 2024 08:19 AM\",\n \"updated_at\": \"15 Mar 2024 08:19 AM\"\n },\n {\n \"id\": 6,\n \"name\": \"Guest\",\n \"slug\": \"guest\",\n \"status\": \"active\",\n \"description\": \"Guest Role\",\n \"created_at\": \"15 Mar 2024 08:19 AM\",\n \"updated_at\": \"15 Mar 2024 08:19 AM\"\n },\n {\n \"id\": 7,\n \"name\": \"Test name\",\n \"slug\": \"test-name\",\n \"status\": \"active\",\n \"description\": \"Test role\",\n \"created_at\": \"15 Mar 2024 12:50 PM\",\n \"updated_at\": \"15 Mar 2024 12:50 PM\"\n },\n {\n \"id\": 8,\n \"name\": \"Writter\",\n \"slug\": \"writter\",\n \"status\": \"active\",\n \"description\": \"All the previllages\",\n \"created_at\": \"15 Mar 2024 01:33 PM\",\n \"updated_at\": \"15 Mar 2024 02:04 PM\"\n }\n ],\n \"links\": {\n \"first\": \"http://127.0.0.1:8000/api/v1/roles?page=1\",\n \"last\": \"http://127.0.0.1:8000/api/v1/roles?page=1\",\n \"prev\": null,\n \"next\": null\n },\n \"meta\": {\n \"current_page\": 1,\n \"from\": 1,\n \"last_page\": 1,\n \"links\": [\n {\n \"url\": null,\n \"label\": \"« Previous\",\n \"active\": false\n },\n {\n \"url\": \"http://127.0.0.1:8000/api/v1/roles?page=1\",\n \"label\": \"1\",\n \"active\": true\n },\n {\n \"url\": null,\n \"label\": \"Next »\",\n \"active\": false\n }\n ],\n \"path\": \"http://127.0.0.1:8000/api/v1/roles\",\n \"per_page\": 10,\n \"to\": 8,\n \"total\": 8\n },\n \"response\": {\n \"status\": \"success\",\n \"status_code\": 200,\n \"message\": \"Records retrieved successfully\"\n }\n}" 110 | }, 111 | { 112 | "name": "response-2", 113 | "originalRequest": { 114 | "method": "GET", 115 | "header": [], 116 | "url": { 117 | "raw": "{{base_url}}/api/v1/roles?status=inactive", 118 | "host": [ 119 | "{{base_url}}" 120 | ], 121 | "path": [ 122 | "api", 123 | "v1", 124 | "roles" 125 | ], 126 | "query": [ 127 | { 128 | "key": "status", 129 | "value": "inactive" 130 | } 131 | ] 132 | } 133 | }, 134 | "status": "OK", 135 | "code": 200, 136 | "_postman_previewlanguage": "json", 137 | "header": [ 138 | { 139 | "key": "Host", 140 | "value": "127.0.0.1:8000" 141 | }, 142 | { 143 | "key": "Date", 144 | "value": "Sun, 17 Mar 2024 14:27:25 GMT" 145 | }, 146 | { 147 | "key": "Date", 148 | "value": "Sun, 17 Mar 2024 14:27:25 GMT" 149 | }, 150 | { 151 | "key": "Connection", 152 | "value": "close" 153 | }, 154 | { 155 | "key": "X-Powered-By", 156 | "value": "PHP/8.2.4" 157 | }, 158 | { 159 | "key": "Cache-Control", 160 | "value": "no-cache, private" 161 | }, 162 | { 163 | "key": "Content-Type", 164 | "value": "application/json" 165 | }, 166 | { 167 | "key": "Access-Control-Allow-Origin", 168 | "value": "*" 169 | } 170 | ], 171 | "cookie": [], 172 | "body": "{\n \"data\": [\n {\n \"id\": 6,\n \"name\": \"Guest\",\n \"slug\": \"guest\",\n \"status\": \"inactive\",\n \"description\": \"Guest Role\",\n \"created_at\": \"15 Mar 2024 08:19 AM\",\n \"updated_at\": \"15 Mar 2024 08:19 AM\"\n },\n {\n \"id\": 8,\n \"name\": \"Writter\",\n \"slug\": \"writter\",\n \"status\": \"inactive\",\n \"description\": \"All the previllages\",\n \"created_at\": \"15 Mar 2024 01:33 PM\",\n \"updated_at\": \"15 Mar 2024 02:04 PM\"\n }\n ],\n \"links\": {\n \"first\": \"http://127.0.0.1:8000/api/v1/roles?page=1\",\n \"last\": \"http://127.0.0.1:8000/api/v1/roles?page=1\",\n \"prev\": null,\n \"next\": null\n },\n \"meta\": {\n \"current_page\": 1,\n \"from\": 1,\n \"last_page\": 1,\n \"links\": [\n {\n \"url\": null,\n \"label\": \"« Previous\",\n \"active\": false\n },\n {\n \"url\": \"http://127.0.0.1:8000/api/v1/roles?page=1\",\n \"label\": \"1\",\n \"active\": true\n },\n {\n \"url\": null,\n \"label\": \"Next »\",\n \"active\": false\n }\n ],\n \"path\": \"http://127.0.0.1:8000/api/v1/roles\",\n \"per_page\": 10,\n \"to\": 2,\n \"total\": 2\n },\n \"response\": {\n \"status\": \"success\",\n \"status_code\": 200,\n \"message\": \"Records retrieved successfully\"\n }\n}" 173 | }, 174 | { 175 | "name": "response-3", 176 | "originalRequest": { 177 | "method": "GET", 178 | "header": [], 179 | "url": { 180 | "raw": "{{base_url}}/api/v1/roles?slug=admin", 181 | "host": [ 182 | "{{base_url}}" 183 | ], 184 | "path": [ 185 | "api", 186 | "v1", 187 | "roles" 188 | ], 189 | "query": [ 190 | { 191 | "key": "slug", 192 | "value": "admin" 193 | } 194 | ] 195 | } 196 | }, 197 | "status": "OK", 198 | "code": 200, 199 | "_postman_previewlanguage": "json", 200 | "header": [ 201 | { 202 | "key": "Host", 203 | "value": "127.0.0.1:8000" 204 | }, 205 | { 206 | "key": "Date", 207 | "value": "Sun, 17 Mar 2024 14:28:28 GMT" 208 | }, 209 | { 210 | "key": "Date", 211 | "value": "Sun, 17 Mar 2024 14:28:28 GMT" 212 | }, 213 | { 214 | "key": "Connection", 215 | "value": "close" 216 | }, 217 | { 218 | "key": "X-Powered-By", 219 | "value": "PHP/8.2.4" 220 | }, 221 | { 222 | "key": "Cache-Control", 223 | "value": "no-cache, private" 224 | }, 225 | { 226 | "key": "Content-Type", 227 | "value": "application/json" 228 | }, 229 | { 230 | "key": "Access-Control-Allow-Origin", 231 | "value": "*" 232 | } 233 | ], 234 | "cookie": [], 235 | "body": "{\n \"data\": [\n {\n \"id\": 2,\n \"name\": \"Admin\",\n \"slug\": \"admin\",\n \"status\": \"active\",\n \"description\": \"Administrator Role\",\n \"created_at\": \"15 Mar 2024 08:19 AM\",\n \"updated_at\": \"15 Mar 2024 08:19 AM\"\n }\n ],\n \"links\": {\n \"first\": \"http://127.0.0.1:8000/api/v1/roles?page=1\",\n \"last\": \"http://127.0.0.1:8000/api/v1/roles?page=1\",\n \"prev\": null,\n \"next\": null\n },\n \"meta\": {\n \"current_page\": 1,\n \"from\": 1,\n \"last_page\": 1,\n \"links\": [\n {\n \"url\": null,\n \"label\": \"« Previous\",\n \"active\": false\n },\n {\n \"url\": \"http://127.0.0.1:8000/api/v1/roles?page=1\",\n \"label\": \"1\",\n \"active\": true\n },\n {\n \"url\": null,\n \"label\": \"Next »\",\n \"active\": false\n }\n ],\n \"path\": \"http://127.0.0.1:8000/api/v1/roles\",\n \"per_page\": 10,\n \"to\": 1,\n \"total\": 1\n },\n \"response\": {\n \"status\": \"success\",\n \"status_code\": 200,\n \"message\": \"Records retrieved successfully\"\n }\n}" 236 | } 237 | ] 238 | }, 239 | { 240 | "name": "store", 241 | "request": { 242 | "auth": { 243 | "type": "bearer", 244 | "bearer": [ 245 | { 246 | "key": "token", 247 | "value": "{{token}}", 248 | "type": "string" 249 | } 250 | ] 251 | }, 252 | "method": "POST", 253 | "header": [], 254 | "body": { 255 | "mode": "raw", 256 | "raw": "{\r\n \"name\": \"Tester\",\r\n \"status\": \"active\",\r\n \"description\": \"Tester role\"\r\n}\r\n", 257 | "options": { 258 | "raw": { 259 | "language": "json" 260 | } 261 | } 262 | }, 263 | "url": { 264 | "raw": "{{base_url}}/api/v1/roles", 265 | "host": [ 266 | "{{base_url}}" 267 | ], 268 | "path": [ 269 | "api", 270 | "v1", 271 | "roles" 272 | ] 273 | } 274 | }, 275 | "response": [] 276 | }, 277 | { 278 | "name": "update", 279 | "request": { 280 | "auth": { 281 | "type": "bearer", 282 | "bearer": [ 283 | { 284 | "key": "token", 285 | "value": "{{token}}", 286 | "type": "string" 287 | } 288 | ] 289 | }, 290 | "method": "PUT", 291 | "header": [], 292 | "body": { 293 | "mode": "raw", 294 | "raw": "{\r\n \"name\": \"Writter\",\r\n \"status\": \"active\",\r\n \"description\": \"All the previllages\"\r\n}\r\n", 295 | "options": { 296 | "raw": { 297 | "language": "json" 298 | } 299 | } 300 | }, 301 | "url": { 302 | "raw": "{{base_url}}/api/v1/roles/8", 303 | "host": [ 304 | "{{base_url}}" 305 | ], 306 | "path": [ 307 | "api", 308 | "v1", 309 | "roles", 310 | "8" 311 | ] 312 | } 313 | }, 314 | "response": [] 315 | }, 316 | { 317 | "name": "show", 318 | "request": { 319 | "auth": { 320 | "type": "bearer", 321 | "bearer": [ 322 | { 323 | "key": "token", 324 | "value": "{{token}}", 325 | "type": "string" 326 | } 327 | ] 328 | }, 329 | "method": "GET", 330 | "header": [], 331 | "url": { 332 | "raw": "{{base_url}}/api/v1/roles/2", 333 | "host": [ 334 | "{{base_url}}" 335 | ], 336 | "path": [ 337 | "api", 338 | "v1", 339 | "roles", 340 | "2" 341 | ] 342 | } 343 | }, 344 | "response": [] 345 | }, 346 | { 347 | "name": "destroy", 348 | "request": { 349 | "auth": { 350 | "type": "bearer", 351 | "bearer": [ 352 | { 353 | "key": "token", 354 | "value": "{{token}}", 355 | "type": "string" 356 | } 357 | ] 358 | }, 359 | "method": "DELETE", 360 | "header": [], 361 | "url": { 362 | "raw": "{{base_url}}/api/v1/roles/8", 363 | "host": [ 364 | "{{base_url}}" 365 | ], 366 | "path": [ 367 | "api", 368 | "v1", 369 | "roles", 370 | "8" 371 | ] 372 | } 373 | }, 374 | "response": [] 375 | } 376 | ] 377 | }, 378 | { 379 | "name": "IPList", 380 | "item": [ 381 | { 382 | "name": "index", 383 | "request": { 384 | "auth": { 385 | "type": "bearer", 386 | "bearer": [ 387 | { 388 | "key": "token", 389 | "value": "{{token}}", 390 | "type": "string" 391 | } 392 | ] 393 | }, 394 | "method": "GET", 395 | "header": [], 396 | "url": { 397 | "raw": "{{base_url}}/api/v1/ips", 398 | "host": [ 399 | "{{base_url}}" 400 | ], 401 | "path": [ 402 | "api", 403 | "v1", 404 | "ips" 405 | ] 406 | } 407 | }, 408 | "response": [] 409 | }, 410 | { 411 | "name": "store", 412 | "request": { 413 | "auth": { 414 | "type": "bearer", 415 | "bearer": [ 416 | { 417 | "key": "token", 418 | "value": "{{token}}", 419 | "type": "string" 420 | } 421 | ] 422 | }, 423 | "method": "POST", 424 | "header": [], 425 | "body": { 426 | "mode": "raw", 427 | "raw": "{\r\n \"ip_address\": \"127.0.0.1\",\r\n \"status\": \"active\",\r\n \"ip_type\" :\"IPv4\",\r\n \"remarks\": \"My Home IP\"\r\n}\r\n", 428 | "options": { 429 | "raw": { 430 | "language": "json" 431 | } 432 | } 433 | }, 434 | "url": { 435 | "raw": "{{base_url}}/api/v1/ips", 436 | "host": [ 437 | "{{base_url}}" 438 | ], 439 | "path": [ 440 | "api", 441 | "v1", 442 | "ips" 443 | ] 444 | } 445 | }, 446 | "response": [] 447 | }, 448 | { 449 | "name": "update", 450 | "request": { 451 | "auth": { 452 | "type": "bearer", 453 | "bearer": [ 454 | { 455 | "key": "token", 456 | "value": "{{token}}", 457 | "type": "string" 458 | } 459 | ] 460 | }, 461 | "method": "PUT", 462 | "header": [], 463 | "body": { 464 | "mode": "raw", 465 | "raw": "{\r\n \"ip_address\": \"192.168.0.101\",\r\n \"status\": \"active\",\r\n \"ip_type\" :\"IPv4\",\r\n \"remarks\": \"My Home IP\"\r\n}\r\n", 466 | "options": { 467 | "raw": { 468 | "language": "json" 469 | } 470 | } 471 | }, 472 | "url": { 473 | "raw": "{{base_url}}/api/v1/ips/2", 474 | "host": [ 475 | "{{base_url}}" 476 | ], 477 | "path": [ 478 | "api", 479 | "v1", 480 | "ips", 481 | "2" 482 | ] 483 | } 484 | }, 485 | "response": [] 486 | }, 487 | { 488 | "name": "show", 489 | "request": { 490 | "auth": { 491 | "type": "bearer", 492 | "bearer": [ 493 | { 494 | "key": "token", 495 | "value": "{{token}}", 496 | "type": "string" 497 | } 498 | ] 499 | }, 500 | "method": "GET", 501 | "header": [], 502 | "url": { 503 | "raw": "{{base_url}}/api/v1/ips/1", 504 | "host": [ 505 | "{{base_url}}" 506 | ], 507 | "path": [ 508 | "api", 509 | "v1", 510 | "ips", 511 | "1" 512 | ] 513 | } 514 | }, 515 | "response": [] 516 | }, 517 | { 518 | "name": "destroy", 519 | "request": { 520 | "auth": { 521 | "type": "bearer", 522 | "bearer": [ 523 | { 524 | "key": "token", 525 | "value": "{{token}}", 526 | "type": "string" 527 | } 528 | ] 529 | }, 530 | "method": "DELETE", 531 | "header": [], 532 | "url": { 533 | "raw": "{{base_url}}/api/v1/ips/1", 534 | "host": [ 535 | "{{base_url}}" 536 | ], 537 | "path": [ 538 | "api", 539 | "v1", 540 | "ips", 541 | "1" 542 | ] 543 | } 544 | }, 545 | "response": [] 546 | } 547 | ] 548 | }, 549 | { 550 | "name": "Auth", 551 | "item": [ 552 | { 553 | "name": "login", 554 | "request": { 555 | "method": "POST", 556 | "header": [], 557 | "body": { 558 | "mode": "formdata", 559 | "formdata": [ 560 | { 561 | "key": "email", 562 | "value": "user1@gmail.com", 563 | "type": "text" 564 | }, 565 | { 566 | "key": "password", 567 | "value": "12345678", 568 | "type": "text" 569 | } 570 | ] 571 | }, 572 | "url": { 573 | "raw": "{{base_url}}/api/auth/login", 574 | "host": [ 575 | "{{base_url}}" 576 | ], 577 | "path": [ 578 | "api", 579 | "auth", 580 | "login" 581 | ] 582 | } 583 | }, 584 | "response": [ 585 | { 586 | "name": "success-response", 587 | "originalRequest": { 588 | "method": "POST", 589 | "header": [], 590 | "body": { 591 | "mode": "formdata", 592 | "formdata": [ 593 | { 594 | "key": "email", 595 | "value": "user1@gmail.com", 596 | "type": "text" 597 | }, 598 | { 599 | "key": "password", 600 | "value": "12345678", 601 | "type": "text" 602 | } 603 | ] 604 | }, 605 | "url": { 606 | "raw": "{{base_url}}/api/auth/login", 607 | "host": [ 608 | "{{base_url}}" 609 | ], 610 | "path": [ 611 | "api", 612 | "auth", 613 | "login" 614 | ] 615 | } 616 | }, 617 | "status": "OK", 618 | "code": 200, 619 | "_postman_previewlanguage": "json", 620 | "header": [ 621 | { 622 | "key": "Host", 623 | "value": "127.0.0.1:8000" 624 | }, 625 | { 626 | "key": "Date", 627 | "value": "Sun, 17 Mar 2024 14:21:28 GMT" 628 | }, 629 | { 630 | "key": "Date", 631 | "value": "Sun, 17 Mar 2024 14:21:28 GMT" 632 | }, 633 | { 634 | "key": "Connection", 635 | "value": "close" 636 | }, 637 | { 638 | "key": "X-Powered-By", 639 | "value": "PHP/8.2.4" 640 | }, 641 | { 642 | "key": "Cache-Control", 643 | "value": "no-cache, private" 644 | }, 645 | { 646 | "key": "Content-Type", 647 | "value": "application/json" 648 | }, 649 | { 650 | "key": "Access-Control-Allow-Origin", 651 | "value": "*" 652 | } 653 | ], 654 | "cookie": [], 655 | "body": "{\n \"response\": {\n \"status\": \"success\",\n \"status_code\": 200,\n \"message\": \"Login successful\",\n \"data\": {\n \"token_type\": \"bearer\",\n \"access_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgwMDAvYXBpL2F1dGgvbG9naW4iLCJpYXQiOjE3MTA2ODUyODcsImV4cCI6MTcxMDY4ODg4NywibmJmIjoxNzEwNjg1Mjg3LCJqdGkiOiJvOVBjUzIyT2ZkSUJGcXRTIiwic3ViIjoiMiIsInBydiI6IjIzYmQ1Yzg5NDlmNjAwYWRiMzllNzAxYzQwMDg3MmRiN2E1OTc2ZjcifQ.6YsgCnyXYFR77WLiR5Xmn-jV9tyfYlJ56IhxQYKev_0\",\n \"expires_in\": 3600\n }\n }\n}" 656 | }, 657 | { 658 | "name": "unauthorized-response", 659 | "originalRequest": { 660 | "method": "POST", 661 | "header": [], 662 | "body": { 663 | "mode": "formdata", 664 | "formdata": [ 665 | { 666 | "key": "email", 667 | "value": "user1@gmail.com", 668 | "type": "text" 669 | }, 670 | { 671 | "key": "password", 672 | "value": "123456789", 673 | "type": "text" 674 | } 675 | ] 676 | }, 677 | "url": { 678 | "raw": "{{base_url}}/api/auth/login", 679 | "host": [ 680 | "{{base_url}}" 681 | ], 682 | "path": [ 683 | "api", 684 | "auth", 685 | "login" 686 | ] 687 | } 688 | }, 689 | "status": "OK", 690 | "code": 200, 691 | "_postman_previewlanguage": "json", 692 | "header": [ 693 | { 694 | "key": "Host", 695 | "value": "127.0.0.1:8000" 696 | }, 697 | { 698 | "key": "Date", 699 | "value": "Sun, 17 Mar 2024 14:22:17 GMT" 700 | }, 701 | { 702 | "key": "Date", 703 | "value": "Sun, 17 Mar 2024 14:22:17 GMT" 704 | }, 705 | { 706 | "key": "Connection", 707 | "value": "close" 708 | }, 709 | { 710 | "key": "X-Powered-By", 711 | "value": "PHP/8.2.4" 712 | }, 713 | { 714 | "key": "Cache-Control", 715 | "value": "no-cache, private" 716 | }, 717 | { 718 | "key": "Content-Type", 719 | "value": "application/json" 720 | }, 721 | { 722 | "key": "Access-Control-Allow-Origin", 723 | "value": "*" 724 | } 725 | ], 726 | "cookie": [], 727 | "body": "{\n \"response\": {\n \"status\": \"error\",\n \"status_code\": 401,\n \"error\": {\n \"message\": \"Unauthorized\",\n \"timestamp\": \"2024-03-17T14:22:17.627750Z\"\n }\n }\n}" 728 | }, 729 | { 730 | "name": "validation-failed", 731 | "originalRequest": { 732 | "method": "POST", 733 | "header": [], 734 | "body": { 735 | "mode": "formdata", 736 | "formdata": [ 737 | { 738 | "key": "email", 739 | "value": "user1@gmail.com44", 740 | "type": "text" 741 | }, 742 | { 743 | "key": "password", 744 | "value": "12345678", 745 | "type": "text" 746 | } 747 | ] 748 | }, 749 | "url": { 750 | "raw": "{{base_url}}/api/auth/login", 751 | "host": [ 752 | "{{base_url}}" 753 | ], 754 | "path": [ 755 | "api", 756 | "auth", 757 | "login" 758 | ] 759 | } 760 | }, 761 | "status": "Unprocessable Content", 762 | "code": 422, 763 | "_postman_previewlanguage": "json", 764 | "header": [ 765 | { 766 | "key": "Host", 767 | "value": "127.0.0.1:8000" 768 | }, 769 | { 770 | "key": "Date", 771 | "value": "Sun, 17 Mar 2024 14:22:49 GMT" 772 | }, 773 | { 774 | "key": "Date", 775 | "value": "Sun, 17 Mar 2024 14:22:49 GMT" 776 | }, 777 | { 778 | "key": "Connection", 779 | "value": "close" 780 | }, 781 | { 782 | "key": "X-Powered-By", 783 | "value": "PHP/8.2.4" 784 | }, 785 | { 786 | "key": "Cache-Control", 787 | "value": "no-cache, private" 788 | }, 789 | { 790 | "key": "Content-Type", 791 | "value": "application/json" 792 | }, 793 | { 794 | "key": "Access-Control-Allow-Origin", 795 | "value": "*" 796 | } 797 | ], 798 | "cookie": [], 799 | "body": "{\n \"response\": {\n \"status\": \"error\",\n \"status_code\": 422,\n \"error\": {\n \"message\": \"Validation failed\",\n \"timestamp\": \"2024-03-17T14:22:49.332003Z\",\n \"details\": {\n \"email\": [\n \"The email address must be a valid email address\"\n ]\n }\n }\n }\n}" 800 | }, 801 | { 802 | "name": "validation-failed-2", 803 | "originalRequest": { 804 | "method": "POST", 805 | "header": [], 806 | "body": { 807 | "mode": "formdata", 808 | "formdata": [ 809 | { 810 | "key": "email", 811 | "value": "user1@gmail.com44", 812 | "type": "text", 813 | "disabled": true 814 | }, 815 | { 816 | "key": "password", 817 | "value": "12345678", 818 | "type": "text" 819 | } 820 | ] 821 | }, 822 | "url": { 823 | "raw": "{{base_url}}/api/auth/login", 824 | "host": [ 825 | "{{base_url}}" 826 | ], 827 | "path": [ 828 | "api", 829 | "auth", 830 | "login" 831 | ] 832 | } 833 | }, 834 | "status": "Unprocessable Content", 835 | "code": 422, 836 | "_postman_previewlanguage": "json", 837 | "header": [ 838 | { 839 | "key": "Host", 840 | "value": "127.0.0.1:8000" 841 | }, 842 | { 843 | "key": "Date", 844 | "value": "Sun, 17 Mar 2024 14:23:49 GMT" 845 | }, 846 | { 847 | "key": "Date", 848 | "value": "Sun, 17 Mar 2024 14:23:49 GMT" 849 | }, 850 | { 851 | "key": "Connection", 852 | "value": "close" 853 | }, 854 | { 855 | "key": "X-Powered-By", 856 | "value": "PHP/8.2.4" 857 | }, 858 | { 859 | "key": "Cache-Control", 860 | "value": "no-cache, private" 861 | }, 862 | { 863 | "key": "Content-Type", 864 | "value": "application/json" 865 | }, 866 | { 867 | "key": "Access-Control-Allow-Origin", 868 | "value": "*" 869 | } 870 | ], 871 | "cookie": [], 872 | "body": "{\n \"response\": {\n \"status\": \"error\",\n \"status_code\": 422,\n \"error\": {\n \"message\": \"Validation failed\",\n \"timestamp\": \"2024-03-17T14:23:49.396391Z\",\n \"details\": {\n \"email\": [\n \"The email address field is required.\"\n ]\n }\n }\n }\n}" 873 | } 874 | ] 875 | }, 876 | { 877 | "name": "register", 878 | "request": { 879 | "method": "POST", 880 | "header": [], 881 | "body": { 882 | "mode": "raw", 883 | "raw": "{\r\n \"name\": \"Mominur Rahman\",\r\n \"email\": \"mominur.rahman@gmail.com\",\r\n \"password\": \"epNc7hKuEBexKQj\"\r\n}", 884 | "options": { 885 | "raw": { 886 | "language": "json" 887 | } 888 | } 889 | }, 890 | "url": { 891 | "raw": "{{base_url}}/api/auth/register", 892 | "host": [ 893 | "{{base_url}}" 894 | ], 895 | "path": [ 896 | "api", 897 | "auth", 898 | "register" 899 | ] 900 | } 901 | }, 902 | "response": [] 903 | }, 904 | { 905 | "name": "logout", 906 | "request": { 907 | "auth": { 908 | "type": "bearer", 909 | "bearer": [ 910 | { 911 | "key": "token", 912 | "value": "{{token}}", 913 | "type": "string" 914 | } 915 | ] 916 | }, 917 | "method": "POST", 918 | "header": [], 919 | "body": { 920 | "mode": "formdata", 921 | "formdata": [ 922 | { 923 | "key": "email", 924 | "value": "admin@gmail.com", 925 | "type": "text" 926 | }, 927 | { 928 | "key": "password", 929 | "value": "12345678", 930 | "type": "text" 931 | } 932 | ] 933 | }, 934 | "url": { 935 | "raw": "{{base_url}}/api/auth/logout", 936 | "host": [ 937 | "{{base_url}}" 938 | ], 939 | "path": [ 940 | "api", 941 | "auth", 942 | "logout" 943 | ] 944 | } 945 | }, 946 | "response": [] 947 | } 948 | ] 949 | } 950 | ] 951 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build" 7 | }, 8 | "devDependencies": { 9 | "axios": "^1.6.4", 10 | "laravel-vite-plugin": "^1.0", 11 | "vite": "^5.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests/Unit 10 | 11 | 12 | tests/Feature 13 | 14 | 15 | 16 | 17 | app 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 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 | # Send Requests To Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sMmominur/restapi-laravel/91e3ddf6dfaa343955cfbe77160a911480adbe4b/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | handleRequest(Request::capture()); 18 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/vendor/telescope/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sMmominur/restapi-laravel/91e3ddf6dfaa343955cfbe77160a911480adbe4b/public/vendor/telescope/favicon.ico -------------------------------------------------------------------------------- /public/vendor/telescope/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/app.js": "/app.js?id=7049e92a398e816f8cd53a915eaea592", 3 | "/app-dark.css": "/app-dark.css?id=b11fa9a28e9d3aeb8c92986f319b3c44", 4 | "/app.css": "/app.css?id=b3ccfbe68f24cff776f83faa8dead721" 5 | } 6 | -------------------------------------------------------------------------------- /resources/css/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sMmominur/restapi-laravel/91e3ddf6dfaa343955cfbe77160a911480adbe4b/resources/css/app.css -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | import './bootstrap'; 2 | -------------------------------------------------------------------------------- /resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | window.axios = axios; 3 | 4 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 5 | -------------------------------------------------------------------------------- /resources/views/welcome.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Laravel 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 |
20 | 21 |
22 |
23 |
24 |
25 | 26 |
27 | @if (Route::has('login')) 28 | 54 | @endif 55 |
56 | 57 |
58 | 163 |
164 | 165 |
166 | Laravel v{{ Illuminate\Foundation\Application::VERSION }} (PHP v{{ PHP_VERSION }}) 167 |
168 |
169 |
170 |
171 | 172 | 173 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | 'api', 'prefix' => 'auth'], function ($router) { 22 | Route::post('/login', [AuthController::class, 'login']); 23 | route::post('/register', [AuthController::class, 'storeUser']); 24 | }); 25 | 26 | # All services api routes 27 | Route::group(['middleware' => ['ip-whitelist','api','jwt-verify'],'prefix' => 'v1'], function ($router) { 28 | 29 | Route::apiResource('roles', RoleController::class); 30 | Route::apiResource('ips', IPController::class); 31 | 32 | }); -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 8 | })->purpose('Display an inspiring quote')->hourly(); 9 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | get('/'); 16 | 17 | $response->assertStatus(200); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import laravel from 'laravel-vite-plugin'; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | laravel({ 7 | input: ['resources/css/app.css', 'resources/js/app.js'], 8 | refresh: true, 9 | }), 10 | ], 11 | }); 12 | --------------------------------------------------------------------------------