diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..b3ea1ff --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,22 @@ +version: "2" + +checks: + similar-code: + enabled: false + method-complexity: + config: + threshold: 9 + +plugins: + fixme: + enabled: true + phpcodesniffer: + enabled: false + phpmd: + enabled: true + config: + rulesets: workbench/phpmd.xml + +ratings: + paths: + - src/**.php diff --git a/.gitattributes b/.gitattributes index c659dff..089a625 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,6 +3,7 @@ # Exclude unused files # see: https://redd.it/2jzp6k +.codeclimate.yml export-ignore .editorconfig export-ignore .env.example export-ignore .gitattributes export-ignore diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2640d0e..1430912 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,10 +9,6 @@ on: branches: [main] # paths: ['.github/**.yml', 'src/*', 'tests/*'] -env: - DB_USERNAME: ${{ github.repository_owner }} - DB_PASSWORD: secret - concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -31,26 +27,10 @@ jobs: runs-on: ubuntu-latest needs: configs env: - DB_NUSA: nusantara - - services: - mysql: - image: mysql:8.0 - env: - MYSQL_ROOT_PASSWORD: ${{ env.DB_PASSWORD }} - MYSQL_DATABASE: ${{ env.DB_NUSA }} - MYSQL_USER: ${{ env.DB_USERNAME }} - MYSQL_PASSWORD: ${{ env.DB_PASSWORD }} - options: >- - --health-cmd="mysqladmin ping" - --health-interval=10s - --health-timeout=5s - --health-retries=3 - ports: - - 3306:3306 - - permissions: - actions: read + DB_USERNAME: root + DB_PASSWORD: root + DB_DATABASE: ${{ github.repository_owner }} + UPSTREAM_DB_DATABASE: nusantara steps: - name: Checkout @@ -58,6 +38,12 @@ jobs: with: submodules: true + - name: Setup MySQL + run: | + sudo /etc/init.d/mysql start + mysql -e 'CREATE DATABASE ${{ env.DB_DATABASE }};' -u${{ env.DB_USERNAME }} -p${{ env.DB_PASSWORD }} + mysql -e 'CREATE DATABASE ${{ env.UPSTREAM_DB_DATABASE }};' -u${{ env.DB_USERNAME }} -p${{ env.DB_PASSWORD }} + - name: Setup PHP uses: shivammathur/setup-php@v2 with: @@ -86,7 +72,7 @@ jobs: run: | ./vendor/bin/testbench nusa:import --ansi ./vendor/bin/testbench nusa:stat -dw --ansi - git status -s resources + git status -s {database,resources} # - name: Get file changed # uses: tj-actions/changed-files@v45 @@ -94,7 +80,7 @@ jobs: # exclude_submodules: true # # files: resources/** - - name: Upload current stat + - name: Store current stat if: ${{ github.event_name != 'pull_request' }} uses: actions/upload-artifact@v4 with: @@ -102,6 +88,14 @@ jobs: path: tests/stats.json if-no-files-found: ignore + - name: Store static resources + uses: actions/upload-artifact@v4 + with: + name: static-resources + path: | + database/nusa.sqlite + resources/static/** + tests: name: Test on PHP ${{ matrix.php }} Laravel ${{ matrix.laravel }} DB ${{ matrix.db }} needs: [configs, prepare] @@ -109,6 +103,8 @@ jobs: env: DB_CONNECTION: ${{ matrix.db }} DB_DATABASE: ${{ github.repository_owner }} + DB_USERNAME: ${{ github.repository_owner }} + DB_PASSWORD: secret services: postgresql: @@ -177,13 +173,20 @@ jobs: composer require "laravel/framework=${{ matrix.laravel }}" --no-update composer update --prefer-dist --no-interaction --no-progress + - name: Restore static resources + uses: actions/download-artifact@v4 + with: + name: static-resources + - name: Run migrations run: | - composer testbench vendor:publish -- --tag creasi-migrations - composer testbench migrate + ./vendor/bin/testbench vendor:publish --ansi --tag creasi-migrations + ./vendor/bin/testbench migrate --ansi - name: Run tests - run: composer test -- --coverage + run: | + ./vendor/bin/testbench config:show database --ansi + ./vendor/bin/testbench package:test --ansi --coverage - name: Generate reports for CodeClimate id: reports @@ -195,7 +198,7 @@ jobs: curl -LSs $CC_TEST_REPORTER_URL > ./cc-test-reporter && chmod +x ./cc-test-reporter ./cc-test-reporter format-coverage -t clover -o $CODECLIMATE_REPORT tests/reports/clover.xml - - name: Upload tests reports + - name: Store tests reports uses: actions/upload-artifact@v4 if: needs.configs.outputs.has-codeclimate == '1' with: @@ -212,4 +215,3 @@ jobs: with: has-coveralls: ${{ needs.configs.outputs.has-coveralls == '1' }} has-codeclimate: ${{ needs.configs.outputs.has-codeclimate == '1' }} - diff --git a/.gitmodules b/.gitmodules index ed0b2ac..d219651 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "workbench/submodules/cahyadsn-wilayah_kodepos"] path = workbench/submodules/cahyadsn-wilayah_kodepos url = https://github.com/cahyadsn/wilayah_kodepos.git +[submodule "workbench/submodules/cahyadsn-wilayah_boundaries"] + path = workbench/submodules/cahyadsn-wilayah_boundaries + url = https://github.com/cahyadsn/wilayah_boundaries.git diff --git a/README.md b/README.md index 6a402c8..61852c2 100644 --- a/README.md +++ b/README.md @@ -883,26 +883,39 @@ using `sqlite` driver. Let us know if you had any issue using another database d ```sh composer install + pnpm install ``` -3. Create new database, by default we use the following configuration : +3. Copy `workbench/.env.example` to `workbench/.env` and update the content you desire + + ```sh + DB_CONNECTION=mysql # Your main db connection to test againsts + DB_HOST=127.0.0.1 + DB_DATABASE=creasi_test # To store the testing data + DB_USERNAME=creasico + DB_PASSWORD=secret - - `dbname` : `nusantara` - - `dbhost` : `127.0.0.1` - - `dbuser` : `root` - - `dbpass` : `secret` + UPSTREAM_DB_DATABASE=nusantara # To store the upstream data + ``` + +4. Create new database to store our upstream and testing data: ```sh - mysql -e 'create database nusantara;' + mysql -e 'create database creasi_test;' # Based on the value of `DB_DATABASE` + mysql -e 'create database nusantara;' # Based on the value of `UPSTREAM_DB_DATABASE` ``` -4. Last but not least, run `db:import` command +5. Last but not least, run `db:import` command ```sh composer db:import ``` -If you were using different configuration you can edit [this file](https://github.com/creasico/laravel-nusa/blob/94cd261d7726b9a5cb46cdef4aa9914522a33b4a/tests/NusaTest.php#L16-L19) but please don't submit your changes. +As you might noticed that we use 3 different databases to develop and maintain this library. Here's the explanation : + +- `database/nusa.sqlite` is the main database in this library that will be included when you install this library as a dependency +- `DB_DATABASE` is mainly for testing purposes, it simulate the actual application where this library installed on +- `UPSTREAM_DB_DATABASE` is contains the upstream database tables that will be used as source of truth for this library Once you've done with your meaningful contributions, run the following command to ensure everythings is works as expected. diff --git a/composer.json b/composer.json index 95d341f..49b2a89 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,9 @@ "issues": "https://github.com/creasico/laravel-nusa/issues" }, "scripts": { + "post-autoload-dump": [ + "testbench vendor:publish --tag=creasi-migrations" + ], "fix": [ "pint --preset laravel" ], diff --git a/database/README.md b/database/README.md index 8507b6c..8b48c8f 100644 --- a/database/README.md +++ b/database/README.md @@ -35,6 +35,8 @@ classDiagram +int regency_code +int province_code +string name + +double latitude + +double longitude +province() Province +regency() Regency +villages() Village[] @@ -45,6 +47,8 @@ classDiagram +int regency_code +int province_code +string name + +double latitude + +double longitude +int postal_code +province() Province +regency() Regency @@ -84,6 +88,8 @@ classDiagram | `regency_code` | `char(4)` | `foreign` | - | | `province_code` | `char(2)` | `foreign` | - | | `name` | `varchar` | - | - | +| `latitude` | `double`, `nullable` | - | - | +| `longitude` | `double`, `nullable` | - | - | **Relation Properties** - `regency_code` : reference `regencies` @@ -98,6 +104,8 @@ classDiagram | `regency_code` | `char(4)` | `foreign` | - | | `province_code` | `char(2)` | `foreign` | - | | `name` | `varchar` | - | - | +| `latitude` | `double`, `nullable` | - | - | +| `longitude` | `double`, `nullable` | - | - | | `postal_code` | `char(5)`, `nullable` | - | - | **Relation Properties** diff --git a/database/migrations/0000_00_00_000000_create_nusa_tables.php b/database/migrations/0000_00_00_000000_create_nusa_tables.php index bd44d10..7cf5976 100644 --- a/database/migrations/0000_00_00_000000_create_nusa_tables.php +++ b/database/migrations/0000_00_00_000000_create_nusa_tables.php @@ -18,6 +18,10 @@ public function up(): void { $tableNames = config('creasi.nusa.table_names'); + if (Schema::hasTable($tableNames['provinces'])) { + return; + } + Schema::create($tableNames['provinces'], function (Blueprint $table) { $table->char('code', 2)->primary(); $table->string('name', 50)->index(); diff --git a/tests/Models/DistrictTest.php b/tests/Models/DistrictTest.php index a08b4cc..cbc2ccb 100644 --- a/tests/Models/DistrictTest.php +++ b/tests/Models/DistrictTest.php @@ -49,6 +49,8 @@ public function it_should_has_many_districts(Collection $districts): Collection { $districts->each(function (District $district) { $this->assertIsInt($district->code, 'Code should be int'); + $this->assertIsFloat($district->latitude, \sprintf('Latitude of district "%s" should be float', $district->code)); + $this->assertIsFloat($district->longitude, \sprintf('Longitude of district "%s" should be float', $district->code)); $this->assertInstanceOf(Collection::class, $district->postal_codes, 'Postal Codes should be instance of collection'); $this->assertInstanceOf(DistrictContract::class, $district); diff --git a/tests/Models/RegencyTest.php b/tests/Models/RegencyTest.php index 8e76661..cffbb70 100644 --- a/tests/Models/RegencyTest.php +++ b/tests/Models/RegencyTest.php @@ -50,8 +50,8 @@ public function it_should_has_many_regencies(Collection $regencies): Collection $regencies->each(function (Regency $regency) { $this->assertIsInt($regency->code, 'Code should be int'); // Comment this out due to https://github.com/cahyadsn/wilayah/pull/47 - // $this->assertIsFloat($regency->latitude, 'Latitude should be float'); - // $this->assertIsFloat($regency->longitude, 'Longitude should be float'); + // $this->assertIsFloat($regency->latitude, \sprintf('Latitude of regency "%s" should be float', $regency->code)); + // $this->assertIsFloat($regency->longitude, \sprintf('Longitude of regency "%s" should be float', $regency->code)); $this->assertInstanceOf(Collection::class, $regency->postal_codes, 'Postal Codes should be instance of collection'); $this->assertInstanceOf(RegencyContract::class, $regency); diff --git a/tests/Models/VillageTest.php b/tests/Models/VillageTest.php index 0e20b0b..8e20eaa 100644 --- a/tests/Models/VillageTest.php +++ b/tests/Models/VillageTest.php @@ -49,6 +49,8 @@ public function it_should_has_many_villages(Collection $villages): Collection { $villages->each(function (Village $village) { $this->assertIsInt($village->code, 'Code should be int'); + $this->assertIsFloat($village->latitude, \sprintf('Latitude of village "%s" should be float', $village->code)); + $this->assertIsFloat($village->longitude, \sprintf('Longitude of village "%s" should be float', $village->code)); if ($village->postal_code) { $this->assertIsInt($village->postal_code, 'Postal Code should be int'); } diff --git a/tests/TestCase.php b/tests/TestCase.php index ee5d283..d2b9420 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,54 +4,15 @@ namespace Creasi\Tests; -use Database\Seeders\DatabaseSeeder; -use Illuminate\Config\Repository; -use Illuminate\Foundation\Testing\DatabaseMigrations; use Orchestra\Testbench\Concerns\WithWorkbench; use Orchestra\Testbench\TestCase as Orchestra; class TestCase extends Orchestra { - use DatabaseMigrations; use WithWorkbench; - private static $shouldMigrate = true; - - protected function defineDatabaseMigrations() - { - $nusa = \config('database.connections.nusa', []); - - if (self::$shouldMigrate) { - $this->recreateDatabase($nusa['database']); - - $this->loadMigrationsWithoutRollbackFrom( - \realpath(\dirname($nusa['database'])).'/migrations' - ); - } - } - - protected function defineDatabaseSeeders() - { - if (self::$shouldMigrate) { - $this->seed(DatabaseSeeder::class); - - self::$shouldMigrate = false; - } - } - - private function recreateDatabase(string $path) - { - if (\file_exists($path)) { - \unlink($path); - } - - \touch($path); - - return $path; - } - - private function mergeConfig(Repository $config, string $key, array $value) + protected function defineEnvironment($app) { - $config->set($key, array_merge($config->get($key, []), $value)); + // $app['config']->set('database.default', env('DB_CONNECTION')); } } diff --git a/workbench/.env.example b/workbench/.env.example index ff7a6d9..8fa75c3 100644 --- a/workbench/.env.example +++ b/workbench/.env.example @@ -1,6 +1,7 @@ +# DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_DATABASE=creasi_test DB_USERNAME=root DB_PASSWORD= -DB_NUSA=nusantara +UPSTREAM_DB_DATABASE=nusantara diff --git a/workbench/app/Console/DatabaseImport.php b/workbench/app/Console/DatabaseImport.php index 3b39d41..287ba16 100644 --- a/workbench/app/Console/DatabaseImport.php +++ b/workbench/app/Console/DatabaseImport.php @@ -2,17 +2,21 @@ namespace Workbench\App\Console; +use Database\Seeders\DatabaseSeeder; use Illuminate\Console\Command; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use PDO; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; use Workbench\App\Support\Normalizer; class DatabaseImport extends Command { use CommandHelpers; - protected $signature = 'nusa:import'; + protected $signature = 'nusa:import + {--fresh : Refresh database migrations and seeders}'; protected $description = 'Import upstream database'; @@ -44,10 +48,11 @@ public function handle() $this->importSql( 'cahyadsn-wilayah/db/wilayah.sql', - 'cahyadsn-wilayah/db/archive/wilayah_level_1_2.sql', 'cahyadsn-wilayah_kodepos/db/wilayah_kodepos.sql', ); + $this->importSqlBoundaries(); + $this->group('Writing CSV and JSON files'); foreach ($this->fetchAll() as $table => $content) { @@ -68,6 +73,21 @@ public function handle() }); } + if ($this->option('fresh') || env('CI') !== null) { + $this->group('Run migrations and seeders'); + + $path = config('database.connections.nusa', [])['database']; + + if (\file_exists($path)) { + \unlink($path); + } + + \touch($path); + + $this->call('vendor:publish', ['--tag' => 'creasi-migrations']); + $this->call('migrate:fresh', ['--seeder' => DatabaseSeeder::class]); + } + $this->endGroup(); } @@ -77,9 +97,9 @@ private function fetchAll(): array SELECT w.kode, w.nama, p.kodepos, - l.lat, l.lng, l.elv, l.tz, l.luas, l.penduduk, l.path path + l.lat, l.lng, l.path path FROM wilayah w - LEFT JOIN wilayah_level_1_2 l ON w.kode = l.kode + LEFT JOIN wilayah_boundaries l ON w.kode = l.kode LEFT JOIN wilayah_kodepos p on w.kode = p.kode ORDER BY w.kode SQL, PDO::FETCH_OBJ); @@ -113,6 +133,48 @@ private function importSql(string ...$paths): void } } + private function importSqlBoundaries() + { + $path = 'workbench/submodules/cahyadsn-wilayah_boundaries'; + // $schema = file_get_contents( + // (string) $this->libPath($path, $schemaPath = 'db/ddl_wilayah_boundaries.sql') + // ); + + // $this->line(" - Importing 'cahyadsn-wilayah_boundaries/{$schemaPath}'"); + // $lines = explode("\n", explode('-- ', $schema)[1]); + // $schema = implode("\n", array_slice($lines, 1, count($lines))); + + $this->line(" - Importing 'cahyadsn-wilayah_boundaries/db/ddl_wilayah_boundaries.sql'"); + $this->query(<<<'SQL' + DROP TABLE IF EXISTS `wilayah_boundaries`; + CREATE TABLE `wilayah_boundaries` ( + `kode` varchar(13) NOT NULL, + `nama` varchar(100) DEFAULT NULL, + `lat` double DEFAULT NULL, + `lng` double DEFAULT NULL, + `path` longtext, + `status` int DEFAULT NULL, + UNIQUE KEY `wilayah_boundaries_kode_IDX` (`kode`) USING BTREE + ) + SQL); + + $sqls = Finder::create() + ->files() + ->in($path.'/db/*/') + ->name('*.sql') + ->filter(static function (SplFileInfo $file) { + return ! in_array($file->getFilename(), [ + 'wilayah_boundaries_kab_75.sql', + ]); + }); + + foreach ($sqls as $sqlPath => $sql) { + $sqlPath = substr($sqlPath, 21); + $this->line(" - Importing '{$sqlPath}'"); + $this->query($sql->getContents()); + } + } + private function query(string $statement, ?int $mode = null, mixed ...$args) { return $this->conn->query($statement, $mode, ...$args); diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php index 7467645..d5f9677 100644 --- a/workbench/app/Providers/WorkbenchServiceProvider.php +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -33,39 +33,38 @@ public function boot(): void $config->set('app.locale', 'id'); $config->set('app.faker_locale', 'id_ID'); - $conn = env('DB_CONNECTION', 'sqlite'); + if (app()->runningInConsole()) { + $nusa = $config->get('database.connections.nusa', []); + + $this->loadMigrationsFrom(\dirname($nusa['database']).'/migrations'); + } $config->set([ 'database.connections.upstream' => [ 'driver' => 'mysql', - 'host' => env('DB_HOST', '127.0.0.1'), + 'host' => env('UPSTREAM_DB_HOST', env('DB_HOST', '127.0.0.1')), 'port' => env('DB_PORT', '3306'), - 'database' => env('DB_NUSA', 'nusantara'), + 'database' => env('UPSTREAM_DB_DATABASE', 'nusantara'), 'username' => env('DB_USERNAME', 'creasico'), 'password' => env('DB_PASSWORD', 'secret'), ], ]); - // $conn = $config->get('database.default'); - - // if ($conn === 'sqlite') { - // // $database = __DIR__.'/test.sqlite'; - - // // if (self::$shouldMigrate) { - // // $this->recreateDatabase($database); - // // } + if (env('DB_CONNECTION') === 'sqlite') { + if (! file_exists($database = database_path('database.sqlite'))) { + touch($database); + } - // $this->mergeConfig($config, 'database.connections.sqlite', [ - // 'database' => ':memory:', - // 'foreign_key_constraints' => true, - // ]); - // } else { - // $this->mergeConfig($config, 'database.connections.'.$conn, [ - // 'database' => env('DB_DATABASE', 'creasi_test'), - // 'username' => env('DB_USERNAME', 'creasico'), - // 'password' => env('DB_PASSWORD', 'secret'), - // ]); - // } + $this->mergeConfig($config, 'database.connections.testing', [ + 'database' => $database, + 'foreign_key_constraints' => true, + ]); + } }); } + + private function mergeConfig(Repository $config, string $key, array $value) + { + $config->set($key, array_merge($config->get($key, []), $value)); + } } diff --git a/workbench/app/Support/Normalizer.php b/workbench/app/Support/Normalizer.php index c631ff0..46d87d7 100644 --- a/workbench/app/Support/Normalizer.php +++ b/workbench/app/Support/Normalizer.php @@ -75,8 +75,8 @@ private function toDistrict(): array 'regency_code' => (int) ($province_code.$regency_code), 'province_code' => (int) $province_code, 'name' => $this->name, - // 'latitude' => $this->latitude, - // 'longitude' => $this->longitude, + 'latitude' => $this->latitude, + 'longitude' => $this->longitude, // 'coordinates' => $this->coordinates, ]; } @@ -92,8 +92,8 @@ private function toVillage(): array 'province_code' => (int) $province_code, 'name' => $this->name, 'postal_code' => $this->postal_code, - // 'latitude' => $this->latitude, - // 'longitude' => $this->longitude, + 'latitude' => $this->latitude, + 'longitude' => $this->longitude, // 'coordinates' => $this->coordinates, ]; } diff --git a/workbench/phpmd.xml b/workbench/phpmd.xml new file mode 100644 index 0000000..6a2a755 --- /dev/null +++ b/workbench/phpmd.xml @@ -0,0 +1,29 @@ + + + Custom rules for creasico projects + + + + + + + + + + + 1 + + + + + + + 1 + + + + + diff --git a/workbench/submodules/cahyadsn-wilayah_boundaries b/workbench/submodules/cahyadsn-wilayah_boundaries new file mode 160000 index 0000000..93e7378 --- /dev/null +++ b/workbench/submodules/cahyadsn-wilayah_boundaries @@ -0,0 +1 @@ +Subproject commit 93e737832c90824fb929aed3d0275077226aed1e