diff --git a/CHANGELOG.md b/CHANGELOG.md index f138746..8fb66db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ All notable changes to the Apify Go client are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.0] - 2026-08-10 + +### Added + +- `TaskClient.Publish`/`Unpublish` methods (thin wrappers around `Update` that set + `isPublic`), and `Task.IsPublic`/`PublicConfig` fields with the new `TaskPublicConfig` type, + mirroring the reference JS client. + +### Changed + +- Synced against Apify OpenAPI spec `v2-2026-08-05T133145Z`. +- Bumped `APISpecVersion` to `v2-2026-08-05T133145Z` and `ClientVersion` to `0.8.0`. +- Removed the duplicated AI-disclaimer paragraph from `docs/README.md` and the package doc + comment in `client.go`; it now appears only in the top-level `README.md`. + ## [0.7.1] - 2026-07-14 ### Changed diff --git a/client.go b/client.go index 9b4be45..1a6c429 100644 --- a/client.go +++ b/client.go @@ -1,9 +1,7 @@ // Package apify is the official, idiomatic Go client for the Apify API // (https://docs.apify.com/api/v2). // -// Official, but experimental — AI-generated and AI-maintained. This is an official Apify -// client, but it is experimental: it is generated and maintained by AI. Review the code -// before relying on it in production and report issues on the repository. +// See the top-level README for the AI-generated/AI-maintained disclaimer. // // It provides a resource-oriented interface that mirrors the official JavaScript and Rust // clients: start from an [ApifyClient], then drill down into resources (Actors, runs, diff --git a/docs/README.md b/docs/README.md index 6e70c7a..54a1d98 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,9 +1,5 @@ # Apify Go client documentation -> **Official, but experimental — AI-generated and AI-maintained.** This is an official Apify -> client, but it is experimental: it is generated and maintained by AI. Review the code before -> relying on it in production and report issues on the repository. - This directory documents the public API of the Apify Go client, organized by resource. Each page lists the available methods with their parameters and short, runnable snippets. For an overview, configuration, error handling, and the full resource table, see the diff --git a/docs/tasks.md b/docs/tasks.md index 69cfbd4..d9463b5 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -22,8 +22,22 @@ A task is a pre-configured Actor run with stored input. Access the task collecti | `Title` | `string` | Human-readable title shown in the UI. | | `CreatedAt` | `*time.Time` | When the task was created. | | `ModifiedAt` | `*time.Time` | When the task was last modified. | +| `IsPublic` | `*bool` | Whether the task is published on its public landing page. Not part of the documented Task schema, but returned by the API in practice; use `Publish`/`Unpublish` to change it. | +| `PublicConfig` | `*TaskPublicConfig` | Public-facing display configuration of the landing page, set once the task has been configured for publishing. | | `Extra` | `map[string]json.RawMessage` | Any other fields returned by the API. | +### `TaskPublicConfig` fields + +| Field | Type | Meaning | +|---|---|---| +| `PublishedAt` | `*time.Time` | When the task was published, or `nil` if it is not published. | +| `SEOTitle` | `*string` | Title shown in search-engine results for the landing page. | +| `SEODescription` | `*string` | Description shown in search-engine results for the landing page. | +| `Categorization` | `*string` | Free-form category label for the landing page. | +| `InputSchemaFields` | `[]string` | Input schema field names highlighted on the landing page. | +| `DatasetName` | `*string` | Display name for the task's default dataset on the landing page. | +| `DatasetView` | `*string` | Name of the dataset view shown on the landing page. | + ## Single task | Method | Description | @@ -31,6 +45,8 @@ A task is a pre-configured Actor run with stored input. Access the task collecti | `Get(ctx) (Task, bool, error)` | Fetch the task. | | `Update(ctx, newFields any) (Task, error)` | Update the task. | | `Delete(ctx) error` | Delete the task. | +| `Publish(ctx) (Task, error)` | Publish the task on its public landing page. | +| `Unpublish(ctx) (Task, error)` | Unpublish the task from its public landing page. | | `Start(ctx, input any, TaskStartOptions) (ActorRun, error)` | Start a run (input overrides stored input). | | `Call(ctx, input any, TaskStartOptions, waitSecs *int64) (ActorRun, error)` | Start and wait. | | `GetInput(ctx) (json.RawMessage, bool, error)` | Fetch the stored input. | @@ -73,3 +89,25 @@ if ok { fmt.Printf("last run: %s (%s)\n", lastRun.ID, lastRun.Status) } ``` + +`Publish`/`Unpublish` toggle the task's public landing page by updating `IsPublic`; both reuse +the same `PUT /actor-tasks/{id}` endpoint as `Update`. `IsPublic` is a `*bool` (nil-checked +before use below) since it is not part of the documented Task schema. + +```go +published, err := client.Task("my-task-id").Publish(ctx) +if err != nil { + log.Fatal(err) +} +if published.IsPublic != nil { + fmt.Println(*published.IsPublic) // true +} + +unpublished, err := client.Task("my-task-id").Unpublish(ctx) +if err != nil { + log.Fatal(err) +} +if unpublished.IsPublic != nil { + fmt.Println(*unpublished.IsPublic) // false +} +``` diff --git a/models.go b/models.go index 3569d75..7273a29 100644 --- a/models.go +++ b/models.go @@ -157,6 +157,13 @@ type Task struct { CreatedAt *time.Time `json:"createdAt"` // ModifiedAt is when the task was last modified. ModifiedAt *time.Time `json:"modifiedAt"` + // IsPublic reports whether the task is published on its public landing page. It is not part + // of the documented Task schema in the OpenAPI spec, but the API returns it in practice + // (mirroring the reference JS client); use TaskClient.Publish/Unpublish to change it. + IsPublic *bool `json:"isPublic,omitempty"` + // PublicConfig is the public-facing display configuration of the task's landing page, set + // when the task has been configured for publishing (nil otherwise). + PublicConfig *TaskPublicConfig `json:"publicConfig,omitempty"` // Extra holds any other fields returned by the API. Extra Extra `json:"-"` } @@ -167,6 +174,28 @@ func (t *Task) UnmarshalJSON(data []byte) error { return unmarshalWithExtra(data, (*alias)(t), known, &t.Extra) } +// TaskPublicConfig is the public-facing display configuration of a task's public landing page. +// +// The task is published when PublishedAt is set and unpublished when it is nil. PublishedAt is +// read-only from the client's perspective; use TaskClient.Publish/Unpublish to change the +// publication state. +type TaskPublicConfig struct { + // PublishedAt is when the task was published, or nil if it is not published. + PublishedAt *time.Time `json:"publishedAt"` + // SEOTitle is the title shown in search-engine results for the landing page. + SEOTitle *string `json:"seoTitle,omitempty"` + // SEODescription is the description shown in search-engine results for the landing page. + SEODescription *string `json:"seoDescription,omitempty"` + // Categorization is a free-form category label for the landing page. + Categorization *string `json:"categorization,omitempty"` + // InputSchemaFields lists the input schema field names highlighted on the landing page. + InputSchemaFields []string `json:"inputSchemaFields,omitempty"` + // DatasetName is the display name used for the task's default dataset on the landing page. + DatasetName *string `json:"datasetName,omitempty"` + // DatasetView is the name of the dataset view shown on the landing page. + DatasetView *string `json:"datasetView,omitempty"` +} + // Dataset stores structured results from Actor runs. type Dataset struct { // ID is the unique dataset ID. diff --git a/task.go b/task.go index b64d384..6a958ae 100644 --- a/task.go +++ b/task.go @@ -33,6 +33,25 @@ func (c *TaskClient) Delete(ctx context.Context) error { return deleteResource(ctx, c.ctx, "") } +// Publish publishes the task on its public landing page by setting IsPublic through Update. +// +// The task's Actor must be public and the task must already have its public display +// configuration (PublicConfig) set up. Requires write permission to both the task and its +// Actor. Publishing an already published task does nothing. +func (c *TaskClient) Publish(ctx context.Context) (Task, error) { + return c.Update(ctx, map[string]any{"isPublic": true}) +} + +// Unpublish unpublishes the task from its public landing page by setting IsPublic through +// Update. +// +// The public display configuration (PublicConfig) is preserved, so the task can be published +// again without re-entering it. Requires write permission to both the task and its Actor. +// Unpublishing a task that is not published does nothing. +func (c *TaskClient) Unpublish(ctx context.Context) (Task, error) { + return c.Update(ctx, map[string]any{"isPublic": false}) +} + // TaskStartOptions configures starting a task run ([TaskClient.Start]/[TaskClient.Call]). // // It mirrors [ActorStartOptions] but omits the fields the task run endpoint does not accept diff --git a/tests/task_test.go b/tests/task_test.go index 3a994a1..8483690 100644 --- a/tests/task_test.go +++ b/tests/task_test.go @@ -74,3 +74,39 @@ func TestTaskCRUDFlow(t *testing.T) { t.Fatalf("runs list: %v", err) } } + +// TestTaskPublishUnpublish exercises Publish/Unpublish against a task whose Actor +// (apify/hello-world) is not owned by the test account. +// +// Publish requires write permission to both the task and its Actor, so it is expected to fail +// (400 for the missing PublicConfig, or 403 for the unowned Actor - the server may reject on +// either ground first). Unpublish only requires write permission to the task itself, so it is +// expected to succeed even though the Actor is unowned, and leaves IsPublic not-true. +func TestTaskPublishUnpublish(t *testing.T) { + client := requireClient(t) + ctx, cancel := testContext(t) + defer cancel() + + task, err := client.Tasks().Create(ctx, taskDef(uniqueName("task-publish"))) + if err != nil { + t.Fatalf("create: %v", err) + } + defer func() { _ = client.Task(task.ID).Delete(ctx) }() + tc := client.Task(task.ID) + + if _, err := tc.Publish(ctx); err == nil { + t.Fatal("publish: expected an error for an unowned Actor without PublicConfig, got nil") + } else if apiErr, ok := apify.AsAPIError(err); !ok { + t.Fatalf("publish: expected an *apify.APIError, got %T: %v", err, err) + } else if apiErr.StatusCode != 400 && apiErr.StatusCode != 403 { + t.Fatalf("publish: expected status 400 or 403, got %d: %v", apiErr.StatusCode, apiErr) + } + + unpublished, err := tc.Unpublish(ctx) + if err != nil { + t.Fatalf("unpublish: %v", err) + } + if unpublished.IsPublic != nil && *unpublished.IsPublic { + t.Fatalf("unpublish: expected IsPublic to not be true, got %v", *unpublished.IsPublic) + } +} diff --git a/version.go b/version.go index 03c663d..c5653f4 100644 --- a/version.go +++ b/version.go @@ -4,10 +4,10 @@ package apify // // It follows Semantic Versioning (https://semver.org/). Changes to the public // interface (other than additive ones) are considered breaking changes. -const ClientVersion = "0.7.1" +const ClientVersion = "0.8.0" // APISpecVersion is the version of the Apify OpenAPI specification that this // client was generated and verified against. // // It corresponds to the `info.version` field of the Apify OpenAPI document. -const APISpecVersion = "v2-2026-07-13T092445Z" +const APISpecVersion = "v2-2026-08-05T133145Z"