mirror of
https://github.com/tiennm99/openai-status-bot.git
synced 2026-07-14 09:05:43 +00:00
212 lines
5.8 KiB
Go
212 lines
5.8 KiB
Go
package mongostore
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
|
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
|
)
|
|
|
|
// deliveryTTL is the retention window for per-event delivery markers; enforced
|
|
// by the TTL index in EnsureIndexes.
|
|
const deliveryTTL = 7 * 24 * time.Hour
|
|
|
|
type PendingComponentEvent struct {
|
|
ComponentID string `bson:"_id"`
|
|
ComponentName string `bson:"componentName"`
|
|
Status string `bson:"status"`
|
|
UpdatedAt string `bson:"updatedAt"`
|
|
Position int `bson:"position"`
|
|
PreviousStatus string `bson:"previousStatus"`
|
|
DeliveryKey string `bson:"deliveryKey"`
|
|
}
|
|
|
|
func (s *Store) IsInitialized(ctx context.Context) (bool, error) {
|
|
err := s.meta.FindOne(ctx, bson.M{"_id": metaInitializedID}).Err()
|
|
if errors.Is(err, mongo.ErrNoDocuments) {
|
|
return false, nil
|
|
}
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (s *Store) SetInitialized(ctx context.Context) error {
|
|
_, err := s.meta.UpdateOne(ctx, bson.M{"_id": metaInitializedID},
|
|
bson.M{"$set": bson.M{"value": true}}, options.UpdateOne().SetUpsert(true))
|
|
return err
|
|
}
|
|
|
|
func (s *Store) ComponentStatuses(ctx context.Context) (map[string]string, error) {
|
|
cursor, err := s.componentStatuses.Find(ctx, bson.M{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer cursor.Close(ctx)
|
|
|
|
statuses := map[string]string{}
|
|
for cursor.Next(ctx) {
|
|
var doc struct {
|
|
ID string `bson:"_id"`
|
|
Status string `bson:"status"`
|
|
}
|
|
if err := cursor.Decode(&doc); err != nil {
|
|
return nil, err
|
|
}
|
|
statuses[doc.ID] = doc.Status
|
|
}
|
|
if err := cursor.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return statuses, nil
|
|
}
|
|
|
|
func (s *Store) SaveComponentStatus(ctx context.Context, componentID, status string) error {
|
|
_, err := s.componentStatuses.UpdateOne(ctx, bson.M{"_id": componentID},
|
|
bson.M{"$set": bson.M{"status": status}}, options.UpdateOne().SetUpsert(true))
|
|
return err
|
|
}
|
|
|
|
func (s *Store) PendingComponentEvents(ctx context.Context) (map[string]PendingComponentEvent, error) {
|
|
cursor, err := s.pendingComponentEvents.Find(ctx, bson.M{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer cursor.Close(ctx)
|
|
|
|
events := map[string]PendingComponentEvent{}
|
|
for cursor.Next(ctx) {
|
|
var event PendingComponentEvent
|
|
if err := cursor.Decode(&event); err != nil {
|
|
return nil, err
|
|
}
|
|
events[event.ComponentID] = event
|
|
}
|
|
if err := cursor.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return events, nil
|
|
}
|
|
|
|
func (s *Store) SavePendingComponentEvent(ctx context.Context, event PendingComponentEvent) error {
|
|
_, err := s.pendingComponentEvents.ReplaceOne(ctx, bson.M{"_id": event.ComponentID}, event,
|
|
options.Replace().SetUpsert(true))
|
|
return err
|
|
}
|
|
|
|
func (s *Store) RemovePendingComponentEvent(ctx context.Context, componentID string) error {
|
|
_, err := s.pendingComponentEvents.DeleteOne(ctx, bson.M{"_id": componentID})
|
|
return err
|
|
}
|
|
|
|
func (s *Store) HasIncidentUpdateVersion(ctx context.Context, updateID, version string) (bool, error) {
|
|
var doc struct {
|
|
Version string `bson:"version"`
|
|
}
|
|
err := s.incidentUpdateVersions.FindOne(ctx, bson.M{"_id": updateID}).Decode(&doc)
|
|
if errors.Is(err, mongo.ErrNoDocuments) {
|
|
return false, nil
|
|
}
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return doc.Version == version, nil
|
|
}
|
|
|
|
func (s *Store) MarkIncidentUpdateVersion(ctx context.Context, updateID, version string) error {
|
|
_, err := s.incidentUpdateVersions.UpdateOne(ctx, bson.M{"_id": updateID},
|
|
bson.M{"$set": bson.M{"version": version}}, options.UpdateOne().SetUpsert(true))
|
|
return err
|
|
}
|
|
|
|
// DeliveredSubscribers returns the subscriber keys already notified for
|
|
// eventKey. One Find replaces a per-subscriber membership check.
|
|
func (s *Store) DeliveredSubscribers(ctx context.Context, eventKey string) (map[string]bool, error) {
|
|
cursor, err := s.delivery.Find(ctx, bson.M{"eventKey": eventKey})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer cursor.Close(ctx)
|
|
|
|
delivered := map[string]bool{}
|
|
for cursor.Next(ctx) {
|
|
var doc struct {
|
|
Subscriber string `bson:"subscriber"`
|
|
}
|
|
if err := cursor.Decode(&doc); err != nil {
|
|
return nil, err
|
|
}
|
|
delivered[doc.Subscriber] = true
|
|
}
|
|
if err := cursor.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return delivered, nil
|
|
}
|
|
|
|
func (s *Store) MarkDelivered(ctx context.Context, eventKey, subscriberKey string) error {
|
|
id := eventKey + "|" + subscriberKey
|
|
update := bson.M{"$set": bson.M{
|
|
"eventKey": eventKey,
|
|
"subscriber": subscriberKey,
|
|
"expiresAt": time.Now().Add(deliveryTTL),
|
|
}}
|
|
_, err := s.delivery.UpdateOne(ctx, bson.M{"_id": id}, update, options.UpdateOne().SetUpsert(true))
|
|
return err
|
|
}
|
|
|
|
func (s *Store) ClearDelivery(ctx context.Context, eventKey string) error {
|
|
_, err := s.delivery.DeleteMany(ctx, bson.M{"eventKey": eventKey})
|
|
return err
|
|
}
|
|
|
|
func (s *Store) TelegramOffset(ctx context.Context) (int64, error) {
|
|
var doc struct {
|
|
Value any `bson:"value"`
|
|
}
|
|
err := s.meta.FindOne(ctx, bson.M{"_id": metaTelegramOffsetID}).Decode(&doc)
|
|
if errors.Is(err, mongo.ErrNoDocuments) {
|
|
return 0, nil
|
|
}
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
offset, ok := parseTelegramOffsetValue(doc.Value)
|
|
if ok {
|
|
return offset, nil
|
|
}
|
|
if _, err := s.meta.DeleteOne(ctx, bson.M{"_id": metaTelegramOffsetID}); err != nil {
|
|
return 0, fmt.Errorf("clear invalid telegram offset: %w", err)
|
|
}
|
|
return 0, nil
|
|
}
|
|
|
|
func parseTelegramOffsetValue(value any) (int64, bool) {
|
|
switch value := value.(type) {
|
|
case int:
|
|
return int64(value), true
|
|
case int32:
|
|
return int64(value), true
|
|
case int64:
|
|
return value, true
|
|
case string:
|
|
offset, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64)
|
|
return offset, err == nil
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
func (s *Store) SaveTelegramOffset(ctx context.Context, offset int64) error {
|
|
_, err := s.meta.UpdateOne(ctx, bson.M{"_id": metaTelegramOffsetID},
|
|
bson.M{"$set": bson.M{"value": offset}}, options.UpdateOne().SetUpsert(true))
|
|
return err
|
|
}
|