package main import ( "io" "log" "net/http" "service-warungpos-go/config" "service-warungpos-go/controllers" "service-warungpos-go/middleware" "service-warungpos-go/models" "github.com/gin-gonic/gin" ) func main() { log.Println("Starting XOGM Go Service...") // 1. Muat Config config.LoadConfig() // 2. Koneksi Database Supabase config.ConnectDatabase() // 3. Auto-Migration GORM untuk kelancaran Supabase DB log.Println("Running Auto-Migration...") err := config.DB.AutoMigrate( &models.Company{}, &models.Branch{}, &models.User{}, &models.Category{}, &models.Product{}, &models.BranchProduct{}, &models.Order{}, &models.OrderItem{}, &models.OTP{}, &models.Setting{}, &models.Member{}, &models.PointRule{}, &models.WalletTransaction{}, &models.TopupRequest{}, &models.Voucher{}, &models.PointHistory{}, &models.UserSession{}, ) if err != nil { log.Fatalf("Gagal melakukan Auto-Migration: %v", err) } log.Println("Auto-Migration Selesai dengan Sukses!") // 4. Seeding super_admin default jika belum ada controllers.SeedDefaultAdmin() controllers.SeedDefaultSetting() // 5. Tripay integration (No SDK init needed, we use plain HTTP Client!) // 6. Router Setup menggunakan Gin r := gin.New() // Global Middlewares r.Use(gin.Logger()) r.Use(gin.Recovery()) r.Use(middleware.CORSMiddleware()) // Health Checks & Health check root r.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "service": "XOGM Go API (Go Version)", "version": "1.0.0", "status": "running", "docs": "/docs", }) }) r.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "healthy"}) }) r.GET("/api/ip", func(c *gin.Context) { resp, err := http.Get("https://api.ipify.org") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Gagal mendapatkan IP outbound: " + err.Error()}) return } defer resp.Body.Close() ipBytes, err := io.ReadAll(resp.Body) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Gagal membaca IP response: " + err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "public_ip": string(ipBytes), "info": "Gunakan IP ini untuk di-whitelist di Tripay", }) }) // API Routing Group api := r.Group("/api") { // === AUTHENTICATION ROUTES === auth := api.Group("/auth") { auth.POST("/login", controllers.Login) auth.POST("/register-tenant", controllers.RegisterTenant) auth.POST("/verify-otp", controllers.VerifyOTP) auth.POST("/resend-otp", controllers.ResendOTP) auth.POST("/forgot-password", controllers.ForgotPassword) auth.POST("/reset-password", controllers.ResetPassword) // Authenticated Auth Routes auth.Use(middleware.GetCurrentUser()) { auth.GET("/me", controllers.GetMe) auth.POST("/logout", controllers.Logout) auth.POST("/register", middleware.RequireSuperAdmin(), controllers.Register) auth.GET("/users", middleware.RequireSuperAdmin(), controllers.ListUsers) auth.PUT("/users/:user_id", middleware.RequireSuperAdmin(), controllers.UpdateUser) // Owner routes for kasir auth.GET("/kasir", middleware.RequireOwner(), controllers.ListKasir) auth.POST("/kasir", middleware.RequireOwner(), controllers.CreateKasir) auth.PUT("/kasir/:user_id", middleware.RequireOwner(), controllers.UpdateKasir) } } // === COMPANY & BRANCH ROUTES === companies := api.Group("/companies") { companies.Use(middleware.GetCurrentUser()) { companies.GET("/me", controllers.GetMyCompany) companies.PUT("/me/points", controllers.UpdateMyCompanyPoints) companies.GET("/", middleware.RequireSuperAdmin(), controllers.ListCompanies) companies.GET("/:company_id", middleware.RequireSuperAdmin(), controllers.GetCompany) companies.POST("/", middleware.RequireSuperAdmin(), controllers.CreateCompany) companies.PUT("/:company_id", middleware.RequireSuperAdmin(), controllers.UpdateCompany) companies.PUT("/:company_id/toggle-active", middleware.RequireSuperAdmin(), controllers.ToggleActiveCompany) companies.DELETE("/:company_id", middleware.RequireSuperAdmin(), controllers.DeleteCompany) companies.PUT("/:company_id/reject", middleware.RequireSuperAdmin(), controllers.RejectCompany) companies.PUT("/:company_id/approve", middleware.RequireSuperAdmin(), controllers.ApproveCompany) } } branches := api.Group("/branches") { branches.Use(middleware.GetCurrentUser(), middleware.RequireOwner()) { branches.GET("/", controllers.ListBranches) branches.POST("/", controllers.CreateBranch) branches.PUT("/:branch_id", controllers.UpdateBranch) branches.DELETE("/:branch_id", controllers.DeleteBranch) branches.PUT("/:branch_id/toggle", controllers.ToggleBranchStatus) branches.GET("/:branch_id/products", controllers.GetBranchProducts) branches.PUT("/:branch_id/products/:product_id", controllers.ToggleProductAvailability) } } // === MEMBERS ROUTES === members := api.Group("/members") { members.Use(middleware.GetCurrentUser(), middleware.RequireTenantContext()) { members.GET("/check", controllers.CheckMember) members.GET("/", controllers.ListMembers) members.POST("/", controllers.CreateMember) members.PUT("/:member_id", controllers.UpdateMember) members.PUT("/:member_id/points", middleware.RequireOwner(), controllers.SetPoints) members.GET("/:member_id/points/history", controllers.GetPointHistory) members.DELETE("/:member_id", middleware.RequireOwner(), controllers.DeleteMember) } } // === PRODUCTS & CATEGORIES ROUTES === products := api.Group("/products") { products.Use(middleware.GetCurrentUser()) { products.POST("/upload-image", middleware.RequireOwner(), controllers.UploadProductImage) products.GET("/", middleware.RequireTenantContext(), controllers.ListProducts) products.GET("/:product_id", middleware.RequireTenantContext(), controllers.GetProduct) products.POST("/", middleware.RequireOwner(), controllers.CreateProduct) products.PUT("/:product_id", middleware.RequireOwner(), controllers.UpdateProduct) products.DELETE("/:product_id", middleware.RequireOwner(), controllers.DeleteProduct) } } categories := api.Group("/categories") { categories.Use(middleware.GetCurrentUser()) { categories.GET("/", middleware.RequireTenantContext(), controllers.ListCategories) categories.GET("/:category_id", middleware.RequireTenantContext(), controllers.GetCategory) categories.POST("/", middleware.RequireOwner(), controllers.CreateCategory) categories.PUT("/:category_id", middleware.RequireOwner(), controllers.UpdateCategory) categories.DELETE("/:category_id", middleware.RequireOwner(), controllers.DeleteCategory) } } // === ORDERS ROUTES === orders := api.Group("/orders") { orders.Use(middleware.GetCurrentUser(), middleware.RequireTenantContext()) { orders.GET("/", controllers.ListOrders) orders.GET("/:order_id", controllers.GetOrder) orders.POST("/", controllers.CreateOrder) orders.POST("/pay/cash", controllers.PayCash) orders.POST("/pay/midtrans", controllers.PayTripay) } } vouchers := api.Group("/vouchers") { vouchers.Use(middleware.GetCurrentUser()) { vouchers.GET("/", controllers.GetVouchers) vouchers.POST("/", controllers.CreateVoucher) vouchers.PUT("/:id", controllers.UpdateVoucher) vouchers.DELETE("/:id", controllers.DeleteVoucher) } } // === PAYMENTS ROUTES === payments := api.Group("/payments") { // Webhook Tripay harus publik agar server Tripay bisa mengirim callbacks payments.POST("/webhook", controllers.TripayWebhook) payments.Use(middleware.GetCurrentUser(), middleware.RequireTenantContext()) { payments.POST("/cash", controllers.PayCash) payments.POST("/midtrans", controllers.PayTripay) // Keep /midtrans for backwards compatibility with front-end payments.POST("/tripay", controllers.PayTripay) // Also expose /tripay natively payments.GET("/:order_id/status", controllers.GetPaymentStatus) payments.POST("/:order_id/cancel", controllers.CancelMidtransOrder) } } // === REPORTS ROUTES === reports := api.Group("/reports") { reports.Use(middleware.GetCurrentUser(), middleware.RequireTenantContext()) { reports.GET("/daily", controllers.DailyReport) reports.GET("/summary", controllers.SummaryReport) } } // === FEES (SUPER ADMIN ONLY) ROUTES === fees := api.Group("/fees") { fees.Use(middleware.GetCurrentUser(), middleware.RequireSuperAdmin()) { fees.GET("/report", controllers.GetFeeReport) fees.PUT("/companies/:company_id/set-fee", controllers.SetCompanyFee) } } // === WALLET ROUTES === wallet := api.Group("/wallet") { wallet.Use(middleware.GetCurrentUser(), middleware.RequireTenantContext()) { wallet.GET("", controllers.GetWalletDetail) wallet.POST("/topup/manual", controllers.RequestTopupManual) wallet.POST("/topup/qris", controllers.RequestTopupQris) } } // === ADMIN TOPUP & WALLET ROUTES === adminTopups := api.Group("/admin/topups") { adminTopups.Use(middleware.GetCurrentUser(), middleware.RequireSuperAdmin()) { adminTopups.GET("", controllers.AdminGetPendingTopups) adminTopups.POST("/:id/approve", controllers.AdminApproveTopup) adminTopups.POST("/:id/reject", controllers.AdminRejectTopup) } } adminCompanies := api.Group("/admin/companies") { adminCompanies.Use(middleware.GetCurrentUser(), middleware.RequireSuperAdmin()) { adminCompanies.POST("/:id/adjust-wallet", controllers.AdminAdjustWallet) adminCompanies.PUT("/:id/settings", controllers.AdminUpdateCompanySettings) } } } // 7. Mulai Server (Membaca dinamis port Hugging Face Spaces jika ada) serverPort := config.GlobalConfig.Port if serverPort == "" { serverPort = "8003" } log.Printf("XOGM Go Server berjalan di port: %s", serverPort) if err := r.Run(":" + serverPort); err != nil { log.Fatalf("Gagal menjalankan server Gin: %v", err) } } // trigger rebuild