feat: add chat

This commit is contained in:
Krispire Admin
2026-08-29 03:49:30 +05:30
parent 3b10e726b7
commit d50031cdb3
21 changed files with 6960 additions and 11 deletions
+53 -6
View File
@@ -69,6 +69,7 @@ type Server struct {
relationH *handler.RelationshipHandler
soketiWebhookH *handler.SoketiWebhookHandler
broadcastAuthH *handler.BroadcastingAuthHandler
chatH *handler.ChatHandler
notificationH *handler.NotificationHandler
sadhnaH *handler.SadhnaHandler
pushH *handler.PushHandler
@@ -112,10 +113,10 @@ func New(cfg *config.Config, logger *zap.Logger) *Server {
discipleRepo: discipleRepo,
// Initialize handlers
bookH: handler.NewBookHandler(bookRepo),
letterH: handler.NewLetterHandler(letterRepo),
essayH: handler.NewEssayHandler(essayRepo),
glossaryH: handler.NewGlossaryHandler(glossaryRepo),
bookH: handler.NewBookHandler(bookRepo),
letterH: handler.NewLetterHandler(letterRepo),
essayH: handler.NewEssayHandler(essayRepo),
glossaryH: handler.NewGlossaryHandler(glossaryRepo),
// userRepo half is attached later in initAuth (Postgres isn't up yet).
discipleH: handler.NewDiscipleHandler(discipleRepo, nil),
journalH: handler.NewJournalHandler(journalRepo),
@@ -258,10 +259,16 @@ func (s *Server) initAuth(cfg *config.Config, logger *zap.Logger) {
cfg.SoketiAppID, cfg.SoketiAppKey, cfg.SoketiAppSecret,
cfg.SoketiHost, cfg.SoketiPort, cfg.SoketiUseTLS, logger,
)
// Chat — DMs, groups and the (still coming-soon) Kautilya threads.
chatRepo := repository.NewChatRepo(s.pg)
chatSvc := service.NewChatService(chatRepo, blockRepo, rtClient, r2, s.redis, logger)
s.chatH = handler.NewChatHandler(chatSvc)
// Channel authorization for private-* subscriptions. Rules are explicit
// and default-deny; chat conversation channels get added here.
// and default-deny — an unlisted channel is refused.
s.broadcastAuthH = handler.NewBroadcastingAuthHandler(rtClient, logger,
handler.NotificationsRule(),
handler.ConversationRule(chatSvc.IsParticipant),
)
pushRepo := repository.NewPushRepo(s.pg)
@@ -296,7 +303,7 @@ func (s *Server) initAuth(cfg *config.Config, logger *zap.Logger) {
// every 15 minutes (no-op when Redis is unavailable).
worker.NewLikesWorker(likeRepo, logger).Run(wctx)
graphSvc := service.NewSocialGraphService(followRepo, blockRepo, reportRepo, postRepo, userRepo, notifSvc, mail, cfg.FeedbackAdminEmails, logger)
graphSvc := service.NewSocialGraphService(followRepo, blockRepo, reportRepo, postRepo, userRepo, chatRepo, notifSvc, mail, cfg.FeedbackAdminEmails, logger)
s.relationH = handler.NewRelationshipHandler(graphSvc)
s.notificationH = handler.NewNotificationHandler(notifSvc)
s.postH = handler.NewPostHandler(postSvc, graphSvc)
@@ -895,6 +902,46 @@ func (s *Server) setupRouter() {
r.Post("/webhooks/soketi", s.soketiWebhookH.Handle)
}
// Chat — inbox, threads, messages. Every route requires a session;
// membership is re-checked per request in the service, so a
// conversation id on its own grants nothing.
if s.chatH != nil {
r.Route("/chat", func(r chi.Router) {
r.Use(middleware.RequireAuth(s.authIssuer, s.userRepo))
r.Get("/conversations", s.chatH.Inbox)
r.Post("/conversations/dm", s.chatH.OpenDM)
r.Post("/conversations/group", s.chatH.CreateGroup)
r.Post("/conversations/kautilya", s.chatH.OpenAIThread)
r.Post("/link-preview", s.chatH.LinkPreview)
r.Get("/settings", s.chatH.Settings)
r.Patch("/settings", s.chatH.UpdateSettings)
r.Get("/attachments/{attachmentID}", s.chatH.Attachment)
r.Route("/conversations/{id}", func(r chi.Router) {
r.Get("/", s.chatH.Detail)
r.Patch("/", s.chatH.UpdateGroup)
r.Post("/members", s.chatH.AddMembers)
r.Delete("/members/{handle}", s.chatH.RemoveMember)
r.Patch("/members/{handle}", s.chatH.SetMemberRole)
r.Get("/messages", s.chatH.Messages)
r.Post("/messages", s.chatH.Send)
r.Post("/read", s.chatH.MarkRead)
r.Post("/delivered", s.chatH.MarkDelivered)
r.Get("/receipts", s.chatH.Receipts)
r.Post("/mute", s.chatH.SetMute)
r.Post("/attachments/presign", s.chatH.PresignAttachment)
r.Post("/avatar/presign", s.chatH.PresignGroupAvatar)
r.Get("/avatar", s.chatH.GroupAvatar)
r.Get("/gallery", s.chatH.Gallery)
r.Get("/starred", s.chatH.Starred)
r.Post("/messages/{messageID}/star", s.chatH.Star)
r.Delete("/messages/{messageID}/star", s.chatH.Unstar)
r.Delete("/messages/{messageID}", s.chatH.DeleteMessage)
r.Post("/messages/{messageID}/report", s.chatH.ReportMessage)
r.Post("/forward", s.chatH.Forward)
})
})
}
// Pusher channel authorization — pusher-js posts here (as its
// `authEndpoint`) before subscribing to a private-/presence- channel.
// Session-authenticated: the signature is only minted for a caller