197 lines
7.9 KiB
Go
197 lines
7.9 KiB
Go
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"`
|
|
|
|
// Likes. LikesCount is the effective total = the denormalized
|
|
// posts.likes_count plus any still-pending Redis delta (hydrated by the
|
|
// like layer). ViewerHasLiked reflects the calling user's membership row;
|
|
// false for anonymous viewers.
|
|
LikesCount int `json:"likes_count"`
|
|
ViewerHasLiked bool `json:"viewer_has_liked"`
|
|
}
|
|
|
|
// 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"`
|
|
|
|
// Aggregation: for post_like, PostID is the liked post and ActorCount is
|
|
// how many likers have aggregated into this single row (the frontend
|
|
// renders "X and N-1 others liked your post"). Other kinds: PostID nil,
|
|
// ActorCount 1.
|
|
PostID *string `json:"post_id,omitempty"`
|
|
ActorCount int `json:"actor_count"`
|
|
|
|
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"`
|
|
}
|