lishwist/core/event.go

151 lines
4.0 KiB
Go

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...)
}