dataproduct

package
v0.5.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 12, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Index

Constants

View Source
const (
	DefaultLimit         = 50
	MaxLimit             = 100
	MaxRules             = 10
	MaxReconcileProducts = 10000
)
View Source
const (
	PatternTypeExact    = "exact"
	PatternTypeWildcard = "wildcard"
	PatternTypeRegex    = "regex"
	PatternTypePrefix   = "prefix"
)
View Source
const (
	SourceManual = "manual"
	SourceRule   = "rule"
)
View Source
const (
	TargetTypeAssetType   = "asset_type"
	TargetTypeProvider    = "provider"
	TargetTypeTag         = "tag"
	TargetTypeMetadataKey = "metadata_key"
	TargetTypeQuery       = "query"
)
View Source
const (
	DefaultReconcileInterval = 30 * time.Minute
)
View Source
const (
	MaxImageSizeBytes = 5 * 1024 * 1024 // 5MB per image
)

Variables

View Source
var (
	ErrNotFound     = errors.New("data product not found")
	ErrConflict     = errors.New("data product with this name already exists")
	ErrInvalidInput = errors.New("invalid input")
	ErrRuleNotFound = errors.New("rule not found")
)
View Source
var ErrImageNotFound = errors.New("image not found")
View Source
var ErrImageTooLarge = errors.New("image exceeds maximum size")
View Source
var ErrInvalidImageType = errors.New("invalid image type")
View Source
var ValidImageTypes = map[string]bool{
	"image/jpeg": true,
	"image/png":  true,
	"image/gif":  true,
	"image/webp": true,
}

Functions

This section is empty.

Types

type AssetGetter

type AssetGetter interface {
	Get(ctx context.Context, id string) (*asset.Asset, error)
}

AssetGetter provides read access to assets.

type AssetSignature

type AssetSignature struct {
	ID           string
	Type         string
	Providers    []string
	Tags         []string
	MetadataKeys []string
}

AssetSignature contains the key fields used for rule matching.

type AssetsResult

type AssetsResult struct {
	AssetIDs []string `json:"asset_ids"`
	Total    int      `json:"total"`
}

type CandidateRule

type CandidateRule struct {
	RuleID        string
	DataProductID string
}

CandidateRule represents a rule that might match an asset.

type CreateInput

type CreateInput struct {
	Name        string                 `json:"name" validate:"required,min=1,max=255"`
	Description *string                `json:"description,omitempty"`
	Metadata    map[string]interface{} `json:"metadata,omitempty"`
	Tags        []string               `json:"tags,omitempty"`
	Owners      []OwnerInput           `json:"owners" validate:"required,min=1,dive"`
	Rules       []RuleInput            `json:"rules,omitempty" validate:"omitempty,dive"`
}

type DataProduct

type DataProduct struct {
	ID          string                 `json:"id"`
	Name        string                 `json:"name"`
	Description *string                `json:"description,omitempty"`
	Metadata    map[string]interface{} `json:"metadata,omitempty"`
	Tags        []string               `json:"tags,omitempty"`
	Owners      []Owner                `json:"owners"`
	Rules       []Rule                 `json:"rules,omitempty"`
	CreatedBy   *string                `json:"created_by,omitempty"`
	CreatedAt   time.Time              `json:"created_at"`
	UpdatedAt   time.Time              `json:"updated_at"`

	AssetCount       int `json:"asset_count,omitempty"`
	ManualAssetCount int `json:"manual_asset_count,omitempty"`
	RuleAssetCount   int `json:"rule_asset_count,omitempty"`

	IconURL *string `json:"icon_url,omitempty"`
}

type ImagePurpose

type ImagePurpose string
const (
	ImagePurposeIcon   ImagePurpose = "icon"
	ImagePurposeHeader ImagePurpose = "header"
)

type ListResult

type ListResult struct {
	DataProducts []*DataProduct `json:"data_products"`
	Total        int            `json:"total"`
}

type Membership

type Membership struct {
	DataProductID string
	AssetID       string
	Source        string  // SourceManual or SourceRule
	RuleID        *string // nil for manual memberships
	CreatedAt     time.Time
}

Membership represents a precomputed relationship between a data product and an asset.

type MembershipConfig

type MembershipConfig struct {
	// MaxWorkers for rule evaluation. Default: 5.
	MaxWorkers int
	// BatchSize for membership updates. Default: 50.
	BatchSize int
	// FlushInterval for batched updates. Default: 500ms.
	FlushInterval time.Duration
}

MembershipConfig configures the membership service.

type MembershipRepository

type MembershipRepository interface {
	// Membership operations
	CreateMemberships(ctx context.Context, memberships []Membership) error
	DeleteMembershipsByAsset(ctx context.Context, assetID string) error
	DeleteMembershipsByRule(ctx context.Context, ruleID string) error
	DeleteMembershipsByDataProduct(ctx context.Context, dataProductID string) error
	GetMemberships(ctx context.Context, dataProductID string, limit, offset int) ([]Membership, int, error)
	GetDataProductsForAsset(ctx context.Context, assetID string) ([]string, error)

	// Rule target operations
	SaveRuleTargets(ctx context.Context, ruleID, dataProductID string, targets []RuleTarget) error
	DeleteRuleTargets(ctx context.Context, ruleID string) error
	FindCandidateRules(ctx context.Context, sig AssetSignature) ([]CandidateRule, error)

	// Rule evaluation
	EvaluateRuleForAsset(ctx context.Context, rule *Rule, assetID string) (bool, error)

	// Stats
	UpdateMembershipStats(ctx context.Context, dataProductID string) error
}

MembershipRepository handles database operations for memberships.

type MembershipService

type MembershipService struct {
	// contains filtered or unexported fields
}

MembershipService handles the evaluation of data product rules and maintains the precomputed membership table.

func NewMembershipService

func NewMembershipService(
	repo Repository,
	memberRepo MembershipRepository,
	assetGetter AssetGetter,
	config *MembershipConfig,
) *MembershipService

NewMembershipService creates a new membership service.

func (*MembershipService) EvaluateRule

func (s *MembershipService) EvaluateRule(ctx context.Context, ruleID string) error

EvaluateRule fully evaluates a rule against all assets. Used for initial rule evaluation and reconciliation.

func (*MembershipService) OnAssetCreated

func (s *MembershipService) OnAssetCreated(ctx context.Context, ast *asset.Asset)

OnAssetCreated is called when a new asset is created. It queues the asset for membership evaluation.

func (*MembershipService) OnAssetDeleted

func (s *MembershipService) OnAssetDeleted(ctx context.Context, assetID string) error

OnAssetDeleted is called when an asset is deleted. It removes all memberships for this asset.

func (*MembershipService) OnRuleCreated

func (s *MembershipService) OnRuleCreated(ctx context.Context, rule *Rule) error

OnRuleCreated is called when a new rule is created. It extracts targets and evaluates the rule against all assets.

func (*MembershipService) OnRuleDeleted

func (s *MembershipService) OnRuleDeleted(ctx context.Context, ruleID string) error

OnRuleDeleted is called when a rule is deleted. Memberships and targets are cascade-deleted by the database.

func (*MembershipService) OnRuleUpdated

func (s *MembershipService) OnRuleUpdated(ctx context.Context, rule *Rule) error

OnRuleUpdated is called when a rule is updated. It re-extracts targets and re-evaluates the rule.

func (*MembershipService) ReconcileAll

func (s *MembershipService) ReconcileAll(ctx context.Context) error

ReconcileAll re-evaluates all rules and updates memberships. This is used for periodic reconciliation to fix any drift.

func (*MembershipService) Start

func (s *MembershipService) Start(ctx context.Context)

Start begins background processing.

func (*MembershipService) Stop

func (s *MembershipService) Stop()

Stop gracefully shuts down the service.

type Owner

type Owner struct {
	ID             string  `json:"id"`
	Username       *string `json:"username,omitempty"`
	Name           string  `json:"name"`
	Type           string  `json:"type"`
	Email          *string `json:"email,omitempty"`
	ProfilePicture *string `json:"profile_picture,omitempty"`
}

type OwnerInput

type OwnerInput struct {
	ID   string `json:"id" validate:"required"`
	Type string `json:"type" validate:"required,oneof=user team"`
}

type PostgresMembershipRepository

type PostgresMembershipRepository struct {
	// contains filtered or unexported fields
}

PostgresMembershipRepository implements MembershipRepository for PostgreSQL.

func NewPostgresMembershipRepository

func NewPostgresMembershipRepository(db *pgxpool.Pool, recorder metrics.Recorder) *PostgresMembershipRepository

NewPostgresMembershipRepository creates a new PostgreSQL membership repository.

func (*PostgresMembershipRepository) CreateMemberships

func (r *PostgresMembershipRepository) CreateMemberships(ctx context.Context, memberships []Membership) error

CreateMemberships inserts multiple memberships, ignoring duplicates.

func (*PostgresMembershipRepository) DeleteMembershipsByAsset

func (r *PostgresMembershipRepository) DeleteMembershipsByAsset(ctx context.Context, assetID string) error

DeleteMembershipsByAsset removes all memberships for a given asset.

func (*PostgresMembershipRepository) DeleteMembershipsByDataProduct

func (r *PostgresMembershipRepository) DeleteMembershipsByDataProduct(ctx context.Context, dataProductID string) error

DeleteMembershipsByDataProduct removes all memberships for a data product.

func (*PostgresMembershipRepository) DeleteMembershipsByRule

func (r *PostgresMembershipRepository) DeleteMembershipsByRule(ctx context.Context, ruleID string) error

DeleteMembershipsByRule removes all memberships created by a specific rule.

func (*PostgresMembershipRepository) DeleteRuleTargets

func (r *PostgresMembershipRepository) DeleteRuleTargets(ctx context.Context, ruleID string) error

DeleteRuleTargets removes all targets for a rule.

func (*PostgresMembershipRepository) EvaluateRuleForAsset

func (r *PostgresMembershipRepository) EvaluateRuleForAsset(ctx context.Context, rule *Rule, assetID string) (bool, error)

EvaluateRuleForAsset checks if a specific asset matches a rule.

func (*PostgresMembershipRepository) FindCandidateRules

func (r *PostgresMembershipRepository) FindCandidateRules(ctx context.Context, sig AssetSignature) ([]CandidateRule, error)

FindCandidateRules finds rules that might match an asset based on its signature.

func (*PostgresMembershipRepository) GetDataProductsForAsset

func (r *PostgresMembershipRepository) GetDataProductsForAsset(ctx context.Context, assetID string) ([]string, error)

GetDataProductsForAsset returns all data product IDs that contain an asset.

func (*PostgresMembershipRepository) GetMemberships

func (r *PostgresMembershipRepository) GetMemberships(ctx context.Context, dataProductID string, limit, offset int) ([]Membership, int, error)

GetMemberships returns all memberships for a data product with pagination.

func (*PostgresMembershipRepository) SaveRuleTargets

func (r *PostgresMembershipRepository) SaveRuleTargets(ctx context.Context, ruleID, dataProductID string, targets []RuleTarget) error

SaveRuleTargets replaces the targets for a rule.

func (*PostgresMembershipRepository) UpdateMembershipStats

func (r *PostgresMembershipRepository) UpdateMembershipStats(ctx context.Context, dataProductID string) error

UpdateMembershipStats updates the membership count on a data product.

type PostgresRepository

type PostgresRepository struct {
	// contains filtered or unexported fields
}

func NewPostgresRepository

func NewPostgresRepository(db *pgxpool.Pool, recorder metrics.Recorder) *PostgresRepository

func (*PostgresRepository) AddAssets

func (r *PostgresRepository) AddAssets(ctx context.Context, dataProductID string, assetIDs []string, createdBy string) error

func (*PostgresRepository) Create

func (r *PostgresRepository) Create(ctx context.Context, dp *DataProduct, owners []OwnerInput) error

func (*PostgresRepository) CreateRule

func (r *PostgresRepository) CreateRule(ctx context.Context, dataProductID string, rule *RuleInput) (*Rule, error)

func (*PostgresRepository) Delete

func (r *PostgresRepository) Delete(ctx context.Context, id string) error

func (*PostgresRepository) DeleteProductImage

func (r *PostgresRepository) DeleteProductImage(ctx context.Context, dataProductID string, purpose ImagePurpose) error

func (*PostgresRepository) DeleteRule

func (r *PostgresRepository) DeleteRule(ctx context.Context, ruleID string) error

func (*PostgresRepository) ExecuteRule

func (r *PostgresRepository) ExecuteRule(ctx context.Context, rule *Rule) ([]string, error)

func (*PostgresRepository) Get

func (*PostgresRepository) GetDataProductsForAsset

func (r *PostgresRepository) GetDataProductsForAsset(ctx context.Context, assetID string) ([]*DataProduct, error)

func (*PostgresRepository) GetManualAssets

func (r *PostgresRepository) GetManualAssets(ctx context.Context, dataProductID string, limit, offset int) (*AssetsResult, error)

func (*PostgresRepository) GetProductImage

func (r *PostgresRepository) GetProductImage(ctx context.Context, imageID string) (*ProductImage, error)

func (*PostgresRepository) GetProductImageByPurpose

func (r *PostgresRepository) GetProductImageByPurpose(ctx context.Context, dataProductID string, purpose ImagePurpose) (*ProductImage, error)

func (*PostgresRepository) GetProductImageMeta

func (r *PostgresRepository) GetProductImageMeta(ctx context.Context, dataProductID string, purpose ImagePurpose) (*ProductImageMeta, error)

func (*PostgresRepository) GetRule

func (r *PostgresRepository) GetRule(ctx context.Context, ruleID string) (*Rule, error)

func (*PostgresRepository) GetRules

func (r *PostgresRepository) GetRules(ctx context.Context, dataProductID string) ([]Rule, error)

func (*PostgresRepository) List

func (r *PostgresRepository) List(ctx context.Context, offset, limit int) (*ListResult, error)

func (*PostgresRepository) ListProductImages

func (r *PostgresRepository) ListProductImages(ctx context.Context, dataProductID string) ([]*ProductImageMeta, error)

func (*PostgresRepository) PreviewRule

func (r *PostgresRepository) PreviewRule(ctx context.Context, rule *RuleInput, limit int) (*RulePreview, error)

func (*PostgresRepository) RemoveAsset

func (r *PostgresRepository) RemoveAsset(ctx context.Context, dataProductID string, assetID string) error

func (*PostgresRepository) ResolveAssets

func (r *PostgresRepository) ResolveAssets(ctx context.Context, dataProductID string, limit, offset int) (*ResolvedAssets, error)

func (*PostgresRepository) Search

func (r *PostgresRepository) Search(ctx context.Context, filter SearchFilter) (*ListResult, error)

func (*PostgresRepository) Update

func (r *PostgresRepository) Update(ctx context.Context, dp *DataProduct, owners []OwnerInput) error

func (*PostgresRepository) UpdateRule

func (r *PostgresRepository) UpdateRule(ctx context.Context, ruleID string, rule *RuleInput) (*Rule, error)

func (*PostgresRepository) UploadProductImage

func (r *PostgresRepository) UploadProductImage(ctx context.Context, dataProductID string, purpose ImagePurpose, input UploadImageInput, createdBy *string) (*ProductImage, error)

type ProductImage

type ProductImage struct {
	ID            string       `json:"id"`
	DataProductID string       `json:"data_product_id"`
	Purpose       ImagePurpose `json:"purpose"`
	Filename      string       `json:"filename"`
	ContentType   string       `json:"content_type"`
	SizeBytes     int          `json:"size_bytes"`
	Data          []byte       `json:"-"`
	CreatedAt     time.Time    `json:"created_at"`
	CreatedBy     *string      `json:"created_by,omitempty"`
}

type ProductImageMeta

type ProductImageMeta struct {
	ID            string       `json:"id"`
	DataProductID string       `json:"data_product_id"`
	Purpose       ImagePurpose `json:"purpose"`
	Filename      string       `json:"filename"`
	ContentType   string       `json:"content_type"`
	SizeBytes     int          `json:"size_bytes"`
	URL           string       `json:"url"`
	CreatedAt     time.Time    `json:"created_at"`
}

type Reconciler

type Reconciler struct {
	// contains filtered or unexported fields
}

Reconciler periodically re-evaluates all rules to fix any membership drift.

func NewReconciler

func NewReconciler(membershipSvc *MembershipService, config *ReconcilerConfig) *Reconciler

NewReconciler creates a new reconciler.

func (*Reconciler) RunNow

func (r *Reconciler) RunNow(ctx context.Context) error

RunNow triggers an immediate reconciliation (for manual/API use).

func (*Reconciler) Start

func (r *Reconciler) Start(ctx context.Context)

Start begins the periodic reconciliation loop.

func (*Reconciler) Stop

func (r *Reconciler) Stop()

Stop gracefully shuts down the reconciler.

type ReconcilerConfig

type ReconcilerConfig struct {
	// Interval between full reconciliation runs. Default: 30 minutes.
	Interval time.Duration
}

ReconcilerConfig configures the reconciler.

type Repository

type Repository interface {
	Create(ctx context.Context, dp *DataProduct, owners []OwnerInput) error
	Get(ctx context.Context, id string) (*DataProduct, error)
	Update(ctx context.Context, dp *DataProduct, owners []OwnerInput) error
	Delete(ctx context.Context, id string) error
	List(ctx context.Context, offset, limit int) (*ListResult, error)
	Search(ctx context.Context, filter SearchFilter) (*ListResult, error)

	AddAssets(ctx context.Context, dataProductID string, assetIDs []string, createdBy string) error
	RemoveAsset(ctx context.Context, dataProductID string, assetID string) error
	GetManualAssets(ctx context.Context, dataProductID string, limit, offset int) (*AssetsResult, error)

	CreateRule(ctx context.Context, dataProductID string, rule *RuleInput) (*Rule, error)
	UpdateRule(ctx context.Context, ruleID string, rule *RuleInput) (*Rule, error)
	DeleteRule(ctx context.Context, ruleID string) error
	GetRules(ctx context.Context, dataProductID string) ([]Rule, error)
	GetRule(ctx context.Context, ruleID string) (*Rule, error)

	ResolveAssets(ctx context.Context, dataProductID string, limit, offset int) (*ResolvedAssets, error)
	ExecuteRule(ctx context.Context, rule *Rule) ([]string, error)
	PreviewRule(ctx context.Context, rule *RuleInput, limit int) (*RulePreview, error)

	GetDataProductsForAsset(ctx context.Context, assetID string) ([]*DataProduct, error)

	UploadProductImage(ctx context.Context, dataProductID string, purpose ImagePurpose, input UploadImageInput, createdBy *string) (*ProductImage, error)
	GetProductImage(ctx context.Context, imageID string) (*ProductImage, error)
	GetProductImageByPurpose(ctx context.Context, dataProductID string, purpose ImagePurpose) (*ProductImage, error)
	GetProductImageMeta(ctx context.Context, dataProductID string, purpose ImagePurpose) (*ProductImageMeta, error)
	DeleteProductImage(ctx context.Context, dataProductID string, purpose ImagePurpose) error
	ListProductImages(ctx context.Context, dataProductID string) ([]*ProductImageMeta, error)
}

type ResolvedAssets

type ResolvedAssets struct {
	ManualAssets  []string `json:"manual_assets"`
	DynamicAssets []string `json:"dynamic_assets"`
	AllAssets     []string `json:"all_assets"`
	Total         int      `json:"total"`
}

type Rule

type Rule struct {
	ID              string    `json:"id"`
	DataProductID   string    `json:"data_product_id"`
	Name            string    `json:"name"`
	Description     *string   `json:"description,omitempty"`
	RuleType        RuleType  `json:"rule_type"`
	QueryExpression *string   `json:"query_expression,omitempty"`
	MetadataField   *string   `json:"metadata_field,omitempty"`
	PatternType     *string   `json:"pattern_type,omitempty"`
	PatternValue    *string   `json:"pattern_value,omitempty"`
	Priority        int       `json:"priority"`
	IsEnabled       bool      `json:"is_enabled"`
	CreatedAt       time.Time `json:"created_at"`
	UpdatedAt       time.Time `json:"updated_at"`

	MatchedAssetCount int `json:"matched_asset_count,omitempty"`
}

type RuleInput

type RuleInput struct {
	ID              *string  `json:"id,omitempty"`
	Name            string   `json:"name" validate:"required,min=1,max=255"`
	Description     *string  `json:"description,omitempty"`
	RuleType        RuleType `json:"rule_type" validate:"required,oneof=query metadata_match"`
	QueryExpression *string  `json:"query_expression,omitempty"`
	MetadataField   *string  `json:"metadata_field,omitempty"`
	PatternType     *string  `json:"pattern_type,omitempty" validate:"omitempty,oneof=exact wildcard regex prefix"`
	PatternValue    *string  `json:"pattern_value,omitempty"`
	Priority        int      `json:"priority"`
	IsEnabled       bool     `json:"is_enabled"`
}

type RuleObserver

type RuleObserver interface {
	OnRuleCreated(ctx context.Context, rule *Rule) error
	OnRuleUpdated(ctx context.Context, rule *Rule) error
	OnRuleDeleted(ctx context.Context, ruleID string) error
}

RuleObserver is notified when rules are created, updated, or deleted.

type RulePreview

type RulePreview struct {
	AssetIDs   []string `json:"asset_ids"`
	AssetCount int      `json:"asset_count"`
	Errors     []string `json:"errors,omitempty"`
}

type RuleTarget

type RuleTarget struct {
	RuleID        string
	DataProductID string
	TargetType    string // TargetTypeAssetType, TargetTypeProvider, TargetTypeTag, TargetTypeMetadataKey, or TargetTypeQuery
	TargetValue   string // empty string for complex queries
}

RuleTarget represents what a rule is targeting for fast candidate lookup.

func ExtractRuleTargets

func ExtractRuleTargets(rule *Rule) []RuleTarget

ExtractRuleTargets analyzes a rule and extracts what it's targeting.

type RuleType

type RuleType string
const (
	RuleTypeQuery         RuleType = "query"
	RuleTypeMetadataMatch RuleType = "metadata_match"
)

type SearchFilter

type SearchFilter struct {
	Query    string   `json:"query,omitempty"`
	OwnerIDs []string `json:"owner_ids,omitempty"`
	Tags     []string `json:"tags,omitempty"`
	Limit    int      `json:"limit,omitempty" validate:"omitempty,gte=0,lte=100"`
	Offset   int      `json:"offset,omitempty" validate:"omitempty,gte=0"`
}

type Service

type Service interface {
	Create(ctx context.Context, input CreateInput) (*DataProduct, error)
	Get(ctx context.Context, id string) (*DataProduct, error)
	Update(ctx context.Context, id string, input UpdateInput) (*DataProduct, error)
	Delete(ctx context.Context, id string) error
	List(ctx context.Context, offset, limit int) (*ListResult, error)
	Search(ctx context.Context, filter SearchFilter) (*ListResult, error)

	AddAssets(ctx context.Context, dataProductID string, assetIDs []string, createdBy string) error
	RemoveAsset(ctx context.Context, dataProductID string, assetID string) error
	GetManualAssets(ctx context.Context, dataProductID string, limit, offset int) (*AssetsResult, error)

	CreateRule(ctx context.Context, dataProductID string, input RuleInput) (*Rule, error)
	UpdateRule(ctx context.Context, ruleID string, input RuleInput) (*Rule, error)
	DeleteRule(ctx context.Context, ruleID string) error
	GetRules(ctx context.Context, dataProductID string) ([]Rule, error)
	PreviewRule(ctx context.Context, input RuleInput, limit int) (*RulePreview, error)

	GetResolvedAssets(ctx context.Context, dataProductID string, limit, offset int) (*ResolvedAssets, error)
	GetDataProductsForAsset(ctx context.Context, assetID string) ([]*DataProduct, error)

	// Image methods
	UploadImage(ctx context.Context, dataProductID string, purpose ImagePurpose, input UploadImageInput, createdBy *string) (*ProductImageMeta, error)
	GetImage(ctx context.Context, imageID string) (*ProductImage, error)
	GetImageByPurpose(ctx context.Context, dataProductID string, purpose ImagePurpose) (*ProductImage, error)
	GetImageMeta(ctx context.Context, dataProductID string, purpose ImagePurpose) (*ProductImageMeta, error)
	DeleteImage(ctx context.Context, dataProductID string, purpose ImagePurpose) error
	ListImages(ctx context.Context, dataProductID string) ([]*ProductImageMeta, error)

	SetRuleObserver(observer RuleObserver)
}

func NewService

func NewService(repo Repository) Service

type UpdateInput

type UpdateInput struct {
	Name        *string                `json:"name,omitempty" validate:"omitempty,min=1,max=255"`
	Description *string                `json:"description,omitempty"`
	Metadata    map[string]interface{} `json:"metadata,omitempty"`
	Tags        []string               `json:"tags,omitempty"`
	Owners      []OwnerInput           `json:"owners,omitempty" validate:"omitempty,min=1,dive"`
}

type UploadImageInput

type UploadImageInput struct {
	Filename    string
	ContentType string
	Data        []byte
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL