package kg import ( "context" "database/sql" "fmt" "time" "git.wntrmute.dev/kyle/exo/core" "git.wntrmute.dev/kyle/exo/db" ) // CellType distinguishes content types within a note. type CellType string const ( CellTypeMarkdown CellType = "markdown" CellTypeCode CellType = "code" CellTypePlain CellType = "plain" ) // Cell is a content unit within a note. A note is composed of // multiple cells of different types. type Cell struct { ID string NodeID string Type CellType Contents []byte Ordinal int Created time.Time Modified time.Time } // NewCell creates a Cell with a new UUID and current timestamps. func NewCell(nodeID string, cellType CellType, contents []byte) *Cell { now := time.Now().UTC() return &Cell{ ID: core.NewUUID(), NodeID: nodeID, Type: cellType, Contents: contents, Created: now, Modified: now, } } // Store persists a Cell to the database. func (c *Cell) Store(ctx context.Context, tx *sql.Tx) error { _, err := tx.ExecContext(ctx, `INSERT INTO cells (id, node_id, type, contents, ordinal, created, modified) VALUES (?, ?, ?, ?, ?, ?, ?)`, c.ID, c.NodeID, string(c.Type), c.Contents, c.Ordinal, db.ToDBTime(c.Created), db.ToDBTime(c.Modified)) if err != nil { return fmt.Errorf("kg: failed to store cell: %w", err) } return nil } // Get retrieves a Cell by its ID. func (c *Cell) Get(ctx context.Context, tx *sql.Tx) error { if c.ID == "" { return fmt.Errorf("kg: cell missing ID: %w", core.ErrNoID) } var cellType, created, modified string row := tx.QueryRowContext(ctx, `SELECT node_id, type, contents, ordinal, created, modified FROM cells WHERE id=?`, c.ID) if err := row.Scan(&c.NodeID, &cellType, &c.Contents, &c.Ordinal, &created, &modified); err != nil { return fmt.Errorf("kg: failed to retrieve cell: %w", err) } c.Type = CellType(cellType) var err error c.Created, err = db.FromDBTime(created, nil) if err != nil { return err } c.Modified, err = db.FromDBTime(modified, nil) return err } // GetCellsForNode retrieves all cells for a node, ordered by ordinal. func GetCellsForNode(ctx context.Context, tx *sql.Tx, nodeID string) ([]*Cell, error) { rows, err := tx.QueryContext(ctx, `SELECT id, type, contents, ordinal, created, modified FROM cells WHERE node_id=? ORDER BY ordinal`, nodeID) if err != nil { return nil, fmt.Errorf("kg: failed to retrieve cells for node: %w", err) } defer func() { _ = rows.Close() }() var cells []*Cell for rows.Next() { c := &Cell{NodeID: nodeID} var cellType, created, modified string if err := rows.Scan(&c.ID, &cellType, &c.Contents, &c.Ordinal, &created, &modified); err != nil { return nil, fmt.Errorf("kg: failed to scan cell: %w", err) } c.Type = CellType(cellType) c.Created, err = db.FromDBTime(created, nil) if err != nil { return nil, err } c.Modified, err = db.FromDBTime(modified, nil) if err != nil { return nil, err } cells = append(cells, c) } return cells, rows.Err() }