diff --git a/backend/internal/domain/tenant_group.go b/backend/internal/domain/tenant_group.go deleted file mode 100644 index db4485a7..00000000 --- a/backend/internal/domain/tenant_group.go +++ /dev/null @@ -1,32 +0,0 @@ -package domain - -import ( - "time" - - "github.com/google/uuid" - "gorm.io/gorm" -) - -// TenantGroup represents a collection of tenants. -type TenantGroup struct { - ID string `gorm:"primaryKey;type:uuid;default:gen_random_uuid()" json:"id"` - Name string `gorm:"not null" json:"name"` - Slug string `gorm:"uniqueIndex;not null" json:"slug"` - Description string `json:"description"` - Tenants []Tenant `gorm:"foreignKey:TenantGroupID" json:"tenants,omitempty"` - Config JSONMap `gorm:"type:jsonb" json:"config,omitempty"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` -} - -func (tg *TenantGroup) TableName() string { - return "tenant_groups" -} - -func (tg *TenantGroup) BeforeCreate(tx *gorm.DB) (err error) { - if tg.ID == "" { - tg.ID = uuid.NewString() - } - return -} diff --git a/backend/internal/handler/tenant_group_handler.go b/backend/internal/handler/tenant_group_handler.go deleted file mode 100644 index f394b340..00000000 --- a/backend/internal/handler/tenant_group_handler.go +++ /dev/null @@ -1,193 +0,0 @@ -package handler - -import ( - "baron-sso-backend/internal/domain" - "baron-sso-backend/internal/service" - "time" - - "github.com/gofiber/fiber/v2" -) - -type TenantGroupHandler struct { - Service service.TenantGroupService - UserService *service.KratosAdminService -} - -func NewTenantGroupHandler(svc service.TenantGroupService, userSvc *service.KratosAdminService) *TenantGroupHandler { - return &TenantGroupHandler{Service: svc, UserService: userSvc} -} - -type tenantGroupSummary struct { - ID string `json:"id"` - Name string `json:"name"` - Slug string `json:"slug"` - Description string `json:"description"` - Tenants []tenantSummary `json:"tenants,omitempty"` - Config domain.JSONMap `json:"config,omitempty"` - CreatedAt string `json:"createdAt"` - UpdatedAt string `json:"updatedAt"` -} - -func (h *TenantGroupHandler) ListGroups(c *fiber.Ctx) error { - limit := c.QueryInt("limit", 50) - offset := c.QueryInt("offset", 0) - - groups, total, err := h.Service.ListGroups(c.Context(), limit, offset) - if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) - } - - items := make([]tenantGroupSummary, 0, len(groups)) - for _, g := range groups { - items = append(items, mapTenantGroupSummary(g)) - } - - return c.JSON(fiber.Map{ - "items": items, - "total": total, - "limit": limit, - "offset": offset, - }) -} - -func (h *TenantGroupHandler) GetGroup(c *fiber.Ctx) error { - id := c.Params("id") - group, err := h.Service.GetGroup(c.Context(), id) - if err != nil { - return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "group not found"}) - } - return c.JSON(mapTenantGroupSummary(*group)) -} - -func (h *TenantGroupHandler) CreateGroup(c *fiber.Ctx) error { - var req struct { - Name string `json:"name"` - Slug string `json:"slug"` - Description string `json:"description"` - } - if err := c.BodyParser(&req); err != nil { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"}) - } - - group, err := h.Service.CreateGroup(c.Context(), req.Name, req.Slug, req.Description) - if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) - } - return c.Status(fiber.StatusCreated).JSON(mapTenantGroupSummary(*group)) -} - -func (h *TenantGroupHandler) UpdateGroup(c *fiber.Ctx) error { - id := c.Params("id") - var req struct { - Name string `json:"name"` - Description string `json:"description"` - } - if err := c.BodyParser(&req); err != nil { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"}) - } - - group, err := h.Service.UpdateGroup(c.Context(), id, req.Name, req.Description) - if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) - } - return c.JSON(mapTenantGroupSummary(*group)) -} - -func (h *TenantGroupHandler) DeleteGroup(c *fiber.Ctx) error { - id := c.Params("id") - if err := h.Service.DeleteGroup(c.Context(), id); err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) - } - return c.SendStatus(fiber.StatusNoContent) -} - -func (h *TenantGroupHandler) AddTenantToGroup(c *fiber.Ctx) error { - groupID := c.Params("id") - tenantID := c.Params("tenantId") - - if err := h.Service.AddTenantToGroup(c.Context(), groupID, tenantID); err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) - } - return c.JSON(fiber.Map{"message": "tenant added to group"}) -} - -func (h *TenantGroupHandler) RemoveTenantFromGroup(c *fiber.Ctx) error { - groupID := c.Params("id") - tenantID := c.Params("tenantId") - - if err := h.Service.RemoveTenantFromGroup(c.Context(), groupID, tenantID); err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) - } - return c.JSON(fiber.Map{"message": "tenant removed from group"}) -} - -func (h *TenantGroupHandler) ListAdmins(c *fiber.Ctx) error { - groupID := c.Params("id") - userIDs, err := h.Service.ListGroupAdmins(c.Context(), groupID) - if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) - } - - type adminInfo struct { - ID string `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - } - - admins := make([]adminInfo, 0, len(userIDs)) - for _, uid := range userIDs { - identity, err := h.UserService.GetIdentity(c.Context(), uid) - if err == nil && identity != nil { - name, _ := identity.Traits["name"].(string) - email, _ := identity.Traits["email"].(string) - admins = append(admins, adminInfo{ - ID: uid, - Name: name, - Email: email, - }) - } else { - // Fallback if identity not found in Kratos - admins = append(admins, adminInfo{ID: uid}) - } - } - - return c.JSON(admins) -} - -func (h *TenantGroupHandler) AddAdmin(c *fiber.Ctx) error { - groupID := c.Params("id") - userID := c.Params("userId") - - if err := h.Service.AddGroupAdmin(c.Context(), groupID, userID); err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) - } - return c.JSON(fiber.Map{"message": "admin added to group"}) -} - -func (h *TenantGroupHandler) RemoveAdmin(c *fiber.Ctx) error { - groupID := c.Params("id") - userID := c.Params("userId") - - if err := h.Service.RemoveGroupAdmin(c.Context(), groupID, userID); err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) - } - return c.JSON(fiber.Map{"message": "admin removed from group"}) -} - -func mapTenantGroupSummary(g domain.TenantGroup) tenantGroupSummary { - tenants := make([]tenantSummary, 0, len(g.Tenants)) - for _, t := range g.Tenants { - tenants = append(tenants, mapTenantSummary(t)) - } - - return tenantGroupSummary{ - ID: g.ID, - Name: g.Name, - Slug: g.Slug, - Description: g.Description, - Tenants: tenants, - Config: g.Config, - CreatedAt: g.CreatedAt.Format(time.RFC3339), - UpdatedAt: g.UpdatedAt.Format(time.RFC3339), - } -} diff --git a/backend/internal/handler/tenant_rebac_handler_test.go b/backend/internal/handler/tenant_rebac_handler_test.go deleted file mode 100644 index 73fb39db..00000000 --- a/backend/internal/handler/tenant_rebac_handler_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package handler - -import ( - "baron-sso-backend/internal/domain" - "baron-sso-backend/internal/middleware" - "baron-sso-backend/internal/service" - "context" - "net/http" - "net/http/httptest" - "testing" - - "github.com/gofiber/fiber/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -// Reusing MockKetoService from previous step or defining here if needed -type MockKetoService struct { - mock.Mock -} - -func (m *MockKetoService) CheckPermission(ctx context.Context, subject, namespace, object, relation string) (bool, error) { - args := m.Called(ctx, subject, namespace, object, relation) - return args.Bool(0), args.Error(1) -} - -func (m *MockKetoService) CreateRelation(ctx context.Context, namespace, object, relation, subject string) error { - return m.Called(ctx, namespace, object, relation, subject).Error(0) -} - -func (m *MockKetoService) DeleteRelation(ctx context.Context, namespace, object, relation, subject string) error { - return m.Called(ctx, namespace, object, relation, subject).Error(0) -} - -func (m *MockKetoService) ListRelations(ctx context.Context, namespace, object, relation, subject string) ([]service.RelationTuple, error) { - args := m.Called(ctx, namespace, object, relation, subject) - return args.Get(0).([]service.RelationTuple), args.Error(1) -} - -func (m *MockKetoService) ListObjects(ctx context.Context, namespace, relation, subject string) ([]string, error) { - args := m.Called(ctx, namespace, relation, subject) - return args.Get(0).([]string), args.Error(1) -} - -// MockAuthHandler implements middleware.AuthProfileProvider -type MockAuthHandler struct { - mock.Mock -} - -func (m *MockAuthHandler) GetEnrichedProfile(c *fiber.Ctx) (*domain.UserProfileResponse, error) { - args := m.Called(c) - return args.Get(0).(*domain.UserProfileResponse), args.Error(1) -} - -func TestRequireKetoPermission_Tenant_AuditContext(t *testing.T) { - app := fiber.New() - mockKeto := new(MockKetoService) - mockAuth := new(MockAuthHandler) - - config := middleware.RBACConfig{ - AuthHandler: mockAuth, - KetoService: mockKeto, - } - - userID := "user-1" - tenantID := "tenant-abc" - - // Mock user profile - mockAuth.On("GetEnrichedProfile", mock.Anything).Return(&domain.UserProfileResponse{ - ID: userID, - Role: domain.RoleTenantAdmin, - }, nil) - - // Mock Keto: Allow access - mockKeto.On("CheckPermission", mock.Anything, userID, "Tenant", tenantID, "manage").Return(true, nil) - - // Route with middleware - app.Get("/test/tenants/:id", middleware.RequireKetoPermission(config, "Tenant", "manage"), func(c *fiber.Ctx) error { - // Verify that tenant_id was injected into Locals for audit log - assert.Equal(t, tenantID, c.Locals("tenant_id")) - return c.SendStatus(fiber.StatusOK) - }) - - // Execute - req := httptest.NewRequest("GET", "/test/tenants/"+tenantID, nil) - resp, _ := app.Test(req) - - // Verify - assert.Equal(t, http.StatusOK, resp.StatusCode) - mockKeto.AssertExpectations(t) - mockAuth.AssertExpectations(t) -} - -func TestRequireKetoPermission_Deny(t *testing.T) { - app := fiber.New() - mockKeto := new(MockKetoService) - mockAuth := new(MockAuthHandler) - - config := middleware.RBACConfig{ - AuthHandler: mockAuth, - KetoService: mockKeto, - } - - userID := "user-bad" - tenantID := "tenant-secret" - - mockAuth.On("GetEnrichedProfile", mock.Anything).Return(&domain.UserProfileResponse{ - ID: userID, - Role: domain.RoleUser, - }, nil) - - // Mock Keto: Deny access - mockKeto.On("CheckPermission", mock.Anything, userID, "Tenant", tenantID, "view").Return(false, nil) - - app.Get("/test/tenants/:id", middleware.RequireKetoPermission(config, "Tenant", "view"), func(c *fiber.Ctx) error { - return c.SendStatus(fiber.StatusOK) - }) - - req := httptest.NewRequest("GET", "/test/tenants/"+tenantID, nil) - resp, _ := app.Test(req) - - assert.Equal(t, http.StatusForbidden, resp.StatusCode) -} diff --git a/backend/internal/repository/tenant_group_repository.go b/backend/internal/repository/tenant_group_repository.go deleted file mode 100644 index 1f22e6b4..00000000 --- a/backend/internal/repository/tenant_group_repository.go +++ /dev/null @@ -1,65 +0,0 @@ -package repository - -import ( - "baron-sso-backend/internal/domain" - "context" - - "gorm.io/gorm" -) - -type TenantGroupRepository interface { - Create(ctx context.Context, group *domain.TenantGroup) error - Update(ctx context.Context, group *domain.TenantGroup) error - Delete(ctx context.Context, id string) error - FindByID(ctx context.Context, id string) (*domain.TenantGroup, error) - List(ctx context.Context, limit, offset int) ([]domain.TenantGroup, int64, error) - AddTenant(ctx context.Context, groupID, tenantID string) error - RemoveTenant(ctx context.Context, groupID, tenantID string) error -} - -type tenantGroupRepository struct { - db *gorm.DB -} - -func NewTenantGroupRepository(db *gorm.DB) TenantGroupRepository { - return &tenantGroupRepository{db: db} -} - -func (r *tenantGroupRepository) Create(ctx context.Context, group *domain.TenantGroup) error { - return r.db.WithContext(ctx).Create(group).Error -} - -func (r *tenantGroupRepository) Update(ctx context.Context, group *domain.TenantGroup) error { - return r.db.WithContext(ctx).Save(group).Error -} - -func (r *tenantGroupRepository) Delete(ctx context.Context, id string) error { - return r.db.WithContext(ctx).Delete(&domain.TenantGroup{}, "id = ?", id).Error -} - -func (r *tenantGroupRepository) FindByID(ctx context.Context, id string) (*domain.TenantGroup, error) { - var group domain.TenantGroup - if err := r.db.WithContext(ctx).Preload("Tenants").First(&group, "id = ?", id).Error; err != nil { - return nil, err - } - return &group, nil -} - -func (r *tenantGroupRepository) List(ctx context.Context, limit, offset int) ([]domain.TenantGroup, int64, error) { - var groups []domain.TenantGroup - var total int64 - db := r.db.WithContext(ctx).Model(&domain.TenantGroup{}) - db.Count(&total) - if err := db.Limit(limit).Offset(offset).Find(&groups).Error; err != nil { - return nil, 0, err - } - return groups, total, nil -} - -func (r *tenantGroupRepository) AddTenant(ctx context.Context, groupID, tenantID string) error { - return r.db.WithContext(ctx).Model(&domain.Tenant{}).Where("id = ?", tenantID).Update("tenant_group_id", groupID).Error -} - -func (r *tenantGroupRepository) RemoveTenant(ctx context.Context, groupID, tenantID string) error { - return r.db.WithContext(ctx).Model(&domain.Tenant{}).Where("id = ? AND tenant_group_id = ?", tenantID, groupID).Update("tenant_group_id", nil).Error -} diff --git a/backend/internal/service/tenant_group_service.go b/backend/internal/service/tenant_group_service.go deleted file mode 100644 index bafd32bc..00000000 --- a/backend/internal/service/tenant_group_service.go +++ /dev/null @@ -1,130 +0,0 @@ -package service - -import ( - "baron-sso-backend/internal/domain" - "baron-sso-backend/internal/repository" - "context" - "log/slog" -) - -type TenantGroupService interface { - CreateGroup(ctx context.Context, name, slug, description string) (*domain.TenantGroup, error) - GetGroup(ctx context.Context, id string) (*domain.TenantGroup, error) - ListGroups(ctx context.Context, limit, offset int) ([]domain.TenantGroup, int64, error) - UpdateGroup(ctx context.Context, id string, name, description string) (*domain.TenantGroup, error) - DeleteGroup(ctx context.Context, id string) error - AddTenantToGroup(ctx context.Context, groupID, tenantID string) error - RemoveTenantFromGroup(ctx context.Context, groupID, tenantID string) error - AddGroupAdmin(ctx context.Context, groupID, userID string) error - RemoveGroupAdmin(ctx context.Context, groupID, userID string) error - ListGroupAdmins(ctx context.Context, groupID string) ([]string, error) -} - -type tenantGroupService struct { - repo repository.TenantGroupRepository - keto KetoService -} - -func NewTenantGroupService(repo repository.TenantGroupRepository, keto KetoService) TenantGroupService { - return &tenantGroupService{repo: repo, keto: keto} -} - -func (s *tenantGroupService) CreateGroup(ctx context.Context, name, slug, description string) (*domain.TenantGroup, error) { - group := &domain.TenantGroup{ - Name: name, - Slug: slug, - Description: description, - } - if err := s.repo.Create(ctx, group); err != nil { - return nil, err - } - return group, nil -} - -func (s *tenantGroupService) GetGroup(ctx context.Context, id string) (*domain.TenantGroup, error) { - return s.repo.FindByID(ctx, id) -} - -func (s *tenantGroupService) ListGroups(ctx context.Context, limit, offset int) ([]domain.TenantGroup, int64, error) { - return s.repo.List(ctx, limit, offset) -} - -func (s *tenantGroupService) UpdateGroup(ctx context.Context, id string, name, description string) (*domain.TenantGroup, error) { - group, err := s.repo.FindByID(ctx, id) - if err != nil { - return nil, err - } - group.Name = name - group.Description = description - if err := s.repo.Update(ctx, group); err != nil { - return nil, err - } - return group, nil -} - -func (s *tenantGroupService) DeleteGroup(ctx context.Context, id string) error { - return s.repo.Delete(ctx, id) -} - -func (s *tenantGroupService) AddTenantToGroup(ctx context.Context, groupID, tenantID string) error { - if err := s.repo.AddTenant(ctx, groupID, tenantID); err != nil { - return err - } - - // [Keto] ReBAC: Tenant -> Group membership - if s.keto != nil { - err := s.keto.CreateRelation(ctx, "Tenant", tenantID, "parent_group", groupID) - if err != nil { - slog.Error("Failed to sync Keto relation for tenant group", "tenantID", tenantID, "groupID", groupID, "error", err) - } - } - return nil -} - -func (s *tenantGroupService) RemoveTenantFromGroup(ctx context.Context, groupID, tenantID string) error { - if err := s.repo.RemoveTenant(ctx, groupID, tenantID); err != nil { - return err - } - - // [Keto] ReBAC: Remove Tenant -> Group membership - if s.keto != nil { - err := s.keto.DeleteRelation(ctx, "Tenant", tenantID, "parent_group", groupID) - if err != nil { - slog.Error("Failed to remove Keto relation for tenant group", "tenantID", tenantID, "groupID", groupID, "error", err) - } - } - return nil -} - -func (s *tenantGroupService) AddGroupAdmin(ctx context.Context, groupID, userID string) error { - if s.keto == nil { - return nil - } - return s.keto.CreateRelation(ctx, "TenantGroup", groupID, "admins", "User:"+userID) -} - -func (s *tenantGroupService) RemoveGroupAdmin(ctx context.Context, groupID, userID string) error { - if s.keto == nil { - return nil - } - return s.keto.DeleteRelation(ctx, "TenantGroup", groupID, "admins", "User:"+userID) -} - -func (s *tenantGroupService) ListGroupAdmins(ctx context.Context, groupID string) ([]string, error) { - if s.keto == nil { - return []string{}, nil - } - tuples, err := s.keto.ListRelations(ctx, "TenantGroup", groupID, "admins", "") - if err != nil { - return nil, err - } - - userIDs := make([]string, 0, len(tuples)) - for _, t := range tuples { - // subject_id is "User:uuid" - if len(t.SubjectID) > 5 && t.SubjectID[:5] == "User:" { - userIDs = append(userIDs, t.SubjectID[5:]) - } - } - return userIDs, nil -} diff --git a/backend/internal/service/tenant_rebac_service_test.go b/backend/internal/service/tenant_rebac_service_test.go deleted file mode 100644 index 07c2f51d..00000000 --- a/backend/internal/service/tenant_rebac_service_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package service - -import ( - "baron-sso-backend/internal/domain" - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -// MockTenantRepository is a mock implementation of repository.TenantRepository -type MockTenantRepository struct { - mock.Mock -} - -func (m *MockTenantRepository) Create(ctx context.Context, tenant *domain.Tenant) error { - return m.Called(ctx, tenant).Error(0) -} - -func (m *MockTenantRepository) Update(ctx context.Context, tenant *domain.Tenant) error { - return m.Called(ctx, tenant).Error(0) -} - -func (m *MockTenantRepository) FindByID(ctx context.Context, id string) (*domain.Tenant, error) { - args := m.Called(ctx, id) - if args.Get(0) == nil { - return nil, args.Error(1) - } - return args.Get(0).(*domain.Tenant), args.Error(1) -} - -func (m *MockTenantRepository) FindBySlug(ctx context.Context, slug string) (*domain.Tenant, error) { - args := m.Called(ctx, slug) - return args.Get(0).(*domain.Tenant), args.Error(1) -} - -func (m *MockTenantRepository) FindByName(ctx context.Context, name string) (*domain.Tenant, error) { - args := m.Called(ctx, name) - return args.Get(0).(*domain.Tenant), args.Error(1) -} - -func (m *MockTenantRepository) FindByDomain(ctx context.Context, domainName string) (*domain.Tenant, error) { - args := m.Called(ctx, domainName) - return args.Get(0).(*domain.Tenant), args.Error(1) -} - -func (m *MockTenantRepository) FindByIDs(ctx context.Context, ids []string) ([]domain.Tenant, error) { - args := m.Called(ctx, ids) - return args.Get(0).([]domain.Tenant), args.Error(1) -} - -func (m *MockTenantRepository) AddDomain(ctx context.Context, tenantID string, domainName string) error { - return m.Called(ctx, tenantID, domainName).Error(0) -} - -func TestTenantService_ListManageableTenants_Inheritance(t *testing.T) { - mockRepo := new(MockTenantRepository) - mockKeto := new(MockKetoService) - svc := &tenantService{ - repo: mockRepo, - keto: mockKeto, - } - - userID := "user-123" - ctx := context.Background() - - // 1. Mock direct tenant management (admins relation) - mockKeto.On("ListObjects", ctx, "Tenant", "admins", userID).Return([]string{"t-direct-1"}, nil) - - // 2. Mock group management (admins of a group) - mockKeto.On("ListObjects", ctx, "TenantGroup", "admins", userID).Return([]string{"g-1"}, nil) - - // 3. Mock tenants belonging to group g-1 - mockKeto.On("ListRelations", ctx, "Tenant", "", "parent_group", "TenantGroup:g-1").Return([]RelationTuple{ - {Object: "t-inherited-1", Relation: "parent_group", SubjectID: "TenantGroup:g-1"}, - {Object: "t-inherited-2", Relation: "parent_group", SubjectID: "TenantGroup:g-1"}, - }, nil) - - // 4. Expect repository to fetch all unique IDs: t-direct-1, t-inherited-1, t-inherited-2 - expectedIDs := []string{"t-direct-1", "t-inherited-1", "t-inherited-2"} - mockRepo.On("FindByIDs", ctx, mock.MatchedBy(func(ids []string) bool { - // Check if all expected IDs are present (order doesn't matter since we dedup via map) - foundCount := 0 - for _, eid := range expectedIDs { - for _, id := range ids { - if id == eid { - foundCount++ - break - } - } - } - return foundCount == len(expectedIDs) && len(ids) == len(expectedIDs) - })).Return([]domain.Tenant{ - {ID: "t-direct-1", Name: "Direct Tenant"}, - {ID: "t-inherited-1", Name: "Inherited Tenant 1"}, - {ID: "t-inherited-2", Name: "Inherited Tenant 2"}, - }, nil) - - // Execute - tenants, err := svc.ListManageableTenants(ctx, userID) - - // Verify - assert.NoError(t, err) - assert.Len(t, tenants, 3) - mockKeto.AssertExpectations(t) - mockRepo.AssertExpectations(t) -} diff --git a/backend/internal/utils/env.go b/backend/internal/utils/env.go deleted file mode 100644 index 6e61dc32..00000000 --- a/backend/internal/utils/env.go +++ /dev/null @@ -1,22 +0,0 @@ -package utils - -import ( - "os" - "strings" -) - -// GetEnv retrieves the value of the environment variable named by the key. -// It returns the value if it exists, otherwise it returns the fallback value. -// It automatically strips surrounding double quotes from the value. -func GetEnv(key, fallback string) string { - v := os.Getenv(key) - if v == "" { - return fallback - } - // Strip surrounding double quotes if present - v = strings.TrimSpace(v) - if len(v) >= 2 && v[0] == '"' && v[len(v)-1] == '"' { - return v[1 : len(v)-1] - } - return v -}