feat: add post mention
This commit is contained in:
+103
-24
@@ -59,10 +59,11 @@ func (h *PostHandler) Report(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
type createPostReq struct {
|
||||
Caption string `json:"caption"`
|
||||
SourceURL string `json:"source_url"`
|
||||
SourceTitle string `json:"source_title"`
|
||||
ImageURLs []string `json:"image_urls"`
|
||||
Caption string `json:"caption"`
|
||||
SourceURL string `json:"source_url"`
|
||||
SourceTitle string `json:"source_title"`
|
||||
ImageURLs []string `json:"image_urls"`
|
||||
MentionUserIDs []string `json:"mention_user_ids"`
|
||||
}
|
||||
|
||||
func (h *PostHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -79,7 +80,7 @@ func (h *PostHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
response.Error(w, http.StatusBadRequest, v.Error())
|
||||
return
|
||||
}
|
||||
p, err := h.svc.Create(r.Context(), uid, req.Caption, req.SourceURL, req.SourceTitle, req.ImageURLs)
|
||||
p, err := h.svc.Create(r.Context(), uid, req.Caption, req.SourceURL, req.SourceTitle, req.ImageURLs, req.MentionUserIDs)
|
||||
if err != nil {
|
||||
mapSocialErr(w, err)
|
||||
return
|
||||
@@ -87,6 +88,60 @@ func (h *PostHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
response.JSON(w, http.StatusCreated, p)
|
||||
}
|
||||
|
||||
// MentionSuggest — GET /posts/mention-suggest?q=&limit=&offset=. Prioritised
|
||||
// user suggestions for the composer (following → followers → others).
|
||||
func (h *PostHandler) MentionSuggest(w http.ResponseWriter, r *http.Request) {
|
||||
uid := middleware.UserID(r.Context())
|
||||
limit, offset := parseLimitOffset(r)
|
||||
q := strings.TrimSpace(r.URL.Query().Get("q"))
|
||||
users, err := h.svc.SuggestMentions(r.Context(), uid, q, limit, offset)
|
||||
if err != nil {
|
||||
mapSocialErr(w, err)
|
||||
return
|
||||
}
|
||||
if users == nil {
|
||||
users = []model.UserSummary{}
|
||||
}
|
||||
response.JSON(w, http.StatusOK, users)
|
||||
}
|
||||
|
||||
// ListMentions — GET /posts/{id}/mentions. The users tagged on a post. Public.
|
||||
func (h *PostHandler) ListMentions(w http.ResponseWriter, r *http.Request) {
|
||||
users, err := h.svc.Mentions(r.Context(), chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
mapSocialErr(w, err)
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, users)
|
||||
}
|
||||
|
||||
// AddMention — POST /posts/{id}/mentions { user_id }. Author only.
|
||||
func (h *PostHandler) AddMention(w http.ResponseWriter, r *http.Request) {
|
||||
uid := middleware.UserID(r.Context())
|
||||
var req struct {
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || strings.TrimSpace(req.UserID) == "" {
|
||||
response.Error(w, http.StatusBadRequest, "missing user_id")
|
||||
return
|
||||
}
|
||||
if err := h.svc.AddMention(r.Context(), uid, chi.URLParam(r, "id"), req.UserID); err != nil {
|
||||
mapSocialErr(w, err)
|
||||
return
|
||||
}
|
||||
response.NoContent(w)
|
||||
}
|
||||
|
||||
// RemoveMention — DELETE /posts/{id}/mentions/{userID}. Author only.
|
||||
func (h *PostHandler) RemoveMention(w http.ResponseWriter, r *http.Request) {
|
||||
uid := middleware.UserID(r.Context())
|
||||
if err := h.svc.RemoveMention(r.Context(), uid, chi.URLParam(r, "id"), chi.URLParam(r, "userID")); err != nil {
|
||||
mapSocialErr(w, err)
|
||||
return
|
||||
}
|
||||
response.NoContent(w)
|
||||
}
|
||||
|
||||
// Reshare materialises a reshare of the post identified by {id} as a
|
||||
// new posts row owned by the caller. Idempotent — second call returns
|
||||
// the same row (still 201). The body is the new (or existing) post so
|
||||
@@ -489,10 +544,11 @@ type ProfileHandler struct {
|
||||
collections *repository.CollectionRepo
|
||||
graph *service.SocialGraphService
|
||||
likes *repository.LikeRepo
|
||||
mentions *repository.MentionRepo
|
||||
}
|
||||
|
||||
func NewProfileHandler(users *repository.UserRepo, posts *repository.PostRepo, collections *repository.CollectionRepo, graph *service.SocialGraphService, likes *repository.LikeRepo) *ProfileHandler {
|
||||
return &ProfileHandler{users: users, posts: posts, collections: collections, graph: graph, likes: likes}
|
||||
func NewProfileHandler(users *repository.UserRepo, posts *repository.PostRepo, collections *repository.CollectionRepo, graph *service.SocialGraphService, likes *repository.LikeRepo, mentions *repository.MentionRepo) *ProfileHandler {
|
||||
return &ProfileHandler{users: users, posts: posts, collections: collections, graph: graph, likes: likes, mentions: mentions}
|
||||
}
|
||||
|
||||
func (h *ProfileHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -550,21 +606,48 @@ func (h *ProfileHandler) Feed(w http.ResponseWriter, r *http.Request) {
|
||||
mapSocialErr(w, err)
|
||||
return
|
||||
}
|
||||
if err := h.hydrateFeed(r, posts); err != nil {
|
||||
mapSocialErr(w, err)
|
||||
return
|
||||
}
|
||||
response.JSONWithMeta(w, http.StatusOK, posts, paginationMeta(total, limit, offset))
|
||||
}
|
||||
|
||||
// MentionsFeed — GET /u/{handle}/mentions?limit=&offset=. Posts that tag this
|
||||
// user, newest first. Public (OptionalAuth) with viewer-state hydration.
|
||||
func (h *ProfileHandler) MentionsFeed(w http.ResponseWriter, r *http.Request) {
|
||||
handle := chi.URLParam(r, "handle")
|
||||
limit, offset := parseLimitOffset(r)
|
||||
posts, total, err := h.posts.ListMentioningByHandle(r.Context(), handle, limit, offset)
|
||||
if err != nil {
|
||||
mapSocialErr(w, err)
|
||||
return
|
||||
}
|
||||
if err := h.hydrateFeed(r, posts); err != nil {
|
||||
mapSocialErr(w, err)
|
||||
return
|
||||
}
|
||||
response.JSONWithMeta(w, http.StatusOK, posts, paginationMeta(total, limit, offset))
|
||||
}
|
||||
|
||||
// hydrateFeed fills viewer reshare state + like counts/state + mention counts
|
||||
// across a page of posts. Shared by the profile feed and the mentions feed.
|
||||
func (h *ProfileHandler) hydrateFeed(r *http.Request, posts []model.Post) error {
|
||||
if len(posts) == 0 {
|
||||
return nil
|
||||
}
|
||||
viewerID := middleware.UserID(r.Context())
|
||||
if viewerID != "" && len(posts) > 0 {
|
||||
if viewerID != "" {
|
||||
ids := make([]string, 0, len(posts))
|
||||
for i := range posts {
|
||||
// Skip own posts — can't reshare yourself, and the swap
|
||||
// already triggers on isOwner.
|
||||
if posts[i].UserID != viewerID {
|
||||
if posts[i].UserID != viewerID { // can't reshare your own post
|
||||
ids = append(ids, posts[i].ID)
|
||||
}
|
||||
}
|
||||
if len(ids) > 0 {
|
||||
states, err := h.posts.ReshareStatesFor(r.Context(), viewerID, ids)
|
||||
if err != nil {
|
||||
mapSocialErr(w, err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
for i := range posts {
|
||||
if states[posts[i].ID] {
|
||||
@@ -573,18 +656,14 @@ func (h *ProfileHandler) Feed(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Like counts (effective) + viewer-liked state for every post in the page.
|
||||
if len(posts) > 0 {
|
||||
ptrs := make([]*model.Post, len(posts))
|
||||
for i := range posts {
|
||||
ptrs[i] = &posts[i]
|
||||
}
|
||||
if err := h.likes.Hydrate(r.Context(), viewerID, ptrs); err != nil {
|
||||
mapSocialErr(w, err)
|
||||
return
|
||||
}
|
||||
ptrs := make([]*model.Post, len(posts))
|
||||
for i := range posts {
|
||||
ptrs[i] = &posts[i]
|
||||
}
|
||||
response.JSONWithMeta(w, http.StatusOK, posts, paginationMeta(total, limit, offset))
|
||||
if err := h.likes.Hydrate(r.Context(), viewerID, ptrs); err != nil {
|
||||
return err
|
||||
}
|
||||
return h.mentions.HydrateCounts(r.Context(), ptrs)
|
||||
}
|
||||
|
||||
// followListResp is the payload for the followers / following list endpoints:
|
||||
|
||||
Reference in New Issue
Block a user