CI / ci (push) Successful in 8m31s
Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
107 lines
3.6 KiB
Go
107 lines
3.6 KiB
Go
package shopping
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/chat"
|
|
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/db"
|
|
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/household"
|
|
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/jobs"
|
|
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/store"
|
|
)
|
|
|
|
// SendToChat posts the Supply run of the week starting weekStart to Google
|
|
// Chat (chat.PostSupplyRun): each dinner with its items still needed, and a
|
|
// link to the Supply run. It records sent_to_chat_at and returns the list.
|
|
//
|
|
// A week is sent once: a Supply run already sent, or with a send
|
|
// unconfirmed, is returned as it is, without posting again, unless resend
|
|
// is true. A week with no Supply run is ErrListNotFound and one frozen into
|
|
// the Supply archive ErrFrozen.
|
|
//
|
|
// The send is recorded as begun, in chat_send_unconfirmed_at, before it is
|
|
// posted, so a post whose outcome is lost is never taken for one never
|
|
// made. A post Google Chat refuses, or that is never made, returns
|
|
// chat.PostSupplyRun's error and clears it. A post Google Chat does not
|
|
// answer, or one it accepts that cannot then be recorded, is
|
|
// ErrSendUnconfirmed and leaves it, as does the process stopping mid-send.
|
|
func (s *Service) SendToChat(ctx context.Context, weekStart store.LocalDate, resend bool) (List, error) {
|
|
s.sending.Lock()
|
|
defer s.sending.Unlock()
|
|
l, err := s.Get(ctx, weekStart)
|
|
if err != nil {
|
|
return List{}, err
|
|
}
|
|
if l.Status != StatusCurrent {
|
|
return List{}, ErrFrozen
|
|
}
|
|
if (l.SentToChatAt != nil || l.ChatSendUnconfirmedAt != nil) && !resend {
|
|
return l, nil
|
|
}
|
|
name, err := household.WeekLabel(weekStart)
|
|
if err != nil {
|
|
return List{}, fmt.Errorf("shopping: send to Google Chat: %w", err)
|
|
}
|
|
if l.Title != nil {
|
|
name = *l.Title
|
|
}
|
|
groups := make([]chat.SupplyRunGroup, len(l.Groups))
|
|
for i, g := range l.Groups {
|
|
groups[i].Dinner = g.DinnerName
|
|
for _, it := range g.Items {
|
|
if !it.Needed {
|
|
continue
|
|
}
|
|
item := chat.SupplyRunItem{Name: it.Name}
|
|
if it.Qty != nil {
|
|
item.Qty = *it.Qty
|
|
}
|
|
groups[i].Items = append(groups[i].Items, item)
|
|
}
|
|
}
|
|
q := db.New(s.st.Writer)
|
|
n, err := q.BeginShoppingListChatSend(ctx, db.BeginShoppingListChatSendParams{
|
|
ChatSendUnconfirmedAt: sql.NullString{String: store.Now().String(), Valid: true},
|
|
UpdatedAt: store.Now().String(),
|
|
ID: l.ID,
|
|
})
|
|
if err != nil {
|
|
return List{}, fmt.Errorf("shopping: record send to Google Chat begun: %w", err)
|
|
}
|
|
if n != 1 {
|
|
return List{}, ErrFrozen
|
|
}
|
|
postErr := s.chat.PostSupplyRun(ctx, weekStart, name, groups)
|
|
// The post's outcome is known: record it even if the client has gone.
|
|
write, cancel := jobs.FinalWriteContext(ctx)
|
|
defer cancel()
|
|
if errors.Is(postErr, chat.ErrUnreachable) {
|
|
return List{}, fmt.Errorf("%w: %w", ErrSendUnconfirmed, postErr)
|
|
}
|
|
if postErr != nil {
|
|
if _, err := q.ClearShoppingListChatSend(write, db.ClearShoppingListChatSendParams{
|
|
UpdatedAt: store.Now().String(),
|
|
ID: l.ID,
|
|
}); err != nil {
|
|
return List{}, errors.Join(postErr, fmt.Errorf("shopping: clear refused send to Google Chat: %w", err))
|
|
}
|
|
return List{}, postErr
|
|
}
|
|
now := store.Now().String()
|
|
n, err = q.MarkShoppingListSentToChat(write, db.MarkShoppingListSentToChatParams{
|
|
SentToChatAt: sql.NullString{String: now, Valid: true},
|
|
UpdatedAt: now,
|
|
ID: l.ID,
|
|
})
|
|
if err != nil {
|
|
return List{}, fmt.Errorf("%w: record sent to Google Chat: %w", ErrSendUnconfirmed, err)
|
|
}
|
|
if n != 1 {
|
|
return List{}, ErrFrozen
|
|
}
|
|
return s.Get(write, weekStart)
|
|
}
|