feat: social graph features
This commit is contained in:
@@ -52,6 +52,8 @@ type Server struct {
|
||||
bookmarkH *handler.BookmarkHandler
|
||||
collectionH *handler.CollectionHandler
|
||||
profileH *handler.ProfileHandler
|
||||
relationH *handler.RelationshipHandler
|
||||
notificationH *handler.NotificationHandler
|
||||
feedbackH *handler.FeedbackHandler
|
||||
statsH *handler.StatsHandler
|
||||
|
||||
@@ -184,10 +186,23 @@ func (s *Server) initAuth(cfg *config.Config, logger *zap.Logger) {
|
||||
} else {
|
||||
logger.Info("oauth: no providers configured — /auth/oauth/* will 404")
|
||||
}
|
||||
s.postH = handler.NewPostHandler(postSvc)
|
||||
s.bookmarkH = handler.NewBookmarkHandler(bookmarkSvc)
|
||||
s.collectionH = handler.NewCollectionHandler(collectionSvc, postRepo, issuer, userRepo)
|
||||
s.profileH = handler.NewProfileHandler(userRepo, postRepo, collectionRepo)
|
||||
|
||||
// Social graph: follows / blocks / reports, plus the notification
|
||||
// "plugin" seam (persists rows + emails today; websocket/push later).
|
||||
// All share the same Postgres pool + mailer. Reports reuse the
|
||||
// feedback admin recipients.
|
||||
followRepo := repository.NewFollowRepo(s.pg)
|
||||
blockRepo := repository.NewBlockRepo(s.pg)
|
||||
reportRepo := repository.NewReportRepo(s.pg)
|
||||
notifRepo := repository.NewNotificationRepo(s.pg)
|
||||
notifSvc := service.NewNotificationService(notifRepo, userRepo, mail, logger, cfg.FrontendURL)
|
||||
graphSvc := service.NewSocialGraphService(followRepo, blockRepo, reportRepo, postRepo, userRepo, notifSvc, mail, cfg.FeedbackAdminEmails, logger)
|
||||
s.relationH = handler.NewRelationshipHandler(graphSvc)
|
||||
s.notificationH = handler.NewNotificationHandler(notifSvc)
|
||||
s.postH = handler.NewPostHandler(postSvc, graphSvc)
|
||||
s.profileH = handler.NewProfileHandler(userRepo, postRepo, collectionRepo, graphSvc)
|
||||
|
||||
// Feedback + stats — share Postgres with the rest of /api/v1 and
|
||||
// the existing mailer instance.
|
||||
@@ -592,6 +607,8 @@ func (s *Server) setupRouter() {
|
||||
// double-clicks don't 4xx.
|
||||
r.Post("/{id}/reshare", s.postH.Reshare)
|
||||
r.Delete("/{id}/reshare", s.postH.Unreshare)
|
||||
// Report a post (category + optional reason) → admin email.
|
||||
r.Post("/{id}/report", s.postH.Report)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -646,9 +663,33 @@ func (s *Server) setupRouter() {
|
||||
r.HandleFunc("/*", s.authUnavailable)
|
||||
return
|
||||
}
|
||||
r.Use(middleware.OptionalAuth(s.authIssuer, s.userRepo))
|
||||
r.Get("/{handle}", s.profileH.Get)
|
||||
r.Get("/{handle}/feed", s.profileH.Feed)
|
||||
// Public reads (anonymous-OK; OptionalAuth populates viewer_id
|
||||
// so Get can include the viewer's relationship + the feed can
|
||||
// carry viewer_has_reshared).
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middleware.OptionalAuth(s.authIssuer, s.userRepo))
|
||||
r.Get("/{handle}", s.profileH.Get)
|
||||
r.Get("/{handle}/feed", s.profileH.Feed)
|
||||
})
|
||||
|
||||
// Relationship actions — follow/block/report. All require a
|
||||
// signed-in viewer; each returns the recomputed relationship
|
||||
// + the target's follower/following counts. Follows are
|
||||
// request-based: POST /follow creates a PENDING request that
|
||||
// the target later accepts or rejects.
|
||||
if s.relationH != nil {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middleware.RequireAuth(s.authIssuer, s.userRepo))
|
||||
r.Post("/{handle}/follow", s.relationH.Follow)
|
||||
r.Delete("/{handle}/follow", s.relationH.Unfollow)
|
||||
r.Post("/{handle}/follow/accept", s.relationH.AcceptFollow)
|
||||
r.Post("/{handle}/follow/reject", s.relationH.RejectFollow)
|
||||
r.Delete("/{handle}/follower", s.relationH.RemoveFollower)
|
||||
r.Post("/{handle}/block", s.relationH.Block)
|
||||
r.Delete("/{handle}/block", s.relationH.Unblock)
|
||||
r.Post("/{handle}/report", s.relationH.Report)
|
||||
})
|
||||
}
|
||||
})
|
||||
// URL Shortener — Postgres-backed, Redis-queued.
|
||||
// The handler short-circuits to 503 if Postgres or Redis are unavailable.
|
||||
@@ -681,9 +722,18 @@ func (s *Server) setupRouter() {
|
||||
r.Post("/feedback", s.feedbackH.Submit)
|
||||
})
|
||||
|
||||
// Push Notifications
|
||||
// Notifications — the signed-in user's in-app feed + unread badge.
|
||||
// Rows are persisted on every follow/accept event; websocket + push
|
||||
// delivery will layer on later. All protected.
|
||||
r.Route("/notifications", func(r chi.Router) {
|
||||
// TODO: register push notification handlers
|
||||
if s.notificationH == nil {
|
||||
r.HandleFunc("/*", s.authUnavailable)
|
||||
return
|
||||
}
|
||||
r.Use(middleware.RequireAuth(s.authIssuer, s.userRepo))
|
||||
r.Get("/", s.notificationH.List)
|
||||
r.Get("/unread-count", s.notificationH.UnreadCount)
|
||||
r.Post("/read", s.notificationH.MarkAllRead)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user