From 9837cbf7cfc130c24f24c264f4d44048eecb4f1c Mon Sep 17 00:00:00 2001 From: nochill Date: Thu, 22 Feb 2024 10:43:39 +0700 Subject: [PATCH] init --- .env.example | 1 + .gitignore | 3 + air.yaml | 0 foo.txt | 212 +++++++++ go.mod | 28 ++ go.sum | 94 ++++ internal/excel.go | 136 ++++++ internal/jwt.go | 63 +++ internal/middleware.go | 26 ++ internal/parse_excel.go | 16 + internal/repository/alamat.repository.go | 53 +++ internal/repository/db.go | 55 +++ internal/repository/keluarga.repository.go | 46 ++ internal/repository/patient.repository.go | 485 +++++++++++++++++++++ internal/repository/store.go | 43 ++ internal/repository/wilayah.repository.go | 20 + internal/rest/handler.go | 333 ++++++++++++++ internal/rest/server.go | 15 + main.go | 115 +++++ model/alamat.go | 24 + model/db.go | 1 + model/keluarga.go | 19 + model/patient.go | 38 ++ model/wilayah.go | 9 + util/common.go | 53 +++ 25 files changed, 1888 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 air.yaml create mode 100644 foo.txt create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/excel.go create mode 100644 internal/jwt.go create mode 100644 internal/middleware.go create mode 100644 internal/parse_excel.go create mode 100644 internal/repository/alamat.repository.go create mode 100644 internal/repository/db.go create mode 100644 internal/repository/keluarga.repository.go create mode 100644 internal/repository/patient.repository.go create mode 100644 internal/repository/store.go create mode 100644 internal/repository/wilayah.repository.go create mode 100644 internal/rest/handler.go create mode 100644 internal/rest/server.go create mode 100644 main.go create mode 100644 model/alamat.go create mode 100644 model/db.go create mode 100644 model/keluarga.go create mode 100644 model/patient.go create mode 100644 model/wilayah.go create mode 100644 util/common.go diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c02d84a --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +JWT_SALT= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6dd034b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +.vscode +tmp \ No newline at end of file diff --git a/air.yaml b/air.yaml new file mode 100644 index 0000000..e69de29 diff --git a/foo.txt b/foo.txt new file mode 100644 index 0000000..993881e --- /dev/null +++ b/foo.txt @@ -0,0 +1,212 @@ +[ + { + "message": "No rekam medis 000000001 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "000000001", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal mendapatakan data pasien no rows in result set", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "Terjadi kesalahan sistem, gagal mendapatakan data pasien dengan BPJS no rows in result set", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "No rekam medis 00000001 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "00000001", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "No rekam medis 00000002 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "00000002", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal mendapatakan data pasien dengan BPJS no rows in result set", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "No rekam medis 00000003 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "00000003", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "No rekam medis 00000004 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "00000004", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "No rekam medis 00000005 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "00000005", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "No rekam medis 00000006 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "00000006", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "No rekam medis 00000007 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "00000007", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "No rekam medis 00000008 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "00000008", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "No rekam medis 00000009 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "00000009", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "No rekam medis 00000010 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "00000010", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal mendapatakan data pasien dengan BPJS no rows in result set", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "No rekam medis 00000011 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "00000011", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal mendapatakan data pasien no rows in result set", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "Terjadi kesalahan sistem, gagal mendapatakan data pasien dengan BPJS no rows in result set", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "No rekam medis 00000012 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "00000012", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "No rekam medis 00000013 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "00000013", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "No rekam medis 00000014 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "00000014", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal mendapatakan data pasien no rows in result set", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "Terjadi kesalahan sistem, gagal mendapatakan data pasien dengan BPJS no rows in result set", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "No rekam medis 00000015 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "00000015", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + }, + { + "message": "No rekam medis 00000016 sudah ada, maka no rekam medis diganti dengan 00064483", + "no_rm": "00000016", + "status": "WARNING" + }, + { + "message": "Terjadi kesalahan sistem, gagal saat import pasien dengan ERROR: INSERT has more target columns than expressions (SQLSTATE 42601)", + "no_rm": "00064483", + "status": "CRITICAL" + } +] \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f95f957 --- /dev/null +++ b/go.mod @@ -0,0 +1,28 @@ +module git.nochill.in/nochill/excel_import_playground + +go 1.21.6 + +require ( + github.com/golang-jwt/jwt/v5 v5.2.0 + github.com/gorilla/mux v1.8.1 + github.com/henvic/pgq v0.0.2 + github.com/jackc/pgx/v5 v5.5.3 + github.com/joho/godotenv v1.5.1 + github.com/xuri/excelize/v2 v2.8.0 +) + +require ( + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/richardlehane/mscfb v1.0.4 // indirect + github.com/richardlehane/msoleps v1.0.3 // indirect + github.com/stretchr/testify v1.8.4 // indirect + github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca // indirect + github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/text v0.14.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..40425d2 --- /dev/null +++ b/go.sum @@ -0,0 +1,94 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/henvic/pgq v0.0.2 h1:4q/G/cW7zpxpwq672Xuh7BkcKcXonZJ6b9kR8ub3EwQ= +github.com/henvic/pgq v0.0.2/go.mod h1:1Q6dKMwtbe2glBXlusJvNZnJrvgbwub/KcfiB/7UXA4= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.3 h1:Ces6/M3wbDXYpM8JyyPD57ivTtJACFZJd885pdIaV2s= +github.com/jackc/pgx/v5 v5.5.3/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= +github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= +github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= +github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca h1:uvPMDVyP7PXMMioYdyPH+0O+Ta/UO1WFfNYMO3Wz0eg= +github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.8.0 h1:Vd4Qy809fupgp1v7X+nCS/MioeQmYVVzi495UCTqB7U= +github.com/xuri/excelize/v2 v2.8.0/go.mod h1:6iA2edBTKxKbZAa7X5bDhcCg51xdOn1Ar5sfoXRGrQg= +github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a h1:Mw2VNrNNNjDtw68VsEj2+st+oCSn4Uz7vZw6TbhcV1o= +github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo= +golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/excel.go b/internal/excel.go new file mode 100644 index 0000000..7fff33d --- /dev/null +++ b/internal/excel.go @@ -0,0 +1,136 @@ +package internal + +import ( + "slices" + "time" +) + +type EnumObject struct { + ID int `json:"id"` + Label string `json:"label"` +} + +var Agama = []EnumObject{ + {ID: 1, Label: "Islam"}, + {ID: 2, Label: "Kristen (Protestan)"}, + {ID: 3, Label: "Katolik"}, + {ID: 4, Label: "Hindu"}, + {ID: 5, Label: "Buddha"}, + {ID: 6, Label: "Konghucu"}, + {ID: 7, Label: "Penghayat"}, + {ID: 8, Label: "Lain-lain"}, +} +var Pendidikan = []EnumObject{ + {ID: 1, Label: "Tidak sekolah"}, + {ID: 2, Label: "SD"}, + {ID: 3, Label: "SLTP sederajat"}, + {ID: 4, Label: "SLTA sederajat"}, + {ID: 5, Label: "D1-D3 sederajat"}, + {ID: 6, Label: "D4"}, // Note: There was a duplicate ID for D4 and S1 + {ID: 6, Label: "S1"}, // Note: There was a duplicate ID for D4 and S1 + {ID: 7, Label: "S2"}, + {ID: 8, Label: "S3"}, +} + +var StatusKawin = []EnumObject{ + {ID: 1, Label: "Belum Kawin"}, + {ID: 2, Label: "Kawin"}, + {ID: 3, Label: "Cerai Hidup"}, + {ID: 4, Label: "Cerai Mati"}, +} + +var Pekerjaan = []EnumObject{ + {ID: 1, Label: "Tidak bekerja"}, + {ID: 2, Label: "PNS"}, + {ID: 3, Label: "TNI/POLRI"}, + {ID: 4, Label: "BUMN"}, + {ID: 5, Label: "Pegawai Swasta/Wirausaha"}, + {ID: 6, Label: "Lain-lain"}, +} + +// export const importDataPasienSchema = z.object({ +// no_rekammedis: z.string(), +// nama_pasien: z.string(), +// no_ktp: z.string().nullish(), +// no_bpjs: z.string().nullish(), +// tgl_lahir: z.date(), +// kelamin: z.number(), +// kebangsaan: z.string().nullish(), +// agama: z.string().nullish().transform(val => Agama.find(x => x.label === val)?.id), +// suku: z.string().nullish(), +// pendidikan: z.coerce.number().nullish(), +// pekerjaan: z.string().nullish().transform(val => Pekerjaan.find(x => x.label === val)?.id), +// hp: z.coerce.string().nullish().transform(val => validatePhoneRegex(val?.replace(/\s+/g, ''))), +// email: z.string().nullish(), +// status_nikah: z.string().nullish().transform(val => StatusKawin.find(x => x.label === val)?.id), +// provinsi: z.string().nullish(), +// kabupaten: z.string().nullish(), +// kecamatan: z.string().nullish(), +// kelurahan: z.string().nullish(), +// kodepos: z.string().nullish(), +// namajalan: z.string().nullish(), +// hp_penjamin: z.coerce.string().nullish().transform(val => validatePhoneRegex(val?.replace(/\s+/g, ''))), +// namapenjamin: z.string().nullish(), +// ktp_penjamin: z.coerce.string().nullish(), +// hubungan_penjamin: z.coerce.number().nullish(), +// pendidikan_penjamin: z.string().nullish().transform(val => Pendidikan.find(x => x.label === val)?.id), +// alamat_penjamin: z.string().nullish(), +// }).transform(({ +// no_rekammedis, +// nama_pasien, +// no_ktp, +// no_bpjs, +// tgl_lahir, +// kelamin, +// status_nikah, +// namajalan, +// hp, +// pendidikan_penjamin, +// ...rest +// }) => ({ +// noRm: no_rekammedis, +// namaPasien: nama_pasien, +// nik: no_ktp, +// noKartuPesertaBPJS: no_bpjs, +// tanggalLahir: tgl_lahir, +// jenisKelamin: kelamin, +// statusPernikahan: status_nikah, +// pendidikanPenjamin: pendidikan_penjamin, +// noHp: hp, +// nama_jalan: namajalan, +// ...rest +// })) + +type Patient struct { + NoRekamMedis string `json:"no_rekammedis"` + NamaPasien string `json:"nama_pasien"` + NoKTP *string `json:"no_ktp"` // Nullable string + NoBPJS *string `json:"no_bpjs"` // Nullable string + TglLahir time.Time `json:"tgl_lahir"` + Kelamin int `json:"kelamin"` + Kebangsaan *string `json:"kebangsaan"` // Nullable string + Agama *int `json:"agama"` // Assuming Agama is an enum, and you'll convert from string to enum ID + Suku *string `json:"suku"` // Nullable string + Pendidikan *int `json:"pendidikan"` // Nullable int + Pekerjaan *int `json:"pekerjaan"` // Assuming Pekerjaan is an enum, and you'll convert from string to enum ID + HP *string `json:"hp"` // Nullable string, assume validation happens elsewhere + Email *string `json:"email"` // Nullable string + StatusNikah *int `json:"status_nikah"` // Assuming StatusKawin is an enum, and you'll convert from string to enum ID + Provinsi *string `json:"provinsi"` // Nullable string + Kabupaten *string `json:"kabupaten"` // Nullable string + Kecamatan *string `json:"kecamatan"` // Nullable string + Kelurahan *string `json:"kelurahan"` // Nullable string + Kodepos *string `json:"kodepos"` // Nullable string + NamaJalan *string `json:"namajalan"` // Nullable string + HPPenjamin *string `json:"hp_penjamin"` // Nullable string, assume validation happens elsewhere + NamaPenjamin *string `json:"namapenjamin"` // Nullable string + KTPPenjamin *string `json:"ktp_penjamin"` // Nullable string + HubunganPenjamin *int `json:"hubungan_penjamin"` // Nullable int + PendidikanPenjamin *int `json:"pendidikan_penjamin"` // Assuming Pendidikan is an enum, and you'll convert from string to enum ID + AlamatPenjamin *string `json:"alamat_penjamin"` // Nullable string +} + +func FindValueByLabel(arr []EnumObject, s string) EnumObject { + idx := slices.IndexFunc(arr, func(c EnumObject) bool { return c.Label == s }) + return arr[idx] +} diff --git a/internal/jwt.go b/internal/jwt.go new file mode 100644 index 0000000..e094a67 --- /dev/null +++ b/internal/jwt.go @@ -0,0 +1,63 @@ +package internal + +import ( + "context" + "log" + "os" + + "github.com/golang-jwt/jwt/v5" +) + +type JwtPayload struct { + UserId float64 `json:"id"` + NamaLengkap string `json:"nama_lengkap"` + NotesId string `json:"notes_id"` + FasyankesId float64 `json:"fasyankes_id"` + Email string `json:"email"` + UserType float64 `json:"user_type"` + PPKPelayanan interface{} `json:"ppk_pelayanan"` + AlamatFasyankes interface{} `json:"alamat_fasyankes"` +} + +func GetPayloadFromContext(ctx context.Context) *JwtPayload { + payload, ok := ctx.Value(UserContext).(JwtPayload) + + if !ok { + return nil + } + + return &payload +} + +func ParseJWT(s string) JwtPayload { + salt := os.Getenv("JWT_SALT") + mySecretKey := []byte(salt) + + // Parse the token + token, err := jwt.Parse(s, func(token *jwt.Token) (interface{}, error) { + return mySecretKey, nil + }) + + if err != nil { + log.Fatal(err) + } + + claims, valid := token.Claims.(jwt.MapClaims) + if !valid && !token.Valid { + log.Fatal("Invalid token") + } + + data := claims["user"].(map[string]interface{}) + + parsedToken := JwtPayload{ + UserId: data["id"].(float64), + NamaLengkap: data["namaLengkap"].(string), + NotesId: data["notesId"].(string), + FasyankesId: data["fasyankesId"].(float64), + Email: data["email"].(string), + UserType: data["userType"].(float64), + PPKPelayanan: data["ppkPelayanan"], + AlamatFasyankes: data["alamatFasyankes"], + } + return parsedToken +} diff --git a/internal/middleware.go b/internal/middleware.go new file mode 100644 index 0000000..9af912a --- /dev/null +++ b/internal/middleware.go @@ -0,0 +1,26 @@ +package internal + +import ( + "context" + "net/http" + "strings" +) + +type contextKey string + +const UserContext contextKey = "user" + +func AuthMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + http.Error(w, "Authorization header is required", http.StatusUnauthorized) + return + } + + authToken := strings.Split(authHeader, "Bearer ")[1] + ctx := context.WithValue(r.Context(), UserContext, ParseJWT(authToken)) + req := r.WithContext(ctx) + next.ServeHTTP(w, req) + }) +} diff --git a/internal/parse_excel.go b/internal/parse_excel.go new file mode 100644 index 0000000..91b5e0d --- /dev/null +++ b/internal/parse_excel.go @@ -0,0 +1,16 @@ +package internal + +import "reflect" + +func ParseExcelValueType[T interface{}](s string) T { + var i T + + t := reflect.TypeOf(i) + + switch t.Kind() { + case reflect.String: + return reflect.ValueOf(s).Convert(t).Interface().(T) + } + + return i +} diff --git a/internal/repository/alamat.repository.go b/internal/repository/alamat.repository.go new file mode 100644 index 0000000..56a69ae --- /dev/null +++ b/internal/repository/alamat.repository.go @@ -0,0 +1,53 @@ +package repository + +import ( + "context" + + "git.nochill.in/nochill/excel_import_playground/model" + "github.com/jackc/pgx/v5" +) + +func (q *Queries) CreateAlamat(ctx context.Context, arg model.Alamat) (model.Alamat, error) { + createAlamatQuery := ` + INSERT INTO "Alamat" ( + kategori, + negara, + provinsi, + kabupaten, + kecamatan, + kelurahan, + kode_pos, + nama_jalan, + rt, + rw, + no_hp, + no_telpon, + pasen_id, + status, + created_at, + updated_at, + ) VALUES ($1,, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) + RETURNING * + ` + + row, _ := q.db.Query(ctx, createAlamatQuery, + arg.Kategori, + arg.Negara, + arg.Provinsi, + arg.Kabupaten, + arg.Kecamatan, + arg.Kelurahan, + arg.Kode_pos, + arg.Nama_jalan, + arg.Rt, + arg.Rw, + arg.No_hp, + arg.No_telpon, + arg.Pasien_id, + arg.Status, + ) + + result, err := pgx.CollectExactlyOneRow[model.Alamat](row, pgx.RowToStructByName[model.Alamat]) + + return result, err +} diff --git a/internal/repository/db.go b/internal/repository/db.go new file mode 100644 index 0000000..cd74303 --- /dev/null +++ b/internal/repository/db.go @@ -0,0 +1,55 @@ +package repository + +import ( + "context" + + "git.nochill.in/nochill/excel_import_playground/model" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" +) + +type Querier interface { + FindLastPatientInCertainFasyankes(ctx context.Context, fasyankes_id int32) (model.Patient, error) + FindPatientByNoRm(ctx context.Context, noRm string) (model.Patient, error) + FindPatientByNik(ctx context.Context, nik string) (model.Patient, error) + FindPatientByBPJSCode(ctx context.Context, BPJSCode string) (model.Patient, error) + UpdatePatientNik(ctx context.Context, nik *string, patientId int32) error + UpdatePatient(ctx context.Context, arg UpdatePatientParams, patientId int32) (model.Patient, error) + // UpdatePatient(ctx context.Context, patientId int32, arg UpdatePatientParams) (model.Patient, error) + GenerateNoRm(ctx context.Context, fasyankesId int32) (string, error) + CreateKeluarga(ctx context.Context, arg model.Keluarga) (model.Keluarga, error) + CreateAlamat(ctx context.Context, arg model.Alamat) (model.Alamat, error) + FindWilayahByName(ctx context.Context, name string) (model.Wilayah, error) +} + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +type Queries struct { + db DBTX +} + +type UpdateableField[T any] struct { + IsFilled bool `default:"false"` + Value T `default:"nil"` +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +func NewTx(db DBTX) *Queries { + return &Queries{db: db} +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} + +var DbPool *pgxpool.Pool diff --git a/internal/repository/keluarga.repository.go b/internal/repository/keluarga.repository.go new file mode 100644 index 0000000..d2ae1e7 --- /dev/null +++ b/internal/repository/keluarga.repository.go @@ -0,0 +1,46 @@ +package repository + +import ( + "context" + + "git.nochill.in/nochill/excel_import_playground/model" + "github.com/jackc/pgx/v5" +) + +func (q *Queries) CreateKeluarga(ctx context.Context, arg model.Keluarga) (model.Keluarga, error) { + createKeluargaQuery := ` + INSERT INTO "Keluarga"( + pasien_id, + nama, + hubungan, + no_identitas, + no_hp, + kota_lahir, + alamat, + tanggal_lahir, + jenis_kelamin, + pendidikan, + pekerjaan, + email, + ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) + ` + + row, _ := q.db.Query(ctx, createKeluargaQuery, + arg.PasienId, + arg.Nama, + arg.Hubungan, + arg.NoIdentitas, + arg.NoHp, + arg.KotaLahir, + arg.Alamat, + arg.TanggalLahir, + arg.JenisKelamin, + arg.Pendidikan, + arg.Pekerjaan, + arg.Email, + ) + + result, err := pgx.CollectExactlyOneRow[model.Keluarga](row, pgx.RowToStructByName[model.Keluarga]) + + return result, err +} diff --git a/internal/repository/patient.repository.go b/internal/repository/patient.repository.go new file mode 100644 index 0000000..8669cde --- /dev/null +++ b/internal/repository/patient.repository.go @@ -0,0 +1,485 @@ +package repository + +import ( + "context" + "strings" + "time" + + "git.nochill.in/nochill/excel_import_playground/model" + "git.nochill.in/nochill/excel_import_playground/util" + "github.com/henvic/pgq" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" +) + +func (q *Queries) FindLastPatientInCertainFasyankes(ctx context.Context, fasyankes_id int32) (model.Patient, error) { + findLastPasienQuery := ` + SELECT * FROM "Pasien" p + WHERE fasyankes_id = $1 + ORDER BY p.id DESC + LIMIT(1) + ` + row, _ := q.db.Query(ctx, findLastPasienQuery, fasyankes_id) + result, err := pgx.CollectExactlyOneRow[model.Patient](row, pgx.RowToStructByName[model.Patient]) + return result, err +} + +func (q *Queries) FindPatientByBPJSCode(ctx context.Context, BPJSCode string) (model.Patient, error) { + findPatientByBPJSCode := ` + SELECT * FROM "Pasien" p + WHERE no_kartu_peserta_bpjs = $1 + ` + + row, _ := q.db.Query(ctx, findPatientByBPJSCode, BPJSCode) + result, err := pgx.CollectExactlyOneRow[model.Patient](row, pgx.RowToStructByName[model.Patient]) + + return result, err +} + +func (q *Queries) FindPatientByNoRm(ctx context.Context, noRm string) (model.Patient, error) { + findPasienByNoRmQuery := ` + SELECT * FROM "Pasien" p + WHERE no_rm = $1 + ` + row, _ := q.db.Query(ctx, findPasienByNoRmQuery, noRm) + result, err := pgx.CollectExactlyOneRow[model.Patient](row, pgx.RowToStructByName[model.Patient]) + return result, err +} + +func (q *Queries) FindPatientByNik(ctx context.Context, nik string) (model.Patient, error) { + findPasienByNikQuery := ` + SELECT * FROM "Pasien" p + WHERE nik = $1 + ` + row, _ := q.db.Query(ctx, findPasienByNikQuery, nik) + result, err := pgx.CollectExactlyOneRow[model.Patient](row, pgx.RowToStructByName[model.Patient]) + return result, err +} + +type UpdatePatientParams struct { + NoRm UpdateableField[string] `json:"no_rm" db:"no_rm"` + NamaPasien UpdateableField[string] `json:"nama_pasien" db:"nama_pasien"` + JenisIdentitas UpdateableField[pgtype.Int4] `json:"jenis_identitas" db:"jenis_identitas"` + NoIdentitas UpdateableField[pgtype.Text] `json:"no_identitas" db:"no_identitas"` + FotoProfil UpdateableField[pgtype.Text] `json:"foto_profil" db:"foto_profil"` + FotoKtp UpdateableField[pgtype.Text] `json:"foto_ktp" db:"foto_ktp"` + KotaLahir UpdateableField[pgtype.Int4] `json:"kota_lahir" db:"kota_lahir"` + TanggalLahir UpdateableField[time.Time] `json:"tanggal_lahir" db:"tanggal_lahir"` + JenisKelamin UpdateableField[pgtype.Int4] `json:"jenis_kelamin" db:"jenis_kelamin"` + Suku UpdateableField[pgtype.Int4] `json:"suku" db:"suku"` + Agama UpdateableField[pgtype.Int4] `json:"agama" db:"agama"` + Kebangsaan UpdateableField[pgtype.Int4] `json:"kebangsaan" db:"kebangsaan"` + Bahasa UpdateableField[pgtype.Int4] `json:"bahasa" db:"bahasa"` + Pendidikan UpdateableField[pgtype.Int4] `json:"pendidikan" db:"pendidikan"` + StatusPerkawinan UpdateableField[pgtype.Int4] `json:"status_perkawinan" db:"status_perkawinan"` + Email UpdateableField[pgtype.Text] `json:"email" db:"email"` + Pekerjaan UpdateableField[pgtype.Int4] `json:"pekerjaan" db:"pekerjaan"` + NoHp UpdateableField[pgtype.Text] `json:"no_hp" db:"no_hp"` + IsDeleted UpdateableField[bool] `json:"is_deleted" db:"is_deleted"` + FasyankesID UpdateableField[int] `json:"fasyankes_id" db:"fasyankes_id"` + Nik UpdateableField[pgtype.Text] `json:"nik" db:"nik"` + NoKk UpdateableField[pgtype.Text] `json:"no_kk" db:"no_kk"` + NoKartuPesertaBPJS UpdateableField[pgtype.Text] `json:"no_kartu_peserta_bpjs" db:"no_kartu_peserta_bpjs"` + UpdateBy int `json:"update_by" db:"update_by"` +} + +func (q *Queries) UpdatePatient(ctx context.Context, arg UpdatePatientParams, patientId int32) (model.Patient, error) { + builder := pgq.Update("\"Pasien\"").Set("update_by", arg.UpdateBy).Set("updated_at", time.Now().UTC()) + + if arg.NoRm.IsFilled { + builder.Set("no_rm", arg.NoRm.Value) + } + if arg.NamaPasien.IsFilled { + builder = builder.Set("nama_pasien", arg.NamaPasien.Value) + } + if arg.JenisIdentitas.IsFilled { + builder = builder.Set("jenis_identitas", arg.JenisIdentitas.Value) + } + if arg.NoIdentitas.IsFilled { + builder = builder.Set("no_identitas", arg.NoIdentitas.Value) + } + if arg.FotoProfil.IsFilled { + builder = builder.Set("foto_profil", arg.FotoProfil.Value) + } + if arg.FotoKtp.IsFilled { + builder = builder.Set("foto_ktp", arg.FotoKtp.Value) + } + if arg.KotaLahir.IsFilled { + builder = builder.Set("kota_lahir", arg.KotaLahir.Value) + } + if arg.TanggalLahir.IsFilled { + builder = builder.Set("tanggal_lahir", arg.TanggalLahir.Value) + } + if arg.JenisKelamin.IsFilled { + builder = builder.Set("jenis_kelamin", arg.JenisKelamin.Value) + } + if arg.Suku.IsFilled { + builder = builder.Set("suku", arg.Suku.Value) + } + if arg.Agama.IsFilled { + builder = builder.Set("agama", arg.Agama.Value) + } + if arg.Kebangsaan.IsFilled { + builder = builder.Set("kebangsaan", arg.Kebangsaan.Value) + } + if arg.Bahasa.IsFilled { + builder = builder.Set("bahasa", arg.Bahasa.Value) + } + if arg.Pendidikan.IsFilled { + builder = builder.Set("pendidikan", arg.Pendidikan.Value) + } + if arg.StatusPerkawinan.IsFilled { + builder = builder.Set("status_perkawinan", arg.StatusPerkawinan.Value) + } + if arg.Email.IsFilled { + builder = builder.Set("email", arg.Email.Value) + } + if arg.Pekerjaan.IsFilled { + builder = builder.Set("pekerjaan", arg.Pekerjaan.Value) + } + if arg.NoHp.IsFilled { + builder = builder.Set("no_hp", arg.NoHp.Value) + } + if arg.IsDeleted.IsFilled { + builder = builder.Set("is_deleted", arg.IsDeleted.Value) + } + if arg.FasyankesID.IsFilled { + builder = builder.Set("fasyankes_id", arg.FasyankesID.Value) + } + if arg.Nik.IsFilled { + builder = builder.Set("nik", arg.Nik.Value) + } + if arg.NoKk.IsFilled { + builder = builder.Set("no_kk", arg.NoKk.Value) + } + if arg.NoKartuPesertaBPJS.IsFilled { + builder = builder.Set("no_kartu_peserta_bpjs", arg.NoKartuPesertaBPJS.Value) + } + + query, args, err := builder.Where(pgq.Eq{"id": patientId}).Suffix("RETURNING *").SQL() + + if err != nil { + return model.Patient{}, err + } + + row, _ := q.db.Query(ctx, query, args...) + result, err := pgx.CollectExactlyOneRow[model.Patient](row, pgx.RowToStructByName[model.Patient]) + return result, err +} + +func (q *Queries) UpdatePatientNik(ctx context.Context, nik *string, patientId int32) error { + updatePatientQuery := ` + UPDATE "Pasien" p + SET nik = $1 + WHERE id = $2 + ` + + _, err := q.db.Exec(ctx, updatePatientQuery, nik, patientId) + return err + +} + +// type UpdatePatientParams struct { +// NoRm string `json:"no_rm" db:"no_rm"` +// NamaPasien string `json:"nama_pasien" db:"nama_pasien"` +// JenisIdentitas pgtype.Int4 `json:"jenis_identitas" db:"jenis_identitas"` +// NoIdentitas pgtype.Text `json:"no_identitas" db:"no_identitas"` +// FotoProfil pgtype.Text `json:"foto_profil" db:"foto_profil"` +// FotoKtp pgtype.Text `json:"foto_ktp" db:"foto_ktp"` +// KotaLahir pgtype.Int4 `json:"kota_lahir" db:"kota_lahir"` +// TanggalLahir *time.Time `json:"tanggal_lahir" db:"tanggal_lahir"` +// JenisKelamin pgtype.Int4 `json:"jenis_kelamin" db:"jenis_kelamin"` +// Suku pgtype.Int4 `json:"suku" db:"suku"` +// Agama pgtype.Int4 `json:"agama" db:"agama"` +// Kebangsaan pgtype.Int4 `json:"kebangsaan" db:"kebangsaan"` +// Bahasa pgtype.Int4 `json:"bahasa" db:"bahasa"` +// Pendidikan pgtype.Int4 `json:"pendidikan" db:"pendidikan"` +// StatusPerkawinan pgtype.Int4 `json:"status_perkawinan" db:"status_perkawinan"` +// Email pgtype.Text `json:"email" db:"email"` +// Pekerjaan pgtype.Int4 `json:"pekerjaan" db:"pekerjaan"` +// NoHp pgtype.Text `json:"no_hp" db:"no_hp"` +// IsDeleted *bool `json:"is_deleted" db:"is_deleted"` +// FasyankesID int `json:"fasyankes_id" db:"fasyankes_id"` +// Nik pgtype.Text `json:"nik" db:"nik"` +// NoKk pgtype.Text `json:"no_kk" db:"no_kk"` +// NoKartuPesertaBPJS pgtype.Text `json:"no_kartu_peserta_bpjs" db:"no_kartu_peserta_bpjs"` +// UpdateBy int `json:"update_by" db:"update_by"` +// UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +// } + +// func (q *Queries) UpdatePatient(ctx context.Context, patientId int32, arg UpdatePatientParams) (model.Patient, error) { +// updatePatientQuery := ` +// UPDATE "Pasien" p +// SET +// no_rm = $1, +// nama_pasien = $2, +// jenis_identitas = $3, +// no_identitas = $4, +// foto_profil = $5, +// foto_ktp = $6, +// kota_lahir = $7, +// tanggal_lahir = $8, +// jenis_kelamin = $9, +// suku = $10, +// agama = $11, +// kebangsaan = $12, +// bahasa = $13, +// pendidikan = $14, +// status_perkawinan = $15, +// email = $16, +// pekerjaan = $17, +// no_hp = $18, +// is_deleted = $19, +// fasyankes_id = $20, +// nik = $21, +// no_kk = $22, +// no_kartu_peserta_bpjs = $23, +// update_by = $24, +// updated_at = $25 +// WHERE id = $26 +// ` + +// row, _ := q.db.Query(ctx, updatePatientQuery, +// arg.NoRm, +// arg.NamaPasien, +// arg.JenisIdentitas, +// arg.NoIdentitas, +// arg.FotoProfil, +// arg.FotoKtp, +// arg.KotaLahir, +// arg.TanggalLahir, +// arg.JenisKelamin, +// arg.Suku, +// arg.Agama, +// arg.Kebangsaan, +// arg.Bahasa, +// arg.Pendidikan, +// arg.StatusPerkawinan, +// arg.Email, +// arg.Pekerjaan, +// arg.NoHp, +// arg.IsDeleted, +// arg.FasyankesID, +// arg.Nik, +// arg.NoKk, +// arg.NoKartuPesertaBPJS, +// arg.UpdateBy, +// arg.UpdatedAt, +// patientId, +// ) + +// result, err := pgx.CollectExactlyOneRow[model.Patient](row, pgx.RowToStructByName[model.Patient]) + +// return result, err +// } + +func (q *Queries) CreatePatient(ctx context.Context, arg model.Patient) (model.Patient, error) { + createPasienQuery := ` + INSERT INTO "Pasien"( + no_rm, + nama_pasien, + jenis_identitas, + no_identitas, + foto_profil, + foto_ktp, + kota_lahir, + tanggal_lahir, + jenis_kelamin, + suku, + agama, + kebangsaan, + bahasa, + pendidikan, + status_perkawinan, + email, + pekerjaan, + no_hp, + is_deleted, + fasyankes_id, + nik, + no_kk, + no_kartu_peserta_bpjs, + create_by, + update_by + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) + RETURNING *; + ` + + row, _ := q.db.Query(ctx, createPasienQuery, + arg.NoRm, + arg.NamaPasien, + arg.JenisIdentitas, + arg.NoIdentitas, + arg.FotoProfil, + arg.FotoKtp, + arg.KotaLahir, + arg.TanggalLahir, + arg.JenisKelamin, + arg.Suku, + arg.Agama, + arg.Kebangsaan, + arg.Bahasa, + arg.Pendidikan, + arg.StatusPerkawinan, + arg.Email, + arg.Pekerjaan, + arg.NoHp, + arg.IsDeleted, + arg.FasyankesID, + arg.Nik, + arg.NoKk, + arg.NoKartuPesertaBPJS, + arg.CreateBy, + arg.UpdateBy, + arg.CreatedAt, + arg.UpdatedAt, + ) + + result, err := pgx.CollectExactlyOneRow[model.Patient](row, pgx.RowToStructByName[model.Patient]) + + return result, err +} + +type ImportDataPasienParams struct { + NoRekamMedis string + NamaPasien string + Nik pgtype.Text + NoBPJS pgtype.Text + TpLahir pgtype.Text + TglLahir time.Time + Kelamin int8 + Kebangsaan *int8 + Agama *int8 + Suku *int32 + Pendidikan *int8 + Pekerjaan *int8 + Hp pgtype.Text + Email pgtype.Text + StatusNikah *int8 + Provinsi *string + Kabupaten *int32 + Kecamatan *int32 + Kelurahan *int32 + Kodepos *int32 + NamaJalan pgtype.Text + HpPenjamin pgtype.Text + NamaPenjamin pgtype.Text + KtpPenjamin pgtype.Text + HubunganPenjamin *int8 + PendidikanPenjamin *int8 + AlamatPenjamin pgtype.Text +} + +func (store *SQLStore) ImportPatientTx(ctx context.Context, params ImportDataPasienParams) error { + err := store.execTx(ctx, func(q *Queries) error { + var err error + + tempNilInt := int8(util.NilIntVal) + + if params.Pendidikan == nil { + params.Pendidikan = &tempNilInt + } + + if params.Agama == nil { + params.Agama = &tempNilInt + } + + if params.Pekerjaan == nil { + params.Pekerjaan = &tempNilInt + } + + if params.StatusNikah == nil { + params.StatusNikah = &tempNilInt + } + + patient, err := q.CreatePatient(ctx, model.Patient{ + NoRm: params.NoRekamMedis, + NamaPasien: params.NamaPasien, + Pendidikan: pgtype.Int4{Valid: params.Pendidikan != nil, Int32: int32(*params.Pendidikan)}, + TanggalLahir: params.TglLahir, + Nik: params.Nik, + Agama: pgtype.Int4{Valid: params.Agama != nil, Int32: int32(*params.Agama)}, + NoHp: params.Hp, + Pekerjaan: pgtype.Int4{Valid: params.Pekerjaan != nil, Int32: int32(*params.Agama)}, + Email: params.Email, + NoKartuPesertaBPJS: params.NoBPJS, + JenisKelamin: params.Kelamin, + StatusPerkawinan: pgtype.Int4{Valid: params.StatusNikah != nil, Int32: int32(*params.StatusNikah)}, + IsDeleted: false, + }) + + if err != nil { + return err + } + + if params.NamaJalan.Valid { + var provinsi_id *int32 + provinsi, err := q.FindWilayahByName(ctx, *params.Provinsi) + + if err != nil { + return err + } + + if provinsi.Id == 0 { + provinsi_id = nil + } else { + provinsi_id = &provinsi.Id + } + + _, err = q.CreateAlamat(ctx, model.Alamat{ + Provinsi: provinsi_id, + Nama_jalan: ¶ms.NamaJalan.String, + Pasien_id: int32(patient.ID), + }) + + if err != nil { + return err + } + + } + + if params.HubunganPenjamin != nil && params.NamaPenjamin.Valid { + _, err = q.CreateKeluarga(ctx, model.Keluarga{ + Id: int32(patient.ID), + Nama: ¶ms.NamaPenjamin.String, + Hubungan: params.HubunganPenjamin, + }) + + if err != nil { + return err + } + } + + return err + }) + + return err +} + +func (q *Queries) GenerateNoRm(ctx context.Context, fasyankesId int32) (string, error) { + lastPasien, err := q.FindLastPatientInCertainFasyankes(ctx, fasyankesId) + + if err != nil { + return "", err + } + + return genNoRm(&lastPasien.NoRm), nil +} + +func genNoRm(lastNoRm *string) string { + var tempNoRm string + + if lastNoRm != nil { + tempNoRm = *lastNoRm + } else { + tempNoRm = "1" + } + + length := strings.IndexFunc(tempNoRm, func(r rune) bool { + return r != '0' + }) + + return util.Pad(tempNoRm, int32(length)) +} + +// func FindPatientBy diff --git a/internal/repository/store.go b/internal/repository/store.go new file mode 100644 index 0000000..126c172 --- /dev/null +++ b/internal/repository/store.go @@ -0,0 +1,43 @@ +package repository + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v5/pgxpool" +) + +type Store interface { + Querier + ImportPatientTx(ctx context.Context, params ImportDataPasienParams) error +} + +type SQLStore struct { + *Queries + pool *pgxpool.Pool +} + +func NewStore(pool *pgxpool.Pool) Store { + return &SQLStore{ + pool: pool, + Queries: New(pool), + } +} + +// TRANSACTION QUERY FUNCTION +func (store *SQLStore) execTx(ctx context.Context, fn func(*Queries) error) error { + tx, err := store.pool.Begin(ctx) + if err != nil { + return err + } + + q := NewTx(tx) + err = fn(q) + if err != nil { + if rbErr := tx.Rollback(ctx); rbErr != nil { + return fmt.Errorf("tx err: %v, rb err : %v", err, rbErr) + } + return err + } + return tx.Commit(ctx) +} diff --git a/internal/repository/wilayah.repository.go b/internal/repository/wilayah.repository.go new file mode 100644 index 0000000..42e6a3b --- /dev/null +++ b/internal/repository/wilayah.repository.go @@ -0,0 +1,20 @@ +package repository + +import ( + "context" + + "git.nochill.in/nochill/excel_import_playground/model" + "github.com/jackc/pgx/v5" +) + +func (q *Queries) FindWilayahByName(ctx context.Context, name string) (model.Wilayah, error) { + findWilayaByNameQuery := ` + SELECT * FROM "Wilayah" + WHERE NamaWilayah = $1 + ` + + row, _ := q.db.Query(ctx, findWilayaByNameQuery, name) + result, err := pgx.CollectExactlyOneRow[model.Wilayah](row, pgx.RowToStructByName[model.Wilayah]) + + return result, err +} diff --git a/internal/rest/handler.go b/internal/rest/handler.go new file mode 100644 index 0000000..f7a635b --- /dev/null +++ b/internal/rest/handler.go @@ -0,0 +1,333 @@ +package rest + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "strconv" + "strings" + "time" + + "git.nochill.in/nochill/excel_import_playground/internal" + "git.nochill.in/nochill/excel_import_playground/internal/repository" + "git.nochill.in/nochill/excel_import_playground/util" + "github.com/gorilla/mux" + "github.com/jackc/pgx/v5/pgtype" + "github.com/xuri/excelize/v2" +) + +func (s *Server) ImportPatientHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + currentUser := internal.GetPayloadFromContext(r.Context()) + + file, header, err := r.FormFile("file") + + if err != nil { + http.Error(w, "Invalid file", http.StatusBadRequest) + return + } + + if !strings.HasSuffix(header.Filename, ".xlsx") && !strings.HasSuffix(header.Filename, ".xls") { + http.Error(w, "File is not an .xlsx or xls file", http.StatusBadRequest) + return + } + + if header.Size > 30<<20 { + http.Error(w, "The uploaded file is too large.", http.StatusBadRequest) + return + } + + buffer := make([]byte, 512) // Create a buffer to store the file header + if _, err = file.Read(buffer); err != nil { + http.Error(w, "Could not read file", http.StatusInternalServerError) + return + } + + // contentType := http.DetectContentType(buffer) + // Reset the read pointer of the file + if _, err = file.Seek(0, 0); err != nil { + http.Error(w, "Could not read file", http.StatusInternalServerError) + return + } + + f, err := excelize.OpenReader(file) + if err != nil { + http.Error(w, "Error opening Excel file", http.StatusInternalServerError) + return + } + defer f.Close() + + rows, err := f.GetRows("import data pasien") + + if err != nil { + json.NewEncoder(w).Encode(err) + return + } + + // _, err = repository.Store.FindLastPatientInCertainFasyankes(s.Store, r.Context(), 2) + + if err != nil { + w.WriteHeader(500) + log.Println(err) + json.NewEncoder(w).Encode(err) + return + } + + // tempArr := make([]repository.ImportDataPasienParams, len(rows)-1) + var errorMsg []interface{} + + for idx, row := range rows { + if idx >= 1 { + + if row[0] == "" || row[1] == "" { + errorMsg = append(errorMsg, map[string]any{ + "baris": idx + 1, + "status": "CRITICAL", + "error_message": "kolom A 'no_rekam medis' dan kolom B 'nama_pasien' wajib diisi", + }) + continue + } + + tanggalLahir, err := time.Parse(util.TIME_PARSE_LAYOUT, row[5]) + if err != nil { + errorMsg = append(errorMsg, map[string]any{ + "baris": idx + 1, + "status": "CRITICAL", + "error_message": "tanggal lahir wajib diisi", + }) + continue + } + + kelamin, err := strconv.Atoi(row[6]) + if err != nil { + log.Printf("row 6: %s, err: %v", row[6], err) + continue + } + + value := repository.ImportDataPasienParams{ + NoRekamMedis: row[0], + NamaPasien: row[1], + Nik: pgtype.Text{String: row[2], Valid: len(row[2]) > 0}, + NoBPJS: pgtype.Text{String: row[3], Valid: len(row[3]) > 0}, + TpLahir: pgtype.Text{String: row[4], Valid: len(row[4]) > 0}, + TglLahir: tanggalLahir, + Kelamin: int8(kelamin), + Kebangsaan: util.StringToIntPtr[int8](row[6]), + Agama: util.StringToIntPtr[int8](row[7]), + Suku: util.StringToIntPtr[int32](row[8]), + Pendidikan: util.StringToIntPtr[int8](row[9]), + Pekerjaan: util.StringToIntPtr[int8](row[10]), + Hp: pgtype.Text{String: row[11], Valid: len(row[11]) > 0}, + Email: pgtype.Text{String: row[12], Valid: len(row[12]) > 0}, + StatusNikah: util.StringToIntPtr[int8](row[13]), + Provinsi: util.StringToStringPtr(row[14]), + Kabupaten: util.StringToIntPtr[int32](row[15]), + Kecamatan: util.StringToIntPtr[int32](row[16]), + Kelurahan: util.StringToIntPtr[int32](row[17]), + Kodepos: util.StringToIntPtr[int32](row[18]), + NamaJalan: pgtype.Text{String: row[19], Valid: len(row[19]) > 0}, + HpPenjamin: pgtype.Text{String: row[20], Valid: len(row[20]) > 0}, + NamaPenjamin: pgtype.Text{String: row[21], Valid: len(row[21]) > 0}, + KtpPenjamin: pgtype.Text{String: row[22], Valid: len(row[22]) > 0}, + HubunganPenjamin: util.StringToIntPtr[int8](row[23]), + PendidikanPenjamin: util.StringToIntPtr[int8](row[24]), + AlamatPenjamin: pgtype.Text{String: row[25], Valid: len(row[25]) > 0}, + } + + patientExist, err := repository.Store.FindPatientByNoRm(s.Store, r.Context(), value.NoRekamMedis) + + if err != nil { + errorMsg = append(errorMsg, map[string]any{ + "no_rm": value.NoRekamMedis, + "status": "CRITICAL", + "message": fmt.Sprintf("Terjadi kesalahan sistem, mencari data pasien %v", err), + }) + continue + } + + if patientExist.NoRm != "" { + if value.NoRekamMedis == patientExist.NoRm { + updatedNoRekamMedis, err := repository.Store.GenerateNoRm(s.Store, r.Context(), int32(currentUser.FasyankesId)) + if err != nil { + errorMsg = append(errorMsg, map[string]any{ + "no_rm": value.NoRekamMedis, + "status": "CRITICAL", + "message": fmt.Sprintf("Terjadi kesalahan sistem, gagal generate NoRM %v", err), + }) + continue + } + + errorMsg = append(errorMsg, map[string]any{ + "no_rm": value.NoRekamMedis, + "status": "WARNING", + "message": fmt.Sprintf("No rekam medis %s sudah ada, maka no rekam medis diganti dengan %s", value.NoRekamMedis, updatedNoRekamMedis), + }) + + value.NoRekamMedis = updatedNoRekamMedis + } + } + + if value.Nik.Valid { + patient, err := repository.Store.FindPatientByNik(s.Store, r.Context(), value.Nik.String) + + if err != nil { + errorMsg = append(errorMsg, map[string]any{ + "no_rm": value.NoRekamMedis, + "status": "CRITICAL", + "message": fmt.Sprintf("Terjadi kesalahan sistem, gagal mendapatakan data pasien %v", err), + }) + } + + if value.Nik.String == patient.Nik.String { + if patient.TanggalLahir.Before(value.TglLahir) { + value.Nik.Valid = false + } else { + _, err := repository.Store.UpdatePatient(s.Store, r.Context(), repository.UpdatePatientParams{ + Nik: repository.UpdateableField[pgtype.Text]{IsFilled: true, Value: pgtype.Text{Valid: false, String: ""}}, + }, int32(patient.ID)) + if err != nil { + errorMsg = append(errorMsg, map[string]any{ + "no_rm": value.NoRekamMedis, + "status": "CRITICAL", + "message": fmt.Sprintf("Terjadi kesalahan sistem, gagal mendapatakan update pasien lama karena NIK sama %v", err), + }) + + continue + } + + errorMsg = append(errorMsg, map[string]any{ + "no_rm": value.NoRekamMedis, + "status": "WARNING", + "message": fmt.Sprintf("NIK kembar dengan pasien: %s, mohon dicek kembali current user: %v", patient.NoRm, value), + }) + + } + + } + } + + if value.NoBPJS.Valid { + patient, err := repository.Store.FindPatientByBPJSCode(s.Store, r.Context(), value.NoBPJS.String) + + if err != nil { + errorMsg = append(errorMsg, map[string]any{ + "no_rm": value.NoRekamMedis, + "status": "CRITICAL", + "message": fmt.Sprintf("Terjadi kesalahan sistem, gagal mendapatakan data pasien dengan BPJS %v", err), + }) + } + + if patient.ID != 0 { + if patient.TanggalLahir.Before(value.TglLahir) { + value.NoBPJS = pgtype.Text{Valid: false, String: ""} + } else { + _, err := repository.Store.UpdatePatient( + s.Store, + r.Context(), + repository.UpdatePatientParams{ + NoKartuPesertaBPJS: repository.UpdateableField[pgtype.Text]{IsFilled: true, Value: pgtype.Text{Valid: false, String: ""}}, + }, + int32(patient.ID), + ) + + if err != nil { + errorMsg = append(errorMsg, map[string]any{ + "no_rm": value.NoRekamMedis, + "status": "CRITICAL", + "message": fmt.Sprintf("Terjadi kesalahan sistem, gagal mendapatakan update pasien dengan BPJS %v", err), + }) + } + continue + } + + errorMsg = append(errorMsg, map[string]any{ + "no_rm": value.NoRekamMedis, + "status": "WARNING", + "message": fmt.Sprintf("No KartuBPJS ada yang sama dengan pasien: %s, mohon dicek kembali. current user: %v", patient.NoRm, value), + }) + } + } + + err = repository.Store.ImportPatientTx(s.Store, r.Context(), value) + + if err != nil { + errorMsg = append(errorMsg, map[string]any{ + "no_rm": value.NoRekamMedis, + "status": "CRITICAL", + "message": fmt.Sprintf("Terjadi kesalahan sistem, gagal saat import pasien dengan %v", err), + }) + + continue + } + + } + } + + if len(errorMsg) > 0 { + jsonMarshal, _ := json.MarshalIndent(errorMsg, "", " ") + if err := os.WriteFile("foo.txt", []byte(jsonMarshal), 0666); err != nil { + log.Fatal(err) + } + } + + a := map[string]any{ + "halo": "halo", + + // "contentType": contentType, + // "patient": tempArr, + "errorData": errorMsg, + } + + json.NewEncoder(w).Encode(a) + +} + +func validateFile(r *http.Request) error { + + return nil +} + +type updatePatientParams struct { + NamaPasien string `json:"patient_name"` +} + +func (s *Server) UpdatePatient(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + var req updatePatientParams + + idParams, ok := vars["id"] + + if !ok { + fmt.Println("id is missing in parameters") + } + + id, err := strconv.Atoi(idParams) + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(err) + return + } + + err = json.NewDecoder(r.Body).Decode(&req) + + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + updatedUser, err := repository.Store.UpdatePatient(s.Store, r.Context(), repository.UpdatePatientParams{ + NamaPasien: repository.UpdateableField[string]{IsFilled: true, Value: req.NamaPasien}, + }, int32(id)) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(updatedUser) + +} diff --git a/internal/rest/server.go b/internal/rest/server.go new file mode 100644 index 0000000..bbcc536 --- /dev/null +++ b/internal/rest/server.go @@ -0,0 +1,15 @@ +package rest + +import "git.nochill.in/nochill/excel_import_playground/internal/repository" + +type Server struct { + Store repository.Store +} + +func NewServer(store repository.Store) (*Server, error) { + server := &Server{ + Store: store, + } + + return server, nil +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..289a513 --- /dev/null +++ b/main.go @@ -0,0 +1,115 @@ +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "runtime" + "time" + + "git.nochill.in/nochill/excel_import_playground/internal" + "git.nochill.in/nochill/excel_import_playground/internal/repository" + "git.nochill.in/nochill/excel_import_playground/internal/rest" + "github.com/gorilla/mux" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/joho/godotenv" +) + +func main() { + runtime.GOMAXPROCS(5) + var wait time.Duration + flag.DurationVar(&wait, "graceful-timeout", time.Second*15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m") + flag.Parse() + + err := godotenv.Load() + if err != nil { + log.Fatal(err.Error()) + } + + _ = os.Getenv("JWT_SALT") + DATABASE_URL := os.Getenv("DATABASE_URL") + APP_HOST := os.Getenv("APP_HOST") + + poolConfig, err := pgxpool.ParseConfig(DATABASE_URL) + if err != nil { + log.Fatal("ENV DATABASE_URL NOT FOUND", err) + } + dbConn, err := pgxpool.NewWithConfig(context.Background(), poolConfig) + + if err != nil { + log.Fatal("cannot connect to db: ", err) + } + + store := repository.NewStore(dbConn) + + server, err := rest.NewServer(store) + + if err != nil { + log.Fatal("Somethng wrong while try to start Server", err) + } + + r := mux.NewRouter() + r.Use(internal.AuthMiddleware) + r.HandleFunc("/", homeRoute).Methods("GET") + r.HandleFunc("/import-pasien", server.ImportPatientHandler).Methods("POST") + r.HandleFunc("/update-pasien/{id}", server.UpdatePatient).Methods("PATCH") + + // r.HandleFunc("/foo", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions) + // r.Use(mux.CORSMethodMiddleware(r)) + + // val := internal.FindValueByLabel("PNS") + + // fmt.Println(val.ID) + + srv := &http.Server{ + Handler: r, + Addr: APP_HOST, + // Good practice: enforce timeouts for servers you create! + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + } + + go func() { + host := fmt.Sprintf("Server running on %s", APP_HOST) + fmt.Println(host) + if err := srv.ListenAndServe(); err != nil { + log.Println(err) + } + }() + + c := make(chan os.Signal, 1) + // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) + // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. + signal.Notify(c, os.Interrupt) + + // Block until we receive our signal. + <-c + + // Create a deadline to wait for. + ctx, cancel := context.WithTimeout(context.Background(), wait) + defer repository.DbPool.Close() + defer cancel() + // Doesn't block if no connections, but will otherwise wait + // until the timeout deadline. + srv.Shutdown(ctx) + // Optionally, you could run srv.Shutdown in a goroutine and block on + // <-ctx.Done() if your application should wait for other services + // to finalize based on context cancellation. + log.Println("shutting down") + os.Exit(0) + +} + +func homeRoute(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + res := map[string]any{ + "halo": "halo", + } + json.NewEncoder(w).Encode(res) + +} diff --git a/model/alamat.go b/model/alamat.go new file mode 100644 index 0000000..9e8ab94 --- /dev/null +++ b/model/alamat.go @@ -0,0 +1,24 @@ +package model + +import "time" + +type Alamat struct { + Id *int32 `json:"id,omitempty"` + Kategori *int32 `json:"kategori,omitempty"` + Negara *int32 `json:"negara,omitempty"` + Provinsi *int32 `json:"provinsi,omitempty"` + Kabupaten *int32 `json:"kabupaten,omitempty"` + Kecamatan *int32 `json:"kecamatan,omitempty"` + Kelurahan *int32 `json:"kelurahan,omitempty"` + Kode_pos *string `json:"kode_pos,omitempty"` + Nama_jalan *string `json:"nama_jalan,omitempty"` + Rt *string `json:"rt,omitempty"` + Rw *string `json:"rw,omitempty"` + No_hp *string `json:"no_hp,omitempty"` + No_telpon *string `json:"no_telpon,omitempty"` + Pasien_id int32 `json:"pasen_id"` + Status *bool `json:"status,omitempty"` + Created_at time.Time `json:"created_at"` + Updated_at time.Time `json:"updated_at"` + Is_deleted *bool `json:"is_deleted,omitempty"` +} diff --git a/model/db.go b/model/db.go new file mode 100644 index 0000000..8b53790 --- /dev/null +++ b/model/db.go @@ -0,0 +1 @@ +package model diff --git a/model/keluarga.go b/model/keluarga.go new file mode 100644 index 0000000..246fcec --- /dev/null +++ b/model/keluarga.go @@ -0,0 +1,19 @@ +package model + +import "time" + +type Keluarga struct { + Id int32 `json:"id"` + PasienId int32 `json:"pasien_id"` + Nama *string `json:"nama,omitempty"` + Hubungan *int8 `json:"hubungan,omitempty"` + NoIdentitas *string `json:"no_identitas,omitempty"` + NoHp *string `json:"no_hp,omitempty"` + KotaLahir *int32 `json:"kota_lahir,omitempty"` + Alamat *string `json:"alamat,omitempty"` + TanggalLahir time.Time `json:"tanggal_lahir,omitempty"` + JenisKelamin *int32 `json:"jenis_kelamin,omitempty"` + Pendidikan *int32 `json:"pendidikan,omitempty"` + Pekerjaan *int32 `json:"pekerjaan,omitempty"` + Email *string `json:"email,omitempty"` +} diff --git a/model/patient.go b/model/patient.go new file mode 100644 index 0000000..38e5bf9 --- /dev/null +++ b/model/patient.go @@ -0,0 +1,38 @@ +package model + +import ( + "time" + + "github.com/jackc/pgx/v5/pgtype" +) + +type Patient struct { + ID int `json:"id" db:"id"` + NoRm string `json:"no_rm" db:"no_rm"` + NamaPasien string `json:"nama_pasien" db:"nama_pasien"` + JenisIdentitas pgtype.Int4 `json:"jenis_identitas" db:"jenis_identitas"` + NoIdentitas pgtype.Text `json:"no_identitas" db:"no_identitas"` + FotoProfil pgtype.Text `json:"foto_profil" db:"foto_profil"` + FotoKtp pgtype.Text `json:"foto_ktp" db:"foto_ktp"` + KotaLahir pgtype.Int4 `json:"kota_lahir" db:"kota_lahir"` + TanggalLahir time.Time `json:"tanggal_lahir" db:"tanggal_lahir"` + JenisKelamin int8 `json:"jenis_kelamin" db:"jenis_kelamin"` + Suku pgtype.Int4 `json:"suku" db:"suku"` + Agama pgtype.Int4 `json:"agama" db:"agama"` + Kebangsaan pgtype.Int4 `json:"kebangsaan" db:"kebangsaan"` + Bahasa pgtype.Int4 `json:"bahasa" db:"bahasa"` + Pendidikan pgtype.Int4 `json:"pendidikan" db:"pendidikan"` + StatusPerkawinan pgtype.Int4 `json:"status_perkawinan" db:"status_perkawinan"` + Email pgtype.Text `json:"email" db:"email"` + Pekerjaan pgtype.Int4 `json:"pekerjaan" db:"pekerjaan"` + NoHp pgtype.Text `json:"no_hp" db:"no_hp"` + IsDeleted bool `json:"is_deleted" db:"is_deleted"` + FasyankesID int `json:"fasyankes_id" db:"fasyankes_id"` + Nik pgtype.Text `json:"nik" db:"nik"` + NoKk pgtype.Text `json:"no_kk" db:"no_kk"` + NoKartuPesertaBPJS pgtype.Text `json:"no_kartu_peserta_bpjs" db:"no_kartu_peserta_bpjs"` + CreateBy int `json:"create_by" db:"create_by"` + UpdateBy int `json:"update_by" db:"update_by"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} diff --git a/model/wilayah.go b/model/wilayah.go new file mode 100644 index 0000000..70f2adb --- /dev/null +++ b/model/wilayah.go @@ -0,0 +1,9 @@ +package model + +type Wilayah struct { + Id int32 + KodeWilayah string + NamaWilayah string + KodeBPJS *string + NamaBPJS *string +} diff --git a/util/common.go b/util/common.go new file mode 100644 index 0000000..0860bd2 --- /dev/null +++ b/util/common.go @@ -0,0 +1,53 @@ +package util + +import ( + "fmt" + "reflect" + "strconv" +) + +const TIME_PARSE_LAYOUT = "2/1/2006" +const NilIntVal = int8(0) + +func StringToIntPtr[T int8 | int32 | int64](s string) *T { + var val T + switch any(val).(type) { + case int8, int32, int64: + num, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return nil + } + switch any(val).(type) { + case int8: + val := int8(num) + return any(&val).(*T) + case int32: + val := int32(num) + return any(&val).(*T) + case int64: + val := int64(num) + return any(&val).(*T) + default: + return nil + } + default: + fmt.Printf("Unsupported type: %s\n", reflect.TypeOf(val).Name()) + return nil + } +} + +func StringToStringPtr(s string) *string { + if s == "" { + return nil + } + + return &s +} + +func Pad(numStr string, size int32) string { + for int32(len(numStr)) < size { + numStr = "0" + numStr + } + + return numStr +}