package workspace import ( "context" "database/sql" "encoding/json" "net/http" "proxui/backend/internal/auth" ) type Repository struct { db *sql.DB } func NewRepository(db *sql.DB) Repository { return Repository{db: db} } func (r Repository) GetWorkspace(ctx context.Context, profileID string) (Workspace, error) { rows, err := r.db.QueryContext(ctx, ` select t.id::text, t.name, t.slug, m.role::text, coalesce(p.id::text, '') as project_id, coalesce(p.name, '') as project_name from public.tenants t join public.memberships m on m.tenant_id = t.id and m.profile_id = $1 left join public.projects p on p.tenant_id = t.id where t.status = 'active' order by t.name asc, p.name asc `, profileID) if err != nil { return Workspace{}, err } defer rows.Close() tenantMap := make(map[string]*Tenant) tenantOrder := []string{} for rows.Next() { var tenantID, tenantName, tenantSlug, role, projectID, projectName string if err := rows.Scan(&tenantID, &tenantName, &tenantSlug, &role, &projectID, &projectName); err != nil { return Workspace{}, err } tenant, exists := tenantMap[tenantID] if !exists { tenant = &Tenant{ ID: tenantID, Name: tenantName, Slug: tenantSlug, Role: role, Projects: []Project{}, } tenantMap[tenantID] = tenant tenantOrder = append(tenantOrder, tenantID) } if projectID != "" { tenant.Projects = append(tenant.Projects, Project{ ID: projectID, Name: projectName, TenantID: tenantID, }) } } if err := rows.Err(); err != nil { return Workspace{}, err } tenants := make([]Tenant, 0, len(tenantOrder)) for _, id := range tenantOrder { tenants = append(tenants, *tenantMap[id]) } return Workspace{Tenants: tenants}, nil } type Workspace struct { Tenants []Tenant `json:"tenants"` } type Tenant struct { ID string `json:"id"` Name string `json:"name"` Slug string `json:"slug"` Role string `json:"role"` Projects []Project `json:"projects"` } type Project struct { ID string `json:"id"` Name string `json:"name"` TenantID string `json:"tenant_id"` } type Handler struct { repository Repository } func NewHandler(repository Repository) Handler { return Handler{repository: repository} } func (h Handler) GetWorkspace(w http.ResponseWriter, r *http.Request) { principal, ok := auth.PrincipalFromRequest(r) if !ok { writeError(w, http.StatusUnauthorized, "unauthorized") return } workspace, err := h.repository.GetWorkspace(r.Context(), principal.Subject) if err != nil { writeError(w, http.StatusInternalServerError, "workspace_failed") return } writeJSON(w, http.StatusOK, workspace) } func writeJSON(w http.ResponseWriter, status int, body any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(body) } func writeError(w http.ResponseWriter, status int, message string) { writeJSON(w, status, map[string]string{"error": message}) }