Files
paliad/internal/services/link_service.go
m e468930342 fix(t-paliad-077): /api/links/suggest 500 — switch to sqlx for paliad.link_*
The suggestion + feedback handlers wrote to legacy public-schema tables
(`patholo_link_suggestions`, `patholo_link_feedback`) via Supabase PostgREST.
The patHoLo→Paliad rebrand moved those tables into the paliad schema as
`paliad.link_suggestions` / `paliad.link_feedback` — PostgREST is not
configured to expose paliad on the youpc Supabase, so all three callsites
500'd in prod.

Replace the PostgREST integration with a new LinkService backed by the same
sqlx pool every other paliad service uses. Schema-qualified table names
work directly via DATABASE_URL, the inconsistent supabaseInsert/Count
helpers go away, and the suggestion/feedback handlers now use requireDB
for clean 503s when the pool isn't wired.

handleSuggestionCount keeps its tolerant 0-on-error behaviour so the admin
badge never blocks page render. When DATABASE_URL is unset the count
endpoint returns 0 instead of 503 — knowledge-platform-only deployments
still serve the Link Hub page.

Flagged in t-paliad-074 (F-12).
2026-04-30 03:18:03 +02:00

78 lines
2.4 KiB
Go

package services
import (
"context"
"fmt"
"github.com/jmoiron/sqlx"
)
// LinkService persists user-submitted Link Hub suggestions and feedback into
// paliad.link_suggestions / paliad.link_feedback. The earlier implementation
// posted these via Supabase PostgREST against a public-schema patholo_link_*
// table; that surface no longer exists after the rebrand to paliad schema, so
// writes go through the same sqlx pool every other paliad service uses.
type LinkService struct {
db *sqlx.DB
}
func NewLinkService(db *sqlx.DB) *LinkService {
return &LinkService{db: db}
}
// LinkSuggestionInput is the payload of a /api/links/suggest call.
// Description and SuggestedBy are optional — empty strings hit the column
// defaults set in the migration.
type LinkSuggestionInput struct {
Title string
URL string
Category string
Description string
SuggestedBy string
}
// InsertSuggestion creates a row with status='pending' so admins can triage
// before promoting the link into the curated hub.
func (s *LinkService) InsertSuggestion(ctx context.Context, in LinkSuggestionInput) error {
const q = `INSERT INTO paliad.link_suggestions
(title, url, category, description, suggested_by, status)
VALUES ($1, $2, $3, $4, $5, 'pending')`
if _, err := s.db.ExecContext(ctx, q,
in.Title, in.URL, in.Category, in.Description, in.SuggestedBy,
); err != nil {
return fmt.Errorf("insert link suggestion: %w", err)
}
return nil
}
// LinkFeedbackInput is the payload of a /api/links/feedback call.
type LinkFeedbackInput struct {
LinkID string
FeedbackType string
Message string
SubmittedBy string
}
func (s *LinkService) InsertFeedback(ctx context.Context, in LinkFeedbackInput) error {
const q = `INSERT INTO paliad.link_feedback
(link_id, feedback_type, message, submitted_by)
VALUES ($1, $2, $3, $4)`
if _, err := s.db.ExecContext(ctx, q,
in.LinkID, in.FeedbackType, in.Message, in.SubmittedBy,
); err != nil {
return fmt.Errorf("insert link feedback: %w", err)
}
return nil
}
// CountPendingSuggestions powers the admin badge on the Link Hub.
func (s *LinkService) CountPendingSuggestions(ctx context.Context) (int, error) {
var n int
if err := s.db.GetContext(ctx, &n,
`SELECT count(*) FROM paliad.link_suggestions WHERE status = 'pending'`,
); err != nil {
return 0, fmt.Errorf("count pending link suggestions: %w", err)
}
return n, nil
}