core

package
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Jun 1, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package core is the reusable Mealie client and workflow layer that sits beneath the CLI. It exposes a small, stable, hand-crafted API surface (clean public DTOs in this package) over the Mealie HTTP API, deliberately hiding upstream quirks such as the camelCase-query / snake_case-pagination mismatch and the households-vs-top-level path split.

The CLI commands and any future MCP server are intended to call this package rather than the HTTP API directly, so there is a single implementation of each workflow. The bundled OpenAPI spec under api/specs is the source of truth for the contract tests that guard the DTO field names against upstream drift.

Index

Constants

View Source
const (
	EntryBreakfast = "breakfast"
	EntryLunch     = "lunch"
	EntryDinner    = "dinner"
	EntrySide      = "side"
	EntrySnack     = "snack"
	EntryDrink     = "drink"
	EntryDessert   = "dessert"
)

Meal plan entry types accepted by the API (the PlanEntryType enum).

Variables

EntryTypes lists the valid meal plan entry types, in display order.

Functions

func FetchAll added in v0.3.0

func FetchAll[T any](ctx context.Context, pageSize int, fetch func(page, perPage int) (*Page[T], error)) ([]T, error)

FetchAll walks a paginated endpoint to completion and returns every item. It is the bounded, client-side replacement for the perPage=-1 "no pagination" sentinel: instead of asking the server for everything in a single response (which a large instance can fail to deliver under the response-size cap), it fetches steady pages until the result set is exhausted.

pageSize is the per-request batch size: 0 falls back to defaultPageSize, and a value < 1 (such as the old -1 sentinel) is rejected with an error rather than being passed back to the server. fetch is invoked once per 1-based page with the resolved batch size and must return that page; any error it returns aborts the whole walk and is propagated unchanged.

func IsConflict

func IsConflict(err error) bool

IsConflict reports a 409 response.

func IsForbidden

func IsForbidden(err error) bool

IsForbidden reports a 403 response.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports a 404 response.

func IsTransport

func IsTransport(err error) bool

IsTransport reports whether err is a transport-level failure.

func IsUnauthorized

func IsUnauthorized(err error) bool

IsUnauthorized reports a 401 response (missing/invalid token).

func IsValidation

func IsValidation(err error) bool

IsValidation reports a 400 or 422 response.

Types

type APIError

type APIError struct {
	StatusCode int
	// Code is a stable machine token derived from the status (e.g. "not_found").
	Code string
	// Message is a single-line human description, extracted from the API body.
	Message string
	// RequestID echoes the server correlation id when present.
	RequestID string
	// Retryable indicates a transient failure (429/502/503/504).
	Retryable bool
	// RetryAfter is the server's suggested wait before retrying, parsed from the
	// Retry-After header (zero when absent).
	RetryAfter time.Duration
	// Fields carries per-field validation errors for 4xx validation responses.
	Fields []FieldError
}

APIError is a non-2xx response from the Mealie API, normalised into a stable shape the CLI can map onto exit codes and the JSON error envelope.

func AsAPIError

func AsAPIError(err error) (*APIError, bool)

AsAPIError extracts an *APIError from err, if present.

func (*APIError) Error

func (e *APIError) Error() string

type About

type About struct {
	Version          string `json:"version"`
	Production       bool   `json:"production"`
	Demo             bool   `json:"demoStatus"`
	AllowSignup      bool   `json:"allowSignup"`
	DefaultGroupSlug string `json:"defaultGroupSlug,omitempty"`
	EnableOIDC       bool   `json:"enableOidc"`
}

About is the public server information from GET /api/app/about. It needs no authentication and is used by `mealie doctor` and `mealie version` to verify connectivity and the server version.

type AddRecipeToList added in v0.3.0

type AddRecipeToList struct {
	RecipeID string  `json:"recipeId"`
	Scale    float64 `json:"recipeIncrementQuantity,omitempty"` // 0/unset → server default (1)
}

AddRecipeToList is one entry in the bulk "add recipe to a shopping list" request body. Scale carries omitempty so an unset (zero) value is dropped from the request, letting the server apply its documented default of 1 rather than us forcing a scale of 0. pkg/core is a public surface, so the tag — not just the CLI — has to honour that default.

type Client

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

Client is a Mealie API client. It is safe for concurrent use once constructed; all configuration happens via options in New.

func New

func New(baseURL, token string, opts ...Option) (*Client, error)

New constructs a Client for the given server base URL (e.g. https://mealie.example.com, without the /api suffix) and API token. The token may be empty for unauthenticated calls such as Login or the public About.

func (*Client) About

func (c *Client) About(ctx context.Context) (*About, error)

About fetches the public server information.

func (*Client) AddRecipesToShoppingList added in v0.3.0

func (c *Client) AddRecipesToShoppingList(ctx context.Context, listID string, recipes []AddRecipeToList) (*ShoppingList, error)

AddRecipesToShoppingList pushes one or more recipes' ingredients onto a shopping list via the bulk endpoint (the non-deprecated .../recipe path; the .../recipe/{recipe_id} variant is deprecated). The request body is a JSON array of AddRecipeToList.

The 200 response is a ShoppingListOut, decoded into the curated ShoppingList. That is deliberately display-lossy — it drops recipeReferences, groupId, userId and the like that the curated struct does not model — because the confirmation render only needs the list name and its listItems, matching the trade-off the rest of the SDK makes.

The POST is intentionally not retried (c.do skips POST): re-running would duplicate the added ingredients, so a single attempt is correct.

func (*Client) AddShoppingItem

func (c *Client) AddShoppingItem(ctx context.Context, in CreateShoppingItem) (*ShoppingListItem, error)

AddShoppingItem adds an item to a list. The create endpoint returns a ShoppingListItemsCollectionOut; the newly created item is returned.

func (*Client) BaseURL

func (c *Client) BaseURL() string

BaseURL returns the configured server root.

func (*Client) CreateMealPlan

func (c *Client) CreateMealPlan(ctx context.Context, in CreateMealPlan) (*MealPlan, error)

CreateMealPlan adds a meal plan entry.

func (*Client) CreateRecipe

func (c *Client) CreateRecipe(ctx context.Context, name string) (string, error)

CreateRecipe creates an empty recipe with the given name and returns its slug.

func (*Client) CreateRecipeFromURL

func (c *Client) CreateRecipeFromURL(ctx context.Context, recipeURL string, includeTags bool) (string, error)

CreateRecipeFromURL scrapes the given URL into a new recipe and returns its slug.

func (*Client) CreateShoppingList

func (c *Client) CreateShoppingList(ctx context.Context, name string) (*ShoppingList, error)

CreateShoppingList creates a new shopping list with the given name.

func (*Client) DeleteMealPlan

func (c *Client) DeleteMealPlan(ctx context.Context, id int) error

DeleteMealPlan removes a meal plan entry by id.

func (*Client) DeleteRecipe

func (c *Client) DeleteRecipe(ctx context.Context, slug string) error

DeleteRecipe deletes a recipe by slug.

func (*Client) DeleteShoppingItem

func (c *Client) DeleteShoppingItem(ctx context.Context, id string) error

DeleteShoppingItem removes an item by id.

func (*Client) DeleteShoppingList

func (c *Client) DeleteShoppingList(ctx context.Context, id string) error

DeleteShoppingList deletes a shopping list by id.

func (*Client) ExportRecipe added in v0.3.0

func (c *Client) ExportRecipe(ctx context.Context, slug string) (json.RawMessage, error)

ExportRecipe fetches a single recipe by slug and returns the response body verbatim. Unlike GetRecipe, which decodes into the curated Recipe struct (and silently drops fields it does not model), this preserves every field the server sends — it is the lossless source for the `recipe export` backup flow.

func (*Client) GetRecipe

func (c *Client) GetRecipe(ctx context.Context, slug string) (*Recipe, error)

GetRecipe fetches a single recipe by slug.

func (*Client) GetShoppingItem

func (c *Client) GetShoppingItem(ctx context.Context, id string) (*ShoppingListItem, error)

GetShoppingItem fetches a single shopping list item by id.

func (*Client) GetShoppingList

func (c *Client) GetShoppingList(ctx context.Context, id string) (*ShoppingList, error)

GetShoppingList fetches a single shopping list, including its items.

func (*Client) ListMealPlans

func (c *Client) ListMealPlans(ctx context.Context, opts ListOptions, start, end string) (*Page[MealPlan], error)

ListMealPlans returns a page of meal plan entries, optionally restricted to a date range (inclusive, YYYY-MM-DD). Empty start/end disable that bound.

func (*Client) ListRecipes

func (c *Client) ListRecipes(ctx context.Context, opts RecipeListOptions) (*Page[RecipeSummary], error)

ListRecipes returns a page of recipe summaries.

func (*Client) ListShoppingLists

func (c *Client) ListShoppingLists(ctx context.Context, opts ListOptions) (*Page[ShoppingList], error)

ListShoppingLists returns a page of shopping lists.

func (*Client) Login

func (c *Client) Login(ctx context.Context, username, password, tokenName string) (string, error)

Login authenticates with a username and password against POST /api/auth/token and, when tokenName is non-empty, mints a long-lived API token named tokenName via POST /api/users/api-tokens. The returned string is the long-lived token (or the short-lived access token if tokenName is empty). The client's own token is not used or modified.

func (*Client) SetItemChecked

func (c *Client) SetItemChecked(ctx context.Context, id string, checked bool) (*ShoppingListItem, error)

SetItemChecked toggles the checked state of an item. It round-trips the full raw item (as a map) so fields outside the curated struct — labelId, foodId, unitId, extras, recipe references, position — are preserved on update, then decodes the ShoppingListItemsCollectionOut update envelope.

func (*Client) TodayMealPlans

func (c *Client) TodayMealPlans(ctx context.Context) ([]MealPlan, error)

TodayMealPlans returns the meal plan entries scheduled for the current day.

func (*Client) Whoami

func (c *Client) Whoami(ctx context.Context) (*User, error)

Whoami returns the user the current token authenticates as. A 401 indicates an absent or invalid token.

type CreateMealPlan

type CreateMealPlan struct {
	Date      string `json:"date"`
	EntryType string `json:"entryType"`
	Title     string `json:"title,omitempty"`
	Text      string `json:"text,omitempty"`
	RecipeID  string `json:"recipeId,omitempty"`
}

CreateMealPlan is the payload for adding a meal plan entry. Provide either a RecipeID (to plan an existing recipe) or a Title/Text (for a free-text entry).

type CreateShoppingItem

type CreateShoppingItem struct {
	ShoppingListID string   `json:"shoppingListId"`
	Note           string   `json:"note,omitempty"`
	Quantity       *float64 `json:"quantity,omitempty"`
}

CreateShoppingItem is the payload for adding a free-text item to a list. Only ShoppingListID is required by Mealie; a note-only item is created by sending just the note (and an optional quantity).

type FieldError

type FieldError struct {
	Location string `json:"location"`
	Message  string `json:"message"`
}

FieldError is a single validation problem reported by the API.

type ListOptions

type ListOptions struct {
	Page           int
	PerPage        int
	OrderBy        string
	OrderDirection string
	QueryFilter    string
	Search         string
}

ListOptions are the pagination/ordering/search parameters common to all list endpoints. Zero-valued fields are omitted from the request.

func All

func All() ListOptions

All returns ListOptions that request every result in a single page (Mealie interprets perPage=-1 as "no pagination").

type MealPlan

type MealPlan struct {
	ID        int            `json:"id"`
	Date      string         `json:"date"`
	EntryType string         `json:"entryType"`
	Title     string         `json:"title,omitempty"`
	Text      string         `json:"text,omitempty"`
	RecipeID  *string        `json:"recipeId,omitempty"`
	Recipe    *RecipeSummary `json:"recipe,omitempty"`
}

MealPlan is a single meal plan entry from /api/households/mealplans. Entry ids are integers in Mealie (unlike most resources which use UUID strings).

type NamedRef

type NamedRef struct {
	ID   string `json:"id,omitempty"`
	Name string `json:"name,omitempty"`
}

NamedRef is a minimal id/name reference (e.g. a food or unit on an ingredient).

type Option

type Option func(*Client)

Option configures a Client.

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

WithHTTPClient supplies a custom *http.Client (e.g. for proxies or test transports). When provided, the client is used as-is and is never mutated; WithTimeout is then ignored (set the timeout on your own client instead).

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets how many times transient failures are retried for idempotent methods (GET/HEAD/PUT/DELETE). POST is never auto-retried.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the per-request timeout applied to the client New builds. It has no effect when a custom client is supplied via WithHTTPClient.

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent sets the User-Agent header, e.g. "mealie-cli/1.2.0".

type OrganizerRef

type OrganizerRef struct {
	ID   string `json:"id,omitempty"`
	Name string `json:"name"`
	Slug string `json:"slug,omitempty"`
}

OrganizerRef is a category, tag or tool reference attached to a recipe.

type Page

type Page[T any] struct {
	Page       int     `json:"page"`
	PerPage    int     `json:"per_page"`
	Total      int     `json:"total"`
	TotalPages int     `json:"total_pages"`
	Items      []T     `json:"items"`
	Next       *string `json:"next"`
	Previous   *string `json:"previous"`
}

Page is the normalised pagination envelope. Upstream Mealie uses snake_case keys here (per_page/total_pages) even though list query parameters are camelCase; this type pins the response contract regardless.

type Recipe

type Recipe struct {
	RecipeSummary
	RecipeIngredient   []RecipeIngredient `json:"recipeIngredient,omitempty"`
	RecipeInstructions []RecipeStep       `json:"recipeInstructions,omitempty"`
	Tools              []OrganizerRef     `json:"tools,omitempty"`
	Notes              []RecipeNote       `json:"notes,omitempty"`
	OrgURL             string             `json:"orgURL,omitempty"`
}

Recipe is the full recipe detail. It embeds RecipeSummary so summary fields appear inline in JSON output.

type RecipeIngredient

type RecipeIngredient struct {
	Quantity     *float64  `json:"quantity,omitempty"`
	Unit         *NamedRef `json:"unit,omitempty"`
	Food         *NamedRef `json:"food,omitempty"`
	Note         string    `json:"note,omitempty"`
	Display      string    `json:"display,omitempty"`
	Title        string    `json:"title,omitempty"`
	OriginalText string    `json:"originalText,omitempty"`
}

RecipeIngredient is one ingredient line. Display/OriginalText carry the human-readable rendering; Food/Unit are the structured references when parsed.

type RecipeListOptions

type RecipeListOptions struct {
	ListOptions
	Categories []string
	Tags       []string
	Tools      []string
	Cookbook   string
}

RecipeListOptions extends ListOptions with recipe-specific filters that map to the convenience query parameters on GET /api/recipes.

type RecipeNote

type RecipeNote struct {
	Title string `json:"title,omitempty"`
	Text  string `json:"text,omitempty"`
}

RecipeNote is a free-text note attached to a recipe.

type RecipeStep

type RecipeStep struct {
	Title string `json:"title,omitempty"`
	Text  string `json:"text,omitempty"`
}

RecipeStep is one instruction step.

type RecipeSummary

type RecipeSummary struct {
	ID          string         `json:"id,omitempty"`
	Slug        string         `json:"slug"`
	Name        string         `json:"name"`
	Description string         `json:"description,omitempty"`
	Image       string         `json:"image,omitempty"`
	RecipeYield string         `json:"recipeYield,omitempty"`
	TotalTime   string         `json:"totalTime,omitempty"`
	PrepTime    string         `json:"prepTime,omitempty"`
	CookTime    string         `json:"cookTime,omitempty"`
	Rating      *float64       `json:"rating,omitempty"`
	Categories  []OrganizerRef `json:"recipeCategory,omitempty"`
	Tags        []OrganizerRef `json:"tags,omitempty"`
	DateAdded   string         `json:"dateAdded,omitempty"`
	DateUpdated string         `json:"dateUpdated,omitempty"`
}

RecipeSummary is the list/summary view of a recipe. Field names mirror the Mealie API (camelCase) so the curated set is a faithful, stable subset.

type ShoppingList

type ShoppingList struct {
	ID        string             `json:"id"`
	Name      string             `json:"name"`
	CreatedAt string             `json:"createdAt,omitempty"`
	UpdatedAt string             `json:"updatedAt,omitempty"`
	ListItems []ShoppingListItem `json:"listItems,omitempty"`
}

ShoppingList is a household shopping list. ListItems is populated by GetShoppingList but is typically empty in the list/summary view.

type ShoppingListItem

type ShoppingListItem struct {
	ID             string    `json:"id"`
	ShoppingListID string    `json:"shoppingListId"`
	Checked        bool      `json:"checked"`
	Position       int       `json:"position"`
	Note           string    `json:"note,omitempty"`
	Quantity       *float64  `json:"quantity,omitempty"`
	FoodID         *string   `json:"foodId,omitempty"`
	Food           *NamedRef `json:"food,omitempty"`
	UnitID         *string   `json:"unitId,omitempty"`
	Unit           *NamedRef `json:"unit,omitempty"`
	LabelID        *string   `json:"labelId,omitempty"`
	Display        string    `json:"display,omitempty"`
}

ShoppingListItem is one entry on a shopping list. Field names mirror the Mealie ShoppingListItemOut schema.

type TransportError

type TransportError struct{ Err error }

TransportError wraps a network/transport-level failure (no HTTP response).

func (*TransportError) Error

func (e *TransportError) Error() string

func (*TransportError) Unwrap

func (e *TransportError) Unwrap() error

type User

type User struct {
	ID        string `json:"id"`
	Username  string `json:"username"`
	FullName  string `json:"fullName,omitempty"`
	Email     string `json:"email,omitempty"`
	Admin     bool   `json:"admin"`
	Group     string `json:"group,omitempty"`
	Household string `json:"household,omitempty"`
}

User is the authenticated user from GET /api/users/self.

Jump to

Keyboard shortcuts

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