package store import ( "context" "database/sql" "fmt" "strings" "time" "plumber/internal/store/sqlc" ) // DiscordLink is the bot-owned mapping from a site post to a Discord message. type DiscordLink struct { PostID string MessageID string ThreadID string CreatedAt string } // DiscordLinkStore is the mapping table used only by the Discord subscriber. // It is not part of Store. type DiscordLinkStore interface { GetByPostID(ctx context.Context, postID string) (*DiscordLink, error) GetByMessageID(ctx context.Context, messageID string) (*DiscordLink, error) GetRootByThreadID(ctx context.Context, threadID string) (*DiscordLink, error) Upsert(ctx context.Context, link DiscordLink) error } // DiscordLinks implements DiscordLinkStore against Postgres. type DiscordLinks struct { db *sql.DB } // NewDiscordLinks wraps db. It is independent of Store. func NewDiscordLinks(db *sql.DB) *DiscordLinks { return &DiscordLinks{db: db} } // GetByPostID returns the link for a site post. func (d *DiscordLinks) GetByPostID(ctx context.Context, postID string) (*DiscordLink, error) { if d == nil || d.db == nil { return nil, fmt.Errorf("discord links: no database") } row, err := sqlc.New(d.db).GetDiscordPostLinkByPostID(ctx, strings.TrimSpace(postID)) if err != nil { return nil, err } return discordLinkFromRow(row), nil } // GetByMessageID returns the link for a Discord message. func (d *DiscordLinks) GetByMessageID(ctx context.Context, messageID string) (*DiscordLink, error) { if d == nil || d.db == nil { return nil, fmt.Errorf("discord links: no database") } row, err := sqlc.New(d.db).GetDiscordPostLinkByMessageID(ctx, strings.TrimSpace(messageID)) if err != nil { return nil, err } return discordLinkFromRow(row), nil } // GetRootByThreadID returns the root link for a Discord thread. func (d *DiscordLinks) GetRootByThreadID(ctx context.Context, threadID string) (*DiscordLink, error) { if d == nil || d.db == nil { return nil, fmt.Errorf("discord links: no database") } row, err := sqlc.New(d.db).GetDiscordPostLinkByThreadID(ctx, strings.TrimSpace(threadID)) if err != nil { return nil, err } return discordLinkFromRow(row), nil } // Upsert inserts or replaces the Discord IDs for a post. func (d *DiscordLinks) Upsert(ctx context.Context, link DiscordLink) error { if d == nil || d.db == nil { return fmt.Errorf("discord links: no database") } link.PostID = strings.TrimSpace(link.PostID) link.MessageID = strings.TrimSpace(link.MessageID) link.ThreadID = strings.TrimSpace(link.ThreadID) if link.PostID == "" || link.MessageID == "" { return fmt.Errorf("discord links: post and message ids are required") } if link.CreatedAt == "" { link.CreatedAt = time.Now().UTC().Format(time.RFC3339Nano) } return sqlc.New(d.db).UpsertDiscordPostLink(ctx, sqlc.UpsertDiscordPostLinkParams{ PostID: link.PostID, DiscordMessageID: link.MessageID, DiscordThreadID: link.ThreadID, CreatedAt: link.CreatedAt, }) } func discordLinkFromRow(row sqlc.DiscordPostLink) *DiscordLink { return &DiscordLink{ PostID: row.PostID, MessageID: row.DiscordMessageID, ThreadID: row.DiscordThreadID, CreatedAt: row.CreatedAt, } }