diff --git a/internal/models/control/control.go b/internal/control/control.go similarity index 68% rename from internal/models/control/control.go rename to internal/control/control.go index 8e37c14..e259f65 100644 --- a/internal/models/control/control.go +++ b/internal/control/control.go @@ -1,16 +1,16 @@ package control import ( + "encoding/json" "fmt" + "os" "sync" - - nigiri "git.sharkk.net/Sharkk/Nigiri" ) var ( - store *nigiri.BaseStore[Control] - global *Control - mu sync.RWMutex + global *Control + mu sync.RWMutex + filename string ) // Control represents the game control settings @@ -24,39 +24,72 @@ type Control struct { Class3Name string `json:"class_3_name"` } -// Init sets up the Nigiri store for control settings -func Init(collection *nigiri.Collection) { - store = nigiri.NewBaseStore[Control]() +// Init loads control settings from the specified JSON file +func Init(jsonFile string) error { + mu.Lock() + defer mu.Unlock() - // Load or create the singleton control instance - all := store.GetAll() - if len(all) == 0 { - // Create default control settings - global = New() - global.ID = 1 - store.Add(1, global) - } else { - // Use the first (and only) control entry - for _, ctrl := range all { - global = ctrl - break + filename = jsonFile + + // Try to load from file + if data, err := os.ReadFile(filename); err == nil { + var ctrl Control + if err := json.Unmarshal(data, &ctrl); err != nil { + return fmt.Errorf("failed to parse JSON: %w", err) } + // Apply defaults for any missing fields defaults := New() - if global.WorldSize == 0 { - global.WorldSize = defaults.WorldSize + if ctrl.WorldSize == 0 { + ctrl.WorldSize = defaults.WorldSize } - if global.Class1Name == "" { - global.Class1Name = defaults.Class1Name + if ctrl.Class1Name == "" { + ctrl.Class1Name = defaults.Class1Name } - if global.Class2Name == "" { - global.Class2Name = defaults.Class2Name + if ctrl.Class2Name == "" { + ctrl.Class2Name = defaults.Class2Name } - if global.Class3Name == "" { - global.Class3Name = defaults.Class3Name + if ctrl.Class3Name == "" { + ctrl.Class3Name = defaults.Class3Name + } + + ctrl.ID = 1 // Ensure singleton ID + global = &ctrl + } else { + // Create default control settings if file doesn't exist + global = New() + if err := save(); err != nil { + return fmt.Errorf("failed to create default config file: %w", err) } - store.Update(global.ID, global) } + + return global.Validate() +} + +// save writes the current control settings to the JSON file (internal use) +func save() error { + if filename == "" { + return fmt.Errorf("no filename set") + } + + data, err := json.MarshalIndent(global, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal JSON: %w", err) + } + + return os.WriteFile(filename, data, 0644) +} + +// Save writes the current control settings to the JSON file (public) +func Save() error { + mu.RLock() + defer mu.RUnlock() + + if global == nil { + return fmt.Errorf("control not initialized") + } + + return save() } // New creates a new Control with sensible defaults @@ -77,12 +110,12 @@ func Get() *Control { mu.RLock() defer mu.RUnlock() if global == nil { - panic("control not initialized - call Initialize first") + panic("control not initialized - call Init first") } return global } -// Set updates the global control instance (thread-safe) +// Set updates the global control instance and saves to file (thread-safe) func Set(control *Control) error { mu.Lock() defer mu.Unlock() @@ -92,15 +125,11 @@ func Set(control *Control) error { return err } - if err := store.Update(1, control); err != nil { - return err - } - global = control - return nil + return save() } -// Update updates specific fields of the control settings +// Update updates specific fields of the control settings and saves to file func Update(updater func(*Control)) error { mu.Lock() defer mu.Unlock() @@ -113,7 +142,20 @@ func Update(updater func(*Control)) error { return err } - if err := store.Update(1, &updated); err != nil { + global = &updated + return save() +} + +// UpdateNoSave updates specific fields without saving (useful for batch updates) +func UpdateNoSave(updater func(*Control)) error { + mu.Lock() + defer mu.Unlock() + + // Create a copy to work with + updated := *global + updater(&updated) + + if err := updated.Validate(); err != nil { return err } @@ -234,14 +276,3 @@ func (c *Control) GetWorldBounds() (minX, minY, maxX, maxY int) { radius := c.GetWorldRadius() return -radius, -radius, radius, radius } - -// Legacy compatibility functions (will be removed later) -func Load(filename string) error { - // No longer needed - Nigiri handles this - return nil -} - -func Save() error { - // No longer needed - Nigiri handles this - return nil -} diff --git a/internal/models/babble/babble.go b/internal/models/babble/babble.go index 07514a3..72a7ed3 100644 --- a/internal/models/babble/babble.go +++ b/internal/models/babble/babble.go @@ -2,19 +2,27 @@ package babble import ( "fmt" - "sort" "strings" "time" - nigiri "git.sharkk.net/Sharkk/Nigiri" + "dk/internal/database" ) // Babble represents a global chat message in the game type Babble struct { - ID int `json:"id"` - Posted int64 `json:"posted"` - Author string `json:"author" db:"index"` - Babble string `json:"babble"` + ID int + Posted int64 + Author string + Babble string +} + +// New creates a new Babble with sensible defaults +func New() *Babble { + return &Babble{ + Posted: time.Now().Unix(), + Author: "", + Babble: "", + } } // Validate checks if babble has valid values @@ -31,136 +39,71 @@ func (b *Babble) Validate() error { return nil } -// Global store with singleton pattern -var store *nigiri.BaseStore[Babble] - -// Init sets up the Nigiri store and indices -func Init(collection *nigiri.Collection) { - store = nigiri.NewBaseStore[Babble]() - - // Register custom indices - store.RegisterIndex("byAuthor", nigiri.BuildStringGroupIndex(func(b *Babble) string { - return strings.ToLower(b.Author) - })) - - store.RegisterIndex("allByPosted", nigiri.BuildSortedListIndex(func(a, b *Babble) bool { - if a.Posted != b.Posted { - return a.Posted > b.Posted // DESC - } - return a.ID > b.ID // DESC - })) - - store.RebuildIndices() -} - -// GetStore returns the babble store -func GetStore() *nigiri.BaseStore[Babble] { - if store == nil { - panic("babble store not initialized - call Initialize first") - } - return store -} - -// Creates a new Babble with sensible defaults -func New() *Babble { - return &Babble{ - Posted: time.Now().Unix(), - Author: "", - Babble: "", - } -} - // CRUD operations -func (b *Babble) Save() error { - if b.ID == 0 { - id, err := store.Create(b) - if err != nil { - return err - } - b.ID = id - return nil - } - return store.Update(b.ID, b) -} - func (b *Babble) Delete() error { - store.Remove(b.ID) - return nil + return database.Exec("DELETE FROM babble WHERE id = %d", b.ID) } -// Insert with ID assignment func (b *Babble) Insert() error { - id, err := store.Create(b) + id, err := database.Insert("babble", b, "ID") if err != nil { return err } - b.ID = id + b.ID = int(id) return nil } // Query functions func Find(id int) (*Babble, error) { - babble, exists := store.Find(id) - if !exists { + var babble Babble + err := database.Get(&babble, "SELECT * FROM babble WHERE id = %d", id) + if err != nil { return nil, fmt.Errorf("babble with ID %d not found", id) } - return babble, nil + return &babble, nil } func All() ([]*Babble, error) { - return store.AllSorted("allByPosted"), nil + var babbles []*Babble + err := database.Select(&babbles, "SELECT * FROM babble ORDER BY posted DESC, id DESC") + return babbles, err } func ByAuthor(author string) ([]*Babble, error) { - messages := store.GroupByIndex("byAuthor", strings.ToLower(author)) - - // Sort by posted DESC, then ID DESC - sort.Slice(messages, func(i, j int) bool { - if messages[i].Posted != messages[j].Posted { - return messages[i].Posted > messages[j].Posted // DESC - } - return messages[i].ID > messages[j].ID // DESC - }) - - return messages, nil + var babbles []*Babble + err := database.Select(&babbles, "SELECT * FROM babble WHERE author = %s COLLATE NOCASE ORDER BY posted DESC, id DESC", author) + return babbles, err } func Recent(limit int) ([]*Babble, error) { - all := store.AllSorted("allByPosted") - if limit > len(all) { - limit = len(all) - } - return all[:limit], nil + var babbles []*Babble + err := database.Select(&babbles, "SELECT * FROM babble ORDER BY posted DESC, id DESC LIMIT %d", limit) + return babbles, err } func Since(since int64) ([]*Babble, error) { - return store.FilterByIndex("allByPosted", func(b *Babble) bool { - return b.Posted >= since - }), nil + var babbles []*Babble + err := database.Select(&babbles, "SELECT * FROM babble WHERE posted >= %d ORDER BY posted DESC, id DESC", since) + return babbles, err } func Between(start, end int64) ([]*Babble, error) { - return store.FilterByIndex("allByPosted", func(b *Babble) bool { - return b.Posted >= start && b.Posted <= end - }), nil + var babbles []*Babble + err := database.Select(&babbles, "SELECT * FROM babble WHERE posted >= %d AND posted <= %d ORDER BY posted DESC, id DESC", start, end) + return babbles, err } func Search(term string) ([]*Babble, error) { - lowerTerm := strings.ToLower(term) - return store.FilterByIndex("allByPosted", func(b *Babble) bool { - return strings.Contains(strings.ToLower(b.Babble), lowerTerm) - }), nil + var babbles []*Babble + searchTerm := "%" + term + "%" + err := database.Select(&babbles, "SELECT * FROM babble WHERE babble LIKE %s ORDER BY posted DESC, id DESC", searchTerm) + return babbles, err } func RecentByAuthor(author string, limit int) ([]*Babble, error) { - messages, err := ByAuthor(author) - if err != nil { - return nil, err - } - if limit > len(messages) { - limit = len(messages) - } - return messages[:limit], nil + var babbles []*Babble + err := database.Select(&babbles, "SELECT * FROM babble WHERE author = %s COLLATE NOCASE ORDER BY posted DESC, id DESC LIMIT %d", author, limit) + return babbles, err } // Helper methods @@ -265,14 +208,3 @@ func (b *Babble) HasMention(username string) bool { } return false } - -// Legacy compatibility functions (will be removed later) -func LoadData(dataPath string) error { - // No longer needed - Nigiri handles this - return nil -} - -func SaveData(dataPath string) error { - // No longer needed - Nigiri handles this - return nil -} diff --git a/internal/models/drops/drops.go b/internal/models/drops/drops.go index df910db..59cd89e 100644 --- a/internal/models/drops/drops.go +++ b/internal/models/drops/drops.go @@ -3,16 +3,16 @@ package drops import ( "fmt" - nigiri "git.sharkk.net/Sharkk/Nigiri" + "dk/internal/database" ) // Drop represents a drop item in the game type Drop struct { - ID int `json:"id"` - Name string `json:"name" db:"required"` - Level int `json:"level" db:"index"` - Type int `json:"type" db:"index"` - Att string `json:"att"` + ID int + Name string + Level int + Type int + Att string } // DropType constants for drop types @@ -20,43 +20,12 @@ const ( TypeConsumable = 1 ) -// Global store -var store *nigiri.BaseStore[Drop] - -// Init sets up the Nigiri store and indices -func Init(collection *nigiri.Collection) { - store = nigiri.NewBaseStore[Drop]() - - // Register custom indices - store.RegisterIndex("byLevel", nigiri.BuildIntGroupIndex(func(d *Drop) int { - return d.Level - })) - - store.RegisterIndex("byType", nigiri.BuildIntGroupIndex(func(d *Drop) int { - return d.Type - })) - - store.RegisterIndex("allByID", nigiri.BuildSortedListIndex(func(a, b *Drop) bool { - return a.ID < b.ID - })) - - store.RebuildIndices() -} - -// GetStore returns the drops store -func GetStore() *nigiri.BaseStore[Drop] { - if store == nil { - panic("drops store not initialized - call Initialize first") - } - return store -} - -// Creates a new Drop with sensible defaults +// New creates a new Drop with sensible defaults func New() *Drop { return &Drop{ Name: "", - Level: 1, // Default minimum level - Type: TypeConsumable, // Default to consumable + Level: 1, + Type: TypeConsumable, Att: "", } } @@ -76,54 +45,45 @@ func (d *Drop) Validate() error { } // CRUD operations -func (d *Drop) Save() error { - if d.ID == 0 { - id, err := store.Create(d) - if err != nil { - return err - } - d.ID = id - return nil - } - return store.Update(d.ID, d) -} - func (d *Drop) Delete() error { - store.Remove(d.ID) - return nil + return database.Exec("DELETE FROM drops WHERE id = %d", d.ID) } -// Insert with ID assignment func (d *Drop) Insert() error { - id, err := store.Create(d) + id, err := database.Insert("drops", d, "ID") if err != nil { return err } - d.ID = id + d.ID = int(id) return nil } // Query functions func Find(id int) (*Drop, error) { - drop, exists := store.Find(id) - if !exists { + var drop Drop + err := database.Get(&drop, "SELECT * FROM drops WHERE id = %d", id) + if err != nil { return nil, fmt.Errorf("drop with ID %d not found", id) } - return drop, nil + return &drop, nil } func All() ([]*Drop, error) { - return store.AllSorted("allByID"), nil + var drops []*Drop + err := database.Select(&drops, "SELECT * FROM drops ORDER BY id ASC") + return drops, err } func ByLevel(minLevel int) ([]*Drop, error) { - return store.FilterByIndex("allByID", func(d *Drop) bool { - return d.Level <= minLevel - }), nil + var drops []*Drop + err := database.Select(&drops, "SELECT * FROM drops WHERE level <= %d ORDER BY id ASC", minLevel) + return drops, err } func ByType(dropType int) ([]*Drop, error) { - return store.GroupByIndex("byType", dropType), nil + var drops []*Drop + err := database.Select(&drops, "SELECT * FROM drops WHERE type = %d ORDER BY id ASC", dropType) + return drops, err } // Helper methods @@ -139,14 +99,3 @@ func (d *Drop) TypeName() string { return "Unknown" } } - -// Legacy compatibility functions (will be removed later) -func LoadData(dataPath string) error { - // No longer needed - Nigiri handles this - return nil -} - -func SaveData(dataPath string) error { - // No longer needed - Nigiri handles this - return nil -} diff --git a/internal/models/fightlogs/fightlogs.go b/internal/models/fightlogs/fightlogs.go new file mode 100644 index 0000000..a221655 --- /dev/null +++ b/internal/models/fightlogs/fightlogs.go @@ -0,0 +1,263 @@ +package fightlogs + +import ( + "fmt" + "strings" + "time" + + "dk/internal/database" +) + +// FightLog represents a single action in a fight +type FightLog struct { + ID int + FightID int + Type int + Data int + Name string + Created int64 +} + +// Action type constants +const ( + ActionAttackHit = 1 + ActionAttackMiss = 2 + ActionSpellHeal = 3 + ActionSpellHurt = 4 + ActionRunSuccess = 5 + ActionRunFail = 6 + ActionGeneric = 7 + ActionMonsterAttack = 8 + ActionMonsterMiss = 9 + ActionMonsterSpell = 10 + ActionMonsterDeath = 11 +) + +// New creates a new FightLog with sensible defaults +func New(fightID int) *FightLog { + return &FightLog{ + FightID: fightID, + Type: ActionGeneric, + Data: 0, + Name: "", + Created: time.Now().Unix(), + } +} + +// Validate checks if fight log has valid values +func (fl *FightLog) Validate() error { + if fl.FightID <= 0 { + return fmt.Errorf("fight log FightID must be positive") + } + if fl.Created <= 0 { + return fmt.Errorf("fight log Created timestamp must be positive") + } + return nil +} + +// CRUD operations +func (fl *FightLog) Delete() error { + return database.Exec("DELETE FROM fight_logs WHERE id = %d", fl.ID) +} + +func (fl *FightLog) Insert() error { + id, err := database.Insert("fight_logs", fl, "ID") + if err != nil { + return err + } + fl.ID = int(id) + return nil +} + +// Query functions +func Find(id int) (*FightLog, error) { + var log FightLog + err := database.Get(&log, "SELECT * FROM fight_logs WHERE id = %d", id) + if err != nil { + return nil, fmt.Errorf("fight log with ID %d not found", id) + } + return &log, nil +} + +func ByFightID(fightID int) ([]*FightLog, error) { + var logs []*FightLog + err := database.Select(&logs, "SELECT * FROM fight_logs WHERE fight_id = %d ORDER BY created ASC, id ASC", fightID) + return logs, err +} + +func DeleteByFightID(fightID int) error { + return database.Exec("DELETE FROM fight_logs WHERE fight_id = %d", fightID) +} + +// Helper functions for adding different types of actions +func AddAction(fightID int, action string) error { + log := &FightLog{ + FightID: fightID, + Type: ActionGeneric, + Name: action, + Created: time.Now().Unix(), + } + return log.Insert() +} + +func AddAttackHit(fightID, damage int) error { + log := &FightLog{ + FightID: fightID, + Type: ActionAttackHit, + Data: damage, + Created: time.Now().Unix(), + } + return log.Insert() +} + +func AddAttackMiss(fightID int) error { + log := &FightLog{ + FightID: fightID, + Type: ActionAttackMiss, + Created: time.Now().Unix(), + } + return log.Insert() +} + +func AddSpellHeal(fightID int, spellName string, healAmount int) error { + log := &FightLog{ + FightID: fightID, + Type: ActionSpellHeal, + Data: healAmount, + Name: spellName, + Created: time.Now().Unix(), + } + return log.Insert() +} + +func AddSpellHurt(fightID int, spellName string, damage int) error { + log := &FightLog{ + FightID: fightID, + Type: ActionSpellHurt, + Data: damage, + Name: spellName, + Created: time.Now().Unix(), + } + return log.Insert() +} + +func AddRunSuccess(fightID int) error { + log := &FightLog{ + FightID: fightID, + Type: ActionRunSuccess, + Created: time.Now().Unix(), + } + return log.Insert() +} + +func AddRunFail(fightID int) error { + log := &FightLog{ + FightID: fightID, + Type: ActionRunFail, + Created: time.Now().Unix(), + } + return log.Insert() +} + +func AddMonsterAttack(fightID int, monsterName string, damage int) error { + log := &FightLog{ + FightID: fightID, + Type: ActionMonsterAttack, + Data: damage, + Name: monsterName, + Created: time.Now().Unix(), + } + return log.Insert() +} + +func AddMonsterMiss(fightID int, monsterName string) error { + log := &FightLog{ + FightID: fightID, + Type: ActionMonsterMiss, + Name: monsterName, + Created: time.Now().Unix(), + } + return log.Insert() +} + +func AddMonsterSpell(fightID int, monsterName, spellName string, damage int) error { + log := &FightLog{ + FightID: fightID, + Type: ActionMonsterSpell, + Data: damage, + Name: monsterName + "|" + spellName, + Created: time.Now().Unix(), + } + return log.Insert() +} + +func AddMonsterDeath(fightID int, monsterName string) error { + log := &FightLog{ + FightID: fightID, + Type: ActionMonsterDeath, + Name: monsterName, + Created: time.Now().Unix(), + } + return log.Insert() +} + +// Convert logs to human-readable strings +func GetActions(fightID int) ([]string, error) { + logs, err := ByFightID(fightID) + if err != nil { + return nil, err + } + + result := make([]string, len(logs)) + for i, log := range logs { + result[i] = log.ToString() + } + return result, nil +} + +func GetLastAction(fightID int) (string, error) { + var log FightLog + err := database.Get(&log, "SELECT * FROM fight_logs WHERE fight_id = %d ORDER BY created DESC, id DESC LIMIT 1", fightID) + if err != nil { + return "", nil // No logs found + } + return log.ToString(), nil +} + +// Helper methods +func (fl *FightLog) CreatedTime() time.Time { + return time.Unix(fl.Created, 0) +} + +func (fl *FightLog) ToString() string { + switch fl.Type { + case ActionAttackHit: + return fmt.Sprintf("You attacked for %d damage!", fl.Data) + case ActionAttackMiss: + return "You missed your attack!" + case ActionSpellHeal: + return fmt.Sprintf("You cast %s and healed %d HP!", fl.Name, fl.Data) + case ActionSpellHurt: + return fmt.Sprintf("You cast %s and dealt %d damage!", fl.Name, fl.Data) + case ActionRunSuccess: + return "You successfully ran away!" + case ActionRunFail: + return "You failed to run away!" + case ActionGeneric: + return fl.Name + case ActionMonsterAttack: + return fmt.Sprintf("%s attacks for %d damage!", fl.Name, fl.Data) + case ActionMonsterMiss: + return fmt.Sprintf("%s missed its attack!", fl.Name) + case ActionMonsterSpell: + parts := strings.Split(fl.Name, "|") + if len(parts) == 2 { + return fmt.Sprintf("%s casts %s for %d damage!", parts[0], parts[1], fl.Data) + } + return fmt.Sprintf("%s casts a spell for %d damage!", fl.Name, fl.Data) + case ActionMonsterDeath: + return fmt.Sprintf("%s has been defeated!", fl.Name) + default: + return "Unknown action" + } +} diff --git a/internal/models/fights/action_codes.go b/internal/models/fights/action_codes.go deleted file mode 100644 index 43ae4df..0000000 --- a/internal/models/fights/action_codes.go +++ /dev/null @@ -1,140 +0,0 @@ -package fights - -import ( - "fmt" - "strings" - "time" -) - -// ActionEntry represents a compacted fight action log. This allows us to store more logs -// in the same space as a single string. -type ActionEntry struct { - Type int `json:"t"` - Data int `json:"d,omitempty"` - Name string `json:"n,omitempty"` // For spell names -} - -// Action type constants -const ( - ActionAttackHit = 1 - ActionAttackMiss = 2 - ActionSpellHeal = 3 - ActionSpellHurt = 4 - ActionRunSuccess = 5 - ActionRunFail = 6 - ActionGeneric = 7 - ActionMonsterAttack = 8 - ActionMonsterMiss = 9 - ActionMonsterSpell = 10 - ActionMonsterDeath = 11 -) - -func (f *Fight) AddAction(action string) { - f.Actions = append(f.Actions, ActionEntry{Type: ActionGeneric, Name: action}) - f.Updated = time.Now().Unix() -} - -func (f *Fight) AddActionAttackHit(damage int) { - f.Actions = append(f.Actions, ActionEntry{Type: ActionAttackHit, Data: damage}) - f.Updated = time.Now().Unix() -} - -func (f *Fight) AddActionAttackMiss() { - f.Actions = append(f.Actions, ActionEntry{Type: ActionAttackMiss}) - f.Updated = time.Now().Unix() -} - -func (f *Fight) AddActionSpellHeal(spellName string, healAmount int) { - f.Actions = append(f.Actions, ActionEntry{Type: ActionSpellHeal, Data: healAmount, Name: spellName}) - f.Updated = time.Now().Unix() -} - -func (f *Fight) AddActionSpellHurt(spellName string, damage int) { - f.Actions = append(f.Actions, ActionEntry{Type: ActionSpellHurt, Data: damage, Name: spellName}) - f.Updated = time.Now().Unix() -} - -func (f *Fight) AddActionRunSuccess() { - f.Actions = append(f.Actions, ActionEntry{Type: ActionRunSuccess}) - f.Updated = time.Now().Unix() -} - -func (f *Fight) AddActionRunFail() { - f.Actions = append(f.Actions, ActionEntry{Type: ActionRunFail}) - f.Updated = time.Now().Unix() -} - -// Convert actions to human-readable strings -func (f *Fight) GetActions() []string { - result := make([]string, len(f.Actions)) - for i, action := range f.Actions { - result[i] = f.actionToString(action) - } - return result -} - -func (f *Fight) GetLastAction() string { - if len(f.Actions) == 0 { - return "" - } - return f.actionToString(f.Actions[len(f.Actions)-1]) -} - -func (f *Fight) ClearActions() { - f.Actions = make([]ActionEntry, 0) - f.Updated = time.Now().Unix() -} - -func (f *Fight) AddActionMonsterAttack(monsterName string, damage int) { - f.Actions = append(f.Actions, ActionEntry{Type: ActionMonsterAttack, Data: damage, Name: monsterName}) - f.Updated = time.Now().Unix() -} - -func (f *Fight) AddActionMonsterMiss(monsterName string) { - f.Actions = append(f.Actions, ActionEntry{Type: ActionMonsterMiss, Name: monsterName}) - f.Updated = time.Now().Unix() -} - -func (f *Fight) AddActionMonsterSpell(monsterName, spellName string, damage int) { - f.Actions = append(f.Actions, ActionEntry{Type: ActionMonsterSpell, Data: damage, Name: monsterName + "|" + spellName}) - f.Updated = time.Now().Unix() -} - -func (f *Fight) AddActionMonsterDeath(monsterName string) { - f.Actions = append(f.Actions, ActionEntry{Type: ActionMonsterDeath, Name: monsterName}) - f.Updated = time.Now().Unix() -} - -// Update actionToString method - add these cases -func (f *Fight) actionToString(action ActionEntry) string { - switch action.Type { - case ActionAttackHit: - return fmt.Sprintf("You attacked for %d damage!", action.Data) - case ActionAttackMiss: - return "You missed your attack!" - case ActionSpellHeal: - return fmt.Sprintf("You cast %s and healed %d HP!", action.Name, action.Data) - case ActionSpellHurt: - return fmt.Sprintf("You cast %s and dealt %d damage!", action.Name, action.Data) - case ActionRunSuccess: - return "You successfully ran away!" - case ActionRunFail: - return "You failed to run away!" - case ActionGeneric: - return action.Name - case ActionMonsterAttack: - return fmt.Sprintf("%s attacks for %d damage!", action.Name, action.Data) - case ActionMonsterMiss: - return fmt.Sprintf("%s missed its attack!", action.Name) - case ActionMonsterSpell: - parts := strings.Split(action.Name, "|") - if len(parts) == 2 { - return fmt.Sprintf("%s casts %s for %d damage!", parts[0], parts[1], action.Data) - } - return fmt.Sprintf("%s casts a spell for %d damage!", action.Name, action.Data) - case ActionMonsterDeath: - return fmt.Sprintf("%s has been defeated!", action.Name) - default: - return "Unknown action" - } -} diff --git a/internal/models/fights/fights.go b/internal/models/fights/fights.go index f0b34bf..07f6f2f 100644 --- a/internal/models/fights/fights.go +++ b/internal/models/fights/fights.go @@ -4,80 +4,29 @@ import ( "fmt" "time" - nigiri "git.sharkk.net/Sharkk/Nigiri" + "dk/internal/database" ) // Fight represents a fight, past or present type Fight struct { - ID int `json:"id"` - UserID int `json:"user_id" db:"index"` - MonsterID int `json:"monster_id" db:"index"` - MonsterHP int `json:"monster_hp"` - MonsterMaxHP int `json:"monster_max_hp"` - MonsterSleep int `json:"monster_sleep"` - MonsterImmune int `json:"monster_immune"` - UberDamage int `json:"uber_damage"` - UberDefense int `json:"uber_defense"` - FirstStrike bool `json:"first_strike"` - Turn int `json:"turn"` - RanAway bool `json:"ran_away"` - Victory bool `json:"victory"` - Won bool `json:"won"` - RewardGold int `json:"reward_gold"` - RewardExp int `json:"reward_exp"` - Actions []ActionEntry `json:"actions"` - Created int64 `json:"created"` - Updated int64 `json:"updated"` -} - -// Global store -var store *nigiri.BaseStore[Fight] - -// Init sets up the Nigiri store and indices -func Init(collection *nigiri.Collection) { - store = nigiri.NewBaseStore[Fight]() - - // Register custom indices - store.RegisterIndex("byUserID", nigiri.BuildIntGroupIndex(func(f *Fight) int { - return f.UserID - })) - - store.RegisterIndex("byMonsterID", nigiri.BuildIntGroupIndex(func(f *Fight) int { - return f.MonsterID - })) - - store.RegisterIndex("activeFights", nigiri.BuildFilteredIntGroupIndex( - func(f *Fight) bool { - return !f.RanAway && !f.Victory - }, - func(f *Fight) int { - return f.UserID - }, - )) - - store.RegisterIndex("allByCreated", nigiri.BuildSortedListIndex(func(a, b *Fight) bool { - if a.Created != b.Created { - return a.Created > b.Created // DESC - } - return a.ID > b.ID // DESC - })) - - store.RegisterIndex("allByUpdated", nigiri.BuildSortedListIndex(func(a, b *Fight) bool { - if a.Updated != b.Updated { - return a.Updated > b.Updated // DESC - } - return a.ID > b.ID // DESC - })) - - store.RebuildIndices() -} - -// GetStore returns the fights store -func GetStore() *nigiri.BaseStore[Fight] { - if store == nil { - panic("fights store not initialized - call Initialize first") - } - return store + ID int + UserID int + MonsterID int + MonsterHP int + MonsterMaxHP int + MonsterSleep int + MonsterImmune int + UberDamage int + UberDefense int + FirstStrike bool + Turn int + RanAway bool + Victory bool + Won bool + RewardGold int + RewardExp int + Created int64 + Updated int64 } // New creates a new Fight with sensible defaults @@ -99,7 +48,6 @@ func New(userID, monsterID int) *Fight { Won: false, RewardGold: 0, RewardExp: 0, - Actions: make([]ActionEntry, 0), Created: now, Updated: now, } @@ -129,75 +77,88 @@ func (f *Fight) Validate() error { } // CRUD operations -func (f *Fight) Save() error { - f.Updated = time.Now().Unix() - if f.ID == 0 { - id, err := store.Create(f) - if err != nil { - return err - } - f.ID = id - return nil - } - return store.Update(f.ID, f) -} - func (f *Fight) Delete() error { - store.Remove(f.ID) - return nil + return database.Exec("DELETE FROM fights WHERE id = %d", f.ID) } -// Insert with ID assignment func (f *Fight) Insert() error { f.Updated = time.Now().Unix() - id, err := store.Create(f) + id, err := database.Insert("fights", f, "ID") if err != nil { return err } - f.ID = id + f.ID = int(id) return nil } +func (f *Fight) Update() error { + f.Updated = time.Now().Unix() + return database.Update("fights", map[string]any{ + "user_id": f.UserID, + "monster_id": f.MonsterID, + "monster_hp": f.MonsterHP, + "monster_max_hp": f.MonsterMaxHP, + "monster_sleep": f.MonsterSleep, + "monster_immune": f.MonsterImmune, + "uber_damage": f.UberDamage, + "uber_defense": f.UberDefense, + "first_strike": f.FirstStrike, + "turn": f.Turn, + "ran_away": f.RanAway, + "victory": f.Victory, + "won": f.Won, + "reward_gold": f.RewardGold, + "reward_exp": f.RewardExp, + "created": f.Created, + "updated": f.Updated, + }, "id", f.ID) +} + // Query functions func Find(id int) (*Fight, error) { - fight, exists := store.Find(id) - if !exists { + var fight Fight + err := database.Get(&fight, "SELECT * FROM fights WHERE id = %d", id) + if err != nil { return nil, fmt.Errorf("fight with ID %d not found", id) } - return fight, nil + return &fight, nil } func All() ([]*Fight, error) { - return store.AllSorted("allByCreated"), nil + var fights []*Fight + err := database.Select(&fights, "SELECT * FROM fights ORDER BY created DESC, id DESC") + return fights, err } func ByUserID(userID int) ([]*Fight, error) { - return store.GroupByIndex("byUserID", userID), nil + var fights []*Fight + err := database.Select(&fights, "SELECT * FROM fights WHERE user_id = %d ORDER BY created DESC, id DESC", userID) + return fights, err } func ByMonsterID(monsterID int) ([]*Fight, error) { - return store.GroupByIndex("byMonsterID", monsterID), nil + var fights []*Fight + err := database.Select(&fights, "SELECT * FROM fights WHERE monster_id = %d ORDER BY created DESC, id DESC", monsterID) + return fights, err } func ActiveByUserID(userID int) ([]*Fight, error) { - return store.GroupByIndex("activeFights", userID), nil + var fights []*Fight + err := database.Select(&fights, "SELECT * FROM fights WHERE user_id = %d AND ran_away = 0 AND victory = 0 ORDER BY created DESC, id DESC", userID) + return fights, err } func Active() ([]*Fight, error) { - result := store.FilterByIndex("allByCreated", func(f *Fight) bool { - return !f.RanAway && !f.Victory - }) - return result, nil + var fights []*Fight + err := database.Select(&fights, "SELECT * FROM fights WHERE ran_away = 0 AND victory = 0 ORDER BY created DESC, id DESC") + return fights, err } func Recent(within time.Duration) ([]*Fight, error) { cutoff := time.Now().Add(-within).Unix() - - result := store.FilterByIndex("allByCreated", func(f *Fight) bool { - return f.Created >= cutoff - }) - - return result, nil + var fights []*Fight + err := database.Select(&fights, "SELECT * FROM fights WHERE created >= %d ORDER BY created DESC, id DESC", cutoff) + return fights, err } // Helper methods diff --git a/internal/models/forum/forum.go b/internal/models/forum/forum.go index c3f12b9..81fb54a 100644 --- a/internal/models/forum/forum.go +++ b/internal/models/forum/forum.go @@ -2,67 +2,32 @@ package forum import ( "fmt" - "sort" "strings" "time" - nigiri "git.sharkk.net/Sharkk/Nigiri" + "dk/internal/database" ) // Forum represents a forum post or thread in the game type Forum struct { - ID int `json:"id"` - Posted int64 `json:"posted"` - LastPost int64 `json:"last_post"` - Author int `json:"author" db:"index"` - Parent int `json:"parent" db:"index"` - Replies int `json:"replies"` - Title string `json:"title" db:"required"` - Content string `json:"content" db:"required"` + ID int + Posted int64 + LastPost int64 + Author int + Parent int + Replies int + Title string + Content string } -// Global store -var store *nigiri.BaseStore[Forum] - -// Init sets up the Nigiri store and indices -func Init(collection *nigiri.Collection) { - store = nigiri.NewBaseStore[Forum]() - - // Register custom indices - store.RegisterIndex("byParent", nigiri.BuildIntGroupIndex(func(f *Forum) int { - return f.Parent - })) - - store.RegisterIndex("byAuthor", nigiri.BuildIntGroupIndex(func(f *Forum) int { - return f.Author - })) - - store.RegisterIndex("allByLastPost", nigiri.BuildSortedListIndex(func(a, b *Forum) bool { - if a.LastPost != b.LastPost { - return a.LastPost > b.LastPost // DESC - } - return a.ID > b.ID // DESC - })) - - store.RebuildIndices() -} - -// GetStore returns the forum store -func GetStore() *nigiri.BaseStore[Forum] { - if store == nil { - panic("forum store not initialized - call Initialize first") - } - return store -} - -// Creates a new Forum with sensible defaults +// New creates a new Forum with sensible defaults func New() *Forum { now := time.Now().Unix() return &Forum{ Posted: now, LastPost: now, Author: 0, - Parent: 0, // Default to thread (not reply) + Parent: 0, Replies: 0, Title: "", Content: "", @@ -93,102 +58,77 @@ func (f *Forum) Validate() error { } // CRUD operations -func (f *Forum) Save() error { - if f.ID == 0 { - id, err := store.Create(f) - if err != nil { - return err - } - f.ID = id - return nil - } - return store.Update(f.ID, f) -} - func (f *Forum) Delete() error { - store.Remove(f.ID) - return nil + return database.Exec("DELETE FROM forum WHERE id = %d", f.ID) } -// Insert with ID assignment func (f *Forum) Insert() error { - id, err := store.Create(f) + id, err := database.Insert("forum", f, "ID") if err != nil { return err } - f.ID = id + f.ID = int(id) return nil } // Query functions func Find(id int) (*Forum, error) { - forum, exists := store.Find(id) - if !exists { + var forum Forum + err := database.Get(&forum, "SELECT * FROM forum WHERE id = %d", id) + if err != nil { return nil, fmt.Errorf("forum post with ID %d not found", id) } - return forum, nil + return &forum, nil } func All() ([]*Forum, error) { - return store.AllSorted("allByLastPost"), nil + var forums []*Forum + err := database.Select(&forums, "SELECT * FROM forum ORDER BY last_post DESC, id DESC") + return forums, err } func Threads() ([]*Forum, error) { - return store.FilterByIndex("allByLastPost", func(f *Forum) bool { - return f.Parent == 0 - }), nil + var forums []*Forum + err := database.Select(&forums, "SELECT * FROM forum WHERE parent = 0 ORDER BY last_post DESC, id DESC") + return forums, err } func ByParent(parentID int) ([]*Forum, error) { - replies := store.GroupByIndex("byParent", parentID) - - // Sort replies chronologically (posted ASC, then ID ASC) - if parentID > 0 && len(replies) > 1 { - sort.Slice(replies, func(i, j int) bool { - if replies[i].Posted != replies[j].Posted { - return replies[i].Posted < replies[j].Posted // ASC - } - return replies[i].ID < replies[j].ID // ASC - }) + var forums []*Forum + if parentID > 0 { + // Replies sorted chronologically + err := database.Select(&forums, "SELECT * FROM forum WHERE parent = %d ORDER BY posted ASC, id ASC", parentID) + return forums, err + } else { + // Threads sorted by last activity + err := database.Select(&forums, "SELECT * FROM forum WHERE parent = %d ORDER BY last_post DESC, id DESC", parentID) + return forums, err } - - return replies, nil } func ByAuthor(authorID int) ([]*Forum, error) { - posts := store.GroupByIndex("byAuthor", authorID) - - // Sort by posted DESC, then ID DESC - sort.Slice(posts, func(i, j int) bool { - if posts[i].Posted != posts[j].Posted { - return posts[i].Posted > posts[j].Posted // DESC - } - return posts[i].ID > posts[j].ID // DESC - }) - - return posts, nil + var forums []*Forum + err := database.Select(&forums, "SELECT * FROM forum WHERE author = %d ORDER BY posted DESC, id DESC", authorID) + return forums, err } func Recent(limit int) ([]*Forum, error) { - all := store.AllSorted("allByLastPost") - if limit > len(all) { - limit = len(all) - } - return all[:limit], nil + var forums []*Forum + err := database.Select(&forums, "SELECT * FROM forum ORDER BY last_post DESC, id DESC LIMIT %d", limit) + return forums, err } func Search(term string) ([]*Forum, error) { - lowerTerm := strings.ToLower(term) - return store.FilterByIndex("allByLastPost", func(f *Forum) bool { - return strings.Contains(strings.ToLower(f.Title), lowerTerm) || - strings.Contains(strings.ToLower(f.Content), lowerTerm) - }), nil + var forums []*Forum + searchTerm := "%" + term + "%" + err := database.Select(&forums, "SELECT * FROM forum WHERE title LIKE %s OR content LIKE %s ORDER BY last_post DESC, id DESC", searchTerm, searchTerm) + return forums, err } func Since(since int64) ([]*Forum, error) { - return store.FilterByIndex("allByLastPost", func(f *Forum) bool { - return f.LastPost >= since - }), nil + var forums []*Forum + err := database.Select(&forums, "SELECT * FROM forum WHERE last_post >= %d ORDER BY last_post DESC, id DESC", since) + return forums, err } // Helper methods @@ -309,14 +249,3 @@ func (f *Forum) GetThread() (*Forum, error) { } return Find(f.Parent) } - -// Legacy compatibility functions (will be removed later) -func LoadData(dataPath string) error { - // No longer needed - Nigiri handles this - return nil -} - -func SaveData(dataPath string) error { - // No longer needed - Nigiri handles this - return nil -} diff --git a/internal/models/items/items.go b/internal/models/items/items.go index 4f89884..7d11034 100644 --- a/internal/models/items/items.go +++ b/internal/models/items/items.go @@ -3,17 +3,17 @@ package items import ( "fmt" - nigiri "git.sharkk.net/Sharkk/Nigiri" + "dk/internal/database" ) // Item represents an item in the game type Item struct { - ID int `json:"id"` - Type int `json:"type" db:"index"` - Name string `json:"name" db:"required"` - Value int `json:"value"` - Att int `json:"att"` - Special string `json:"special"` + ID int + Type int + Name string + Value int + Att int + Special string } // ItemType constants for item types @@ -23,37 +23,10 @@ const ( TypeShield = 3 ) -// Global store -var store *nigiri.BaseStore[Item] - -// Init sets up the Nigiri store and indices -func Init(collection *nigiri.Collection) { - store = nigiri.NewBaseStore[Item]() - - // Register custom indices - store.RegisterIndex("byType", nigiri.BuildIntGroupIndex(func(i *Item) int { - return i.Type - })) - - store.RegisterIndex("allByID", nigiri.BuildSortedListIndex(func(a, b *Item) bool { - return a.ID < b.ID - })) - - store.RebuildIndices() -} - -// GetStore returns the items store -func GetStore() *nigiri.BaseStore[Item] { - if store == nil { - panic("items store not initialized - call Initialize first") - } - return store -} - -// Creates a new Item with sensible defaults +// New creates a new Item with sensible defaults func New() *Item { return &Item{ - Type: TypeWeapon, // Default to weapon + Type: TypeWeapon, Name: "", Value: 0, Att: 0, @@ -79,48 +52,39 @@ func (i *Item) Validate() error { } // CRUD operations -func (i *Item) Save() error { - if i.ID == 0 { - id, err := store.Create(i) - if err != nil { - return err - } - i.ID = id - return nil - } - return store.Update(i.ID, i) -} - func (i *Item) Delete() error { - store.Remove(i.ID) - return nil + return database.Exec("DELETE FROM items WHERE id = %d", i.ID) } -// Insert with ID assignment func (i *Item) Insert() error { - id, err := store.Create(i) + id, err := database.Insert("items", i, "ID") if err != nil { return err } - i.ID = id + i.ID = int(id) return nil } // Query functions func Find(id int) (*Item, error) { - item, exists := store.Find(id) - if !exists { + var item Item + err := database.Get(&item, "SELECT * FROM items WHERE id = %d", id) + if err != nil { return nil, fmt.Errorf("item with ID %d not found", id) } - return item, nil + return &item, nil } func All() ([]*Item, error) { - return store.AllSorted("allByID"), nil + var items []*Item + err := database.Select(&items, "SELECT * FROM items ORDER BY id ASC") + return items, err } func ByType(itemType int) ([]*Item, error) { - return store.GroupByIndex("byType", itemType), nil + var items []*Item + err := database.Select(&items, "SELECT * FROM items WHERE type = %d ORDER BY id ASC", itemType) + return items, err } // Helper methods @@ -156,14 +120,3 @@ func (i *Item) HasSpecial() bool { func (i *Item) IsEquippable() bool { return i.Type == TypeWeapon || i.Type == TypeArmor || i.Type == TypeShield } - -// Legacy compatibility functions (will be removed later) -func LoadData(dataPath string) error { - // No longer needed - Nigiri handles this - return nil -} - -func SaveData(dataPath string) error { - // No longer needed - Nigiri handles this - return nil -} diff --git a/internal/models/monsters/monsters.go b/internal/models/monsters/monsters.go index 6ea12f8..e8f53cb 100644 --- a/internal/models/monsters/monsters.go +++ b/internal/models/monsters/monsters.go @@ -3,20 +3,20 @@ package monsters import ( "fmt" - nigiri "git.sharkk.net/Sharkk/Nigiri" + "dk/internal/database" ) // Monster represents a monster in the game type Monster struct { - ID int `json:"id"` - Name string `json:"name" db:"required"` - MaxHP int `json:"max_hp"` - MaxDmg int `json:"max_dmg"` - Armor int `json:"armor"` - Level int `json:"level" db:"index"` - MaxExp int `json:"max_exp"` - MaxGold int `json:"max_gold"` - Immune int `json:"immune" db:"index"` + ID int + Name string + MaxHP int + MaxDmg int + Armor int + Level int + MaxExp int + MaxGold int + Immune int } // Immunity constants @@ -26,41 +26,7 @@ const ( ImmuneSleep = 2 ) -// Global store -var store *nigiri.BaseStore[Monster] - -// Init sets up the Nigiri store and indices -func Init(collection *nigiri.Collection) { - store = nigiri.NewBaseStore[Monster]() - - // Register custom indices - store.RegisterIndex("byLevel", nigiri.BuildIntGroupIndex(func(m *Monster) int { - return m.Level - })) - - store.RegisterIndex("byImmunity", nigiri.BuildIntGroupIndex(func(m *Monster) int { - return m.Immune - })) - - store.RegisterIndex("allByLevel", nigiri.BuildSortedListIndex(func(a, b *Monster) bool { - if a.Level == b.Level { - return a.ID < b.ID - } - return a.Level < b.Level - })) - - store.RebuildIndices() -} - -// GetStore returns the monsters store -func GetStore() *nigiri.BaseStore[Monster] { - if store == nil { - panic("monsters store not initialized - call Initialize first") - } - return store -} - -// Creates a new Monster with sensible defaults +// New creates a new Monster with sensible defaults func New() *Monster { return &Monster{ Name: "", @@ -92,61 +58,51 @@ func (m *Monster) Validate() error { } // CRUD operations -func (m *Monster) Save() error { - if m.ID == 0 { - id, err := store.Create(m) - if err != nil { - return err - } - m.ID = id - return nil - } - return store.Update(m.ID, m) -} - func (m *Monster) Delete() error { - store.Remove(m.ID) - return nil + return database.Exec("DELETE FROM monsters WHERE id = %d", m.ID) } -// Insert with ID assignment func (m *Monster) Insert() error { - id, err := store.Create(m) + id, err := database.Insert("monsters", m, "ID") if err != nil { return err } - m.ID = id + m.ID = int(id) return nil } // Query functions func Find(id int) (*Monster, error) { - monster, exists := store.Find(id) - if !exists { + var monster Monster + err := database.Get(&monster, "SELECT * FROM monsters WHERE id = %d", id) + if err != nil { return nil, fmt.Errorf("monster with ID %d not found", id) } - return monster, nil + return &monster, nil } func All() ([]*Monster, error) { - return store.AllSorted("allByLevel"), nil + var monsters []*Monster + err := database.Select(&monsters, "SELECT * FROM monsters ORDER BY level ASC, id ASC") + return monsters, err } func ByLevel(level int) ([]*Monster, error) { - return store.GroupByIndex("byLevel", level), nil + var monsters []*Monster + err := database.Select(&monsters, "SELECT * FROM monsters WHERE level = %d ORDER BY id ASC", level) + return monsters, err } func ByLevelRange(minLevel, maxLevel int) ([]*Monster, error) { - var result []*Monster - for level := minLevel; level <= maxLevel; level++ { - monsters := store.GroupByIndex("byLevel", level) - result = append(result, monsters...) - } - return result, nil + var monsters []*Monster + err := database.Select(&monsters, "SELECT * FROM monsters WHERE level >= %d AND level <= %d ORDER BY level ASC, id ASC", minLevel, maxLevel) + return monsters, err } func ByImmunity(immunityType int) ([]*Monster, error) { - return store.GroupByIndex("byImmunity", immunityType), nil + var monsters []*Monster + err := database.Select(&monsters, "SELECT * FROM monsters WHERE immune = %d ORDER BY level ASC, id ASC", immunityType) + return monsters, err } // Helper methods @@ -195,14 +151,3 @@ func (m *Monster) GoldPerHP() float64 { } return float64(m.MaxGold) / float64(m.MaxHP) } - -// Legacy compatibility functions (will be removed later) -func LoadData(dataPath string) error { - // No longer needed - Nigiri handles this - return nil -} - -func SaveData(dataPath string) error { - // No longer needed - Nigiri handles this - return nil -} diff --git a/internal/models/news/news.go b/internal/models/news/news.go index bc54c9b..eb817d0 100644 --- a/internal/models/news/news.go +++ b/internal/models/news/news.go @@ -5,53 +5,23 @@ import ( "strings" "time" - nigiri "git.sharkk.net/Sharkk/Nigiri" + "dk/internal/database" ) // News represents a news post in the game type News struct { - ID int `json:"id"` - Author int `json:"author" db:"index"` - Posted int64 `json:"posted"` - Content string `json:"content" db:"required"` + ID int + Author int + Posted int64 + Content string } -// Global store -var store *nigiri.BaseStore[News] - -// Init sets up the Nigiri store and indices -func Init(collection *nigiri.Collection) { - store = nigiri.NewBaseStore[News]() - - // Register custom indices - store.RegisterIndex("byAuthor", nigiri.BuildIntGroupIndex(func(n *News) int { - return n.Author - })) - - store.RegisterIndex("allByPosted", nigiri.BuildSortedListIndex(func(a, b *News) bool { - if a.Posted != b.Posted { - return a.Posted > b.Posted // DESC - } - return a.ID > b.ID // DESC - })) - - store.RebuildIndices() -} - -// GetStore returns the news store -func GetStore() *nigiri.BaseStore[News] { - if store == nil { - panic("news store not initialized - call Init first") - } - return store -} - -// Creates a new News with sensible defaults +// New creates a new News with sensible defaults func New() *News { return &News{ - Author: 0, // No author by default - Posted: time.Now().Unix(), // Current time - Content: "", // Empty content + Author: 0, + Posted: time.Now().Unix(), + Content: "", } } @@ -67,75 +37,64 @@ func (n *News) Validate() error { } // CRUD operations -func (n *News) Save() error { - if n.ID == 0 { - id, err := store.Create(n) - if err != nil { - return err - } - n.ID = id - return nil - } - return store.Update(n.ID, n) -} - func (n *News) Delete() error { - store.Remove(n.ID) - return nil + return database.Exec("DELETE FROM news WHERE id = %d", n.ID) } -// Insert with ID assignment func (n *News) Insert() error { - id, err := store.Create(n) + id, err := database.Insert("news", n, "ID") if err != nil { return err } - n.ID = id + n.ID = int(id) return nil } // Query functions func Find(id int) (*News, error) { - news, exists := store.Find(id) - if !exists { + var news News + err := database.Get(&news, "SELECT * FROM news WHERE id = %d", id) + if err != nil { return nil, fmt.Errorf("news with ID %d not found", id) } - return news, nil + return &news, nil } func All() ([]*News, error) { - return store.AllSorted("allByPosted"), nil + var news []*News + err := database.Select(&news, "SELECT * FROM news ORDER BY posted DESC, id DESC") + return news, err } func ByAuthor(authorID int) ([]*News, error) { - return store.GroupByIndex("byAuthor", authorID), nil + var news []*News + err := database.Select(&news, "SELECT * FROM news WHERE author = %d ORDER BY posted DESC, id DESC", authorID) + return news, err } func Recent(limit int) ([]*News, error) { - all := store.AllSorted("allByPosted") - if limit > len(all) { - limit = len(all) - } - return all[:limit], nil + var news []*News + err := database.Select(&news, "SELECT * FROM news ORDER BY posted DESC, id DESC LIMIT %d", limit) + return news, err } func Since(since int64) ([]*News, error) { - return store.FilterByIndex("allByPosted", func(n *News) bool { - return n.Posted >= since - }), nil + var news []*News + err := database.Select(&news, "SELECT * FROM news WHERE posted >= %d ORDER BY posted DESC, id DESC", since) + return news, err } func Between(start, end int64) ([]*News, error) { - return store.FilterByIndex("allByPosted", func(n *News) bool { - return n.Posted >= start && n.Posted <= end - }), nil + var news []*News + err := database.Select(&news, "SELECT * FROM news WHERE posted >= %d AND posted <= %d ORDER BY posted DESC, id DESC", start, end) + return news, err } func Search(term string) ([]*News, error) { - lowerTerm := strings.ToLower(term) - return store.FilterByIndex("allByPosted", func(n *News) bool { - return strings.Contains(strings.ToLower(n.Content), lowerTerm) - }), nil + var news []*News + searchTerm := "%" + term + "%" + err := database.Select(&news, "SELECT * FROM news WHERE content LIKE %s ORDER BY posted DESC, id DESC", searchTerm) + return news, err } // Helper methods @@ -213,14 +172,3 @@ func (n *News) Contains(term string) bool { func (n *News) IsEmpty() bool { return strings.TrimSpace(n.Content) == "" } - -// Legacy compatibility functions (will be removed later) -func LoadData(dataPath string) error { - // No longer needed - Nigiri handles this - return nil -} - -func SaveData(dataPath string) error { - // No longer needed - Nigiri handles this - return nil -} diff --git a/internal/models/spells/spells.go b/internal/models/spells/spells.go index 92be606..e37ac1b 100644 --- a/internal/models/spells/spells.go +++ b/internal/models/spells/spells.go @@ -2,18 +2,17 @@ package spells import ( "fmt" - "strings" - nigiri "git.sharkk.net/Sharkk/Nigiri" + "dk/internal/database" ) // Spell represents a spell in the game type Spell struct { - ID int `json:"id"` - Name string `json:"name" db:"required,unique"` - MP int `json:"mp" db:"index"` - Attribute int `json:"attribute"` - Type int `json:"type" db:"index"` + ID int + Name string + MP int + Attribute int + Type int } // SpellType constants for spell types @@ -25,54 +24,13 @@ const ( TypeDefenseBoost = 5 ) -// Global store -var store *nigiri.BaseStore[Spell] - -// Init sets up the Nigiri store and indices -func Init(collection *nigiri.Collection) { - store = nigiri.NewBaseStore[Spell]() - - // Register custom indices - store.RegisterIndex("byType", nigiri.BuildIntGroupIndex(func(s *Spell) int { - return s.Type - })) - - store.RegisterIndex("byName", nigiri.BuildCaseInsensitiveLookupIndex(func(s *Spell) string { - return s.Name - })) - - store.RegisterIndex("byMP", nigiri.BuildIntGroupIndex(func(s *Spell) int { - return s.MP - })) - - store.RegisterIndex("allByTypeMP", nigiri.BuildSortedListIndex(func(a, b *Spell) bool { - if a.Type != b.Type { - return a.Type < b.Type - } - if a.MP != b.MP { - return a.MP < b.MP - } - return a.ID < b.ID - })) - - store.RebuildIndices() -} - -// GetStore returns the spells store -func GetStore() *nigiri.BaseStore[Spell] { - if store == nil { - panic("spells store not initialized - call Initialize first") - } - return store -} - -// Creates a new Spell with sensible defaults +// New creates a new Spell with sensible defaults func New() *Spell { return &Spell{ Name: "", - MP: 5, // Default MP cost - Attribute: 10, // Default attribute value - Type: TypeHealing, // Default to healing spell + MP: 5, + Attribute: 10, + Type: TypeHealing, } } @@ -94,68 +52,60 @@ func (s *Spell) Validate() error { } // CRUD operations -func (s *Spell) Save() error { - if s.ID == 0 { - id, err := store.Create(s) - if err != nil { - return err - } - s.ID = id - return nil - } - return store.Update(s.ID, s) -} - func (s *Spell) Delete() error { - store.Remove(s.ID) - return nil + return database.Exec("DELETE FROM spells WHERE id = %d", s.ID) } -// Insert with ID assignment func (s *Spell) Insert() error { - id, err := store.Create(s) + id, err := database.Insert("spells", s, "ID") if err != nil { return err } - s.ID = id + s.ID = int(id) return nil } // Query functions func Find(id int) (*Spell, error) { - spell, exists := store.Find(id) - if !exists { + var spell Spell + err := database.Get(&spell, "SELECT * FROM spells WHERE id = %d", id) + if err != nil { return nil, fmt.Errorf("spell with ID %d not found", id) } - return spell, nil + return &spell, nil } func All() ([]*Spell, error) { - return store.AllSorted("allByTypeMP"), nil + var spells []*Spell + err := database.Select(&spells, "SELECT * FROM spells ORDER BY type ASC, mp ASC, id ASC") + return spells, err } func ByType(spellType int) ([]*Spell, error) { - return store.GroupByIndex("byType", spellType), nil + var spells []*Spell + err := database.Select(&spells, "SELECT * FROM spells WHERE type = %d ORDER BY mp ASC, id ASC", spellType) + return spells, err } func ByMaxMP(maxMP int) ([]*Spell, error) { - return store.FilterByIndex("allByTypeMP", func(s *Spell) bool { - return s.MP <= maxMP - }), nil + var spells []*Spell + err := database.Select(&spells, "SELECT * FROM spells WHERE mp <= %d ORDER BY type ASC, mp ASC, id ASC", maxMP) + return spells, err } func ByTypeAndMaxMP(spellType, maxMP int) ([]*Spell, error) { - return store.FilterByIndex("allByTypeMP", func(s *Spell) bool { - return s.Type == spellType && s.MP <= maxMP - }), nil + var spells []*Spell + err := database.Select(&spells, "SELECT * FROM spells WHERE type = %d AND mp <= %d ORDER BY mp ASC, id ASC", spellType, maxMP) + return spells, err } func ByName(name string) (*Spell, error) { - spell, exists := store.LookupByIndex("byName", strings.ToLower(name)) - if !exists { + var spell Spell + err := database.Get(&spell, "SELECT * FROM spells WHERE name = %s COLLATE NOCASE", name) + if err != nil { return nil, fmt.Errorf("spell with name '%s' not found", name) } - return spell, nil + return &spell, nil } // Helper methods @@ -214,14 +164,3 @@ func (s *Spell) IsOffensive() bool { func (s *Spell) IsSupport() bool { return s.Type == TypeHealing || s.Type == TypeAttackBoost || s.Type == TypeDefenseBoost } - -// Legacy compatibility functions (will be removed later) -func LoadData(dataPath string) error { - // No longer needed - Nigiri handles this - return nil -} - -func SaveData(dataPath string) error { - // No longer needed - Nigiri handles this - return nil -}