-
Notifications
You must be signed in to change notification settings - Fork 0
chore(deps): bump pinner for workspace label-slug op help #707
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| package cli | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "strconv" | ||
| "strings" | ||
|
|
||
| ipfs "go.lumeweb.com/ipfs-sdk" | ||
| "go.lumeweb.com/pinner" | ||
| "go.lumeweb.com/pinner/core/workspaces" | ||
| ) | ||
|
|
||
| // workspaces_label.go maps the workspace's opaque URL label (its user-facing | ||
| // identity) onto its sequential numeric ID. Generated labels settled on the | ||
| // bare slug (portal-plugin-ipfs#1040, e.g. "ugki684o" -> <slug>.build.<root>); | ||
| // legacy labels carry a "ws-" prefix (e.g. "ws-ugki684o"). The strip-fallback | ||
| // logic here accepts both, so present-day and post-#1040 workspaces answer to | ||
| // the same short form. | ||
|
|
||
| // workspaceLabelLegacyPrefix is the prefix stripped from legacy generated | ||
| // workspace labels ("ws-<slug>") to produce the short user-facing ID. | ||
| const workspaceLabelLegacyPrefix = "ws-" | ||
|
|
||
| // workspaceSlugFromLabel is the user-facing workspace ID implied by a label: | ||
| // the label with any legacy "ws-" prefix removed. | ||
| func workspaceSlugFromLabel(label string) string { | ||
| return strings.TrimPrefix(label, workspaceLabelLegacyPrefix) | ||
| } | ||
|
|
||
| // workspaceSlugID is the user-facing ID for a workspace response: the slug | ||
| // form of its label, falling back to the numeric ID when the label is empty. | ||
| func workspaceSlugID(w *ipfs.WorkspaceResponse) string { | ||
| if w == nil { | ||
| return "" | ||
| } | ||
| if w.Label == "" { | ||
| return strconv.Itoa(w.Id) | ||
| } | ||
| return workspaceSlugFromLabel(w.Label) | ||
| } | ||
|
|
||
| // workspaceScanPageSize is the page size used when scanning the user's | ||
| // workspace list to map a label onto its numeric ID. | ||
| const workspaceScanPageSize = 100 | ||
|
|
||
| // labelResolvingWorkspaces decorates a workspaces.Service so the | ||
| // single-workspace operations (get/attach/suspend/resume/access/delete) | ||
| // accept the workspace's label slug (or its full legacy "ws-<slug>" label) as | ||
| // the `id` argument, resolving it to the numeric ID by scanning the paged | ||
| // list. Label matching runs first for every input — including numeric-looking | ||
| // ones — so a purely numeric label slug is never shadowed by the numeric | ||
| // interpretation; an unmatched numeric id falls back to the numeric form the | ||
| // backend expects. Resolve failure says so explicitly rather than letting the | ||
| // backend 404 an unresolvable input. | ||
| // | ||
| // Both CLI and MCP surfaces build their workspaces catalog deps through this | ||
| // wrapper, so label-based control is consistent across frontends. | ||
| type labelResolvingWorkspaces struct { | ||
| inner workspaces.Service | ||
| } | ||
|
|
||
| // wrapLabelResolvingWorkspaces returns svc wrapped by labelResolvingWorkspaces | ||
| // (a nil inner is returned unchanged; catalogops rejects it downstream). | ||
| func wrapLabelResolvingWorkspaces(svc workspaces.Service) workspaces.Service { | ||
| if svc == nil { | ||
| return nil | ||
| } | ||
| return &labelResolvingWorkspaces{inner: svc} | ||
| } | ||
|
|
||
| func (s *labelResolvingWorkspaces) RequireAuthenticated() error { | ||
| return s.inner.RequireAuthenticated() | ||
| } | ||
|
|
||
| func (s *labelResolvingWorkspaces) SetAuthToken(token string) { | ||
| s.inner.SetAuthToken(token) | ||
| } | ||
|
|
||
| func (s *labelResolvingWorkspaces) List(ctx context.Context, opts workspaces.ListOptions) ([]ipfs.WorkspaceResponse, error) { | ||
| return s.inner.List(ctx, opts) | ||
| } | ||
|
|
||
| func (s *labelResolvingWorkspaces) Create(ctx context.Context, req ipfs.WorkspaceRequest) (*ipfs.WorkspaceResponse, error) { | ||
| return s.inner.Create(ctx, req) | ||
| } | ||
|
|
||
| func (s *labelResolvingWorkspaces) Get(ctx context.Context, id string) (*ipfs.WorkspaceResponse, error) { | ||
| resolved, err := s.resolveID(ctx, id) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return s.inner.Get(ctx, resolved) | ||
| } | ||
|
|
||
| func (s *labelResolvingWorkspaces) Delete(ctx context.Context, id string) (*ipfs.WorkspaceResponse, error) { | ||
| resolved, err := s.resolveID(ctx, id) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return s.inner.Delete(ctx, resolved) | ||
| } | ||
|
|
||
| func (s *labelResolvingWorkspaces) Access(ctx context.Context, id string, rotate bool) (*ipfs.WorkspaceAccessResponse, error) { | ||
| resolved, err := s.resolveID(ctx, id) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return s.inner.Access(ctx, resolved, rotate) | ||
| } | ||
|
|
||
| func (s *labelResolvingWorkspaces) Attach(ctx context.Context, id string, websiteID int) (*ipfs.WorkspaceResponse, error) { | ||
| resolved, err := s.resolveID(ctx, id) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return s.inner.Attach(ctx, resolved, websiteID) | ||
| } | ||
|
|
||
| func (s *labelResolvingWorkspaces) Resume(ctx context.Context, id string) (*ipfs.WorkspaceResponse, error) { | ||
| resolved, err := s.resolveID(ctx, id) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return s.inner.Resume(ctx, resolved) | ||
| } | ||
|
|
||
| func (s *labelResolvingWorkspaces) Suspend(ctx context.Context, id string) (*ipfs.WorkspaceResponse, error) { | ||
| resolved, err := s.resolveID(ctx, id) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return s.inner.Suspend(ctx, resolved) | ||
| } | ||
|
|
||
| // resolveID maps a workspace id argument to the numeric ID the backend | ||
| // expects. It matches the workspace's label slug (tolerating the legacy | ||
| // "ws-<slug>" form and the full label) by scanning the user's paged list — | ||
| // labels are matched before the numeric interpretation so a purely numeric | ||
| // label slug is reachable — and passes an unmatched numeric id through. | ||
| func (s *labelResolvingWorkspaces) resolveID(ctx context.Context, id string) (string, error) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. resolveID runs a full paged scan of the user's workspace list for every single-workspace operation, even when a plain numeric ID has no label match, turning one backend call into N sequential list HTTP calls per command with no caching. Short-circuit the scan when strconv.Atoi(id) succeeds and no workspace label could shadow the numeric ID, or return the numeric ID immediately on the first page without a label match. if _, err := strconv.Atoi(id); err == nil {
// numeric id: only scan if a workspace label could shadow it
if !s.labelMightShadowNumeric(id) {
return id, nil
}
}
ws, found, err := pinner.ScanPagesWithOptions(ctx, s, matchesLabel, nil, workspaceScanPageSize)
if err != nil {
return "", err
}
if found {
return strconv.Itoa(ws.Id), nil
}
if _, err := strconv.Atoi(id); err == nil {
return id, nil
}Prompt for LLMTalk to Kody by mentioning @kody Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction. |
||
| if id == "" { | ||
| return id, nil | ||
| } | ||
| matchesLabel := func(w ipfs.WorkspaceResponse) (bool, error) { | ||
| return workspaceSlugFromLabel(w.Label) == id || w.Label == id, nil | ||
| } | ||
| ws, found, err := pinner.ScanPagesWithOptions(ctx, s, matchesLabel, nil, workspaceScanPageSize) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
| if found { | ||
| return strconv.Itoa(ws.Id), nil | ||
| } | ||
| if _, err := strconv.Atoi(id); err == nil { | ||
| return id, nil | ||
| } | ||
| return "", fmt.Errorf("workspace %q not found", id) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renderWorkspacesListResult rebuilds ListResult with only Noun/Headers/Rows/Total, dropping the original Truncated state, so truncated workspace lists print "Found N workspace(s)" instead of "Showing N of M workspace(s)". Preserve the backend's Truncated/Count fields in the rebuilt ListResultMeta to keep the partial-count render.
Prompt for LLM
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.