From fc0d140c5cec620e2d0acb5af7944dbb00945f10 Mon Sep 17 00:00:00 2001 From: teguh nugroho Date: Mon, 17 Aug 2026 18:39:41 +0700 Subject: [PATCH] import province --- internal/handlers/dto/province.go | 26 +- internal/handlers/router/province/province.go | 116 +++++- internal/importers/province/importer.go | 84 ++++ internal/usecases/province.go | 24 +- pkg/csv/csv.go | 90 ++++ pkg/importer/importer.go | 389 ++++++++++++++++++ pkg/jobqueue/jobqueue.go | 207 ++++++++++ storage/logs/app.log | 114 +++++ storage/logs/db.log | 105 +++++ 9 files changed, 1130 insertions(+), 25 deletions(-) create mode 100644 internal/importers/province/importer.go create mode 100644 pkg/csv/csv.go create mode 100644 pkg/importer/importer.go create mode 100644 pkg/jobqueue/jobqueue.go diff --git a/internal/handlers/dto/province.go b/internal/handlers/dto/province.go index 52c5878..8187640 100644 --- a/internal/handlers/dto/province.go +++ b/internal/handlers/dto/province.go @@ -5,10 +5,34 @@ type CreateProvinceRequest struct { } type CreateProvinceResponse struct { ID string `json:"id"` - Name string `json:"name"` + Name *string `json:"name"` MerchantID *string `json:"merchant_id"` } +type ProvinceFilterAllRequest struct { + ID string `json:"id" form:"id"` + Name string `json:"name" form:"name"` + MerchantID string `json:"merchant_id" form:"merchant_id"` +} + type UpdateProvinceRequest struct { Name string `json:"name" binding:"required,min=2"` } + +type ImportLog struct { + Row int `json:"row"` + Status string `json:"status"` + Data string `json:"data"` + Message string `json:"message"` +} + +type ImportProvinceResponse struct { + JobID string `json:"job_id,omitempty"` + TotalRows int `json:"total_rows"` + Imported int `json:"imported"` + Skipped int `json:"skipped"` + Logs []ImportLog `json:"logs"` + Errors []string `json:"errors"` + IsAsync bool `json:"is_async"` + Message string `json:"message,omitempty"` +} diff --git a/internal/handlers/router/province/province.go b/internal/handlers/router/province/province.go index d39ea8a..3926280 100644 --- a/internal/handlers/router/province/province.go +++ b/internal/handlers/router/province/province.go @@ -2,11 +2,12 @@ package province import ( "cargo-erp-backend/internal/handlers/dto" + provinceimporter "cargo-erp-backend/internal/importers/province" "cargo-erp-backend/internal/usecases" "cargo-erp-backend/pkg/helpers" + "cargo-erp-backend/pkg/importer" "cargo-erp-backend/pkg/response" "net/http" - "strconv" "github.com/gin-gonic/gin" ) @@ -33,6 +34,9 @@ func (h *ProvinceHandler) Router() { r.POST("/", h.Create) r.PUT("/:id", h.Update) r.DELETE("/:id", h.Delete) + r.GET("/template", h.Template) + r.POST("/import", h.Import) + r.GET("/import/status/:jobId", h.ImportStatus) } } @@ -42,24 +46,24 @@ func (h *ProvinceHandler) GetAll(c *gin.Context) { response.Error(c, http.StatusUnauthorized, err.Error()) return } - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) - - if page < 1 { - page = 1 + var req dto.ProvinceFilterAllRequest + if err := c.ShouldBindQuery(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return } - if limit < 1 { - limit = 10 - } - u := usecases.NewProvinceUsecase(h.Helper, *userid) - provinces, total, err := u.GetAll(page, limit) + provinces, _, err := u.GetAll(req) if err != nil { response.Error(c, http.StatusInternalServerError, err.Error()) return } - - response.PaginatedResponse(c, provinces, int(total), page, limit) + var responseDTO = make([]dto.CreateProvinceResponse, 0) + for _, v := range provinces { + var DTO dto.CreateProvinceResponse + h.Helper.MapStructToStruct(v, "json", &DTO, "json") + responseDTO = append(responseDTO, DTO) + } + response.Success(c, responseDTO) } func (h *ProvinceHandler) GetList(c *gin.Context) { @@ -70,8 +74,6 @@ func (h *ProvinceHandler) GetList(c *gin.Context) { } var req dto.DataTableRequest - - // 1. Bind JSON request otomatis ke struct if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, http.StatusBadRequest, "Request Format request invalid") return @@ -153,8 +155,9 @@ func (h *ProvinceHandler) Update(c *gin.Context) { response.Error(c, http.StatusInternalServerError, err.Error()) return } - - response.Success(c, province) + var responseDTO dto.CreateProvinceResponse + h.Helper.MapStructToStruct(province, "json", &responseDTO, "json") + response.Success(c, responseDTO) } func (h *ProvinceHandler) Delete(c *gin.Context) { @@ -172,3 +175,82 @@ func (h *ProvinceHandler) Delete(c *gin.Context) { response.Success(c, map[string]interface{}{"message": "province deleted"}) } + +func (h *ProvinceHandler) Template(c *gin.Context) { + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + handler := provinceimporter.NewProvinceImportHandler(*userid) + imp := importer.NewImporter(h.Helper, handler) + imp.ServeTemplate(c.Writer) +} + +func (h *ProvinceHandler) Import(c *gin.Context) { + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + + file, header, err := c.Request.FormFile("file") + if err != nil { + response.Error(c, http.StatusBadRequest, "File is required") + return + } + defer file.Close() + + handler := provinceimporter.NewProvinceImportHandler(*userid) + imp := importer.NewImporter(h.Helper, handler) + + result, err := imp.Process(file, userid.ID, header.Filename) + if err != nil { + response.Error(c, http.StatusBadRequest, err.Error()) + return + } + + var importLogs []dto.ImportLog + for _, l := range result.Logs { + importLogs = append(importLogs, dto.ImportLog{ + Row: l.Row, + Status: l.Status, + Data: l.Data, + Message: l.Message, + }) + } + + response.Success(c, dto.ImportProvinceResponse{ + JobID: result.JobID, + TotalRows: result.TotalRows, + Imported: result.Imported, + Skipped: result.Skipped, + Logs: importLogs, + Errors: result.Errors, + IsAsync: result.IsAsync, + Message: result.Message, + }) +} + +func (h *ProvinceHandler) ImportStatus(c *gin.Context) { + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + jobId := c.Param("jobId") + + handler := provinceimporter.NewProvinceImportHandler(*userid) + imp := importer.NewImporter(h.Helper, handler) + + job, logs, err := imp.GetJobStatus(jobId) + if err != nil { + response.Error(c, http.StatusNotFound, "Job not found") + return + } + + response.Success(c, map[string]interface{}{ + "job": job, + "logs": logs, + }) +} diff --git a/internal/importers/province/importer.go b/internal/importers/province/importer.go new file mode 100644 index 0000000..944728d --- /dev/null +++ b/internal/importers/province/importer.go @@ -0,0 +1,84 @@ +package province + +import ( + "cargo-erp-backend/internal/domain" + "cargo-erp-backend/pkg/importer" + "fmt" + "strings" + "time" + + "gorm.io/gorm" +) + +type ProvinceImportHandler struct { + UserData domain.User +} + +func NewProvinceImportHandler(user_data domain.User) importer.ImportHandler { + return &ProvinceImportHandler{ + UserData: user_data, + } +} + +func (h *ProvinceImportHandler) ValidateRow(row []string, rowNumber int) (importer.RowData, []error) { + var errors []error + + if len(row) < 1 { + errors = append(errors, fmt.Errorf("row is empty")) + return nil, errors + } + + name := strings.TrimSpace(row[0]) + if name == "" { + errors = append(errors, fmt.Errorf("name is required")) + return nil, errors + } + + if len(name) < 2 { + errors = append(errors, fmt.Errorf("name must be at least 2 characters")) + return nil, errors + } + + if len(name) > 255 { + errors = append(errors, fmt.Errorf("name must be at most 255 characters")) + return nil, errors + } + + now := time.Now() + province := &domain.Province{ + MerchantID: h.UserData.MerchantID, + Name: &name, + CreatedOn: &now, + UpdatedOn: &now, + } + + return province, nil +} + +func (h *ProvinceImportHandler) GetDataString(data importer.RowData) string { + province := data.(*domain.Province) + if province.Name != nil { + return *province.Name + } + return "" +} + +func (h *ProvinceImportHandler) IsDuplicate(db *gorm.DB, data importer.RowData) bool { + province := data.(*domain.Province) + var count int64 + db.Model(&domain.Province{}).Where("UPPER(name) = ?", strings.ToUpper(*province.Name)).Count(&count) + return count > 0 +} + +func (h *ProvinceImportHandler) GetTemplateHeaders() []string { + return []string{"name"} +} + +func (h *ProvinceImportHandler) GetTemplateRows() [][]string { + return [][]string{ + {"DKI Jakarta"}, + {"Jawa Barat"}, + {"Jawa Tengah"}, + {"Jawa Timur"}, + } +} diff --git a/internal/usecases/province.go b/internal/usecases/province.go index 5b67a40..78b6b4e 100644 --- a/internal/usecases/province.go +++ b/internal/usecases/province.go @@ -5,11 +5,13 @@ import ( "cargo-erp-backend/internal/handlers/dto" "cargo-erp-backend/pkg/datatable" "cargo-erp-backend/pkg/helpers" + "fmt" + "reflect" "time" ) type ProvinceUsecaseInterface interface { - GetAll(page int, limit int) ([]domain.Province, int64, error) + GetAll(filter dto.ProvinceFilterAllRequest) ([]domain.Province, int64, error) GetList(dto.DataTableRequest) (dto.DataTableResponse, error) GetByID(id string) (domain.Province, error) Create(name string, createdBy string, merchantid *string) (domain.Province, error) @@ -26,21 +28,29 @@ func NewProvinceUsecase(helper helpers.HelperInterface, authUser domain.User) Pr return &ProvinceUsecase{Helper: helper, AuthUser: authUser} } -func (u *ProvinceUsecase) GetAll(page int, limit int) ([]domain.Province, int64, error) { +func (u *ProvinceUsecase) GetAll(filter dto.ProvinceFilterAllRequest) ([]domain.Province, int64, error) { var provinces []domain.Province var total int64 db := u.Helper.GetDB("slave") + tx := db.Model(&provinces) if u.AuthUser.UsersRole_Relation.RoleKey != "SPM" { - db.Where(db.Where("merchant_id IS NULL").Or("merchant_id=?", u.AuthUser.MerchantID)) + tx.Where(db.Where("merchant_id IS NULL").Or("merchant_id=?", u.AuthUser.MerchantID)) } - db.Model(&domain.Province{}).Count(&total) + v := reflect.ValueOf(filter) + t := reflect.TypeOf(filter) - offset := (page - 1) * limit - if err := db.Offset(offset).Limit(limit).Find(&provinces).Error; err != nil { + for i := 0; i < v.NumField(); i++ { + if v.Field(i).String() != "" { + tagKey := t.Field(i).Tag.Get("form") + tx.Where(fmt.Sprintf("%v=?", tagKey), v.Field(i)) + } + } + tx.Model(&domain.Province{}).Count(&total) + tx.Order("name ASC") + if err := tx.Find(&provinces).Error; err != nil { return nil, 0, err } - return provinces, total, nil } diff --git a/pkg/csv/csv.go b/pkg/csv/csv.go new file mode 100644 index 0000000..9ced353 --- /dev/null +++ b/pkg/csv/csv.go @@ -0,0 +1,90 @@ +package csv + +import ( + "encoding/csv" + "io" + "strings" +) + +type RowHandler func(row []string, rowNumber int) error + +func ReadCSV(file io.Reader, handler RowHandler) (int, []string, error) { + reader := csv.NewReader(file) + reader.LazyQuotes = true + reader.TrimLeadingSpace = true + + var errors []string + rowNumber := 0 + totalRows := 0 + + for { + row, err := reader.Read() + if err == io.EOF { + break + } + rowNumber++ + + if err != nil { + errors = append(errors, "Row "+itoa(rowNumber)+": "+err.Error()) + continue + } + + // Skip empty rows + if len(row) == 0 || (len(row) == 1 && strings.TrimSpace(row[0]) == "") { + continue + } + + // Skip header row (first row) + if rowNumber == 1 { + continue + } + + if err := handler(row, rowNumber); err != nil { + errors = append(errors, "Row "+itoa(rowNumber)+": "+err.Error()) + } + totalRows++ + } + + return totalRows, errors, nil +} + +func CountRows(file io.Reader) (int, error) { + reader := csv.NewReader(file) + count := 0 + for { + _, err := reader.Read() + if err == io.EOF { + break + } + if err != nil { + continue + } + count++ + } + return count, nil +} + +func WriteCSV(headers []string, rows [][]string) string { + var sb strings.Builder + writer := csv.NewWriter(&sb) + + writer.Write(headers) + for _, row := range rows { + writer.Write(row) + } + writer.Flush() + + return sb.String() +} + +func itoa(i int) string { + if i == 0 { + return "0" + } + result := "" + for i > 0 { + result = string(rune('0'+i%10)) + result + i /= 10 + } + return result +} diff --git a/pkg/importer/importer.go b/pkg/importer/importer.go new file mode 100644 index 0000000..7d7411b --- /dev/null +++ b/pkg/importer/importer.go @@ -0,0 +1,389 @@ +package importer + +import ( + csvlib "cargo-erp-backend/pkg/csv" + "cargo-erp-backend/pkg/helpers" + "cargo-erp-backend/pkg/jobqueue" + "context" + "encoding/csv" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/google/uuid" + "gorm.io/gorm" +) + +type RowData interface { + TableName() string +} + +type ImportHandler interface { + ValidateRow(row []string, rowNumber int) (RowData, []error) + IsDuplicate(db *gorm.DB, data RowData) bool + GetDataString(data RowData) string + GetTemplateHeaders() []string + GetTemplateRows() [][]string +} + +type ImportLog struct { + Row int `json:"row"` + Status string `json:"status"` + Data string `json:"data"` + Message string `json:"message"` +} + +type ImportResult struct { + JobID string `json:"job_id,omitempty"` + TotalRows int `json:"total_rows"` + Imported int `json:"imported"` + Skipped int `json:"skipped"` + Logs []ImportLog `json:"logs"` + Errors []string `json:"errors"` + IsAsync bool `json:"is_async"` + Message string `json:"message,omitempty"` +} + +type Importer struct { + Helper helpers.HelperInterface + Handler ImportHandler + JobQ *jobqueue.JobQueue +} + +func NewImporter(helper helpers.HelperInterface, handler ImportHandler) *Importer { + return &Importer{ + Helper: helper, + Handler: handler, + JobQ: jobqueue.NewJobQueue(helper.GetRedis("master")), + } +} + +func (i *Importer) Process(file io.Reader, userID string, filename string) (*ImportResult, error) { + if !strings.HasSuffix(strings.ToLower(filename), ".csv") { + return nil, fmt.Errorf("file must be .csv format") + } + + headerErr := i.validateHeaders(file) + if headerErr != nil { + return nil, headerErr + } + + rowCount, err := csvlib.CountRows(file) + if err != nil { + return nil, fmt.Errorf("failed to count rows: %w", err) + } + + if rowCount <= 1 { + return &ImportResult{ + TotalRows: 0, + Imported: 0, + Skipped: 0, + Logs: []ImportLog{}, + Errors: []string{"CSV file is empty or has only headers"}, + }, nil + } + + totalDataRows := rowCount - 1 + + if totalDataRows >= 10000 { + return i.processAsync(file, userID, totalDataRows) + } + + return i.processSync(file, userID, totalDataRows) +} + +func (i *Importer) validateHeaders(file io.Reader) error { + reader := csv.NewReader(file) + header, err := reader.Read() + if err != nil { + return fmt.Errorf("failed to read CSV headers: %w", err) + } + + expectedHeaders := i.Handler.GetTemplateHeaders() + + if len(header) != len(expectedHeaders) { + return fmt.Errorf("invalid CSV headers. Expected: %s. Got: %s", + strings.Join(expectedHeaders, ", "), + strings.Join(header, ", ")) + } + + for idx, h := range header { + if strings.TrimSpace(strings.ToLower(h)) != strings.TrimSpace(strings.ToLower(expectedHeaders[idx])) { + return fmt.Errorf("invalid CSV headers. Expected: %s. Got: %s", + strings.Join(expectedHeaders, ", "), + strings.Join(header, ", ")) + } + } + + return nil +} + +func (i *Importer) processSync(file io.Reader, userID string, totalRows int) (*ImportResult, error) { + db := i.Helper.GetDB("master") + + var imported, skipped int + var logs []ImportLog + var errors []string + var batch []RowData + batchSize := 500 + + csvlib.ReadCSV(file, func(row []string, rowNumber int) error { + data, rowErrors := i.Handler.ValidateRow(row, rowNumber) + if len(rowErrors) > 0 { + for _, e := range rowErrors { + errorMsg := fmt.Sprintf("Row %d: %s", rowNumber, e.Error()) + errors = append(errors, errorMsg) + logs = append(logs, ImportLog{ + Row: rowNumber, + Status: "error", + Data: strings.Join(row, ","), + Message: errorMsg, + }) + } + skipped++ + return nil + } + + if i.Handler.IsDuplicate(db, data) { + dataStr := i.Handler.GetDataString(data) + logs = append(logs, ImportLog{ + Row: rowNumber, + Status: "skipped", + Data: dataStr, + Message: fmt.Sprintf("Row %d: '%s' already exists, skipped", rowNumber, dataStr), + }) + skipped++ + return nil + } + + batch = append(batch, data) + if len(batch) >= batchSize { + if err := i.insertBatch(db, batch); err != nil { + errorMsg := fmt.Sprintf("Batch insert error: %v", err) + errors = append(errors, errorMsg) + logs = append(logs, ImportLog{ + Row: rowNumber, + Status: "error", + Data: strings.Join(row, ","), + Message: errorMsg, + }) + } else { + for _, b := range batch { + dataStr := i.Handler.GetDataString(b) + logs = append(logs, ImportLog{ + Row: rowNumber, + Status: "inserted", + Data: dataStr, + Message: fmt.Sprintf("Row %d: '%s' inserted successfully", rowNumber, dataStr), + }) + } + imported += len(batch) + } + batch = nil + } + return nil + }) + + if len(batch) > 0 { + if err := i.insertBatch(db, batch); err != nil { + errorMsg := fmt.Sprintf("Batch insert error: %v", err) + errors = append(errors, errorMsg) + logs = append(logs, ImportLog{ + Row: totalRows + 1, + Status: "error", + Data: "", + Message: errorMsg, + }) + } else { + for _, b := range batch { + dataStr := i.Handler.GetDataString(b) + logs = append(logs, ImportLog{ + Row: imported + 1, + Status: "inserted", + Data: dataStr, + Message: fmt.Sprintf("'%s' inserted successfully", dataStr), + }) + } + imported += len(batch) + } + } + + return &ImportResult{ + TotalRows: totalRows, + Imported: imported, + Skipped: skipped, + Logs: logs, + Errors: errors, + IsAsync: false, + }, nil +} + +func (i *Importer) processAsync(file io.Reader, userID string, totalRows int) (*ImportResult, error) { + jobID := uuid.New().String() + ctx := context.Background() + + metadata := map[string]string{ + "user_id": userID, + "table": i.Handler.GetTemplateHeaders()[0], + } + + if err := i.JobQ.CreateJob(ctx, jobID, totalRows, metadata); err != nil { + return nil, fmt.Errorf("failed to create job: %w", err) + } + + go i.processAsyncWorker(file, jobID, userID, totalRows) + + return &ImportResult{ + JobID: jobID, + TotalRows: totalRows, + IsAsync: true, + Message: fmt.Sprintf("Import started. Poll /import/status/%s for progress.", jobID), + }, nil +} + +func (i *Importer) processAsyncWorker(file io.Reader, jobID string, userID string, totalRows int) { + db := i.Helper.GetDB("master") + ctx := context.Background() + + var imported, skipped int + var logs []ImportLog + var errors []string + var batch []RowData + batchSize := 500 + + i.JobQ.UpdateProgress(ctx, jobID, 0, 0, nil) + + csvlib.ReadCSV(file, func(row []string, rowNumber int) error { + data, rowErrors := i.Handler.ValidateRow(row, rowNumber) + if len(rowErrors) > 0 { + for _, e := range rowErrors { + errorMsg := fmt.Sprintf("Row %d: %s", rowNumber, e.Error()) + errors = append(errors, errorMsg) + logs = append(logs, ImportLog{ + Row: rowNumber, + Status: "error", + Data: strings.Join(row, ","), + Message: errorMsg, + }) + } + skipped++ + return nil + } + + if i.Handler.IsDuplicate(db, data) { + dataStr := i.Handler.GetDataString(data) + logs = append(logs, ImportLog{ + Row: rowNumber, + Status: "skipped", + Data: dataStr, + Message: fmt.Sprintf("Row %d: '%s' already exists, skipped", rowNumber, dataStr), + }) + skipped++ + return nil + } + + batch = append(batch, data) + if len(batch) >= batchSize { + if err := i.insertBatch(db, batch); err != nil { + errorMsg := fmt.Sprintf("Batch insert error: %v", err) + errors = append(errors, errorMsg) + logs = append(logs, ImportLog{ + Row: rowNumber, + Status: "error", + Data: strings.Join(row, ","), + Message: errorMsg, + }) + } else { + for _, b := range batch { + dataStr := i.Handler.GetDataString(b) + logs = append(logs, ImportLog{ + Row: rowNumber, + Status: "inserted", + Data: dataStr, + Message: fmt.Sprintf("Row %d: '%s' inserted successfully", rowNumber, dataStr), + }) + } + imported += len(batch) + } + batch = nil + i.saveJobLogs(ctx, jobID, logs, imported, skipped, errors) + } + return nil + }) + + if len(batch) > 0 { + if err := i.insertBatch(db, batch); err != nil { + errorMsg := fmt.Sprintf("Batch insert error: %v", err) + errors = append(errors, errorMsg) + logs = append(logs, ImportLog{ + Row: totalRows + 1, + Status: "error", + Data: "", + Message: errorMsg, + }) + } else { + for _, b := range batch { + dataStr := i.Handler.GetDataString(b) + logs = append(logs, ImportLog{ + Row: imported + 1, + Status: "inserted", + Data: dataStr, + Message: fmt.Sprintf("'%s' inserted successfully", dataStr), + }) + } + imported += len(batch) + } + } + + i.JobQ.CompleteJob(ctx, jobID, imported, skipped, errors) + i.saveJobLogs(ctx, jobID, logs, imported, skipped, errors) +} + +func (i *Importer) saveJobLogs(ctx context.Context, jobID string, logs []ImportLog, imported int, skipped int, errors []string) { + logsJSON, _ := json.Marshal(logs) + key := fmt.Sprintf("import_job:%s:logs", jobID) + i.Helper.GetRedis("master").Set(ctx, key, logsJSON, 24*time.Hour) +} + +func (i *Importer) insertBatch(db *gorm.DB, batch []RowData) error { + if len(batch) == 0 { + return nil + } + + return db.CreateInBatches(batch, 500).Error +} + +func (i *Importer) GetTemplate() string { + headers := i.Handler.GetTemplateHeaders() + rows := i.Handler.GetTemplateRows() + return csvlib.WriteCSV(headers, rows) +} + +func (i *Importer) ServeTemplate(w http.ResponseWriter) { + template := i.GetTemplate() + + w.Header().Set("Content-Type", "text/csv") + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s_template.csv", time.Now().Format("20060102"))) + w.Header().Set("Content-Length", fmt.Sprintf("%d", len(template))) + w.Write([]byte(template)) +} + +func (i *Importer) GetJobStatus(jobID string) (*jobqueue.Job, []ImportLog, error) { + ctx := context.Background() + job, err := i.JobQ.GetJob(ctx, jobID) + if err != nil { + return nil, nil, err + } + + var logs []ImportLog + key := fmt.Sprintf("import_job:%s:logs", jobID) + logsJSON, err := i.Helper.GetRedis("slave").Get(ctx, key).Result() + if err == nil { + json.Unmarshal([]byte(logsJSON), &logs) + } + + return job, logs, nil +} diff --git a/pkg/jobqueue/jobqueue.go b/pkg/jobqueue/jobqueue.go new file mode 100644 index 0000000..3b5cc28 --- /dev/null +++ b/pkg/jobqueue/jobqueue.go @@ -0,0 +1,207 @@ +package jobqueue + +import ( + "context" + "fmt" + "time" + + "github.com/redis/go-redis/v9" +) + +type JobStatus string + +const ( + StatusPending JobStatus = "pending" + StatusProcessing JobStatus = "processing" + StatusCompleted JobStatus = "completed" + StatusFailed JobStatus = "failed" +) + +type Job struct { + ID string `json:"id"` + Status JobStatus `json:"status"` + Total int `json:"total"` + Imported int `json:"imported"` + Skipped int `json:"skipped"` + Progress int `json:"progress"` + Errors []string `json:"errors,omitempty"` + CreatedAt time.Time `json:"created_at"` + FinishedAt *time.Time `json:"finished_at,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` +} + +type JobQueue struct { + Redis *redis.Client +} + +func NewJobQueue(redis *redis.Client) *JobQueue { + return &JobQueue{Redis: redis} +} + +func (jq *JobQueue) CreateJob(ctx context.Context, jobID string, total int, metadata map[string]string) error { + key := fmt.Sprintf("import_job:%s", jobID) + now := time.Now() + + job := map[string]interface{}{ + "id": jobID, + "status": string(StatusPending), + "total": total, + "imported": 0, + "skipped": 0, + "progress": 0, + "created_at": now.Format(time.RFC3339), + } + + if metadata != nil { + for k, v := range metadata { + job["meta_"+k] = v + } + } + + if err := jq.Redis.HSet(ctx, key, job).Err(); err != nil { + return err + } + + jq.Redis.Expire(ctx, key, 24*time.Hour) + return nil +} + +func (jq *JobQueue) UpdateProgress(ctx context.Context, jobID string, imported int, skipped int, errors []string) error { + key := fmt.Sprintf("import_job:%s", jobID) + + job, err := jq.Redis.HGetAll(ctx, key).Result() + if err != nil { + return err + } + + total := parseInt(job["total"]) + progress := 0 + if total > 0 { + progress = ((imported + skipped) * 100) / total + } + + update := map[string]interface{}{ + "status": string(StatusProcessing), + "imported": imported, + "skipped": skipped, + "progress": progress, + } + + if len(errors) > 0 { + update["errors"] = joinErrors(errors) + } + + return jq.Redis.HSet(ctx, key, update).Err() +} + +func (jq *JobQueue) CompleteJob(ctx context.Context, jobID string, imported int, skipped int, errors []string) error { + key := fmt.Sprintf("import_job:%s", jobID) + now := time.Now() + + update := map[string]interface{}{ + "status": string(StatusCompleted), + "imported": imported, + "skipped": skipped, + "progress": 100, + "finished_at": now.Format(time.RFC3339), + } + + if len(errors) > 0 { + update["errors"] = joinErrors(errors) + } + + return jq.Redis.HSet(ctx, key, update).Err() +} + +func (jq *JobQueue) FailJob(ctx context.Context, jobID string, errMsg string) error { + key := fmt.Sprintf("import_job:%s", jobID) + now := time.Now() + + return jq.Redis.HSet(ctx, key, map[string]interface{}{ + "status": string(StatusFailed), + "finished_at": now.Format(time.RFC3339), + "errors": errMsg, + }).Err() +} + +func (jq *JobQueue) GetJob(ctx context.Context, jobID string) (*Job, error) { + key := fmt.Sprintf("import_job:%s", jobID) + data, err := jq.Redis.HGetAll(ctx, key).Result() + if err != nil { + return nil, err + } + + if len(data) == 0 { + return nil, fmt.Errorf("job not found") + } + + job := &Job{ + ID: data["id"], + Status: JobStatus(data["status"]), + Total: parseInt(data["total"]), + Imported: parseInt(data["imported"]), + Skipped: parseInt(data["skipped"]), + Progress: parseInt(data["progress"]), + } + + if data["errors"] != "" { + job.Errors = splitErrors(data["errors"]) + } + + if t, err := time.Parse(time.RFC3339, data["created_at"]); err == nil { + job.CreatedAt = t + } + + if data["finished_at"] != "" { + if t, err := time.Parse(time.RFC3339, data["finished_at"]); err == nil { + job.FinishedAt = &t + } + } + + return job, nil +} + +func (jq *JobQueue) DeleteJob(ctx context.Context, jobID string) error { + key := fmt.Sprintf("import_job:%s", jobID) + return jq.Redis.Del(ctx, key).Err() +} + +func parseInt(s string) int { + n := 0 + for _, c := range s { + if c >= '0' && c <= '9' { + n = n*10 + int(c-'0') + } + } + return n +} + +func joinErrors(errors []string) string { + result := "" + for i, e := range errors { + if i > 0 { + result += "||" + } + result += e + } + return result +} + +func splitErrors(s string) []string { + result := []string{} + current := "" + for _, c := range s { + if c == '|' { + if current != "" { + result = append(result, current) + } + current = "" + } else { + current += string(c) + } + } + if current != "" { + result = append(result, current) + } + return result +} diff --git a/storage/logs/app.log b/storage/logs/app.log index eaeb8a7..5e3ce8c 100644 --- a/storage/logs/app.log +++ b/storage/logs/app.log @@ -687,3 +687,117 @@ {"level":"info","ts":"2026-08-17T14:21:37.526+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"} {"level":"info","ts":"2026-08-17T14:21:37.550+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} {"level":"info","ts":"2026-08-17T14:21:37.621+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":201,"method":"POST","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.095641333,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T14:25:11.586+0700","caller":"database/database.go:53","msg":"Connect To Master DB"} +{"level":"info","ts":"2026-08-17T14:25:11.681+0700","caller":"database/database.go:63","msg":"DB Master Connected"} +{"level":"info","ts":"2026-08-17T14:25:11.681+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"} +{"level":"info","ts":"2026-08-17T14:25:11.752+0700","caller":"database/database.go:86","msg":"DB Connection Done"} +{"level":"info","ts":"2026-08-17T14:25:11.856+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"} +{"level":"info","ts":"2026-08-17T14:25:13.926+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"} +{"level":"info","ts":"2026-08-17T14:25:13.950+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} +{"level":"info","ts":"2026-08-17T14:25:14.035+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"DELETE","path":"/master-data/provinces/1a00e98ea5f87d9a1026ba6441","query":"","ip":"127.0.0.1","latency":0.1097195,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T14:25:32.443+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"} +{"level":"info","ts":"2026-08-17T14:25:32.474+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} +{"level":"info","ts":"2026-08-17T14:25:32.600+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":201,"method":"POST","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.157767833,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T14:29:01.138+0700","caller":"database/database.go:53","msg":"Connect To Master DB"} +{"level":"info","ts":"2026-08-17T14:29:01.281+0700","caller":"database/database.go:63","msg":"DB Master Connected"} +{"level":"info","ts":"2026-08-17T14:29:01.281+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"} +{"level":"info","ts":"2026-08-17T14:29:01.363+0700","caller":"database/database.go:86","msg":"DB Connection Done"} +{"level":"info","ts":"2026-08-17T14:29:01.481+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"} +{"level":"info","ts":"2026-08-17T14:29:01.947+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"} +{"level":"info","ts":"2026-08-17T14:29:01.968+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} +{"level":"info","ts":"2026-08-17T14:29:02.064+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"PUT","path":"/master-data/provinces/1a00e9c80365466b3d2a04918c","query":"","ip":"127.0.0.1","latency":0.117066958,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T14:30:30.255+0700","caller":"database/database.go:53","msg":"Connect To Master DB"} +{"level":"info","ts":"2026-08-17T14:30:30.350+0700","caller":"database/database.go:63","msg":"DB Master Connected"} +{"level":"info","ts":"2026-08-17T14:30:30.350+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"} +{"level":"info","ts":"2026-08-17T14:30:30.424+0700","caller":"database/database.go:86","msg":"DB Connection Done"} +{"level":"info","ts":"2026-08-17T14:30:30.532+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"} +{"level":"info","ts":"2026-08-17T14:30:34.315+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"} +{"level":"info","ts":"2026-08-17T14:30:34.340+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} +{"level":"info","ts":"2026-08-17T14:30:34.428+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"PUT","path":"/master-data/provinces/1a00e9c80365466b3d2a04918c","query":"","ip":"127.0.0.1","latency":0.113493,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T14:32:39.551+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"} +{"level":"info","ts":"2026-08-17T14:32:39.592+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} +{"level":"info","ts":"2026-08-17T14:32:39.657+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"DELETE","path":"/master-data/provinces/1a00e9c80365466b3d2a04918c","query":"","ip":"127.0.0.1","latency":0.108803959,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T14:38:27.830+0700","caller":"database/database.go:53","msg":"Connect To Master DB"} +{"level":"info","ts":"2026-08-17T14:38:27.951+0700","caller":"database/database.go:63","msg":"DB Master Connected"} +{"level":"info","ts":"2026-08-17T14:38:27.951+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"} +{"level":"info","ts":"2026-08-17T14:38:28.024+0700","caller":"database/database.go:86","msg":"DB Connection Done"} +{"level":"info","ts":"2026-08-17T14:38:28.135+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"} +{"level":"info","ts":"2026-08-17T14:39:10.542+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"} +{"level":"info","ts":"2026-08-17T14:39:10.568+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} +{"level":"info","ts":"2026-08-17T14:39:10.654+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.113548917,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T14:39:47.378+0700","caller":"database/database.go:53","msg":"Connect To Master DB"} +{"level":"info","ts":"2026-08-17T14:39:47.470+0700","caller":"database/database.go:63","msg":"DB Master Connected"} +{"level":"info","ts":"2026-08-17T14:39:47.470+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"} +{"level":"info","ts":"2026-08-17T14:39:47.543+0700","caller":"database/database.go:86","msg":"DB Connection Done"} +{"level":"info","ts":"2026-08-17T14:39:47.656+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"} +{"level":"info","ts":"2026-08-17T14:39:50.136+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"} +{"level":"info","ts":"2026-08-17T14:39:50.160+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} +{"level":"info","ts":"2026-08-17T14:39:50.234+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.099099792,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T14:40:13.477+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"} +{"level":"info","ts":"2026-08-17T14:40:13.517+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} +{"level":"info","ts":"2026-08-17T14:40:13.593+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.117934,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T14:40:47.694+0700","caller":"database/database.go:53","msg":"Connect To Master DB"} +{"level":"info","ts":"2026-08-17T14:40:47.855+0700","caller":"database/database.go:63","msg":"DB Master Connected"} +{"level":"info","ts":"2026-08-17T14:40:47.855+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"} +{"level":"info","ts":"2026-08-17T14:40:47.976+0700","caller":"database/database.go:86","msg":"DB Connection Done"} +{"level":"info","ts":"2026-08-17T14:40:48.089+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"} +{"level":"warn","ts":"2026-08-17T14:40:52.337+0700","caller":"handlers/handlers.go:64","msg":"Client Error","status":401,"method":"GET","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.000458083,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T14:40:58.800+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"POST","path":"/login","query":"","ip":"127.0.0.1","latency":0.180353416,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T14:41:02.287+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTYwNTgsImlhdCI6MTc4Njk1MjQ1OH0.TZXGJLNng6TyfzspOkA6HuEyFi61K-wnie_L6ybYzyY"} +{"level":"info","ts":"2026-08-17T14:41:02.315+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} +{"level":"info","ts":"2026-08-17T14:41:02.440+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.153650875,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T14:42:37.997+0700","caller":"database/database.go:53","msg":"Connect To Master DB"} +{"level":"info","ts":"2026-08-17T14:42:38.100+0700","caller":"database/database.go:63","msg":"DB Master Connected"} +{"level":"info","ts":"2026-08-17T14:42:38.100+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"} +{"level":"info","ts":"2026-08-17T14:42:38.180+0700","caller":"database/database.go:86","msg":"DB Connection Done"} +{"level":"info","ts":"2026-08-17T14:42:38.286+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"} +{"level":"info","ts":"2026-08-17T14:42:40.017+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTYwNTgsImlhdCI6MTc4Njk1MjQ1OH0.TZXGJLNng6TyfzspOkA6HuEyFi61K-wnie_L6ybYzyY"} +{"level":"info","ts":"2026-08-17T14:42:40.041+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} +{"level":"info","ts":"2026-08-17T14:42:40.111+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.0938845,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T14:43:03.312+0700","caller":"database/database.go:53","msg":"Connect To Master DB"} +{"level":"info","ts":"2026-08-17T14:43:03.433+0700","caller":"database/database.go:63","msg":"DB Master Connected"} +{"level":"info","ts":"2026-08-17T14:43:03.433+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"} +{"level":"info","ts":"2026-08-17T14:43:03.513+0700","caller":"database/database.go:86","msg":"DB Connection Done"} +{"level":"info","ts":"2026-08-17T14:43:03.623+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"} +{"level":"info","ts":"2026-08-17T14:43:03.660+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTYwNTgsImlhdCI6MTc4Njk1MjQ1OH0.TZXGJLNng6TyfzspOkA6HuEyFi61K-wnie_L6ybYzyY"} +{"level":"info","ts":"2026-08-17T14:43:03.681+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} +{"level":"info","ts":"2026-08-17T14:43:03.742+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.082196166,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T14:45:05.375+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTYwNTgsImlhdCI6MTc4Njk1MjQ1OH0.TZXGJLNng6TyfzspOkA6HuEyFi61K-wnie_L6ybYzyY"} +{"level":"info","ts":"2026-08-17T14:45:05.402+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} +{"level":"info","ts":"2026-08-17T14:45:05.466+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"name=aceh","ip":"127.0.0.1","latency":0.093297541,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T14:50:33.706+0700","caller":"database/database.go:53","msg":"Connect To Master DB"} +{"level":"info","ts":"2026-08-17T14:50:33.793+0700","caller":"database/database.go:63","msg":"DB Master Connected"} +{"level":"info","ts":"2026-08-17T14:50:33.794+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"} +{"level":"info","ts":"2026-08-17T14:50:33.875+0700","caller":"database/database.go:86","msg":"DB Connection Done"} +{"level":"info","ts":"2026-08-17T14:50:34.004+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"} +{"level":"info","ts":"2026-08-17T14:50:41.249+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTYwNTgsImlhdCI6MTc4Njk1MjQ1OH0.TZXGJLNng6TyfzspOkA6HuEyFi61K-wnie_L6ybYzyY"} +{"level":"info","ts":"2026-08-17T14:50:41.276+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} +{"level":"info","ts":"2026-08-17T14:59:52.379+0700","caller":"database/database.go:53","msg":"Connect To Master DB"} +{"level":"info","ts":"2026-08-17T14:59:52.469+0700","caller":"database/database.go:63","msg":"DB Master Connected"} +{"level":"info","ts":"2026-08-17T14:59:52.469+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"} +{"level":"info","ts":"2026-08-17T14:59:52.543+0700","caller":"database/database.go:86","msg":"DB Connection Done"} +{"level":"info","ts":"2026-08-17T14:59:52.653+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"} +{"level":"info","ts":"2026-08-17T14:59:56.806+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTYwNTgsImlhdCI6MTc4Njk1MjQ1OH0.TZXGJLNng6TyfzspOkA6HuEyFi61K-wnie_L6ybYzyY"} +{"level":"info","ts":"2026-08-17T14:59:56.837+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} +{"level":"info","ts":"2026-08-17T14:59:56.919+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"name=aceh","ip":"127.0.0.1","latency":0.113733667,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T15:02:00.031+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTYwNTgsImlhdCI6MTc4Njk1MjQ1OH0.TZXGJLNng6TyfzspOkA6HuEyFi61K-wnie_L6ybYzyY"} +{"level":"info","ts":"2026-08-17T15:02:00.064+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} +{"level":"info","ts":"2026-08-17T15:02:00.145+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"name=ACEH","ip":"127.0.0.1","latency":0.116235959,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T15:28:06.336+0700","caller":"database/database.go:53","msg":"Connect To Master DB"} +{"level":"info","ts":"2026-08-17T15:28:06.443+0700","caller":"database/database.go:63","msg":"DB Master Connected"} +{"level":"info","ts":"2026-08-17T15:28:06.443+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"} +{"level":"info","ts":"2026-08-17T15:28:06.515+0700","caller":"database/database.go:86","msg":"DB Connection Done"} +{"level":"info","ts":"2026-08-17T15:28:06.623+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"} +{"level":"info","ts":"2026-08-17T15:29:04.258+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTYwNTgsImlhdCI6MTc4Njk1MjQ1OH0.TZXGJLNng6TyfzspOkA6HuEyFi61K-wnie_L6ybYzyY"} +{"level":"info","ts":"2026-08-17T15:29:04.289+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} +{"level":"info","ts":"2026-08-17T15:29:04.291+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/template","query":"","ip":"127.0.0.1","latency":0.034353167,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-17T18:38:50.151+0700","caller":"database/database.go:53","msg":"Connect To Master DB"} +{"level":"info","ts":"2026-08-17T18:38:50.222+0700","caller":"database/database.go:63","msg":"DB Master Connected"} +{"level":"info","ts":"2026-08-17T18:38:50.223+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"} +{"level":"info","ts":"2026-08-17T18:38:50.274+0700","caller":"database/database.go:86","msg":"DB Connection Done"} +{"level":"info","ts":"2026-08-17T18:38:50.356+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"} +{"level":"info","ts":"2026-08-17T18:39:33.406+0700","caller":"database/database.go:53","msg":"Connect To Master DB"} +{"level":"info","ts":"2026-08-17T18:39:33.467+0700","caller":"database/database.go:63","msg":"DB Master Connected"} +{"level":"info","ts":"2026-08-17T18:39:33.467+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"} +{"level":"info","ts":"2026-08-17T18:39:33.514+0700","caller":"database/database.go:86","msg":"DB Connection Done"} +{"level":"info","ts":"2026-08-17T18:39:33.609+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"} diff --git a/storage/logs/db.log b/storage/logs/db.log index bf9c223..595f785 100644 --- a/storage/logs/db.log +++ b/storage/logs/db.log @@ -3358,3 +3358,108 @@ {"level":"info","ts":"2026-08-17T14:21:30.235+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"} {"level":"info","ts":"2026-08-17T14:21:30.290+0700","caller":"database/redis.go:62","msg":"Connected"} {"level":"info","ts":"2026-08-17T14:21:37.620+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:100","elapsed":0.064875875,"rows":1,"sql":"INSERT INTO \"province\" (\"name\",\"created_by\",\"created_on\",\"updated_by\",\"updated_on\",\"merchant_id\") VALUES ('tambahan','19ff104c16522a2252cbaca24d','2026-08-17 14:21:37.555','19ff104c16522a2252cbaca24d','2026-08-17 14:21:37.555','19ff111bca6c2c382d9eb43b3c') RETURNING \"id\""} +{"level":"info","ts":"2026-08-17T14:25:11.752+0700","caller":"database/redis.go:34","msg":"Parse config slave"} +{"level":"info","ts":"2026-08-17T14:25:11.753+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"} +{"level":"info","ts":"2026-08-17T14:25:11.802+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:25:11.802+0700","caller":"database/redis.go:34","msg":"Parse config master"} +{"level":"info","ts":"2026-08-17T14:25:11.802+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"} +{"level":"info","ts":"2026-08-17T14:25:11.856+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:25:14.035+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:135","elapsed":0.062923208,"rows":1,"sql":"DELETE FROM \"province\" WHERE id = '1a00e98ea5f87d9a1026ba6441'"} +{"level":"info","ts":"2026-08-17T14:25:32.594+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:100","elapsed":0.081571459,"rows":1,"sql":"INSERT INTO \"province\" (\"name\",\"created_by\",\"created_on\",\"updated_by\",\"updated_on\",\"merchant_id\") VALUES ('tambahan','19ff104c16522a2252cbaca24d','2026-08-17 14:25:32.504','19ff104c16522a2252cbaca24d','2026-08-17 14:25:32.504','19ff111bca6c2c382d9eb43b3c') RETURNING \"id\""} +{"level":"info","ts":"2026-08-17T14:29:01.363+0700","caller":"database/redis.go:34","msg":"Parse config master"} +{"level":"info","ts":"2026-08-17T14:29:01.363+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"} +{"level":"info","ts":"2026-08-17T14:29:01.426+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:29:01.426+0700","caller":"database/redis.go:34","msg":"Parse config slave"} +{"level":"info","ts":"2026-08-17T14:29:01.426+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"} +{"level":"info","ts":"2026-08-17T14:29:01.481+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:29:02.013+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:114","elapsed":0.044179208,"rows":1,"sql":"SELECT * FROM \"province\" WHERE id = '1a00e9c80365466b3d2a04918c' ORDER BY \"province\".\"id\" LIMIT 1"} +{"level":"info","ts":"2026-08-17T14:29:02.064+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:123","elapsed":0.050348583,"rows":1,"sql":"UPDATE \"province\" SET \"name\"='tambahan xxx',\"created_by\"='19ff104c16522a2252cbaca24d',\"created_on\"='2026-08-17 14:25:32.504',\"updated_by\"='19ff104c16522a2252cbaca24d',\"updated_on\"='2026-08-17 14:29:02.024',\"merchant_id\"='19ff111bca6c2c382d9eb43b3c' WHERE \"id\" = '1a00e9c80365466b3d2a04918c'"} +{"level":"info","ts":"2026-08-17T14:30:30.424+0700","caller":"database/redis.go:34","msg":"Parse config slave"} +{"level":"info","ts":"2026-08-17T14:30:30.424+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"} +{"level":"info","ts":"2026-08-17T14:30:30.477+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:30:30.477+0700","caller":"database/redis.go:34","msg":"Parse config master"} +{"level":"info","ts":"2026-08-17T14:30:30.477+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"} +{"level":"info","ts":"2026-08-17T14:30:30.531+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:30:34.384+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:114","elapsed":0.040815125,"rows":1,"sql":"SELECT * FROM \"province\" WHERE id = '1a00e9c80365466b3d2a04918c' ORDER BY \"province\".\"id\" LIMIT 1"} +{"level":"info","ts":"2026-08-17T14:30:34.428+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:123","elapsed":0.043398083,"rows":1,"sql":"UPDATE \"province\" SET \"name\"='tambahan xxx',\"created_by\"='19ff104c16522a2252cbaca24d',\"created_on\"='2026-08-17 14:25:32.504',\"updated_by\"='19ff104c16522a2252cbaca24d',\"updated_on\"='2026-08-17 14:30:34.395',\"merchant_id\"='19ff111bca6c2c382d9eb43b3c' WHERE \"id\" = '1a00e9c80365466b3d2a04918c'"} +{"level":"info","ts":"2026-08-17T14:32:39.655+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:135","elapsed":0.056869167,"rows":1,"sql":"DELETE FROM \"province\" WHERE id = '1a00e9c80365466b3d2a04918c'"} +{"level":"info","ts":"2026-08-17T14:38:28.024+0700","caller":"database/redis.go:34","msg":"Parse config master"} +{"level":"info","ts":"2026-08-17T14:38:28.025+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"} +{"level":"info","ts":"2026-08-17T14:38:28.076+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:38:28.076+0700","caller":"database/redis.go:34","msg":"Parse config slave"} +{"level":"info","ts":"2026-08-17T14:38:28.076+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"} +{"level":"info","ts":"2026-08-17T14:38:28.134+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:39:10.622+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:37","elapsed":0.040459209,"rows":1,"sql":"SELECT count(*) FROM \"province\""} +{"level":"info","ts":"2026-08-17T14:39:10.653+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:39","elapsed":0.030940792,"rows":34,"sql":"SELECT * FROM \"province\""} +{"level":"info","ts":"2026-08-17T14:39:47.543+0700","caller":"database/redis.go:34","msg":"Parse config master"} +{"level":"info","ts":"2026-08-17T14:39:47.543+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"} +{"level":"info","ts":"2026-08-17T14:39:47.599+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:39:47.600+0700","caller":"database/redis.go:34","msg":"Parse config slave"} +{"level":"info","ts":"2026-08-17T14:39:47.600+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"} +{"level":"info","ts":"2026-08-17T14:39:47.656+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:39:50.204+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:37","elapsed":0.039307917,"rows":1,"sql":"SELECT count(*) FROM \"province\""} +{"level":"info","ts":"2026-08-17T14:39:50.234+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:39","elapsed":0.029045833,"rows":34,"sql":"SELECT * FROM \"province\""} +{"level":"info","ts":"2026-08-17T14:40:13.574+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:37","elapsed":0.028055875,"rows":1,"sql":"SELECT count(*) FROM \"province\""} +{"level":"info","ts":"2026-08-17T14:40:13.588+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:39","elapsed":0.013114458,"rows":34,"sql":"SELECT * FROM \"province\""} +{"level":"info","ts":"2026-08-17T14:40:47.976+0700","caller":"database/redis.go:34","msg":"Parse config master"} +{"level":"info","ts":"2026-08-17T14:40:47.976+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"} +{"level":"info","ts":"2026-08-17T14:40:48.031+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:40:48.031+0700","caller":"database/redis.go:34","msg":"Parse config slave"} +{"level":"info","ts":"2026-08-17T14:40:48.031+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"} +{"level":"info","ts":"2026-08-17T14:40:48.088+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:40:58.695+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/auth.go:29","elapsed":0.020184542,"rows":1,"sql":"SELECT * FROM \"users_roles\" WHERE \"users_roles\".\"id\" = '19fe814c5350d67814c3e6057c'"} +{"level":"info","ts":"2026-08-17T14:40:58.697+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/auth.go:29","elapsed":0.072182583,"rows":1,"sql":"SELECT * FROM \"users\" WHERE email='gcx@gmail.com'"} +{"level":"info","ts":"2026-08-17T14:41:02.382+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:37","elapsed":0.064269583,"rows":1,"sql":"SELECT count(*) FROM \"province\""} +{"level":"info","ts":"2026-08-17T14:41:02.440+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:39","elapsed":0.056326958,"rows":34,"sql":"SELECT * FROM \"province\""} +{"level":"info","ts":"2026-08-17T14:42:38.180+0700","caller":"database/redis.go:34","msg":"Parse config slave"} +{"level":"info","ts":"2026-08-17T14:42:38.180+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"} +{"level":"info","ts":"2026-08-17T14:42:38.234+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:42:38.234+0700","caller":"database/redis.go:34","msg":"Parse config master"} +{"level":"info","ts":"2026-08-17T14:42:38.234+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"} +{"level":"info","ts":"2026-08-17T14:42:38.286+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:42:40.080+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:38","elapsed":0.03832,"rows":1,"sql":"SELECT count(*) FROM \"province\" WHERE merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c'"} +{"level":"info","ts":"2026-08-17T14:42:40.111+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:40","elapsed":0.029813584,"rows":34,"sql":"SELECT * FROM \"province\""} +{"level":"info","ts":"2026-08-17T14:43:03.513+0700","caller":"database/redis.go:34","msg":"Parse config slave"} +{"level":"info","ts":"2026-08-17T14:43:03.513+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"} +{"level":"info","ts":"2026-08-17T14:43:03.569+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:43:03.569+0700","caller":"database/redis.go:34","msg":"Parse config master"} +{"level":"info","ts":"2026-08-17T14:43:03.569+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"} +{"level":"info","ts":"2026-08-17T14:43:03.623+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:43:03.715+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:38","elapsed":0.033541541,"rows":1,"sql":"SELECT count(*) FROM \"province\" WHERE merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c'"} +{"level":"info","ts":"2026-08-17T14:43:03.742+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:40","elapsed":0.026353084,"rows":34,"sql":"SELECT * FROM \"province\" WHERE merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c' ORDER BY name ASC"} +{"level":"info","ts":"2026-08-17T14:45:05.446+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:38","elapsed":0.032110583,"rows":1,"sql":"SELECT count(*) FROM \"province\" WHERE merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c'"} +{"level":"info","ts":"2026-08-17T14:45:05.465+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:40","elapsed":0.017382292,"rows":34,"sql":"SELECT * FROM \"province\" WHERE merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c' ORDER BY name ASC"} +{"level":"info","ts":"2026-08-17T14:50:33.875+0700","caller":"database/redis.go:34","msg":"Parse config master"} +{"level":"info","ts":"2026-08-17T14:50:33.876+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"} +{"level":"info","ts":"2026-08-17T14:50:33.933+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:50:33.934+0700","caller":"database/redis.go:34","msg":"Parse config slave"} +{"level":"info","ts":"2026-08-17T14:50:33.934+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"} +{"level":"info","ts":"2026-08-17T14:50:34.000+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:59:52.543+0700","caller":"database/redis.go:34","msg":"Parse config slave"} +{"level":"info","ts":"2026-08-17T14:59:52.544+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"} +{"level":"info","ts":"2026-08-17T14:59:52.598+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:59:52.599+0700","caller":"database/redis.go:34","msg":"Parse config master"} +{"level":"info","ts":"2026-08-17T14:59:52.599+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"} +{"level":"info","ts":"2026-08-17T14:59:52.652+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T14:59:56.871+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:51","elapsed":0.033292084,"rows":1,"sql":"SELECT count(*) FROM \"province\" WHERE (merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c') AND name='aceh'"} +{"level":"info","ts":"2026-08-17T14:59:56.918+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:53","elapsed":0.046337875,"rows":0,"sql":"SELECT * FROM \"province\" WHERE (merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c') AND name='aceh' ORDER BY name ASC"} +{"level":"info","ts":"2026-08-17T15:02:00.123+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:51","elapsed":0.03923575,"rows":1,"sql":"SELECT count(*) FROM \"province\" WHERE (merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c') AND name='ACEH'"} +{"level":"info","ts":"2026-08-17T15:02:00.143+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:53","elapsed":0.018068625,"rows":1,"sql":"SELECT * FROM \"province\" WHERE (merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c') AND name='ACEH' ORDER BY name ASC"} +{"level":"info","ts":"2026-08-17T15:28:06.516+0700","caller":"database/redis.go:34","msg":"Parse config master"} +{"level":"info","ts":"2026-08-17T15:28:06.516+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"} +{"level":"info","ts":"2026-08-17T15:28:06.572+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T15:28:06.572+0700","caller":"database/redis.go:34","msg":"Parse config slave"} +{"level":"info","ts":"2026-08-17T15:28:06.572+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"} +{"level":"info","ts":"2026-08-17T15:28:06.622+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T18:38:50.279+0700","caller":"database/redis.go:34","msg":"Parse config master"} +{"level":"info","ts":"2026-08-17T18:38:50.279+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"} +{"level":"info","ts":"2026-08-17T18:38:50.317+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T18:38:50.317+0700","caller":"database/redis.go:34","msg":"Parse config slave"} +{"level":"info","ts":"2026-08-17T18:38:50.317+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"} +{"level":"info","ts":"2026-08-17T18:38:50.355+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T18:39:33.514+0700","caller":"database/redis.go:34","msg":"Parse config master"} +{"level":"info","ts":"2026-08-17T18:39:33.515+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"} +{"level":"info","ts":"2026-08-17T18:39:33.550+0700","caller":"database/redis.go:62","msg":"Connected"} +{"level":"info","ts":"2026-08-17T18:39:33.550+0700","caller":"database/redis.go:34","msg":"Parse config slave"} +{"level":"info","ts":"2026-08-17T18:39:33.551+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"} +{"level":"info","ts":"2026-08-17T18:39:33.584+0700","caller":"database/redis.go:62","msg":"Connected"}