package store import ( "context" "fmt" "strings" "time" "github.com/google/uuid" ext "git.warky.dev/wdevs/amcs/internal/types" ) func (db *DB) AddFamilyMember(ctx context.Context, m ext.FamilyMember) (ext.FamilyMember, error) { row := db.pool.QueryRow(ctx, ` insert into family_members (name, relationship, birth_date, notes) values ($1, $2, $3, $4) returning id, created_at `, m.Name, nullStr(m.Relationship), m.BirthDate, nullStr(m.Notes)) created := m if err := row.Scan(&created.ID, &created.CreatedAt); err != nil { return ext.FamilyMember{}, fmt.Errorf("insert family member: %w", err) } return created, nil } func (db *DB) ListFamilyMembers(ctx context.Context) ([]ext.FamilyMember, error) { rows, err := db.pool.Query(ctx, `select id, name, relationship, birth_date, notes, created_at from family_members order by name`) if err != nil { return nil, fmt.Errorf("list family members: %w", err) } defer rows.Close() var members []ext.FamilyMember for rows.Next() { var m ext.FamilyMember var relationship, notes *string if err := rows.Scan(&m.ID, &m.Name, &relationship, &m.BirthDate, ¬es, &m.CreatedAt); err != nil { return nil, fmt.Errorf("scan family member: %w", err) } m.Relationship = strVal(relationship) m.Notes = strVal(notes) members = append(members, m) } return members, rows.Err() } func (db *DB) AddActivity(ctx context.Context, a ext.Activity) (ext.Activity, error) { row := db.pool.QueryRow(ctx, ` insert into activities (family_member_id, title, activity_type, day_of_week, start_time, end_time, start_date, end_date, location, notes) values ($1, $2, $3, $4, $5::time, $6::time, $7, $8, $9, $10) returning id, created_at `, a.FamilyMemberID, a.Title, nullStr(a.ActivityType), nullStr(a.DayOfWeek), nullStr(a.StartTime), nullStr(a.EndTime), a.StartDate, a.EndDate, nullStr(a.Location), nullStr(a.Notes)) created := a if err := row.Scan(&created.ID, &created.CreatedAt); err != nil { return ext.Activity{}, fmt.Errorf("insert activity: %w", err) } return created, nil } func (db *DB) GetWeekSchedule(ctx context.Context, weekStart time.Time) ([]ext.Activity, error) { weekEnd := weekStart.AddDate(0, 0, 7) rows, err := db.pool.Query(ctx, ` select a.id, a.family_member_id, fm.name, a.title, a.activity_type, a.day_of_week, a.start_time::text, a.end_time::text, a.start_date, a.end_date, a.location, a.notes, a.created_at from activities a left join family_members fm on fm.id = a.family_member_id where (a.start_date >= $1 and a.start_date < $2) or (a.day_of_week is not null and (a.end_date is null or a.end_date >= $1)) order by a.start_date, a.start_time `, weekStart, weekEnd) if err != nil { return nil, fmt.Errorf("get week schedule: %w", err) } defer rows.Close() return scanActivities(rows) } func (db *DB) SearchActivities(ctx context.Context, query, activityType string, memberID *uuid.UUID) ([]ext.Activity, error) { args := []any{} conditions := []string{} if q := strings.TrimSpace(query); q != "" { args = append(args, "%"+q+"%") conditions = append(conditions, fmt.Sprintf("(a.title ILIKE $%d OR a.notes ILIKE $%d)", len(args), len(args))) } if t := strings.TrimSpace(activityType); t != "" { args = append(args, t) conditions = append(conditions, fmt.Sprintf("a.activity_type = $%d", len(args))) } if memberID != nil { args = append(args, *memberID) conditions = append(conditions, fmt.Sprintf("a.family_member_id = $%d", len(args))) } q := ` select a.id, a.family_member_id, fm.name, a.title, a.activity_type, a.day_of_week, a.start_time::text, a.end_time::text, a.start_date, a.end_date, a.location, a.notes, a.created_at from activities a left join family_members fm on fm.id = a.family_member_id ` if len(conditions) > 0 { q += " where " + strings.Join(conditions, " and ") } q += " order by a.start_date, a.start_time" rows, err := db.pool.Query(ctx, q, args...) if err != nil { return nil, fmt.Errorf("search activities: %w", err) } defer rows.Close() return scanActivities(rows) } func (db *DB) AddImportantDate(ctx context.Context, d ext.ImportantDate) (ext.ImportantDate, error) { row := db.pool.QueryRow(ctx, ` insert into important_dates (family_member_id, title, date_value, recurring_yearly, reminder_days_before, notes) values ($1, $2, $3, $4, $5, $6) returning id, created_at `, d.FamilyMemberID, d.Title, d.DateValue, d.RecurringYearly, d.ReminderDaysBefore, nullStr(d.Notes)) created := d if err := row.Scan(&created.ID, &created.CreatedAt); err != nil { return ext.ImportantDate{}, fmt.Errorf("insert important date: %w", err) } return created, nil } func (db *DB) GetUpcomingDates(ctx context.Context, daysAhead int) ([]ext.ImportantDate, error) { if daysAhead <= 0 { daysAhead = 30 } now := time.Now() cutoff := now.AddDate(0, 0, daysAhead) // For yearly recurring events, check if this year's occurrence falls in range rows, err := db.pool.Query(ctx, ` select d.id, d.family_member_id, fm.name, d.title, d.date_value, d.recurring_yearly, d.reminder_days_before, d.notes, d.created_at from important_dates d left join family_members fm on fm.id = d.family_member_id where ( (d.recurring_yearly = false and d.date_value between $1 and $2) or (d.recurring_yearly = true and make_date(extract(year from now())::int, extract(month from d.date_value)::int, extract(day from d.date_value)::int) between $1 and $2) ) order by d.date_value `, now, cutoff) if err != nil { return nil, fmt.Errorf("get upcoming dates: %w", err) } defer rows.Close() var dates []ext.ImportantDate for rows.Next() { var d ext.ImportantDate var memberID *uuid.UUID var memberName, notes *string if err := rows.Scan(&d.ID, &memberID, &memberName, &d.Title, &d.DateValue, &d.RecurringYearly, &d.ReminderDaysBefore, ¬es, &d.CreatedAt); err != nil { return nil, fmt.Errorf("scan important date: %w", err) } d.FamilyMemberID = memberID d.MemberName = strVal(memberName) d.Notes = strVal(notes) dates = append(dates, d) } return dates, rows.Err() } func scanActivities(rows interface { Next() bool Scan(...any) error Err() error Close() }) ([]ext.Activity, error) { defer rows.Close() var activities []ext.Activity for rows.Next() { var a ext.Activity var memberName, activityType, dayOfWeek, startTime, endTime, location, notes *string if err := rows.Scan( &a.ID, &a.FamilyMemberID, &memberName, &a.Title, &activityType, &dayOfWeek, &startTime, &endTime, &a.StartDate, &a.EndDate, &location, ¬es, &a.CreatedAt, ); err != nil { return nil, fmt.Errorf("scan activity: %w", err) } a.MemberName = strVal(memberName) a.ActivityType = strVal(activityType) a.DayOfWeek = strVal(dayOfWeek) a.StartTime = strVal(startTime) a.EndTime = strVal(endTime) a.Location = strVal(location) a.Notes = strVal(notes) activities = append(activities, a) } return activities, rows.Err() }