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
24 changes: 23 additions & 1 deletion internal/api/workspace_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ func (h *workspaceHandler) get(w http.ResponseWriter, r *http.Request) {
respondJSON(w, http.StatusOK, resp)
}

// demoUserWorkspaceQuota caps how many workspaces the shared public-demo
// user can own concurrently. Prevents pollution of the demo instance from
// viral traffic. TODO(#102): replace with proper plans/quotas system.
const demoUserWorkspaceQuota = 5

func (h *workspaceHandler) create(w http.ResponseWriter, r *http.Request) {
var body struct {
Name string `json:"name"`
Expand All @@ -78,13 +83,30 @@ func (h *workspaceHandler) create(w http.ResponseWriter, r *http.Request) {
respondError(w, http.StatusBadRequest, errorf("name and purpose are required"))
return
}

// Enforce demo user workspace quota (see #102 for planned refactor).
claims := auth.ClaimsFromCtx(r.Context())
if claims != nil && h.svc.Cfg.DemoMode && claims.Email == h.svc.Cfg.DemoEmail {
n, err := h.store.CountOwnedByUser(claims.UserID)
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return
}
if n >= demoUserWorkspaceQuota {
respondError(w, http.StatusTooManyRequests, errorf(
"demo limit reached (%d workspaces). Deploy your own instance for unlimited workspaces — star us on GitHub: https://github.com/DisruptiveWorks/archipulse",
demoUserWorkspaceQuota))
return
}
}

ws, err := h.store.Create(body.Name, body.Purpose, body.Description)
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return
}
// Seed the creating user as workspace owner.
if claims := auth.ClaimsFromCtx(r.Context()); claims != nil {
if claims != nil {
_ = h.svc.Enforcer.SeedOwner(ws.ID.String(), claims.UserID)
}
respondJSON(w, http.StatusCreated, ws)
Expand Down
15 changes: 15 additions & 0 deletions internal/workspace/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,21 @@ func (s *Store) ListForUser(userID string, isAdmin bool) ([]Workspace, error) {
return out, rows.Err()
}

// CountOwnedByUser returns the number of workspaces where the user has the
// 'owner' role. Used by handlers that need to enforce per-user quotas
// (e.g. the demo user workspace limit). See issue #102 for the planned
// refactor to a proper plans/quotas system.
func (s *Store) CountOwnedByUser(userID string) (int, error) {
var n int
err := s.db.QueryRow(`
SELECT COUNT(*) FROM workspace_members
WHERE user_id = $1 AND role = 'owner'`, userID).Scan(&n)
if err != nil {
return 0, fmt.Errorf("count workspaces owned by user: %w", err)
}
return n, nil
}

// Get returns a single workspace by ID.
func (s *Store) Get(id uuid.UUID) (*Workspace, error) {
var w Workspace
Expand Down
Loading