From 78f1502930180b2bdd25c40f9bf46a1af0e96654 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 12 Nov 2025 12:07:22 +0700 Subject: [PATCH 01/18] migration from server --- cmd/main-migration/migrations/atlas.sum | 80 ++++++++++++------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/cmd/main-migration/migrations/atlas.sum b/cmd/main-migration/migrations/atlas.sum index 6bdc5512..36eff156 100644 --- a/cmd/main-migration/migrations/atlas.sum +++ b/cmd/main-migration/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:G7XwMRXjqZGaTeOozryI7WRd+FUJqE+DYsSfura7viU= +h1:gxzRzsKXNeg9LaO7Hak8tLYmALxv5wumfuA+BSfFD3E= 20250904105930.sql h1:MEM6blCgke9DzWQSTnLzasbPIrcHssNNrJqZpSkEo6k= 20250904141448.sql h1:J8cmYNk4ZrG9fhfbi2Z1IWz7YkfvhFqTzrLFo58BPY0= 20250908062237.sql h1:Pu23yEW/aKkwozHoOuROvHS/GK4ngARJGdO7FB7HZuI= @@ -75,42 +75,42 @@ h1:G7XwMRXjqZGaTeOozryI7WRd+FUJqE+DYsSfura7viU= 20251106040137.sql h1:ppcqkVoT0o9jZcjI/TN7LuaPxXhJQhnIXEJtloP/46o= 20251106041333.sql h1:2JkxyelQ/EeB+boL5bfpnzefw32ttEGKvKchtQjWmAU= 20251106042006.sql h1:ruppYa1kAJQUU3ufQBbKGMcXrGbGJJiRPclT+dNc/YQ= -20251106050412.sql h1:1002KYtHd8AwrQTMewbs/PPHDylHDghigE/3S7PVdMA= -20251106063418.sql h1:jPW/gBnbFl4RO39lQ0ZMDtYA6xbhyD6CgQupT50HmaY= -20251106071906.sql h1:leYGKxR3EQn794aOehf0sd/ZPmOnvBMZPy5/anGmRB4= -20251106073157.sql h1:KASMzjjjk5UB7Zj8lCRtM1utc4ZnDjlnpZbtTe3vONE= -20251106074218.sql h1:Z5q5deOvLaZDPhiVTN9st3/s56RepBa2YOyrMXBdj4A= -20251106081846.sql h1:P+VsWwhGt60adDIZuE/Aa38JVp/yX1rnsdpXpxASodw= -20251106082844.sql h1:Dmi5A8i9frQZvdXYPwc7f8CisZtBH8liSXq1rI6z1iM= -20251106090021.sql h1:4JwdKgO8T46YhyWVJUxpRIwudBDlG8QN1brSOYmgQ20= -20251106144745.sql h1:nqnQCzGrVJaq8ilOEOGXeRUL1dolj+OPWKuP8A92FRA= -20251107012049.sql h1:Pff4UqltGS3clSlGr0qq8CQM56L29wyxY0FC/N/YAhU= -20251107064812.sql h1:GB9a0ZfMYTIoGNmKUG+XcYUsTnRMFfT4/dAD71uCPc4= -20251107064937.sql h1:IC5pw1Ifj30hiE6dr5NMHXaSHoQI+vRd40N5ABgBHRI= -20251107071420.sql h1:9NO3iyLEXEtWa2kSRjM/8LyzuVIk6pdFL2SuheWjB08= -20251107074318.sql h1:7fHbSRrdjOmHh/xwnjCLwoiB5cW5zeH+uxLV0vZbkIA= -20251107075050.sql h1:np+3uTOnU9QNtK7Knaw8eRMhkyB9AwrtSNHphOBxbHY= -20251107080604.sql h1:cXDBLPJDVWLTG6yEJqkJsOQ7p7VYxLM2SY+mwO8qSHo= -20251107081830.sql h1:/S7OQZo4ZnK80t28g/JyiOTZtmWG/dP5Wg2zXNMQ/iE= -20251107091033.sql h1:/cbkF1nO/IjNSIfDJJx456KJtQ9rWFXOBFAkR/M2xiE= -20251107091209.sql h1:jrLQOUeV8ji2fg0pnEcs1bw4ANUxzTSMXC/rrHLIY+M= -20251107091541.sql h1:6UqbhQQRmzA2+eKu5lIvkwOkk+lH70QLZC8Pjpjcq68= -20251110012217.sql h1:C9HpX0iyHzKjyNv/5DSAn2MCHj6MX4p5UQ/NrY7QD0w= -20251110012306.sql h1:J54yb27d30LBbYp9n1P66gFVRlxPguKu0kxmWIBBG8g= -20251110052049.sql h1:232T2x8xTczJl9nk4jxJpZXhoOGYthhxjJ7nK8Jd8vg= -20251110062042.sql h1:WnfVUXrzYoj8qdkkjO9/JQQ8agGd4GfSHQdMjo7LDAg= -20251110063202.sql h1:hSzGfwVMWa6q3vwIQZUkxKgBNCzHjB+6GKy54zfV+oQ= -20251110063633.sql h1:/VpofIAqNS1CnazEnpW/+evbzn9Kew3xDW48r57M+Xg= -20251110085551.sql h1:bFZwSmfvVbTUr/enWB82WqjG88gpqcZ6s45btUvO0uo= -20251110091516.sql h1:KkJMwPQuaZQhiqnKrNQrgP12gw9rV8T3P2o3mtGTcvY= -20251110091948.sql h1:I4odAYrJdvNf1jPw6ppDC0XdI7v6vKBACg/ABwUgA7I= -20251110092729.sql h1:l1out8soEmVP6dNjaIOtGYo6QDcoJZRI8X1sjZ5ZGmo= -20251110093522.sql h1:nsz8jCxGjEdr/bz9g+4ozfZzIP803xONjVmucad1GMc= -20251110100258.sql h1:IBqt1VZj5WjQ+l9aAFGHOCCBtzb03KlLLihFLut7itg= -20251110100545.sql h1:6/LV7751iyKxE2xI6vO1zly+aHUwxXD/IBwLcVpKxqM= -20251111072601.sql h1:dEhwrkT0hJ06/YcvQd5alvdskimcHcYT27QKAzVY5+8= -20251111073546.sql h1:JnJZ4SdOObSe6Jf8v/i/KiRxoCz5KMeXCYytQMZgkZM= -20251111074148.sql h1:95Ui1eo1P68itOz5kZDNFi2ha0ZhUF4gMYiYVcip6fo= -20251111074652.sql h1:vUZbN0qgktRQ2GAlCpdYrbld2grPiSbvcMePEQMfxPs= -20251111082257.sql h1:Zr3Xg5n+p4C8F6Evqm5PVC0pqUvPTBcq692PiUEJlT8= -20251111111017.sql h1:RNLwrUNguAy/m2h3oYwMd5VuL1juRJMS1Taqujvye08= +20251106050412.sql h1:MiEMJ1HCFYnalKuq3Z38xJeogfBAMqsTv2sG4EF8dDw= +20251106063418.sql h1:y3veDJPjKekOWLCZek/LgQwXPRhZtOppTfUXiqoL95s= +20251106071906.sql h1:/TUZA3XpMY23qEJXdkTwlzrNMvSSl6JJniPcgAttBaw= +20251106073157.sql h1:78txeibJ602DMD7huD618ZSMt6phSRzDNPTlo0PGyrc= +20251106074218.sql h1:8Xz7WywrtUnSxOHhlal53gG9rE7r86LFUt5zBFe/mIs= +20251106081846.sql h1:jp91Bf5bxGXMiUB1VIuN6y768vb2iWwow44WfCE5J5k= +20251106082844.sql h1:RHYzRO4G1fSWwf+xc/3QezZ/Iil67cZPIgNpNz3TNhQ= +20251106090021.sql h1:dFDk6mq+zjbYWmfWIrHf9DiKvvoXHjrr0++zssMTWP8= +20251106144745.sql h1:aHcr23iBFqCHer5D/SsPMXBCLjGqUYvWYfRU8jSJgIw= +20251107012049.sql h1:hu/7NHhnAkT4xK0RNtqmMDdH1Bo5EZbl7itDRjiCT+g= +20251107064812.sql h1:sfCXDQYnMf0ddrQ9oYljWJLLSt9NJjJV6o8VS3p7aZE= +20251107064937.sql h1:DlYGJ9LZFwZyR7jBP5zaGB128aIc4HAixBKPYCz9EkY= +20251107071420.sql h1:ynCdZAd2utLl+FhtWZwtahNXgIVOvuk3s/rOq7lfXA4= +20251107074318.sql h1:WE9cPhibWtZ0dbu1VEGirTeY6ijFYGMNhHdBtM32kOc= +20251107075050.sql h1:8tvneruqdynDOaJK1+0z4CH7YXZStZpGdqwIeOMLik4= +20251107080604.sql h1:8c4jd4Tql7tcdhbI9NS0tgvN+ADu9FnCf8wMUbmW7A0= +20251107081830.sql h1:SAAe3lmsm9vGXuSEsDdl7ad0EAxP5CMmFRDEgp9M7yY= +20251107091033.sql h1:JLdX/u7GUdBfjrPrMSNAqc8HtSoj0YA9iW9Vc6FJZdw= +20251107091209.sql h1:CzhYtwAwT+GHrbqcagnJE+v3mbl/rObf1IJaLCKlzrs= +20251107091541.sql h1:+3ZyWJTftDY2JeWThXuIxGWpUBnyMPyOyY4jBjdWYJI= +20251110012217.sql h1:f4Z8TuGc+XMSJ+Ekn4/PeHRE2FlHWkc5gKPJB0hAX7c= +20251110012306.sql h1:ENPyI6Kjdk6qKJQb0yJ6MCTDPAmO1WD/uhKuCSl+jYo= +20251110052049.sql h1:OrQ0acnyoQLKnTitZfnBcVr5jDslF59OFLaqT7SpdVs= +20251110062042.sql h1:9KwldQt0NpVPR86L0T4hlkfHAGau+7CiZYgu5rF+yhg= +20251110063202.sql h1:A117DuZmZ6U0jWHA3DISnr+yvBjKIr1ObrUr047YezQ= +20251110063633.sql h1:qTiC0F19JnhUIXF4LGJQ21jEV6kKGyhTr1x2kimFqPQ= +20251110085551.sql h1:HZcJM0RSC6HBaUSjKBE8MgDG8Vn9f3LmwA/OnT9Cp7I= +20251110091516.sql h1:W3AQhQLgirEWuCObbLl+Prdrbq6k6EEY1xcoWsmbog4= +20251110091948.sql h1:3tsITMrZr/T+L4wqUMz8sHS229jCJl4T0Nu3dMccxH8= +20251110092729.sql h1:uU+k88RH/e0Ns4/SmJl03RVYPscBAPuiLfxR6CJqaf0= +20251110093522.sql h1:O7upSj8VNjzvroL4IU59bfxKATOkAVGBArcUbVNq9aM= +20251110100258.sql h1://JSArUMNI3/gAtYDx2VN94C198SFW0ANjgs+p6eCRM= +20251110100545.sql h1:ENPOqeJYRpMI4e8VCKwaQgaql8se6pIidAhG2cjskBg= +20251111072601.sql h1:6p2qynJ2vr3C18peYciAbOIWOoj/+PnNcobU3wNHboQ= +20251111073546.sql h1:BORlFReAZTDLUfpS/eIf5qGkJ+FNznMGK47HRiPkfL8= +20251111074148.sql h1:qzjLGQRWo1Gg8LkJFgTb5Ak7h53tdz+eQrpJqC9+Gc0= +20251111074652.sql h1:uuThrt4wjdq4qEbvS67fF0Nfw/dlj7vGnLqywWBznqk= +20251111082257.sql h1:eUfu0tDtl9KqX7UK8is6Qc04NSQs4BXwKsykZwnVH1w= +20251111111017.sql h1:UGUI29WXoDdEp4jWtbzrFGhfO8XO50IafBE7cMfo70k= From 4b78e489ef806ebba23700f1914b1e018ff35f36 Mon Sep 17 00:00:00 2001 From: vanilia Date: Thu, 13 Nov 2025 10:33:37 +0700 Subject: [PATCH 02/18] add instalation entity --- cmd/simgos-sync-migration/Makefile | 18 ++++++ cmd/simgos-sync-migration/README-ATLAS.MD | 59 +++++++++++++++++++ cmd/simgos-sync-migration/atlas.hcl.example | 22 +++++++ cmd/simgos-sync-migration/migration.go | 9 +++ internal/domain/references/common/common.go | 4 ++ .../simgos-entities/installation/entity.go | 12 ++++ internal/domain/simgos-sync-entities/.keep | 0 .../sync-entities/installation/entity.go | 29 +++++++++ internal/interface/migration/migration.go | 2 + .../migration/simgossync-entities.go | 9 +++ internal/interface/migration/tycovar.go | 5 +- 11 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 cmd/simgos-sync-migration/Makefile create mode 100644 cmd/simgos-sync-migration/README-ATLAS.MD create mode 100644 cmd/simgos-sync-migration/atlas.hcl.example create mode 100644 cmd/simgos-sync-migration/migration.go create mode 100644 internal/domain/simgos-entities/installation/entity.go delete mode 100644 internal/domain/simgos-sync-entities/.keep create mode 100644 internal/domain/sync-entities/installation/entity.go create mode 100644 internal/interface/migration/simgossync-entities.go diff --git a/cmd/simgos-sync-migration/Makefile b/cmd/simgos-sync-migration/Makefile new file mode 100644 index 00000000..e83356e5 --- /dev/null +++ b/cmd/simgos-sync-migration/Makefile @@ -0,0 +1,18 @@ +# Makefile for Atlas migrations + +# Default environment +ENV ?= gorm + +.PHONY: diff apply hash + +## Generate a new migration diff +diff: + atlas migrate diff --env $(ENV) + +## Apply migrations to the database +apply: + atlas migrate apply --env $(ENV) + +## Calculate the schema hash +hash: + atlas migrate hash diff --git a/cmd/simgos-sync-migration/README-ATLAS.MD b/cmd/simgos-sync-migration/README-ATLAS.MD new file mode 100644 index 00000000..da249823 --- /dev/null +++ b/cmd/simgos-sync-migration/README-ATLAS.MD @@ -0,0 +1,59 @@ +# Database Migration with Atlas + +This project uses [Atlas](https://atlasgo.io/) for database schema management and migrations. + +## 📋 Prerequisites + +1. **Download and Install Atlas CLI** + Run the following command in PowerShell or Git Bash: + + ```sh + curl -sSf https://atlasgo.sh | sh + ``` + Verify installation: + + ```sh + atlas version + ``` + +2. Install GORM Provider + Run inside your Go project: + + ```sh + go get -u ariga.io/atlas-provider-gorm + ``` + +3. Create atlas.hcl configuration file + Just create an atlas.hcl file in your project root as example given at atlas.hcl.example +4. Create migrations folder + ```sh + mkdir migrations + ``` +5. Usage +You can use the provided Makefile for common commands: + + Generate a migration diff + ```sh + make diff + ``` + + Apply migrations + ```sh + make apply + ``` + + Compute schema hash + ```sh + make hash + ``` + + If you don’t have make installed, you can run the Atlas commands directly: + ```sh + atlas migrate diff --env gorm + ``` + ```sh + atlas migrate apply --env gorm + ``` + ```sh + atlas migrate hash + ``` \ No newline at end of file diff --git a/cmd/simgos-sync-migration/atlas.hcl.example b/cmd/simgos-sync-migration/atlas.hcl.example new file mode 100644 index 00000000..857d1352 --- /dev/null +++ b/cmd/simgos-sync-migration/atlas.hcl.example @@ -0,0 +1,22 @@ +data "external_schema" "gorm" { + program = [ + "go", + "run", + "-mod=mod", + ".", + ] +} + +env "gorm" { + src = data.external_schema.gorm.url + dev = "" // dsn db to check the diff + migration { + dir = "file://migrations" + } + url = "" // dsn db to apply + format { + migrate { + diff = "{{ sql . \" \" }}" + } + } +} \ No newline at end of file diff --git a/cmd/simgos-sync-migration/migration.go b/cmd/simgos-sync-migration/migration.go new file mode 100644 index 00000000..fdd2c995 --- /dev/null +++ b/cmd/simgos-sync-migration/migration.go @@ -0,0 +1,9 @@ +package main + +import ( + m "simrs-vx/internal/interface/migration" +) + +func main() { + m.Migrate(m.SimgosSync) +} diff --git a/internal/domain/references/common/common.go b/internal/domain/references/common/common.go index 2941049c..71396f2a 100644 --- a/internal/domain/references/common/common.go +++ b/internal/domain/references/common/common.go @@ -16,6 +16,7 @@ type ( DataVerifiedCode string CrudCode string DataApprovalCode string + ProcessStatusCode string ) const ( @@ -101,6 +102,9 @@ const ( DACNew DataApprovalCode = "new" DACApproved DataApprovalCode = "approved" DACRejected DataApprovalCode = "rejected" + + PSCSuccess ProcessStatusCode = "success" + PSCFailed ProcessStatusCode = "failed" ) func GetDayCodes() map[DayCode]string { diff --git a/internal/domain/simgos-entities/installation/entity.go b/internal/domain/simgos-entities/installation/entity.go new file mode 100644 index 00000000..88028fc4 --- /dev/null +++ b/internal/domain/simgos-entities/installation/entity.go @@ -0,0 +1,12 @@ +package installation + +type MInstalasi struct { + NoInstalasi uint `json:"no_instalasi"` + NamaInstalasi string `json:"nama_instalasi"` + StatusRawatInap *uint `json:"status_rawat_inap"` + StAktif *uint64 `json:"st_aktif"` +} + +func (MInstalasi) TableName() string { + return "m_instalasi" +} diff --git a/internal/domain/simgos-sync-entities/.keep b/internal/domain/simgos-sync-entities/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/internal/domain/sync-entities/installation/entity.go b/internal/domain/sync-entities/installation/entity.go new file mode 100644 index 00000000..46ffe71b --- /dev/null +++ b/internal/domain/sync-entities/installation/entity.go @@ -0,0 +1,29 @@ +package installation + +import ( + ecore "simrs-vx/internal/domain/base-entities/core" + erc "simrs-vx/internal/domain/references/common" + "time" +) + +type InstallationLink struct { + ecore.Main + Simx_Id string `json:"simx_id" gorm:"unique"` + Simgos_Id uint64 `json:"simgos_id" gorm:"unique"` +} + +type InstallationSimxLog struct { + ecore.Main + Value *string `json:"value"` + Date *time.Time `json:"date"` + Status erc.ProcessStatusCode `json:"status"` + ErrMessage *string `json:"errMessage"` +} + +type InstallationSimgosLog struct { + ecore.Main + Value *string `json:"value"` + Date *time.Time `json:"date"` + Status erc.ProcessStatusCode `json:"status"` + ErrMessage *string `json:"errMessage"` +} diff --git a/internal/interface/migration/migration.go b/internal/interface/migration/migration.go index 6cec68dc..31ec1840 100644 --- a/internal/interface/migration/migration.go +++ b/internal/interface/migration/migration.go @@ -31,6 +31,8 @@ func getEntities(input string) []any { return getMainEntities() case "satusehat": return getSatuSehatEntities() + case "simgossync": + return getSimgosSyncEntities() } return nil } diff --git a/internal/interface/migration/simgossync-entities.go b/internal/interface/migration/simgossync-entities.go new file mode 100644 index 00000000..175a0ba9 --- /dev/null +++ b/internal/interface/migration/simgossync-entities.go @@ -0,0 +1,9 @@ +package migration + +func getSimgosSyncEntities() []any { + return []any{ + //&installation.InstallationLink{}, + //&installation.InstallationSimxLog{}, + //&installation.InstallationSimgosLog{}, + } +} diff --git a/internal/interface/migration/tycovar.go b/internal/interface/migration/tycovar.go index f86f855d..f6c57b4b 100644 --- a/internal/interface/migration/tycovar.go +++ b/internal/interface/migration/tycovar.go @@ -2,6 +2,7 @@ package migration const ( - Main = "main" - SatuSehat = "satusehat" + Main = "main" + SatuSehat = "satusehat" + SimgosSync = "simgossync" ) From 8cfd561a6cc328983a741f3d435a0d4c32d0deb1 Mon Sep 17 00:00:00 2001 From: vanilia Date: Thu, 13 Nov 2025 10:57:53 +0700 Subject: [PATCH 03/18] add migration sync --- .../migrations/20251113035508.sql | 36 +++++++++++++++++++ .../migrations/atlas.sum | 2 ++ .../sync-entities/installation/entity.go | 4 +-- .../migration/simgossync-entities.go | 11 ++++-- 4 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 cmd/simgos-sync-migration/migrations/20251113035508.sql create mode 100644 cmd/simgos-sync-migration/migrations/atlas.sum diff --git a/cmd/simgos-sync-migration/migrations/20251113035508.sql b/cmd/simgos-sync-migration/migrations/20251113035508.sql new file mode 100644 index 00000000..5dddf417 --- /dev/null +++ b/cmd/simgos-sync-migration/migrations/20251113035508.sql @@ -0,0 +1,36 @@ +-- Create "InstallationLink" table +CREATE TABLE "public"."InstallationLink" ( + "Id" bigserial NOT NULL, + "CreatedAt" timestamptz NULL, + "UpdatedAt" timestamptz NULL, + "DeletedAt" timestamptz NULL, + "Simx_Id" bigint NULL, + "Simgos_Id" bigint NULL, + PRIMARY KEY ("Id"), + CONSTRAINT "uni_InstallationLink_Simgos_Id" UNIQUE ("Simgos_Id"), + CONSTRAINT "uni_InstallationLink_Simx_Id" UNIQUE ("Simx_Id") +); +-- Create "InstallationSimgosLog" table +CREATE TABLE "public"."InstallationSimgosLog" ( + "Id" bigserial NOT NULL, + "CreatedAt" timestamptz NULL, + "UpdatedAt" timestamptz NULL, + "DeletedAt" timestamptz NULL, + "Value" text NULL, + "Date" timestamptz NULL, + "Status" text NULL, + "ErrMessage" text NULL, + PRIMARY KEY ("Id") +); +-- Create "InstallationSimxLog" table +CREATE TABLE "public"."InstallationSimxLog" ( + "Id" bigserial NOT NULL, + "CreatedAt" timestamptz NULL, + "UpdatedAt" timestamptz NULL, + "DeletedAt" timestamptz NULL, + "Value" text NULL, + "Date" timestamptz NULL, + "Status" text NULL, + "ErrMessage" text NULL, + PRIMARY KEY ("Id") +); diff --git a/cmd/simgos-sync-migration/migrations/atlas.sum b/cmd/simgos-sync-migration/migrations/atlas.sum new file mode 100644 index 00000000..50f3c7d3 --- /dev/null +++ b/cmd/simgos-sync-migration/migrations/atlas.sum @@ -0,0 +1,2 @@ +h1:OaRZyFJMii/8wQ0XLWEpF9MIL8IXblx3IYvB7LN2XQ4= +20251113035508.sql h1:p/FKefphuiBjEND87ChQLS/lcZcnvn0aT5tRV43Dn5o= diff --git a/internal/domain/sync-entities/installation/entity.go b/internal/domain/sync-entities/installation/entity.go index 46ffe71b..a7818f3f 100644 --- a/internal/domain/sync-entities/installation/entity.go +++ b/internal/domain/sync-entities/installation/entity.go @@ -8,8 +8,8 @@ import ( type InstallationLink struct { ecore.Main - Simx_Id string `json:"simx_id" gorm:"unique"` - Simgos_Id uint64 `json:"simgos_id" gorm:"unique"` + Simx_Id uint `json:"simx_id" gorm:"unique"` + Simgos_Id uint `json:"simgos_id" gorm:"unique"` } type InstallationSimxLog struct { diff --git a/internal/interface/migration/simgossync-entities.go b/internal/interface/migration/simgossync-entities.go index 175a0ba9..3b67265f 100644 --- a/internal/interface/migration/simgossync-entities.go +++ b/internal/interface/migration/simgossync-entities.go @@ -1,9 +1,14 @@ package migration +import ( + /************** Source ***************/ + installation "simrs-vx/internal/domain/sync-entities/installation" +) + func getSimgosSyncEntities() []any { return []any{ - //&installation.InstallationLink{}, - //&installation.InstallationSimxLog{}, - //&installation.InstallationSimgosLog{}, + &installation.InstallationLink{}, + &installation.InstallationSimxLog{}, + &installation.InstallationSimgosLog{}, } } From 4f9663d98ae9ed0c696b695c9b76c072475b690f Mon Sep 17 00:00:00 2001 From: vanilia Date: Thu, 13 Nov 2025 11:41:29 +0700 Subject: [PATCH 04/18] update m_installation --- internal/domain/simgos-entities/installation/entity.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/domain/simgos-entities/installation/entity.go b/internal/domain/simgos-entities/installation/entity.go index 88028fc4..d620af5e 100644 --- a/internal/domain/simgos-entities/installation/entity.go +++ b/internal/domain/simgos-entities/installation/entity.go @@ -1,10 +1,10 @@ package installation type MInstalasi struct { - NoInstalasi uint `json:"no_instalasi"` - NamaInstalasi string `json:"nama_instalasi"` - StatusRawatInap *uint `json:"status_rawat_inap"` - StAktif *uint64 `json:"st_aktif"` + NoInstalasi uint `json:"no_instalasi"` + NamaInstalasi string `json:"nama_instalasi"` + StatusRawatInap uint `json:"status_rawat_inap"` + StAktif uint `json:"st_aktif"` } func (MInstalasi) TableName() string { From 3720ca2e718206292e08eb753b5ddf9c78cd6de9 Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Sat, 8 Nov 2025 06:07:14 +0700 Subject: [PATCH 05/18] feat/sso-auth: wip --- .../main-handler/authentication/handler.go | 28 +- .../main-use-case/authentication/case.go | 332 ++++++++++++------ .../main-use-case/authentication/helper.go | 122 +++++++ 3 files changed, 368 insertions(+), 114 deletions(-) diff --git a/internal/interface/main-handler/authentication/handler.go b/internal/interface/main-handler/authentication/handler.go index 709f33a3..fc36dc52 100644 --- a/internal/interface/main-handler/authentication/handler.go +++ b/internal/interface/main-handler/authentication/handler.go @@ -6,11 +6,13 @@ import ( d "github.com/karincake/dodol" rw "github.com/karincake/risoles" + sp "github.com/karincake/semprit" + sr "github.com/karincake/serabi" m "simrs-vx/internal/domain/main-entities/user" - s "simrs-vx/internal/use-case/main-use-case/authentication" - + mf "simrs-vx/internal/domain/main-entities/user-fes" pa "simrs-vx/internal/lib/auth" + s "simrs-vx/internal/use-case/main-use-case/authentication" ) func Login(w http.ResponseWriter, r *http.Request) { @@ -50,3 +52,25 @@ func GuardMW(next http.Handler) http.Handler { next.ServeHTTP(w, r.WithContext(ctx)) }) } + +func LoginFes(w http.ResponseWriter, r *http.Request) { + var input mf.LoginDto + err := sp.IOReaderJson(input, r.Body) + if err != nil { + rw.WriteJSON(w, http.StatusUnauthorized, d.II{"errors": err}, nil) + } + + input.AuthPartner_Code = r.Header.Get("X-AuthPartner-Code") + input.AuthPartner_SecretKey = r.Header.Get("X-AuthPartner-SecretKey") + if err = (sr.Validate(input)); err != nil { + rw.WriteJSON(w, http.StatusUnauthorized, d.II{"errors": err}, nil) + } + + // input.Position = Position + res, err := s.GenTokenFes(input) + if err != nil { + rw.WriteJSON(w, http.StatusUnauthorized, d.II{"errors": err}, nil) + } else { + rw.DataResponse(w, res, err) + } +} diff --git a/internal/use-case/main-use-case/authentication/case.go b/internal/use-case/main-use-case/authentication/case.go index bd9dcee6..061677d4 100644 --- a/internal/use-case/main-use-case/authentication/case.go +++ b/internal/use-case/main-use-case/authentication/case.go @@ -19,16 +19,9 @@ import ( pl "simrs-vx/pkg/logger" p "simrs-vx/pkg/password" - ed "simrs-vx/internal/domain/main-entities/doctor" - ee "simrs-vx/internal/domain/main-entities/employee" - "simrs-vx/internal/domain/main-entities/intern" - em "simrs-vx/internal/domain/main-entities/midwife" - en "simrs-vx/internal/domain/main-entities/nurse" - ep "simrs-vx/internal/domain/main-entities/pharmacist" eu "simrs-vx/internal/domain/main-entities/user" - + euf "simrs-vx/internal/domain/main-entities/user-fes" erc "simrs-vx/internal/domain/references/common" - erg "simrs-vx/internal/domain/references/organization" ) const source = "authentication" @@ -115,121 +108,150 @@ func GenToken(input eu.LoginDto) (*d.Data, error) { } // extra - role := []string{} - switch user.ContractPosition_Code { - case erg.CSCEmp: - // employee - employee := ee.Employee{} - dg.I.Where("\"User_Id\" = ?", user.Id).First(&employee) - if employee.Id == 0 { - return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noEmployee", Message: pl.GenMessage("auth-noEmployee")}} - } - atClaims["employee_id"] = employee.Id - outputData["employee_id"] = employee.Id - role = append(role, "emp-"+string(*employee.Position_Code)) + // role := []string{} + // switch user.ContractPosition_Code { + // case erg.CSCEmp: + // // employee + // employee := ee.Employee{} + // dg.I.Where("\"User_Id\" = ?", user.Id).First(&employee) + // if employee.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noEmployee", Message: pl.GenMessage("auth-noEmployee")}} + // } + // atClaims["employee_id"] = employee.Id + // outputData["employee_id"] = employee.Id + // role = append(role, "emp-"+string(*employee.Position_Code)) - //if employee.Division_Code != nil { - // atClaims["employee_division_code"] = employee.Division_Code - // outputData["employee_division_code"] = employee.Division_Code - //} + // //if employee.Division_Code != nil { + // // atClaims["employee_division_code"] = employee.Division_Code + // // outputData["employee_division_code"] = employee.Division_Code + // //} - // employee position - if employee.Id > 0 && employee.Position_Code != nil { - atClaims["employee_position_code"] = *employee.Position_Code - switch *employee.Position_Code { - case erg.EPCDoc: - doctor := ed.Doctor{} - dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&doctor) - if doctor.Id == 0 { - return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noDoctor", Message: pl.GenMessage("auth-noDoctor")}} - } - atClaims["doctor_code"] = doctor.Code - outputData["doctor_code"] = doctor.Code + // // employee position + // if employee.Id > 0 && employee.Position_Code != nil { + // atClaims["employee_position_code"] = *employee.Position_Code + // switch *employee.Position_Code { + // case erg.EPCDoc: + // doctor := ed.Doctor{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&doctor) + // if doctor.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noDoctor", Message: pl.GenMessage("auth-noDoctor")}} + // } + // atClaims["doctor_code"] = doctor.Code + // outputData["doctor_code"] = doctor.Code - // specialist - if doctor.Specialist_Code != nil { - atClaims["specialist_code"] = doctor.Specialist_Code - outputData["specialist_code"] = doctor.Specialist_Code - } - if doctor.Subspecialist_Code != nil { - atClaims["subspecialist_code"] = doctor.Subspecialist_Code - outputData["subspecialist_code"] = doctor.Subspecialist_Code - } - case erg.EPCNur: - empData := en.Nurse{} - dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) - if empData.Id == 0 { - return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noNurse", Message: pl.GenMessage("auth-noNurse")}} - } - atClaims["nurse_code"] = empData.Code - outputData["nurse_code"] = empData.Code - case erg.EPCMwi: - empData := em.Midwife{} - dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) - if empData.Id == 0 { - return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noMidwife", Message: pl.GenMessage("auth-noMidwife")}} - } - atClaims["midwife_code"] = empData.Code - outputData["midwife_code"] = empData.Code - case erg.EPCPha: - empData := ep.Pharmacist{} - dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) - if empData.Id == 0 { - return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noPharmacist", Message: pl.GenMessage("auth-noPharmacist")}} - } - atClaims["pharmacist_code"] = empData.Code - outputData["pharmacist_code"] = empData.Code - } + // // specialist + // if doctor.Specialist_Code != nil { + // atClaims["specialist_code"] = doctor.Specialist_Code + // outputData["specialist_code"] = doctor.Specialist_Code + // } + // if doctor.Subspecialist_Code != nil { + // atClaims["subspecialist_code"] = doctor.Subspecialist_Code + // outputData["subspecialist_code"] = doctor.Subspecialist_Code + // } + // case erg.EPCNur: + // empData := en.Nurse{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + // if empData.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noNurse", Message: pl.GenMessage("auth-noNurse")}} + // } + // atClaims["nurse_code"] = empData.Code + // outputData["nurse_code"] = empData.Code + // case erg.EPCMwi: + // empData := em.Midwife{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + // if empData.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noMidwife", Message: pl.GenMessage("auth-noMidwife")}} + // } + // atClaims["midwife_code"] = empData.Code + // outputData["midwife_code"] = empData.Code + // case erg.EPCPha: + // empData := ep.Pharmacist{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + // if empData.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noPharmacist", Message: pl.GenMessage("auth-noPharmacist")}} + // } + // atClaims["pharmacist_code"] = empData.Code + // outputData["pharmacist_code"] = empData.Code + // } + // // specialist + // if doctor.Specialist_Code != nil { + // atClaims["specialist_code"] = doctor.Specialist_Code + // outputData["specialist_code"] = doctor.Specialist_Code + // } + // if doctor.Subspecialist_Code != nil { + // atClaims["subspecialist_code"] = doctor.Subspecialist_Code + // outputData["subspecialist_code"] = doctor.Subspecialist_Code + // } + // case erg.EPCNur: + // empData := en.Nurse{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + // if empData.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noNurse", Message: pl.GenMessage("auth-noNurse")}} + // } + // atClaims["nurse_code"] = empData.Code + // outputData["nurse_code"] = empData.Code + // case erg.EPCMwi: + // empData := em.Midwife{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + // if empData.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noMidwife", Message: pl.GenMessage("auth-noMidwife")}} + // } + // atClaims["midwife_code"] = empData.Code + // outputData["midwife_code"] = empData.Code + // } - errorGetPosition := d.FieldErrors{"authentication": d.FieldError{Code: "auth-getData-failed", Message: pl.GenMessage("auth-getData-failed")}} + // errorGetPosition := d.FieldErrors{"authentication": d.FieldError{Code: "auth-getData-failed", Message: pl.GenMessage("auth-getData-failed")}} - // division position - divisionPositions, err := getDivisionPosition(employee.Id, &event) - if err != nil { - return nil, errorGetPosition - } + // // division position + // divisionPositions, err := getDivisionPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } - // installation position - installationPositions, err := getInstallationPosition(employee.Id, &event) - if err != nil { - return nil, errorGetPosition - } + // // installation position + // installationPositions, err := getInstallationPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } - // unit position - unitPositions, err := getUnitPosition(employee.Id, &event) - if err != nil { - return nil, errorGetPosition - } + // // unit position + // unitPositions, err := getUnitPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } - // specialist position - specialistPositions, err := getSpecialistPosition(employee.Id, &event) - if err != nil { - return nil, errorGetPosition - } + // // specialist position + // specialistPositions, err := getSpecialistPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } - // subspecialist position - subspecialistPositions, err := getSubspecialistPosition(employee.Id, &event) - if err != nil { - return nil, errorGetPosition - } + // // subspecialist position + // subspecialistPositions, err := getSubspecialistPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } - role = append(role, divisionPositions...) - role = append(role, installationPositions...) - role = append(role, unitPositions...) - role = append(role, specialistPositions...) - role = append(role, subspecialistPositions...) - // atClaims["division_positions"] = divsionPositions - // outputData["division_positions"] = divsionPositions - } - case erg.CSCInt: - intern := intern.Intern{} - dg.I.Where("\"User_Id\" = ?", user.Id).First(&intern) - role = append(role, "int-"+string(*intern.Position_Code)) - case erg.CSCSys: - role = append(role, "system") + // role = append(role, divisionPositions...) + // role = append(role, installationPositions...) + // role = append(role, unitPositions...) + // role = append(role, specialistPositions...) + // role = append(role, subspecialistPositions...) + // // atClaims["division_positions"] = divsionPositions + // // outputData["division_positions"] = divsionPositions + // } + // case erg.CSCInt: + // intern := intern.Intern{} + // dg.I.Where("\"User_Id\" = ?", user.Id).First(&intern) + // role = append(role, "int-"+string(*intern.Position_Code)) + // case erg.CSCSys: + // role = append(role, "system") + // } + // atClaims["roles"] = role + // outputData["roles"] = role + if err := populateRoles(user, atClaims, outputData, event); err != nil { + return nil, err } - atClaims["roles"] = role - outputData["roles"] = role // Generate jwt at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) @@ -339,3 +361,89 @@ func ExtractToken(r *http.Request, tokenType TokenType) (data *pa.AuthInfo, err func GetConfig() { a.ParseCfg(&authCfg) } + +func GenTokenFes(input euf.LoginDto) (*d.Data, error) { + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Get User Fes + userFes := &euf.UserFes{Name: input.Name, AuthPartner_Code: input.AuthPartner_Code} + if errCode := getAndCheck(userFes, userFes); errCode != "" { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} + } + if userFes.AuthPartner.SecretKey != input.AuthPartner_SecretKey { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-secretKey-invalid", Message: pl.GenMessage("auth-secretKey-invalid")}} + } + + user := &eu.User{Name: userFes.User_Name} + if errCode := getAndCheck(user, user); errCode != "" { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} + } + + // Access token prep + id, err := uuid.NewRandom() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("uuid-gen-fail"), err)) + } + if input.Duration == 0 { + input.Duration = 24 * 60 + } + duration := time.Minute * time.Duration(input.Duration) + aUuid := id.String() + atExpires := time.Now().Add(duration).Unix() + atSecretKey := authCfg.AtSecretKey + + // Create Claim + atClaims := jwt.MapClaims{} + atClaims["user_id"] = user.Id + atClaims["user_name"] = user.Name + atClaims["user_contractPosition_code"] = user.ContractPosition_Code + atClaims["uuid"] = aUuid + atClaims["exp"] = atExpires + + // Create output + outputData := d.II{ + "user_id": strconv.Itoa(int(user.Id)), + "user_name": user.Name, + "user_contractPosition_code": user.ContractPosition_Code, + } + + // extra + if err := populateRoles(user, atClaims, outputData, event); err != nil { + return nil, err + } + + // Generate jwt + at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) + ats, err := at.SignedString([]byte(atSecretKey)) + if err != nil { + return nil, d.FieldErrors{"user": d.FieldError{Code: "token-sign-err", Message: pl.GenMessage("token-sign-err")}} + } + outputData["accessToken"] = ats + + // Save to redis + now := time.Now() + atx := time.Unix(atExpires, 0) //converting Unix to UTC(to Time object) + err = ms.I.Set(aUuid, strconv.Itoa(int(user.Id)), atx.Sub(now)).Err() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("redis-store-fail"), err.Error())) + } + + tn := time.Now() + user.LoginAttemptCount = 0 + user.LastSuccessLogin = &tn + user.LastAllowdLogin = &tn + dg.I.Save(&user) + + // Current data + return &d.Data{ + Meta: d.IS{ + "source": "authentication", + "structure": "single-data", + "status": "verified", + }, + Data: outputData, + }, nil +} diff --git a/internal/use-case/main-use-case/authentication/helper.go b/internal/use-case/main-use-case/authentication/helper.go index a919cfe9..0da9f943 100644 --- a/internal/use-case/main-use-case/authentication/helper.go +++ b/internal/use-case/main-use-case/authentication/helper.go @@ -3,13 +3,23 @@ package authentication import ( dg "github.com/karincake/apem/db-gorm-pg" + "github.com/golang-jwt/jwt" + d "github.com/karincake/dodol" + pl "simrs-vx/pkg/logger" edp "simrs-vx/internal/domain/main-entities/division-position" + ed "simrs-vx/internal/domain/main-entities/doctor" + ee "simrs-vx/internal/domain/main-entities/employee" eip "simrs-vx/internal/domain/main-entities/installation-position" + "simrs-vx/internal/domain/main-entities/intern" + em "simrs-vx/internal/domain/main-entities/midwife" + en "simrs-vx/internal/domain/main-entities/nurse" esp "simrs-vx/internal/domain/main-entities/specialist-position" essp "simrs-vx/internal/domain/main-entities/subspecialist-position" eup "simrs-vx/internal/domain/main-entities/unit-position" + eu "simrs-vx/internal/domain/main-entities/user" + erg "simrs-vx/internal/domain/references/organization" udp "simrs-vx/internal/use-case/main-use-case/division-position" uip "simrs-vx/internal/use-case/main-use-case/installation-position" @@ -162,3 +172,115 @@ func checkUntPtrClaims(claim map[string]interface{}, key string) *uint { } return nil } + +func populateRoles(user *eu.User, atClaims jwt.MapClaims, outputData d.II, event pl.Event) error { + roles := []string{} + switch user.ContractPosition_Code { + case erg.CSCEmp: + // employee + employee := ee.Employee{} + dg.I.Where("\"User_Id\" = ?", user.Id).First(&employee) + if employee.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noEmployee", Message: pl.GenMessage("auth-noEmployee")}} + } + atClaims["employee_id"] = employee.Id + outputData["employee_id"] = employee.Id + roles = append(roles, "emp-"+string(*employee.Position_Code)) + + //if employee.Division_Code != nil { + // atClaims["employee_division_code"] = employee.Division_Code + // outputData["employee_division_code"] = employee.Division_Code + //} + + // employee position + if employee.Id > 0 && employee.Position_Code != nil { + atClaims["employee_position_code"] = *employee.Position_Code + switch *employee.Position_Code { + case erg.EPCDoc: + doctor := ed.Doctor{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&doctor) + if doctor.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noDoctor", Message: pl.GenMessage("auth-noDoctor")}} + } + atClaims["doctor_code"] = doctor.Code + outputData["doctor_code"] = doctor.Code + + // specialist + if doctor.Specialist_Code != nil { + atClaims["specialist_code"] = doctor.Specialist_Code + outputData["specialist_code"] = doctor.Specialist_Code + } + if doctor.Subspecialist_Code != nil { + atClaims["subspecialist_code"] = doctor.Subspecialist_Code + outputData["subspecialist_code"] = doctor.Subspecialist_Code + } + case erg.EPCNur: + empData := en.Nurse{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + if empData.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noNurse", Message: pl.GenMessage("auth-noNurse")}} + } + atClaims["nurse_code"] = empData.Code + outputData["nurse_code"] = empData.Code + case erg.EPCMwi: + empData := em.Midwife{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + if empData.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noMidwife", Message: pl.GenMessage("auth-noMidwife")}} + } + atClaims["midwife_code"] = empData.Code + outputData["midwife_code"] = empData.Code + } + + errorGetPosition := d.FieldErrors{"authentication": d.FieldError{Code: "auth-getData-failed", Message: pl.GenMessage("auth-getData-failed")}} + + // division position + divisionPositions, err := getDivisionPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + // installation position + installationPositions, err := getInstallationPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + // unit position + unitPositions, err := getUnitPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + // specialist position + specialistPositions, err := getSpecialistPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + // subspecialist position + subspecialistPositions, err := getSubspecialistPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + roles = append(roles, divisionPositions...) + roles = append(roles, installationPositions...) + roles = append(roles, unitPositions...) + roles = append(roles, specialistPositions...) + roles = append(roles, subspecialistPositions...) + // atClaims["division_positions"] = divsionPositions + // outputData["division_positions"] = divsionPositions + } + case erg.CSCInt: + intern := intern.Intern{} + dg.I.Where("\"User_Id\" = ?", user.Id).First(&intern) + roles = append(roles, "int-"+string(*intern.Position_Code)) + case erg.CSCSys: + roles = append(roles, "system") + } + + atClaims["roles"] = roles + outputData["roles"] = roles + return nil +} From 5aa45088f401ad4164797586ca40a81f3ab2ac82 Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Mon, 10 Nov 2025 22:53:32 +0700 Subject: [PATCH 06/18] feat/sso-auth: added the use cases --- internal/domain/main-entities/user-fes/dto.go | 11 +- internal/lib/auth/tycovar.go | 2 +- .../main-use-case/auth-partner/case.go | 276 +++++++++++ .../main-use-case/auth-partner/helper.go | 19 + .../main-use-case/auth-partner/lib.go | 150 ++++++ .../auth-partner/middleware-runner.go | 103 ++++ .../main-use-case/auth-partner/middleware.go | 9 + .../main-use-case/auth-partner/tycovar.go | 44 ++ .../main-use-case/authentication/case.go | 332 ++----------- .../main-use-case/authentication/case.go.bu | 463 ++++++++++++++++++ .../main-use-case/authentication/helper.go | 116 +++-- .../main-use-case/authentication/helper.go.bu | 323 ++++++++++++ .../use-case/main-use-case/user-fes/case.go | 276 +++++++++++ .../use-case/main-use-case/user-fes/helper.go | 18 + .../use-case/main-use-case/user-fes/lib.go | 143 ++++++ .../user-fes/middleware-runner.go | 103 ++++ .../main-use-case/user-fes/middleware.go | 5 + .../main-use-case/user-fes/tycovar.go | 44 ++ 18 files changed, 2121 insertions(+), 316 deletions(-) create mode 100644 internal/use-case/main-use-case/auth-partner/case.go create mode 100644 internal/use-case/main-use-case/auth-partner/helper.go create mode 100644 internal/use-case/main-use-case/auth-partner/lib.go create mode 100644 internal/use-case/main-use-case/auth-partner/middleware-runner.go create mode 100644 internal/use-case/main-use-case/auth-partner/middleware.go create mode 100644 internal/use-case/main-use-case/auth-partner/tycovar.go create mode 100644 internal/use-case/main-use-case/authentication/case.go.bu create mode 100644 internal/use-case/main-use-case/authentication/helper.go.bu create mode 100644 internal/use-case/main-use-case/user-fes/case.go create mode 100644 internal/use-case/main-use-case/user-fes/helper.go create mode 100644 internal/use-case/main-use-case/user-fes/lib.go create mode 100644 internal/use-case/main-use-case/user-fes/middleware-runner.go create mode 100644 internal/use-case/main-use-case/user-fes/middleware.go create mode 100644 internal/use-case/main-use-case/user-fes/tycovar.go diff --git a/internal/domain/main-entities/user-fes/dto.go b/internal/domain/main-entities/user-fes/dto.go index 6cb3636b..3cf9d68e 100644 --- a/internal/domain/main-entities/user-fes/dto.go +++ b/internal/domain/main-entities/user-fes/dto.go @@ -41,6 +41,13 @@ type DeleteDto struct { Id uint `json:"id"` } +type LoginDto struct { + Name string `json:"name" validate:"required"` + Duration uint32 `json:"duration"` + AuthPartner_Code string `json:"X-AuthPartner-Code" validate:"required"` + AuthPartner_SecretKey string `json:"X-AuthPartner-SecretKey" validate:"required"` +} + type MetaDto struct { PageNumber int `json:"page_number"` PageSize int `json:"page_size"` @@ -59,8 +66,8 @@ func (d UserFes) ToResponse() ResponseDto { resp := ResponseDto{ Name: d.Name, AuthPartner_Code: d.AuthPartner_Code, - AuthPartner: d.AuthPartner, - User_Name: d.User_Name, + // AuthPartner: d.AuthPartner, + User_Name: d.User_Name, } resp.Main = d.Main return resp diff --git a/internal/lib/auth/tycovar.go b/internal/lib/auth/tycovar.go index 467bd1b6..3567cd61 100644 --- a/internal/lib/auth/tycovar.go +++ b/internal/lib/auth/tycovar.go @@ -11,7 +11,7 @@ type AuthInfo struct { Uuid string User_Id uint User_Name string - User_ContractPosition_code string + User_ContractPosition_Code string Employee_Position_Code *string Employee_Id *uint Doctor_Code *string diff --git a/internal/use-case/main-use-case/auth-partner/case.go b/internal/use-case/main-use-case/auth-partner/case.go new file mode 100644 index 00000000..f5f7b423 --- /dev/null +++ b/internal/use-case/main-use-case/auth-partner/case.go @@ -0,0 +1,276 @@ +package authpartner + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/auth-partner" + "strconv" + + dg "github.com/karincake/apem/db-gorm-pg" + d "github.com/karincake/dodol" + + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" +) + +const source = "device" + +func Create(input e.CreateDto) (*d.Data, error) { + data := e.AuthPartner{} + + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "create") + + err := dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunCreateMiddleware(createPreMw, &input, &data); err != nil { + return err + } + + if resData, err := CreateData(input, &event, tx); err != nil { + return err + } else { + data = *resData + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunCreateMiddleware(createPostMw, &input, &data); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.II{ + "source": source, + "structure": "single-data", + "status": "created", + }, + Data: data.ToResponse(), + }, nil +} + +func ReadList(input e.ReadListDto) (*d.Data, error) { + var data *e.AuthPartner + var dataList []e.AuthPartner + var metaList *e.MetaDto + var err error + + event := pl.Event{ + Feature: "ReadList", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "readList") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunReadListMiddleware(readListPreMw, &input, data); err != nil { + return err + } + + if dataList, metaList, err = ReadListData(input, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunReadListMiddleware(readListPostMw, &input, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "list-data", + "status": "fetched", + "page_number": strconv.Itoa(metaList.PageNumber), + "page_size": strconv.Itoa(metaList.PageSize), + "record_totalCount": strconv.Itoa(metaList.Count), + "record_currentCount": strconv.Itoa(len(dataList)), + }, + Data: e.ToResponseList(dataList), + }, nil +} + +func ReadDetail(input e.ReadDetailDto) (*d.Data, error) { + var data *e.AuthPartner + var err error + + event := pl.Event{ + Feature: "ReadDetail", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "readDetail") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunReadDetailMiddleware(readDetailPreMw, &input, data); err != nil { + return err + } + + if data, err = ReadDetailData(input, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunReadDetailMiddleware(readDetailPostMw, &input, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "fetched", + }, + Data: data.ToResponse(), + }, nil +} + +func Update(input e.UpdateDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Id: &input.Id} + var data *e.AuthPartner + var err error + + event := pl.Event{ + Feature: "Update", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "update") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") + if data, err = ReadDetailData(rdDto, &event, tx); err != nil { + return err + } + + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunUpdateMiddleware(readDetailPreMw, &rdDto, data); err != nil { + return err + } + + if err := UpdateData(input, data, &event, tx); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunUpdateMiddleware(readDetailPostMw, &rdDto, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "updated", + }, + Data: data.ToResponse(), + }, nil + +} + +func Delete(input e.DeleteDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Id: &input.Id} + var data *e.AuthPartner + var err error + + event := pl.Event{ + Feature: "Delete", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "delete") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") + if data, err = ReadDetailData(rdDto, &event, tx); err != nil { + return err + } + + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunDeleteMiddleware(readDetailPreMw, &rdDto, data); err != nil { + return err + } + + if err := DeleteData(data, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunDeleteMiddleware(readDetailPostMw, &rdDto, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "deleted", + }, + Data: data.ToResponse(), + }, nil + +} diff --git a/internal/use-case/main-use-case/auth-partner/helper.go b/internal/use-case/main-use-case/auth-partner/helper.go new file mode 100644 index 00000000..36e1ce7b --- /dev/null +++ b/internal/use-case/main-use-case/auth-partner/helper.go @@ -0,0 +1,19 @@ +package authpartner + +import ( + e "simrs-vx/internal/domain/main-entities/auth-partner" +) + +func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.AuthPartner) { + var inputSrc *e.CreateDto + if inputT, ok := any(input).(*e.CreateDto); ok { + inputSrc = inputT + } else { + inputTemp := any(input).(*e.UpdateDto) + inputSrc = &inputTemp.CreateDto + } + + data.Code = inputSrc.Code + data.Name = inputSrc.Name + data.SecretKey = inputSrc.SecretKey +} diff --git a/internal/use-case/main-use-case/auth-partner/lib.go b/internal/use-case/main-use-case/auth-partner/lib.go new file mode 100644 index 00000000..41253e2c --- /dev/null +++ b/internal/use-case/main-use-case/auth-partner/lib.go @@ -0,0 +1,150 @@ +package authpartner + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/auth-partner" + + plh "simrs-vx/pkg/lib-helper" + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + dg "github.com/karincake/apem/db-gorm-pg" + gh "github.com/karincake/getuk" +) + +func CreateData(input e.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*e.AuthPartner, error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + data := e.AuthPartner{} + setData(&input, &data) + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.Create(&data).Error; err != nil { + return nil, plh.HandleCreateError(input, event, err) + } + + pl.SetLogInfo(event, nil, "complete") + return &data, nil +} + +func ReadListData(input e.ReadListDto, event *pl.Event, dbx ...*gorm.DB) ([]e.AuthPartner, *e.MetaDto, error) { + pl.SetLogInfo(event, input, "started", "DBReadList") + data := []e.AuthPartner{} + pagination := gh.Pagination{} + count := int64(0) + meta := e.MetaDto{} + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + tx = tx. + Model(&e.AuthPartner{}). + Scopes(gh.Preload(input.Includes)). + Scopes(gh.Filter(input.FilterDto)). + Count(&count). + Scopes(gh.Paginate(input, &pagination)). + Scopes(gh.Sort(input.Sort)) + + if err := tx.Find(&data).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, &meta, nil + } + return nil, nil, plh.HandleListError(input, event, err) + } + + meta.Count = int(count) + meta.PageNumber = pagination.PageNumber + meta.PageSize = pagination.PageSize + + pl.SetLogInfo(event, nil, "complete") + return data, &meta, nil +} + +func ReadDetailData(input e.ReadDetailDto, event *pl.Event, dbx ...*gorm.DB) (*e.AuthPartner, error) { + pl.SetLogInfo(event, input, "started", "DBReadDetail") + data := e.AuthPartner{} + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if input.Code != nil { + tx = tx.Where("\"Code\" = ?", *input.Code) + } + if input.Id != nil { + tx = tx.Where("\"Id\" = ?", input.Id) + } + + if err := tx. + Scopes(gh.Preload(input.Includes)). + First(&data).Error; err != nil { + if processedErr := pu.HandleReadError(err, event, source, input.Id, data); processedErr != nil { + return nil, processedErr + } + } + + pl.SetLogInfo(event, nil, "complete") + return &data, nil +} + +func UpdateData(input e.UpdateDto, data *e.AuthPartner, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, data, "started", "DBUpdate") + setData(&input, data) + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.Save(&data).Error; err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-update-fail", + Detail: "Database update failed", + Raw: err, + } + return pl.SetLogError(event, input) + } + + pl.SetLogInfo(event, nil, "complete") + return nil +} + +func DeleteData(data *e.AuthPartner, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, data, "started", "DBDelete") + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.Delete(&data).Error; err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-delete-fail", + Detail: "Database delete failed", + Raw: err, + } + return pl.SetLogError(event, data) + } + + pl.SetLogInfo(event, nil, "complete") + return nil +} diff --git a/internal/use-case/main-use-case/auth-partner/middleware-runner.go b/internal/use-case/main-use-case/auth-partner/middleware-runner.go new file mode 100644 index 00000000..d1cabe38 --- /dev/null +++ b/internal/use-case/main-use-case/auth-partner/middleware-runner.go @@ -0,0 +1,103 @@ +package authpartner + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/auth-partner" + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" +) + +type middlewareRunner struct { + Event *pl.Event + Tx *gorm.DB + MwType pu.MWType +} + +// NewMiddlewareExecutor creates a new middleware executor +func newMiddlewareRunner(event *pl.Event, tx *gorm.DB) *middlewareRunner { + return &middlewareRunner{ + Event: event, + Tx: tx, + } +} + +// ExecuteCreateMiddleware executes create middleware +func (me *middlewareRunner) RunCreateMiddleware(middlewares []createMw, input *e.CreateDto, data *e.AuthPartner) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunReadListMiddleware(middlewares []readListMw, input *e.ReadListDto, data *e.AuthPartner) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunReadDetailMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.AuthPartner) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunUpdateMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.AuthPartner) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunDeleteMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.AuthPartner) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) setMwType(mwType pu.MWType) { + me.MwType = mwType +} diff --git a/internal/use-case/main-use-case/auth-partner/middleware.go b/internal/use-case/main-use-case/auth-partner/middleware.go new file mode 100644 index 00000000..4ec1231f --- /dev/null +++ b/internal/use-case/main-use-case/auth-partner/middleware.go @@ -0,0 +1,9 @@ +package authpartner + +// example of middleware +// func init() { +// createPreMw = append(createPreMw, +// CreateMw{Name: "modif-input", Func: pm.ModifInput}, +// CreateMw{Name: "check-data", Func: pm.CheckData}, +// ) +// } diff --git a/internal/use-case/main-use-case/auth-partner/tycovar.go b/internal/use-case/main-use-case/auth-partner/tycovar.go new file mode 100644 index 00000000..43f69d98 --- /dev/null +++ b/internal/use-case/main-use-case/auth-partner/tycovar.go @@ -0,0 +1,44 @@ +/* +DESCRIPTION: +A sample, part of the package that contains type, constants, and/or variables. + +In this sample it also provides type and variable regarding the needs of the +middleware to separate from main use-case which has the basic CRUD +functionality. The purpose of this is to make the code more maintainable. +*/ +package authpartner + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/auth-partner" +) + +type createMw struct { + Name string + Func func(input *e.CreateDto, data *e.AuthPartner, tx *gorm.DB) error +} + +type readListMw struct { + Name string + Func func(input *e.ReadListDto, data *e.AuthPartner, tx *gorm.DB) error +} + +type readDetailMw struct { + Name string + Func func(input *e.ReadDetailDto, data *e.AuthPartner, tx *gorm.DB) error +} + +type UpdateMw = readDetailMw +type DeleteMw = readDetailMw + +var createPreMw []createMw // preprocess middleware +var createPostMw []createMw // postprocess middleware +var readListPreMw []readListMw // .. +var readListPostMw []readListMw // .. +var readDetailPreMw []readDetailMw +var readDetailPostMw []readDetailMw +var updatePreMw []readDetailMw +var updatePostMw []readDetailMw +var deletePreMw []readDetailMw +var deletePostMw []readDetailMw diff --git a/internal/use-case/main-use-case/authentication/case.go b/internal/use-case/main-use-case/authentication/case.go index 061677d4..db83d315 100644 --- a/internal/use-case/main-use-case/authentication/case.go +++ b/internal/use-case/main-use-case/authentication/case.go @@ -8,7 +8,6 @@ import ( "time" "github.com/golang-jwt/jwt" - "github.com/google/uuid" a "github.com/karincake/apem" dg "github.com/karincake/apem/db-gorm-pg" ms "github.com/karincake/apem/ms-redis" @@ -19,6 +18,7 @@ import ( pl "simrs-vx/pkg/logger" p "simrs-vx/pkg/password" + eap "simrs-vx/internal/domain/main-entities/auth-partner" eu "simrs-vx/internal/domain/main-entities/user" euf "simrs-vx/internal/domain/main-entities/user-fes" erc "simrs-vx/internal/domain/references/common" @@ -42,13 +42,11 @@ func GenToken(input eu.LoginDto) (*d.Data, error) { // Get User user := &eu.User{Name: input.Name} - // if input.Position_Code != "" { - // user.Position_Code = input.Position_Code - // } - if errCode := getAndCheck(user, user); errCode != "" { + if errCode := getAndCheck(user, user, nil); errCode != "" { return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} } + // Check login attempt if user.LoginAttemptCount > 5 { if user.LastSuccessLogin != nil { now := time.Now() @@ -69,6 +67,7 @@ func GenToken(input eu.LoginDto) (*d.Data, error) { } } + // Check password if !p.Check(input.Password, user.Password) { user.LoginAttemptCount++ dg.I.Save(&user) @@ -79,196 +78,14 @@ func GenToken(input eu.LoginDto) (*d.Data, error) { return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-login-unverified", Message: pl.GenMessage("auth-login-unverified")}} } - // Access token prep - id, err := uuid.NewRandom() - if err != nil { - panic(fmt.Sprintf(l.I.Msg("uuid-gen-fail"), err)) - } - if input.Duration == 0 { - input.Duration = 24 * 60 - } - duration := time.Minute * time.Duration(input.Duration) - aUuid := id.String() - atExpires := time.Now().Add(duration).Unix() - atSecretKey := authCfg.AtSecretKey - - // Create Claim + // Data and output population atClaims := jwt.MapClaims{} - atClaims["user_id"] = user.Id - atClaims["user_name"] = user.Name - atClaims["user_contractPosition_code"] = user.ContractPosition_Code - atClaims["uuid"] = aUuid - atClaims["exp"] = atExpires - - // Create output - outputData := d.II{ - "user_id": strconv.Itoa(int(user.Id)), - "user_name": user.Name, - "user_contractPosition_code": user.ContractPosition_Code, - } - - // extra - // role := []string{} - // switch user.ContractPosition_Code { - // case erg.CSCEmp: - // // employee - // employee := ee.Employee{} - // dg.I.Where("\"User_Id\" = ?", user.Id).First(&employee) - // if employee.Id == 0 { - // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noEmployee", Message: pl.GenMessage("auth-noEmployee")}} - // } - // atClaims["employee_id"] = employee.Id - // outputData["employee_id"] = employee.Id - // role = append(role, "emp-"+string(*employee.Position_Code)) - - // //if employee.Division_Code != nil { - // // atClaims["employee_division_code"] = employee.Division_Code - // // outputData["employee_division_code"] = employee.Division_Code - // //} - - // // employee position - // if employee.Id > 0 && employee.Position_Code != nil { - // atClaims["employee_position_code"] = *employee.Position_Code - // switch *employee.Position_Code { - // case erg.EPCDoc: - // doctor := ed.Doctor{} - // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&doctor) - // if doctor.Id == 0 { - // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noDoctor", Message: pl.GenMessage("auth-noDoctor")}} - // } - // atClaims["doctor_code"] = doctor.Code - // outputData["doctor_code"] = doctor.Code - - // // specialist - // if doctor.Specialist_Code != nil { - // atClaims["specialist_code"] = doctor.Specialist_Code - // outputData["specialist_code"] = doctor.Specialist_Code - // } - // if doctor.Subspecialist_Code != nil { - // atClaims["subspecialist_code"] = doctor.Subspecialist_Code - // outputData["subspecialist_code"] = doctor.Subspecialist_Code - // } - // case erg.EPCNur: - // empData := en.Nurse{} - // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) - // if empData.Id == 0 { - // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noNurse", Message: pl.GenMessage("auth-noNurse")}} - // } - // atClaims["nurse_code"] = empData.Code - // outputData["nurse_code"] = empData.Code - // case erg.EPCMwi: - // empData := em.Midwife{} - // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) - // if empData.Id == 0 { - // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noMidwife", Message: pl.GenMessage("auth-noMidwife")}} - // } - // atClaims["midwife_code"] = empData.Code - // outputData["midwife_code"] = empData.Code - // case erg.EPCPha: - // empData := ep.Pharmacist{} - // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) - // if empData.Id == 0 { - // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noPharmacist", Message: pl.GenMessage("auth-noPharmacist")}} - // } - // atClaims["pharmacist_code"] = empData.Code - // outputData["pharmacist_code"] = empData.Code - // } - // // specialist - // if doctor.Specialist_Code != nil { - // atClaims["specialist_code"] = doctor.Specialist_Code - // outputData["specialist_code"] = doctor.Specialist_Code - // } - // if doctor.Subspecialist_Code != nil { - // atClaims["subspecialist_code"] = doctor.Subspecialist_Code - // outputData["subspecialist_code"] = doctor.Subspecialist_Code - // } - // case erg.EPCNur: - // empData := en.Nurse{} - // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) - // if empData.Id == 0 { - // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noNurse", Message: pl.GenMessage("auth-noNurse")}} - // } - // atClaims["nurse_code"] = empData.Code - // outputData["nurse_code"] = empData.Code - // case erg.EPCMwi: - // empData := em.Midwife{} - // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) - // if empData.Id == 0 { - // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noMidwife", Message: pl.GenMessage("auth-noMidwife")}} - // } - // atClaims["midwife_code"] = empData.Code - // outputData["midwife_code"] = empData.Code - // } - - // errorGetPosition := d.FieldErrors{"authentication": d.FieldError{Code: "auth-getData-failed", Message: pl.GenMessage("auth-getData-failed")}} - - // // division position - // divisionPositions, err := getDivisionPosition(employee.Id, &event) - // if err != nil { - // return nil, errorGetPosition - // } - - // // installation position - // installationPositions, err := getInstallationPosition(employee.Id, &event) - // if err != nil { - // return nil, errorGetPosition - // } - - // // unit position - // unitPositions, err := getUnitPosition(employee.Id, &event) - // if err != nil { - // return nil, errorGetPosition - // } - - // // specialist position - // specialistPositions, err := getSpecialistPosition(employee.Id, &event) - // if err != nil { - // return nil, errorGetPosition - // } - - // // subspecialist position - // subspecialistPositions, err := getSubspecialistPosition(employee.Id, &event) - // if err != nil { - // return nil, errorGetPosition - // } - - // role = append(role, divisionPositions...) - // role = append(role, installationPositions...) - // role = append(role, unitPositions...) - // role = append(role, specialistPositions...) - // role = append(role, subspecialistPositions...) - // // atClaims["division_positions"] = divsionPositions - // // outputData["division_positions"] = divsionPositions - // } - // case erg.CSCInt: - // intern := intern.Intern{} - // dg.I.Where("\"User_Id\" = ?", user.Id).First(&intern) - // role = append(role, "int-"+string(*intern.Position_Code)) - // case erg.CSCSys: - // role = append(role, "system") - // } - // atClaims["roles"] = role - // outputData["roles"] = role - if err := populateRoles(user, atClaims, outputData, event); err != nil { + outputData := d.II{} + if err := populateRoles(user, input, atClaims, outputData, event); err != nil { return nil, err } - // Generate jwt - at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) - ats, err := at.SignedString([]byte(atSecretKey)) - if err != nil { - return nil, d.FieldErrors{"user": d.FieldError{Code: "token-sign-err", Message: pl.GenMessage("token-sign-err")}} - } - outputData["accessToken"] = ats - - // Save to redis - now := time.Now() - atx := time.Unix(atExpires, 0) //converting Unix to UTC(to Time object) - err = ms.I.Set(aUuid, strconv.Itoa(int(user.Id)), atx.Sub(now)).Err() - if err != nil { - panic(fmt.Sprintf(l.I.Msg("redis-store-fail"), err.Error())) - } - + // Only manual login tn := time.Now() user.LoginAttemptCount = 0 user.LastSuccessLogin = &tn @@ -286,6 +103,51 @@ func GenToken(input eu.LoginDto) (*d.Data, error) { }, nil } +func GenTokenFes(input euf.LoginDto) (*d.Data, error) { + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Get User Fes + userFes := &euf.UserFes{Name: input.Name, AuthPartner_Code: input.AuthPartner_Code} + if errCode := getAndCheck(userFes, userFes, "AuthPartner"); errCode != "" { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} + } + // Preload above fails and I am so done, do it manually + authPartner := eap.AuthPartner{} + if err := dg.I.Where("\"Code\" = ?", userFes.AuthPartner_Code).First(&authPartner).Error; err != nil { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-secretKey-invalid", Message: pl.GenMessage("auth-secretKey-invalid")}} + } + if authPartner.SecretKey != input.AuthPartner_SecretKey { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-secretKey-invalid", Message: pl.GenMessage("auth-secretKey-invalid")}} + } + + // Get User + user := &eu.User{Name: userFes.User_Name} + if errCode := getAndCheck(user, user, nil); errCode != "" { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} + } + + // Data and output population + atClaims := jwt.MapClaims{} + outputData := d.II{} + primInput := eu.LoginDto{Duration: input.Duration} + if err := populateRoles(user, primInput, atClaims, outputData, event); err != nil { + return nil, err + } + + // Current data + return &d.Data{ + Meta: d.IS{ + "source": "authentication", + "structure": "single-data", + "status": "verified", + }, + Data: outputData, + }, nil +} + func RevokeToken(uuid string) { ms.I.Del(uuid) } @@ -343,7 +205,7 @@ func ExtractToken(r *http.Request, tokenType TokenType) (data *pa.AuthInfo, err User_Name: fmt.Sprintf("%v", claims["user_name"]), } - data.User_ContractPosition_code = checkStrClaims(claims, "contractPosition_code") + data.User_ContractPosition_Code = checkStrClaims(claims, "contractPosition_code") data.Employee_Position_Code = checkStrPtrClaims(claims, "employee_position_code") data.Doctor_Code = checkStrPtrClaims(claims, "doctor_code") data.Nurse_Code = checkStrPtrClaims(claims, "nurse_code") @@ -361,89 +223,3 @@ func ExtractToken(r *http.Request, tokenType TokenType) (data *pa.AuthInfo, err func GetConfig() { a.ParseCfg(&authCfg) } - -func GenTokenFes(input euf.LoginDto) (*d.Data, error) { - event := pl.Event{ - Feature: "Create", - Source: source, - } - - // Get User Fes - userFes := &euf.UserFes{Name: input.Name, AuthPartner_Code: input.AuthPartner_Code} - if errCode := getAndCheck(userFes, userFes); errCode != "" { - return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} - } - if userFes.AuthPartner.SecretKey != input.AuthPartner_SecretKey { - return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-secretKey-invalid", Message: pl.GenMessage("auth-secretKey-invalid")}} - } - - user := &eu.User{Name: userFes.User_Name} - if errCode := getAndCheck(user, user); errCode != "" { - return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} - } - - // Access token prep - id, err := uuid.NewRandom() - if err != nil { - panic(fmt.Sprintf(l.I.Msg("uuid-gen-fail"), err)) - } - if input.Duration == 0 { - input.Duration = 24 * 60 - } - duration := time.Minute * time.Duration(input.Duration) - aUuid := id.String() - atExpires := time.Now().Add(duration).Unix() - atSecretKey := authCfg.AtSecretKey - - // Create Claim - atClaims := jwt.MapClaims{} - atClaims["user_id"] = user.Id - atClaims["user_name"] = user.Name - atClaims["user_contractPosition_code"] = user.ContractPosition_Code - atClaims["uuid"] = aUuid - atClaims["exp"] = atExpires - - // Create output - outputData := d.II{ - "user_id": strconv.Itoa(int(user.Id)), - "user_name": user.Name, - "user_contractPosition_code": user.ContractPosition_Code, - } - - // extra - if err := populateRoles(user, atClaims, outputData, event); err != nil { - return nil, err - } - - // Generate jwt - at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) - ats, err := at.SignedString([]byte(atSecretKey)) - if err != nil { - return nil, d.FieldErrors{"user": d.FieldError{Code: "token-sign-err", Message: pl.GenMessage("token-sign-err")}} - } - outputData["accessToken"] = ats - - // Save to redis - now := time.Now() - atx := time.Unix(atExpires, 0) //converting Unix to UTC(to Time object) - err = ms.I.Set(aUuid, strconv.Itoa(int(user.Id)), atx.Sub(now)).Err() - if err != nil { - panic(fmt.Sprintf(l.I.Msg("redis-store-fail"), err.Error())) - } - - tn := time.Now() - user.LoginAttemptCount = 0 - user.LastSuccessLogin = &tn - user.LastAllowdLogin = &tn - dg.I.Save(&user) - - // Current data - return &d.Data{ - Meta: d.IS{ - "source": "authentication", - "structure": "single-data", - "status": "verified", - }, - Data: outputData, - }, nil -} diff --git a/internal/use-case/main-use-case/authentication/case.go.bu b/internal/use-case/main-use-case/authentication/case.go.bu new file mode 100644 index 00000000..1743407a --- /dev/null +++ b/internal/use-case/main-use-case/authentication/case.go.bu @@ -0,0 +1,463 @@ +package authentication + +import ( + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "github.com/golang-jwt/jwt" + "github.com/google/uuid" + a "github.com/karincake/apem" + dg "github.com/karincake/apem/db-gorm-pg" + ms "github.com/karincake/apem/ms-redis" + d "github.com/karincake/dodol" + l "github.com/karincake/lepet" + + pa "simrs-vx/internal/lib/auth" + pl "simrs-vx/pkg/logger" + p "simrs-vx/pkg/password" + +<<<<<<< HEAD + ed "simrs-vx/internal/domain/main-entities/doctor" + ee "simrs-vx/internal/domain/main-entities/employee" + "simrs-vx/internal/domain/main-entities/intern" + em "simrs-vx/internal/domain/main-entities/midwife" + en "simrs-vx/internal/domain/main-entities/nurse" + ep "simrs-vx/internal/domain/main-entities/pharmacist" + eu "simrs-vx/internal/domain/main-entities/user" + +======= + eu "simrs-vx/internal/domain/main-entities/user" + euf "simrs-vx/internal/domain/main-entities/user-fes" +>>>>>>> fc7e74b (feat/sso-auth: wip) + erc "simrs-vx/internal/domain/references/common" +) + +const source = "authentication" + +var authCfg AuthCfg + +func init() { + a.RegisterExtCall(GetConfig) +} + +// Generates token and store in redis at one place +// just return the error code +func GenToken(input eu.LoginDto) (*d.Data, error) { + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Get User + user := &eu.User{Name: input.Name} + // if input.Position_Code != "" { + // user.Position_Code = input.Position_Code + // } + if errCode := getAndCheck(user, user); errCode != "" { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} + } + + if user.LoginAttemptCount > 5 { + if user.LastSuccessLogin != nil { + now := time.Now() + lastAllowdLogin := user.LastAllowdLogin + if lastAllowdLogin.After(now.Add(-time.Hour * 1)) { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-login-tooMany", Message: pl.GenMessage("auth-login-tooMany")}} + } else { + tn := time.Now() + user.LastAllowdLogin = &tn + user.LoginAttemptCount = 0 + dg.I.Save(&user) + } + } else { + tn := time.Now() + user.LastAllowdLogin = &tn + dg.I.Save(&user) + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-login-tooMany", Message: pl.GenMessage("auth-login-tooMany")}} + } + } + + if !p.Check(input.Password, user.Password) { + user.LoginAttemptCount++ + dg.I.Save(&user) + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-login-incorrect", Message: pl.GenMessage("auth-login-incorrect")}} + } else if user.Status_Code == erc.USCBlocked { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-login-blocked", Message: pl.GenMessage("auth-login-blocked")}} + } else if user.Status_Code == erc.USCNew { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-login-unverified", Message: pl.GenMessage("auth-login-unverified")}} + } + + // Access token prep + id, err := uuid.NewRandom() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("uuid-gen-fail"), err)) + } + if input.Duration == 0 { + input.Duration = 24 * 60 + } + duration := time.Minute * time.Duration(input.Duration) + aUuid := id.String() + atExpires := time.Now().Add(duration).Unix() + atSecretKey := authCfg.AtSecretKey + + // Create Claim + atClaims := jwt.MapClaims{} + atClaims["user_id"] = user.Id + atClaims["user_name"] = user.Name + atClaims["user_contractPosition_code"] = user.ContractPosition_Code + atClaims["uuid"] = aUuid + atClaims["exp"] = atExpires + + // Create output + outputData := d.II{ + "user_id": strconv.Itoa(int(user.Id)), + "user_name": user.Name, + "user_contractPosition_code": user.ContractPosition_Code, + } + + // extra + // role := []string{} + // switch user.ContractPosition_Code { + // case erg.CSCEmp: + // // employee + // employee := ee.Employee{} + // dg.I.Where("\"User_Id\" = ?", user.Id).First(&employee) + // if employee.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noEmployee", Message: pl.GenMessage("auth-noEmployee")}} + // } + // atClaims["employee_id"] = employee.Id + // outputData["employee_id"] = employee.Id + // role = append(role, "emp-"+string(*employee.Position_Code)) + + // //if employee.Division_Code != nil { + // // atClaims["employee_division_code"] = employee.Division_Code + // // outputData["employee_division_code"] = employee.Division_Code + // //} + + // // employee position + // if employee.Id > 0 && employee.Position_Code != nil { + // atClaims["employee_position_code"] = *employee.Position_Code + // switch *employee.Position_Code { + // case erg.EPCDoc: + // doctor := ed.Doctor{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&doctor) + // if doctor.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noDoctor", Message: pl.GenMessage("auth-noDoctor")}} + // } + // atClaims["doctor_code"] = doctor.Code + // outputData["doctor_code"] = doctor.Code + +<<<<<<< HEAD + // specialist + if doctor.Specialist_Code != nil { + atClaims["specialist_code"] = doctor.Specialist_Code + outputData["specialist_code"] = doctor.Specialist_Code + } + if doctor.Subspecialist_Code != nil { + atClaims["subspecialist_code"] = doctor.Subspecialist_Code + outputData["subspecialist_code"] = doctor.Subspecialist_Code + } + case erg.EPCNur: + empData := en.Nurse{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + if empData.Id == 0 { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noNurse", Message: pl.GenMessage("auth-noNurse")}} + } + atClaims["nurse_code"] = empData.Code + outputData["nurse_code"] = empData.Code + case erg.EPCMwi: + empData := em.Midwife{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + if empData.Id == 0 { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noMidwife", Message: pl.GenMessage("auth-noMidwife")}} + } + atClaims["midwife_code"] = empData.Code + outputData["midwife_code"] = empData.Code + case erg.EPCPha: + empData := ep.Pharmacist{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + if empData.Id == 0 { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noPharmacist", Message: pl.GenMessage("auth-noPharmacist")}} + } + atClaims["pharmacist_code"] = empData.Code + outputData["pharmacist_code"] = empData.Code + } +======= + // // specialist + // if doctor.Specialist_Code != nil { + // atClaims["specialist_code"] = doctor.Specialist_Code + // outputData["specialist_code"] = doctor.Specialist_Code + // } + // if doctor.Subspecialist_Code != nil { + // atClaims["subspecialist_code"] = doctor.Subspecialist_Code + // outputData["subspecialist_code"] = doctor.Subspecialist_Code + // } + // case erg.EPCNur: + // empData := en.Nurse{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + // if empData.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noNurse", Message: pl.GenMessage("auth-noNurse")}} + // } + // atClaims["nurse_code"] = empData.Code + // outputData["nurse_code"] = empData.Code + // case erg.EPCMwi: + // empData := em.Midwife{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + // if empData.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noMidwife", Message: pl.GenMessage("auth-noMidwife")}} + // } + // atClaims["midwife_code"] = empData.Code + // outputData["midwife_code"] = empData.Code + // } +>>>>>>> fc7e74b (feat/sso-auth: wip) + + // errorGetPosition := d.FieldErrors{"authentication": d.FieldError{Code: "auth-getData-failed", Message: pl.GenMessage("auth-getData-failed")}} + + // // division position + // divisionPositions, err := getDivisionPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } + + // // installation position + // installationPositions, err := getInstallationPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } + + // // unit position + // unitPositions, err := getUnitPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } + + // // specialist position + // specialistPositions, err := getSpecialistPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } + + // // subspecialist position + // subspecialistPositions, err := getSubspecialistPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } + + // role = append(role, divisionPositions...) + // role = append(role, installationPositions...) + // role = append(role, unitPositions...) + // role = append(role, specialistPositions...) + // role = append(role, subspecialistPositions...) + // // atClaims["division_positions"] = divsionPositions + // // outputData["division_positions"] = divsionPositions + // } + // case erg.CSCInt: + // intern := intern.Intern{} + // dg.I.Where("\"User_Id\" = ?", user.Id).First(&intern) + // role = append(role, "int-"+string(*intern.Position_Code)) + // case erg.CSCSys: + // role = append(role, "system") + // } + // atClaims["roles"] = role + // outputData["roles"] = role + if err := populateRoles(user, atClaims, outputData, event); err != nil { + return nil, err + } + + // Generate jwt + at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) + ats, err := at.SignedString([]byte(atSecretKey)) + if err != nil { + return nil, d.FieldErrors{"user": d.FieldError{Code: "token-sign-err", Message: pl.GenMessage("token-sign-err")}} + } + outputData["accessToken"] = ats + + // Save to redis + now := time.Now() + atx := time.Unix(atExpires, 0) //converting Unix to UTC(to Time object) + err = ms.I.Set(aUuid, strconv.Itoa(int(user.Id)), atx.Sub(now)).Err() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("redis-store-fail"), err.Error())) + } + + tn := time.Now() + user.LoginAttemptCount = 0 + user.LastSuccessLogin = &tn + user.LastAllowdLogin = &tn + dg.I.Save(&user) + + // Current data + return &d.Data{ + Meta: d.IS{ + "source": "authentication", + "structure": "single-data", + "status": "verified", + }, + Data: outputData, + }, nil +} + +func RevokeToken(uuid string) { + ms.I.Del(uuid) +} + +func VerifyToken(r *http.Request, tokenType TokenType) (data *jwt.Token, errCode, errDetail string) { + auth := r.Header.Get("Authorization") + if auth == "" { + return nil, "auth-missingHeader", "" + } + authArr := strings.Split(auth, " ") + if len(authArr) == 2 { + auth = authArr[1] + } + + token, err := jwt.Parse(auth, func(token *jwt.Token) (any, error) { + //Make sure that the token method conform to "SigningMethodHMAC" + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf(l.I.Msg("token-sign-unexcpeted"), token.Header["alg"]) + } + if tokenType == AccessToken { + return []byte(authCfg.AtSecretKey), nil + } else { + return []byte(authCfg.RtSecretKey), nil + } + }) + if err != nil { + return nil, "token-parse-fail", err.Error() + } + return token, "", "" +} + +func ExtractToken(r *http.Request, tokenType TokenType) (data *pa.AuthInfo, err error) { + token, errCode, errDetail := VerifyToken(r, tokenType) + if errCode != "" { + return nil, d.FieldError{Code: errCode, Message: pl.GenMessage(errCode, errDetail)} + } + claims, ok := token.Claims.(jwt.MapClaims) + if ok && token.Valid { + accessUuid, ok := claims["uuid"].(string) + if !ok { + return nil, d.FieldError{Code: "token-invalid", Message: pl.GenMessage("token-invalid", "uuid not available")} + } + user_id, myErr := strconv.ParseInt(fmt.Sprintf("%.f", claims["user_id"]), 10, 64) + if myErr != nil { + return nil, d.FieldError{Code: "token-invalid", Message: pl.GenMessage("token-invalid", "uuid is not available")} + } + accessUuidRedis := ms.I.Get(accessUuid) + if accessUuidRedis.String() == "" { + return nil, d.FieldError{Code: "token-unidentified", Message: pl.GenMessage("token-unidentified")} + } + + data = &pa.AuthInfo{ + Uuid: accessUuid, + User_Id: uint(user_id), + User_Name: fmt.Sprintf("%v", claims["user_name"]), + } + + data.User_ContractPosition_code = checkStrClaims(claims, "contractPosition_code") + data.Employee_Position_Code = checkStrPtrClaims(claims, "employee_position_code") + data.Doctor_Code = checkStrPtrClaims(claims, "doctor_code") + data.Nurse_Code = checkStrPtrClaims(claims, "nurse_code") + data.Midwife_Code = checkStrPtrClaims(claims, "midwife_code") + data.Nutritionist_Code = checkStrPtrClaims(claims, "nutritionist_code") + data.Laborant_Code = checkStrPtrClaims(claims, "laborant_code") + data.Pharmachist_Code = checkStrPtrClaims(claims, "pharmachist_code") + data.Intern_Position_Code = checkStrPtrClaims(claims, "intern_position_code") + data.Employee_Id = checkUntPtrClaims(claims, "employee_id") + return + } + return nil, d.FieldError{Code: "token", Message: "token-invalid"} +} + +func GetConfig() { + a.ParseCfg(&authCfg) +} + +func GenTokenFes(input euf.LoginDto) (*d.Data, error) { + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Get User Fes + userFes := &euf.UserFes{Name: input.Name, AuthPartner_Code: input.AuthPartner_Code} + if errCode := getAndCheck(userFes, userFes); errCode != "" { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} + } + if userFes.AuthPartner.SecretKey != input.AuthPartner_SecretKey { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-secretKey-invalid", Message: pl.GenMessage("auth-secretKey-invalid")}} + } + + user := &eu.User{Name: userFes.User_Name} + if errCode := getAndCheck(user, user); errCode != "" { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} + } + + // Access token prep + id, err := uuid.NewRandom() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("uuid-gen-fail"), err)) + } + if input.Duration == 0 { + input.Duration = 24 * 60 + } + duration := time.Minute * time.Duration(input.Duration) + aUuid := id.String() + atExpires := time.Now().Add(duration).Unix() + atSecretKey := authCfg.AtSecretKey + + // Create Claim + atClaims := jwt.MapClaims{} + atClaims["user_id"] = user.Id + atClaims["user_name"] = user.Name + atClaims["user_contractPosition_code"] = user.ContractPosition_Code + atClaims["uuid"] = aUuid + atClaims["exp"] = atExpires + + // Create output + outputData := d.II{ + "user_id": strconv.Itoa(int(user.Id)), + "user_name": user.Name, + "user_contractPosition_code": user.ContractPosition_Code, + } + + // extra + if err := populateRoles(user, atClaims, outputData, event); err != nil { + return nil, err + } + + // Generate jwt + at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) + ats, err := at.SignedString([]byte(atSecretKey)) + if err != nil { + return nil, d.FieldErrors{"user": d.FieldError{Code: "token-sign-err", Message: pl.GenMessage("token-sign-err")}} + } + outputData["accessToken"] = ats + + // Save to redis + now := time.Now() + atx := time.Unix(atExpires, 0) //converting Unix to UTC(to Time object) + err = ms.I.Set(aUuid, strconv.Itoa(int(user.Id)), atx.Sub(now)).Err() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("redis-store-fail"), err.Error())) + } + + tn := time.Now() + user.LoginAttemptCount = 0 + user.LastSuccessLogin = &tn + user.LastAllowdLogin = &tn + dg.I.Save(&user) + + // Current data + return &d.Data{ + Meta: d.IS{ + "source": "authentication", + "structure": "single-data", + "status": "verified", + }, + Data: outputData, + }, nil +} diff --git a/internal/use-case/main-use-case/authentication/helper.go b/internal/use-case/main-use-case/authentication/helper.go index 0da9f943..c0a3bd0f 100644 --- a/internal/use-case/main-use-case/authentication/helper.go +++ b/internal/use-case/main-use-case/authentication/helper.go @@ -1,10 +1,16 @@ package authentication import ( - dg "github.com/karincake/apem/db-gorm-pg" + "fmt" + "strconv" + "time" "github.com/golang-jwt/jwt" + "github.com/google/uuid" + dg "github.com/karincake/apem/db-gorm-pg" + ms "github.com/karincake/apem/ms-redis" d "github.com/karincake/dodol" + l "github.com/karincake/lepet" pl "simrs-vx/pkg/logger" @@ -15,6 +21,7 @@ import ( "simrs-vx/internal/domain/main-entities/intern" em "simrs-vx/internal/domain/main-entities/midwife" en "simrs-vx/internal/domain/main-entities/nurse" + ep "simrs-vx/internal/domain/main-entities/pharmacist" esp "simrs-vx/internal/domain/main-entities/specialist-position" essp "simrs-vx/internal/domain/main-entities/subspecialist-position" eup "simrs-vx/internal/domain/main-entities/unit-position" @@ -29,8 +36,21 @@ import ( ) // just return the error code -func getAndCheck(input, condition any) (eCode string) { - result := dg.I.Where(condition).Find(&input) +func getAndCheck(input, condition any, includes any) (eCode string) { + qry := dg.I.Where(condition) + + // WARNING THIS PRELOAD FAILS + if includes != nil { + if val := includes.(string); val != "" { + qry = qry.Preload(val) + } else if vals := includes.([]string); len(vals) > 0 { + for _, val := range vals { + qry = qry.Preload(val) + } + } + } + + result := qry.First(&input) if result.Error != nil { return "fetch-fail" } else if result.RowsAffected == 0 { @@ -44,18 +64,14 @@ func getDivisionPosition(employee_id uint, event *pl.Event) ([]string, error) { var result []string // get data division_position based on employee_id - data, _, err := udp.ReadListData(edp.ReadListDto{ - FilterDto: edp.FilterDto{Employee_Id: &employee_id}, - Includes: "Division"}, event) + data, _, err := udp.ReadListData(edp.ReadListDto{FilterDto: edp.FilterDto{Employee_Id: &employee_id}}, event) if err != nil { return nil, err } if len(data) > 0 { for _, dp := range data { - if dp.Division != nil { - result = append(result, "div-"+dp.Division.Code+"-"+dp.Code) - } + result = append(result, "div|"+*dp.Division_Code+"|"+dp.Code) } } @@ -75,9 +91,7 @@ func getInstallationPosition(employeeId uint, event *pl.Event) ([]string, error) if len(data) > 0 { for _, dp := range data { - if dp.Installation != nil { - result = append(result, "inst-"+dp.Installation.Code+"-"+dp.Code) - } + result = append(result, "inst|"+*dp.Installation_Code+"|"+dp.Code) } } @@ -88,18 +102,14 @@ func getUnitPosition(employeeId uint, event *pl.Event) ([]string, error) { var result []string // get data unit_position based on employee_id - data, _, err := uup.ReadListData(eup.ReadListDto{ - FilterDto: eup.FilterDto{Employee_Id: &employeeId}, - Includes: "unit"}, event) + data, _, err := uup.ReadListData(eup.ReadListDto{FilterDto: eup.FilterDto{Employee_Id: &employeeId}}, event) if err != nil { return nil, err } if len(data) > 0 { for _, dp := range data { - if dp.Unit != nil { - result = append(result, "unit-"+dp.Unit.Code+"-"+dp.Code) - } + result = append(result, "unit|"+*dp.Unit_Code+"|"+dp.Code) } } @@ -110,18 +120,14 @@ func getSpecialistPosition(employeeId uint, event *pl.Event) ([]string, error) { var result []string // get data unit_position based on employee_id - data, _, err := usp.ReadListData(esp.ReadListDto{ - FilterDto: esp.FilterDto{Employee_Id: &employeeId}, - Includes: "specialist"}, event) + data, _, err := usp.ReadListData(esp.ReadListDto{FilterDto: esp.FilterDto{Employee_Id: &employeeId}}, event) if err != nil { return nil, err } if len(data) > 0 { for _, dp := range data { - if dp.Specialist != nil { - result = append(result, "spec-"+dp.Specialist.Code+"-"+dp.Code) - } + result = append(result, "spec|"+*dp.Specialist_Code+"|"+dp.Code) } } @@ -141,9 +147,7 @@ func getSubspecialistPosition(employeeId uint, event *pl.Event) ([]string, error if len(data) > 0 { for _, dp := range data { - if dp.Subspecialist != nil { - result = append(result, "subspec-"+dp.Subspecialist.Code+"-"+dp.Code) - } + result = append(result, "subspec|"+dp.Subspecialist.Code+"|"+dp.Code) } } @@ -173,7 +177,28 @@ func checkUntPtrClaims(claim map[string]interface{}, key string) *uint { return nil } -func populateRoles(user *eu.User, atClaims jwt.MapClaims, outputData d.II, event pl.Event) error { +func populateRoles(user *eu.User, input eu.LoginDto, atClaims jwt.MapClaims, outputData d.II, event pl.Event) error { + id, err := uuid.NewRandom() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("uuid-gen-fail"), err)) + } + if input.Duration == 0 { + input.Duration = 24 * 60 + } + duration := time.Minute * time.Duration(input.Duration) + aUuid := id.String() + atExpires := time.Now().Add(duration).Unix() + + atClaims["uuid"] = aUuid + atClaims["exp"] = atExpires + atClaims["user_id"] = user.Id + atClaims["user_name"] = user.Name + atClaims["user_contractPosition_code"] = user.ContractPosition_Code + + outputData["user_id"] = user.Id + outputData["user_name"] = user.Name + outputData["user_contractPosition_code"] = user.ContractPosition_Code + roles := []string{} switch user.ContractPosition_Code { case erg.CSCEmp: @@ -185,12 +210,7 @@ func populateRoles(user *eu.User, atClaims jwt.MapClaims, outputData d.II, event } atClaims["employee_id"] = employee.Id outputData["employee_id"] = employee.Id - roles = append(roles, "emp-"+string(*employee.Position_Code)) - - //if employee.Division_Code != nil { - // atClaims["employee_division_code"] = employee.Division_Code - // outputData["employee_division_code"] = employee.Division_Code - //} + roles = append(roles, "emp|"+string(*employee.Position_Code)) // employee position if employee.Id > 0 && employee.Position_Code != nil { @@ -230,6 +250,14 @@ func populateRoles(user *eu.User, atClaims jwt.MapClaims, outputData d.II, event } atClaims["midwife_code"] = empData.Code outputData["midwife_code"] = empData.Code + case erg.EPCPha: + empData := ep.Pharmacist{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + if empData.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noPharmacist", Message: pl.GenMessage("auth-noPharmacist")}} + } + atClaims["pharmacist_code"] = empData.Code + outputData["pharmacist_code"] = empData.Code } errorGetPosition := d.FieldErrors{"authentication": d.FieldError{Code: "auth-getData-failed", Message: pl.GenMessage("auth-getData-failed")}} @@ -279,8 +307,26 @@ func populateRoles(user *eu.User, atClaims jwt.MapClaims, outputData d.II, event case erg.CSCSys: roles = append(roles, "system") } - atClaims["roles"] = roles outputData["roles"] = roles + + // Generate jwt + atSecretKey := authCfg.AtSecretKey + at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) + ats, err := at.SignedString([]byte(atSecretKey)) + if err != nil { + return d.FieldErrors{"user": d.FieldError{Code: "token-sign-err", Message: pl.GenMessage("token-sign-err")}} + } + outputData["accessToken"] = ats + + // Save to redis + exp, _ := atClaims["exp"].(int64) + now := time.Now() + atx := time.Unix(exp, 0) //converting Unix to UTC(to Time object) + err = ms.I.Set(atClaims["uuid"].(string), strconv.Itoa(int(user.Id)), atx.Sub(now)).Err() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("redis-store-fail"), err.Error())) + } + return nil } diff --git a/internal/use-case/main-use-case/authentication/helper.go.bu b/internal/use-case/main-use-case/authentication/helper.go.bu new file mode 100644 index 00000000..b7ccc660 --- /dev/null +++ b/internal/use-case/main-use-case/authentication/helper.go.bu @@ -0,0 +1,323 @@ +package authentication + +import ( + "fmt" + "strconv" + "time" + + "github.com/golang-jwt/jwt" + "github.com/google/uuid" + dg "github.com/karincake/apem/db-gorm-pg" + ms "github.com/karincake/apem/ms-redis" + d "github.com/karincake/dodol" + l "github.com/karincake/lepet" + + pl "simrs-vx/pkg/logger" + + edp "simrs-vx/internal/domain/main-entities/division-position" + ed "simrs-vx/internal/domain/main-entities/doctor" + ee "simrs-vx/internal/domain/main-entities/employee" + eip "simrs-vx/internal/domain/main-entities/installation-position" + "simrs-vx/internal/domain/main-entities/intern" + em "simrs-vx/internal/domain/main-entities/midwife" + en "simrs-vx/internal/domain/main-entities/nurse" + esp "simrs-vx/internal/domain/main-entities/specialist-position" + essp "simrs-vx/internal/domain/main-entities/subspecialist-position" + eup "simrs-vx/internal/domain/main-entities/unit-position" + eu "simrs-vx/internal/domain/main-entities/user" + erg "simrs-vx/internal/domain/references/organization" + + udp "simrs-vx/internal/use-case/main-use-case/division-position" + uip "simrs-vx/internal/use-case/main-use-case/installation-position" + usp "simrs-vx/internal/use-case/main-use-case/specialist-position" + ussp "simrs-vx/internal/use-case/main-use-case/subspecialist-position" + uup "simrs-vx/internal/use-case/main-use-case/unit-position" +) + +// just return the error code +func getAndCheck(input, condition any, includes any) (eCode string) { + qry := dg.I.Where(condition) + + // WARNING THIS PRELOAD FAILS + if includes != nil { + if val := includes.(string); val != "" { + qry = qry.Preload(val) + } else if vals := includes.([]string); len(vals) > 0 { + for _, val := range vals { + qry = qry.Preload(val) + } + } + } + + result := qry.First(&input) + if result.Error != nil { + return "fetch-fail" + } else if result.RowsAffected == 0 { + return "auth-login-incorrect" + } + + return "" +} + +func getDivisionPosition(employee_id uint, event *pl.Event) ([]string, error) { + var result []string + + // get data division_position based on employee_id + data, _, err := udp.ReadListData(edp.ReadListDto{FilterDto: edp.FilterDto{Employee_Id: &employee_id}}, event) + if err != nil { + return nil, err + } + + if len(data) > 0 { + for _, dp := range data { + result = append(result, "div-"+*dp.Division_Code) + } + } + + return result, nil +} + +func getInstallationPosition(employeeId uint, event *pl.Event) ([]string, error) { + var result []string + + // get data unit_position based on employee_id + data, _, err := uip.ReadListData(eip.ReadListDto{ + FilterDto: eip.FilterDto{Employee_Id: &employeeId}, + Includes: "installation"}, event) + if err != nil { + return nil, err + } + + if len(data) > 0 { + for _, dp := range data { + result = append(result, "inst-"+*dp.Installation_Code+"-"+dp.Code) + } + } + + return result, nil +} + +func getUnitPosition(employeeId uint, event *pl.Event) ([]string, error) { + var result []string + + // get data unit_position based on employee_id + data, _, err := uup.ReadListData(eup.ReadListDto{FilterDto: eup.FilterDto{Employee_Id: &employeeId}}, event) + if err != nil { + return nil, err + } + + if len(data) > 0 { + for _, dp := range data { + result = append(result, "unit-"+*dp.Unit_Code+"-"+dp.Code) + } + } + + return result, nil +} + +func getSpecialistPosition(employeeId uint, event *pl.Event) ([]string, error) { + var result []string + + // get data unit_position based on employee_id + data, _, err := usp.ReadListData(esp.ReadListDto{FilterDto: esp.FilterDto{Employee_Id: &employeeId}}, event) + if err != nil { + return nil, err + } + + if len(data) > 0 { + for _, dp := range data { + result = append(result, "spec-"+*dp.Specialist_Code+"-"+dp.Code) + } + } + + return result, nil +} + +func getSubspecialistPosition(employeeId uint, event *pl.Event) ([]string, error) { + var result []string + + // get data unit_position based on employee_id + data, _, err := ussp.ReadListData(essp.ReadListDto{ + FilterDto: essp.FilterDto{Employee_Id: &employeeId}, + Includes: "subspecialist"}, event) + if err != nil { + return nil, err + } + + if len(data) > 0 { + for _, dp := range data { + result = append(result, "subspec-"+dp.Subspecialist.Code+"-"+dp.Code) + } + } + + return result, nil +} + +func checkStrClaims(claim map[string]interface{}, key string) string { + if v, exist := claim[key]; exist && v != nil { + return v.(string) + } + return "" +} + +func checkStrPtrClaims(claim map[string]interface{}, key string) *string { + if v, exist := claim[key]; exist && v != nil { + val := v.(string) + return &val + } + return nil +} + +func checkUntPtrClaims(claim map[string]interface{}, key string) *uint { + if v, exist := claim[key]; exist && v != nil { + val := uint(v.(float64)) + return &val + } + return nil +} + +func populateRoles(user *eu.User, input eu.LoginDto, atClaims jwt.MapClaims, outputData d.II, event pl.Event) error { + id, err := uuid.NewRandom() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("uuid-gen-fail"), err)) + } + if input.Duration == 0 { + input.Duration = 24 * 60 + } + duration := time.Minute * time.Duration(input.Duration) + aUuid := id.String() + atExpires := time.Now().Add(duration).Unix() + + atClaims["uuid"] = aUuid + atClaims["exp"] = atExpires + atClaims["user_id"] = user.Id + atClaims["user_name"] = user.Name + atClaims["user_contractPosition_code"] = user.ContractPosition_Code + + outputData["user_id"] = user.Id + outputData["user_name"] = user.Name + outputData["user_contractPosition_code"] = user.ContractPosition_Code + + roles := []string{} + switch user.ContractPosition_Code { + case erg.CSCEmp: + // employee + employee := ee.Employee{} + dg.I.Where("\"User_Id\" = ?", user.Id).First(&employee) + if employee.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noEmployee", Message: pl.GenMessage("auth-noEmployee")}} + } + atClaims["employee_id"] = employee.Id + outputData["employee_id"] = employee.Id + roles = append(roles, "emp-"+string(*employee.Position_Code)) + + // employee position + if employee.Id > 0 && employee.Position_Code != nil { + atClaims["employee_position_code"] = *employee.Position_Code + switch *employee.Position_Code { + case erg.EPCDoc: + doctor := ed.Doctor{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&doctor) + if doctor.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noDoctor", Message: pl.GenMessage("auth-noDoctor")}} + } + atClaims["doctor_code"] = doctor.Code + outputData["doctor_code"] = doctor.Code + + // specialist + if doctor.Specialist_Code != nil { + atClaims["specialist_code"] = doctor.Specialist_Code + outputData["specialist_code"] = doctor.Specialist_Code + } + if doctor.Subspecialist_Code != nil { + atClaims["subspecialist_code"] = doctor.Subspecialist_Code + outputData["subspecialist_code"] = doctor.Subspecialist_Code + } + case erg.EPCNur: + empData := en.Nurse{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + if empData.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noNurse", Message: pl.GenMessage("auth-noNurse")}} + } + atClaims["nurse_code"] = empData.Code + outputData["nurse_code"] = empData.Code + case erg.EPCMwi: + empData := em.Midwife{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + if empData.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noMidwife", Message: pl.GenMessage("auth-noMidwife")}} + } + atClaims["midwife_code"] = empData.Code + outputData["midwife_code"] = empData.Code + } + + errorGetPosition := d.FieldErrors{"authentication": d.FieldError{Code: "auth-getData-failed", Message: pl.GenMessage("auth-getData-failed")}} + + // division position + divisionPositions, err := getDivisionPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + // installation position + installationPositions, err := getInstallationPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + // unit position + unitPositions, err := getUnitPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + // specialist position + specialistPositions, err := getSpecialistPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + // subspecialist position + subspecialistPositions, err := getSubspecialistPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + roles = append(roles, divisionPositions...) + roles = append(roles, installationPositions...) + roles = append(roles, unitPositions...) + roles = append(roles, specialistPositions...) + roles = append(roles, subspecialistPositions...) + // atClaims["division_positions"] = divsionPositions + // outputData["division_positions"] = divsionPositions + } + case erg.CSCInt: + intern := intern.Intern{} + dg.I.Where("\"User_Id\" = ?", user.Id).First(&intern) + roles = append(roles, "int-"+string(*intern.Position_Code)) + case erg.CSCSys: + roles = append(roles, "system") + } + atClaims["roles"] = roles + outputData["roles"] = roles + + // Generate jwt + atSecretKey := authCfg.AtSecretKey + at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) + ats, err := at.SignedString([]byte(atSecretKey)) + if err != nil { + return d.FieldErrors{"user": d.FieldError{Code: "token-sign-err", Message: pl.GenMessage("token-sign-err")}} + } + outputData["accessToken"] = ats + + // Save to redis + exp, _ := atClaims["exp"].(int64) + now := time.Now() + atx := time.Unix(exp, 0) //converting Unix to UTC(to Time object) + err = ms.I.Set(atClaims["uuid"].(string), strconv.Itoa(int(user.Id)), atx.Sub(now)).Err() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("redis-store-fail"), err.Error())) + } + + return nil +} diff --git a/internal/use-case/main-use-case/user-fes/case.go b/internal/use-case/main-use-case/user-fes/case.go new file mode 100644 index 00000000..51225927 --- /dev/null +++ b/internal/use-case/main-use-case/user-fes/case.go @@ -0,0 +1,276 @@ +package userfes + +import ( + "strconv" + + dg "github.com/karincake/apem/db-gorm-pg" + d "github.com/karincake/dodol" + "gorm.io/gorm" + + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + e "simrs-vx/internal/domain/main-entities/user-fes" +) + +const source = "ext-user" + +func Create(input e.CreateDto) (*d.Data, error) { + data := e.UserFes{} + + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "create") + + err := dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunCreateMiddleware(createPreMw, &input, &data); err != nil { + return err + } + + if resData, err := CreateData(input, &event, tx); err != nil { + return err + } else { + data = *resData + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunCreateMiddleware(createPostMw, &input, &data); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.II{ + "source": source, + "structure": "single-data", + "status": "created", + }, + Data: data.ToResponse(), + }, nil +} + +func ReadList(input e.ReadListDto) (*d.Data, error) { + var data *e.UserFes + var dataList []e.UserFes + var metaList *e.MetaDto + var err error + + event := pl.Event{ + Feature: "ReadList", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "readList") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunReadListMiddleware(readListPreMw, &input, data); err != nil { + return err + } + + if dataList, metaList, err = ReadListData(input, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunReadListMiddleware(readListPostMw, &input, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "list-data", + "status": "fetched", + "page_number": strconv.Itoa(metaList.PageNumber), + "page_size": strconv.Itoa(metaList.PageSize), + "record_totalCount": strconv.Itoa(metaList.Count), + "record_currentCount": strconv.Itoa(len(dataList)), + }, + Data: e.ToResponseList(dataList), + }, nil +} + +func ReadDetail(input e.ReadDetailDto) (*d.Data, error) { + var data *e.UserFes + var err error + + event := pl.Event{ + Feature: "ReadDetail", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "readDetail") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunReadDetailMiddleware(readDetailPreMw, &input, data); err != nil { + return err + } + + if data, err = ReadDetailData(input, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunReadDetailMiddleware(readDetailPostMw, &input, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "fetched", + }, + Data: data.ToResponse(), + }, nil +} + +func Update(input e.UpdateDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Id: input.Id} + var data *e.UserFes + var err error + + event := pl.Event{ + Feature: "Update", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "update") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") + if data, err = ReadDetailData(rdDto, &event, tx); err != nil { + return err + } + + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunUpdateMiddleware(readDetailPreMw, &rdDto, data); err != nil { + return err + } + + if err := UpdateData(input, data, &event, tx); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunUpdateMiddleware(readDetailPostMw, &rdDto, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "updated", + }, + Data: data.ToResponse(), + }, nil + +} + +func Delete(input e.DeleteDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Id: input.Id} + var data *e.UserFes + var err error + + event := pl.Event{ + Feature: "Delete", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "delete") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") + if data, err = ReadDetailData(rdDto, &event, tx); err != nil { + return err + } + + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunDeleteMiddleware(readDetailPreMw, &rdDto, data); err != nil { + return err + } + + if err := DeleteData(data, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunDeleteMiddleware(readDetailPostMw, &rdDto, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "deleted", + }, + Data: data.ToResponse(), + }, nil + +} diff --git a/internal/use-case/main-use-case/user-fes/helper.go b/internal/use-case/main-use-case/user-fes/helper.go new file mode 100644 index 00000000..b83d303f --- /dev/null +++ b/internal/use-case/main-use-case/user-fes/helper.go @@ -0,0 +1,18 @@ +package userfes + +import ( + e "simrs-vx/internal/domain/main-entities/user-fes" +) + +func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.UserFes) { + var inputSrc *e.CreateDto + if inputT, ok := any(input).(*e.CreateDto); ok { + inputSrc = inputT + } else { + inputTemp := any(input).(*e.UpdateDto) + inputSrc = &inputTemp.CreateDto + } + data.Name = inputSrc.Name + data.AuthPartner_Code = inputSrc.AuthPartner_Code + data.User_Name = inputSrc.User_Name +} diff --git a/internal/use-case/main-use-case/user-fes/lib.go b/internal/use-case/main-use-case/user-fes/lib.go new file mode 100644 index 00000000..ce7c8280 --- /dev/null +++ b/internal/use-case/main-use-case/user-fes/lib.go @@ -0,0 +1,143 @@ +package userfes + +import ( + "gorm.io/gorm" + + dg "github.com/karincake/apem/db-gorm-pg" + gh "github.com/karincake/getuk" + + plh "simrs-vx/pkg/lib-helper" + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + eu "simrs-vx/internal/domain/main-entities/user-fes" +) + +func CreateData(input eu.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*eu.UserFes, error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + data := eu.UserFes{} + setData(&input, &data) + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.Create(&data).Error; err != nil { + return nil, plh.HandleCreateError(input, event, err) + } + + pl.SetLogInfo(event, nil, "complete") + return &data, nil +} + +func ReadListData(input eu.ReadListDto, event *pl.Event, dbx ...*gorm.DB) ([]eu.UserFes, *eu.MetaDto, error) { + pl.SetLogInfo(event, input, "started", "DBReadList") + data := []eu.UserFes{} + pagination := gh.Pagination{} + count := int64(0) + meta := eu.MetaDto{} + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + tx = tx. + Model(&eu.UserFes{}). + Scopes(gh.Preload(input.Includes)). + Scopes(gh.Filter(input.FilterDto)). + Count(&count). + Scopes(gh.Paginate(input, &pagination)). + Scopes(gh.Sort(input.Sort)) + + if err := tx.Find(&data).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, &meta, nil + } + return nil, nil, plh.HandleListError(input, event, err) + } + + meta.Count = int(count) + meta.PageNumber = pagination.PageNumber + meta.PageSize = pagination.PageSize + + pl.SetLogInfo(event, nil, "complete") + return data, &meta, nil +} + +func ReadDetailData(input eu.ReadDetailDto, event *pl.Event, dbx ...*gorm.DB) (*eu.UserFes, error) { + pl.SetLogInfo(event, input, "started", "DBReadDetail") + data := eu.UserFes{} + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx. + Scopes(gh.Preload(input.Includes)). + First(&data, input.Id).Error; err != nil { + if processedErr := pu.HandleReadError(err, event, source, input.Id, data); processedErr != nil { + return nil, processedErr + } + } + + pl.SetLogInfo(event, nil, "complete") + return &data, nil +} + +func UpdateData(input eu.UpdateDto, data *eu.UserFes, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, data, "started", "DBUpdate") + setData(&input, data) + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.Save(&data).Error; err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-update-fail", + Detail: "Database update failed", + Raw: err, + } + return pl.SetLogError(event, input) + } + + pl.SetLogInfo(event, nil, "complete") + return nil +} + +func DeleteData(data *eu.UserFes, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, data, "started", "DBDelete") + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.Delete(&data).Error; err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-delete-fail", + Detail: "Database delete failed", + Raw: err, + } + return pl.SetLogError(event, data) + } + + pl.SetLogInfo(event, nil, "complete") + return nil +} diff --git a/internal/use-case/main-use-case/user-fes/middleware-runner.go b/internal/use-case/main-use-case/user-fes/middleware-runner.go new file mode 100644 index 00000000..f669d4cd --- /dev/null +++ b/internal/use-case/main-use-case/user-fes/middleware-runner.go @@ -0,0 +1,103 @@ +package userfes + +import ( + e "simrs-vx/internal/domain/main-entities/user-fes" + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + "gorm.io/gorm" +) + +type middlewareRunner struct { + Event *pl.Event + Tx *gorm.DB + MwType pu.MWType +} + +// NewMiddlewareExecutor creates a new middleware executor +func newMiddlewareRunner(event *pl.Event, tx *gorm.DB) *middlewareRunner { + return &middlewareRunner{ + Event: event, + Tx: tx, + } +} + +// ExecuteCreateMiddleware executes create middleware +func (me *middlewareRunner) RunCreateMiddleware(middlewares []createMw, input *e.CreateDto, data *e.UserFes) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunReadListMiddleware(middlewares []readListMw, input *e.ReadListDto, data *e.UserFes) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunReadDetailMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.UserFes) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunUpdateMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.UserFes) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunDeleteMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.UserFes) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) setMwType(mwType pu.MWType) { + me.MwType = mwType +} diff --git a/internal/use-case/main-use-case/user-fes/middleware.go b/internal/use-case/main-use-case/user-fes/middleware.go new file mode 100644 index 00000000..71dac997 --- /dev/null +++ b/internal/use-case/main-use-case/user-fes/middleware.go @@ -0,0 +1,5 @@ +package userfes + +func init() { + +} diff --git a/internal/use-case/main-use-case/user-fes/tycovar.go b/internal/use-case/main-use-case/user-fes/tycovar.go new file mode 100644 index 00000000..008f5cfa --- /dev/null +++ b/internal/use-case/main-use-case/user-fes/tycovar.go @@ -0,0 +1,44 @@ +/* +DESCRIPTION: +A sample, part of the package that contains type, constants, and/or variables. + +In this sample it also provides type and variable regarding the needs of the +middleware to separate from main use-case which has the basic CRUD +functionality. The purpose of this is to make the code more maintainable. +*/ +package userfes + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/user-fes" +) + +type createMw struct { + Name string + Func func(input *e.CreateDto, data *e.UserFes, tx *gorm.DB) error +} + +type readListMw struct { + Name string + Func func(input *e.ReadListDto, data *e.UserFes, tx *gorm.DB) error +} + +type readDetailMw struct { + Name string + Func func(input *e.ReadDetailDto, data *e.UserFes, tx *gorm.DB) error +} + +type UpdateMw = readDetailMw +type DeleteMw = readDetailMw + +var createPreMw []createMw // preprocess middleware +var createPostMw []createMw // postprocess middleware +var readListPreMw []readListMw // .. +var readListPostMw []readListMw // .. +var readDetailPreMw []readDetailMw +var readDetailPostMw []readDetailMw +var updatePreMw []readDetailMw +var updatePostMw []readDetailMw +var deletePreMw []readDetailMw +var deletePostMw []readDetailMw From fe804dabb3334d708cf9fb80390559a4c742f0ef Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Mon, 10 Nov 2025 22:54:08 +0700 Subject: [PATCH 07/18] feat/sso-auth: added the handler --- .../main-handler/auth-partner/handler.go | 71 +++++++++++++++++++ .../main-handler/authentication/handler.go | 46 ++++++------ .../interface/main-handler/main-handler.go | 6 ++ .../main-handler/user-fes/handler.go | 71 +++++++++++++++++++ 4 files changed, 172 insertions(+), 22 deletions(-) create mode 100644 internal/interface/main-handler/auth-partner/handler.go create mode 100644 internal/interface/main-handler/user-fes/handler.go diff --git a/internal/interface/main-handler/auth-partner/handler.go b/internal/interface/main-handler/auth-partner/handler.go new file mode 100644 index 00000000..f31e538d --- /dev/null +++ b/internal/interface/main-handler/auth-partner/handler.go @@ -0,0 +1,71 @@ +package authpartner + +import ( + "net/http" + + rw "github.com/karincake/risoles" + sf "github.com/karincake/semprit" + + // ua "github.com/karincake/tumpeng/auth/svc" + + e "simrs-vx/internal/domain/main-entities/auth-partner" + u "simrs-vx/internal/use-case/main-use-case/auth-partner" +) + +type myBase struct{} + +var O myBase + +func (obj myBase) Create(w http.ResponseWriter, r *http.Request) { + dto := e.CreateDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + res, err := u.Create(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) GetList(w http.ResponseWriter, r *http.Request) { + dto := e.ReadListDto{} + sf.UrlQueryParam(&dto, *r.URL) + res, err := u.ReadList(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) GetDetail(w http.ResponseWriter, r *http.Request) { + id := uint16(rw.ValidateInt(w, "id", r.PathValue("id"))) + if id <= 0 { + return + } + dto := e.ReadDetailDto{} + dto.Id = &id + res, err := u.ReadDetail(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) Update(w http.ResponseWriter, r *http.Request) { + id := uint16(rw.ValidateInt(w, "id", r.PathValue("id"))) + if id <= 0 { + return + } + + dto := e.UpdateDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + dto.Id = id + res, err := u.Update(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) Delete(w http.ResponseWriter, r *http.Request) { + id := uint16(rw.ValidateInt(w, "id", r.PathValue("id"))) + if id <= 0 { + return + } + + dto := e.DeleteDto{} + dto.Id = id + res, err := u.Delete(dto) + rw.DataResponse(w, res, err) +} diff --git a/internal/interface/main-handler/authentication/handler.go b/internal/interface/main-handler/authentication/handler.go index fc36dc52..243ba9cd 100644 --- a/internal/interface/main-handler/authentication/handler.go +++ b/internal/interface/main-handler/authentication/handler.go @@ -30,6 +30,30 @@ func Login(w http.ResponseWriter, r *http.Request) { } } +func LoginFes(w http.ResponseWriter, r *http.Request) { + var input mf.LoginDto + err := sp.IOReaderJson(&input, r.Body) + if err != nil { + rw.WriteJSON(w, http.StatusUnauthorized, d.II{"errors": err}, nil) + return + } + + input.AuthPartner_Code = r.Header.Get("X-AuthPartner-Code") + input.AuthPartner_SecretKey = r.Header.Get("X-AuthPartner-SecretKey") + if err = sr.Validate(input); err != nil { + rw.WriteJSON(w, http.StatusUnauthorized, d.II{"errors": err}, nil) + return + } + + // input.Position = Position + res, err := s.GenTokenFes(input) + if err != nil { + rw.WriteJSON(w, http.StatusUnauthorized, d.II{"errors": err}, nil) + } else { + rw.DataResponse(w, res, err) + } +} + func Logout(w http.ResponseWriter, r *http.Request) { ctxVal := r.Context().Value(pa.AuthKey{}) if ctxVal == nil { @@ -52,25 +76,3 @@ func GuardMW(next http.Handler) http.Handler { next.ServeHTTP(w, r.WithContext(ctx)) }) } - -func LoginFes(w http.ResponseWriter, r *http.Request) { - var input mf.LoginDto - err := sp.IOReaderJson(input, r.Body) - if err != nil { - rw.WriteJSON(w, http.StatusUnauthorized, d.II{"errors": err}, nil) - } - - input.AuthPartner_Code = r.Header.Get("X-AuthPartner-Code") - input.AuthPartner_SecretKey = r.Header.Get("X-AuthPartner-SecretKey") - if err = (sr.Validate(input)); err != nil { - rw.WriteJSON(w, http.StatusUnauthorized, d.II{"errors": err}, nil) - } - - // input.Position = Position - res, err := s.GenTokenFes(input) - if err != nil { - rw.WriteJSON(w, http.StatusUnauthorized, d.II{"errors": err}, nil) - } else { - rw.DataResponse(w, res, err) - } -} diff --git a/internal/interface/main-handler/main-handler.go b/internal/interface/main-handler/main-handler.go index b2589f86..013bc98c 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -36,6 +36,7 @@ import ( /******************** actor ********************/ + authpartner "simrs-vx/internal/interface/main-handler/auth-partner" doctor "simrs-vx/internal/interface/main-handler/doctor" employee "simrs-vx/internal/interface/main-handler/employee" nurse "simrs-vx/internal/interface/main-handler/nurse" @@ -47,6 +48,7 @@ import ( personinsurance "simrs-vx/internal/interface/main-handler/person-insurance" pharmacist "simrs-vx/internal/interface/main-handler/pharmacist" user "simrs-vx/internal/interface/main-handler/user" + userfes "simrs-vx/internal/interface/main-handler/user-fes" /******************** external ********************/ a "github.com/karincake/apem" @@ -132,7 +134,9 @@ func SetRoutes() http.Handler { r.HandleFunc("/", home.Home) r.HandleFunc("POST /v1/authentication/login", auth.Login) + r.HandleFunc("POST /v1/authentication/login-fes", auth.LoginFes) hk.Route("POST /v1/authentication/logout", r, auth.GuardMW, auth.Logout) + hc.RegCrud(r, "/v1/auth-partner", authpartner.O) hc.RegCrud(r, "/v1/practice-schedule", practiceschedule.O) hc.RegCrud(r, "/v1/counter", counter.O) hc.RegCrud(r, "/v1/medicine-mix", medicicinemix.O) @@ -284,6 +288,8 @@ func SetRoutes() http.Handler { "PATCH /{id}/block": user.O.Block, "PATCH /{id}/active": user.O.Active, }) + hc.RegCrud(r, "/v1/user-fes", userfes.O) + hk.GroupRoutes("/v1/patient", r, hk.MapHandlerFunc{ "GET /": patient.O.GetList, "GET /{id}": patient.O.GetDetail, diff --git a/internal/interface/main-handler/user-fes/handler.go b/internal/interface/main-handler/user-fes/handler.go new file mode 100644 index 00000000..0c37ff08 --- /dev/null +++ b/internal/interface/main-handler/user-fes/handler.go @@ -0,0 +1,71 @@ +package userfes + +import ( + "net/http" + + rw "github.com/karincake/risoles" + sf "github.com/karincake/semprit" + + // ua "github.com/karincake/tumpeng/auth/svc" + + e "simrs-vx/internal/domain/main-entities/user-fes" + u "simrs-vx/internal/use-case/main-use-case/user-fes" +) + +type myBase struct{} + +var O myBase + +func (obj myBase) Create(w http.ResponseWriter, r *http.Request) { + dto := e.CreateDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + res, err := u.Create(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) GetList(w http.ResponseWriter, r *http.Request) { + dto := e.ReadListDto{} + sf.UrlQueryParam(&dto, *r.URL) + res, err := u.ReadList(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) GetDetail(w http.ResponseWriter, r *http.Request) { + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + dto := e.ReadDetailDto{} + dto.Id = uint(id) + res, err := u.ReadDetail(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) Update(w http.ResponseWriter, r *http.Request) { + id := uint16(rw.ValidateInt(w, "id", r.PathValue("id"))) + if id <= 0 { + return + } + + dto := e.UpdateDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + dto.Id = uint(id) + res, err := u.Update(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) Delete(w http.ResponseWriter, r *http.Request) { + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + + dto := e.DeleteDto{} + dto.Id = uint(id) + res, err := u.Delete(dto) + rw.DataResponse(w, res, err) +} From eb57a01a943aed5cd4b86063e5a55a6dd39b703e Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Mon, 10 Nov 2025 23:00:31 +0700 Subject: [PATCH 08/18] feat/sso-auth: entities adjustment --- .../migrations/20251110155448.sql | 6 ++ cmd/main-migration/migrations/atlas.sum | 81 ++++++++++--------- .../domain/main-entities/auth-partner/dto.go | 4 +- .../main-entities/auth-partner/entity.go | 8 +- .../domain/main-entities/user-fes/entity.go | 11 ++- 5 files changed, 60 insertions(+), 50 deletions(-) create mode 100644 cmd/main-migration/migrations/20251110155448.sql diff --git a/cmd/main-migration/migrations/20251110155448.sql b/cmd/main-migration/migrations/20251110155448.sql new file mode 100644 index 00000000..203a27cb --- /dev/null +++ b/cmd/main-migration/migrations/20251110155448.sql @@ -0,0 +1,6 @@ +-- Modify "AuthPartner" table +ALTER TABLE "public"."AuthPartner" ALTER COLUMN "Id" TYPE integer; +-- Modify "UserFes" table +ALTER TABLE "public"."UserFes" ALTER COLUMN "AuthPartner_Code" TYPE character varying(50); +-- Create index "idx-userFes-name-authPartner_code" to table: "UserFes" +CREATE UNIQUE INDEX "idx-userFes-name-authPartner_code" ON "public"."UserFes" ("Name", "AuthPartner_Code"); diff --git a/cmd/main-migration/migrations/atlas.sum b/cmd/main-migration/migrations/atlas.sum index 36eff156..8c3dedc2 100644 --- a/cmd/main-migration/migrations/atlas.sum +++ b/cmd/main-migration/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:gxzRzsKXNeg9LaO7Hak8tLYmALxv5wumfuA+BSfFD3E= +h1:eTC10wvsOLnENRLwgViQvRs0NYu3w94Ah1fOaGCqFEg= 20250904105930.sql h1:MEM6blCgke9DzWQSTnLzasbPIrcHssNNrJqZpSkEo6k= 20250904141448.sql h1:J8cmYNk4ZrG9fhfbi2Z1IWz7YkfvhFqTzrLFo58BPY0= 20250908062237.sql h1:Pu23yEW/aKkwozHoOuROvHS/GK4ngARJGdO7FB7HZuI= @@ -75,42 +75,43 @@ h1:gxzRzsKXNeg9LaO7Hak8tLYmALxv5wumfuA+BSfFD3E= 20251106040137.sql h1:ppcqkVoT0o9jZcjI/TN7LuaPxXhJQhnIXEJtloP/46o= 20251106041333.sql h1:2JkxyelQ/EeB+boL5bfpnzefw32ttEGKvKchtQjWmAU= 20251106042006.sql h1:ruppYa1kAJQUU3ufQBbKGMcXrGbGJJiRPclT+dNc/YQ= -20251106050412.sql h1:MiEMJ1HCFYnalKuq3Z38xJeogfBAMqsTv2sG4EF8dDw= -20251106063418.sql h1:y3veDJPjKekOWLCZek/LgQwXPRhZtOppTfUXiqoL95s= -20251106071906.sql h1:/TUZA3XpMY23qEJXdkTwlzrNMvSSl6JJniPcgAttBaw= -20251106073157.sql h1:78txeibJ602DMD7huD618ZSMt6phSRzDNPTlo0PGyrc= -20251106074218.sql h1:8Xz7WywrtUnSxOHhlal53gG9rE7r86LFUt5zBFe/mIs= -20251106081846.sql h1:jp91Bf5bxGXMiUB1VIuN6y768vb2iWwow44WfCE5J5k= -20251106082844.sql h1:RHYzRO4G1fSWwf+xc/3QezZ/Iil67cZPIgNpNz3TNhQ= -20251106090021.sql h1:dFDk6mq+zjbYWmfWIrHf9DiKvvoXHjrr0++zssMTWP8= -20251106144745.sql h1:aHcr23iBFqCHer5D/SsPMXBCLjGqUYvWYfRU8jSJgIw= -20251107012049.sql h1:hu/7NHhnAkT4xK0RNtqmMDdH1Bo5EZbl7itDRjiCT+g= -20251107064812.sql h1:sfCXDQYnMf0ddrQ9oYljWJLLSt9NJjJV6o8VS3p7aZE= -20251107064937.sql h1:DlYGJ9LZFwZyR7jBP5zaGB128aIc4HAixBKPYCz9EkY= -20251107071420.sql h1:ynCdZAd2utLl+FhtWZwtahNXgIVOvuk3s/rOq7lfXA4= -20251107074318.sql h1:WE9cPhibWtZ0dbu1VEGirTeY6ijFYGMNhHdBtM32kOc= -20251107075050.sql h1:8tvneruqdynDOaJK1+0z4CH7YXZStZpGdqwIeOMLik4= -20251107080604.sql h1:8c4jd4Tql7tcdhbI9NS0tgvN+ADu9FnCf8wMUbmW7A0= -20251107081830.sql h1:SAAe3lmsm9vGXuSEsDdl7ad0EAxP5CMmFRDEgp9M7yY= -20251107091033.sql h1:JLdX/u7GUdBfjrPrMSNAqc8HtSoj0YA9iW9Vc6FJZdw= -20251107091209.sql h1:CzhYtwAwT+GHrbqcagnJE+v3mbl/rObf1IJaLCKlzrs= -20251107091541.sql h1:+3ZyWJTftDY2JeWThXuIxGWpUBnyMPyOyY4jBjdWYJI= -20251110012217.sql h1:f4Z8TuGc+XMSJ+Ekn4/PeHRE2FlHWkc5gKPJB0hAX7c= -20251110012306.sql h1:ENPyI6Kjdk6qKJQb0yJ6MCTDPAmO1WD/uhKuCSl+jYo= -20251110052049.sql h1:OrQ0acnyoQLKnTitZfnBcVr5jDslF59OFLaqT7SpdVs= -20251110062042.sql h1:9KwldQt0NpVPR86L0T4hlkfHAGau+7CiZYgu5rF+yhg= -20251110063202.sql h1:A117DuZmZ6U0jWHA3DISnr+yvBjKIr1ObrUr047YezQ= -20251110063633.sql h1:qTiC0F19JnhUIXF4LGJQ21jEV6kKGyhTr1x2kimFqPQ= -20251110085551.sql h1:HZcJM0RSC6HBaUSjKBE8MgDG8Vn9f3LmwA/OnT9Cp7I= -20251110091516.sql h1:W3AQhQLgirEWuCObbLl+Prdrbq6k6EEY1xcoWsmbog4= -20251110091948.sql h1:3tsITMrZr/T+L4wqUMz8sHS229jCJl4T0Nu3dMccxH8= -20251110092729.sql h1:uU+k88RH/e0Ns4/SmJl03RVYPscBAPuiLfxR6CJqaf0= -20251110093522.sql h1:O7upSj8VNjzvroL4IU59bfxKATOkAVGBArcUbVNq9aM= -20251110100258.sql h1://JSArUMNI3/gAtYDx2VN94C198SFW0ANjgs+p6eCRM= -20251110100545.sql h1:ENPOqeJYRpMI4e8VCKwaQgaql8se6pIidAhG2cjskBg= -20251111072601.sql h1:6p2qynJ2vr3C18peYciAbOIWOoj/+PnNcobU3wNHboQ= -20251111073546.sql h1:BORlFReAZTDLUfpS/eIf5qGkJ+FNznMGK47HRiPkfL8= -20251111074148.sql h1:qzjLGQRWo1Gg8LkJFgTb5Ak7h53tdz+eQrpJqC9+Gc0= -20251111074652.sql h1:uuThrt4wjdq4qEbvS67fF0Nfw/dlj7vGnLqywWBznqk= -20251111082257.sql h1:eUfu0tDtl9KqX7UK8is6Qc04NSQs4BXwKsykZwnVH1w= -20251111111017.sql h1:UGUI29WXoDdEp4jWtbzrFGhfO8XO50IafBE7cMfo70k= +20251106050412.sql h1:1002KYtHd8AwrQTMewbs/PPHDylHDghigE/3S7PVdMA= +20251106063418.sql h1:jPW/gBnbFl4RO39lQ0ZMDtYA6xbhyD6CgQupT50HmaY= +20251106071906.sql h1:leYGKxR3EQn794aOehf0sd/ZPmOnvBMZPy5/anGmRB4= +20251106073157.sql h1:KASMzjjjk5UB7Zj8lCRtM1utc4ZnDjlnpZbtTe3vONE= +20251106074218.sql h1:Z5q5deOvLaZDPhiVTN9st3/s56RepBa2YOyrMXBdj4A= +20251106081846.sql h1:P+VsWwhGt60adDIZuE/Aa38JVp/yX1rnsdpXpxASodw= +20251106082844.sql h1:Dmi5A8i9frQZvdXYPwc7f8CisZtBH8liSXq1rI6z1iM= +20251106090021.sql h1:4JwdKgO8T46YhyWVJUxpRIwudBDlG8QN1brSOYmgQ20= +20251106144745.sql h1:nqnQCzGrVJaq8ilOEOGXeRUL1dolj+OPWKuP8A92FRA= +20251107012049.sql h1:Pff4UqltGS3clSlGr0qq8CQM56L29wyxY0FC/N/YAhU= +20251107064812.sql h1:GB9a0ZfMYTIoGNmKUG+XcYUsTnRMFfT4/dAD71uCPc4= +20251107064937.sql h1:IC5pw1Ifj30hiE6dr5NMHXaSHoQI+vRd40N5ABgBHRI= +20251107071420.sql h1:9NO3iyLEXEtWa2kSRjM/8LyzuVIk6pdFL2SuheWjB08= +20251107074318.sql h1:7fHbSRrdjOmHh/xwnjCLwoiB5cW5zeH+uxLV0vZbkIA= +20251107075050.sql h1:np+3uTOnU9QNtK7Knaw8eRMhkyB9AwrtSNHphOBxbHY= +20251107080604.sql h1:cXDBLPJDVWLTG6yEJqkJsOQ7p7VYxLM2SY+mwO8qSHo= +20251107081830.sql h1:/S7OQZo4ZnK80t28g/JyiOTZtmWG/dP5Wg2zXNMQ/iE= +20251107091033.sql h1:/cbkF1nO/IjNSIfDJJx456KJtQ9rWFXOBFAkR/M2xiE= +20251107091209.sql h1:jrLQOUeV8ji2fg0pnEcs1bw4ANUxzTSMXC/rrHLIY+M= +20251107091541.sql h1:6UqbhQQRmzA2+eKu5lIvkwOkk+lH70QLZC8Pjpjcq68= +20251110012217.sql h1:C9HpX0iyHzKjyNv/5DSAn2MCHj6MX4p5UQ/NrY7QD0w= +20251110012306.sql h1:J54yb27d30LBbYp9n1P66gFVRlxPguKu0kxmWIBBG8g= +20251110052049.sql h1:232T2x8xTczJl9nk4jxJpZXhoOGYthhxjJ7nK8Jd8vg= +20251110062042.sql h1:WnfVUXrzYoj8qdkkjO9/JQQ8agGd4GfSHQdMjo7LDAg= +20251110063202.sql h1:hSzGfwVMWa6q3vwIQZUkxKgBNCzHjB+6GKy54zfV+oQ= +20251110063633.sql h1:/VpofIAqNS1CnazEnpW/+evbzn9Kew3xDW48r57M+Xg= +20251110085551.sql h1:bFZwSmfvVbTUr/enWB82WqjG88gpqcZ6s45btUvO0uo= +20251110091516.sql h1:KkJMwPQuaZQhiqnKrNQrgP12gw9rV8T3P2o3mtGTcvY= +20251110091948.sql h1:I4odAYrJdvNf1jPw6ppDC0XdI7v6vKBACg/ABwUgA7I= +20251110092729.sql h1:l1out8soEmVP6dNjaIOtGYo6QDcoJZRI8X1sjZ5ZGmo= +20251110093522.sql h1:nsz8jCxGjEdr/bz9g+4ozfZzIP803xONjVmucad1GMc= +20251110100258.sql h1:IBqt1VZj5WjQ+l9aAFGHOCCBtzb03KlLLihFLut7itg= +20251110100545.sql h1:6/LV7751iyKxE2xI6vO1zly+aHUwxXD/IBwLcVpKxqM= +20251110155448.sql h1:kFPobJB+cpflsXBAWUwy3lohuWvrb/VRlXnhJWl7i3Y= +20251111072601.sql h1:ch8F+yVhsSM5xY+TwMLY3PxdLa4Wuhtj76oyw79R7Js= +20251111073546.sql h1:cCv0NPscADAOBahRVqtDWFs6G2t7n+4a+RwlF8vk/c4= +20251111074148.sql h1:70TsV83u1gQ5TktI13K7NQiyCCa35Td2aR6CNtKUa4U= +20251111074652.sql h1:ddfQ/sRKMezPM75xBFTGytUQX5AwZ3znrJVpg73gKPA= +20251111082257.sql h1:ZsdLY1ROouos0l3oS0lkeSiuKLEUGbVvBhpcM2AVhkw= +20251111111017.sql h1:qrJ93dNtQwcuAvpsP/lAK/H63C4cinXrsVaPmWsTqkU= diff --git a/internal/domain/main-entities/auth-partner/dto.go b/internal/domain/main-entities/auth-partner/dto.go index 2b1589fb..4a723251 100644 --- a/internal/domain/main-entities/auth-partner/dto.go +++ b/internal/domain/main-entities/auth-partner/dto.go @@ -45,7 +45,7 @@ type MetaDto struct { } type ResponseDto struct { - ecore.Main + ecore.SmallMain Code string `json:"code"` Name string `json:"name"` SecretKey string `json:"secretKey"` @@ -57,7 +57,7 @@ func (d AuthPartner) ToResponse() ResponseDto { Name: d.Name, SecretKey: d.SecretKey, } - resp.Main = d.Main + resp.SmallMain = d.SmallMain return resp } diff --git a/internal/domain/main-entities/auth-partner/entity.go b/internal/domain/main-entities/auth-partner/entity.go index b4ad8e49..3a810f51 100644 --- a/internal/domain/main-entities/auth-partner/entity.go +++ b/internal/domain/main-entities/auth-partner/entity.go @@ -5,8 +5,8 @@ import ( ) type AuthPartner struct { - ecore.Main // adjust this according to the needs - Code string `json:"code" gorm:"unique;size:50"` - Name string `json:"name" gorm:"unique;size:100"` - SecretKey string `json:"secretKey" gorm:"size:255"` + ecore.SmallMain // adjust this according to the needs + Code string `json:"code" gorm:"unique;size:50"` + Name string `json:"name" gorm:"unique;size:100"` + SecretKey string `json:"secretKey" gorm:"size:255"` } diff --git a/internal/domain/main-entities/user-fes/entity.go b/internal/domain/main-entities/user-fes/entity.go index 7356166c..5261ed62 100644 --- a/internal/domain/main-entities/user-fes/entity.go +++ b/internal/domain/main-entities/user-fes/entity.go @@ -9,9 +9,12 @@ import ( type UserFes struct { ecore.Main // adjust this according to the needs - Name string `json:"name" gorm:"size:100"` - AuthPartner_Code string `json:"authPartner_code" gorm:"size:30"` + Name string `json:"name" gorm:"size:100;uniqueIndex:idx-userFes-name-authPartner_code"` + AuthPartner_Code string `json:"authPartner_code" gorm:"size:50;uniqueIndex:idx-userFes-name-authPartner_code"` AuthPartner *eap.AuthPartner `json:"authPartner,omitempty" gorm:"foreignKey:AuthPartner_Code;references:Code"` - User_Name string `json:"user_name" gorm:"size:50"` - User *eau.User `json:"user,omitempty" gorm:"foreignKey:User_Name;references:Name"` + // Even tried this one, still failed to preload + // AuthPartner_Id uint16 `json:"authPartner_id"` + // AuthPartner *eap.AuthPartner `json:"authPartner,omitempty" gorm:"foreignKey:AuthPartner_Id;references:Id"` + User_Name string `json:"user_name" gorm:"size:50"` + User *eau.User `json:"user,omitempty" gorm:"foreignKey:User_Name;references:Name"` } From a552e51fa7506fae96aa254d24fc14c5f4dca9ae Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Thu, 13 Nov 2025 17:27:59 +0700 Subject: [PATCH 09/18] feat/prescription: updated entities --- .../migrations/20251113101344.sql | 11 +++ cmd/main-migration/migrations/atlas.sum | 3 +- .../domain/main-entities/medicine-form/dto.go | 67 +++++++++++++++++++ .../main-entities/medicine-form/entity.go | 11 +++ internal/interface/migration/main-entities.go | 2 + 5 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 cmd/main-migration/migrations/20251113101344.sql create mode 100644 internal/domain/main-entities/medicine-form/dto.go create mode 100644 internal/domain/main-entities/medicine-form/entity.go diff --git a/cmd/main-migration/migrations/20251113101344.sql b/cmd/main-migration/migrations/20251113101344.sql new file mode 100644 index 00000000..cda3abed --- /dev/null +++ b/cmd/main-migration/migrations/20251113101344.sql @@ -0,0 +1,11 @@ +-- Create "MedicineForm" table +CREATE TABLE "public"."MedicineForm" ( + "Id" serial NOT NULL, + "CreatedAt" timestamptz NULL, + "UpdatedAt" timestamptz NULL, + "DeletedAt" timestamptz NULL, + "Code" character varying(20) NULL, + "Name" character varying(50) NULL, + PRIMARY KEY ("Id"), + CONSTRAINT "uni_MedicineForm_Code" UNIQUE ("Code") +); diff --git a/cmd/main-migration/migrations/atlas.sum b/cmd/main-migration/migrations/atlas.sum index 8c3dedc2..12fe7e83 100644 --- a/cmd/main-migration/migrations/atlas.sum +++ b/cmd/main-migration/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:eTC10wvsOLnENRLwgViQvRs0NYu3w94Ah1fOaGCqFEg= +h1:6WOy+P2ZTzKcl82qh91tJo8GQlP7SQq7ijFoGqOnC14= 20250904105930.sql h1:MEM6blCgke9DzWQSTnLzasbPIrcHssNNrJqZpSkEo6k= 20250904141448.sql h1:J8cmYNk4ZrG9fhfbi2Z1IWz7YkfvhFqTzrLFo58BPY0= 20250908062237.sql h1:Pu23yEW/aKkwozHoOuROvHS/GK4ngARJGdO7FB7HZuI= @@ -115,3 +115,4 @@ h1:eTC10wvsOLnENRLwgViQvRs0NYu3w94Ah1fOaGCqFEg= 20251111074652.sql h1:ddfQ/sRKMezPM75xBFTGytUQX5AwZ3znrJVpg73gKPA= 20251111082257.sql h1:ZsdLY1ROouos0l3oS0lkeSiuKLEUGbVvBhpcM2AVhkw= 20251111111017.sql h1:qrJ93dNtQwcuAvpsP/lAK/H63C4cinXrsVaPmWsTqkU= +20251113101344.sql h1:oG4MVNBSZ5CiFS3CfhoLr5oqBFIGwhLo+QiVcZ6W25A= diff --git a/internal/domain/main-entities/medicine-form/dto.go b/internal/domain/main-entities/medicine-form/dto.go new file mode 100644 index 00000000..a7145a2f --- /dev/null +++ b/internal/domain/main-entities/medicine-form/dto.go @@ -0,0 +1,67 @@ +package medicineform + +import ( + ecore "simrs-vx/internal/domain/base-entities/core" +) + +type CreateDto struct { + Code *string `json:"code" validate:"maxLength=20"` + Name string `json:"name" validate:"maxLength=50"` +} + +type ReadListDto struct { + FilterDto + Includes string `json:"includes"` + Sort string `json:"sort"` + Pagination ecore.Pagination +} + +type FilterDto struct { + Code string `json:"code"` + Name string `json:"name"` + Search string `json:"search" gormhelper:"searchColumns=Code,Name"` +} + +type ReadDetailDto struct { + Id *uint16 `json:"id"` + Code *string `json:"code"` +} + +type UpdateDto struct { + Id *uint `json:"id"` + CreateDto +} + +type DeleteDto struct { + Id *uint `json:"id"` + Code *string `json:"code"` +} + +type MetaDto struct { + PageNumber int `json:"page_number"` + PageSize int `json:"page_size"` + Count int `json:"count"` +} + +type ResponseDto struct { + ecore.SmallMain + Code string `json:"code"` + Name string `json:"name"` +} + +func (d MedicineForm) ToResponse() ResponseDto { + resp := ResponseDto{ + Code: d.Code, + Name: d.Name, + } + resp.SmallMain = d.SmallMain + return resp +} + +func ToResponseList(data []MedicineForm) []ResponseDto { + resp := make([]ResponseDto, len(data)) + for i, u := range data { + resp[i] = u.ToResponse() + } + return resp +} diff --git a/internal/domain/main-entities/medicine-form/entity.go b/internal/domain/main-entities/medicine-form/entity.go new file mode 100644 index 00000000..75b1b104 --- /dev/null +++ b/internal/domain/main-entities/medicine-form/entity.go @@ -0,0 +1,11 @@ +package medicineform + +import ( + ecore "simrs-vx/internal/domain/base-entities/core" +) + +type MedicineForm struct { + ecore.SmallMain // adjust this according to the needs + Code string `json:"code" gorm:"unique;size:20"` + Name string `json:"name" gorm:"size:50"` +} diff --git a/internal/interface/migration/main-entities.go b/internal/interface/migration/main-entities.go index 272cd4ce..94b5d545 100644 --- a/internal/interface/migration/main-entities.go +++ b/internal/interface/migration/main-entities.go @@ -57,6 +57,7 @@ import ( medicationitem "simrs-vx/internal/domain/main-entities/medication-item" medicationitemdist "simrs-vx/internal/domain/main-entities/medication-item-dist" medicine "simrs-vx/internal/domain/main-entities/medicine" + medicineform "simrs-vx/internal/domain/main-entities/medicine-form" medicinegroup "simrs-vx/internal/domain/main-entities/medicine-group" medicinemethod "simrs-vx/internal/domain/main-entities/medicine-method" medicinemix "simrs-vx/internal/domain/main-entities/medicine-mix" @@ -145,6 +146,7 @@ func getMainEntities() []any { ðnic.Ethnic{}, &insurancecompany.InsuranceCompany{}, &medicine.Medicine{}, + &medicineform.MedicineForm{}, &medicinemix.MedicineMix{}, &medicinemixitem.MedicineMixItem{}, &medicalactionsrc.MedicalActionSrc{}, From 403c085d56fa209ddb34ac0f5993ca1c028d5d62 Mon Sep 17 00:00:00 2001 From: vanilia Date: Thu, 13 Nov 2025 17:31:49 +0700 Subject: [PATCH 10/18] revise m_instalasi --- internal/domain/simgos-entities/installation/entity.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/domain/simgos-entities/installation/entity.go b/internal/domain/simgos-entities/installation/entity.go index d620af5e..faf2786e 100644 --- a/internal/domain/simgos-entities/installation/entity.go +++ b/internal/domain/simgos-entities/installation/entity.go @@ -1,10 +1,10 @@ package installation type MInstalasi struct { - NoInstalasi uint `json:"no_instalasi"` - NamaInstalasi string `json:"nama_instalasi"` - StatusRawatInap uint `json:"status_rawat_inap"` - StAktif uint `json:"st_aktif"` + No_Instalasi uint `json:"no_instalasi" gorm:"primaryKey;autoIncrement"` + Nama_Instalasi string `json:"nama_instalasi"` + Status_Rawat_Inap uint `json:"status_rawat_inap"` + St_Aktif uint `json:"st_aktif"` } func (MInstalasi) TableName() string { From 50b2c7b781adbf89eb11f42ebe52e9b56877ea4c Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Thu, 13 Nov 2025 19:07:04 +0700 Subject: [PATCH 11/18] feat/prescription: relating MedicineForm --- cmd/main-migration/migrations/20251113120533.sql | 2 ++ cmd/main-migration/migrations/atlas.sum | 5 +++-- internal/domain/main-entities/medicine/entity.go | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 cmd/main-migration/migrations/20251113120533.sql diff --git a/cmd/main-migration/migrations/20251113120533.sql b/cmd/main-migration/migrations/20251113120533.sql new file mode 100644 index 00000000..b8d56fe8 --- /dev/null +++ b/cmd/main-migration/migrations/20251113120533.sql @@ -0,0 +1,2 @@ +-- Modify "Medicine" table +ALTER TABLE "public"."Medicine" ADD COLUMN "MedicineForm_Code" character varying(20) NULL, ADD CONSTRAINT "fk_Medicine_MedicineForm" FOREIGN KEY ("MedicineForm_Code") REFERENCES "public"."MedicineForm" ("Code") ON UPDATE NO ACTION ON DELETE NO ACTION; diff --git a/cmd/main-migration/migrations/atlas.sum b/cmd/main-migration/migrations/atlas.sum index 12fe7e83..1c172e0c 100644 --- a/cmd/main-migration/migrations/atlas.sum +++ b/cmd/main-migration/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:6WOy+P2ZTzKcl82qh91tJo8GQlP7SQq7ijFoGqOnC14= +h1:RvRW5FTVb8Zdn/J5rSwIrXB7cbCmWJBNaVsxqArNl4E= 20250904105930.sql h1:MEM6blCgke9DzWQSTnLzasbPIrcHssNNrJqZpSkEo6k= 20250904141448.sql h1:J8cmYNk4ZrG9fhfbi2Z1IWz7YkfvhFqTzrLFo58BPY0= 20250908062237.sql h1:Pu23yEW/aKkwozHoOuROvHS/GK4ngARJGdO7FB7HZuI= @@ -115,4 +115,5 @@ h1:6WOy+P2ZTzKcl82qh91tJo8GQlP7SQq7ijFoGqOnC14= 20251111074652.sql h1:ddfQ/sRKMezPM75xBFTGytUQX5AwZ3znrJVpg73gKPA= 20251111082257.sql h1:ZsdLY1ROouos0l3oS0lkeSiuKLEUGbVvBhpcM2AVhkw= 20251111111017.sql h1:qrJ93dNtQwcuAvpsP/lAK/H63C4cinXrsVaPmWsTqkU= -20251113101344.sql h1:oG4MVNBSZ5CiFS3CfhoLr5oqBFIGwhLo+QiVcZ6W25A= +20251113101344.sql h1:xaOZvAUP1fFfnO+syEFOzJUIg5lTfBe5AWHPbBWuCLA= +20251113120533.sql h1:MOk7YBwVLRCoqgPrC/mQAarp5WRl4aTk6NAzIoQPMNc= diff --git a/internal/domain/main-entities/medicine/entity.go b/internal/domain/main-entities/medicine/entity.go index dda220bc..0a79e7e5 100644 --- a/internal/domain/main-entities/medicine/entity.go +++ b/internal/domain/main-entities/medicine/entity.go @@ -4,6 +4,7 @@ import ( ecore "simrs-vx/internal/domain/base-entities/core" ein "simrs-vx/internal/domain/main-entities/infra" eit "simrs-vx/internal/domain/main-entities/item" + emf "simrs-vx/internal/domain/main-entities/medicine-form" emg "simrs-vx/internal/domain/main-entities/medicine-group" emm "simrs-vx/internal/domain/main-entities/medicine-method" eu "simrs-vx/internal/domain/main-entities/uom" @@ -17,6 +18,8 @@ type Medicine struct { MedicineGroup *emg.MedicineGroup `json:"medicineGroup,omitempty" gorm:"foreignKey:MedicineGroup_Code;references:Code"` MedicineMethod_Code *string `json:"medicineMethod_code" gorm:"size:10"` MedicineMethod *emm.MedicineMethod `json:"medicineMethod,omitempty" gorm:"foreignKey:MedicineMethod_Code;references:Code"` + MedicineForm_Code *string `json:"medicineForm_code" gorm:"size:20"` + MedicineForm *emf.MedicineForm `json:"medicineForm,omitempty" gorm:"foreignKey:MedicineForm_Code;references:Code"` Uom_Code *string `json:"uom_code" gorm:"size:10"` Uom *eu.Uom `json:"uom" gorm:"foreignKey:Uom_Code;references:Code"` Dose uint8 `json:"dose"` From 435d3512fe93319d8b22c12743c4567efdef4d30 Mon Sep 17 00:00:00 2001 From: vanilia Date: Thu, 13 Nov 2025 21:01:03 +0700 Subject: [PATCH 12/18] revise instalasi entity --- cmd/simgos-sync-migration/migrations/atlas.sum | 4 ++-- internal/domain/simgos-entities/installation/entity.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/simgos-sync-migration/migrations/atlas.sum b/cmd/simgos-sync-migration/migrations/atlas.sum index 50f3c7d3..f1405dd2 100644 --- a/cmd/simgos-sync-migration/migrations/atlas.sum +++ b/cmd/simgos-sync-migration/migrations/atlas.sum @@ -1,2 +1,2 @@ -h1:OaRZyFJMii/8wQ0XLWEpF9MIL8IXblx3IYvB7LN2XQ4= -20251113035508.sql h1:p/FKefphuiBjEND87ChQLS/lcZcnvn0aT5tRV43Dn5o= +h1:8jMmMBxSEls9jaOUrpQQV0wUPlORRwJWd5g9742Z2fQ= +20251113035508.sql h1:rjDlu6yDdy5xv6nrCOr7NialrLSLT23pzduYNq29Hf0= diff --git a/internal/domain/simgos-entities/installation/entity.go b/internal/domain/simgos-entities/installation/entity.go index faf2786e..f2b291f5 100644 --- a/internal/domain/simgos-entities/installation/entity.go +++ b/internal/domain/simgos-entities/installation/entity.go @@ -1,10 +1,10 @@ package installation type MInstalasi struct { - No_Instalasi uint `json:"no_instalasi" gorm:"primaryKey;autoIncrement"` - Nama_Instalasi string `json:"nama_instalasi"` - Status_Rawat_Inap uint `json:"status_rawat_inap"` - St_Aktif uint `json:"st_aktif"` + No_Instalasi uint `json:"no_instalasi" gorm:"primaryKey;autoIncrement;column:no_instalasi"` + Nama_Instalasi string `json:"nama_instalasi" gorm:"column:nama_instalasi"` + Status_Rawat_Inap uint `json:"status_rawat_inap" gorm:"column:status_rawat_inap"` + St_Aktif uint `json:"st_aktif" gorm:"column:st_aktif"` } func (MInstalasi) TableName() string { From d3ddebb2264b15a73e2350e7ceeff1105a759b10 Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Fri, 14 Nov 2025 08:39:01 +0700 Subject: [PATCH 13/18] feat/prescription: added MedicineForm flow --- .../interface/main-handler/main-handler.go | 3 + .../main-handler/medicine-form/handler.go | 71 +++++ .../main-use-case/medicine-form/case.go | 276 ++++++++++++++++++ .../main-use-case/medicine-form/helper.go | 22 ++ .../main-use-case/medicine-form/lib.go | 147 ++++++++++ .../medicine-form/middleware-runner.go | 103 +++++++ .../main-use-case/medicine-form/middleware.go | 9 + .../main-use-case/medicine-form/tycovar.go | 44 +++ 8 files changed, 675 insertions(+) create mode 100644 internal/interface/main-handler/medicine-form/handler.go create mode 100644 internal/use-case/main-use-case/medicine-form/case.go create mode 100644 internal/use-case/main-use-case/medicine-form/helper.go create mode 100644 internal/use-case/main-use-case/medicine-form/lib.go create mode 100644 internal/use-case/main-use-case/medicine-form/middleware-runner.go create mode 100644 internal/use-case/main-use-case/medicine-form/middleware.go create mode 100644 internal/use-case/main-use-case/medicine-form/tycovar.go diff --git a/internal/interface/main-handler/main-handler.go b/internal/interface/main-handler/main-handler.go index 04bef359..346436d0 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -91,6 +91,7 @@ import ( medicalactionsrc "simrs-vx/internal/interface/main-handler/medical-action-src" medicalactionsrcitem "simrs-vx/internal/interface/main-handler/medical-action-src-item" medicine "simrs-vx/internal/interface/main-handler/medicine" + medicineform "simrs-vx/internal/interface/main-handler/medicine-form" medicinegroup "simrs-vx/internal/interface/main-handler/medicine-group" medicinemethod "simrs-vx/internal/interface/main-handler/medicine-method" pharmacycompany "simrs-vx/internal/interface/main-handler/pharmacy-company" @@ -140,6 +141,7 @@ func SetRoutes() http.Handler { hc.RegCrud(r, "/v1/auth-partner", authpartner.O) hc.RegCrud(r, "/v1/practice-schedule", practiceschedule.O) hc.RegCrud(r, "/v1/counter", counter.O) + hc.RegCrudByCode(r, "/v1/medicine-form", medicineform.O) hc.RegCrud(r, "/v1/medicine-mix", medicicinemix.O) hc.RegCrud(r, "/v1/medicine-mix-item", medicicinemixitem.O) hc.RegCrud(r, "/v1/soapi", auth.GuardMW, soapi.O) @@ -188,6 +190,7 @@ func SetRoutes() http.Handler { "POST /": prescription.O.Create, "PATCH /{id}": prescription.O.Update, "DELETE /{id}": prescription.O.Delete, + "PATCH /{id}/submit": prescription.O.Submit, "PATCH /{id}/approve": prescription.O.Approve, }) hk.GroupRoutes("/v1/mcu-order-sub-item", r, auth.GuardMW, hk.MapHandlerFunc{ diff --git a/internal/interface/main-handler/medicine-form/handler.go b/internal/interface/main-handler/medicine-form/handler.go new file mode 100644 index 00000000..a363fb30 --- /dev/null +++ b/internal/interface/main-handler/medicine-form/handler.go @@ -0,0 +1,71 @@ +package medicineform + +import ( + "net/http" + + rw "github.com/karincake/risoles" + sf "github.com/karincake/semprit" + + // ua "github.com/karincake/tumpeng/auth/svc" + + e "simrs-vx/internal/domain/main-entities/medicine-form" + u "simrs-vx/internal/use-case/main-use-case/medicine-form" +) + +type myBase struct{} + +var O myBase + +func (obj myBase) Create(w http.ResponseWriter, r *http.Request) { + dto := e.CreateDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + res, err := u.Create(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) GetList(w http.ResponseWriter, r *http.Request) { + dto := e.ReadListDto{} + sf.UrlQueryParam(&dto, *r.URL) + res, err := u.ReadList(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) GetDetail(w http.ResponseWriter, r *http.Request) { + code := rw.ValidateString(w, "code", r.PathValue("code")) + if code == "" { + return + } + dto := e.ReadDetailDto{} + dto.Code = &code + res, err := u.ReadDetail(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) Update(w http.ResponseWriter, r *http.Request) { + code := rw.ValidateString(w, "code", r.PathValue("code")) + if code == "" { + return + } + + dto := e.UpdateDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + dto.Code = &code + res, err := u.Update(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) Delete(w http.ResponseWriter, r *http.Request) { + code := rw.ValidateString(w, "code", r.PathValue("code")) + if code == "" { + return + } + + dto := e.DeleteDto{} + dto.Code = &code + res, err := u.Delete(dto) + rw.DataResponse(w, res, err) +} diff --git a/internal/use-case/main-use-case/medicine-form/case.go b/internal/use-case/main-use-case/medicine-form/case.go new file mode 100644 index 00000000..6eb0fb75 --- /dev/null +++ b/internal/use-case/main-use-case/medicine-form/case.go @@ -0,0 +1,276 @@ +package medicineform + +import ( + e "simrs-vx/internal/domain/main-entities/medicine-form" + "strconv" + + dg "github.com/karincake/apem/db-gorm-pg" + d "github.com/karincake/dodol" + + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + "gorm.io/gorm" +) + +const source = "medicine-form" + +func Create(input e.CreateDto) (*d.Data, error) { + data := e.MedicineForm{} + + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "create") + + err := dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunCreateMiddleware(createPreMw, &input, &data); err != nil { + return err + } + + if resData, err := CreateData(input, &event, tx); err != nil { + return err + } else { + data = *resData + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunCreateMiddleware(createPostMw, &input, &data); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.II{ + "source": source, + "structure": "single-data", + "status": "created", + }, + Data: data.ToResponse(), + }, nil +} + +func ReadList(input e.ReadListDto) (*d.Data, error) { + var data *e.MedicineForm + var dataList []e.MedicineForm + var metaList *e.MetaDto + var err error + + event := pl.Event{ + Feature: "ReadList", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "readList") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunReadListMiddleware(readListPreMw, &input, data); err != nil { + return err + } + + if dataList, metaList, err = ReadListData(input, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunReadListMiddleware(readListPostMw, &input, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "list-data", + "status": "fetched", + "page_number": strconv.Itoa(metaList.PageNumber), + "page_size": strconv.Itoa(metaList.PageSize), + "record_totalCount": strconv.Itoa(metaList.Count), + "record_currentCount": strconv.Itoa(len(dataList)), + }, + Data: e.ToResponseList(dataList), + }, nil +} + +func ReadDetail(input e.ReadDetailDto) (*d.Data, error) { + var data *e.MedicineForm + var err error + + event := pl.Event{ + Feature: "ReadDetail", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "readDetail") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunReadDetailMiddleware(readDetailPreMw, &input, data); err != nil { + return err + } + + if data, err = ReadDetailData(input, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunReadDetailMiddleware(readDetailPostMw, &input, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "fetched", + }, + Data: data.ToResponse(), + }, nil +} + +func Update(input e.UpdateDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Code: input.Code} + var data *e.MedicineForm + var err error + + event := pl.Event{ + Feature: "Update", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "update") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") + if data, err = ReadDetailData(rdDto, &event, tx); err != nil { + return err + } + + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunUpdateMiddleware(readDetailPreMw, &rdDto, data); err != nil { + return err + } + + if err := UpdateData(input, data, &event, tx); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunUpdateMiddleware(readDetailPostMw, &rdDto, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "updated", + }, + Data: data.ToResponse(), + }, nil + +} + +func Delete(input e.DeleteDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Code: input.Code} + var data *e.MedicineForm + var err error + + event := pl.Event{ + Feature: "Delete", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "delete") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") + if data, err = ReadDetailData(rdDto, &event, tx); err != nil { + return err + } + + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunDeleteMiddleware(readDetailPreMw, &rdDto, data); err != nil { + return err + } + + if err := DeleteData(data, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunDeleteMiddleware(readDetailPostMw, &rdDto, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "deleted", + }, + Data: data.ToResponse(), + }, nil + +} diff --git a/internal/use-case/main-use-case/medicine-form/helper.go b/internal/use-case/main-use-case/medicine-form/helper.go new file mode 100644 index 00000000..8e80369c --- /dev/null +++ b/internal/use-case/main-use-case/medicine-form/helper.go @@ -0,0 +1,22 @@ +/* +DESCRIPTION: +Any functions that are used internally by the use-case +*/ +package medicineform + +import ( + e "simrs-vx/internal/domain/main-entities/medicine-form" +) + +func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.MedicineForm) { + var inputSrc *e.CreateDto + if inputT, ok := any(input).(*e.CreateDto); ok { + inputSrc = inputT + } else { + inputTemp := any(input).(*e.UpdateDto) + inputSrc = &inputTemp.CreateDto + } + + data.Code = *inputSrc.Code + data.Name = inputSrc.Name +} diff --git a/internal/use-case/main-use-case/medicine-form/lib.go b/internal/use-case/main-use-case/medicine-form/lib.go new file mode 100644 index 00000000..01d570a2 --- /dev/null +++ b/internal/use-case/main-use-case/medicine-form/lib.go @@ -0,0 +1,147 @@ +package medicineform + +import ( + e "simrs-vx/internal/domain/main-entities/medicine-form" + + plh "simrs-vx/pkg/lib-helper" + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + dg "github.com/karincake/apem/db-gorm-pg" + gh "github.com/karincake/getuk" + "gorm.io/gorm" +) + +func CreateData(input e.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*e.MedicineForm, error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + data := e.MedicineForm{} + setData(&input, &data) + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.Create(&data).Error; err != nil { + return nil, plh.HandleCreateError(input, event, err) + } + + pl.SetLogInfo(event, nil, "complete") + return &data, nil +} + +func ReadListData(input e.ReadListDto, event *pl.Event, dbx ...*gorm.DB) ([]e.MedicineForm, *e.MetaDto, error) { + pl.SetLogInfo(event, input, "started", "DBReadList") + data := []e.MedicineForm{} + pagination := gh.Pagination{} + count := int64(0) + meta := e.MetaDto{} + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + tx = tx. + Model(&e.MedicineForm{}). + Scopes(gh.Preload(input.Includes)). + Scopes(gh.Filter(input.FilterDto)). + Count(&count). + Scopes(gh.Paginate(input, &pagination)). + Scopes(gh.Sort(input.Sort)) + + if err := tx.Find(&data).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, &meta, nil + } + return nil, nil, plh.HandleListError(input, event, err) + } + + meta.Count = int(count) + meta.PageNumber = pagination.PageNumber + meta.PageSize = pagination.PageSize + + pl.SetLogInfo(event, nil, "complete") + return data, &meta, nil +} + +func ReadDetailData(input e.ReadDetailDto, event *pl.Event, dbx ...*gorm.DB) (*e.MedicineForm, error) { + pl.SetLogInfo(event, input, "started", "DBReadDetail") + data := e.MedicineForm{} + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if input.Code != nil { + tx = tx.Where("\"Code\" = ?", *input.Code) + } + if input.Id != nil { + tx = tx.Where("\"Id\" = ?", input.Id) + } + + if err := tx.First(&data).Error; err != nil { + if processedErr := pu.HandleReadError(err, event, source, input.Id, data); processedErr != nil { + return nil, processedErr + } + } + + pl.SetLogInfo(event, nil, "complete") + return &data, nil +} + +func UpdateData(input e.UpdateDto, data *e.MedicineForm, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, data, "started", "DBUpdate") + setData(&input, data) + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.Save(&data).Error; err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-update-fail", + Detail: "Database update failed", + Raw: err, + } + return pl.SetLogError(event, input) + } + + pl.SetLogInfo(event, nil, "complete") + return nil +} + +func DeleteData(data *e.MedicineForm, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, data, "started", "DBDelete") + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.Delete(&data).Error; err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-delete-fail", + Detail: "Database delete failed", + Raw: err, + } + return pl.SetLogError(event, data) + } + + pl.SetLogInfo(event, nil, "complete") + return nil +} diff --git a/internal/use-case/main-use-case/medicine-form/middleware-runner.go b/internal/use-case/main-use-case/medicine-form/middleware-runner.go new file mode 100644 index 00000000..9b22189b --- /dev/null +++ b/internal/use-case/main-use-case/medicine-form/middleware-runner.go @@ -0,0 +1,103 @@ +package medicineform + +import ( + e "simrs-vx/internal/domain/main-entities/medicine-form" + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + "gorm.io/gorm" +) + +type middlewareRunner struct { + Event *pl.Event + Tx *gorm.DB + MwType pu.MWType +} + +// NewMiddlewareExecutor creates a new middleware executor +func newMiddlewareRunner(event *pl.Event, tx *gorm.DB) *middlewareRunner { + return &middlewareRunner{ + Event: event, + Tx: tx, + } +} + +// ExecuteCreateMiddleware executes create middleware +func (me *middlewareRunner) RunCreateMiddleware(middlewares []createMw, input *e.CreateDto, data *e.MedicineForm) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunReadListMiddleware(middlewares []readListMw, input *e.ReadListDto, data *e.MedicineForm) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunReadDetailMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.MedicineForm) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunUpdateMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.MedicineForm) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunDeleteMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.MedicineForm) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) setMwType(mwType pu.MWType) { + me.MwType = mwType +} diff --git a/internal/use-case/main-use-case/medicine-form/middleware.go b/internal/use-case/main-use-case/medicine-form/middleware.go new file mode 100644 index 00000000..b4388010 --- /dev/null +++ b/internal/use-case/main-use-case/medicine-form/middleware.go @@ -0,0 +1,9 @@ +package medicineform + +// example of middleware +// func init() { +// createPreMw = append(createPreMw, +// CreateMw{Name: "modif-input", Func: pm.ModifInput}, +// CreateMw{Name: "check-data", Func: pm.CheckData}, +// ) +// } diff --git a/internal/use-case/main-use-case/medicine-form/tycovar.go b/internal/use-case/main-use-case/medicine-form/tycovar.go new file mode 100644 index 00000000..8dbd3509 --- /dev/null +++ b/internal/use-case/main-use-case/medicine-form/tycovar.go @@ -0,0 +1,44 @@ +/* +DESCRIPTION: +A sample, part of the package that contains type, constants, and/or variables. + +In this sample it also provides type and variable regarding the needs of the +middleware to separate from main use-case which has the basic CRUD +functionality. The purpose of this is to make the code more maintainable. +*/ +package medicineform + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/medicine-form" +) + +type createMw struct { + Name string + Func func(input *e.CreateDto, data *e.MedicineForm, tx *gorm.DB) error +} + +type readListMw struct { + Name string + Func func(input *e.ReadListDto, data *e.MedicineForm, tx *gorm.DB) error +} + +type readDetailMw struct { + Name string + Func func(input *e.ReadDetailDto, data *e.MedicineForm, tx *gorm.DB) error +} + +type UpdateMw = readDetailMw +type DeleteMw = readDetailMw + +var createPreMw []createMw // preprocess middleware +var createPostMw []createMw // postprocess middleware +var readListPreMw []readListMw // .. +var readListPostMw []readListMw // .. +var readDetailPreMw []readDetailMw +var readDetailPostMw []readDetailMw +var updatePreMw []readDetailMw +var updatePostMw []readDetailMw +var deletePreMw []readDetailMw +var deletePostMw []readDetailMw From 9d26054e1b6bd3058670778515d4c9450e7378e8 Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Fri, 14 Nov 2025 08:39:50 +0700 Subject: [PATCH 14/18] feat/prescription: more flow on medicine --- internal/domain/main-entities/medicine/dto.go | 5 +++++ internal/use-case/main-use-case/medicine/lib.go | 2 ++ 2 files changed, 7 insertions(+) diff --git a/internal/domain/main-entities/medicine/dto.go b/internal/domain/main-entities/medicine/dto.go index 945db1e5..71d41d42 100644 --- a/internal/domain/main-entities/medicine/dto.go +++ b/internal/domain/main-entities/medicine/dto.go @@ -4,6 +4,7 @@ import ( ecore "simrs-vx/internal/domain/base-entities/core" ein "simrs-vx/internal/domain/main-entities/infra" eit "simrs-vx/internal/domain/main-entities/item" + emf "simrs-vx/internal/domain/main-entities/medicine-form" emg "simrs-vx/internal/domain/main-entities/medicine-group" emm "simrs-vx/internal/domain/main-entities/medicine-method" eu "simrs-vx/internal/domain/main-entities/uom" @@ -71,6 +72,8 @@ type ResponseDto struct { MedicineGroup *emg.MedicineGroup `json:"medicineGroup"` MedicineMethod_Code *string `json:"medicineMethod_code"` MedicineMethod *emm.MedicineMethod `json:"medicineMethod"` + MedicineForm_Code *string `json:"medicineForm_code"` + MedicineForm *emf.MedicineForm `json:"medicineForm"` Uom_Code *string `json:"uom_code"` Uom *eu.Uom `json:"uom"` Dose uint8 `json:"dose"` @@ -89,6 +92,8 @@ func (d Medicine) ToResponse() ResponseDto { MedicineGroup: d.MedicineGroup, MedicineMethod_Code: d.MedicineMethod_Code, MedicineMethod: d.MedicineMethod, + MedicineForm_Code: d.MedicineForm_Code, + MedicineForm: d.MedicineForm, Uom_Code: d.Uom_Code, Uom: d.Uom, Dose: d.Dose, diff --git a/internal/use-case/main-use-case/medicine/lib.go b/internal/use-case/main-use-case/medicine/lib.go index f9fccb2e..8dfc4457 100644 --- a/internal/use-case/main-use-case/medicine/lib.go +++ b/internal/use-case/main-use-case/medicine/lib.go @@ -1,6 +1,7 @@ package medicine import ( + "fmt" e "simrs-vx/internal/domain/main-entities/medicine" plh "simrs-vx/pkg/lib-helper" @@ -47,6 +48,7 @@ func ReadListData(input e.ReadListDto, event *pl.Event, dbx ...*gorm.DB) ([]e.Me tx = dg.I } + fmt.Println(input.Includes) tx = tx. Model(&e.Medicine{}). Scopes(gh.Preload(input.Includes)). From ae2ca4b1159f831bffa81c6a143cae838b68e31f Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Fri, 14 Nov 2025 08:40:17 +0700 Subject: [PATCH 15/18] feat/prescription: finalize Prescription --- .../main-entities/prescription-item/dto.go | 2 +- .../main-entities/prescription/entity.go | 4 ++ internal/domain/references/common/common.go | 1 + .../main-handler/prescription/handler.go | 12 ++++ .../main-use-case/prescription/case.go | 63 +++++++++++++++++++ 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/internal/domain/main-entities/prescription-item/dto.go b/internal/domain/main-entities/prescription-item/dto.go index 33e63ed2..6e871428 100644 --- a/internal/domain/main-entities/prescription-item/dto.go +++ b/internal/domain/main-entities/prescription-item/dto.go @@ -19,7 +19,7 @@ type CreateDto struct { Usage string `json:"usage"` Interval uint8 `json:"interval"` IntervalUnit_Code erc.TimeUnitCode `json:"intervalUnit_code"` - IntervalMultiplier *uint16 `json:"intervalMultiplier" validate:"required"` + IntervalMultiplier *uint16 `json:"intervalMultiplier"` Quantity float64 `json:"quantity"` } diff --git a/internal/domain/main-entities/prescription/entity.go b/internal/domain/main-entities/prescription/entity.go index 849e8083..8c879b29 100644 --- a/internal/domain/main-entities/prescription/entity.go +++ b/internal/domain/main-entities/prescription/entity.go @@ -20,6 +20,10 @@ type Prescription struct { Status_Code erc.DataStatusCode `json:"status_code"` } +func (d Prescription) IsNotNew() bool { + return d.Status_Code != erc.DSCNew +} + func (d Prescription) IsApproved() bool { return d.Status_Code == erc.DSCDone } diff --git a/internal/domain/references/common/common.go b/internal/domain/references/common/common.go index 2941049c..44a5c79b 100644 --- a/internal/domain/references/common/common.go +++ b/internal/domain/references/common/common.go @@ -58,6 +58,7 @@ const ( SCInactive ActiveStatusCode = "inactive" // Tidak aktif DSCNew DataStatusCode = "new" // Baru + DSCSubmited DataStatusCode = "submited" // Submited DSCReview DataStatusCode = "review" // Review DSCProcess DataStatusCode = "process" // Proses DSCDone DataStatusCode = "done" // Selesai diff --git a/internal/interface/main-handler/prescription/handler.go b/internal/interface/main-handler/prescription/handler.go index 24d73372..30d5998e 100644 --- a/internal/interface/main-handler/prescription/handler.go +++ b/internal/interface/main-handler/prescription/handler.go @@ -78,6 +78,18 @@ func (obj myBase) Delete(w http.ResponseWriter, r *http.Request) { rw.DataResponse(w, res, err) } +func (obj myBase) Submit(w http.ResponseWriter, r *http.Request) { + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + + dto := e.ReadDetailDto{} + dto.Id = uint(id) + res, err := u.Submit(dto) + rw.DataResponse(w, res, err) +} + func (obj myBase) Approve(w http.ResponseWriter, r *http.Request) { id := rw.ValidateInt(w, "id", r.PathValue("id")) if id <= 0 { diff --git a/internal/use-case/main-use-case/prescription/case.go b/internal/use-case/main-use-case/prescription/case.go index fa68ec44..e67ec03d 100644 --- a/internal/use-case/main-use-case/prescription/case.go +++ b/internal/use-case/main-use-case/prescription/case.go @@ -279,6 +279,69 @@ func Delete(input e.DeleteDto) (*d.Data, error) { } +func Submit(input e.ReadDetailDto) (*d.Data, error) { + var data *e.Prescription + var err error + + event := pl.Event{ + Feature: "Process", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "process") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + data, err = ReadDetailData(input, &event, tx) + if err != nil { + return err + } + + if data.IsNotNew() { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-state-mismatch", + Detail: "prescription is not in new state", + Raw: errors.New("prescription is not in new state"), + } + return pl.SetLogError(&event, input) + } + + data.Status_Code = erc.DSCSubmited + if err := tx.Save(&data).Error; err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-update-fail", + Detail: "Database update failed", + Raw: err, + } + return pl.SetLogError(&event, input) + } + + if err := createMedication(input.Id, &event, tx); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "submited", + }, + Data: data.ToResponse(), + }, nil + +} + func Approve(input e.ReadDetailDto) (*d.Data, error) { var data *e.Prescription var err error From 05682754b5240107455c981dab50ee6c338dfc67 Mon Sep 17 00:00:00 2001 From: vanilia Date: Fri, 14 Nov 2025 10:38:00 +0700 Subject: [PATCH 16/18] add constant upload --- internal/domain/references/upload/upload.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/internal/domain/references/upload/upload.go b/internal/domain/references/upload/upload.go index 603be6c8..b22960f4 100644 --- a/internal/domain/references/upload/upload.go +++ b/internal/domain/references/upload/upload.go @@ -8,13 +8,16 @@ type ( ) const ( - UCPRN UploadCode = "person-resident-number" // Person Resident Number - UCPDL UploadCode = "person-driver-license" // Person Driver License - UCPP UploadCode = "person-passport" // Person Passport - UCPFC UploadCode = "person-family-card" // Person Family Card - UCMIR UploadCode = "mcu-item-result" // Mcu Item Result - UCSEP UploadCode = "vclaim-sep" // SEP - UCSIPP UploadCode = "vclaim-sipp" // SIPP + UCPRN UploadCode = "person-resident-number" // Person Resident Number + UCPDL UploadCode = "person-driver-license" // Person Driver License + UCPP UploadCode = "person-passport" // Person Passport + UCPFC UploadCode = "person-family-card" // Person Family Card + UCMIR UploadCode = "mcu-item-result" // Mcu Item Result + UCEnPatient UploadCode = "encounter-patient" + UCEnSupport UploadCode = "encounter-support" + UcEnOther UploadCode = "encounter-other" + UCSEP UploadCode = "vclaim-sep" // SEP + UCSIPP UploadCode = "vclaim-sipp" // SIPP ETCPerson EntityTypeCode = "person" ETCEncounter EntityTypeCode = "encounter" @@ -26,7 +29,7 @@ var validUploadCodesByEntity = map[EntityTypeCode][]UploadCode{ UCPRN, UCPDL, UCPP, UCPFC, }, ETCEncounter: { - UCSEP, UCSIPP, + UCSEP, UCSIPP, UCEnPatient, UCEnSupport, UcEnOther, }, ETCMCU: { UCMIR, From 12108e11764cc299388650a10dea8493dbd93357 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Fri, 14 Nov 2025 13:43:27 +0700 Subject: [PATCH 17/18] add status code therapy protocol --- .../migrations/20251114062746.sql | 2 ++ cmd/main-migration/migrations/atlas.sum | 5 ++-- .../main-entities/therapy-protocol/entity.go | 25 ++++++++++--------- 3 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 cmd/main-migration/migrations/20251114062746.sql diff --git a/cmd/main-migration/migrations/20251114062746.sql b/cmd/main-migration/migrations/20251114062746.sql new file mode 100644 index 00000000..cdcfb350 --- /dev/null +++ b/cmd/main-migration/migrations/20251114062746.sql @@ -0,0 +1,2 @@ +-- Modify "TherapyProtocol" table +ALTER TABLE "public"."TherapyProtocol" ADD COLUMN "Status_Code" character varying(10) NULL; diff --git a/cmd/main-migration/migrations/atlas.sum b/cmd/main-migration/migrations/atlas.sum index 1c172e0c..d2484ac6 100644 --- a/cmd/main-migration/migrations/atlas.sum +++ b/cmd/main-migration/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:RvRW5FTVb8Zdn/J5rSwIrXB7cbCmWJBNaVsxqArNl4E= +h1:ZxTxK8LgNDXKTGBTF9olozXM0T7DDjK1+OJiwUwomeY= 20250904105930.sql h1:MEM6blCgke9DzWQSTnLzasbPIrcHssNNrJqZpSkEo6k= 20250904141448.sql h1:J8cmYNk4ZrG9fhfbi2Z1IWz7YkfvhFqTzrLFo58BPY0= 20250908062237.sql h1:Pu23yEW/aKkwozHoOuROvHS/GK4ngARJGdO7FB7HZuI= @@ -116,4 +116,5 @@ h1:RvRW5FTVb8Zdn/J5rSwIrXB7cbCmWJBNaVsxqArNl4E= 20251111082257.sql h1:ZsdLY1ROouos0l3oS0lkeSiuKLEUGbVvBhpcM2AVhkw= 20251111111017.sql h1:qrJ93dNtQwcuAvpsP/lAK/H63C4cinXrsVaPmWsTqkU= 20251113101344.sql h1:xaOZvAUP1fFfnO+syEFOzJUIg5lTfBe5AWHPbBWuCLA= -20251113120533.sql h1:MOk7YBwVLRCoqgPrC/mQAarp5WRl4aTk6NAzIoQPMNc= +20251113120533.sql h1:f3/U1Ve2yF2zSMhkt+xtwF8wUYfUKYwgbNeGfE37EW4= +20251114062746.sql h1:4ypWL0cP2AUJLVHhPKo3GAQ7uqolL6F8o4nUW32KnCI= diff --git a/internal/domain/main-entities/therapy-protocol/entity.go b/internal/domain/main-entities/therapy-protocol/entity.go index 7caaf0a6..1378eb25 100644 --- a/internal/domain/main-entities/therapy-protocol/entity.go +++ b/internal/domain/main-entities/therapy-protocol/entity.go @@ -16,16 +16,17 @@ type TherapyProtocol struct { Doctor_Code *string `json:"doctor_code"` Doctor *ed.Doctor `json:"doctor,omitempty" gorm:"foreignKey:Doctor_Code;references:Code"` - Anamnesis *string `json:"anamnesis" gorm:"size:2048"` - MedicalDiagnoses *string `json:"medicalDiagnoses"` - FunctionDiagnoses *string `json:"functionDiagnoses"` - Procedures *string `json:"procedures"` - SupportingExams *string `json:"supportingExams" gorm:"size:2048"` - Instruction *string `json:"instruction" gorm:"size:2048"` - Evaluation *string `json:"evaluation" gorm:"size:2048"` - WorkCauseStatus *string `json:"workCauseStatus" gorm:"size:2048"` - Frequency *uint `json:"frequency"` - IntervalUnit_Code *common.TimeUnitCode `json:"intervalUnit_code" gorm:"size:10"` - Duration *uint `json:"duration"` - DurationUnit_Code *common.TimeUnitCode `json:"durationUnit_code" gorm:"size:10"` + Anamnesis *string `json:"anamnesis" gorm:"size:2048"` + MedicalDiagnoses *string `json:"medicalDiagnoses"` + FunctionDiagnoses *string `json:"functionDiagnoses"` + Procedures *string `json:"procedures"` + SupportingExams *string `json:"supportingExams" gorm:"size:2048"` + Instruction *string `json:"instruction" gorm:"size:2048"` + Evaluation *string `json:"evaluation" gorm:"size:2048"` + WorkCauseStatus *string `json:"workCauseStatus" gorm:"size:2048"` + Frequency *uint `json:"frequency"` + IntervalUnit_Code *common.TimeUnitCode `json:"intervalUnit_code" gorm:"size:10"` + Duration *uint `json:"duration"` + DurationUnit_Code *common.TimeUnitCode `json:"durationUnit_code" gorm:"size:10"` + Status_Code *common.DataVerifiedCode `json:"status_code" gorm:"size:10"` } From b51606b408db18ed9e39ebac99ac69bae01f069e Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Fri, 14 Nov 2025 13:45:20 +0700 Subject: [PATCH 18/18] add makefile command --- cmd/main-migration/Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/main-migration/Makefile b/cmd/main-migration/Makefile index f445a815..a93bb244 100644 --- a/cmd/main-migration/Makefile +++ b/cmd/main-migration/Makefile @@ -16,3 +16,7 @@ apply: ## Calculate the schema hash hash: atlas migrate hash + +## Apply non-linear +apply-non-linear: + atlas migrate apply --env $(ENV) --exec-order non-linear \ No newline at end of file