diff --git a/config.sample.yml b/config.sample.yml index 94987c8..5672ba2 100755 --- a/config.sample.yml +++ b/config.sample.yml @@ -20,6 +20,8 @@ server: name: "iSend.to" host: localhost port: 8080 + # URL pública para enlaces en correos, Telegram y webhooks (producción) + public_url: https://admin.u-site.app redis: instances: diff --git a/config.yml b/config.yml index cc1dce0..5d9e710 100755 --- a/config.yml +++ b/config.yml @@ -30,6 +30,7 @@ server: &server name: "USITE " host: 0.0.0.0 port: 8080 + public_url: https://admin.u-site.app profiler: enabled: false diff --git a/config/server.go b/config/server.go index 988188b..f97fe4e 100755 --- a/config/server.go +++ b/config/server.go @@ -28,6 +28,7 @@ type ServerConfig struct { Env string `mapstructure:"APP_ENV" yaml:"env" env:"APP_ENV" env-default:"dev"` Key string `mapstructure:"APP_KEY" yaml:"key" env:"APP_KEY" env-default:"1894cde6c936a294a478cff0a9227fd276d86df6573b51af5dc59c9064edf426"` Url string `mapstructure:"APP_URL" yaml:"url" env:"APP_URL" env-default:"http://localhost"` + PublicUrl string `mapstructure:"APP_PUBLIC_URL" yaml:"public_url" env:"APP_PUBLIC_URL"` Host string `mapstructure:"APP_HOST" yaml:"host" env:"APP_HOST" env-default:"localhost"` Port string `mapstructure:"APP_PORT" yaml:"port" env:"APP_PORT" env-default:"8080"` Path string `mapstructure:"APP_PATH" yaml:"path" env:"APP_PATH"` diff --git a/pkg/services/app_url.go b/pkg/services/app_url.go index 8f64d51..8e33a4f 100644 --- a/pkg/services/app_url.go +++ b/pkg/services/app_url.go @@ -10,8 +10,21 @@ import ( "github.com/sujit-baniya/fiber-boilerplate/app" ) -// getAppURL devuelve la URL base absoluta (sin slash final). -// Si APP_URL es http://localhost sin puerto, añade APP_PORT para enlaces en desarrollo. +// GetPublicURL devuelve la URL pública para enlaces en correos, Telegram y webhooks. +func GetPublicURL() string { + return getPublicURL() +} + +// getPublicURL usa APP_PUBLIC_URL si está configurada; si no, APP_URL (con puerto en localhost). +func getPublicURL() string { + if u := normalizeBaseURL(app.Http.Server.PublicUrl); u != "" { + return u + } + return getAppURL() +} + +// getAppURL devuelve la URL base de la app (sin slash final). +// Si APP_URL es http://localhost sin puerto, añade APP_PORT para desarrollo local. func getAppURL() string { u := strings.TrimRight(app.Http.Server.Url, "/") if u == "" { @@ -33,10 +46,21 @@ func getAppURL() string { return u } -// absAppURL convierte una ruta relativa en URL absoluta para correo/Telegram. +func normalizeBaseURL(raw string) string { + u := strings.TrimSpace(strings.TrimRight(raw, "/")) + if u == "" { + return "" + } + if !strings.Contains(u, "://") { + u = "https://" + u + } + return strings.TrimRight(u, "/") +} + +// absAppURL convierte una ruta relativa en URL absoluta pública (correo/Telegram). func absAppURL(path string) string { if path == "" { - return getAppURL() + return getPublicURL() } if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") { return path @@ -44,7 +68,7 @@ func absAppURL(path string) string { if !strings.HasPrefix(path, "/") { path = "/" + path } - return getAppURL() + path + return getPublicURL() + path } func adminTicketPath(ticketID uint) string { diff --git a/pkg/services/app_url_test.go b/pkg/services/app_url_test.go index acbadbe..2fc9b40 100644 --- a/pkg/services/app_url_test.go +++ b/pkg/services/app_url_test.go @@ -19,7 +19,35 @@ func TestGetAppURLLocalhostAddsPort(t *testing.T) { } } -func TestAbsAppURLRelativePath(t *testing.T) { +func TestGetPublicURLPrefersPublicURL(t *testing.T) { + app.Http = &config.AppConfig{ + Server: config.ServerConfig{ + Url: "http://localhost:8080", + PublicUrl: "admin.u-site.app", + Port: "8080", + }, + } + if got := getPublicURL(); got != "https://admin.u-site.app" { + t.Fatalf("getPublicURL() = %q, want https://admin.u-site.app", got) + } +} + +func TestAbsAppURLUsesPublicURL(t *testing.T) { + app.Http = &config.AppConfig{ + Server: config.ServerConfig{ + Url: "http://localhost:8080", + PublicUrl: "https://admin.u-site.app", + Port: "8080", + }, + } + got := absAppURL("/portal/proyecto/bot?tab=Tickets&ticket=1") + want := "https://admin.u-site.app/portal/proyecto/bot?tab=Tickets&ticket=1" + if got != want { + t.Fatalf("absAppURL() = %q, want %q", got, want) + } +} + +func TestAbsAppURLFallsBackToAppURL(t *testing.T) { app.Http = &config.AppConfig{ Server: config.ServerConfig{ Url: "http://localhost", @@ -34,8 +62,8 @@ func TestAbsAppURLRelativePath(t *testing.T) { } func TestTelegramHTMLLinkEscapesAmpersand(t *testing.T) { - link := telegramHTMLLink("http://localhost:8084/portal/p?tab=Tickets&ticket=1", "Ver portal") - if link != `Ver portal` { + link := telegramHTMLLink("https://admin.u-site.app/portal/p?tab=Tickets&ticket=1", "Ver portal") + if link != `Ver portal` { t.Fatalf("telegramHTMLLink() = %q", link) } } diff --git a/pkg/services/mail_service.go b/pkg/services/mail_service.go index fc3f0cd..50f37e3 100755 --- a/pkg/services/mail_service.go +++ b/pkg/services/mail_service.go @@ -80,7 +80,7 @@ func GeneratePasswordResetURL(email string, baseURL string) string { // SendPortalCredentialsEmail envía al usuario del portal su URL de acceso y contraseña inicial. func SendPortalCredentialsEmail(email, nombre, password string) { - loginURL := fmt.Sprintf("%s/portal/login", app.Http.Server.Url) + loginURL := absAppURL("/portal/login") htmlBody := fmt.Sprintf(`
diff --git a/rest/controllers/api/auth_controller.go b/rest/controllers/api/auth_controller.go index 92bac05..ee45f86 100755 --- a/rest/controllers/api/auth_controller.go +++ b/rest/controllers/api/auth_controller.go @@ -84,7 +84,7 @@ func ApiRegisterPost(c *fiber.Ctx) error { store.Set("user_id", user.ID) // save to storage _ = store.Save() - go services.SendConfirmationEmail(user.Email, c.BaseURL(), user.NombreUsuario) + go services.SendConfirmationEmail(user.Email, services.GetPublicURL(), user.NombreUsuario) return c.JSON(fiber.Map{ "success": true, "message": "Registered successfully! Please confirm your email", diff --git a/rest/controllers/register_controller.go b/rest/controllers/register_controller.go index dc7c8a1..4422e32 100755 --- a/rest/controllers/register_controller.go +++ b/rest/controllers/register_controller.go @@ -42,7 +42,7 @@ func RegisterPost(c *fiber.Ctx) error { store.Set("user_id", user.ID) // guardar en el almacenamiento _ = store.Save() - go services.SendConfirmationEmail(user.Email, app.Http.Server.Url, user.NombreUsuario) + go services.SendConfirmationEmail(user.Email, services.GetPublicURL(), user.NombreUsuario) return c.Redirect("/") } @@ -55,7 +55,7 @@ func VerifyRegisteredEmail(c *fiber.Ctx) error { // Función para reenviar el correo de confirmación func ResendConfirmEmail(c *fiber.Ctx) error { user, _ := auth.User(c) - go services.SendConfirmationEmail(user.Email, app.Http.Server.Url, user.NombreUsuario) + go services.SendConfirmationEmail(user.Email, services.GetPublicURL(), user.NombreUsuario) return c.Redirect("/") } @@ -70,7 +70,7 @@ func ReenvioEmail(c *fiber.Ctx) error { } // Reenvío del correo electrónico de forma asíncrona - go services.SendConfirmationEmail(user.Email, app.Http.Server.Url, user.NombreUsuario) + go services.SendConfirmationEmail(user.Email, services.GetPublicURL(), user.NombreUsuario) return c.Status(fiber.StatusOK).JSON(fiber.Map{ "success": true, @@ -94,7 +94,7 @@ func RequestPasswordResetPost(c *fiber.Ctx) error { usuario_email := user.Email log.Println("Sending password reset email to:", usuario_email) - go services.SendPasswordResetEmail(usuario_email, app.Http.Server.Url) + go services.SendPasswordResetEmail(usuario_email, services.GetPublicURL()) return c.JSON(fiber.Map{ "success": true, diff --git a/rest/controllers/telegram_controller.go b/rest/controllers/telegram_controller.go index 642d221..650b470 100755 --- a/rest/controllers/telegram_controller.go +++ b/rest/controllers/telegram_controller.go @@ -12,8 +12,8 @@ import ( "strings" "github.com/gofiber/fiber/v2" - "github.com/sujit-baniya/fiber-boilerplate/app" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" + "github.com/sujit-baniya/fiber-boilerplate/pkg/services" ) // TelegramIndex renderiza la vista de configuración de Telegram. @@ -222,12 +222,7 @@ func SetPortalWebhook(c *fiber.Ctx) error { return c.Status(404).JSON(fiber.Map{"error": "Configuración no encontrada"}) } - // Construir URL base desde la configuración de la app - baseURL := app.Http.Server.Url - if len(baseURL) > 0 && baseURL[len(baseURL)-1] == '/' { - baseURL = baseURL[:len(baseURL)-1] - } - webhookURL := baseURL + "/webhooks/telegram-portal" + webhookURL := services.GetPublicURL() + "/webhooks/telegram-portal" apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/setWebhook", cfg.BotToken) payload, _ := json.Marshal(map[string]string{"url": webhookURL}) diff --git a/rest/controllers/users_controller.go b/rest/controllers/users_controller.go index 60073bf..5957740 100755 --- a/rest/controllers/users_controller.go +++ b/rest/controllers/users_controller.go @@ -486,7 +486,7 @@ func CreateUserGas(c *fiber.Ctx) error { } // Envía un correo de confirmación - go services.SendConfirmationEmail(m.Email, app.Http.Server.Url, m.NombreUsuario) + go services.SendConfirmationEmail(m.Email, services.GetPublicURL(), m.NombreUsuario) return c.Status(fiber.StatusCreated).JSON(fiber.Map{ "message": "Usuario creado con éxito",