···
+
"Coves/internal/core/posts"
+
type postgresPostRepo struct {
+
// NewPostRepository creates a new PostgreSQL post repository
+
func NewPostRepository(db *sql.DB) posts.Repository {
+
return &postgresPostRepo{db: db}
+
// Create inserts a new post into the posts table
+
// Called by Jetstream consumer after post is created on PDS
+
func (r *postgresPostRepo) Create(ctx context.Context, post *posts.Post) error {
+
// Serialize JSON fields for storage
+
var facetsJSON, embedJSON sql.NullString
+
if post.ContentFacets != nil {
+
facetsJSON.String = *post.ContentFacets
+
facetsJSON.Valid = true
+
embedJSON.String = *post.Embed
+
// Convert content labels to PostgreSQL array
+
var labelsArray pq.StringArray
+
if post.ContentLabels != nil {
+
// Parse JSON array string to []string
+
if err := json.Unmarshal([]byte(*post.ContentLabels), &labels); err == nil {
+
uri, cid, rkey, author_did, community_did,
+
title, content, content_facets, embed, content_labels,
+
RETURNING id, indexed_at
+
err := r.db.QueryRowContext(
+
post.URI, post.CID, post.RKey, post.AuthorDID, post.CommunityDID,
+
post.Title, post.Content, facetsJSON, embedJSON, labelsArray,
+
).Scan(&post.ID, &post.IndexedAt)
+
// Check for duplicate URI (post already indexed)
+
if strings.Contains(err.Error(), "duplicate key") && strings.Contains(err.Error(), "posts_uri_key") {
+
return fmt.Errorf("post already indexed: %s", post.URI)
+
// Check for foreign key violations
+
if strings.Contains(err.Error(), "violates foreign key constraint") {
+
if strings.Contains(err.Error(), "fk_author") {
+
return fmt.Errorf("author DID not found: %s", post.AuthorDID)
+
if strings.Contains(err.Error(), "fk_community") {
+
return fmt.Errorf("community DID not found: %s", post.CommunityDID)
+
return fmt.Errorf("failed to insert post: %w", err)
+
// GetByURI retrieves a post by its AT-URI
+
// Used for E2E test verification and future GET endpoint
+
func (r *postgresPostRepo) GetByURI(ctx context.Context, uri string) (*posts.Post, error) {
+
id, uri, cid, rkey, author_did, community_did,
+
title, content, content_facets, embed, content_labels,
+
created_at, edited_at, indexed_at, deleted_at,
+
upvote_count, downvote_count, score, comment_count
+
var facetsJSON, embedJSON sql.NullString
+
var contentLabels pq.StringArray
+
err := r.db.QueryRowContext(ctx, query, uri).Scan(
+
&post.ID, &post.URI, &post.CID, &post.RKey,
+
&post.AuthorDID, &post.CommunityDID,
+
&post.Title, &post.Content, &facetsJSON, &embedJSON, &contentLabels,
+
&post.CreatedAt, &post.EditedAt, &post.IndexedAt, &post.DeletedAt,
+
&post.UpvoteCount, &post.DownvoteCount, &post.Score, &post.CommentCount,
+
if err == sql.ErrNoRows {
+
return nil, posts.ErrNotFound
+
return nil, fmt.Errorf("failed to get post by URI: %w", err)
+
// Convert SQL types back to Go types
+
post.ContentFacets = &facetsJSON.String
+
post.Embed = &embedJSON.String
+
if len(contentLabels) > 0 {
+
labelsJSON, marshalErr := json.Marshal(contentLabels)
+
labelsStr := string(labelsJSON)
+
post.ContentLabels = &labelsStr