package lishwist import ( "fmt" "log" "strings" "time" "lishwist/core/internal/db" ) const ( eventActionCreate = "CREATE" eventActionHide = "HIDE" eventActionUnhide = "UNHIDE" eventActionClaim = "CLAIM" eventActionUnclaim = "UNCLAIM" eventActionComplete = "COMPLETE" // eventActionDelete = "DELETE" NOTE: We can't have this, because there'll be no target to reference ) const ( eventTargetGroup = "GROUP" eventTargetUser = "USER" eventTargetWish = "WISH" eventTargetGroupMember = "GROUP_MEMBER" ) type Event struct { Id string ActorId string ActionType string TargetType string TargetId string CreatedAt time.Time } // type EventCreateGroupMember struct { // Event // Actor User // User // Group // } func queryManyEvents(query string, args ...any) ([]Event, error) { rows, err := db.Connection.Query(query, args...) if err != nil { return nil, err } defer rows.Close() events := []Event{} for rows.Next() { var g Event var createdAt string err = rows.Scan(&g.Id, &g.ActorId, &g.ActionType, &g.TargetType, &g.TargetId, &createdAt) if err != nil { return nil, err } g.CreatedAt, err = time.Parse(time.RFC3339Nano, createdAt) if err != nil { return nil, fmt.Errorf("failed to parse created_at: %w", err) } events = append(events, g) } err = rows.Err() if err != nil { return nil, err } return events, nil } func queryOneEvent(query string, args ...any) (*Event, error) { events, err := queryManyEvents(query, args...) if err != nil { return nil, err } if len(events) < 1 { return nil, nil } return &events[0], nil } func (a *Admin) ListEvents() ([]Event, error) { query := "SELECT id, actor_id, action_type, target_type, target_id, created_at FROM event;" return queryManyEvents(query) } func recordEvent(actorId, actionType, targetType string, targetIds ...string) { // TODO: If this were to accept sql.Tx it could be used in atomic transactions numTargets := len(targetIds) if numTargets < 1 { log.Println("Warning: recordEvent called with no target IDs. Skipping.") return } stmt := "INSERT INTO event (actor_id, action_type, target_type, target_id) VALUES (?, ?, ?, ?)" extraValuePlaceholders := strings.Repeat(", (?, ?, ?, ?)", numTargets-1) args := make([]any, numTargets*4) for i, id := range targetIds { args[i*4] = actorId args[i*4+1] = actionType args[i*4+2] = targetType args[i*4+3] = id } _, err := db.Connection.Exec(stmt+extraValuePlaceholders, args...) if err == nil { return } if numTargets == 1 { log.Printf("Failed to record %s %s event: failed to execute query: %s\n", actionType, targetType, err) } else { log.Printf("Failed to record %d %s %s events: failed to execute query: %s\n", numTargets, actionType, targetType, err) } } func recordEventCreateGroup(actorId, groupId string) { recordEvent(actorId, eventActionCreate, eventTargetGroup, groupId) } func recordEventCreateUser(actorId, userId string) { recordEvent(actorId, eventActionCreate, eventTargetUser, userId) } func recordEventCreateWish(actorId, wishId string) { recordEvent(actorId, eventActionCreate, eventTargetWish, wishId) } func recordEventCreateGroupMember(actorId, groupMemberId string) { recordEvent(actorId, eventActionCreate, eventTargetGroupMember, groupMemberId) } // FIXME: I can't use these yet because the associated actions use reference // func recordEventHideUser(actorId, userId string) { // recordEvent(actorId, eventActionHide, eventTargetUser, userId) // } // func recordEventUnhideUser(actorId, userId string) { // recordEvent(actorId, eventActionUnhide, eventTargetUser, userId) // } func recordEventClaimWishes(actorId string, wishIds ...string) { recordEvent(actorId, eventActionClaim, eventTargetWish, wishIds...) } func recordEventUnclaimWishes(actorId string, wishIds ...string) { recordEvent(actorId, eventActionUnclaim, eventTargetWish, wishIds...) } func recordEventCompleteWishes(actorId string, wishIds ...string) { recordEvent(actorId, eventActionComplete, eventTargetWish, wishIds...) }