gohorsejobs/backend/internal/api/handlers/chat_handlers.go
Tiago Yamamoto 841b1d780c feat: Email System, Avatar Upload, Email Templates UI, and Public Job Posting
- Backend: Email producer (LavinMQ), EmailService interface
- Backend: CRUD API for email_templates and email_settings
- Backend: avatar_url field in users table + UpdateMyProfile support
- Backend: StorageService for pre-signed URLs
- NestJS: Email consumer with Nodemailer and Handlebars
- Frontend: Email Templates admin pages (list/edit)
- Frontend: Updated profileApi.uploadAvatar with pre-signed URL flow
- Frontend: New /post-job public page (company registration + job creation wizard)
- Migrations: 027_create_email_system.sql, 028_add_avatar_url_to_users.sql
2025-12-26 12:21:34 -03:00

146 lines
4 KiB
Go

package handlers
import (
"encoding/json"
"net/http"
"github.com/rede5/gohorsejobs/backend/internal/api/middleware"
"github.com/rede5/gohorsejobs/backend/internal/services"
)
type ChatHandlers struct {
chatService *services.ChatService
}
func NewChatHandlers(s *services.ChatService) *ChatHandlers {
return &ChatHandlers{chatService: s}
}
// ListConversations lists all conversations for the authenticated user
// @Summary List Conversations
// @Description List chat conversations for candidate or company
// @Tags Chat
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {array} services.Conversation
// @Router /api/v1/conversations [get]
func (h *ChatHandlers) ListConversations(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
userID, _ := ctx.Value(middleware.ContextUserID).(string)
tenantID, _ := ctx.Value(middleware.ContextTenantID).(string)
roles := middleware.ExtractRoles(ctx.Value(middleware.ContextRoles))
if userID == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
isCandidate := false
for _, r := range roles {
if r == "candidate" || r == "CANDIDATE" {
isCandidate = true
break
}
}
// Logic: If user has company (tenantID), prefer company view?
// But a user might be both candidate and admin (unlikely in this domain model).
// If tenantID is present, assume acting as Company.
if tenantID != "" {
isCandidate = false
} else {
isCandidate = true
}
convs, err := h.chatService.ListConversations(ctx, userID, tenantID, isCandidate)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(convs)
}
// ListMessages lists messages in a conversation
// @Summary List Messages
// @Description Get message history for a conversation
// @Tags Chat
// @Accept json
// @Produce json
// @Param id path string true "Conversation ID"
// @Security BearerAuth
// @Success 200 {array} services.Message
// @Router /api/v1/conversations/{id}/messages [get]
func (h *ChatHandlers) ListMessages(w http.ResponseWriter, r *http.Request) {
conversationID := r.PathValue("id")
if conversationID == "" {
http.Error(w, "Conversation ID required", http.StatusBadRequest)
return
}
msgs, err := h.chatService.ListMessages(r.Context(), conversationID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Mark "IsMine"
userID, _ := r.Context().Value(middleware.ContextUserID).(string)
for i := range msgs {
if msgs[i].SenderID == userID {
msgs[i].IsMine = true
}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(msgs)
}
// SendMessage sends a new message
// @Summary Send Message
// @Description Send a message to a conversation
// @Tags Chat
// @Accept json
// @Produce json
// @Param id path string true "Conversation ID"
// @Param request body map[string]string true "Message Content"
// @Security BearerAuth
// @Success 200 {object} services.Message
// @Router /api/v1/conversations/{id}/messages [post]
func (h *ChatHandlers) SendMessage(w http.ResponseWriter, r *http.Request) {
conversationID := r.PathValue("id")
if conversationID == "" {
http.Error(w, "Conversation ID required", http.StatusBadRequest)
return
}
userID, _ := r.Context().Value(middleware.ContextUserID).(string)
if userID == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
var req struct {
Content string `json:"content"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid body", http.StatusBadRequest)
return
}
if req.Content == "" {
http.Error(w, "Content required", http.StatusBadRequest)
return
}
msg, err := h.chatService.SendMessage(r.Context(), userID, conversationID, req.Content)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
msg.IsMine = true
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(msg)
}