Initial commit: Эфир мессенджер
This commit is contained in:
41
internal/api/handlers/admin.go
Normal file
41
internal/api/handlers/admin.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"messenger/internal/api/responses"
|
||||
"messenger/internal/service"
|
||||
"net/http"
|
||||
|
||||
//"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type AdminHandler struct {
|
||||
adminService *service.AdminService
|
||||
}
|
||||
|
||||
func NewAdminHandler(adminService *service.AdminService) *AdminHandler {
|
||||
return &AdminHandler{
|
||||
adminService: adminService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AdminHandler) DeleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
// userID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
// if err != nil {
|
||||
// responses.BadRequest(w, "invalid user id")
|
||||
// return
|
||||
// }
|
||||
|
||||
// TODO: Реализовать удаление пользователя
|
||||
responses.Success(w, http.StatusOK, map[string]string{"message": "user deleted"})
|
||||
}
|
||||
|
||||
func (h *AdminHandler) DeleteMessage(w http.ResponseWriter, r *http.Request) {
|
||||
// messageID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
// if err != nil {
|
||||
// responses.BadRequest(w, "invalid message id")
|
||||
// return
|
||||
// }
|
||||
|
||||
// TODO: Реализовать удаление сообщения
|
||||
responses.Success(w, http.StatusOK, map[string]string{"message": "message deleted"})
|
||||
}
|
||||
80
internal/api/handlers/auth.go
Normal file
80
internal/api/handlers/auth.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"messenger/internal/api/middleware"
|
||||
"messenger/internal/api/responses"
|
||||
"messenger/internal/service"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
authService *service.AuthService
|
||||
}
|
||||
|
||||
func NewAuthHandler(authService *service.AuthService) *AuthHandler {
|
||||
return &AuthHandler{authService: authService}
|
||||
}
|
||||
|
||||
type RegisterRequest struct {
|
||||
Login string `json:"login"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Login string `json:"login"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type AuthResponse struct {
|
||||
Token string `json:"token"`
|
||||
User interface{} `json:"user"`
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
|
||||
var req RegisterRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
responses.BadRequest(w, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
user, token, err := h.authService.Register(r.Context(), req.Login, req.Password)
|
||||
if err != nil {
|
||||
responses.BadRequest(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusCreated, AuthResponse{
|
||||
Token: token,
|
||||
User: user.ToSafe(),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
var req LoginRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
responses.BadRequest(w, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
user, token, err := h.authService.Login(r.Context(), req.Login, req.Password)
|
||||
if err != nil {
|
||||
responses.Unauthorized(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusOK, AuthResponse{
|
||||
Token: token,
|
||||
User: user.ToSafe(),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUserFromContext(r.Context())
|
||||
if user == nil {
|
||||
responses.Unauthorized(w, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusOK, user.ToSafe())
|
||||
}
|
||||
228
internal/api/handlers/chats.go
Normal file
228
internal/api/handlers/chats.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"messenger/internal/api/middleware"
|
||||
"messenger/internal/api/responses"
|
||||
"messenger/internal/models"
|
||||
"messenger/internal/service"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type ChatHandler struct {
|
||||
chatService *service.ChatService
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
func NewChatHandler(chatService *service.ChatService, userService *service.UserService) *ChatHandler {
|
||||
return &ChatHandler{
|
||||
chatService: chatService,
|
||||
userService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ChatHandler) CreatePrivateChat(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUserFromContext(r.Context())
|
||||
if user == nil {
|
||||
responses.Unauthorized(w, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.CreatePrivateChatRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
responses.BadRequest(w, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if req.TargetLogin == "" {
|
||||
responses.BadRequest(w, "target_login is required")
|
||||
return
|
||||
}
|
||||
|
||||
// Находим целевого пользователя по логину
|
||||
targetUser, err := h.userService.GetUserByLogin(r.Context(), req.TargetLogin)
|
||||
if err != nil {
|
||||
responses.NotFound(w, "target user not found")
|
||||
return
|
||||
}
|
||||
|
||||
if targetUser == nil {
|
||||
responses.NotFound(w, "target user not found")
|
||||
return
|
||||
}
|
||||
|
||||
// Создаем приватный чат
|
||||
chat, err := h.chatService.CreatePrivateChat(r.Context(), user.ID, targetUser.ID)
|
||||
if err != nil {
|
||||
responses.InternalServerError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusCreated, chat)
|
||||
}
|
||||
|
||||
func (h *ChatHandler) CreateGroupChat(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUserFromContext(r.Context())
|
||||
if user == nil {
|
||||
responses.Unauthorized(w, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.CreateGroupChatRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
responses.BadRequest(w, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
chat, err := h.chatService.CreateGroupChat(r.Context(), user.ID, req.Title, req.MemberLogins)
|
||||
if err != nil {
|
||||
responses.BadRequest(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusCreated, chat)
|
||||
}
|
||||
|
||||
func (h *ChatHandler) GetMyChats(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUserFromContext(r.Context())
|
||||
if user == nil {
|
||||
responses.Unauthorized(w, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
chats, err := h.chatService.GetUserChatsWithDetails(r.Context(), user.ID)
|
||||
if err != nil {
|
||||
responses.InternalServerError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusOK, chats)
|
||||
}
|
||||
|
||||
func (h *ChatHandler) GetChatByID(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUserFromContext(r.Context())
|
||||
if user == nil {
|
||||
responses.Unauthorized(w, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
chatID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
responses.BadRequest(w, "invalid chat id")
|
||||
return
|
||||
}
|
||||
|
||||
chat, err := h.chatService.GetChatByID(r.Context(), chatID, user.ID)
|
||||
if err != nil {
|
||||
responses.NotFound(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusOK, chat)
|
||||
}
|
||||
|
||||
func (h *ChatHandler) GetChatMembers(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUserFromContext(r.Context())
|
||||
if user == nil {
|
||||
responses.Unauthorized(w, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
chatID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
responses.BadRequest(w, "invalid chat id")
|
||||
return
|
||||
}
|
||||
|
||||
members, err := h.chatService.GetChatMembers(r.Context(), chatID, user.ID)
|
||||
if err != nil {
|
||||
responses.Forbidden(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusOK, members)
|
||||
}
|
||||
|
||||
func (h *ChatHandler) AddMembers(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUserFromContext(r.Context())
|
||||
if user == nil {
|
||||
responses.Unauthorized(w, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
chatID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
responses.BadRequest(w, "invalid chat id")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.AddMembersRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
responses.BadRequest(w, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.chatService.AddMembers(r.Context(), chatID, user.ID, req.UserLogins); err != nil {
|
||||
responses.BadRequest(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusOK, map[string]string{"message": "members added"})
|
||||
}
|
||||
|
||||
func (h *ChatHandler) RemoveMember(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUserFromContext(r.Context())
|
||||
if user == nil {
|
||||
responses.Unauthorized(w, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
chatID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
responses.BadRequest(w, "invalid chat id")
|
||||
return
|
||||
}
|
||||
|
||||
memberID, err := strconv.ParseInt(chi.URLParam(r, "user_id"), 10, 64)
|
||||
if err != nil {
|
||||
responses.BadRequest(w, "invalid member id")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.chatService.RemoveMember(r.Context(), chatID, user.ID, memberID); err != nil {
|
||||
responses.BadRequest(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusOK, map[string]string{"message": "member removed"})
|
||||
}
|
||||
|
||||
func (h *ChatHandler) UpdateChatTitle(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUserFromContext(r.Context())
|
||||
if user == nil {
|
||||
responses.Unauthorized(w, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
chatID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
responses.BadRequest(w, "invalid chat id")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.UpdateChatTitleRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
responses.BadRequest(w, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.chatService.UpdateChatTitle(r.Context(), chatID, user.ID, req.Title); err != nil {
|
||||
responses.BadRequest(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusOK, map[string]string{"message": "chat title updated"})
|
||||
}
|
||||
82
internal/api/handlers/files.go
Normal file
82
internal/api/handlers/files.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"messenger/internal/api/middleware"
|
||||
"messenger/internal/api/responses"
|
||||
"messenger/internal/service"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type FileHandler struct {
|
||||
fileService *service.FileService
|
||||
}
|
||||
|
||||
func NewFileHandler(fileService *service.FileService) *FileHandler {
|
||||
return &FileHandler{
|
||||
fileService: fileService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *FileHandler) UploadFile(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUserFromContext(r.Context())
|
||||
if user == nil {
|
||||
responses.Unauthorized(w, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
chatID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
responses.BadRequest(w, "invalid chat id")
|
||||
return
|
||||
}
|
||||
|
||||
// Parse multipart form (20 MB max)
|
||||
if err := r.ParseMultipartForm(20 << 20); err != nil {
|
||||
responses.BadRequest(w, "failed to parse form")
|
||||
return
|
||||
}
|
||||
|
||||
file, header, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
responses.BadRequest(w, "file is required")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
attachment, err := h.fileService.UploadFile(r.Context(), chatID, user.ID, header)
|
||||
if err != nil {
|
||||
responses.BadRequest(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusOK, attachment)
|
||||
}
|
||||
|
||||
func (h *FileHandler) DownloadFile(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUserFromContext(r.Context())
|
||||
if user == nil {
|
||||
responses.Unauthorized(w, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
attachmentID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
responses.BadRequest(w, "invalid attachment id")
|
||||
return
|
||||
}
|
||||
|
||||
fileName, file, mimeType, err := h.fileService.DownloadFile(r.Context(), attachmentID, user.ID)
|
||||
if err != nil {
|
||||
responses.NotFound(w, err.Error())
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
w.Header().Set("Content-Type", mimeType)
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+strconv.Quote(fileName))
|
||||
http.ServeContent(w, r, fileName, time.Now(), file)
|
||||
}
|
||||
83
internal/api/handlers/messages.go
Normal file
83
internal/api/handlers/messages.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
//"encoding/json"
|
||||
"messenger/internal/api/middleware"
|
||||
"messenger/internal/api/responses"
|
||||
//"messenger/internal/models"
|
||||
"messenger/internal/service"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type MessageHandler struct {
|
||||
messageService *service.MessageService
|
||||
chatService *service.ChatService
|
||||
}
|
||||
|
||||
func NewMessageHandler(messageService *service.MessageService, chatService *service.ChatService) *MessageHandler {
|
||||
return &MessageHandler{
|
||||
messageService: messageService,
|
||||
chatService: chatService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *MessageHandler) GetMessages(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUserFromContext(r.Context())
|
||||
if user == nil {
|
||||
responses.Unauthorized(w, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
chatID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
responses.BadRequest(w, "invalid chat id")
|
||||
return
|
||||
}
|
||||
|
||||
limit := 50
|
||||
if l := r.URL.Query().Get("limit"); l != "" {
|
||||
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 100 {
|
||||
limit = parsed
|
||||
}
|
||||
}
|
||||
|
||||
var before time.Time
|
||||
if b := r.URL.Query().Get("before"); b != "" {
|
||||
if parsed, err := time.Parse(time.RFC3339, b); err == nil {
|
||||
before = parsed
|
||||
}
|
||||
}
|
||||
|
||||
messages, err := h.messageService.GetChatHistory(r.Context(), chatID, user.ID, limit, before)
|
||||
if err != nil {
|
||||
responses.InternalServerError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusOK, messages)
|
||||
}
|
||||
|
||||
func (h *MessageHandler) MarkAsRead(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUserFromContext(r.Context())
|
||||
if user == nil {
|
||||
responses.Unauthorized(w, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
messageID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
responses.BadRequest(w, "invalid message id")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.messageService.MarkMessageAsRead(r.Context(), messageID, user.ID); err != nil {
|
||||
responses.InternalServerError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusOK, map[string]string{"message": "marked as read"})
|
||||
}
|
||||
103
internal/api/handlers/users.go
Normal file
103
internal/api/handlers/users.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"messenger/internal/api/middleware"
|
||||
"messenger/internal/api/responses"
|
||||
"messenger/internal/models"
|
||||
"messenger/internal/service"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type UserHandler struct {
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
func NewUserHandler(userService *service.UserService) *UserHandler {
|
||||
return &UserHandler{userService: userService}
|
||||
}
|
||||
|
||||
type UpdateProfileRequest struct {
|
||||
DisplayName *string `json:"display_name,omitempty"`
|
||||
Bio *string `json:"bio,omitempty"`
|
||||
}
|
||||
|
||||
func (h *UserHandler) GetProfile(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUserFromContext(r.Context())
|
||||
if user == nil {
|
||||
responses.Unauthorized(w, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
profile, err := h.userService.GetProfile(r.Context(), user.ID)
|
||||
if err != nil {
|
||||
responses.NotFound(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusOK, profile)
|
||||
}
|
||||
|
||||
func (h *UserHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUserFromContext(r.Context())
|
||||
if user == nil {
|
||||
responses.Unauthorized(w, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateProfileRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
responses.BadRequest(w, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.userService.UpdateProfile(r.Context(), user.ID, req.DisplayName, req.Bio); err != nil {
|
||||
responses.BadRequest(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusOK, map[string]string{"message": "profile updated"})
|
||||
}
|
||||
|
||||
func (h *UserHandler) SearchUsers(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query().Get("q")
|
||||
if query == "" {
|
||||
responses.BadRequest(w, "search query required")
|
||||
return
|
||||
}
|
||||
|
||||
limit := 20
|
||||
if l := r.URL.Query().Get("limit"); l != "" {
|
||||
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 100 {
|
||||
limit = parsed
|
||||
}
|
||||
}
|
||||
|
||||
users, err := h.userService.SearchUsers(r.Context(), query, limit)
|
||||
if err != nil {
|
||||
responses.InternalServerError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses.Success(w, http.StatusOK, users)
|
||||
}
|
||||
|
||||
func (h *UserHandler) GetUserByID(w http.ResponseWriter, r *http.Request) {
|
||||
// Реализация с chi.URLParam будет в main.go
|
||||
// Пока заглушка
|
||||
responses.Success(w, http.StatusOK, nil)
|
||||
}
|
||||
|
||||
// GetUserByLoginFromContext - метод для получения пользователя по логину (используется в других хендлерах)
|
||||
func (h *UserHandler) GetUserByLogin(ctx context.Context, login string) (*models.SafeUser, error) {
|
||||
user, err := h.userService.GetUserByLogin(ctx, login)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return user.ToSafe(), nil
|
||||
}
|
||||
58
internal/api/handlers/websocket.go
Normal file
58
internal/api/handlers/websocket.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
gorillaWS "github.com/gorilla/websocket"
|
||||
|
||||
"messenger/internal/pkg/logger"
|
||||
"messenger/internal/service"
|
||||
ws "messenger/internal/websocket"
|
||||
)
|
||||
|
||||
var upgrader = gorillaWS.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
type WebSocketHandler struct {
|
||||
hub *ws.Hub
|
||||
authService *service.AuthService
|
||||
}
|
||||
|
||||
func NewWebSocketHandler(hub *ws.Hub, authService *service.AuthService) *WebSocketHandler {
|
||||
return &WebSocketHandler{
|
||||
hub: hub,
|
||||
authService: authService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WebSocketHandler) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.URL.Query().Get("token")
|
||||
if token == "" {
|
||||
http.Error(w, "missing token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.authService.ValidateToken(token)
|
||||
if err != nil {
|
||||
logger.Error("WebSocket auth failed", "error", err)
|
||||
http.Error(w, "invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("WebSocket connecting", "user_id", user.ID, "login", user.Login)
|
||||
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
logger.Error("WebSocket upgrade failed", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
client := ws.NewClient(h.hub, conn, user)
|
||||
h.hub.GetRegisterChan() <- client
|
||||
|
||||
go client.WritePump()
|
||||
client.ReadPump()
|
||||
}
|
||||
66
internal/api/middleware/auth.go
Normal file
66
internal/api/middleware/auth.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"messenger/internal/api/responses"
|
||||
"messenger/internal/models"
|
||||
"messenger/internal/service"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const UserContextKey contextKey = "user"
|
||||
|
||||
func JWTAuth(authService *service.AuthService) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Получаем токен из заголовка Authorization
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
responses.Unauthorized(w, "missing authorization header")
|
||||
return
|
||||
}
|
||||
|
||||
// Проверяем формат Bearer token
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
|
||||
responses.Unauthorized(w, "invalid authorization header format")
|
||||
return
|
||||
}
|
||||
|
||||
token := parts[1]
|
||||
|
||||
// Валидируем токен
|
||||
user, err := authService.ValidateToken(token)
|
||||
if err != nil {
|
||||
responses.Unauthorized(w, "invalid or expired token")
|
||||
return
|
||||
}
|
||||
|
||||
// Сохраняем пользователя в контексте
|
||||
ctx := context.WithValue(r.Context(), UserContextKey, user)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func GetUserFromContext(ctx context.Context) *models.User {
|
||||
user, ok := ctx.Value(UserContextKey).(*models.User)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
func RequireGlobalAdmin(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
user := GetUserFromContext(r.Context())
|
||||
if user == nil || !user.IsGlobalAdmin() {
|
||||
responses.Forbidden(w, "global admin access required")
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
38
internal/api/middleware/cors.go
Normal file
38
internal/api/middleware/cors.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
//"strings"
|
||||
)
|
||||
|
||||
func CORS(allowedOrigins []string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
origin := r.Header.Get("Origin")
|
||||
|
||||
// Проверяем разрешен ли origin
|
||||
allowed := false
|
||||
for _, o := range allowedOrigins {
|
||||
if o == "*" || o == origin {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if allowed {
|
||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
}
|
||||
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
|
||||
if r.Method == "OPTIONS" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
36
internal/api/middleware/logging.go
Normal file
36
internal/api/middleware/logging.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Logging(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
|
||||
// Оборачиваем ResponseWriter для захвата статуса
|
||||
wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
|
||||
|
||||
next.ServeHTTP(wrapped, r)
|
||||
|
||||
slog.Info("HTTP request",
|
||||
"method", r.Method,
|
||||
"path", r.URL.Path,
|
||||
"status", wrapped.statusCode,
|
||||
"duration", time.Since(start),
|
||||
"remote_addr", r.RemoteAddr,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
type responseWriter struct {
|
||||
http.ResponseWriter
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (rw *responseWriter) WriteHeader(code int) {
|
||||
rw.statusCode = code
|
||||
rw.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
23
internal/api/middleware/recovery.go
Normal file
23
internal/api/middleware/recovery.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
func Recovery(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
slog.Error("panic recovered",
|
||||
"error", err,
|
||||
"stack", string(debug.Stack()),
|
||||
"path", r.URL.Path,
|
||||
)
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
54
internal/api/responses/json.go
Normal file
54
internal/api/responses/json.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Success bool `json:"success"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Code int `json:"code,omitempty"`
|
||||
}
|
||||
|
||||
func JSON(w http.ResponseWriter, statusCode int, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
json.NewEncoder(w).Encode(data)
|
||||
}
|
||||
|
||||
func Success(w http.ResponseWriter, statusCode int, data interface{}) {
|
||||
JSON(w, statusCode, Response{
|
||||
Success: true,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
func Error(w http.ResponseWriter, statusCode int, errMsg string) {
|
||||
JSON(w, statusCode, Response{
|
||||
Success: false,
|
||||
Error: errMsg,
|
||||
Code: statusCode,
|
||||
})
|
||||
}
|
||||
|
||||
func BadRequest(w http.ResponseWriter, errMsg string) {
|
||||
Error(w, http.StatusBadRequest, errMsg)
|
||||
}
|
||||
|
||||
func Unauthorized(w http.ResponseWriter, errMsg string) {
|
||||
Error(w, http.StatusUnauthorized, errMsg)
|
||||
}
|
||||
|
||||
func Forbidden(w http.ResponseWriter, errMsg string) {
|
||||
Error(w, http.StatusForbidden, errMsg)
|
||||
}
|
||||
|
||||
func NotFound(w http.ResponseWriter, errMsg string) {
|
||||
Error(w, http.StatusNotFound, errMsg)
|
||||
}
|
||||
|
||||
func InternalServerError(w http.ResponseWriter, errMsg string) {
|
||||
Error(w, http.StatusInternalServerError, errMsg)
|
||||
}
|
||||
Reference in New Issue
Block a user