Giter VIP home page Giter VIP logo

fityannugroho / idn-area Goto Github PK

View Code? Open in Web Editor NEW
90.0 3.0 15.0 3.99 MB

API that provides information about Indonesia administrative area, based on the latest official data 🇮🇩

Home Page: https://idn-area.up.railway.app

License: MIT License

JavaScript 0.44% TypeScript 98.98% Dockerfile 0.58%
typescript api open-source indonesia nodejs wilayah-indonesia openapi idn-area wilayah island

idn-area's Introduction

F for Fityan

You can call me Fityan. I'm a software engineer from 🇮🇩 Indonesia. I love coding and building things that make people's lives easier. Most of the time was working with PHP, JavaScript, and TypeScript, using various frameworks to build front-end and back-end applications such as Laravel, React, Next.js, and NestJS. I'm interest with website, android, and open source project 🚀

An image of @fityannugroho's Holopin badges, which is a link to view their full Holopin profile

Statistics

Fityan's Most Used Language Fityan's Github Stats

Keep Connected

LinkedIn X / Twitter YouTube Medium

Support Me

Buy Me a ko-fi Trakteer Saya

idn-area's People

Contributors

charithacs avatar dependabot[bot] avatar fityannugroho avatar kaustab28 avatar kshjessica avatar rakeshrk6 avatar snyk-bot avatar tedante avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

idn-area's Issues

[SUGGEST] Speed up tests with `swc`

Is your feature request related to a problem? Please describe.

Currently, testing using jest seems to be very slow, especially for end-to-end tests.

Describe the solution you'd like

Use @swc/jest instead of ts-jest as jest transformer. See the guide from NestJS here.

Describe alternatives you've considered

Based on benchmark testing report (https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4), seems jasmine more promising in terms of performance.

Additional context

None for now.

`vitest run` crash: thread '<unnamed>' panicked at 'Failed to deserialize constructor options.

Describe the bug

Crash when run these test commands:

  • npm run test (alias for vitest run)
  • npm run test:watch (alias for vitest)

This happens often but not always. Crashes usually occur on the first try, then succeed on the next.

And since the npm run test command is used in CI workflows, those workflows are also impacted and hamper the DevOps process. The first case is found in https://github.com/fityannugroho/idn-area/actions/runs/6508997855/job/17679490953.

To Reproduce

Steps to reproduce the behavior:

  1. Clone and install this project (see the guidelines)
  2. Run npm run test or npm run test:watch
  3. See the error as shown below

Expected behavior

The command should run successfully without any errors.

Screenshots or Error logs

 ✓ src/village/village.controller.spec.ts (9) 749ms
 ✓ src/island/island.controller.spec.ts (12) 378ms
 ✓ src/district/district.controller.spec.ts (9)
 ✓ src/district/district.service.spec.ts (7)
 ✓ src/island/island.service.spec.ts (9)
 ✓ src/regency/regency.controller.spec.ts (9)
 ✓ src/regency/regency.service.spec.ts (7)
thread '<unnamed>' panicked at '
            Failed to deserialize constructor options.

            This usually happens when the javascript object passed to the constructor is missing
            properties for the ConstructorOptions fields that must have some value.

            If you set some of these in javascript trough environment variables, make sure there are
            values for data_model, log_level, and any field that is not Option<T>
            : Error { status: PendingException, reason: "", maybe_raw: 0x0, maybe_env: 0x0 }', query-engine/query-engine-node-api/src/engine.rs:166:45
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
fatal runtime error: failed to initiate panic, error 5
Aborted

Also affects the CI workflow, for example:
https://github.com/fityannugroho/idn-area/actions/runs/6871822395/job/18689316010

Additional context

The strange thing is that there is no problem with the npm run test:cov command.

Consistent response

Is your feature request related to a problem? Please describe.

Currently, the response is not consistent. For example:

  • Error: Not Found

    Request: http://localhost:3000/province

    {
      "message": "Cannot GET /province",
      "error": "Not Found",
      "statusCode": 404
    }
  • Error: Bad Request

    Request: http://localhost:3000/provinces/abc

    {
      "message": [
        "code must be a number string"
      ],
      "error": "Bad Request",
      "statusCode": 400
    }
  • Success: get provinces by name

    Request: http://localhost:3000/provinces?name=jawa

    [
      {
        "code": "32",
        "name": "JAWA BARAT"
      },
      {
        "code": "33",
        "name": "JAWA TENGAH"
      },
      {
        "code": "35",
        "name": "JAWA TIMUR"
      }
    ]
  • Success: get single province

    Request: http://localhost:3000/provinces/32

    {
      "code": "32",
      "name": "JAWA BARAT"
    }

As you can see, the response is not consistent in terms of the structure.

Describe the solution you'd like

Use interceptor to mapping the response (see the documentation and Stack Overflow). The response mapping will be explained below.

Must exist properties

Each response must have the following properties, either success or error:

  • statusCode (number): contains the status code of the response
  • message (string or array of string): contains the message(s) of the response

Error response

For error response, it must have the following properties:

  • error (string): contains the kind of error

Success response

For success response, it must have the following properties:

  • data (object or array): contains the data of the response

If the response is an array, it must have the following properties:

  • total (number): contains the total of the data

If the endpoint supports pagination (proposed in #177), it must have the following properties:

  • links (object): contains the links of the pagination
    • self (string): contains the link of the current page
    • first (string): contains the link of the first page
    • prev (string): contains the link of the previous page (if exists)
    • next (string): contains the link of the next page (if exists)
    • last (string): contains the link of the last page

Describe alternatives you've considered

None

Additional context

This is a BREAKING CHANGES

Bring back MongoDB `id` property to API response

Describe the bug

End-to-end testing fails after PR #307 if using MongoDB as the database provider. This happens because the new parent property (#307) that includes areas data (either Province, Regency, or District) contains id property which is brought from MongoDB.

Before this, the id property was removed automatically by the transformer TransformInterceptor (see the implementations in PR #179, #185, and #187).

To Reproduce

Steps to reproduce the behavior:

  1. Just follow the installation guide and choose MongoDB as the database provider
  2. Run npm run test:e2e

Expected behavior

End-to-end testing successful without any errors.

Screenshots or Error logs

> [email protected] test:e2e
> vitest run --config ./vitest.config.e2e.ts


 RUN  v0.34.6 .../idn-area

 ❯ test/district.e2e-spec.ts  (9 tests | 1 failed) 5137ms
   ❯ test/district.e2e-spec.ts > District (e2e) > GET /districts/{code} > should return the district with the `code`
     → expected { Object (code, name, ...) } to deeply equal { code: '327325', …(3) }
 ❯ test/island.e2e-spec.ts  (10 tests | 1 failed) 5747ms
   ❯ test/island.e2e-spec.ts > Island (e2e) > GET /islands/{code} > should return the island with the `code`
     → expected { code: '110140001', …(8) } to deeply equal { code: '110140001', …(8) }
 ❯ test/village.e2e-spec.ts  (9 tests | 1 failed) 6684ms
   ❯ test/village.e2e-spec.ts > Village (e2e) > GET /villages/{code} > should return the village data if the `code` exists
     → expected { code: '3204052004', …(3) } to deeply equal { code: '3204052004', …(3) }
 ✓ test/app.e2e-spec.ts  (1 test) 72ms
 ✓ test/regency.e2e-spec.ts  (8 tests) 4188ms
 ✓ test/province.e2e-spec.ts  (8 tests) 3924ms

⎯⎯⎯⎯⎯⎯⎯ Failed Tests 3 ⎯⎯⎯⎯⎯⎯⎯

 FAIL  test/district.e2e-spec.ts > District (e2e) > GET /districts/{code} > should return the district with the `code`
AssertionError: expected { Object (code, name, ...) } to deeply equal { code: '327325', …(3) }

- Expected
+ Received

  Object {
    "code": "327325",
    "name": StringMatching /^[a-zA-Z0-9\-'.\\/() ]+$/,
    "parent": Object {
      "province": Object {
        "code": "32",
+       "id": "65dbcf93f3d15088058db400",
        "name": StringMatching /^(?!\s)(?!PROVINSI)[A-Z ]+$/,
      },
      "regency": Object {
        "code": "3273",
+       "id": "65dbcf94f3d15088058db4cf",
        "name": StringMatching /^(?:KABUPATEN|KOTA)[A-Z ]+$/,
        "provinceCode": "32",
      },
    },
    "regencyCode": "3273",
  }

 ❯ test/district.e2e-spec.ts:108:24
    106|       );
    107| 
    108|       expect(district).toEqual({
       |                        ^
    109|         code: testCode,
    110|         name: expect.stringMatching(districtRegex.name),

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/3]⎯

 FAIL  test/island.e2e-spec.ts > Island (e2e) > GET /islands/{code} > should return the island with the `code`
AssertionError: expected { code: '110140001', …(8) } to deeply equal { code: '110140001', …(8) }

- Expected
+ Received

  Object {
    "code": "110140001",
    "coordinate": StringMatching /^([0-8][0-9]|90)°([0-5][0-9]|60)'(([0-5][0-9].[0-9]{2})|60.00)"\s(N|S)\s(0\d{2}|1([0-7][0-9]|80))°([0-5][0-9]|60)'(([0-5][0-9].[0-9]{2})|60.00)"\s(E|W)$/,
    "isOutermostSmall": Any<Boolean>,
    "isPopulated": Any<Boolean>,
    "latitude": Any<Number>,
    "longitude": Any<Number>,
    "name": StringMatching /^[a-zA-Z0-9\-'/ ]+$/,
    "parent": Object {
      "province": Object {
        "code": "11",
+       "id": "65dbcf93f3d15088058db3f5",
        "name": StringMatching /^[a-zA-Z0-9\-'/ ]+$/,
      },
      "regency": Object {
        "code": "1101",
+       "id": "65dbcf94f3d15088058db41b",
        "name": StringMatching /^(?:KABUPATEN|KOTA)[A-Z ]+$/,
        "provinceCode": "11",
      },
    },
    "regencyCode": "1101",
  }

 ❯ test/island.e2e-spec.ts:136:22
    134|       const island = await tester.expectData<Island>(`${baseUrl}/${tes…
    135| 
    136|       expect(island).toEqual({
       |                      ^
    137|         code: testCode,
    138|         coordinate: expect.stringMatching(islandRegex.coordinate),

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/3]⎯

 FAIL  test/village.e2e-spec.ts > Village (e2e) > GET /villages/{code} > should return the village data if the `code` exists
AssertionError: expected { code: '3204052004', …(3) } to deeply equal { code: '3204052004', …(3) }

- Expected
+ Received

  Object {
    "code": "3204052004",
    "districtCode": "320405",
    "name": StringMatching /^[a-zA-Z0-9\-'"’.*\\/() ]+$/,
    "parent": Object {
      "district": Object {
        "code": "320405",
+       "id": "65dbcf96f3d15088058dbe6e",
        "name": StringMatching /^[a-zA-Z0-9\-'.\\/() ]+$/,
        "regencyCode": "3204",
      },
      "province": Object {
        "code": "32",
+       "id": "65dbcf93f3d15088058db400",
        "name": StringMatching /^(?!\s)(?!PROVINSI)[A-Z ]+$/,
      },
      "regency": Object {
        "code": "3204",
+       "id": "65dbcf94f3d15088058db4be",
        "name": StringMatching /^(?:KABUPATEN|KOTA)[A-Z ]+$/,
        "provinceCode": "32",
      },
    },
  }

 ❯ test/village.e2e-spec.ts:109:23
    107|       );
    108| 
    109|       expect(village).toEqual({
       |                       ^
    110|         code: testCode,
    111|         name: expect.stringMatching(villageRegex.name),

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/3]⎯

 Test Files  3 failed | 3 passed (6)
      Tests  3 failed | 42 passed (45)
   Start at  07:25:07
   Duration  11.88s (transform 103ms, setup 0ms, collect 4.96s, tests 25.75s, environment 1ms, prepare 477ms)

Suggestion

It gets more complicated if we have to delete the id property from all area data in parent property. Therefore, it will be better if we bring back the generated MongoDB id to the response. We just need to explain this MongoDB behavior in the documentation.

[SUGGEST] Optimize the seeder

Is your feature request related to a problem? Please describe.

Currently, the seeder works by deleting all data and adding new data to database, triggered by npm run db:seed command. This behavior is inefficient, especially when there are no data changes.

This behavior can also cause the test workflow to fail because the npm run db:seed command is called asynchronously every time there is a new PR, causing database conflicts.

Describe the solution you'd like

The seeder needs to check if there are any data changes, and skip the seeding if there are no data changes.

  • Check if there are data added or deleted.
  • Check if there are different record data.

Describe alternatives you've considered

Since the data is provided by idn-area-data package, we can assume if the data changes if idn-area-data version changes.

Additional context

None for now.

[BUG] Failed to start or build when using database provider other than mongodb

Describe the bug

Run npm run start, npm run start:dev, or npm run build always failed when using mysql or postgresql database provider

To Reproduce

Steps to reproduce the behavior:

  1. In .env file, set DB_PROVIDER to 'mysql' or 'postgresql' and update the DB_URL.
  2. Run npm run db:migrate.
  3. Run npm run db:seeder.
  4. Run npm run start, npm run start:dev, or npm run build.
  5. See the error.

Expected behavior

Successful build and start the app.

Screenshots or Error logs

prisma/mongodb/seeder.ts:14:30 - error TS2339: Property '$runCommandRaw' does not exist on type 'PrismaClient<PrismaClientOptions, never, RejectOnNotFound | RejectPerOperation>'.

14     return await this.prisma.$runCommandRaw({
                                ~~~~~~~~~~~~~~

Found 1 error(s).

Additional context

Ignore the TypeScript checking in prisma/mongodb/seeder.ts:14:30 can solve this issue, since $runCommandRaw() method only available for mongodb provider. See the details about $runCommandRaw() method here.

[SUGGEST] Create island endpoints 🏝️

Is your feature request related to a problem? Please describe.

This API is currently not providing the islands data contained in Keputusan Menteri Dalam Negeri Nomor 050-145 Tahun 2022 Tentang Pemberian dan Pemutakhiran Kode, Data Wilayah Administrasi Pemerintahan, dan Pulau Tahun 2021 (starts from page 3548 to 4038).

That island data consists of:

  • Island code, for example: 11.06.40007
    11.06 is the regency code, 4 is digit indicating it's an island, and 0007 is the serial number starts from 0001.

  • Island name, for example: Pulau Bateeleblah

  • Island coordinate, for example: 05°47'34.72" U 094°58'26.09" T
    The coordinate is written in DMS (degree minute seconds) format. U stands for "Utara" or "North", and T stands for "Timur" or "East".

  • Island information, for example: TBP (PPKT)
    The information available includes:

    • BT: stands for "Berpenghuni" or "Populated".
    • TBP: stands for "Tidak Berpenghuni" or "Unpopulated".
    • PPKT: stands for "Pulau-Pulau Kecil Terluar" or "Outermost Small Islands" (see the definition).

Describe the solution you'd like

  1. Create islands.csv file that contains this column:

    • code for the island code without dot separation. For example: 110640007.
    • regency_code for relation to the regency data. For example: 1106.
    • coordinate for the island coordinate with English term. For example: 05°47'34.72" N 094°58'26.09" E.
    • is_populated with value 1 if the island is populated (BT), or 0 if (TBP) and no available data.
    • is_outermost_small with value 1 if the island is The Outermost Small Islands (PPKT), or 0 if not.
    • name for the island name. For example: Pulau Bateeleblah.
  2. Create a helper to convert the coordinate into latitude and longitude (see the coordinates converter tools example), because the latitude-longitude format is more widely supported in tech world.

  3. Create some new endpoints to get the island data:

    • /regencies/{regencyCode}/islands: to get all island in a regency.
    • /islands?name={islandName}: to get island(s) by the name.

    The return scheme of all endpoints above is same: an array of islands that contains code, regencyCode, name, coordinate, isPopulated, isOutermostSmall, plus latitude and longitude (return [] if there are no any island).

Describe alternatives you've considered

The latitude-longitude feature can be ruled out first. The main goal is that the two endpoints above can return the data as-is in the csv file.

Additional context

Especially for the development of this feature, please PR to feat/island branch.

Add `meta` property in the response

Motivation

Currently the response structure for array data (since PR #179) is like this:

{
  "statusCode": 200,
  "message": "OK",
  "data": [
    {
      "id": 1,
      "name": "Data 1"
    }
  ],
  "total": 1
}

But this behavior make API response will not consistent if pagination is implemented (see #177). It is complicated to put the total and pagination property in outside the data property.

Proposed

Add optional meta property that contains all side properties of the data. So that the response structure will be like this:

{
  "statusCode": 200,
  "message": "OK",
  "data": [
    {
      "id": 1,
      "name": "Data 1"
    }
  ],
  "meta": {
    "total": 1,
    "pagination": {
      "first": "...",
      "last": "...",
      "...": "..."
    },
    "foo": "bar"
  }
}

Each controller methods need to return an object that contains data (and meta) properties.

The decorator can be simplified by merging the @ResponseMessage() decorator into @ApiDataResponse() and adding message property (string, optional).

We also need to refactoring the response for root endpoint / to be using @ApiOkResponse() so that we can remove @UnwrapResponse() decorator.

[SUGGEST] sqlite as DB

This is nice, lanjutkan, but I imagine the source data also provided in sqlite data hehehe :)

Can't search villages with quotation mark `"`

Describe the bug

When you search for villages with names containing quotation mark ", you will get 400 Bad Request response.

To Reproduce

Steps to reproduce the behavior:

  1. Fetch this url or open it in browser: https://idn-area.up.railway.app/villages?name=soka"an
  2. You will get 400 Bad Request response.

Expected behavior

It should give the 200 OK response because the village named soka"an really exists.

Screenshots or Error logs

{
  "message": [
    "name must not contain any symbols except this: '()-./"
  ],
  "error": "Bad Request",
  "statusCode": 400
}

image

Additional context

None

General Improvement

  • Uninstall unused dependencies (papaparse and @types/papaparse)
  • Improve documentation

[SUGGEST] Speed up testing

Is your feature request related to a problem? Please describe.

Currently, jest is used for testing. But it seems too slow, even after we replace the ts-jest with @swc/jest in #85.

Describe the solution you'd like

Use vitest to replace jest. See https://docs.nestjs.com/recipes/swc#vitest.

Describe alternatives you've considered

None for now

Additional context

None

Fix the island code example

Describe the bug

The code example for GET /islands/{code} endpoint is wrong.

To Reproduce

Steps to reproduce the behavior:

  1. Go to /docs endpoint
  2. Scroll down to GET /islands/{code}
  3. Click on GET /islands/{code} section
  4. Click "Try it out" button
  5. Click "Execute"
  6. See the error

Expected behavior

The docs gives real island code example, which is 110140001.

Screenshots or Error logs

image

Additional context

Guide for contributor

  • Take a look at island.dto.ts file.
  • Find Island class and code property inside it.
  • Change the value of example argument in the @ApiProperty() decorator into '110140001'.
  • That's it.

Refactor: separate data management

Use idn-area-data repository to manage the data.

So this repository can focus on developing the API.

TODO:

  • Update the test CI and package.json script runner.
  • Remove the data testing.
  • Refactor seeder code to use data from idn-area-data.
  • Remove unused utils, helpers, and the other codes.
  • Delete data directory and all files inside it.
  • Update the documentation (README & CONTRIBUTING).

Add pagination

Is your feature request related to a problem? Please describe.

To be able to make name query param in base endpoints (/regencies, etc) optional and still ensure reasonable resource usage (avoiding requests that will return large amounts of data in a single request), we need a pagination feature that will limit the amount of data returned for each request.

Describe the solution you'd like

  • Using environment variables to define the maximum and the default amount of data in single page
  • Create pagination queries: page and limit
  • Implement pagination queries for each base endpoints
  • Create new API decorator for pagination
  • Create paginator() in PrismaService
  • Use PrismaService.paginator() in controller methods that support pagination (will return data and meta property)
  • For the endpoint that supports pagination, it must have the following properties inside meta property:
    • pagination (object): contains the pagination metadata
      • total (number): the number of available data with the filter query.
      • pages (object)
        • first (number): the number of the first page
        • last (number): the number of the last page
        • current (number | null): the number of the current page (if not exceed the last page)
        • previous (number | null): the number of the previous page (if exists)
        • next (number | null): the number of the next page (if exists)

Describe alternatives you've considered

None

Additional context

We need to resolve these issues first:

Translate the README to other language

Translate the README into Bahasa Indonesia and other languages.

Guide

  • Save the translation files in docs folder and named it with README_{2 letter of language code}.md the language code must follows the ISO 639-1 code languages, e.g. README_id.md for Indonesian (Bahasa Indonesia).
  • Create new PR and fill the form by following the contribution guide. Make sure you reference this issue (#95) in your PR
  • Make sure all links is working, especially link in TOC and link to other file in this repo.
    • The links in the table of content (TOC) also need to be translated to make it work. The link is a snake-case version of the header without any symbol.
    • Use relative paths for links that go to other files (see the following article).

For everyone who wants to contribute to this issue, please fork this repo, make changes, and create a new PR. When ready, ask me to review your work. From now on, I will not assign each person individually.

Filter `GET /islands` by the `regencyCode`

Is your feature request related to a problem? Please describe.

Currently, we can't get all islands which the regencyCode is null (GET /regencies//islands won't work since the code param is required).

Describe the solution you'd like

Add new query param for GET /islands endpoint to filter the islands by the regencyCode which is optional and can be empty string ''.

Describe alternatives you've considered

None

Additional context

Making code param of GET /regencies/{code}/islands endpoint to accept empty string is won't work and a bad idea as the validation is used by other /regencies endpoints. It also will breaks our url patterns.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.