Add Discord outbound posting (#14)

Stacks on #13. Adds discord_post_links and a subscriber that posts roots into a public thread, replies into that thread, and edits the linked Discord message. Unset Discord env leaves the site unchanged.

Reviewed-on: #14
Co-authored-by: codegirl-007 <s.raide@gmail.com>
This commit was merged in pull request #14.
This commit is contained in:
2026-08-29 18:24:28 +00:00
committed by codegirl007
parent b8f1d88d6e
commit 5aabc784de
16 changed files with 1037 additions and 2 deletions
+105
View File
@@ -0,0 +1,105 @@
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,
}
}