// ©AngelaMos | 2026 // cache.go package vuln import ( "database/sql" "encoding/json" "fmt" "os" "path/filepath" "time" _ "modernc.org/sqlite" "github.com/CarterPerez-dev/bomber/pkg/types" ) type Cache struct { db *sql.DB ttl time.Duration } func NewCache(dbPath string, ttl time.Duration) (*Cache, error) { dir := filepath.Dir(dbPath) if err := os.MkdirAll(dir, 0o750); err != nil { return nil, fmt.Errorf("create cache dir: %w", err) } db, err := sql.Open("sqlite", dbPath) if err != nil { return nil, fmt.Errorf("open cache db: %w", err) } createSQL := `CREATE TABLE IF NOT EXISTS vuln_cache ( purl TEXT NOT NULL, source TEXT NOT NULL, data BLOB NOT NULL, created_at INTEGER NOT NULL, PRIMARY KEY (purl, source) )` if _, err := db.Exec(createSQL); err != nil { _ = db.Close() return nil, fmt.Errorf("create cache table: %w", err) } return &Cache{db: db, ttl: ttl}, nil } func (c *Cache) Put(purl, source string, matches []types.VulnMatch) error { data, err := json.Marshal(matches) if err != nil { return fmt.Errorf("marshal cache data: %w", err) } upsertSQL := `INSERT OR REPLACE INTO vuln_cache (purl, source, data, created_at) VALUES (?, ?, ?, ?)` _, err = c.db.Exec(upsertSQL, purl, source, data, time.Now().UnixMilli()) if err != nil { return fmt.Errorf("insert cache: %w", err) } return nil } func (c *Cache) Get(purl, source string) ([]types.VulnMatch, bool, error) { querySQL := `SELECT data, created_at FROM vuln_cache WHERE purl = ? AND source = ?` row := c.db.QueryRow(querySQL, purl, source) var data []byte var createdAt int64 if err := row.Scan(&data, &createdAt); err != nil { if err == sql.ErrNoRows { return nil, false, nil } return nil, false, fmt.Errorf("query cache: %w", err) } created := time.UnixMilli(createdAt) if time.Since(created) > c.ttl { return nil, false, nil } var matches []types.VulnMatch if err := json.Unmarshal(data, &matches); err != nil { return nil, false, fmt.Errorf("unmarshal cache data: %w", err) } return matches, true, nil } func (c *Cache) Close() error { return c.db.Close() } func DefaultCachePath() string { home, err := os.UserHomeDir() if err != nil { return ".bomber/cache.db" } return filepath.Join(home, ".bomber", "cache.db") }