package drops import ( "fmt" "dk/internal/database" "dk/internal/helpers/scanner" "zombiezen.com/go/sqlite" ) // Drop represents a drop item in the database type Drop struct { ID int `db:"id" json:"id"` Name string `db:"name" json:"name"` Level int `db:"level" json:"level"` Type int `db:"type" json:"type"` Att string `db:"att" json:"att"` } // New creates a new Drop with sensible defaults func New() *Drop { return &Drop{ Name: "", Level: 1, // Default minimum level Type: TypeConsumable, // Default to consumable Att: "", } } var dropScanner = scanner.New[Drop]() // dropColumns returns the column list for drop queries func dropColumns() string { return dropScanner.Columns() } // scanDrop populates a Drop struct using the fast scanner func scanDrop(stmt *sqlite.Stmt) *Drop { drop := &Drop{} dropScanner.Scan(stmt, drop) return drop } // DropType constants for drop types const ( TypeConsumable = 1 ) // Find retrieves a drop by ID func Find(id int) (*Drop, error) { var drop *Drop query := `SELECT ` + dropColumns() + ` FROM drops WHERE id = ?` err := database.Query(query, func(stmt *sqlite.Stmt) error { drop = scanDrop(stmt) return nil }, id) if err != nil { return nil, fmt.Errorf("failed to find drop: %w", err) } if drop == nil { return nil, fmt.Errorf("drop with ID %d not found", id) } return drop, nil } // All retrieves all drops func All() ([]*Drop, error) { var drops []*Drop query := `SELECT ` + dropColumns() + ` FROM drops ORDER BY id` err := database.Query(query, func(stmt *sqlite.Stmt) error { drop := scanDrop(stmt) drops = append(drops, drop) return nil }) if err != nil { return nil, fmt.Errorf("failed to retrieve all drops: %w", err) } return drops, nil } // ByLevel retrieves drops by minimum level requirement func ByLevel(minLevel int) ([]*Drop, error) { var drops []*Drop query := `SELECT ` + dropColumns() + ` FROM drops WHERE level <= ? ORDER BY level, id` err := database.Query(query, func(stmt *sqlite.Stmt) error { drop := scanDrop(stmt) drops = append(drops, drop) return nil }, minLevel) if err != nil { return nil, fmt.Errorf("failed to retrieve drops by level: %w", err) } return drops, nil } // ByType retrieves drops by type func ByType(dropType int) ([]*Drop, error) { var drops []*Drop query := `SELECT ` + dropColumns() + ` FROM drops WHERE type = ? ORDER BY level, id` err := database.Query(query, func(stmt *sqlite.Stmt) error { drop := scanDrop(stmt) drops = append(drops, drop) return nil }, dropType) if err != nil { return nil, fmt.Errorf("failed to retrieve drops by type: %w", err) } return drops, nil } // Save updates an existing drop in the database func (d *Drop) Save() error { if d.ID == 0 { return fmt.Errorf("cannot save drop without ID") } query := `UPDATE drops SET name = ?, level = ?, type = ?, att = ? WHERE id = ?` return database.Exec(query, d.Name, d.Level, d.Type, d.Att, d.ID) } // Insert saves a new drop to the database and sets the ID func (d *Drop) Insert() error { if d.ID != 0 { return fmt.Errorf("drop already has ID %d, use Save() to update", d.ID) } // Use a transaction to ensure we can get the ID err := database.Transaction(func(tx *database.Tx) error { query := `INSERT INTO drops (name, level, type, att) VALUES (?, ?, ?, ?)` if err := tx.Exec(query, d.Name, d.Level, d.Type, d.Att); err != nil { return fmt.Errorf("failed to insert drop: %w", err) } // Get the last insert ID var id int err := tx.Query("SELECT last_insert_rowid()", func(stmt *sqlite.Stmt) error { id = stmt.ColumnInt(0) return nil }) if err != nil { return fmt.Errorf("failed to get insert ID: %w", err) } d.ID = id return nil }) return err } // Delete removes the drop from the database func (d *Drop) Delete() error { if d.ID == 0 { return fmt.Errorf("cannot delete drop without ID") } return database.Exec("DELETE FROM drops WHERE id = ?", d.ID) } // IsConsumable returns true if the drop is a consumable item func (d *Drop) IsConsumable() bool { return d.Type == TypeConsumable } // TypeName returns the string representation of the drop type func (d *Drop) TypeName() string { switch d.Type { case TypeConsumable: return "Consumable" default: return "Unknown" } } // ToMap converts the drop to a map for efficient template rendering func (d *Drop) ToMap() map[string]any { return map[string]any{ "ID": d.ID, "Name": d.Name, "Level": d.Level, "Type": d.Type, "Att": d.Att, // Computed values "IsConsumable": d.IsConsumable(), "TypeName": d.TypeName(), } }