package schedulerepo import ( "context" "database/sql" "time" ) type Repo struct { db *sql.DB } type ScheduleStatus string const ( StatusPending ScheduleStatus = "pending" StatusAccepted ScheduleStatus = "accepted" StatusDeclined ScheduleStatus = "declined" StatusRescheduleRequested ScheduleStatus = "reschedule_requested" ) type Schedule struct { ID int64 GuildID int64 RequesterID int64 InviteeID int64 ScheduledAt time.Time Status ScheduleStatus Description string ReminderSent bool CreatedAt time.Time } func NewRepo(db *sql.DB) *Repo { return &Repo{db: db} } func (r *Repo) CreateSchedule(ctx context.Context, guildID, requesterID, inviteeID int64, when time.Time, description string) (int64, error) { const q = ` INSERT INTO schedules (guild_id, requester_id, invitee_id, scheduled_at, description) VALUES ($1, $2, $3, $4, $5) RETURNING id ` var id int64 if err := r.db.QueryRowContext(ctx, q, guildID, requesterID, inviteeID, when, description).Scan(&id); err != nil { return 0, err } return id, nil } func (r *Repo) AddMessage(ctx context.Context, scheduleID, messageID, channelID int64, isDM bool) error { const q = ` INSERT INTO schedule_messages (schedule_id, message_id, channel_id, is_dm) VALUES ($1, $2, $3, $4) ON CONFLICT (schedule_id, message_id) DO NOTHING ` _, err := r.db.ExecContext(ctx, q, scheduleID, messageID, channelID, isDM) return err } func (r *Repo) GetByMessageID(ctx context.Context, messageID int64) (*Schedule, error) { const q = ` SELECT s.id, s.guild_id, s.requester_id, s.invitee_id, s.scheduled_at, s.status, s.description, s.reminder_sent, s.created_at FROM schedules s JOIN schedule_messages m ON m.schedule_id = s.id WHERE m.message_id = $1 LIMIT 1 ` row := r.db.QueryRowContext(ctx, q, messageID) var sch Schedule if err := row.Scan(&sch.ID, &sch.GuildID, &sch.RequesterID, &sch.InviteeID, &sch.ScheduledAt, &sch.Status, &sch.Description, &sch.ReminderSent, &sch.CreatedAt); err != nil { if err == sql.ErrNoRows { return nil, nil } return nil, err } return &sch, nil } func (r *Repo) UpdateStatus(ctx context.Context, scheduleID int64, status ScheduleStatus) error { const q = ` UPDATE schedules SET status = $1 WHERE id = $2 ` _, err := r.db.ExecContext(ctx, q, status, scheduleID) return err } func (r *Repo) ListForUser(ctx context.Context, guildID, userID int64, limit int) ([]*Schedule, error) { const q = ` SELECT id, guild_id, requester_id, invitee_id, scheduled_at, status, description, reminder_sent, created_at FROM schedules WHERE guild_id = $1 AND (requester_id = $2 OR invitee_id = $2) AND status IN ('pending', 'accepted', 'reschedule_requested') ORDER BY scheduled_at ASC LIMIT $3 ` rows, err := r.db.QueryContext(ctx, q, guildID, userID, limit) if err != nil { return nil, err } defer rows.Close() var out []*Schedule for rows.Next() { var sch Schedule if err := rows.Scan(&sch.ID, &sch.GuildID, &sch.RequesterID, &sch.InviteeID, &sch.ScheduledAt, &sch.Status, &sch.Description, &sch.ReminderSent, &sch.CreatedAt); err != nil { return nil, err } out = append(out, &sch) } if err := rows.Err(); err != nil { return nil, err } return out, nil } func (r *Repo) GetByID(ctx context.Context, id int64) (*Schedule, error) { const q = ` SELECT id, guild_id, requester_id, invitee_id, scheduled_at, status, description, reminder_sent, created_at FROM schedules WHERE id = $1 ` row := r.db.QueryRowContext(ctx, q, id) var sch Schedule if err := row.Scan(&sch.ID, &sch.GuildID, &sch.RequesterID, &sch.InviteeID, &sch.ScheduledAt, &sch.Status, &sch.Description, &sch.ReminderSent, &sch.CreatedAt); err != nil { if err == sql.ErrNoRows { return nil, nil } return nil, err } return &sch, nil } func (r *Repo) Reschedule(ctx context.Context, id int64, when time.Time) error { const q = ` UPDATE schedules SET scheduled_at = $1, status = 'pending', reminder_sent = FALSE WHERE id = $2 ` _, err := r.db.ExecContext(ctx, q, when, id) return err } // ListDueForReminder returns sessions starting between now and now+ahead, not yet reminded. func (r *Repo) ListDueForReminder(ctx context.Context, now time.Time, ahead time.Duration) ([]*Schedule, error) { const q = ` SELECT id, guild_id, requester_id, invitee_id, scheduled_at, status, description, reminder_sent, created_at FROM schedules WHERE reminder_sent = FALSE AND status IN ('pending', 'accepted', 'reschedule_requested') AND scheduled_at > $1 AND scheduled_at <= $2 ` rows, err := r.db.QueryContext(ctx, q, now, now.Add(ahead)) if err != nil { return nil, err } defer rows.Close() var out []*Schedule for rows.Next() { var sch Schedule if err := rows.Scan(&sch.ID, &sch.GuildID, &sch.RequesterID, &sch.InviteeID, &sch.ScheduledAt, &sch.Status, &sch.Description, &sch.ReminderSent, &sch.CreatedAt); err != nil { return nil, err } out = append(out, &sch) } if err := rows.Err(); err != nil { return nil, err } return out, nil } func (r *Repo) MarkReminded(ctx context.Context, scheduleID int64) error { const q = ` UPDATE schedules SET reminder_sent = TRUE WHERE id = $1 ` _, err := r.db.ExecContext(ctx, q, scheduleID) return err }