From 79caa168dbbd919578b81e3fd09ea4e55c9375de Mon Sep 17 00:00:00 2001 From: Gregor Schulte Date: Tue, 17 Feb 2026 11:34:44 +0100 Subject: [PATCH] =?UTF-8?q?F=C3=BCge=20BlockTypes,=20BlockSchemas,=20Block?= =?UTF-8?q?Documents=20und=20BlockCapabilities=20Services=20hinzu;=20erwei?= =?UTF-8?q?tere=20Modelle=20f=C3=BCr=20Blocktypen,=20Blockschemas=20und=20?= =?UTF-8?q?Blockdokumente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/client/blocks.go | 406 +++++++++++++++++++++++++++++++++++++++++++ pkg/client/client.go | 26 ++- pkg/models/models.go | 114 ++++++++++++ 3 files changed, 537 insertions(+), 9 deletions(-) create mode 100644 pkg/client/blocks.go diff --git a/pkg/client/blocks.go b/pkg/client/blocks.go new file mode 100644 index 0000000..87fe7b2 --- /dev/null +++ b/pkg/client/blocks.go @@ -0,0 +1,406 @@ +package client + +import ( + "context" + "fmt" + "net/url" + + "git.schultes.dev/schultesdev/prefect-go/pkg/models" + "github.com/google/uuid" +) + +// BlockTypesService handles operations related to block types. +type BlockTypesService struct { + client *Client +} + +// Create creates a new block type. +func (s *BlockTypesService) Create(ctx context.Context, req *models.BlockTypeCreate) (*models.BlockType, error) { + var blockType models.BlockType + if err := s.client.post(ctx, "/block_types/", req, &blockType); err != nil { + return nil, fmt.Errorf("failed to create block type: %w", err) + } + return &blockType, nil +} + +// Get retrieves a block type by ID. +func (s *BlockTypesService) Get(ctx context.Context, id uuid.UUID) (*models.BlockType, error) { + var blockType models.BlockType + path := joinPath("/block_types", id.String()) + if err := s.client.get(ctx, path, &blockType); err != nil { + return nil, fmt.Errorf("failed to get block type: %w", err) + } + return &blockType, nil +} + +// GetBySlug retrieves a block type by slug. +func (s *BlockTypesService) GetBySlug(ctx context.Context, slug string) (*models.BlockType, error) { + var blockType models.BlockType + path := joinPath("/block_types/slug", slug) + if err := s.client.get(ctx, path, &blockType); err != nil { + return nil, fmt.Errorf("failed to get block type by slug: %w", err) + } + return &blockType, nil +} + +// List retrieves block types with optional filtering. +func (s *BlockTypesService) List(ctx context.Context, filter *models.BlockTypeFilter, offset, limit int) ([]models.BlockType, error) { + type nameFilter struct { + Like *string `json:"like_,omitempty"` + } + type slugFilter struct { + Any []string `json:"any_,omitempty"` + } + type blockTypeFilterCriteria struct { + Name *nameFilter `json:"name,omitempty"` + Slug *slugFilter `json:"slug,omitempty"` + } + type capabilitiesFilter struct { + All []string `json:"all_,omitempty"` + } + type blockSchemaFilterCriteria struct { + Capabilities *capabilitiesFilter `json:"block_capabilities,omitempty"` + } + type request struct { + BlockTypes *blockTypeFilterCriteria `json:"block_types,omitempty"` + BlockSchemas *blockSchemaFilterCriteria `json:"block_schemas,omitempty"` + Offset int `json:"offset,omitempty"` + Limit int `json:"limit,omitempty"` + } + + req := request{Offset: offset, Limit: limit} + + if filter != nil { + btf := &blockTypeFilterCriteria{} + if filter.Name != nil { + btf.Name = &nameFilter{Like: filter.Name} + } + if len(filter.Slugs) > 0 { + btf.Slug = &slugFilter{Any: filter.Slugs} + } + if btf.Name != nil || btf.Slug != nil { + req.BlockTypes = btf + } + if len(filter.Capabilities) > 0 { + req.BlockSchemas = &blockSchemaFilterCriteria{ + Capabilities: &capabilitiesFilter{All: filter.Capabilities}, + } + } + } + + var blockTypes []models.BlockType + if err := s.client.post(ctx, "/block_types/filter", req, &blockTypes); err != nil { + return nil, fmt.Errorf("failed to list block types: %w", err) + } + return blockTypes, nil +} + +// Update updates a block type. +func (s *BlockTypesService) Update(ctx context.Context, id uuid.UUID, req *models.BlockTypeUpdate) error { + path := joinPath("/block_types", id.String()) + if err := s.client.patch(ctx, path, req, nil); err != nil { + return fmt.Errorf("failed to update block type: %w", err) + } + return nil +} + +// Delete deletes a block type by ID. +func (s *BlockTypesService) Delete(ctx context.Context, id uuid.UUID) error { + path := joinPath("/block_types", id.String()) + if err := s.client.delete(ctx, path); err != nil { + return fmt.Errorf("failed to delete block type: %w", err) + } + return nil +} + +// InstallSystemBlockTypes installs the system block types. +func (s *BlockTypesService) InstallSystemBlockTypes(ctx context.Context) error { + if err := s.client.post(ctx, "/block_types/install_system_block_types", nil, nil); err != nil { + return fmt.Errorf("failed to install system block types: %w", err) + } + return nil +} + +// ListBlockDocumentsBySlug retrieves all block documents for a block type identified by slug. +func (s *BlockTypesService) ListBlockDocumentsBySlug(ctx context.Context, slug string, includeSecrets bool) ([]models.BlockDocument, error) { + path := joinPath("/block_types/slug", slug, "block_documents") + if includeSecrets { + query := url.Values{} + query.Set("include_secrets", "true") + path = buildPathWithValues(path, query) + } + var blockDocuments []models.BlockDocument + if err := s.client.get(ctx, path, &blockDocuments); err != nil { + return nil, fmt.Errorf("failed to list block documents for block type: %w", err) + } + return blockDocuments, nil +} + +// GetBlockDocumentByName retrieves a block document by name for a given block type slug. +func (s *BlockTypesService) GetBlockDocumentByName(ctx context.Context, slug, name string, includeSecrets bool) (*models.BlockDocument, error) { + path := joinPath("/block_types/slug", slug, "block_documents/name", name) + if includeSecrets { + query := url.Values{} + query.Set("include_secrets", "true") + path = buildPathWithValues(path, query) + } + var blockDocument models.BlockDocument + if err := s.client.get(ctx, path, &blockDocument); err != nil { + return nil, fmt.Errorf("failed to get block document by name: %w", err) + } + return &blockDocument, nil +} + +// BlockSchemasService handles operations related to block schemas. +type BlockSchemasService struct { + client *Client +} + +// Create creates a new block schema. +func (s *BlockSchemasService) Create(ctx context.Context, req *models.BlockSchemaCreate) (*models.BlockSchema, error) { + var blockSchema models.BlockSchema + if err := s.client.post(ctx, "/block_schemas/", req, &blockSchema); err != nil { + return nil, fmt.Errorf("failed to create block schema: %w", err) + } + return &blockSchema, nil +} + +// Get retrieves a block schema by ID. +func (s *BlockSchemasService) Get(ctx context.Context, id uuid.UUID) (*models.BlockSchema, error) { + var blockSchema models.BlockSchema + path := joinPath("/block_schemas", id.String()) + if err := s.client.get(ctx, path, &blockSchema); err != nil { + return nil, fmt.Errorf("failed to get block schema: %w", err) + } + return &blockSchema, nil +} + +// GetByChecksum retrieves a block schema by checksum and optional version. +func (s *BlockSchemasService) GetByChecksum(ctx context.Context, checksum string, version string) (*models.BlockSchema, error) { + path := joinPath("/block_schemas/checksum", checksum) + if version != "" { + query := url.Values{} + query.Set("version", version) + path = buildPathWithValues(path, query) + } + var blockSchema models.BlockSchema + if err := s.client.get(ctx, path, &blockSchema); err != nil { + return nil, fmt.Errorf("failed to get block schema by checksum: %w", err) + } + return &blockSchema, nil +} + +// List retrieves block schemas with optional filtering. +func (s *BlockSchemasService) List(ctx context.Context, filter *models.BlockSchemaFilter, offset, limit int) ([]models.BlockSchema, error) { + type blockTypeIDFilter struct { + Any []string `json:"any_,omitempty"` + } + type capabilitiesFilter struct { + All []string `json:"all_,omitempty"` + } + type versionFilter struct { + Any []string `json:"any_,omitempty"` + } + type blockSchemaFilterCriteria struct { + BlockTypeID *blockTypeIDFilter `json:"block_type_id,omitempty"` + Capabilities *capabilitiesFilter `json:"block_capabilities,omitempty"` + Version *versionFilter `json:"version,omitempty"` + } + type request struct { + BlockSchemas *blockSchemaFilterCriteria `json:"block_schemas,omitempty"` + Offset int `json:"offset,omitempty"` + Limit int `json:"limit,omitempty"` + } + + req := request{Offset: offset, Limit: limit} + + if filter != nil { + bsf := &blockSchemaFilterCriteria{} + if filter.BlockTypeID != nil { + bsf.BlockTypeID = &blockTypeIDFilter{Any: []string{filter.BlockTypeID.String()}} + } + if len(filter.Capabilities) > 0 { + bsf.Capabilities = &capabilitiesFilter{All: filter.Capabilities} + } + if filter.Version != nil { + bsf.Version = &versionFilter{Any: []string{*filter.Version}} + } + if bsf.BlockTypeID != nil || bsf.Capabilities != nil || bsf.Version != nil { + req.BlockSchemas = bsf + } + } + + var blockSchemas []models.BlockSchema + if err := s.client.post(ctx, "/block_schemas/filter", req, &blockSchemas); err != nil { + return nil, fmt.Errorf("failed to list block schemas: %w", err) + } + return blockSchemas, nil +} + +// Delete deletes a block schema by ID. +func (s *BlockSchemasService) Delete(ctx context.Context, id uuid.UUID) error { + path := joinPath("/block_schemas", id.String()) + if err := s.client.delete(ctx, path); err != nil { + return fmt.Errorf("failed to delete block schema: %w", err) + } + return nil +} + +// BlockDocumentsService handles operations related to block documents. +type BlockDocumentsService struct { + client *Client +} + +// Create creates a new block document. +func (s *BlockDocumentsService) Create(ctx context.Context, req *models.BlockDocumentCreate) (*models.BlockDocument, error) { + var blockDocument models.BlockDocument + if err := s.client.post(ctx, "/block_documents/", req, &blockDocument); err != nil { + return nil, fmt.Errorf("failed to create block document: %w", err) + } + return &blockDocument, nil +} + +// Get retrieves a block document by ID. +func (s *BlockDocumentsService) Get(ctx context.Context, id uuid.UUID, includeSecrets bool) (*models.BlockDocument, error) { + path := joinPath("/block_documents", id.String()) + if includeSecrets { + query := url.Values{} + query.Set("include_secrets", "true") + path = buildPathWithValues(path, query) + } + var blockDocument models.BlockDocument + if err := s.client.get(ctx, path, &blockDocument); err != nil { + return nil, fmt.Errorf("failed to get block document: %w", err) + } + return &blockDocument, nil +} + +// List retrieves block documents with optional filtering. +func (s *BlockDocumentsService) List(ctx context.Context, filter *models.BlockDocumentFilter, includeSecrets bool, sort models.BlockDocumentSort, offset, limit int) ([]models.BlockDocument, error) { + type idFilter struct { + Any []string `json:"any_,omitempty"` + } + type nameFilter struct { + Any []string `json:"any_,omitempty"` + } + type boolFilter struct { + Eq bool `json:"eq_"` + } + type blockDocumentFilterCriteria struct { + BlockTypeID *idFilter `json:"block_type_id,omitempty"` + Name *nameFilter `json:"name,omitempty"` + IsAnonymous *boolFilter `json:"is_anonymous,omitempty"` + } + type request struct { + BlockDocuments *blockDocumentFilterCriteria `json:"block_documents,omitempty"` + IncludeSecrets bool `json:"include_secrets,omitempty"` + Sort models.BlockDocumentSort `json:"sort,omitempty"` + Offset int `json:"offset,omitempty"` + Limit int `json:"limit,omitempty"` + } + + req := request{ + IncludeSecrets: includeSecrets, + Sort: sort, + Offset: offset, + Limit: limit, + } + + if filter != nil { + bdf := &blockDocumentFilterCriteria{} + if filter.BlockTypeID != nil { + bdf.BlockTypeID = &idFilter{Any: []string{filter.BlockTypeID.String()}} + } + if filter.Name != nil { + bdf.Name = &nameFilter{Any: []string{*filter.Name}} + } + if filter.IsAnonymous != nil { + bdf.IsAnonymous = &boolFilter{Eq: *filter.IsAnonymous} + } + if bdf.BlockTypeID != nil || bdf.Name != nil || bdf.IsAnonymous != nil { + req.BlockDocuments = bdf + } + } + + var blockDocuments []models.BlockDocument + if err := s.client.post(ctx, "/block_documents/filter", req, &blockDocuments); err != nil { + return nil, fmt.Errorf("failed to list block documents: %w", err) + } + return blockDocuments, nil +} + +// Count returns the number of block documents matching the filter. +func (s *BlockDocumentsService) Count(ctx context.Context, filter *models.BlockDocumentFilter) (int, error) { + type idFilter struct { + Any []string `json:"any_,omitempty"` + } + type nameFilter struct { + Any []string `json:"any_,omitempty"` + } + type boolFilter struct { + Eq bool `json:"eq_"` + } + type blockDocumentFilterCriteria struct { + BlockTypeID *idFilter `json:"block_type_id,omitempty"` + Name *nameFilter `json:"name,omitempty"` + IsAnonymous *boolFilter `json:"is_anonymous,omitempty"` + } + type request struct { + BlockDocuments *blockDocumentFilterCriteria `json:"block_documents,omitempty"` + } + + var req request + if filter != nil { + bdf := &blockDocumentFilterCriteria{} + if filter.BlockTypeID != nil { + bdf.BlockTypeID = &idFilter{Any: []string{filter.BlockTypeID.String()}} + } + if filter.Name != nil { + bdf.Name = &nameFilter{Any: []string{*filter.Name}} + } + if filter.IsAnonymous != nil { + bdf.IsAnonymous = &boolFilter{Eq: *filter.IsAnonymous} + } + if bdf.BlockTypeID != nil || bdf.Name != nil || bdf.IsAnonymous != nil { + req.BlockDocuments = bdf + } + } + + var count int + if err := s.client.post(ctx, "/block_documents/count", req, &count); err != nil { + return 0, fmt.Errorf("failed to count block documents: %w", err) + } + return count, nil +} + +// Update updates a block document's data. +func (s *BlockDocumentsService) Update(ctx context.Context, id uuid.UUID, req *models.BlockDocumentUpdate) error { + path := joinPath("/block_documents", id.String()) + if err := s.client.patch(ctx, path, req, nil); err != nil { + return fmt.Errorf("failed to update block document: %w", err) + } + return nil +} + +// Delete deletes a block document by ID. +func (s *BlockDocumentsService) Delete(ctx context.Context, id uuid.UUID) error { + path := joinPath("/block_documents", id.String()) + if err := s.client.delete(ctx, path); err != nil { + return fmt.Errorf("failed to delete block document: %w", err) + } + return nil +} + +// BlockCapabilitiesService handles operations related to block capabilities. +type BlockCapabilitiesService struct { + client *Client +} + +// List retrieves all available block capabilities. +func (s *BlockCapabilitiesService) List(ctx context.Context) ([]string, error) { + var capabilities []string + if err := s.client.get(ctx, "/block_capabilities/", &capabilities); err != nil { + return nil, fmt.Errorf("failed to list block capabilities: %w", err) + } + return capabilities, nil +} diff --git a/pkg/client/client.go b/pkg/client/client.go index 0f2b714..554b3b2 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -31,15 +31,19 @@ type Client struct { userAgent string // Services - Flows *FlowsService - FlowRuns *FlowRunsService - Deployments *DeploymentsService - TaskRuns *TaskRunsService - WorkPools *WorkPoolsService - WorkQueues *WorkQueuesService - Variables *VariablesService - Logs *LogsService - Admin *AdminService + Flows *FlowsService + FlowRuns *FlowRunsService + Deployments *DeploymentsService + TaskRuns *TaskRunsService + WorkPools *WorkPoolsService + WorkQueues *WorkQueuesService + Variables *VariablesService + Logs *LogsService + Admin *AdminService + BlockTypes *BlockTypesService + BlockSchemas *BlockSchemasService + BlockDocuments *BlockDocumentsService + BlockCapabilities *BlockCapabilitiesService } // Option is a functional option for configuring the client. @@ -79,6 +83,10 @@ func NewClient(opts ...Option) (*Client, error) { c.Variables = &VariablesService{client: c} c.Logs = &LogsService{client: c} c.Admin = &AdminService{client: c} + c.BlockTypes = &BlockTypesService{client: c} + c.BlockSchemas = &BlockSchemasService{client: c} + c.BlockDocuments = &BlockDocumentsService{client: c} + c.BlockCapabilities = &BlockCapabilitiesService{client: c} return c, nil } diff --git a/pkg/models/models.go b/pkg/models/models.go index eadb384..47b452b 100644 --- a/pkg/models/models.go +++ b/pkg/models/models.go @@ -291,6 +291,120 @@ type LogCreate struct { TaskRunID *uuid.UUID `json:"task_run_id,omitempty"` } +// BlockDocumentSort represents sort options for block documents. +type BlockDocumentSort string + +const ( + BlockDocumentSortNameAsc BlockDocumentSort = "NAME_ASC" + BlockDocumentSortNameDesc BlockDocumentSort = "NAME_DESC" +) + +// BlockType represents a Prefect block type. +type BlockType struct { + ID uuid.UUID `json:"id"` + Created *time.Time `json:"created"` + Updated *time.Time `json:"updated"` + Name string `json:"name"` + Slug string `json:"slug"` + LogoURL *string `json:"logo_url"` + DocumentationURL *string `json:"documentation_url"` + Description *string `json:"description"` + CodeExample *string `json:"code_example"` + IsProtected bool `json:"is_protected"` +} + +// BlockTypeCreate represents the request to create a block type. +type BlockTypeCreate struct { + Name string `json:"name"` + Slug string `json:"slug"` + LogoURL *string `json:"logo_url,omitempty"` + DocumentationURL *string `json:"documentation_url,omitempty"` + Description *string `json:"description,omitempty"` + CodeExample *string `json:"code_example,omitempty"` +} + +// BlockTypeUpdate represents the request to update a block type. +type BlockTypeUpdate struct { + LogoURL *string `json:"logo_url,omitempty"` + DocumentationURL *string `json:"documentation_url,omitempty"` + Description *string `json:"description,omitempty"` + CodeExample *string `json:"code_example,omitempty"` +} + +// BlockTypeFilter represents filter criteria for querying block types. +type BlockTypeFilter struct { + Name *string // Filter by name (partial match) + Slugs []string // Filter by slugs + Capabilities []string // Filter by block capabilities +} + +// BlockSchema represents a Prefect block schema. +type BlockSchema struct { + ID uuid.UUID `json:"id"` + Created *time.Time `json:"created"` + Updated *time.Time `json:"updated"` + Checksum string `json:"checksum"` + Fields map[string]interface{} `json:"fields"` + BlockTypeID *uuid.UUID `json:"block_type_id"` + BlockType *BlockType `json:"block_type"` + Capabilities []string `json:"capabilities"` + Version string `json:"version"` +} + +// BlockSchemaCreate represents the request to create a block schema. +type BlockSchemaCreate struct { + Fields map[string]interface{} `json:"fields,omitempty"` + BlockTypeID uuid.UUID `json:"block_type_id"` + Capabilities []string `json:"capabilities,omitempty"` + Version *string `json:"version,omitempty"` +} + +// BlockSchemaFilter represents filter criteria for querying block schemas. +type BlockSchemaFilter struct { + BlockTypeID *uuid.UUID // Filter by block type ID + Capabilities []string // Filter by required capabilities + Version *string // Filter by schema version +} + +// BlockDocument represents a Prefect block document. +type BlockDocument struct { + ID uuid.UUID `json:"id"` + Created *time.Time `json:"created"` + Updated *time.Time `json:"updated"` + Name *string `json:"name"` + Data map[string]interface{} `json:"data"` + BlockSchemaID uuid.UUID `json:"block_schema_id"` + BlockSchema *BlockSchema `json:"block_schema"` + BlockTypeID uuid.UUID `json:"block_type_id"` + BlockTypeName *string `json:"block_type_name"` + BlockType *BlockType `json:"block_type"` + BlockDocumentReferences map[string]interface{} `json:"block_document_references"` + IsAnonymous bool `json:"is_anonymous"` +} + +// BlockDocumentCreate represents the request to create a block document. +type BlockDocumentCreate struct { + Name *string `json:"name,omitempty"` + Data map[string]interface{} `json:"data,omitempty"` + BlockSchemaID uuid.UUID `json:"block_schema_id"` + BlockTypeID uuid.UUID `json:"block_type_id"` + IsAnonymous bool `json:"is_anonymous,omitempty"` +} + +// BlockDocumentUpdate represents the request to update a block document. +type BlockDocumentUpdate struct { + BlockSchemaID *uuid.UUID `json:"block_schema_id,omitempty"` + Data map[string]interface{} `json:"data"` + MergeExisting *bool `json:"merge_existing_data,omitempty"` +} + +// BlockDocumentFilter represents filter criteria for querying block documents. +type BlockDocumentFilter struct { + BlockTypeID *uuid.UUID // Filter by block type ID + Name *string // Filter by document name + IsAnonymous *bool // Filter by anonymity +} + // UnmarshalJSON implements custom JSON unmarshaling for time fields. func (f *Flow) UnmarshalJSON(data []byte) error { type Alias Flow