package main import ( "context" "database/sql" "fmt" "gen_transit_point/model" "gen_transit_point/utils" "log" "strings" "time" _ "github.com/lib/pq" "github.com/opentracing/opentracing-go" "github.com/xuri/excelize/v2" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/logger" ) func InitGormDB(host string, port int, user, pass, dbname, sslmode string) (*gorm.DB, error) { dsn := fmt.Sprintf( "host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", host, port, user, pass, dbname, sslmode, ) gormDB, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ PrepareStmt: true, Logger: logger.Default.LogMode(logger.Warn), }) if err != nil { return nil, err } // Setup connection pooling sqlDB, err := gormDB.DB() if err != nil { return nil, err } sqlDB.SetMaxIdleConns(10) sqlDB.SetMaxOpenConns(100) sqlDB.SetConnMaxLifetime(time.Hour) return gormDB, nil } func main() { ctx := context.Background() now := time.Now() dbhost := "127.0.0.1" dbport := 5432 dbuser := "postgres" dbpass := "postgres" dbname := "oslogweb" sslmode := "disable" //prod /* dbhost := "" dbport := 5454 dbuser := "postgres" dbpass := "" dbname := "oslogweb" sslmode := "require" */ gormDB, err := InitGormDB(dbhost, dbport, dbuser, dbpass, dbname, sslmode) if err != nil { log.Fatalf("Gagal init GORM: %v", err) } sqlDB, err := gormDB.DB() if err != nil { log.Fatalf("Gagal ambil sql.DB: %v", err) } if err := sqlDB.Ping(); err != nil { log.Fatalf("Gagal ping database: %v", err) } fmt.Println("Koneksi ke DB berhasil ✅") filename := "transit_point_indosps.xlsx" f, err := excelize.OpenFile(filename) if err != nil { log.Fatalf("Gagal membuka file Excel: %v", err) } defer f.Close() sheets := f.GetSheetList() if len(sheets) == 0 { log.Fatal("File Excel tidak memiliki sheet.") } sheetName := sheets[0] fmt.Printf("Membaca sheet: %s\n", sheetName) rows, err := f.GetRows(sheetName) if err != nil { log.Fatalf("Gagal membaca baris: %v", err) } excelData := []model.TransitPoint{} //skip header nya jadi init dari 1 bukan 0 for i := 1; i < len(rows); i++ { row := rows[i] if len(row) < 5 { continue } name := strings.TrimSpace(row[1]) if name == "" { continue } address := strings.TrimSpace(row[2]) lat := strings.TrimSpace(row[3]) lon := strings.TrimSpace(row[4]) var latF, lonF float64 fmt.Sscanf(lat, "%f", &latF) fmt.Sscanf(lon, "%f", &lonF) excelData = append(excelData, model.TransitPoint{ CompanyID: 784, Name: name, Address: address, Latitude: latF, Longitude: lonF, CreatedDate: utils.FormatToWithoutTZ(ctx, now), CreatedBy: "system", }) } names := []string{} for _, p := range excelData { names = append(names, fmt.Sprintf("'%s'", strings.ReplaceAll(p.Name, "'", "''"))) } filter := fmt.Sprintf("AND a.company_id=784 AND a.name IN (%s)", strings.Join(names, ",")) data, err := GetTransitPoints(ctx, gormDB, filter) if err != nil { log.Fatalf("Gagal GetTransitPoints: %v", err) } dbMap := map[string]model.TransitPoint{} for _, d := range data { dbMap[d.Name] = *d } for _, p := range excelData { geomWkt := GenerateGeomWkt(ctx, gormDB, p.Longitude, p.Latitude, float64(100)) p.GeomWkt = geomWkt p.ModifiedBy = "system" p.ModifiedDate = utils.FormatToWithoutTZ(ctx, now) if existing, exists := dbMap[p.Name]; exists { if existing.Address != p.Address || existing.Latitude != p.Latitude || existing.Longitude != p.Longitude { _, err := UpdateTransitPointDB(ctx, gormDB, &p) if err != nil { log.Printf("Gagal update %s: %v", p.Name, err) } else { fmt.Printf("Berhasil update: %s\n", p.Name) } } } else { _, err := InsertTransitPointDB(ctx, gormDB, &p) if err != nil { log.Printf("Gagal insert %s: %v", p.Name, err) } else { fmt.Printf("Berhasil insert: %s\n", p.Name) } } } } func InsertTransitPointDB(ctx context.Context, db *gorm.DB, data *model.TransitPoint) (*model.TransitPoint, error) { span, spanCtx := opentracing.StartSpanFromContext(ctx, "insertTransitPointDB") defer span.Finish() payload := data.BeforeCheck(spanCtx) tx := db.WithContext(spanCtx).Begin().Scopes(payload.TableName(spanCtx)) if err := tx.Error; err != nil { return nil, err } if payload.GeomWkt.String != "" { tx.Clauses(clause.Expr{SQL: "ST_GeomFromText(?, 4326)", Vars: []interface{}{payload.GeomWkt}}) } tx.Create(&payload) if err := tx.Error; err != nil { tx.Rollback() return nil, err } if err := tx.Commit().Error; err != nil { tx.Rollback() return nil, err } return payload.Convert(spanCtx), nil } func UpdateTransitPointDB(ctx context.Context, db *gorm.DB, data *model.TransitPoint) (*model.TransitPoint, error) { span, spanCtx := opentracing.StartSpanFromContext(ctx, "updateTransitPointDB") defer span.Finish() payload := data.BeforeCheck(spanCtx) tx := db.WithContext(spanCtx).Begin().Scopes(payload.TableName(spanCtx)) if err := tx.Error; err != nil { return nil, err } // Update GeomWkt jika ada if payload.GeomWkt.String != "" { tx = tx.Model(&payload).Clauses(clause.Expr{ SQL: "geom = ST_GeomFromText(?, 4326)", Vars: []interface{}{payload.GeomWkt}, }) } tx = tx.Model(&payload).Where("id = ?", payload.ID).Updates(map[string]interface{}{ "address": payload.Address, "latitude": payload.Latitude, "longitude": payload.Longitude, "modified_by": payload.ModifiedBy, "modified_date": payload.ModifiedDate, }) if tx.Error != nil { tx.Rollback() return nil, tx.Error } if err := tx.Commit().Error; err != nil { tx.Rollback() return nil, err } return payload.Convert(spanCtx), nil } func GenerateGeomWkt(ctx context.Context, db *gorm.DB, lon, lat, bufferRadius float64) string { var value sql.NullString tx := db.WithContext(ctx) query := fmt.Sprintf("SELECT ST_AsText(ST_Buffer(ST_GeomFromText('POINT(%0.7f %0.7f)', 4326), 0.00001 * %v, 'quad_segs=2')) AS geom_text LIMIT 1", lon, lat, bufferRadius) tx = tx.Raw(query) tx.Row().Scan(&value) return strings.TrimSpace(value.String) } func GetTransitPoints(ctx context.Context, db *gorm.DB, filter string) ([]*model.TransitPoint, error) { tx := db.WithContext(ctx) query := fmt.Sprintf(`SELECT a.id,a.company_id,a.customer_id,a.customer_ids,a.trip_group_transit_point_id,a.status,a.blacklist ,a.latitude,a.longitude,a.register_date,a.unregister_date,a.created_date,a.modified_date ,a.name,a.code,a.geocode,a.address,a.created_by,a.modified_by,a.pic_name,a.pic_phone,a.pic_mobile ,a.transit_point_type,a.remark,a.coords,a.fax,a.mobile_no,a.phone,a.image,a.zip_code,a.geom FROM m_transit_point AS a WHERE a.name IS NOT NULL AND a.name <> '' %v`, filter) fmt.Printf("query : %v\n", query) rows, err := tx.Raw(query).Rows() if err != nil { return nil, err } defer rows.Close() data := []*model.TransitPoint{} for rows.Next() { dataScan := model.TransitPointDB{} err := rows.Scan( &dataScan.ID, &dataScan.CompanyID, &dataScan.CustomerID, &dataScan.CustomerIDs, &dataScan.TripGroupTransitPointID, &dataScan.Status, &dataScan.Blacklist, &dataScan.Latitude, &dataScan.Longitude, &dataScan.RegisterDate, &dataScan.UnregisterDate, &dataScan.CreatedDate, &dataScan.ModifiedDate, &dataScan.Name, &dataScan.Code, &dataScan.Geocode, &dataScan.Address, &dataScan.CreatedBy, &dataScan.ModifiedBy, &dataScan.PicName, &dataScan.PicPhone, &dataScan.PicMobile, &dataScan.TransitPointType, &dataScan.Remark, &dataScan.Coords, &dataScan.Fax, &dataScan.MobileNo, &dataScan.Phone, &dataScan.Image, &dataScan.ZipCode, &dataScan.GeomWkb, ) if err != nil { return nil, err } result := dataScan.Convert(ctx) data = append(data, result) } return data, nil }