Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 1 addition & 59 deletions internal/api/dashboard_org.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package api

import (
"log"
"net/http"

"github.com/google/uuid"
Expand Down Expand Up @@ -141,7 +140,7 @@ func (s *Server) dashboardSendInvitation(c echo.Context) error {
return c.JSON(http.StatusNotFound, map[string]string{"error": "org not found"})
}
if org.WorkOSOrgID == nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "org not linked to WorkOS — run backfill first"})
return c.JSON(http.StatusBadRequest, map[string]string{"error": "org not linked to WorkOS"})
}

// Get inviter's WorkOS user ID
Expand Down Expand Up @@ -380,60 +379,3 @@ func (s *Server) dashboardGetCredits(c echo.Context) error {
})
}

// dashboardBackfillWorkOSOrgs is a one-time admin endpoint to create WorkOS orgs
// for existing orgs that predate the WorkOS integration.
func (s *Server) dashboardBackfillWorkOSOrgs(c echo.Context) error {
if s.store == nil || s.workos == nil || s.workos.OrgMgr() == nil {
return c.JSON(http.StatusServiceUnavailable, map[string]string{"error": "not configured"})
}

ctx := c.Request().Context()
orgs, err := s.store.ListOrgsWithoutWorkOS(ctx)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}

type result struct {
OrgID uuid.UUID `json:"orgId"`
OrgName string `json:"orgName"`
WorkOSOrgID string `json:"workosOrgId,omitempty"`
Error string `json:"error,omitempty"`
}

var results []result
for _, org := range orgs {
workosOrgID, err := s.workos.OrgMgr().CreateOrganization(ctx, org.Name)
if err != nil {
log.Printf("backfill: failed to create WorkOS org for %s: %v", org.Name, err)
results = append(results, result{OrgID: org.ID, OrgName: org.Name, Error: err.Error()})
continue
}

if err := s.store.UpdateOrgWorkOSID(ctx, org.ID, workosOrgID); err != nil {
log.Printf("backfill: failed to update org %s with WorkOS ID: %v", org.Name, err)
results = append(results, result{OrgID: org.ID, OrgName: org.Name, Error: err.Error()})
continue
}

// Create WorkOS memberships for all users in this org
users, err := s.store.ListUsersByOrgID(ctx, org.ID)
if err == nil {
for _, user := range users {
if user.WorkOSUserID != nil {
_, err := s.workos.OrgMgr().CreateMembership(ctx, workosOrgID, *user.WorkOSUserID, "admin")
if err != nil {
log.Printf("backfill: failed to create membership for user %s in org %s: %v", user.Email, org.Name, err)
}
}
}
}

log.Printf("backfill: created WorkOS org %s for %s", workosOrgID, org.Name)
results = append(results, result{OrgID: org.ID, OrgName: org.Name, WorkOSOrgID: workosOrgID})
}

return c.JSON(http.StatusOK, map[string]interface{}{
"processed": len(results),
"results": results,
})
}
1 change: 0 additions & 1 deletion internal/api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,6 @@ func NewServer(mgr sandbox.Manager, ptyMgr *sandbox.PTYManager, apiKey string, o
dash.POST("/billing/redeem", s.billingRedeem)

// Admin endpoints
dash.POST("/admin/backfill-workos-orgs", s.dashboardBackfillWorkOSOrgs)

// Session detail + stats
dash.GET("/sessions/:sandboxId", s.dashboardGetSession)
Expand Down
2 changes: 1 addition & 1 deletion internal/auth/workos.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ func (w *WorkOSMiddleware) ProvisionOrgAndUser(ctx context.Context, email, name,
wid, err := w.orgManager.CreateOrganization(ctx, personalOrgName)
if err != nil {
log.Printf("workos: failed to create WorkOS org for %s: %v", email, err)
// Continue without WorkOS org — can be backfilled later
// Continue without WorkOS org
} else {
workosPersonalOrgID = wid
}
Expand Down
43 changes: 0 additions & 43 deletions internal/db/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -720,49 +720,6 @@ func (s *Store) SetOrgOwner(ctx context.Context, orgID, userID uuid.UUID) error
return err
}

// UpdateOrgWorkOSID sets the WorkOS org ID on an existing org (used for backfill).
func (s *Store) UpdateOrgWorkOSID(ctx context.Context, orgID uuid.UUID, workosOrgID string) error {
_, err := s.pool.Exec(ctx,
`UPDATE orgs SET workos_org_id = $1, updated_at = now() WHERE id = $2`,
workosOrgID, orgID)
return err
}

// UpdateUserWorkOSID sets the WorkOS user ID on an existing user (used for backfill).
func (s *Store) UpdateUserWorkOSID(ctx context.Context, userID uuid.UUID, workosUserID string) error {
_, err := s.pool.Exec(ctx,
`UPDATE users SET workos_user_id = $1 WHERE id = $2`,
workosUserID, userID)
return err
}

// ListOrgsWithoutWorkOS returns orgs that have no WorkOS org ID (for backfill).
func (s *Store) ListOrgsWithoutWorkOS(ctx context.Context) ([]*Org, error) {
rows, err := s.pool.Query(ctx,
`SELECT `+orgColumns+` FROM orgs WHERE workos_org_id IS NULL ORDER BY created_at`)
if err != nil {
return nil, err
}
defer rows.Close()

var orgs []*Org
for rows.Next() {
org := &Org{}
err := rows.Scan(
&org.ID, &org.Name, &org.Slug, &org.Plan, &org.MaxConcurrentSandboxes,
&org.MaxSandboxTimeoutSec, &org.CreatedAt, &org.UpdatedAt,
&org.CustomDomain, &org.CFHostnameID, &org.DomainVerificationStatus, &org.DomainSSLStatus,
&org.VerificationTxtName, &org.VerificationTxtValue, &org.SSLTxtName, &org.SSLTxtValue,
&org.WorkOSOrgID, &org.IsPersonal, &org.OwnerUserID, &org.CreditBalanceCents,
)
if err != nil {
return nil, err
}
orgs = append(orgs, org)
}
return orgs, nil
}

// GetUserByWorkOSID looks up a user by their WorkOS user ID.
func (s *Store) GetUserByWorkOSID(ctx context.Context, workosUserID string) (*User, error) {
user, err := scanUser(s.pool.QueryRow(ctx,
Expand Down