package model import "time" // Post is a public quote on a user's feed. Caption is the verbatim selected // text from the knowledgebase; SourceURL links back to the page the user // was reading. Images are loaded separately via PostImage rows. // // Reshares are real Post rows owned by the resharer, with // ResharedFromPostID pointing to the canonical original. Likes, comments, // and images all hang off the post's own id, so they diverge naturally // across reshare boundaries — no parallel "per-resharer" tables. type Post struct { ID string `json:"id"` UserID string `json:"user_id"` Caption string `json:"caption"` SourceURL string `json:"source_url"` SourceTitle string `json:"source_title"` Images []PostImage `json:"images"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` // Author info — populated by repo joins for feed listings. AuthorName string `json:"author_name,omitempty"` AuthorHandle string `json:"author_handle,omitempty"` AuthorAvatarURL *string `json:"author_avatar_url,omitempty"` // Reshare attribution. ResharedFromPostID is non-nil when this post // is a reshare; the four `Original*` fields carry the canonical // post's author info so the frontend can render a // "Reshared from @handle" link without an extra round-trip. ResharedFromPostID *string `json:"reshared_from_post_id,omitempty"` OriginalAuthorName *string `json:"original_author_name,omitempty"` OriginalAuthorHandle *string `json:"original_author_handle,omitempty"` OriginalAuthorAvatar *string `json:"original_author_avatar_url,omitempty"` // ViewerHasReshared is true when the calling user has reshared the // canonical version of this post. Hydrated by the service layer // after the repo returns posts; always false for anonymous viewers. // Drives the post-card "Reshare ↔ Send" swap on the frontend. ViewerHasReshared bool `json:"viewer_has_reshared"` } // PostImage represents one attached image on a post. type PostImage struct { ID string `json:"id"` URL string `json:"url"` Position int `json:"position"` } // Bookmark is a private "save my place" marker. type Bookmark struct { ID string `json:"id"` UserID string `json:"user_id"` SourceURL string `json:"source_url"` SourceTitle string `json:"source_title"` Excerpt *string `json:"excerpt,omitempty"` Note *string `json:"note,omitempty"` CreatedAt time.Time `json:"created_at"` } // CollectionRecord is a user-curated group of posts. // // Renamed to avoid colliding with the legacy `Collection` model in user.go // from when bookmarks/collections were sketched out earlier. Once that old // model is unused we can drop it; for now this keeps both compiling. type CollectionRecord struct { ID string `json:"id"` UserID string `json:"user_id"` Title string `json:"title"` Description *string `json:"description,omitempty"` CoverURL *string `json:"cover_url,omitempty"` IsPublic bool `json:"is_public"` ItemCount int `json:"item_count"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // PublicProfile is what /u/{handle} returns — the parts of a User safe to // expose to anyone, plus aggregate counts and the user's posts. type PublicProfile struct { Handle string `json:"handle"` Name string `json:"name"` AvatarURL *string `json:"avatar_url"` JoinedAt time.Time `json:"joined_at"` PostCount int `json:"post_count"` BookmarkCount int `json:"-"` // never serialized — private } // RelationshipState is the viewer's relationship to a target profile, // computed fresh per request from the follows + blocks tables. Every // field is "from the viewer looking at the target": // // - Following: viewer → target follow edge is accepted. // - FollowRequested: viewer → target follow edge is pending (awaiting // the target's approval). The "Requested" button state. // - FollowsYou: target → viewer follow edge is accepted (they // already follow the viewer). Drives the "Follow back" label and // the "Remove from followers" menu item. // - RequestsYou: target → viewer follow edge is pending (the target // asked to follow the viewer). Drives the Accept / Reject buttons. // - Blocked: viewer has blocked the target. // - BlockedBy: target has blocked the viewer. // // nil (not this struct's zero value) is returned for anonymous viewers // and for a user viewing their own profile — callers special-case those. type RelationshipState struct { Following bool `json:"following"` FollowRequested bool `json:"follow_requested"` FollowsYou bool `json:"follows_you"` RequestsYou bool `json:"requests_you"` Blocked bool `json:"blocked"` BlockedBy bool `json:"blocked_by"` } // Follow is one row of the follows table — a directed, request-based // edge from Follower to Following. Status is "pending" until the target // accepts. type Follow struct { ID string `json:"id"` FollowerID string `json:"follower_id"` FollowingID string `json:"following_id"` Status string `json:"status"` CreatedAt time.Time `json:"created_at"` RespondedAt *time.Time `json:"responded_at,omitempty"` } // UserBlock is one row of user_blocks — Blocker has blocked Blocked. type UserBlock struct { ID string `json:"id"` BlockerID string `json:"blocker_id"` BlockedID string `json:"blocked_id"` CreatedAt time.Time `json:"created_at"` } // UserReport is one row of user_reports — Reporter flagged Reported. type UserReport struct { ID string `json:"id"` ReporterID string `json:"reporter_id"` ReportedID string `json:"reported_id"` Category string `json:"category"` Reason string `json:"reason"` CreatedAt time.Time `json:"created_at"` ResolvedAt *time.Time `json:"resolved_at,omitempty"` } // PostReport is one row of post_reports — Reporter flagged a specific // post. Reason is optional ("" when none given). type PostReport struct { ID string `json:"id"` ReporterID string `json:"reporter_id"` PostID string `json:"post_id"` Category string `json:"category"` Reason string `json:"reason"` CreatedAt time.Time `json:"created_at"` ResolvedAt *time.Time `json:"resolved_at,omitempty"` } // Notification is one delivered/queued notification for a recipient // (UserID). ActorID is who triggered it; Kind is a short machine string. // Hydrated Actor* fields are filled by repo joins for list rendering. type Notification struct { ID string `json:"id"` UserID string `json:"user_id"` ActorID *string `json:"actor_id,omitempty"` Kind string `json:"kind"` CreatedAt time.Time `json:"created_at"` ReadAt *time.Time `json:"read_at,omitempty"` ActorName *string `json:"actor_name,omitempty"` ActorHandle *string `json:"actor_handle,omitempty"` ActorAvatar *string `json:"actor_avatar_url,omitempty"` } // PushSubscription is one browser/device Web Push endpoint a user granted. type PushSubscription struct { ID string `json:"id"` UserID string `json:"user_id"` Endpoint string `json:"endpoint"` P256dh string `json:"p256dh"` Auth string `json:"auth"` }