├── LICENSE ├── README.md ├── compose.yaml ├── composer.json ├── config └── nusa.php ├── database ├── README.md ├── migrations │ └── create_addresses_tables.php.stub └── nusa.sqlite ├── resources └── lang │ ├── en │ └── nusa.php │ └── id │ └── nusa.php ├── routes └── nusa.php └── src ├── Contracts ├── Address.php ├── District.php ├── HasAddress.php ├── HasAddresses.php ├── HasCoordinate.php ├── Province.php ├── Regency.php └── Village.php ├── Http ├── Controllers │ ├── DistrictController.php │ ├── ProvinceController.php │ ├── RegencyController.php │ └── VillageController.php ├── Requests │ └── NusaRequest.php └── Resources │ └── NusaResource.php ├── Models ├── Address.php ├── Concerns │ ├── WithAddress.php │ ├── WithAddresses.php │ ├── WithCoordinate.php │ ├── WithDistrict.php │ ├── WithDistricts.php │ ├── WithProvince.php │ ├── WithRegency.php │ ├── WithVillage.php │ └── WithVillages.php ├── District.php ├── Model.php ├── Province.php ├── Regency.php └── Village.php └── ServiceProvider.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Creasi.co 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Version](https://img.shields.io/packagist/v/creasi/laravel-nusa?style=flat-square)](https://packagist.org/packages/creasi/laravel-nusa) 2 | [![License](https://img.shields.io/github/license/creasico/laravel-nusa?style=flat-square)](https://github.com/creasico/laravel-nusa/blob/main/LICENSE) 3 | [![Actions Status](https://img.shields.io/github/actions/workflow/status/creasico/laravel-nusa/tests.yml?branch=main&style=flat-square)](https://github.com/creasico/laravel-nusa/actions) 4 | 5 | # Creasi Nusa 6 | 7 | Simple library aims to provide Indonesia Administrative Region Data based including the coordinates 8 | and postal codes, that easily integrated with our laravel project. 9 | 10 | ## Requirements 11 | 12 | - PHP `>=v8.1` with `php-sqlite3` extension 13 | - Laravel `>=10.0` 14 | 15 | ## Why? 16 | 17 | Why don't just use existsing [laravolt/indonesia](https://github.com/laravolt/indonesia), you may ask? 18 | That packages have been around for quite sometimes and already 've been used by hundreds of people, 19 | indeed. But, we need a package that ready-to-use once its installed. 20 | 21 | I've been using [edwardsamuel/Wilayah-Administratif-Indonesia](https://github.com/edwardsamuel/Wilayah-Administratif-Indonesia) 22 | for a while and put some contributions there, but it seems no longer maintained since 2018. More over its built for python not PHP. 23 | 24 | That's why we choose [cahyadsn/wilayah](https://github.com/cahyadsn/wilayah) it has robust and strong 25 | database in terms of legality, but its not actually a package that can be installed as dependency. By 26 | that said, it has some work to-do. 27 | 28 | We also found that [w3appdev/kodepos](https://github.com/w3appdev/kodepos) provides better database 29 | structures that can easily mapped with databases from [cahyadsn/wilayah](https://github.com/cahyadsn/wilayah) 30 | in single query. Until we decided to swap it with [cahyadsn/wilayah_kodepos](https://github.com/cahyadsn/wilayah_kodepos) 31 | due to [`#41`](https://github.com/creasico/laravel-nusa/issues/41). 32 | 33 | Our takes for the words **"easily integrated"** and **"ready-to-use once its installed"** means we 34 | shouldn't dealing with the data migration and seeding, hence Indonesia isn't a small country, right? 35 | running seeder for such amount of data can takes quite some times to proceed let alone the app seeder. 36 | 37 | Why PHP `>=8.1` and Laravel `>=10.0`, you may ask? Because, why not! 38 | 39 | ## Installation 40 | 41 | ```sh 42 | composer require creasi/laravel-nusa 43 | ``` 44 | 45 | That's all 46 | 47 | ## Roadmaps 48 | 49 | - [x] Basic Models 50 | - [x] Provinces, includes `laltitude`, `longitude` and `coordinates` 51 | - [x] Regencies, includes `laltitude`, `longitude` and `coordinates` 52 | - [x] Districts 53 | - [x] Vilages, include `postal_code` 54 | - [x] Routing 55 | 56 | ## Usage 57 | 58 | Thankfully Laravel provides us convenience way to have some sort of "relations" regardless of the 59 | database engine. So we can have this administrative data shipped in `sqlite` data and once we install 60 | it, then all we need is use it from our project with convenience of eloquent models. 61 | 62 | ### ReSTful API 63 | 64 | - Get all provinces 65 | 66 | `GET {APP_URL}/nusa/provinces` 67 | 68 | - **Query Params :** 69 | 70 | | Field | Type | Option | Description | 71 | | --- | --- | --- | --- | 72 | | `codes` | `numeric[]` | `optional` | Fetch only specified province code | 73 | | `search` | `string` | `optional` | Search province by a keyword | 74 | - **Example :** 75 | -
GET http://localhost:8000/nusa/provinces 76 | 77 | ```jsonc 78 | { 79 | "data": [ 80 | { 81 | "code": 33, 82 | "name": "Jawa Tengah", 83 | "latitude": -6.9934809206806, 84 | "longitude": 110.42024335421, 85 | "coordinates": [...], 86 | "postal_codes": [...], 87 | }, 88 | { ... } 89 | ], 90 | "links": { 91 | "first": "http://localhost:8000/nusa/provinces?page=1", 92 | "last": "http://localhost:8000/nusa/provinces?page=3", 93 | "prev": null, 94 | "next": "http://localhost:8000/nusa/provinces?page=2", 95 | }, 96 | "meta": { 97 | "current_page": 1, 98 | "from": 1, 99 | "last_page": 3, 100 | "links": [ 101 | { 102 | "url": null, 103 | "label": "« Previous", 104 | "active": false, 105 | }, 106 | { 107 | "url": "http://localhost:8000/nusa/provinces?page=1", 108 | "label": "1", 109 | "active": true, 110 | }, 111 | { ... }, 112 | { 113 | "url": "http://localhost/nusa/provinces?page=2", 114 | "label": "Next »", 115 | "active": false, 116 | }, 117 | ], 118 | "path": "http://localhost:8000/nusa/provinces", 119 | "per_page": 15, 120 | "to": 15, 121 | "total": 34 122 | } 123 | } 124 | ``` 125 |
126 | 127 | - Show a province 128 | 129 | `GET {APP_URL}/nusa/provinces/{province}` 130 | 131 | - **Route Param :** `{province}` province code 132 | - **Example :** 133 | -
GET http://localhost:8000/nusa/provinces/33 134 | 135 | ```jsonc 136 | { 137 | "data": { 138 | "code": 33, 139 | "name": "Jawa Tengah", 140 | "latitude": -6.9934809206806, 141 | "longitude": 110.42024335421, 142 | "coordinates": [...], 143 | "postal_codes": [...], 144 | }, 145 | "meta": {} 146 | } 147 | ``` 148 |
149 | 150 | - Get all regencies in a province 151 | 152 | `GET {APP_URL}/nusa/provinces/{province}/regencies` 153 | 154 | - **Route Param :** `{province}` province code 155 | - **Example :** 156 | -
GET http://localhost:8000/nusa/provinces/33/regencies 157 | 158 | ```jsonc 159 | { 160 | "data": [ 161 | { 162 | "code": 3375, 163 | "province_code": 33, 164 | "name": "Kota Pekalongan", 165 | "latitude": -6.8969497174987, 166 | "longitude": 109.66208089654, 167 | "coordinates": [...], 168 | "postal_codes": [...], 169 | }, 170 | { ... } 171 | ], 172 | "links": { 173 | "first": "http://localhost:8000/nusa/provinces/33/regencies?page=1", 174 | "last": "http://localhost:8000/nusa/provinces/33/regencies?page=3", 175 | "prev": null, 176 | "next": "http://localhost:8000/nusa/provinces/33/regencies?page=2", 177 | }, 178 | "meta": { 179 | "current_page": 1, 180 | "from": 1, 181 | "last_page": 3, 182 | "links": [ 183 | { 184 | "url": null, 185 | "label": "« Previous", 186 | "active": false, 187 | }, 188 | { 189 | "url": "http://localhost:8000/nusa/provinces/33/regencies?page=1", 190 | "label": "1", 191 | "active": true, 192 | }, 193 | { ... }, 194 | { 195 | "url": "http://localhost/nusa/provinces/33/regencies?page=2", 196 | "label": "Next »", 197 | "active": false, 198 | }, 199 | ], 200 | "path": "http://localhost:8000/nusa/provinces/33/regencies", 201 | "per_page": 15, 202 | "to": 15, 203 | "total": 35 204 | } 205 | } 206 | ``` 207 |
208 | 209 | - Get all districts in a province 210 | 211 | `GET {APP_URL}/nusa/provinces/{province}/districts` 212 | 213 | - **Route Param :** `{province}` province code 214 | - **Example :** 215 | -
GET http://localhost:8000/nusa/provinces/33/districts 216 | 217 | ```jsonc 218 | { 219 | "data": [ 220 | { 221 | "code": 330101, 222 | "regency_code": 3301, 223 | "province_code": 33, 224 | "name": "Kedungreja", 225 | "postal_codes": [ 226 | 53263 227 | ] 228 | }, 229 | { ... } 230 | ], 231 | "links": { 232 | "first": "http://localhost:8000/nusa/provinces/33/districts?page=1", 233 | "last": "http://localhost:8000/nusa/provinces/33/districts?page=39", 234 | "prev": null, 235 | "next": "http://localhost/nusa/provinces/33/districts?page=2", 236 | }, 237 | "meta": { 238 | "current_page": 1, 239 | "from": 1, 240 | "last_page": 39, 241 | "links": [ 242 | { 243 | "url": null, 244 | "label": "« Previous", 245 | "active": false, 246 | }, 247 | { 248 | "url": "http://localhost:8000/nusa/provinces/33/districts?page=1", 249 | "label": "1", 250 | "active": true, 251 | }, 252 | { ... }, 253 | { 254 | "url": "http://localhost/nusa/provinces/33/districts?page=2", 255 | "label": "Next »", 256 | "active": false, 257 | }, 258 | ], 259 | "path": "http://localhost:8000/nusa/provinces/33/districts", 260 | "per_page": 15, 261 | "to": 15, 262 | "total": 576 263 | } 264 | } 265 | ``` 266 |
267 | 268 | - Get all villages in a province 269 | 270 | `GET {APP_URL}/nusa/provinces/{province}/villages` 271 | 272 | - **Route Param :** `{province}` province code 273 | - **Example :** 274 | -
GET http://localhost:8000/nusa/provinces/33/villages 275 | 276 | ```jsonc 277 | { 278 | "data": [ 279 | { 280 | "code": 3301012001, 281 | "district_code": 330101, 282 | "regency_code": 3301, 283 | "province_code": 33, 284 | "name": "Tambakreja", 285 | "postal_code": 53263, 286 | }, 287 | { ... } 288 | ], 289 | "links": { 290 | "first": "http://localhost:8000/nusa/provinces/33/villages?page=1", 291 | "last": "http://localhost:8000/nusa/provinces/33/villages?page=571", 292 | "prev": null, 293 | "next": "http://localhost:8000/nusa/provinces/33/villages?page=2", 294 | }, 295 | "meta": { 296 | "current_page": 1, 297 | "from": 1, 298 | "last_page": 571, 299 | "links": [ 300 | { 301 | "url": null, 302 | "label": "« Previous", 303 | "active": false, 304 | }, 305 | { 306 | "url": "http://localhost:8000/nusa/provinces/33/villages?page=1", 307 | "label": "1", 308 | "active": true, 309 | }, 310 | { ... }, 311 | { 312 | "url": "http://localhost:8000/nusa/provinces/33/villages?page=2", 313 | "label": "Next »", 314 | "active": false, 315 | }, 316 | ], 317 | "path": "http://localhost:8000/nusa/provinces/33/villages", 318 | "per_page": 15, 319 | "to": 15, 320 | "total": 8562 321 | } 322 | } 323 | ``` 324 |
325 | 326 | - Get all regencies 327 | 328 | `GET {APP_URL}/nusa/regencies` 329 | 330 | - **Query Params :** 331 | 332 | | Field | Type | Option | Description | 333 | | --- | --- | --- | --- | 334 | | `codes` | `numeric[]` | `optional` | Fetch only specified regency code | 335 | | `search` | `string` | `optional` | Search regency by a keyword | 336 | - **Example :** 337 | -
GET http://localhost:8000/nusa/regencies 338 | 339 | ```jsonc 340 | { 341 | "data": [ 342 | { 343 | "code": 3375, 344 | "province_code": 33, 345 | "name": "Kota Pekalongan", 346 | "latitude": -6.8969497174987, 347 | "longitude": 109.66208089654, 348 | "coordinates": [...], 349 | "postal_codes": [...], 350 | }, 351 | { ... } 352 | ], 353 | "links": { 354 | "first": "http://localhost:8000/nusa/regencies?page=1", 355 | "last": "http://localhost:8000/nusa/regencies?page=35", 356 | "prev": null, 357 | "next": "http://localhost:8000/nusa/regencies?page=2", 358 | }, 359 | "meta": { 360 | "current_page": 1, 361 | "from": 1, 362 | "last_page": 35, 363 | "links": [ 364 | { 365 | "url": null, 366 | "label": "« Previous", 367 | "active": false, 368 | }, 369 | { 370 | "url": "http://localhost:8000/nusa/regencies?page=1", 371 | "label": "1", 372 | "active": true, 373 | }, 374 | { ... }, 375 | { 376 | "url": "http://localhost/nusa/regencies?page=2", 377 | "label": "Next »", 378 | "active": false, 379 | }, 380 | ], 381 | "path": "http://localhost:8000/nusa/regencies", 382 | "per_page": 15, 383 | "to": 15, 384 | "total": 514 385 | } 386 | } 387 | ``` 388 |
389 | 390 | - Show a regency 391 | 392 | `GET {APP_URL}/nusa/regencies/{regency}` 393 | 394 | - **Route Param :** `{regency}` regency code 395 | - **Example :** 396 | -
GET http://localhost:8000/nusa/regencies/3375 397 | 398 | ```jsonc 399 | { 400 | "data": { 401 | "code": 3375, 402 | "province_code": 33, 403 | "name": "Kota Pekalongan", 404 | "latitude": -6.8969497174987, 405 | "longitude": 109.66208089654, 406 | "coordinates": [...], 407 | "postal_codes": [...], 408 | }, 409 | "meta": {} 410 | } 411 | ``` 412 |
413 | 414 | - Get all districts in a regency 415 | 416 | `GET {APP_URL}/nusa/regencies/{regency}/districts` 417 | 418 | - **Route Param :** `{regency}` regency code 419 | - **Example :** 420 | -
GET http://localhost:8000/nusa/regencies/3375/districts 421 | 422 | ```jsonc 423 | { 424 | "data": [ 425 | { 426 | "code": 337501, 427 | "regency_code": 3375, 428 | "province_code": 33, 429 | "name": "Pekalongan Barat", 430 | "postal_codes": [ 431 | 51111 432 | 51112 433 | 51113 434 | 51116 435 | 51117 436 | 51151 437 | ] 438 | }, 439 | { ... } 440 | ], 441 | "links": { 442 | "first": "http://localhost:8000/nusa/regencies/3375/districts?page=1", 443 | "last": "http://localhost:8000/nusa/regencies/3375/districts?page=1", 444 | "prev": null, 445 | "next": null, 446 | }, 447 | "meta": { 448 | "current_page": 1, 449 | "from": 1, 450 | "last_page": 1, 451 | "links": [ 452 | { 453 | "url": null, 454 | "label": "« Previous", 455 | "active": false, 456 | }, 457 | { 458 | "url": "http://localhost:8000/nusa/regencies/3375/districts?page=1", 459 | "label": "1", 460 | "active": true, 461 | }, 462 | { ... }, 463 | { 464 | "url": null, 465 | "label": "Next »", 466 | "active": false, 467 | }, 468 | ], 469 | "path": "http://localhost:8000/nusa/regencies/3375/districts", 470 | "per_page": 15, 471 | "to": 4, 472 | "total": 4 473 | } 474 | } 475 | ``` 476 |
477 | 478 | - Get all villages in a regency 479 | 480 | `GET {APP_URL}/nusa/regencies/{regency}/villages` 481 | 482 | - **Route Param :** `{regency}` regency code 483 | - **Example :** 484 | -
GET http://localhost:8000/nusa/regencies/3375/villages 485 | 486 | ```jsonc 487 | { 488 | "data": [ 489 | { 490 | "code": 3375011002, 491 | "district_code": 337501, 492 | "regency_code": 3375, 493 | "province_code": 33, 494 | "name": "Medono", 495 | "postal_code": 51111, 496 | }, 497 | { ... } 498 | ], 499 | "links": { 500 | "first": "http://localhost:8000/nusa/regencies/3375/villages?page=1", 501 | "last": "http://localhost:8000/nusa/regencies/3375/villages?page=2", 502 | "prev": null, 503 | "next": "http://localhost:8000/nusa/regencies/3375/villages?page=2", 504 | }, 505 | "meta": { 506 | "current_page": 1, 507 | "from": 1, 508 | "last_page": 2, 509 | "links": [ 510 | { 511 | "url": null, 512 | "label": "« Previous", 513 | "active": false, 514 | }, 515 | { 516 | "url": "http://localhost:8000/nusa/regencies/3375/villages?page=1", 517 | "label": "1", 518 | "active": true, 519 | }, 520 | { ... }, 521 | { 522 | "url": "http://localhost:8000/nusa/regencies/3375/villages?page=2", 523 | "label": "Next »", 524 | "active": false, 525 | }, 526 | ], 527 | "path": "http://localhost:8000/nusa/regencies/3375/villages", 528 | "per_page": 15, 529 | "to": 15, 530 | "total": 27 531 | } 532 | } 533 | ``` 534 |
535 | 536 | - Get all districts 537 | 538 | `GET {APP_URL}/nusa/districts` 539 | 540 | - **Query Params :** 541 | 542 | | Field | Type | Option | Description | 543 | | --- | --- | --- | --- | 544 | | `codes` | `numeric[]` | `optional` | Fetch only specified district code | 545 | | `search` | `string` | `optional` | Search district by a keyword | 546 | - **Example :** 547 | -
GET http://localhost:8000/nusa/districts 548 | 549 | ```jsonc 550 | { 551 | "data": [ 552 | { 553 | "code": 110101, 554 | "regency_code": 1101, 555 | "province_code": 11, 556 | "name": "Bakongan", 557 | "postal_codes": [ 558 | 23773 559 | ], 560 | }, 561 | { ... } 562 | ], 563 | "links": { 564 | "first": "http://localhost:8000/nusa/districts?page=1", 565 | "last": "http://localhost:8000/nusa/districts?page=485", 566 | "prev": null, 567 | "next": "http://localhost:8000/nusa/districts?page=2", 568 | }, 569 | "meta": { 570 | "current_page": 1, 571 | "from": 1, 572 | "last_page": 485, 573 | "links": [ 574 | { 575 | "url": null, 576 | "label": "« Previous", 577 | "active": false, 578 | }, 579 | { 580 | "url": "http://localhost:8000/nusa/districts?page=1", 581 | "label": "1", 582 | "active": true, 583 | }, 584 | { ... }, 585 | { 586 | "url": "http://localhost:8000/nusa/districts?page=2", 587 | "label": "Next »", 588 | "active": false, 589 | }, 590 | ], 591 | "path": "http://localhost:8000/nusa/districts", 592 | "per_page": 15, 593 | "to": 15, 594 | "total": 7266 595 | } 596 | } 597 | ``` 598 |
599 | 600 | - Show a district 601 | 602 | `GET {APP_URL}/nusa/districts/{district}` 603 | 604 | - **Route Param :** `{district}` district code 605 | - **Example :** 606 | -
GET http://localhost:8000/nusa/districts/337503 607 | 608 | ```jsonc 609 | { 610 | "data": { 611 | "code": 337503, 612 | "regency_code": 3375, 613 | "province_code": 33, 614 | "name": "Pekalongan Utara", 615 | "postal_codes": [ 616 | 51141 617 | 51143 618 | 51146 619 | 51147 620 | 51148 621 | 51149 622 | ], 623 | }, 624 | "meta": {} 625 | } 626 | ``` 627 |
628 | 629 | - Get all villages in a district 630 | 631 | `GET {APP_URL}/nusa/districts/{district}/villages` 632 | 633 | - **Route Param :** `{district}` district code 634 | - **Example :** 635 | -
GET http://localhost:8000/nusa/districts/337503/villages 636 | 637 | ```jsonc 638 | { 639 | "data": [ 640 | { 641 | "code": 3375031002, 642 | "district_code": 337503, 643 | "regency_code": 3375, 644 | "province_code": 33, 645 | "name": "Krapyak", 646 | "postal_code": 51147, 647 | }, 648 | { ... } 649 | ], 650 | "links": { 651 | "first": "http://localhost:8000/nusa/districts/337503/villages?page=1", 652 | "last": "http://localhost:8000/nusa/districts/337503/villages?page=1", 653 | "prev": null, 654 | "next": null, 655 | }, 656 | "meta": { 657 | "current_page": 1, 658 | "from": 1, 659 | "last_page": 1, 660 | "links": [ 661 | { 662 | "url": null, 663 | "label": "« Previous", 664 | "active": false, 665 | }, 666 | { 667 | "url": "http://localhost:8000/nusa/districts/337503/villages?page=1", 668 | "label": "1", 669 | "active": true, 670 | }, 671 | { ... }, 672 | { 673 | "url": null, 674 | "label": "Next »", 675 | "active": false, 676 | }, 677 | ], 678 | "path": "http://localhost:8000/nusa/districts/337503/villages", 679 | "per_page": 15, 680 | "to": 7, 681 | "total": 7 682 | } 683 | } 684 | ``` 685 |
686 | 687 | - Get all villages 688 | 689 | `GET {APP_URL}/nusa/villages` 690 | 691 | - **Query Params :** 692 | 693 | | Field | Type | Option | Description | 694 | | --- | --- | --- | --- | 695 | | `codes` | `numeric[]` | `optional` | Fetch only specified village code | 696 | | `search` | `string` | `optional` | Search village by a keyword | 697 | - **Example :** 698 | -
GET http://localhost:8000/nusa/villages 699 | 700 | ```jsonc 701 | { 702 | "data": [ 703 | { 704 | "code": 1101012001, 705 | "district_code": 110101, 706 | "regency_code": 1101, 707 | "province_code": 11, 708 | "name": "Keude Bakongan", 709 | "postal_code": 23773, 710 | }, 711 | { ... } 712 | ], 713 | "links": { 714 | "first": "http://localhost:8000/nusa/villages?page=1", 715 | "last": "http://localhost:8000/nusa/villages?page=5565", 716 | "prev": null, 717 | "next": "http://localhost:8000/nusa/villages?page=2", 718 | }, 719 | "meta": { 720 | "current_page": 1, 721 | "from": 1, 722 | "last_page": 5565, 723 | "links": [ 724 | { 725 | "url": null, 726 | "label": "« Previous", 727 | "active": false, 728 | }, 729 | { 730 | "url": "http://localhost:8000/nusa/villages?page=1", 731 | "label": "1", 732 | "active": true, 733 | }, 734 | { ... }, 735 | { 736 | "url": "http://localhost:8000/nusa/villages?page=2", 737 | "label": "Next »", 738 | "active": false, 739 | }, 740 | ], 741 | "path": "http://localhost:8000/nusa/villages", 742 | "per_page": 15, 743 | "to": 15, 744 | "total": 83467 745 | } 746 | } 747 | ``` 748 |
749 | 750 | - Get a village 751 | 752 | `GET {APP_URL}/nusa/villages/{village}` 753 | 754 | - **Route Param :** `{village}` village code 755 | - **Example :** 756 | -
GET http://localhost:8000/nusa/villages/3375031006 757 | 758 | ```jsonc 759 | { 760 | "data": { 761 | "code": 3375031006, 762 | "district_code": 337503, 763 | "regency_code": 3375, 764 | "province_code": 33, 765 | "name": "Padukuhan Kraton", 766 | "postal_code": 51146, 767 | }, 768 | "meta": {} 769 | } 770 | ``` 771 |
772 | 773 | ### Models 774 | 775 | This library comes with 4 primary models as follows : 776 | 777 | - `Creasi\Nusa\Models\Province` 778 | - `Creasi\Nusa\Models\Regency` 779 | - `Creasi\Nusa\Models\District` 780 | - `Creasi\Nusa\Models\Village` 781 | 782 | Every models comes with similar interfaces, which mean every model has `code` and `name` field in it, 783 | you can also use `search()` scope method to query model either by `code` or `name`. e.g: 784 | 785 | ```php 786 | use Creasi\Nusa\Models\Province; 787 | 788 | $province = Province::search(33)->first(); 789 | 790 | // or 791 | 792 | $province = Province::search('Jawa Tengah')->first(); 793 | ``` 794 | 795 | Please note that only `Province` and `Regency` that comes with `latitude`, `longitude` and `coordinates` 796 | data, while `Village` comes with `postal_code`. That's due to what [cahyadsn/wilayah](https://github.com/cahyadsn/wilayah) provide us. 797 | 798 | In that regard we expect that `Province`, `Regency` and `District` should have access of any `postal_codes` 799 | that available within those area, and we believe that might be helpful in some cases. And there you go 800 | 801 | ```php 802 | // Retrieve distict list of postal codes available in the province 803 | $province->postal_codes; 804 | ``` 805 | 806 | Base on our experiences developing huge variety of products, the most use cases we need such a data 807 | is to fill up address form. But the requirement is might be vary on every single project. For that 808 | reason we also provide the bare minimun of `Address` model that use your default db connection and 809 | can easily extended to comply with project's requirement. 810 | 811 | In that case you might wanna use our `WithAddresses` or `WithAdress` trait to your exitsting model, like so 812 | 813 | ```php 814 | use Creasi\Nusa\Contracts\HasAddresses; 815 | use Creasi\Nusa\Models\Concerns\WithAddresses; 816 | 817 | class User extends Model implements HasAddresses 818 | { 819 | use WithAddresses; 820 | } 821 | ``` 822 | 823 | To be able to use `Address` model, all you need is to publish the migration, like so 824 | 825 | ```sh 826 | php artisan vendor:publish --tag creasi-migrations 827 | ``` 828 | 829 | Then simply run `artisan migrate` to apply the additional migrations. 830 | 831 | ### Databases 832 | 833 | The database structure documentation please consult to [`database/README.md`](https://github.com/creasico/laravel-nusa/blob/main/database/README.md). 834 | 835 | ## Customization 836 | 837 | By default, `nusa` will add another `database.connections` config to your project and use it as main 838 | database for all `nusa`'s models, and you can customize it anyway. 839 | 840 | 1. Publish `nusa`'s config by running the following commands 841 | 842 | ```sh 843 | ./artisan vendor:publish --tag creasi-nusa-config 844 | ``` 845 | 846 | 2. Add new `database.connections` with key of your `creasi.nusa.connection`, say you have 847 | 848 | ```php 849 | // config/nusa.php 850 | return [ 851 | 'connection' => 'indonesia', 852 | ]; 853 | ``` 854 | 855 | So, you'll need 856 | ```php 857 | // config/database.php 858 | return [ 859 | 'connections' => [ 860 | 'indonesia' => [ 861 | // ... 862 | ] 863 | ], 864 | ]; 865 | ``` 866 | 867 | In term of extending `Address` model, please a look at `creasi.nusa.addressable` config if you wanna 868 | use your own implementation of `Address` model. 869 | 870 | ### Notes 871 | As of now, only `connection` name and `table` names are available to customize, also we only test it 872 | using `sqlite` driver. Let us know if you had any issue using another database drivers. 873 | 874 | ## Contributing 875 | 876 | 1. Clone the repo and `cd` into it 877 | 878 | ```sh 879 | git clone --recurse-submodules git@github.com:creasico/laravel-nusa.git 880 | ``` 881 | 882 | 2. Install dependencies 883 | 884 | ```sh 885 | composer install && pnpm install 886 | ``` 887 | 888 | 3. Update `workbench/.env` as you desire 889 | 890 | ```sh 891 | DB_CONNECTION=mysql # Your main db connection to test againsts 892 | DB_HOST=127.0.0.1 # Change this to `upstream` if you wanna use your docker 893 | DB_DATABASE=testing # To store the testing data 894 | DB_USERNAME=creasi 895 | DB_PASSWORD=secret 896 | 897 | UPSTREAM_DB_DATABASE=nusantara # To store the upstream data 898 | ``` 899 | 900 | 4. Init the databases, here you have 2 options: 901 | 902 | 1. Use your local MySQL server. 903 | 904 | All you need is create 2 database for testing and upstream as you define in `workbench/.env` file 905 | 906 | ```sh 907 | mysql -e 'create database testing;' # Based on the value of `DB_DATABASE` 908 | mysql -e 'create database nusantara;' # Based on the value of `UPSTREAM_DB_DATABASE` 909 | ``` 910 | 911 | Once you've done, run the following command to import the upstream db. 912 | 913 | ```sh 914 | composer testbench nusa:import -- --fresh 915 | ``` 916 | 917 | 2. Use our provided docker `compose.yaml` by simply run the following command 918 | 919 | > [!IMPORTANT] 920 | > Make sure that the value of `DB_HOST` is set to service name on `compose.yaml` which is `upstream` 921 | 922 | ```sh 923 | composer upstream:up 924 | ``` 925 | 926 | The command will create all required database and run the `nusa:import` command for you, and use the following command to stop the container. 927 | 928 | ```sh 929 | composer upstream:down 930 | ``` 931 | 932 | As you might noticed that we use 3 different databases to develop and maintain this library. Here's the explanation : 933 | 934 | - `database/nusa.sqlite` is the main database in this library that will be included when you install this library as a dependency 935 | - `DB_DATABASE` is mainly for testing purposes, it simulate the actual application where this library installed on 936 | - `UPSTREAM_DB_DATABASE` is contains the upstream database tables that will be used as source of truth for this library 937 | 938 | Once you've done with your meaningful contributions, run the following command to ensure everythings is works as expected. 939 | 940 | ```sh 941 | composer test 942 | ``` 943 | 944 | > [!IMPORTANT] 945 | > - **Commit Convention**: This project follows [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) 946 | > using [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/master/@commitlint/config-conventional) as standart, so make sure you install its npm dependencies. 947 | > - **Code Style**: This project uses [`Laravel Pint`](https://laravel.com/docs/pint) with `laravel` preset as coding standard, so make sure you follow the rules. 948 | > - Anytime you make a change on `workbench/.env` file, make sure to run `composer testbench:purge`. 949 | 950 | ## GitHub Actions Workflows 951 | 952 | This project includes automated workflows for maintaining the Indonesian administrative database, especially when upstream data sources are updated by Dependabot. 953 | 954 | ### Automated Distribution Database Updates 955 | 956 | When Dependabot updates the upstream submodules containing Indonesian administrative data, our workflows automatically: 957 | 958 | 1. **Import fresh data** from updated submodules 959 | 2. **Create distribution database** with coordinates removed for privacy 960 | 3. **Commit updated databases** back to the repository 961 | 4. **Upload artifacts** for backup and distribution 962 | 963 | ### Workflow Files 964 | 965 | #### `merged.yml` - General Merge Handler 966 | - **Triggers**: All pushes to `main` branch 967 | - **Purpose**: Creates distribution database for any merge to main 968 | - Detects if the merge was from Dependabot and handles accordingly 969 | 970 | #### `dependabot-merged.yml` - Dependabot Specialist 971 | - **Triggers**: Pushes to `main` affecting submodules or `.gitmodules` 972 | - **Purpose**: Handles submodule updates with fresh data import 973 | - Runs `nusa:import --fresh` and `nusa:dist --force` automatically 974 | 975 | ### Database Files Managed 976 | 977 | - **Development Database**: `database/nusa.{branch-name}.sqlite` (~407MB with coordinates) 978 | - **Distribution Database**: `database/nusa.sqlite` (~10MB without coordinates) 979 | 980 | ### Scripts 981 | 982 | All workflow logic is organized in reusable scripts in `.github/scripts/`: 983 | 984 | - `check-dependabot.sh` - Detects Dependabot updates 985 | - `update-databases.sh` - Handles database import and distribution creation 986 | - `check-changes.sh` - Analyzes database changes 987 | - `commit-changes.sh` - Commits changes with appropriate messages 988 | - `create-summary.sh` - Generates comprehensive workflow reports 989 | 990 | ### Testing Workflows Locally 991 | 992 | You can test these workflows locally using [`act`](https://github.com/nektos/act): 993 | 994 | ```bash 995 | # Install act (macOS) 996 | brew install act 997 | 998 | # List available workflows 999 | act -l --container-architecture linux/amd64 1000 | 1001 | # Test dependabot workflow (dry run) 1002 | act push -W .github/workflows/dependabot-merged.yml --container-architecture linux/amd64 -n 1003 | 1004 | # Test main workflow 1005 | act push -W .github/workflows/merged.yml --container-architecture linux/amd64 -n 1006 | ``` 1007 | 1008 | ### What Happens When Dependabot Updates Submodules 1009 | 1010 | 1. Dependabot creates PR updating `workbench/submodules/cahyadsn-wilayah` (or other submodules) 1011 | 2. PR gets merged to main branch 1012 | 3. `dependabot-merged.yml` workflow triggers automatically 1013 | 4. Fresh upstream data import runs: `testbench nusa:import --fresh` 1014 | 5. Distribution database creation runs: `testbench nusa:dist --force` 1015 | 6. Updated databases are committed with detailed commit messages 1016 | 7. Database artifacts are uploaded for download/backup 1017 | 8. Comprehensive summary is generated with database statistics 1018 | 1019 | This ensures the distribution database stays current with upstream changes while maintaining data privacy by removing coordinate information. 1020 | 1021 | ## Credits 1022 | - [cahyadsn/wilayah](https://github.com/cahyadsn/wilayah) [![License](https://img.shields.io/github/license/cahyadsn/wilayah?style=flat-square)](https://github.com/cahyadsn/wilayah/blob/master/LICENSE) 1023 | - [cahyadsn/wilayah_kodepos](https://github.com/cahyadsn/wilayah_kodepos) [![License](https://img.shields.io/github/license/cahyadsn/wilayah_kodepos?style=flat-square)](https://github.com/cahyadsn/wilayah_kodepos/blob/master/LICENSE) 1024 | - [cahyadsn/wilayah_boundaries](https://github.com/cahyadsn/wilayah_boundaries) [![License](https://img.shields.io/github/license/cahyadsn/wilayah_boundaries?style=flat-square)](https://github.com/cahyadsn/wilayah_boundaries/blob/master/LICENSE) 1025 | - [w3appdev/kodepos](https://github.com/w3appdev/kodepos) 1026 | - [edwardsamuel/Wilayah-Administratif-Indonesia](https://github.com/edwardsamuel/Wilayah-Administratif-Indonesia) [![License](https://img.shields.io/github/license/edwardsamuel/Wilayah-Administratif-Indonesia?style=flat-square)](https://github.com/edwardsamuel/Wilayah-Administratif-Indonesia/blob/master/license.md) 1027 | - [laravolt/indonesia](https://github.com/laravolt/indonesia) [![License](https://img.shields.io/github/license/laravolt/indonesia?style=flat-square)](https://github.com/laravolt/indonesia/blob/master/LICENSE) 1028 | 1029 | ## License 1030 | 1031 | This library is open-sourced software licensed under [MIT license](LICENSE). -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | upstream: 3 | image: mysql:8.0 4 | container_name: upstream-mysql 5 | extra_hosts: 6 | - host.docker.internal:host-gateway 7 | ports: 8 | - ${FORWARD_DB_PORT:-3306}:3306 9 | environment: 10 | MYSQL_USER: ${DB_USERNAME} 11 | MYSQL_PASSWORD: ${DB_PASSWORD} 12 | MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} 13 | MYSQL_DATABASE: ${UPSTREAM_DB_DATABASE} 14 | env_file: workbench/.env 15 | volumes: 16 | - upstream:/var/lib/mysql 17 | - ./workbench/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh 18 | healthcheck: 19 | test: [CMD, mysqladmin, ping, "-p${DB_PASSWORD}"] 20 | retries: 3 21 | timeout: 5s 22 | 23 | networks: 24 | default: 25 | name: nusa-upstream 26 | enable_ipv6: false 27 | 28 | volumes: 29 | upstream: 30 | driver: local 31 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "creasi/laravel-nusa", 3 | "description": "A Laravel package that aim to provide Indonesia' Administrative Data", 4 | "keywords": [ 5 | "laravel", 6 | "package", 7 | "indonesia", 8 | "provinsi", 9 | "kabupaten", 10 | "kota", 11 | "kecamatan", 12 | "kelurahan", 13 | "desa" 14 | ], 15 | "license": "MIT", 16 | "type": "library", 17 | "authors": [ 18 | { 19 | "name": "Creasi Developers", 20 | "email": "developers@creasi.co" 21 | } 22 | ], 23 | "funding": [ 24 | { 25 | "type": "github", 26 | "url": "https://github.com/sponsors/creasico" 27 | } 28 | ], 29 | "support": { 30 | "source": "https://github.com/creasico/laravel-nusa", 31 | "forum": "https://github.com/orgs/creasico/discussions", 32 | "issues": "https://github.com/creasico/laravel-nusa/issues" 33 | }, 34 | "scripts": { 35 | "post-autoload-dump": [ 36 | "testbench vendor:publish --tag=creasi-migrations" 37 | ], 38 | "post-install-cmd": [ 39 | "@php -r \"file_exists('workbench/.env') || copy('workbench/.env.example', 'workbench/.env');\"" 40 | ], 41 | "fix": [ 42 | "pint --preset laravel" 43 | ], 44 | "test": [ 45 | "Composer\\Config::disableProcessTimeout", 46 | "testbench package:test --ansi" 47 | ], 48 | "testbench": [ 49 | "Composer\\Config::disableProcessTimeout", 50 | "testbench" 51 | ], 52 | "testbench:purge": [ 53 | "testbench workbench:purge-skeleton" 54 | ], 55 | "tinker": [ 56 | "Composer\\Config::disableProcessTimeout", 57 | "testbench tinker" 58 | ], 59 | "upstream": [ 60 | "docker compose --env-file workbench/.env" 61 | ], 62 | "upstream:up": [ 63 | "Composer\\Config::disableProcessTimeout", 64 | "docker compose --env-file workbench/.env up -d", 65 | "@php -r \"sleep(3);\"", 66 | "testbench nusa:import --fresh" 67 | ], 68 | "upstream:down": [ 69 | "docker compose --env-file workbench/.env down", 70 | "testbench workbench:purge-skeleton" 71 | ] 72 | }, 73 | "autoload": { 74 | "psr-4": { 75 | "Creasi\\Nusa\\": "src" 76 | } 77 | }, 78 | "autoload-dev": { 79 | "psr-4": { 80 | "Creasi\\Tests\\": "tests", 81 | "Database\\Seeders\\": "database/seeders/", 82 | "Workbench\\App\\": "workbench/app/" 83 | } 84 | }, 85 | "require": { 86 | "php": "^8.2", 87 | "ext-sqlite3": "*", 88 | "laravel/framework": "^9.0|^10.0|^11.0|^12.0" 89 | }, 90 | "require-dev": { 91 | "composer-runtime-api": "*", 92 | "laravel/pint": "^1.1", 93 | "nunomaduro/collision": "^7.4|^8.0", 94 | "orchestra/testbench": "^8.5|^9.0|^10.0" 95 | }, 96 | "config": { 97 | "preferred-install": "dist", 98 | "sort-packages": true 99 | }, 100 | "extra": { 101 | "laravel": { 102 | "providers": [ 103 | "Creasi\\Nusa\\ServiceProvider" 104 | ] 105 | } 106 | }, 107 | "minimum-stability": "dev", 108 | "prefer-stable": true 109 | } 110 | -------------------------------------------------------------------------------- /config/nusa.php: -------------------------------------------------------------------------------- 1 | env('CREASI_NUSA_CONNECTION', 'nusa'), 10 | 11 | 'table_names' => [ 12 | 'provinces' => 'provinces', 13 | 'districts' => 'districts', 14 | 'regencies' => 'regencies', 15 | 'villages' => 'villages', 16 | ], 17 | 18 | 'addressable' => Address::class, 19 | 20 | 'routes_enable' => env('CREASI_NUSA_ROUTES_ENABLE', true), 21 | 22 | 'routes_prefix' => env('CREASI_NUSA_ROUTES_PREFIX', 'nusa'), 23 | ]; 24 | -------------------------------------------------------------------------------- /database/README.md: -------------------------------------------------------------------------------- 1 | # Database Structure 2 | 3 | ```mermaid 4 | classDiagram 5 | Province "1" --> "*" Regency 6 | Province "1" --> "*" District 7 | Province "1" --> "*" Village 8 | Regency "1" --> "*" Village 9 | Regency "1" --> "*" District 10 | District "1" --> "*" Village 11 | 12 | class Province { 13 | +int code 14 | +string name 15 | +double latitude 16 | +double longitude 17 | +array coordinates 18 | +regencies() Regency[] 19 | +districts() District[] 20 | +villages() Village[] 21 | } 22 | class Regency { 23 | +int code 24 | +int province_code 25 | +string name 26 | +double latitude 27 | +double longitude 28 | +array coordinates 29 | +province() Province 30 | +districts() District[] 31 | +villages() Village[] 32 | } 33 | class District { 34 | +int code 35 | +int regency_code 36 | +int province_code 37 | +string name 38 | +double latitude 39 | +double longitude 40 | +province() Province 41 | +regency() Regency 42 | +villages() Village[] 43 | } 44 | class Village { 45 | +int code 46 | +int district_code 47 | +int regency_code 48 | +int province_code 49 | +string name 50 | +double latitude 51 | +double longitude 52 | +int postal_code 53 | +province() Province 54 | +regency() Regency 55 | +district() District 56 | } 57 | ``` 58 | 59 | ## `provinces` 60 | 61 | | Field | Attribute | Key | Description | 62 | | --- | --- | --- | --- | 63 | | `code` | `char(2)` | `primary` | - | 64 | | `name` | `varchar` | - | - | 65 | | `latitude` | `double`, `nullable` | - | - | 66 | | `longitude` | `double`, `nullable` | - | - | 67 | | `coordinates` | `array`, `nullable` | - | - | 68 | 69 | ## `regencies` 70 | 71 | | Field | Attribute | Key | Description | 72 | | --- | --- | --- | --- | 73 | | `code` | `char(4)` | `primary` | - | 74 | | `province_code` | `char(2)` | `foreign` | - | 75 | | `name` | `varchar` | - | - | 76 | | `latitude` | `double`, `nullable` | - | - | 77 | | `longitude` | `double`, `nullable` | - | - | 78 | | `coordinates` | `array`, `nullable` | - | - | 79 | 80 | **Relation Properties** 81 | - `province_code` : reference `provinces` 82 | 83 | ## `districts` 84 | 85 | | Field | Attribute | Key | Description | 86 | | --- | --- | --- | --- | 87 | | `code` | `char(6)` | `primary` | - | 88 | | `regency_code` | `char(4)` | `foreign` | - | 89 | | `province_code` | `char(2)` | `foreign` | - | 90 | | `name` | `varchar` | - | - | 91 | | `latitude` | `double`, `nullable` | - | - | 92 | | `longitude` | `double`, `nullable` | - | - | 93 | 94 | **Relation Properties** 95 | - `regency_code` : reference `regencies` 96 | - `province_code` : reference `provinces` 97 | 98 | ## `villages` 99 | 100 | | Field | Attribute | Key | Description | 101 | | --- | --- | --- | --- | 102 | | `code` | `char(10)` | `primary` | - | 103 | | `district_code` | `char(6)` | `foreign` | - | 104 | | `regency_code` | `char(4)` | `foreign` | - | 105 | | `province_code` | `char(2)` | `foreign` | - | 106 | | `name` | `varchar` | - | - | 107 | | `latitude` | `double`, `nullable` | - | - | 108 | | `longitude` | `double`, `nullable` | - | - | 109 | | `postal_code` | `char(5)`, `nullable` | - | - | 110 | 111 | **Relation Properties** 112 | - `district_code` : reference `districts` 113 | - `regency_code` : reference `regencies` 114 | - `province_code` : reference `provinces` 115 | -------------------------------------------------------------------------------- /database/migrations/create_addresses_tables.php.stub: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->nullableMorphs('addressable'); 17 | $table->string('line'); 18 | $table->char('village_code', 10)->nullable(); 19 | $table->char('district_code', 6)->nullable(); 20 | $table->char('regency_code', 4)->nullable(); 21 | $table->char('province_code', 2)->nullable(); 22 | $table->char('postal_code', 5)->nullable(); 23 | 24 | $table->timestamps(); 25 | }); 26 | 27 | Schema::create('has_one_addresses', function (Blueprint $table) { 28 | $table->id(); 29 | }); 30 | 31 | Schema::create('has_many_addresses', function (Blueprint $table) { 32 | $table->id(); 33 | }); 34 | } 35 | 36 | /** 37 | * Reverse the migrations. 38 | */ 39 | public function down(): void 40 | { 41 | Schema::dropIfExists('has_many_addresses'); 42 | Schema::dropIfExists('has_one_addresses'); 43 | Schema::dropIfExists('addresses'); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /database/nusa.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creasico/laravel-nusa/5d4ea3a5f046f66067825360d4f3254752d2a3ac/database/nusa.sqlite -------------------------------------------------------------------------------- /resources/lang/en/nusa.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'label' => 'RT', 6 | 'placeholder' => '000', 7 | ], 8 | 9 | 'rw' => [ 10 | 'label' => 'RW', 11 | 'placeholder' => '000', 12 | ], 13 | 14 | 'address_line' => [ 15 | 'label' => 'Address Line', 16 | 'placeholder' => 'Address Line/Street/Block and Number', 17 | ], 18 | 19 | 'province_code' => [ 20 | 'label' => 'Province', 21 | 'placeholder' => 'Choose Province', 22 | ], 23 | 24 | 'regency_code' => [ 25 | 'label' => 'Regency', 26 | 'placeholder' => 'Choose Regency', 27 | ], 28 | 29 | 'district_code' => [ 30 | 'label' => 'District', 31 | 'placeholder' => 'Choose District', 32 | ], 33 | 34 | 'village_code' => [ 35 | 'label' => 'Village', 36 | 'placeholder' => 'Choose Village', 37 | ], 38 | 39 | 'postal_code' => [ 40 | 'label' => 'Postal Code', 41 | 'placeholder' => '5 Digit Postal Code', 42 | ], 43 | ]; 44 | -------------------------------------------------------------------------------- /resources/lang/id/nusa.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'label' => 'RT', 6 | 'placeholder' => '000', 7 | ], 8 | 9 | 'rw' => [ 10 | 'label' => 'RW', 11 | 'placeholder' => '000', 12 | ], 13 | 14 | 'address_line' => [ 15 | 'label' => 'Alamat Lengkap', 16 | 'placeholder' => 'Alamat Jalan/Gang/Blok dan Nomor Rumah', 17 | ], 18 | 19 | 'province_code' => [ 20 | 'label' => 'Propinsi', 21 | 'placeholder' => 'Pilih Propinsi', 22 | ], 23 | 24 | 'regency_code' => [ 25 | 'label' => 'Kota/Kabupaten', 26 | 'placeholder' => 'Pilih Kota/Kabupaten', 27 | ], 28 | 29 | 'district_code' => [ 30 | 'label' => 'Kecamatan', 31 | 'placeholder' => 'Pilih Kecamatan', 32 | ], 33 | 34 | 'village_code' => [ 35 | 'label' => 'Kelurahan', 36 | 'placeholder' => 'Pilih Kelurahan', 37 | ], 38 | 39 | 'postal_code' => [ 40 | 'label' => 'Kode Pos', 41 | 'placeholder' => '5 Digit Kode Pos', 42 | ], 43 | ]; 44 | -------------------------------------------------------------------------------- /routes/nusa.php: -------------------------------------------------------------------------------- 1 | prefix('provinces')->group(function () { 10 | Route::get('', 'index')->name('provinces.index'); 11 | Route::get('{province}', 'show')->name('provinces.show'); 12 | Route::get('{province}/regencies', 'regencies')->name('provinces.regencies'); 13 | Route::get('{province}/districts', 'districts')->name('provinces.districts'); 14 | Route::get('{province}/villages', 'villages')->name('provinces.villages'); 15 | }); 16 | 17 | Route::controller(RegencyController::class)->prefix('regencies')->group(function () { 18 | Route::get('', 'index')->name('regencies.index'); 19 | Route::get('{regency}', 'show')->name('regencies.show'); 20 | Route::get('{regency}/districts', 'districts')->name('regencies.districts'); 21 | Route::get('{regency}/villages', 'villages')->name('regencies.villages'); 22 | }); 23 | 24 | Route::controller(DistrictController::class)->prefix('districts')->group(function () { 25 | Route::get('', 'index')->name('districts.index'); 26 | Route::get('{district}', 'show')->name('districts.show'); 27 | Route::get('{district}/villages', 'villages')->name('districts.villages'); 28 | }); 29 | 30 | Route::controller(VillageController::class)->prefix('villages')->group(function () { 31 | Route::get('', 'index')->name('villages.index'); 32 | Route::get('{village}', 'show')->name('villages.show'); 33 | }); 34 | -------------------------------------------------------------------------------- /src/Contracts/Address.php: -------------------------------------------------------------------------------- 1 | $villages 13 | * 14 | * @mixin \Creasi\Nusa\Models\Model 15 | */ 16 | interface District 17 | { 18 | /** 19 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Province 20 | */ 21 | public function province(); 22 | 23 | /** 24 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Regency 25 | */ 26 | public function regency(); 27 | 28 | /** 29 | * @return \Illuminate\Database\Eloquent\Relations\HasMany|Village 30 | */ 31 | public function villages(); 32 | } 33 | -------------------------------------------------------------------------------- /src/Contracts/HasAddress.php: -------------------------------------------------------------------------------- 1 | $addresses 9 | * 10 | * @mixin \Illuminate\Database\Eloquent\Model 11 | */ 12 | interface HasAddresses 13 | { 14 | /** 15 | * @return \Illuminate\Database\Eloquent\Relations\MorphMany|Address 16 | */ 17 | public function addresses(); 18 | } 19 | -------------------------------------------------------------------------------- /src/Contracts/HasCoordinate.php: -------------------------------------------------------------------------------- 1 | $regencies 9 | * @property-read \Illuminate\Support\Collection $districts 10 | * @property-read \Illuminate\Support\Collection $villages 11 | * 12 | * @mixin \Creasi\Nusa\Models\Model 13 | */ 14 | interface Province 15 | { 16 | /** 17 | * @return \Illuminate\Database\Eloquent\Relations\HasMany|Regency 18 | */ 19 | public function regencies(); 20 | 21 | /** 22 | * @return \Illuminate\Database\Eloquent\Relations\HasMany|District 23 | */ 24 | public function districts(); 25 | 26 | /** 27 | * @return \Illuminate\Database\Eloquent\Relations\HasMany|Village 28 | */ 29 | public function villages(); 30 | } 31 | -------------------------------------------------------------------------------- /src/Contracts/Regency.php: -------------------------------------------------------------------------------- 1 | $districts 11 | * @property-read \Illuminate\Support\Collection $villages 12 | * 13 | * @mixin \Creasi\Nusa\Models\Model 14 | */ 15 | interface Regency 16 | { 17 | /** 18 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Province 19 | */ 20 | public function province(); 21 | 22 | /** 23 | * @return \Illuminate\Database\Eloquent\Relations\HasMany|District 24 | */ 25 | public function districts(); 26 | 27 | /** 28 | * @return \Illuminate\Database\Eloquent\Relations\HasMany|Village 29 | */ 30 | public function villages(); 31 | } 32 | -------------------------------------------------------------------------------- /src/Contracts/Village.php: -------------------------------------------------------------------------------- 1 | relations($this->model); 20 | 21 | return NusaResource::collection($request->apply($this->model)); 22 | } 23 | 24 | public function show(NusaRequest $request, string $district) 25 | { 26 | $district = $this->model->findOrFail($district); 27 | 28 | $request->relations($district); 29 | 30 | return new NusaResource($district); 31 | } 32 | 33 | public function villages(NusaRequest $request, string $district) 34 | { 35 | $district = $this->model->findOrFail($district); 36 | 37 | return NusaResource::collection( 38 | $request->apply($district->villages()) 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Http/Controllers/ProvinceController.php: -------------------------------------------------------------------------------- 1 | relations($this->model); 20 | 21 | return NusaResource::collection($request->apply($this->model)); 22 | } 23 | 24 | public function show(NusaRequest $request, string $province) 25 | { 26 | $province = $this->model->findOrFail($province); 27 | 28 | $request->relations($province); 29 | 30 | return new NusaResource($province); 31 | } 32 | 33 | public function regencies(NusaRequest $request, string $province) 34 | { 35 | $province = $this->model->findOrFail($province); 36 | 37 | return NusaResource::collection( 38 | $request->apply($province->regencies()) 39 | ); 40 | } 41 | 42 | public function districts(NusaRequest $request, string $province) 43 | { 44 | $province = $this->model->findOrFail($province); 45 | 46 | return NusaResource::collection( 47 | $request->apply($province->districts()) 48 | ); 49 | } 50 | 51 | public function villages(NusaRequest $request, string $province) 52 | { 53 | $province = $this->model->findOrFail($province); 54 | 55 | return NusaResource::collection( 56 | $request->apply($province->villages()) 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Http/Controllers/RegencyController.php: -------------------------------------------------------------------------------- 1 | relations($this->model); 20 | 21 | return NusaResource::collection($request->apply($this->model)); 22 | } 23 | 24 | public function show(NusaRequest $request, string $regency) 25 | { 26 | $regency = $this->model->findOrFail($regency); 27 | 28 | $request->relations($regency); 29 | 30 | return new NusaResource($regency); 31 | } 32 | 33 | public function districts(NusaRequest $request, string $regency) 34 | { 35 | $regency = $this->model->findOrFail($regency); 36 | 37 | return NusaResource::collection( 38 | $request->apply($regency->districts()) 39 | ); 40 | } 41 | 42 | public function villages(NusaRequest $request, string $regency) 43 | { 44 | $regency = $this->model->findOrFail($regency); 45 | 46 | return NusaResource::collection( 47 | $request->apply($regency->villages()) 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Http/Controllers/VillageController.php: -------------------------------------------------------------------------------- 1 | relations($this->model); 20 | 21 | return NusaResource::collection($request->apply($this->model)); 22 | } 23 | 24 | public function show(NusaRequest $request, string $village) 25 | { 26 | $village = $this->model->findOrFail($village); 27 | 28 | $request->relations($village); 29 | 30 | return new NusaResource($village); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Http/Requests/NusaRequest.php: -------------------------------------------------------------------------------- 1 | ['nullable', 'array'], 16 | 'with.*' => ['string'], 17 | 'codes' => ['nullable', 'array'], 18 | 'codes.*' => ['string'], 19 | 'search' => ['nullable', 'string'], 20 | 'postal_code' => ['nullable', 'numeric', 'digits:5'], 21 | 'page' => ['nullable', 'numeric'], 22 | 'per-page' => ['nullable', 'numeric'], 23 | ]; 24 | } 25 | 26 | /** 27 | * @param \Creasi\Nusa\Models\Model $model 28 | * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator 29 | */ 30 | public function apply($model) 31 | { 32 | $query = $model instanceof HasMany 33 | ? $model 34 | : $model->load($this->relations($model))->query(); 35 | 36 | $result = $query 37 | ->when($this->filled('search'), function (Builder $query) { 38 | $query->search($this->query('search')); 39 | }) 40 | ->when($this->filled('codes'), function (Builder $query) { 41 | $query->whereIn($query->getModel()->getKeyName(), $this->query('codes', [])); 42 | }) 43 | ->when($this->filled('postal_code') && $model instanceof Village, function (Builder $query) { 44 | $query->where('postal_code', $this->query('postal_code')); 45 | }); 46 | 47 | return $result->paginate($this->query('per-page')); 48 | } 49 | 50 | /** 51 | * @param \Creasi\Nusa\Models\Model $model 52 | * @return string[] 53 | */ 54 | public function relations($model): array 55 | { 56 | $relations = \array_filter( 57 | (array) $this->query('with', []), 58 | fn (string $relate) => \method_exists($model, $relate) 59 | ); 60 | 61 | $model->load($relations); 62 | 63 | return $relations; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Http/Resources/NusaResource.php: -------------------------------------------------------------------------------- 1 | additional([ 20 | 'meta' => [], 21 | ]); 22 | } 23 | 24 | public function toArray(Request $request): array 25 | { 26 | $with = $request->query('with', []); 27 | $arr = $this->normalize($this->resource, $with); 28 | 29 | foreach ($with as $relation) { 30 | if (\in_array($relation, ['postal_codes', 'coordinates'], true)) { 31 | continue; 32 | } 33 | 34 | $arr[$relation] = $this->whenLoaded($relation, function () use ($relation, $with) { 35 | $relate = $this->resource->getRelation($relation); 36 | 37 | if ($relate instanceof Model) { 38 | return $this->normalize($relate, $with); 39 | } 40 | 41 | return $relate->map(fn (Model $model) => $this->normalize($model, $with)); 42 | }); 43 | } 44 | 45 | return $arr; 46 | } 47 | 48 | /** 49 | * @param string[] $with 50 | * @return array 51 | */ 52 | private function normalize(Model $resource, array $with = []): array 53 | { 54 | $arr = \array_filter([ 55 | $resource->getKeyName() => $resource->getKey(), 56 | 'name' => $resource->name, 57 | 'district_code' => $resource->district_code, 58 | 'regency_code' => $resource->regency_code, 59 | 'province_code' => $resource->province_code, 60 | 'postal_code' => $this->isVillage($resource) ? $resource->postal_code : null, 61 | 'latitude' => $resource->latitude, 62 | 'longitude' => $resource->longitude, 63 | ]); 64 | 65 | if (! $this->isVillage($resource)) { 66 | if (\in_array('postal_codes', $with)) { 67 | $arr['postal_codes'] = $resource->postal_codes->toArray(); 68 | } 69 | 70 | if (\in_array('coordinates', $with)) { 71 | $arr['coordinates'] = $resource->coordinates; 72 | } 73 | } 74 | 75 | return $arr; 76 | } 77 | 78 | private function isVillage(Model $resource): bool 79 | { 80 | return $resource instanceof Village; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Models/Address.php: -------------------------------------------------------------------------------- 1 | */ 23 | use Concerns\WithDistrict; 24 | 25 | /** @use Concerns\WithProvince */ 26 | use Concerns\WithProvince; 27 | 28 | /** @use Concerns\WithRegency */ 29 | use Concerns\WithRegency; 30 | 31 | /** @use Concerns\WithVillage */ 32 | use Concerns\WithVillage; 33 | 34 | public function getCasts() 35 | { 36 | return \array_merge(parent::getCasts(), [ 37 | 'postal_code' => 'int', 38 | ]); 39 | } 40 | 41 | public function getFillable() 42 | { 43 | return \array_merge(parent::getFillable(), ['line', 'postal_code']); 44 | } 45 | 46 | public function associateWith( 47 | Village $village, 48 | ?District $district = null, 49 | ?Regency $regency = null, 50 | ?Province $province = null, 51 | ) { 52 | $this->village()->associate($village); 53 | 54 | if (! $district) { 55 | $district = $village->district; 56 | } 57 | 58 | $this->district()->associate($district); 59 | 60 | if (! $regency) { 61 | $regency = $village->regency; 62 | } 63 | 64 | $this->regency()->associate($regency); 65 | 66 | if (! $province) { 67 | $province = $village->province; 68 | } 69 | 70 | $this->province()->associate($province); 71 | 72 | return $this->fresh(); 73 | } 74 | 75 | /** 76 | * @return MorphTo 77 | */ 78 | public function addressable(): MorphTo 79 | { 80 | return $this->morphTo('addressable'); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Models/Concerns/WithAddress.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | public function address(): MorphOne 21 | { 22 | return $this->morphOne(\config('creasi.nusa.addressable'), 'addressable'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Models/Concerns/WithAddresses.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | public function addresses(): MorphMany 21 | { 22 | return $this->morphMany(\config('creasi.nusa.addressable'), 'addressable'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Models/Concerns/WithCoordinate.php: -------------------------------------------------------------------------------- 1 | mergeCasts([ 20 | 'latitude' => 'float', 21 | 'longitude' => 'float', 22 | ]); 23 | 24 | $this->mergeFillable([ 25 | 'latitude', 26 | 'longitude', 27 | ]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Models/Concerns/WithDistrict.php: -------------------------------------------------------------------------------- 1 | mergeFillable([ 25 | $this->districtKeyName(), 26 | ]); 27 | } 28 | 29 | /** 30 | * @return BelongsTo 31 | */ 32 | public function district(): BelongsTo 33 | { 34 | return $this->belongsTo(District::class, $this->districtKeyName()); 35 | } 36 | 37 | protected function districtKeyName(): string 38 | { 39 | return \property_exists($this, 'districtKey') ? $this->districtKey : 'district_code'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Models/Concerns/WithDistricts.php: -------------------------------------------------------------------------------- 1 | $districts 14 | * 15 | * @mixin \Illuminate\Database\Eloquent\Model 16 | */ 17 | trait WithDistricts 18 | { 19 | /** 20 | * @return HasMany 21 | */ 22 | public function districts(): HasMany 23 | { 24 | return $this->hasMany(District::class); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Models/Concerns/WithProvince.php: -------------------------------------------------------------------------------- 1 | mergeFillable([ 25 | $this->provinceKeyName(), 26 | ]); 27 | } 28 | 29 | /** 30 | * @return BelongsTo 31 | */ 32 | public function province(): BelongsTo 33 | { 34 | return $this->belongsTo(Province::class, $this->provinceKeyName()); 35 | } 36 | 37 | protected function provinceKeyName(): string 38 | { 39 | return \property_exists($this, 'provinceKey') ? $this->provinceKey : 'province_code'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Models/Concerns/WithRegency.php: -------------------------------------------------------------------------------- 1 | mergeFillable([ 25 | $this->regencyKeyName(), 26 | ]); 27 | } 28 | 29 | /** 30 | * @return BelongsTo 31 | */ 32 | public function regency(): BelongsTo 33 | { 34 | return $this->belongsTo(Regency::class, $this->regencyKeyName()); 35 | } 36 | 37 | protected function regencyKeyName(): string 38 | { 39 | return \property_exists($this, 'regencyKey') ? $this->regencyKey : 'regency_code'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Models/Concerns/WithVillage.php: -------------------------------------------------------------------------------- 1 | mergeFillable([ 25 | $this->villageKeyName(), 26 | ]); 27 | } 28 | 29 | /** 30 | * @return BelongsTo 31 | */ 32 | public function village(): BelongsTo 33 | { 34 | return $this->belongsTo(Village::class, $this->villageKeyName()); 35 | } 36 | 37 | protected function villageKeyName(): string 38 | { 39 | return \property_exists($this, 'villageKey') ? $this->villageKey : 'village_code'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Models/Concerns/WithVillages.php: -------------------------------------------------------------------------------- 1 | $postal_codes 15 | * @property-read \Illuminate\Database\Eloquent\Collection $villages 16 | * 17 | * @mixin \Illuminate\Database\Eloquent\Model 18 | */ 19 | trait WithVillages 20 | { 21 | /** 22 | * Initialize the trait. 23 | */ 24 | final protected function initializeWithVillages(): void 25 | { 26 | $this->append('postal_codes'); 27 | 28 | $this->makeHidden('distinctVillagesByPostalCodes'); 29 | } 30 | 31 | /** 32 | * @return HasMany 33 | */ 34 | public function villages(): HasMany 35 | { 36 | return $this->hasMany(Village::class); 37 | } 38 | 39 | public function postalCodes(): Attribute 40 | { 41 | $this->loadMissing('distinctVillagesByPostalCodes'); 42 | 43 | return Attribute::get(fn () => $this->distinctVillagesByPostalCodes->pluck('postal_code')); 44 | } 45 | 46 | public function distinctVillagesByPostalCodes() 47 | { 48 | return $this->villages()->distinct('postal_code')->groupBy('postal_code'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Models/District.php: -------------------------------------------------------------------------------- 1 | */ 12 | use Concerns\WithProvince; 13 | 14 | /** @use Concerns\WithRegency */ 15 | use Concerns\WithRegency; 16 | 17 | /** @use Concerns\WithVillages */ 18 | use Concerns\WithVillages; 19 | 20 | protected $fillable = []; 21 | 22 | protected $casts = []; 23 | 24 | public function getTable() 25 | { 26 | return config('creasi.nusa.table_names.districts', parent::getTable()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Models/Model.php: -------------------------------------------------------------------------------- 1 | 'array', 43 | ]); 44 | } 45 | 46 | public function getFillable() 47 | { 48 | return \array_merge(parent::getFillable(), ['code', 'name', 'coordinates']); 49 | } 50 | 51 | public function scopeSearch(Builder $query, string $keyword) 52 | { 53 | return $query->whereRaw(match ($this->getConnection()->getDriverName()) { 54 | 'pgsql' => 'name ilike ?', 55 | 'mysql' => 'lower(name) like lower(?)', 56 | 'sqlite' => 'name like ? COLLATE NOCASE', 57 | }, "%{$keyword}%"); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Models/Province.php: -------------------------------------------------------------------------------- 1 | $regencies 11 | */ 12 | class Province extends Model implements ProvinceContract 13 | { 14 | /** @use Concerns\WithDistricts */ 15 | use Concerns\WithDistricts; 16 | 17 | /** @use Concerns\WithVillages */ 18 | use Concerns\WithVillages; 19 | 20 | protected $fillable = []; 21 | 22 | protected $casts = []; 23 | 24 | public function getTable() 25 | { 26 | return config('creasi.nusa.table_names.provinces', parent::getTable()); 27 | } 28 | 29 | /** 30 | * @return \Illuminate\Database\Eloquent\Relations\HasMany|Regency 31 | */ 32 | public function regencies() 33 | { 34 | return $this->hasMany(Regency::class); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Models/Regency.php: -------------------------------------------------------------------------------- 1 | */ 12 | use Concerns\WithDistricts; 13 | 14 | /** @use Concerns\WithProvince */ 15 | use Concerns\WithProvince; 16 | 17 | /** @use Concerns\WithVillages */ 18 | use Concerns\WithVillages; 19 | 20 | protected $fillable = []; 21 | 22 | protected $casts = []; 23 | 24 | public function getTable() 25 | { 26 | return config('creasi.nusa.table_names.regencies', parent::getTable()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Models/Village.php: -------------------------------------------------------------------------------- 1 | */ 12 | use Concerns\WithDistrict; 13 | 14 | /** @use Concerns\WithProvince */ 15 | use Concerns\WithProvince; 16 | 17 | /** @use Concerns\WithRegency */ 18 | use Concerns\WithRegency; 19 | 20 | protected $fillable = ['postal_code']; 21 | 22 | protected $casts = [ 23 | 'postal_code' => 'int', 24 | ]; 25 | 26 | public function getTable() 27 | { 28 | return config('creasi.nusa.table_names.villages', parent::getTable()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | runningInConsole()) { 19 | $this->registerPublishables(); 20 | } 21 | 22 | $this->loadTranslationsFrom(self::LIB_PATH.'/resources/lang', 'creasico'); 23 | 24 | $this->defineRoutes(); 25 | } 26 | 27 | public function register() 28 | { 29 | config([ 30 | 'database.connections.nusa' => array_merge([ 31 | 'driver' => 'sqlite', 32 | 'database' => realpath(self::LIB_PATH.'/database/nusa.sqlite'), 33 | 'foreign_key_constraints' => true, 34 | ], config('database.connections.nusa', [])), 35 | ]); 36 | 37 | if (! app()->configurationIsCached()) { 38 | $this->mergeConfigFrom(self::LIB_PATH.'/config/nusa.php', 'creasi.nusa'); 39 | } 40 | 41 | $this->registerBindings(); 42 | } 43 | 44 | protected function defineRoutes() 45 | { 46 | if (app()->routesAreCached() && config('creasi.nusa.routes_enable') === false) { 47 | return; 48 | } 49 | 50 | Route::prefix(config('creasi.nusa.routes_prefix', 'nusa')) 51 | ->name('nusa.') 52 | ->group(self::LIB_PATH.'/routes/nusa.php'); 53 | } 54 | 55 | protected function registerPublishables() 56 | { 57 | $this->publishes([ 58 | self::LIB_PATH.'/config/nusa.php' => \config_path('creasi/nusa.php'), 59 | ], ['creasi-config', 'creasi-nusa-config']); 60 | 61 | $this->publishes([ 62 | self::LIB_PATH.'/resources/lang' => \resource_path('lang/vendor/creasico'), 63 | ], ['creasi-lang']); 64 | 65 | $this->publishes([ 66 | self::LIB_PATH.'/database/migrations/create_addresses_tables.php.stub' => $this->getMigrationFileName('create_addresses_tables.php'), 67 | ], 'creasi-migrations'); 68 | } 69 | 70 | protected function registerBindings() 71 | { 72 | $this->app->bind(Contracts\Address::class, function ($app) { 73 | $addressable = config('creasi.nusa.addressable'); 74 | 75 | return $app->make($addressable); 76 | }); 77 | 78 | $this->app->bind(Contracts\Province::class, Models\Province::class); 79 | $this->app->bind(Contracts\Regency::class, Models\Regency::class); 80 | $this->app->bind(Contracts\District::class, Models\District::class); 81 | $this->app->bind(Contracts\Village::class, Models\Village::class); 82 | } 83 | 84 | /** 85 | * Returns existing migration file if found, else uses the current timestamp. 86 | * 87 | * @link https://github.com/spatie/laravel-permission/blob/main/src/PermissionServiceProvider.php 88 | */ 89 | protected function getMigrationFileName(string $migrationFileName): string 90 | { 91 | $filesystem = app()->make(Filesystem::class); 92 | $migrationPath = app()->databasePath().DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR; 93 | 94 | return Collection::make([$migrationPath]) 95 | ->flatMap(fn ($path) => $filesystem->glob($path.'*_'.$migrationFileName)) 96 | ->push($migrationPath.date('Y_m_d_His').'_'.$migrationFileName) 97 | ->first(); 98 | } 99 | 100 | public function provides() 101 | { 102 | return [ 103 | Contracts\Address::class, 104 | Contracts\Province::class, 105 | Contracts\Regency::class, 106 | Contracts\District::class, 107 | Contracts\Village::class, 108 | ]; 109 | } 110 | } 111 | --------------------------------------------------------------------------------