storage

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Dec 28, 2025 License: MIT Imports: 8 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Database

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

Database is a thin wrapper around an *sql.DB connection pool. It centralizes database access for the package and provides a single place to implement queries, transactions, migrations and other persistence-related helpers.

The underlying *sql.DB is safe for concurrent use, but a Database with a nil internal pointer is not usable — it must be initialized (for example via sql.Open) and closed when no longer needed.

func Initialize

func Initialize() (*Database, error)

Initialize ensures the on-disk storage for the application exists, opens the SQLite database, and returns a Database wrapper.

Specifically, Initialize:

  • determines the current user's home directory,
  • checks the TMPO_DEV environment variable:
  • if TMPO_DEV is "1" or "true", uses "$HOME/.tmpo-dev" (development mode),
  • otherwise uses "$HOME/.tmpo" (production mode, the default),
  • creates the appropriate directory if it does not already exist,
  • opens (or creates) the SQLite database file "tmpo.db" in that directory,
  • ensures the time_entries table exists with the schema: id INTEGER PRIMARY KEY AUTOINCREMENT, project_name TEXT NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME, description TEXT

On success it returns a *Database containing the opened *sql.DB. It returns a non-nil error if any step fails: obtaining the home directory, creating the directory, opening the database, or creating the table. The caller is responsible for closing the database when finished.

func (*Database) Close

func (d *Database) Close() error

Close closes the Database, releasing any underlying resources. It delegates to the wrapped database's Close method and returns any error encountered. After Close is called, the Database must not be used for further operations.

func (*Database) CreateEntry

func (d *Database) CreateEntry(projectName, description string, hourlyRate *float64, milestoneName *string) (*TimeEntry, error)

CreateEntry inserts a new time entry for the specified projectName with the given description, hourlyRate, and milestoneName. The entry's start_time is set to the current time. If hourlyRate is nil, the hourly_rate column will be set to NULL. If milestoneName is nil, the milestone_name column will be set to NULL. On success it returns the created *TimeEntry (retrieved by querying the database for the last insert id). If the insert or the subsequent retrieval fails, an error wrapping the underlying database error is returned.

func (*Database) CreateManualEntry

func (d *Database) CreateManualEntry(projectName, description string, startTime, endTime time.Time, hourlyRate *float64, milestoneName *string) (*TimeEntry, error)

CreateManualEntry inserts a completed time entry with specific start and end times. Unlike CreateEntry which uses the current time and leaves end_time NULL, this method creates a fully specified historical entry for manual record-keeping. If hourlyRate is nil, the hourly_rate column will be set to NULL. If milestoneName is nil, the milestone_name column will be set to NULL. On success it returns the created *TimeEntry (retrieved by querying the database for the last insert id). If the insert or the subsequent retrieval fails, an error wrapping the underlying database error is returned.

func (*Database) CreateMilestone added in v0.3.0

func (d *Database) CreateMilestone(projectName, name string) (*Milestone, error)

CreateMilestone inserts a new milestone for the specified projectName with the given name. The milestone's start_time is set to the current time and end_time is NULL (indicating active status). On success it returns the created *Milestone (retrieved by querying the database for the last insert id). If a milestone with the same project_name and name already exists, an error is returned.

func (*Database) DeleteTimeEntry

func (d *Database) DeleteTimeEntry(id int64) error

DeleteTimeEntry deletes a time entry from the database by its ID. Returns an error if the deletion fails. Does not verify that a row with the given ID exists; if no rows are affected the function will still return nil (no error).

func (*Database) FinishMilestone added in v0.3.0

func (d *Database) FinishMilestone(id int64) error

FinishMilestone sets the end_time of the milestone identified by id to the current time. This marks the milestone as completed.

func (*Database) GetActiveMilestoneForProject added in v0.3.0

func (d *Database) GetActiveMilestoneForProject(projectName string) (*Milestone, error)

GetActiveMilestoneForProject retrieves the currently active milestone for the specified projectName (i.e., has a NULL end_time). Returns (nil, nil) if no active milestone exists for the project.

func (*Database) GetAllMilestones added in v0.3.0

func (d *Database) GetAllMilestones() ([]*Milestone, error)

GetAllMilestones retrieves all milestones from all projects, ordered by start_time DESC (newest first).

func (*Database) GetAllProjects

func (d *Database) GetAllProjects() ([]string, error)

GetAllProjects retrieves all distinct project names from the time_entries table. The results are returned in ascending order by project_name. On success it returns a slice of project names (which will be empty if no projects exist) and a nil error. If the underlying database query or a row scan fails, it returns a non-nil error describing the failure.

func (*Database) GetCompletedEntriesByProject

func (d *Database) GetCompletedEntriesByProject(projectName string) ([]*TimeEntry, error)

GetCompletedEntriesByProject retrieves completed time entries (where end_time IS NOT NULL) for the specified projectName from the time_entries table. Results are ordered by start_time in descending order (newest first).

For each row a TimeEntry is populated. Since this query only returns completed entries, the EndTime field will always be non-nil. If the hourly_rate column is NULL the returned TimeEntry.HourlyRate will be nil; otherwise HourlyRate will point to the scanned float64. If the milestone_name column is NULL the returned TimeEntry.MilestoneName will be nil.

On success the function returns a slice of pointers to TimeEntry. If there are no matching rows the returned slice will have length 0 (it may be nil). On failure the function returns a non-nil error and a nil slice. Errors may originate from the query execution, row scanning, or row iteration.

func (*Database) GetEntries

func (d *Database) GetEntries(limit int) ([]*TimeEntry, error)

GetEntries retrieves time entries from the Database.

It returns time entries ordered by start_time in descending order. If limit > 0, at most `limit` entries are returned; if limit <= 0 all matching entries are returned. Each returned element is a pointer to a TimeEntry. The EndTime field of a TimeEntry will be nil when the corresponding end_time column in the database is NULL. The HourlyRate field will be nil when the corresponding hourly_rate column in the database is NULL. The MilestoneName field will be nil when the corresponding milestone_name column is NULL.

The function performs a SQL query selecting id, project_name, start_time, end_time, description, hourly_rate, and milestone_name. It returns a slice of entries and an error if the query or row scanning fails; any underlying error is wrapped.

func (*Database) GetEntriesByDateRange

func (d *Database) GetEntriesByDateRange(start, end time.Time) ([]*TimeEntry, error)

GetEntriesByDateRange retrieves time entries whose start_time falls between start and end (inclusive). Results are returned in descending order by start_time. The provided start and end times are passed to the database driver as-is; callers should ensure they use the intended timezone/representation. For rows with a NULL end_time the returned TimeEntry.EndTime will be nil; otherwise EndTime points to the parsed time value. For rows with a NULL hourly_rate the returned TimeEntry.HourlyRate will be nil; otherwise HourlyRate points to the parsed float64. For rows with a NULL milestone_name the returned TimeEntry.MilestoneName will be nil. Returns a slice of pointers to TimeEntry (which may be empty) or an error if the database query or row scanning fails.

func (*Database) GetEntriesByMilestone added in v0.3.0

func (d *Database) GetEntriesByMilestone(projectName, milestoneName string) ([]*TimeEntry, error)

GetEntriesByMilestone retrieves time entries for the specified projectName and milestoneName from the time_entries table. Results are ordered by start_time DESC.

func (*Database) GetEntriesByProject

func (d *Database) GetEntriesByProject(projectName string) ([]*TimeEntry, error)

GetEntriesByProject retrieves time entries for the specified projectName from the time_entries table. Results are ordered by start_time in descending order (newest first).

For each row a TimeEntry is populated. If the end_time column is NULL the returned TimeEntry.EndTime will be nil; otherwise EndTime will point to the scanned time.Time. If the hourly_rate column is NULL the returned TimeEntry.HourlyRate will be nil; otherwise HourlyRate will point to the scanned float64. If the milestone_name column is NULL the returned TimeEntry.MilestoneName will be nil.

On success the function returns a slice of pointers to TimeEntry. If there are no matching rows the returned slice will have length 0 (it may be nil). On failure the function returns a non-nil error and a nil slice. Errors may originate from the query execution, row scanning, or row iteration.

func (*Database) GetEntry

func (d *Database) GetEntry(id int64) (*TimeEntry, error)

GetEntry retrieves a TimeEntry by its ID from the database. It queries the time_entries table for id, project_name, start_time, end_time, description, hourly_rate, and milestone_name, scans the result into a TimeEntry value, and returns a pointer to it. If the end_time column is NULL in the database, the returned TimeEntry.EndTime will be nil; otherwise EndTime will point to the retrieved time value. If the hourly_rate column is NULL, the returned TimeEntry.HourlyRate will be nil; otherwise HourlyRate will point to the retrieved value. If the milestone_name column is NULL, the returned TimeEntry.MilestoneName will be nil. If no row is found or an error occurs during query/scan, an error is returned (wrapped with the context "failed to get entry").

func (*Database) GetLastStoppedEntry added in v0.1.2

func (d *Database) GetLastStoppedEntry() (*TimeEntry, error)

GetLastStoppedEntry retrieves the most recently stopped time entry (i.e. has a non-NULL end_time) from the time_entries table. The query orders by start_time descending and returns at most one row.

If there is no stopped entry, GetLastStoppedEntry returns (nil, nil). If the database query or scan fails, it returns a non-nil error describing the failure.

The function scans id, project_name, start_time, end_time, description, hourly_rate, and milestone_name into a TimeEntry. Since this query only returns stopped entries, the EndTime field will always be non-nil. The HourlyRate and MilestoneName fields are set only if their scanned values are non-NULL.

func (*Database) GetMilestone added in v0.3.0

func (d *Database) GetMilestone(id int64) (*Milestone, error)

GetMilestone retrieves a Milestone by its ID from the database. Returns (nil, nil) if no milestone with the given ID exists.

func (*Database) GetMilestoneByName added in v0.3.0

func (d *Database) GetMilestoneByName(projectName, milestoneName string) (*Milestone, error)

GetMilestoneByName retrieves a milestone by its project name and milestone name. Returns (nil, nil) if not found.

func (*Database) GetMilestonesByProject added in v0.3.0

func (d *Database) GetMilestonesByProject(projectName string) ([]*Milestone, error)

GetMilestonesByProject retrieves all milestones for the specified projectName, ordered by start_time DESC (newest first).

func (*Database) GetProjectsWithCompletedEntries

func (d *Database) GetProjectsWithCompletedEntries() ([]string, error)

GetProjectsWithCompletedEntries retrieves all distinct project names that have at least one completed time entry (end_time IS NOT NULL) from the time_entries table. The results are returned in ascending order by project_name. On success it returns a slice of project names (which will be empty if no completed entries exist) and a nil error. If the underlying database query or a row scan fails, it returns a non-nil error describing the failure.

func (*Database) GetRunningEntry

func (d *Database) GetRunningEntry() (*TimeEntry, error)

GetRunningEntry retrieves the most recently started time entry that is still running (i.e. has a NULL end_time) from the time_entries table. The query orders by start_time descending and returns at most one row.

If there is no running entry, GetRunningEntry returns (nil, nil). If the database query or scan fails, it returns a non-nil error describing the failure.

The function scans id, project_name, start_time, end_time, description, hourly_rate, and milestone_name into a TimeEntry. The EndTime field on the returned TimeEntry is set only if the scanned end_time is non-NULL. The HourlyRate field is set only if the scanned hourly_rate is non-NULL. The MilestoneName field is set only if the scanned milestone_name is non-NULL.

func (*Database) StopEntry

func (d *Database) StopEntry(id int64) error

StopEntry sets the end_time of the time entry identified by id to the current time. It updates the corresponding row in the time_entries table using time.Now(). If the update fails (for example if the row does not exist or the database returns an error), an error is returned wrapped with context. This method overwrites any existing end_time value and does not return the updated entry or perform additional validation on id.

func (*Database) UpdateTimeEntry

func (d *Database) UpdateTimeEntry(id int64, entry *TimeEntry) error

UpdateTimeEntry updates an existing time entry in the database with the values from the provided TimeEntry. It updates the project_name, start_time, end_time, description, hourly_rate, and milestone_name fields for the entry with the matching ID.

If the provided entry's EndTime is nil, the end_time column will be set to NULL. If the provided entry's HourlyRate is nil, the hourly_rate column will be set to NULL. If the provided entry's MilestoneName is nil, the milestone_name column will be set to NULL.

Returns an error if the update fails. Does not verify that a row with the given ID exists; if no rows are affected the function will still return nil (no error).

type Milestone added in v0.3.0

type Milestone struct {
	ID          int64
	ProjectName string
	Name        string
	StartTime   time.Time
	EndTime     *time.Time
}

Milestone represents a time-boxed period for grouping time entries. It includes a unique identifier, the project name it belongs to, the milestone name, the start time when it was created, and an optional end time (nil indicates the milestone is still active).

func (*Milestone) Duration added in v0.3.0

func (m *Milestone) Duration() time.Duration

Duration returns the elapsed time for the Milestone. If EndTime is non-nil, it returns the difference EndTime.Sub(StartTime). If EndTime is nil (the milestone is ongoing), it returns time.Since(StartTime).

func (*Milestone) IsActive added in v0.3.0

func (m *Milestone) IsActive() bool

IsActive reports whether the Milestone is currently active. It returns true when EndTime is nil, indicating no end timestamp has been set.

type TimeEntry

type TimeEntry struct {
	ID            int64
	ProjectName   string
	StartTime     time.Time
	EndTime       *time.Time
	Description   string
	HourlyRate    *float64
	MilestoneName *string
}

TimeEntry represents a recorded period of work on a project. It includes a unique identifier, the project name, the start time, an optional end time (nil indicates the entry is still in progress), a free-form description of the work performed, an optional hourly rate (nil indicates no rate was configured when the entry was created), and an optional milestone name (nil indicates the entry is not part of any milestone).

func (*TimeEntry) Duration

func (t *TimeEntry) Duration() time.Duration

Duration returns the elapsed time for the TimeEntry. If EndTime is non-nil, it returns the difference EndTime.Sub(StartTime). If EndTime is nil (the entry is ongoing), it returns time.Since(StartTime).

func (*TimeEntry) IsRunning

func (t *TimeEntry) IsRunning() bool

IsRunning reports whether the TimeEntry is currently running. It returns true when EndTime is nil, indicating no end timestamp has been set.

func (*TimeEntry) RoundedHours added in v0.1.3

func (t *TimeEntry) RoundedHours() float64

RoundedHours returns the duration in hours rounded to 2 decimal places. This rounding is used for earnings calculations to ensure transparency: the displayed hours value (e.g., "1.83 hours") matches exactly what is used in billing calculations.

Future enhancement: This could be made configurable via user settings to support different rounding increments (e.g., 0.1 hours for 6-minute billing, or 0.25 hours for 15-minute billing).

Jump to

Keyboard shortcuts

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