package checkin import ( "context" "database/sql" "time" ) // UserEvent is an event a user has checked into, enriched with event metadata. type UserEvent struct { EventURI string EventName string Location string StartTime time.Time EndTime time.Time CheckedInAt time.Time IsOngoing bool } // ListForUser returns all events a user has checked into, most recent first. // Each event appears once (even if the user checked in multiple times). // did can be an ATProto DID ("did:plc:...") or a local ID ("local_..."). func ListForUser(ctx context.Context, db *sql.DB, did string, now time.Time) ([]UserEvent, error) { rows, err := db.QueryContext(ctx, ` SELECT e.uri, e.name, e.location, e.start_time, e.end_time, MAX(c.checked_in_at) AS last_checkin FROM checkins c JOIN events e ON e.uri = c.event_uri WHERE c.did = ? GROUP BY e.uri ORDER BY last_checkin DESC `, did) if err != nil { return nil, err } defer rows.Close() var out []UserEvent for rows.Next() { var ue UserEvent var checkedInAtStr string if err := rows.Scan(&ue.EventURI, &ue.EventName, &ue.Location, &ue.StartTime, &ue.EndTime, &checkedInAtStr); err != nil { return nil, err } ue.CheckedInAt, _ = time.Parse(time.RFC3339, checkedInAtStr) if ue.CheckedInAt.IsZero() { ue.CheckedInAt, _ = time.Parse("2006-01-02 15:04:05", checkedInAtStr) } ue.IsOngoing = !now.Before(ue.StartTime) && !now.After(ue.EndTime) out = append(out, ue) } return out, rows.Err() } // ListAttendeesForEvent returns distinct DIDs of users who checked into a // specific event, ordered by check-in time (earliest first). func ListAttendeesForEvent(ctx context.Context, db *sql.DB, eventURI string) ([]string, error) { rows, err := db.QueryContext(ctx, ` SELECT did FROM checkins WHERE event_uri = ? GROUP BY did ORDER BY MIN(checked_in_at) ASC `, eventURI) if err != nil { return nil, err } defer rows.Close() var out []string for rows.Next() { var did string if err := rows.Scan(&did); err != nil { return nil, err } out = append(out, did) } return out, rows.Err() }