calsvc

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Jun 25, 2026 License: Unlicense Imports: 23 Imported by: 0

Documentation

Overview

Package calsvc is the shared service layer between the user-facing frontends (the CLI and the MCP server) and the calendar/event domain packages.

It owns everything between "user-shaped strings" and domain calls: session bootstrap, key unlocking, calendar resolution and caching, datetime/occurrence parsing, recurrence-option validation, and the orchestration of list/create/update/delete operations. Frontends are reduced to argument binding and rendering.

Bootstrap responses (key material and the calendar list - never event content) are cached on disk with liberal TTLs; stale entries are self-healing because key material fails cryptographically when wrong (see cacheapi.go).

Index

Constants

This section is empty.

Variables

View Source
var ErrICSUndecryptable = errors.New("event could not be decrypted into iCalendar")

ErrICSUndecryptable is the error frontends return when an ICS export was requested but no card could be decrypted (GotEvent.ICS is empty).

Functions

func FormatOccurrenceStart

func FormatOccurrenceStart(ts int64, allDay bool, loc *time.Location) string

FormatOccurrenceStart renders an occurrence's original start in the form the occurrence arguments accept back: the date (all-day, midnight UTC) or wall time in loc.

func ResolveColorCreate

func ResolveColorCreate(spec string) (string, error)

ResolveColorCreate resolves a create-side color spec to a palette hex. "" or "default" return "" (a create with no color inherits the calendar's).

func ResolveCreateReminders

func ResolveCreateReminders(mode ReminderMode, specs []string) (list []caltypes.Notification, set bool, err error)

ResolveCreateReminders turns a mode + specs into the create-side (list, set) pair: inherit -> (nil, false); none -> (nil, true); custom -> (list, true). ReminderKeep is treated as inherit (a create with no reminders inherits).

func ResolveUpdateReminders

func ResolveUpdateReminders(mode ReminderMode, specs []string) (*event.RemindersUpdate, error)

ResolveUpdateReminders turns a mode + specs into the update tri-state (nil = keep). The Inherit:true (revert -> null) vs List:nil (none -> []) distinction is preserved deliberately; see event/write.go.

Types

type ColorUpdate

type ColorUpdate struct {
	Inherit bool
	Hex     string
}

ColorUpdate is the calsvc-level color change intent. Inherit reverts to the calendar's color (resolved at apply time); else Hex is the palette color to set.

func ResolveColorUpdate

func ResolveColorUpdate(spec string) (*ColorUpdate, error)

ResolveColorUpdate resolves an update-side color spec to the change intent. "default" reverts to the calendar color; else the spec is a name/hex. The caller decides whether a color was supplied at all (nil = keep).

type CreateCalendarInput

type CreateCalendarInput struct {
	Name        string
	Description string
	Color       string
	MakeDefault bool
}

CreateCalendarInput describes a new owned calendar. Name is required; Color is a Proton color name or hex (empty = a random palette color, as the web client does). MakeDefault also sets it as the account's default calendar.

type CreateEventInput

type CreateEventInput struct {
	Summary     string
	Description string
	Location    string
	Start       string // "YYYY-MM-DD HH:MM"; with AllDay: "YYYY-MM-DD"
	End         string // timed: "" defaults to start + calendar default duration; all-day: inclusive end date, "" = Start
	AllDay      bool
	Recurrence  Recurrence
	Calendar    string
	TZ          string
	// Reminders + RemindersSet select the tri-state (false = inherit default,
	// true+empty = none, true+non-empty = custom); pre-parsed by the frontend.
	Reminders    []caltypes.Notification
	RemindersSet bool
	// Color is the per-event color override ("#RRGGBB"); "" = inherit.
	Color string
}

CreateEventInput describes a new event with user-shaped strings.

type CreatedEvent

type CreatedEvent struct {
	ID          string
	UID         string
	Summary     string
	Description string
	Location    string
	Start       time.Time
	End         time.Time
	AllDay      bool
	RRule       string
	Reminders   []caltypes.Notification
	Color       string
}

CreatedEvent reports a create outcome. ID/UID empty when the server didn't echo the row; Start/End are echoed times if available, else requested. Reminders/Color reflect what was REQUESTED (the create response doesn't reliably echo row fields).

type DeleteCalendarInput

type DeleteCalendarInput struct {
	Selector string
	Password string
}

DeleteCalendarInput describes a calendar deletion. Password (the login password) is required only for owned (normal) calendars, which need the elevated "locked" scope; it is ignored for managed (holidays) calendars.

type DeleteEventInput

type DeleteEventInput struct {
	EventID    string
	Occurrence string // "" = whole event/series; else the ORIGINAL start of one occurrence
	Calendar   string
	TZ         string
}

DeleteEventInput addresses a deletion with user-shaped strings.

type EventList

type EventList struct {
	Calendars []calendar.Info // the resolved calendars this window covers
	Location  *time.Location  // display zone the read was resolved in
	From      time.Time
	FromGiven bool // false when the window starts "now"
	Days      int
	Items     []ListedItem // sorted by occurrence start, then row ID
}

EventList is a resolved, expanded, decrypted listing window. It may span multiple calendars; each item carries its own calendar context.

func (*EventList) SingleCalendar

func (l *EventList) SingleCalendar() bool

SingleCalendar reports whether the window covers exactly one calendar (the common case); frontends use it to decide whether to label each event with its calendar.

type GetEventInput

type GetEventInput struct {
	EventID  string
	Calendar string // calendar ID or name; "" = default, else first
	TZ       string
	WithICS  bool // also reconstruct the raw iCalendar (BuildICS)
	// NoSeries (with WithICS on a recurring event) limits the export to the single
	// addressed VEVENT instead of the whole series; no-op (warns) for non-recurring.
	NoSeries bool
}

GetEventInput addresses a single event for detailed retrieval.

type GotCalendar

type GotCalendar struct {
	Info      calendar.Info
	Settings  calendar.Settings
	IsDefault bool
}

GotCalendar is a single resolved calendar with its default settings and a flag for whether it is the configured default.

type GotEvent

type GotEvent struct {
	Calendar calendar.Info
	Settings calendar.Settings // calendar default reminders (for inheritance)
	Location *time.Location    // display zone the read was resolved in
	Event    *event.Event
	Raw      *caltypes.RawEvent
	ICS      string // populated only when GetEventInput.WithICS
}

GotEvent is a single decrypted event with its raw row, the resolved calendar, the display zone, and (when requested) the reconstructed ICS.

type ListEventsInput

type ListEventsInput struct {
	Calendars    []string // calendar IDs or names; empty = default, else first
	AllCalendars bool     // list every calendar (ignores Calendars)
	TZ           string   // IANA zone override; "" = configured / system
	From         string   // window start "YYYY-MM-DD [HH:MM]"; "" = now
	Days         int      // days to look ahead; <= 0 = 7
}

ListEventsInput addresses a listing window with user-shaped strings. The window spans one or more calendars: an empty Calendars (with AllCalendars false) lists the default-or-first calendar; AllCalendars lists every calendar.

type ListedItem

type ListedItem struct {
	event.Listed
	Calendar calendar.Info
	Settings calendar.Settings
}

ListedItem is one expanded occurrence tagged with the calendar it came from. event.Listed is embedded so callers reach Event/Occurrence directly; Calendar and Settings give the per-item calendar context needed when a listing spans multiple calendars (each calendar has its own color and default reminders).

type Recurrence

type Recurrence struct {
	Repeat   string // "", "daily", "weekly", "monthly", "yearly"
	Every    int    // interval; 0 or 1 = default
	Count    int    // 0 = unset
	Until    string // "" = unset; YYYY-MM-DD
	RawRRule string // raw RRULE value; replaces the structured options
}

Recurrence holds the structured recurrence options shared by the create and update operations.

func (Recurrence) Empty

func (r Recurrence) Empty() bool

Empty reports whether no recurrence options were given.

type ReminderMode

type ReminderMode int

ReminderMode is the surface-independent reminder change intent, shared by the CLI (from boolean flags) and the MCP server (from a string). The CLI never uses ReminderKeep on create (a new event with no flags inherits, same as ReminderInherit); update uses all four.

const (
	ReminderKeep    ReminderMode = iota // leave the event's reminders unchanged (update only)
	ReminderInherit                     // revert to the calendar default
	ReminderNone                        // explicitly no reminders
	ReminderCustom                      // use the supplied list
)

Reminder modes, shared by both frontends.

func ParseReminderMode

func ParseReminderMode(s string) (ReminderMode, error)

ParseReminderMode maps the MCP reminders_mode string to a ReminderMode. "" and "keep" both mean keep.

type Service

type Service struct {

	// Notify receives short human-readable notices (e.g. which calendar
	// was picked when no selector was given). Nil = silent.
	Notify func(msg string)
	// contains filtered or unexported fields
}

Service bundles the authenticated state shared by calendar operations. Construct with New; safe for concurrent use (the MCP server shares one).

func New

func New(noCache bool) (*Service, error)

New restores an authenticated Service from the persisted session (missing session yields a "run login" error). noCache disables the on-disk bootstrap cache. Call Close when done.

func NewDetached

func NewDetached(cfg config.Config) *Service

NewDetached returns a Service with config only - no session or client. For tests exercising validation paths; domain operations on it will panic.

func NewWithAPI

func NewWithAPI(cfg config.Config, api papi.API) *Service

NewWithAPI builds a Service whose reads are served by the given papi.API (both cached and fresh path), with no session client. A test seam for resolution and read-only ops; operations needing the *papi.Client (key unlock) will panic.

func (*Service) Calendars

func (s *Service) Calendars(ctx context.Context) ([]calendar.Info, error)

Calendars fetches the calendar list fresh (so a long-running MCP server sees newly created calendars; the cache-staleness escape hatch), still updating both caches.

func (*Service) Close

func (s *Service) Close()

Close releases the underlying API client.

func (*Service) CreateCalendar

func (s *Service) CreateCalendar(ctx context.Context, in CreateCalendarInput) (*GotCalendar, error)

CreateCalendar creates a new owned calendar and returns its refreshed detail. It resolves the color (random when unset), unlocks the account keys to pick the signing address, then performs the two-step create (metadata + keys). On a key-setup failure the (keyless) calendar's ID is reported via the wrapped error so it can be completed or removed.

func (*Service) CreateEvent

func (s *Service) CreateEvent(ctx context.Context, in CreateEventInput) (*CreatedEvent, error)

CreateEvent validates, resolves and creates an event.

func (*Service) DefaultCalendarID

func (s *Service) DefaultCalendarID(ctx context.Context) (string, error)

DefaultCalendarID returns the account's server-side default calendar ID (source of truth for default markers and unspecified selectors); cached fetch, empty = no default set.

func (*Service) DeleteCalendar

func (s *Service) DeleteCalendar(ctx context.Context, in DeleteCalendarInput) error

DeleteCalendar removes a calendar. Owned (normal) calendars need the elevated scope (re-prove the login password via SRP); managed (holidays) use the managed route without a password. Subscribed calendars cannot be deleted this way.

func (*Service) DeleteEvent

func (s *Service) DeleteEvent(ctx context.Context, in DeleteEventInput) (*event.DeleteResult, error)

DeleteEvent resolves and deletes an event, series or single occurrence (per event.SmartDelete).

func (*Service) EffectiveTimezone

func (s *Service) EffectiveTimezone(override string) string

EffectiveTimezone resolves a timezone: the explicit override when given, else the configured / system timezone.

func (*Service) GetCalendar

func (s *Service) GetCalendar(ctx context.Context, selector string) (*GotCalendar, error)

GetCalendar resolves a calendar by selector ("" = configured default, else first), unlocks it to read default settings, and reports whether it is the configured default. The bootstrap fetch is cached.

func (*Service) GetEvent

func (s *Service) GetEvent(ctx context.Context, in GetEventInput) (*GotEvent, error)

GetEvent fetches, decrypts and (optionally) reconstructs the ICS of a single event. It must live in the resolved calendar (default/first unless Calendar is given); a missing event returns an error advising --calendar.

func (*Service) ListEvents

func (s *Service) ListEvents(ctx context.Context, in ListEventsInput) (*EventList, error)

ListEvents lists all event occurrences in the addressed window across one or more calendars, merged and sorted by occurrence start.

func (*Service) ResolveCalendarInfo

func (s *Service) ResolveCalendarInfo(ctx context.Context, selector string) (calendar.Info, error)

ResolveCalendarInfo resolves a selector to a calendar's Info without unlocking keys. Used for pre-delete dry runs and to decide whether a password is needed.

func (*Service) UpdateCalendar

func (s *Service) UpdateCalendar(ctx context.Context, in UpdateCalendarInput) (*GotCalendar, error)

UpdateCalendar applies metadata/settings/default changes and returns refreshed detail. Only owned (normal) calendars can be modified. Applied in order (metadata, settings, default); a later failure leaves earlier changes standing.

func (*Service) UpdateEvent

func (s *Service) UpdateEvent(ctx context.Context, in UpdateEventInput) (*event.UpdateOutcome, error)

UpdateEvent validates, resolves and applies a partial update; series and single-occurrence semantics are handled per event.SmartUpdate.

type UpdateCalendarInput

type UpdateCalendarInput struct {
	Selector string

	// Metadata (per-member): nil = keep.
	Name        *string
	Description *string
	Color       *string // a Proton color name or hex; validated to a palette hex

	// Default settings: nil = keep.
	DefaultDuration  *int
	PartDayReminders *[]caltypes.Notification
	FullDayReminders *[]caltypes.Notification
	MakesUserBusy    *bool

	// MakeDefault sets this calendar as the account's default calendar.
	MakeDefault bool
}

UpdateCalendarInput describes a partial update to a calendar's metadata/settings plus an optional make-default request. Pointer fields nil = leave unchanged; an empty string clears Description, but Name/Color must be non-empty.

type UpdateEventInput

type UpdateEventInput struct {
	EventID     string
	Summary     *string
	Description *string
	Location    *string
	Start       string // "" = keep
	End         string // "" = keep
	Occurrence  string // "" = whole event/series; else the ORIGINAL start of one occurrence
	NoRepeat    bool
	Recurrence  Recurrence
	Calendar    string
	TZ          string
	// Reminders: nil = keep current; non-nil overrides (frontend resolves the
	// keep/inherit/none/custom intent into the event tri-state type).
	Reminders *event.RemindersUpdate
	// Color: nil = keep current; non-nil overrides. Proton has no color "inherit"
	// sentinel - reverting means setting the calendar's own color, which UpdateEvent
	// resolves, so this carries the intent rather than a literal hex.
	Color *ColorUpdate
}

UpdateEventInput describes a partial update with user-shaped strings. Text fields are pointers so frontends choose presence semantics (CLI sets on flag presence, empty clears; MCP only for non-empty values).

Jump to

Keyboard shortcuts

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