package client import ( "context" "net/http" "net/url" "strconv" "github.com/iceBear67/simplepages/api" ) // ---------------------------------------------------------------- identity // WhoAmI describes the credential the client is using. func (c *Client) WhoAmI(ctx context.Context) (api.WhoAmI, error) { var out api.WhoAmI err := c.do(ctx, http.MethodGet, api.PathWhoAmI(), nil, &out) return out, err } // SystemInfo returns server-wide counters. Admin scope only. func (c *Client) SystemInfo(ctx context.Context) (api.SystemInfo, error) { var out api.SystemInfo err := c.do(ctx, http.MethodGet, api.PathSystemInfo(), nil, &out) return out, err } // ---------------------------------------------------------------- projects // CreateProject creates a project. Admin scope only. func (c *Client) CreateProject(ctx context.Context, req api.CreateProjectRequest) (api.Project, error) { var out api.Project err := c.do(ctx, http.MethodPost, api.PathProjects(), req, &out) return out, err } // ListOptions pages a listing endpoint. A zero Limit takes the server default. type ListOptions struct { Limit int Cursor string } func (o ListOptions) query() string { q := url.Values{} if o.Limit > 0 { q.Set("limit", strconv.Itoa(o.Limit)) } if o.Cursor != "" { q.Set("cursor", o.Cursor) } if len(q) == 0 { return "" } return "?" + q.Encode() } // ListProjects returns one page of projects. Admin scope only. func (c *Client) ListProjects(ctx context.Context, opts ListOptions) (api.ProjectList, error) { var out api.ProjectList err := c.do(ctx, http.MethodGet, api.PathProjects()+opts.query(), nil, &out) return out, err } // ListAllProjects follows the cursor to the end. // // The CLI pages on the user's behalf because "pages project list" that silently // showed the first hundred of three hundred projects would be a lie; a caller // that wants one page asks for ListProjects. func (c *Client) ListAllProjects(ctx context.Context) ([]api.Project, error) { var all []api.Project opts := ListOptions{Limit: 500} for { page, err := c.ListProjects(ctx, opts) if err != nil { return nil, err } all = append(all, page.Projects...) if page.NextCursor == "" || len(page.Projects) == 0 { return all, nil } opts.Cursor = page.NextCursor } } // GetProject reads one project. A project-scoped key may read only its own. func (c *Client) GetProject(ctx context.Context, name string) (api.Project, error) { var out api.Project err := c.do(ctx, http.MethodGet, api.PathProject(name), nil, &out) return out, err } // PatchProject applies a partial update. Admin scope only. func (c *Client) PatchProject(ctx context.Context, name string, patch api.ProjectPatch) (api.Project, error) { var out api.Project err := c.do(ctx, http.MethodPatch, api.PathProject(name), patch, &out) return out, err } // DeleteProject removes a project and everything under it. Admin scope only. func (c *Client) DeleteProject(ctx context.Context, name string) error { return c.do(ctx, http.MethodDelete, api.PathProject(name), nil, nil) } // -------------------------------------------------------------------- keys // CreateAdminKey mints an admin key. Admin scope only. // // The returned token is the only copy that will ever exist; the caller shows it // once and must not log it. func (c *Client) CreateAdminKey(ctx context.Context, req api.CreateKeyRequest) (api.CreateKeyResponse, error) { var out api.CreateKeyResponse err := c.do(ctx, http.MethodPost, api.PathKeys(), req, &out) return out, err } // CreateProjectKey mints a key scoped to one project. Admin scope only. func (c *Client) CreateProjectKey(ctx context.Context, project string, req api.CreateKeyRequest) (api.CreateKeyResponse, error) { var out api.CreateKeyResponse err := c.do(ctx, http.MethodPost, api.PathProjectKeys(project), req, &out) return out, err } // ListKeys returns every key on the server. Admin scope only. func (c *Client) ListKeys(ctx context.Context) (api.KeyList, error) { var out api.KeyList err := c.do(ctx, http.MethodGet, api.PathKeys(), nil, &out) return out, err } // ListProjectKeys returns the keys of one project. func (c *Client) ListProjectKeys(ctx context.Context, project string) (api.KeyList, error) { var out api.KeyList err := c.do(ctx, http.MethodGet, api.PathProjectKeys(project), nil, &out) return out, err } // RevokeKey revokes a key by id. It takes effect immediately, and is // idempotent: revoking an already-revoked key succeeds. func (c *Client) RevokeKey(ctx context.Context, keyID string) error { return c.do(ctx, http.MethodDelete, api.PathKey(keyID), nil, nil) } // ------------------------------------------------------------- deployments // DeploymentListOptions selects and pages a project's deployments. A zero State // lists every state. type DeploymentListOptions struct { ListOptions State string } func (o DeploymentListOptions) query() string { q := url.Values{} if o.Limit > 0 { q.Set("limit", strconv.Itoa(o.Limit)) } if o.Cursor != "" { q.Set("cursor", o.Cursor) } if o.State != "" { q.Set("state", o.State) } if len(q) == 0 { return "" } return "?" + q.Encode() } // ListDeployments returns one page of a project's deployments, newest first. func (c *Client) ListDeployments(ctx context.Context, project string, opts DeploymentListOptions) (api.DeploymentList, error) { var out api.DeploymentList err := c.do(ctx, http.MethodGet, api.PathDeployments(project)+opts.query(), nil, &out) return out, err } // GetDeployment reads one deployment. withFiles asks for its manifest too, // which for a large site is a great deal more response than the rest of it. func (c *Client) GetDeployment(ctx context.Context, project, id string, withFiles bool) (api.Deployment, error) { var out api.Deployment path := api.PathDeployment(project, id) if withFiles { path += "?files=true" } err := c.do(ctx, http.MethodGet, path, nil, &out) return out, err } // DeleteDeployment removes a deployment and its files. The active one cannot be // deleted; the server answers deployment_active until something else is // activated. func (c *Client) DeleteDeployment(ctx context.Context, project, id string) error { return c.do(ctx, http.MethodDelete, api.PathDeployment(project, id), nil, nil) } // ---------------------------------------------------------------- upkeep // Collect runs a garbage collection pass. Admin scope only. func (c *Client) Collect(ctx context.Context, dryRun bool) (api.GCStats, error) { var out api.GCStats err := c.do(ctx, http.MethodPost, api.PathGC(), api.GCRequest{DryRun: dryRun}, &out) return out, err } // Fsck checks the blob reference counts against the manifests, optionally // correcting them. Admin scope only. func (c *Client) Fsck(ctx context.Context, repair bool) (api.FsckReport, error) { var out api.FsckReport err := c.do(ctx, http.MethodPost, api.PathFsck(), api.FsckRequest{Repair: repair}, &out) return out, err }