v0.0.18 Удален текущий сервис messages и реализован новый на базе микросервиса gRPC
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
madipo2611 2025-08-12 20:20:39 +03:00
parent aa623ff1c4
commit dcb1cccb52
18 changed files with 2639 additions and 1442 deletions

1
.env
View File

@ -11,3 +11,4 @@ SMTP_USERNAME=info@tailly.ru
SMTP_PASSWORD="U8c64CyoD928cqij"
SMTP_FROM=info@tailly.ru
AppURL="https://tailly.ru"
MESSAGE_SERVICE_ADDRESS="localhost:50051"

View File

@ -2,6 +2,7 @@ package main
import (
"context"
"google.golang.org/grpc"
"log"
"os"
"os/signal"
@ -10,9 +11,9 @@ import (
"tailly_back_v2/internal/http"
"tailly_back_v2/internal/repository"
"tailly_back_v2/internal/service"
"tailly_back_v2/internal/ws"
"tailly_back_v2/pkg/auth"
"tailly_back_v2/pkg/database"
"tailly_back_v2/proto"
"time"
)
@ -30,6 +31,14 @@ func main() {
}
defer db.Close()
// Подключение к gRPC серверу сообщений
grpcConn, err := grpc.Dial(cfg.GRPC.MessageServiceAddress, grpc.WithInsecure())
if err != nil {
log.Fatalf("failed to connect to messages gRPC service: %v", err)
}
defer grpcConn.Close()
messageClient := proto.NewMessageServiceClient(grpcConn)
// Инициализация зависимостей
tokenAuth := auth.NewTokenAuth(
cfg.Auth.AccessTokenSecret,
@ -38,16 +47,11 @@ func main() {
cfg.Auth.RefreshTokenExpiry,
)
// Инициализация чата
chatHub := ws.NewHub()
go chatHub.Run()
// Репозитории
userRepo := repository.NewUserRepository(db)
postRepo := repository.NewPostRepository(db)
commentRepo := repository.NewCommentRepository(db)
likeRepo := repository.NewLikeRepository(db)
chatRepo := repository.NewChatRepository(db)
auditRepo := repository.NewAuditRepository(db)
recoveryRepo := repository.NewRecoveryRepository(db)
deviceRepo := repository.NewDeviceRepository(db)
@ -72,7 +76,6 @@ func main() {
postService := service.NewPostService(postRepo)
commentService := service.NewCommentService(commentRepo, postRepo)
likeService := service.NewLikeService(likeRepo, postRepo)
chatService := service.NewChatService(chatRepo, userRepo, chatHub)
auditService := service.NewAuditService(auditRepo)
recoveryService := service.NewRecoveryService(recoveryRepo, userRepo, sessionRepo, deviceRepo, mailService)
sessionService := service.NewSessionService(sessionRepo, deviceRepo, userRepo, mailService)
@ -84,12 +87,11 @@ func main() {
Post: postService,
Comment: commentService,
Like: likeService,
Chat: chatService,
Audit: auditService,
Recovery: recoveryService,
Session: sessionService,
Mail: mailService,
ChatHub: chatHub, // Добавляем хаб в Services
Messages: messageClient, // Добавляем gRPC клиент
}
// HTTP сервер - передаем db как дополнительный параметр
@ -101,7 +103,6 @@ func main() {
if err := server.Run(); err != nil {
log.Printf("server error: %v", err)
}
}()
// Ожидание сигнала завершения

View File

@ -28,6 +28,9 @@ type Config struct {
From string `env:"SMTP_FROM,required"`
URL string `env:"AppURL,required"`
}
GRPC struct {
MessageServiceAddress string `env:"MESSAGE_SERVICE_ADDRESS"`
}
}
func Load() (*Config, error) {

View File

@ -3,21 +3,23 @@ package domain
import "time"
type Chat struct {
ID int `json:"id"`
User1ID int `json:"user1Id"`
User2ID int `json:"user2Id"`
CreatedAt time.Time `json:"createdAt"`
ID int `json:"id"`
User1ID int `json:"user1Id"`
User2ID int `json:"user2Id"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
LastMessage *Message `json:"lastMessage"`
}
type Message struct {
ID int `json:"id"`
ChatID int `json:"chatId"`
SenderID int `json:"senderId"`
ReceiverID int `json:"receiverId"`
Content string `json:"content"`
Status string `json:"status"` // "sent", "delivered", "read"
CreatedAt time.Time `json:"createdAt"`
ID int `json:"id"`
ChatID int `json:"chatId"`
SenderID int `json:"senderId"`
Content string `json:"content"`
Status string `json:"status"` // "sent", "delivered", "read"
CreatedAt time.Time `json:"createdAt"`
}
type ChatSession struct {
User *User `json:"user"`
LastMessage *Message `json:"lastMessage"`

File diff suppressed because it is too large Load Diff

View File

@ -1,247 +0,0 @@
package graph
import (
"context"
"errors"
"fmt"
"tailly_back_v2/internal/domain"
"tailly_back_v2/internal/ws"
"time"
)
type messageResolver struct{ *Resolver }
// GetChatHistory - возвращает историю сообщений
func (r *queryResolver) GetChatHistory(ctx context.Context, userID int) ([]*domain.Message, error) {
currentUserID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, errors.New("не авторизован")
}
chat, err := r.Services.Chat.GetOrCreateChat(ctx, currentUserID, userID)
if err != nil {
return nil, fmt.Errorf("ошибка получения чата: %v", err)
}
messages, err := r.Services.Chat.GetChatMessages(ctx, chat.ID, currentUserID, 50, 0)
if err != nil {
return nil, fmt.Errorf("ошибка получения сообщений: %v", err)
}
return messages, nil
}
// GetUserChats - возвращает чаты пользователя
func (r *queryResolver) GetUserChats(ctx context.Context) ([]*ChatSession, error) {
userID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, errors.New("не авторизован")
}
// Проверяем инициализацию сервиса
if r.Services == nil || r.Services.Chat == nil {
return nil, errors.New("chat service not initialized")
}
// Получаем чаты пользователя
chats, err := r.Services.Chat.GetUserChats(ctx, userID)
if err != nil {
return nil, fmt.Errorf("ошибка получения чатов: %v", err)
}
var sessions []*ChatSession
for _, chat := range chats {
// Определяем другого участника чата
otherUserID := chat.User1ID
if userID == chat.User1ID {
otherUserID = chat.User2ID
}
// Получаем данные другого пользователя
otherUser, err := r.Services.User.GetByID(ctx, otherUserID)
if err != nil {
return nil, fmt.Errorf("ошибка получения пользователя %d: %v", otherUserID, err)
}
// Получаем последнее сообщение
messages, err := r.Services.Chat.GetChatMessages(ctx, chat.ID, userID, 1, 0)
if err != nil {
return nil, fmt.Errorf("ошибка получения сообщений: %v", err)
}
var lastMessage *domain.Message
if len(messages) > 0 {
lastMessage = messages[0]
} else {
// Создаем пустое сообщение, если чат новый
lastMessage = &domain.Message{
ChatID: chat.ID,
Content: "Чат создан",
Status: "system",
CreatedAt: chat.CreatedAt,
}
}
// Получаем количество непрочитанных сообщений
unreadCount, err := r.Services.Chat.GetUnreadCount(ctx, chat.ID, userID)
if err != nil {
return nil, fmt.Errorf("ошибка получения количества непрочитанных: %v", err)
}
sessions = append(sessions, &ChatSession{
User: otherUser,
LastMessage: lastMessage,
UnreadCount: unreadCount,
})
}
return sessions, nil
}
// Sender - возвращает отправителя сообщения
func (r *messageResolver) Sender(ctx context.Context, obj *domain.Message) (*domain.User, error) {
user, err := r.Services.User.GetByID(ctx, obj.SenderID)
if err != nil {
return nil, fmt.Errorf("ошибка получения отправителя: %v", err)
}
return user, nil
}
func (r *messageResolver) Receiver(ctx context.Context, obj *domain.Message) (*domain.User, error) {
// 1. Если receiver явно указан в сообщении
if obj.ReceiverID != 0 {
return r.Services.User.GetByID(ctx, obj.ReceiverID)
}
// 2. Если есть chat, определяем получателя через чат
if obj.ChatID != 0 {
chat, err := r.chatRepo.GetChatByID(ctx, obj.ChatID)
if err != nil {
return nil, fmt.Errorf("failed to get chat: %v", err)
}
// Определяем получателя
receiverID := chat.User1ID
if obj.SenderID == chat.User1ID {
receiverID = chat.User2ID
}
return r.Services.User.GetByID(ctx, receiverID)
}
// 3. Если ничего не найдено
return nil, fmt.Errorf("cannot determine receiver - both receiver_id and chat_id are not set")
}
// CreatedAt - форматирует время сообщения
func (r *messageResolver) CreatedAt(ctx context.Context, obj *domain.Message) (string, error) {
return obj.CreatedAt.Format(time.RFC3339), nil
}
// SendMessage - отправляет сообщение
func (r *mutationResolver) SendMessage(ctx context.Context, receiverID int, content string) (*domain.Message, error) {
senderID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, errors.New("не авторизован")
}
chat, err := r.Services.Chat.GetOrCreateChat(ctx, senderID, receiverID)
if err != nil {
return nil, fmt.Errorf("ошибка создания чата: %v", err)
}
message, err := r.Services.Chat.SendMessage(ctx, senderID, chat.ID, content)
if err != nil {
return nil, err
}
// Рассылаем сообщение через WebSocket
if r.Services.ChatHub != nil {
r.Services.ChatHub.Broadcast(message)
}
return message, nil
}
// MarkAsRead - помечает сообщение как прочитанное
func (r *mutationResolver) MarkAsRead(ctx context.Context, messageID int) (bool, error) {
if r.Services == nil || r.Services.Chat == nil {
return false, errors.New("сервис чатов не инициализирован")
}
// Все операции через сервис
err := r.Services.Chat.MarkAsRead(ctx, messageID)
if err != nil {
return false, fmt.Errorf("ошибка отметки как прочитанного: %v", err)
}
return true, nil
}
type subscriptionResolver struct{ *Resolver }
// Subscription returns SubscriptionResolver implementation.
func (r *Resolver) Subscription() SubscriptionResolver { return &subscriptionResolver{r} }
// MessageReceived - подписка на новые сообщения
func (r *subscriptionResolver) MessageReceived(ctx context.Context) (<-chan *domain.Message, error) {
userID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, errors.New("не авторизован")
}
messageChan := make(chan *domain.Message, 1)
// Создаем клиента для хаба
client := &ws.Client{
UserID: userID,
Send: messageChan,
}
// Регистрируем клиента в хабе
r.Services.ChatHub.RegisterClient(client)
// Горутина для обработки отключения
go func() {
<-ctx.Done()
r.Services.ChatHub.UnregisterClient(client)
close(messageChan)
}()
return messageChan, nil
}
// CreateChat is the resolver for the createChat field.
func (r *mutationResolver) CreateChat(ctx context.Context, userID int) (*ChatSession, error) {
currentUserID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, errors.New("не авторизован")
}
// Создаем или получаем существующий чат
chat, err := r.Services.Chat.GetOrCreateChat(ctx, currentUserID, userID)
if err != nil {
return nil, fmt.Errorf("ошибка создания чата: %v", err)
}
// Получаем данные другого пользователя
otherUser, err := r.Services.User.GetByID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("ошибка получения пользователя: %v", err)
}
// Создаем пустое последнее сообщение (или можно вернуть nil, если схема позволяет)
emptyMessage := &domain.Message{
ChatID: chat.ID,
SenderID: currentUserID,
ReceiverID: otherUser.ID,
Content: "Чат создан",
Status: "system",
CreatedAt: time.Now(),
}
return &ChatSession{
User: otherUser,
LastMessage: emptyMessage,
UnreadCount: 0,
}, nil
}

View File

@ -0,0 +1,201 @@
package graph
import (
"context"
"fmt"
"tailly_back_v2/internal/domain"
"tailly_back_v2/proto"
"time"
)
// Subscription возвращает реализацию SubscriptionResolver
func (r *Resolver) Subscription() SubscriptionResolver {
return &subscriptionResolver{r}
}
type subscriptionResolver struct{ *Resolver }
// CreateChat is the resolver for the createChat field.
func (r *mutationResolver) CreateChat(ctx context.Context, user1Id int, user2Id int) (*domain.Chat, error) {
// Просто вызываем gRPC метод, вся логика уже там
res, err := r.MessageClient.CreateChat(ctx, &proto.CreateChatRequest{
User1Id: int32(user1Id),
User2Id: int32(user2Id),
})
if err != nil {
return nil, fmt.Errorf("failed to create chat: %w", err)
}
// Преобразуем proto-чат в domain-модель
return protoChatToDomain(res.Chat), nil
}
// SendMessage реализация мутации для отправки сообщения
func (r *mutationResolver) SendMessage(ctx context.Context, chatID int, senderID int, content string) (*domain.Message, error) {
res, err := r.MessageClient.SendMessage(ctx, &proto.SendMessageRequest{
ChatId: int32(chatID),
SenderId: int32(senderID),
Content: content,
})
if err != nil {
return nil, fmt.Errorf("failed to send message: %w", err)
}
return protoMessageToDomain(res.Message), nil
}
// UpdateMessageStatus реализация мутации для обновления статуса сообщения
func (r *mutationResolver) UpdateMessageStatus(ctx context.Context, messageID int, status MessageStatus) (*domain.Message, error) {
var statusStr string
switch status {
case MessageStatusSent:
statusStr = "SENT"
case MessageStatusDelivered:
statusStr = "DELIVERED"
case MessageStatusRead:
statusStr = "READ"
default:
return nil, fmt.Errorf("unknown message status: %v", status)
}
res, err := r.MessageClient.UpdateMessageStatus(ctx, &proto.UpdateMessageStatusRequest{
MessageId: int32(messageID),
Status: statusStr,
})
if err != nil {
return nil, fmt.Errorf("failed to update message status: %w", err)
}
return protoMessageToDomain(res.Message), nil
}
// GetChat реализация запроса для получения чата
func (r *queryResolver) GetChat(ctx context.Context, user1Id int, user2Id int) (*domain.Chat, error) {
res, err := r.MessageClient.GetChat(ctx, &proto.GetChatRequest{
User1Id: int32(user1Id),
User2Id: int32(user2Id),
})
if err != nil {
return nil, fmt.Errorf("failed to get chat: %w", err)
}
return protoChatToDomain(res.Chat), nil
}
// GetChatMessages реализация запроса для получения сообщений чата
func (r *queryResolver) GetChatMessages(ctx context.Context, chatID int, limit int, offset int) ([]*domain.Message, error) {
res, err := r.MessageClient.GetChatMessages(ctx, &proto.GetChatMessagesRequest{
ChatId: int32(chatID),
Limit: int32(limit),
Offset: int32(offset),
})
if err != nil {
return nil, fmt.Errorf("failed to get chat messages: %w", err)
}
var messages []*domain.Message
for _, msg := range res.Messages {
messages = append(messages, protoMessageToDomain(msg))
}
return messages, nil
}
// GetUserChats реализация запроса для получения чатов пользователя
func (r *queryResolver) GetUserChats(ctx context.Context, userID int) ([]*domain.Chat, error) {
res, err := r.MessageClient.GetUserChats(ctx, &proto.GetUserChatsRequest{
UserId: int32(userID),
})
if err != nil {
return nil, fmt.Errorf("failed to get user chats: %w", err)
}
var chats []*domain.Chat
for _, chat := range res.Chats {
chats = append(chats, protoChatToDomain(chat))
}
return chats, nil
}
// MessageStream реализация подписки на новые сообщения
func (r *subscriptionResolver) MessageStream(ctx context.Context, userID int) (<-chan *domain.Message, error) {
stream, err := r.MessageClient.StreamMessages(ctx, &proto.StreamMessagesRequest{
UserId: int32(userID),
})
if err != nil {
return nil, fmt.Errorf("failed to stream messages: %w", err)
}
messageChan := make(chan *domain.Message)
go func() {
defer close(messageChan)
for {
msg, err := stream.Recv()
if err != nil {
return
}
select {
case <-ctx.Done():
return
case messageChan <- protoMessageToDomain(msg.Message):
}
}
}()
return messageChan, nil
}
// Преобразование proto-сообщения в domain-модель
func protoMessageToDomain(msg *proto.Message) *domain.Message {
return &domain.Message{
ID: int(msg.Id),
ChatID: int(msg.ChatId),
SenderID: int(msg.SenderId),
Content: msg.Content,
Status: msg.Status,
CreatedAt: msg.CreatedAt.AsTime(),
}
}
// Преобразование proto-чата в domain-модель
func protoChatToDomain(chat *proto.Chat) *domain.Chat {
gqlChat := &domain.Chat{
ID: int(chat.Id),
User1ID: int(chat.User1Id),
User2ID: int(chat.User2Id),
CreatedAt: chat.CreatedAt.AsTime(),
UpdatedAt: chat.UpdatedAt.AsTime(),
}
if chat.LastMessage != nil {
gqlChat.LastMessage = protoMessageToDomain(chat.LastMessage)
}
return gqlChat
}
// Реализации резолверов для отдельных полей
type chatResolver struct{ *Resolver }
type messageResolver struct{ *Resolver }
// Chat возвращает ChatResolver
func (r *Resolver) Chat() ChatResolver { return &chatResolver{r} }
// Message возвращает MessageResolver
func (r *Resolver) Message() MessageResolver { return &messageResolver{r} }
// CreatedAt для Chat
func (r *chatResolver) CreatedAt(ctx context.Context, obj *domain.Chat) (string, error) {
return obj.CreatedAt.Format(time.RFC3339), nil
}
// UpdatedAt для Chat
func (r *chatResolver) UpdatedAt(ctx context.Context, obj *domain.Chat) (string, error) {
return obj.UpdatedAt.Format(time.RFC3339), nil
}
// Status для Message
func (r *messageResolver) Status(ctx context.Context, obj *domain.Message) (MessageStatus, error) {
return MessageStatus(obj.Status), nil
}
// CreatedAt для Message
func (r *messageResolver) CreatedAt(ctx context.Context, obj *domain.Message) (string, error) {
return obj.CreatedAt.Format(time.RFC3339), nil
}

View File

@ -3,15 +3,12 @@
package graph
import (
"tailly_back_v2/internal/domain"
"bytes"
"fmt"
"io"
"strconv"
)
type ChatSession struct {
User *domain.User `json:"user"`
LastMessage *domain.Message `json:"lastMessage"`
UnreadCount int `json:"unreadCount"`
}
type Mutation struct {
}
@ -20,3 +17,60 @@ type Query struct {
type Subscription struct {
}
type MessageStatus string
const (
MessageStatusSent MessageStatus = "SENT"
MessageStatusDelivered MessageStatus = "DELIVERED"
MessageStatusRead MessageStatus = "READ"
)
var AllMessageStatus = []MessageStatus{
MessageStatusSent,
MessageStatusDelivered,
MessageStatusRead,
}
func (e MessageStatus) IsValid() bool {
switch e {
case MessageStatusSent, MessageStatusDelivered, MessageStatusRead:
return true
}
return false
}
func (e MessageStatus) String() string {
return string(e)
}
func (e *MessageStatus) UnmarshalGQL(v any) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = MessageStatus(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid MessageStatus", str)
}
return nil
}
func (e MessageStatus) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
func (e *MessageStatus) UnmarshalJSON(b []byte) error {
s, err := strconv.Unquote(string(b))
if err != nil {
return err
}
return e.UnmarshalGQL(s)
}
func (e MessageStatus) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
e.MarshalGQL(&buf)
return buf.Bytes(), nil
}

View File

@ -7,6 +7,7 @@ import (
"fmt"
"tailly_back_v2/internal/repository"
"tailly_back_v2/internal/service"
"tailly_back_v2/proto"
)
// This file will not be regenerated automatically.
@ -14,18 +15,20 @@ import (
// It serves as dependency injection for your app, add any dependencies you require here.
type Resolver struct {
Services *service.Services
DeviceRepo repository.DeviceRepository // Добавляем репозиторий устройств напрямую
chatRepo repository.ChatRepository
Services *service.Services
DeviceRepo repository.DeviceRepository // Добавляем репозиторий устройств напрямую
MessageClient proto.MessageServiceClient
}
func NewResolver(
services *service.Services,
db *sql.DB, // Принимаем подключение к БД
messageClient proto.MessageServiceClient,
) *Resolver {
return &Resolver{
Services: services,
DeviceRepo: repository.NewDeviceRepository(db), // Инициализируем репозиторий
Services: services,
DeviceRepo: repository.NewDeviceRepository(db),
MessageClient: messageClient,
}
}
@ -33,8 +36,7 @@ func NewResolver(
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }
// Query returns QueryResolver implementation.
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
func (r *Resolver) Message() MessageResolver { return &messageResolver{r} }
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
type mutationResolver struct{ *Resolver }

View File

@ -64,19 +64,29 @@ input LoginInput {
type Message {
id: Int!
sender: User!
receiver: User!
chatId: Int!
senderId: Int!
content: String!
status: MessageStatus!
createdAt: String!
status: String!
}
type ChatSession {
user: User!
lastMessage: Message!
unreadCount: Int!
enum MessageStatus {
SENT
DELIVERED
READ
}
type Chat {
id: Int!
user1Id: Int!
user2Id: Int!
createdAt: String!
updatedAt: String!
lastMessage: Message
}
type Session {
id: Int!
device: Device!
@ -101,8 +111,9 @@ type Query {
getUserPosts(userId: Int!): [Post!]!
user(id: Int!): User! # Получить пользователя по ID
users: [User!]!
getChatHistory(userId: Int!): [Message!]!
getUserChats: [ChatSession!]!
getChat(user1Id: Int!, user2Id: Int!): Chat!
getChatMessages(chatId: Int!, limit: Int!, offset: Int!): [Message!]!
getUserChats(userId: Int!): [Chat!]!
mySessions: [Session!]!
comments(postID: Int!): [Comment!]!
}
@ -131,21 +142,20 @@ type Mutation {
unlikePost(postId: Int!): Boolean!
updateProfile(username: String!, email: String!, avatar: String!): User!
changePassword(oldPassword: String!, newPassword: String!): Boolean!
sendMessage(receiverId: Int!, content: String!): Message!
markAsRead(messageId: Int!): Boolean!
terminateSession(sessionId: Int!): Boolean!
renameDevice(deviceId: Int!, name: String!): Device!
# Запрос на подтверждение email
requestEmailConfirmation: Boolean!
createChat(user1Id: Int!, user2Id: Int!): Chat!
sendMessage(chatId: Int!, senderId: Int!, content: String!): Message!
updateMessageStatus(messageId: Int!, status: MessageStatus!): Message!
# Подтверждение email по токену
confirmEmail(token: String!): Boolean!
createChat(userId: Int!): ChatSession!
# Повторная отправка подтверждения email
resendEmailConfirmation: Boolean!
deletePost(id: Int!): Boolean!
}
type Subscription {
messageReceived: Message!
messageStream(userId: Int!): Message!
}

View File

@ -12,11 +12,8 @@ import (
"os"
"tailly_back_v2/internal/config"
"tailly_back_v2/internal/http/graph"
"tailly_back_v2/internal/http/handlers"
"tailly_back_v2/internal/http/middleware"
"tailly_back_v2/internal/repository"
"tailly_back_v2/internal/service"
"tailly_back_v2/internal/ws"
"tailly_back_v2/pkg/auth"
)
@ -53,18 +50,6 @@ func (s *Server) configureRouter() {
"http://localhost:3000",
"https://tailly.ru",
}
// Инициализация WebSocket хаба
hub := ws.NewHub()
go hub.Run()
// Инициализация сервиса чата
chatService := service.NewChatService(
repository.NewChatRepository(s.db),
repository.NewUserRepository(s.db),
hub,
)
s.services.Chat = chatService
logger := log.New(os.Stdout, "HTTP: ", log.LstdFlags)
s.router.Use(middleware.WebSocketMiddleware)
@ -73,7 +58,7 @@ func (s *Server) configureRouter() {
s.router.Use(middleware.CORS(allowedOrigins))
s.router.Use(middleware.AuthMiddleware(s.tokenAuth))
resolver := graph.NewResolver(s.services, s.db)
resolver := graph.NewResolver(s.services, s.db, s.services.Messages)
srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{
Resolvers: resolver,
}))
@ -81,8 +66,7 @@ func (s *Server) configureRouter() {
s.router.Handle("/", playground.Handler("GraphQL playground", "/query"))
s.router.Handle("/query", srv)
s.router.Handle("/uploads/*", http.StripPrefix("/uploads/", http.FileServer(http.Dir("./uploads"))))
chatHandler := handlers.NewChatHandler(chatService, hub, s.tokenAuth)
s.router.HandleFunc("/ws", chatHandler.HandleWebSocket)
}
func (s *Server) configureMetrics() {

View File

@ -1,261 +0,0 @@
package repository
import (
"context"
"database/sql"
"errors"
"fmt"
"tailly_back_v2/internal/domain"
"time"
)
var (
ErrMessageNotFound = errors.New("message not found")
ErrChatNotFound = errors.New("chat not found")
)
type ChatRepository interface {
// Основные методы сообщений
SaveMessage(ctx context.Context, message *domain.Message) error
GetMessageByID(ctx context.Context, id int) (*domain.Message, error)
GetMessagesByChat(ctx context.Context, chatID int, limit, offset int) ([]*domain.Message, error)
UpdateMessageStatus(ctx context.Context, id int, status string) error
DeleteMessage(ctx context.Context, id int) error
// Методы чатов
CreateChat(ctx context.Context, user1ID, user2ID int) (*domain.Chat, error)
GetChatByID(ctx context.Context, id int) (*domain.Chat, error)
GetUserChats(ctx context.Context, userID int) ([]*domain.Chat, error)
GetChatByParticipants(ctx context.Context, user1ID, user2ID int) (*domain.Chat, error)
GetUnreadCount(ctx context.Context, chatID, userID int) (int, error)
}
type chatRepository struct {
db *sql.DB
}
func NewChatRepository(db *sql.DB) ChatRepository {
return &chatRepository{db: db}
}
func (r *chatRepository) SaveMessage(ctx context.Context, message *domain.Message) error {
if message.ReceiverID == 0 {
return errors.New("receiver_id is required")
}
query := `
INSERT INTO messages (chat_id, sender_id, receiver_id, content, status, created_at)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id
`
err := r.db.QueryRowContext(ctx, query,
message.ChatID,
message.SenderID,
message.ReceiverID,
message.Content,
message.Status,
message.CreatedAt,
).Scan(&message.ID)
return err
}
func (r *chatRepository) GetMessageByID(ctx context.Context, id int) (*domain.Message, error) {
query := `
SELECT id, chat_id, sender_id, content, status, created_at
FROM messages
WHERE id = $1
`
message := &domain.Message{}
err := r.db.QueryRowContext(ctx, query, id).Scan(
&message.ID,
&message.ChatID,
&message.SenderID,
&message.Content,
&message.Status,
&message.CreatedAt,
)
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrMessageNotFound
}
return message, err
}
func (r *chatRepository) GetMessagesByChat(ctx context.Context, chatID int, limit, offset int) ([]*domain.Message, error) {
query := `
SELECT id, chat_id, sender_id, content, status, created_at
FROM messages
WHERE chat_id = $1
ORDER BY created_at DESC
LIMIT $2 OFFSET $3
`
rows, err := r.db.QueryContext(ctx, query, chatID, limit, offset)
if err != nil {
return nil, err
}
defer rows.Close()
var messages []*domain.Message
for rows.Next() {
var message domain.Message
if err := rows.Scan(
&message.ID,
&message.ChatID,
&message.SenderID,
&message.Content,
&message.Status,
&message.CreatedAt,
); err != nil {
return nil, err
}
messages = append(messages, &message)
}
return messages, nil
}
func (r *chatRepository) UpdateMessageStatus(ctx context.Context, id int, status string) error {
query := `
UPDATE messages
SET status = $1
WHERE id = $2
`
_, err := r.db.ExecContext(ctx, query, status, id)
return err
}
func (r *chatRepository) DeleteMessage(ctx context.Context, id int) error {
query := `DELETE FROM messages WHERE id = $1`
_, err := r.db.ExecContext(ctx, query, id)
return err
}
func (r *chatRepository) CreateChat(ctx context.Context, user1ID, user2ID int) (*domain.Chat, error) {
// Проверяем, что пользователи разные
if user1ID == user2ID {
return nil, errors.New("cannot create chat with yourself")
}
// Проверяем существование пользователей
if err := r.checkUserExists(ctx, user1ID); err != nil {
return nil, fmt.Errorf("user1 does not exist: %v", err)
}
if err := r.checkUserExists(ctx, user2ID); err != nil {
return nil, fmt.Errorf("user2 does not exist: %v", err)
}
// Упорядочиваем ID пользователей согласно CHECK constraint
if user1ID > user2ID {
user1ID, user2ID = user2ID, user1ID
}
query := `
INSERT INTO chats (user1_id, user2_id, created_at)
VALUES ($1, $2, $3)
RETURNING id
`
chat := &domain.Chat{
User1ID: user1ID,
User2ID: user2ID,
CreatedAt: time.Now(),
}
err := r.db.QueryRowContext(ctx, query, user1ID, user2ID, chat.CreatedAt).Scan(&chat.ID)
if err != nil {
return nil, fmt.Errorf("failed to create chat: %v", err)
}
return chat, nil
}
func (r *chatRepository) checkUserExists(ctx context.Context, userID int) error {
var exists bool
err := r.db.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM users WHERE id = $1)", userID).Scan(&exists)
if err != nil {
return err
}
if !exists {
return errors.New("user not found")
}
return nil
}
func (r *chatRepository) GetChatByID(ctx context.Context, id int) (*domain.Chat, error) {
query := `
SELECT id, user1_id, user2_id, created_at
FROM chats
WHERE id = $1
`
chat := &domain.Chat{}
err := r.db.QueryRowContext(ctx, query, id).Scan(
&chat.ID,
&chat.User1ID,
&chat.User2ID,
&chat.CreatedAt,
)
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrChatNotFound
}
return chat, err
}
func (r *chatRepository) GetUserChats(ctx context.Context, userID int) ([]*domain.Chat, error) {
query := `
SELECT id, user1_id, user2_id, created_at
FROM chats
WHERE user1_id = $1 OR user2_id = $1
ORDER BY created_at DESC
`
rows, err := r.db.QueryContext(ctx, query, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var chats []*domain.Chat
for rows.Next() {
var chat domain.Chat
if err := rows.Scan(
&chat.ID,
&chat.User1ID,
&chat.User2ID,
&chat.CreatedAt,
); err != nil {
return nil, err
}
chats = append(chats, &chat)
}
return chats, nil
}
func (r *chatRepository) GetChatByParticipants(ctx context.Context, user1ID, user2ID int) (*domain.Chat, error) {
// Упорядочиваем ID пользователей согласно CHECK constraint
if user1ID > user2ID {
user1ID, user2ID = user2ID, user1ID
}
query := `
SELECT id, user1_id, user2_id, created_at
FROM chats
WHERE user1_id = $1 AND user2_id = $2
LIMIT 1
`
chat := &domain.Chat{}
err := r.db.QueryRowContext(ctx, query, user1ID, user2ID).Scan(
&chat.ID,
&chat.User1ID,
&chat.User2ID,
&chat.CreatedAt,
)
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrChatNotFound
}
return chat, err
}
func (r *chatRepository) GetUnreadCount(ctx context.Context, chatID, userID int) (int, error) {
var count int
err := r.db.QueryRowContext(ctx, `
SELECT COUNT(*)
FROM messages
WHERE chat_id = $1
AND sender_id != $2
AND status = 'sent'
`, chatID, userID).Scan(&count)
return count, err
}

View File

@ -1,135 +0,0 @@
package service
import (
"context"
"errors"
"tailly_back_v2/internal/domain"
"tailly_back_v2/internal/repository"
"tailly_back_v2/internal/ws"
"time"
)
type ChatService interface {
SendMessage(ctx context.Context, senderID, chatID int, content string) (*domain.Message, error)
GetChatMessages(ctx context.Context, chatID, userID int, limit, offset int) ([]*domain.Message, error)
MarkAsRead(ctx context.Context, messageID int) error
DeleteMessage(ctx context.Context, messageID, userID int) error
GetUserChats(ctx context.Context, userID int) ([]*domain.Chat, error)
GetOrCreateChat(ctx context.Context, user1ID, user2ID int) (*domain.Chat, error)
GetUnreadCount(ctx context.Context, chatID, userID int) (int, error)
GetChatByID(ctx context.Context, id int) (*domain.Chat, error)
}
type chatService struct {
chatRepo repository.ChatRepository
userRepo repository.UserRepository
hub *ws.Hub
}
func NewChatService(chatRepo repository.ChatRepository, userRepo repository.UserRepository, hub *ws.Hub) ChatService {
return &chatService{
chatRepo: chatRepo,
userRepo: userRepo,
hub: hub,
}
}
func (s *chatService) SendMessage(ctx context.Context, senderID, chatID int, content string) (*domain.Message, error) {
chat, err := s.chatRepo.GetChatByID(ctx, chatID)
if err != nil {
return nil, err
}
// Проверяем, что отправитель является участником чата
if senderID != chat.User1ID && senderID != chat.User2ID {
return nil, errors.New("user is not a participant of this chat")
}
// Определяем получателя
receiverID := chat.User1ID
if senderID == chat.User1ID {
receiverID = chat.User2ID
}
// Создаем сообщение
message := &domain.Message{
ChatID: chatID,
SenderID: senderID,
ReceiverID: receiverID, // Гарантируем что receiverID всегда установлен
Content: content,
Status: "sent",
CreatedAt: time.Now(),
}
// Сохраняем в БД
if err := s.chatRepo.SaveMessage(ctx, message); err != nil {
return nil, err
}
// Отправляем через WebSocket только один раз
if s.hub != nil {
// Добавляем проверку перед рассылкой
if message.ReceiverID == 0 {
return nil, errors.New("receiver ID is required")
}
s.hub.Broadcast(message)
}
return message, nil
}
func (s *chatService) GetChatMessages(ctx context.Context, chatID, userID int, limit, offset int) ([]*domain.Message, error) {
// Проверяем доступ пользователя к чату
chat, err := s.chatRepo.GetChatByID(ctx, chatID)
if err != nil {
return nil, err
}
if userID != chat.User1ID && userID != chat.User2ID {
return nil, errors.New("access denied")
}
return s.chatRepo.GetMessagesByChat(ctx, chatID, limit, offset)
}
func (s *chatService) MarkAsRead(ctx context.Context, messageID int) error {
return s.chatRepo.UpdateMessageStatus(ctx, messageID, "read")
}
func (s *chatService) DeleteMessage(ctx context.Context, messageID, userID int) error {
message, err := s.chatRepo.GetMessageByID(ctx, messageID)
if err != nil {
return err
}
if message.SenderID != userID {
return errors.New("only sender can delete the message")
}
return s.chatRepo.DeleteMessage(ctx, messageID)
}
func (s *chatService) GetUserChats(ctx context.Context, userID int) ([]*domain.Chat, error) {
return s.chatRepo.GetUserChats(ctx, userID)
}
func (s *chatService) GetOrCreateChat(ctx context.Context, user1ID, user2ID int) (*domain.Chat, error) {
// Проверяем существование чата
chat, err := s.chatRepo.GetChatByParticipants(ctx, user1ID, user2ID)
if err == nil {
return chat, nil
}
if !errors.Is(err, repository.ErrChatNotFound) {
return nil, err
}
// Создаем новый чат
return s.chatRepo.CreateChat(ctx, user1ID, user2ID)
}
func (s *chatService) GetUnreadCount(ctx context.Context, chatID, userID int) (int, error) {
return s.chatRepo.GetUnreadCount(ctx, chatID, userID)
}
func (s *chatService) GetChatByID(ctx context.Context, id int) (*domain.Chat, error) {
return s.chatRepo.GetChatByID(ctx, id)
}

View File

@ -2,7 +2,7 @@ package service
import (
_ "tailly_back_v2/internal/repository"
"tailly_back_v2/internal/ws"
"tailly_back_v2/proto"
)
type Services struct {
@ -15,11 +15,10 @@ type Services struct {
Mail MailService
Recovery RecoveryService
Audit AuditService
Chat ChatService
ChatHub *ws.Hub
Messages proto.MessageServiceClient
}
func NewServices(authService AuthService, userService UserService, postService PostService, commentService CommentService, likeService LikeService, mailService MailService, auditService AuditService, recoveryService RecoveryService, sessionService SessionService, chatService ChatService, chatHub *ws.Hub) *Services {
func NewServices(authService AuthService, userService UserService, postService PostService, commentService CommentService, likeService LikeService, mailService MailService, auditService AuditService, recoveryService RecoveryService, sessionService SessionService, messages proto.MessageServiceClient) *Services {
return &Services{
Auth: authService,
User: userService,
@ -30,7 +29,6 @@ func NewServices(authService AuthService, userService UserService, postService P
Mail: mailService,
Recovery: recoveryService,
Audit: auditService,
Chat: chatService,
ChatHub: chatHub,
Messages: messages,
}
}

View File

@ -1,126 +0,0 @@
package ws
import (
"log"
"sync"
"tailly_back_v2/internal/domain"
"time"
)
type Client struct {
UserID int
Send chan *domain.Message
LastSeen time.Time
CloseChan chan bool
}
type Hub struct {
clients map[int]*Client
register chan *Client
unregister chan *Client
broadcast chan *domain.Message
mu sync.RWMutex
}
func NewHub() *Hub {
hub := &Hub{
clients: make(map[int]*Client),
register: make(chan *Client),
unregister: make(chan *Client),
broadcast: make(chan *domain.Message, 100),
}
go hub.cleanupInactiveClients()
return hub
}
// Добавляем периодическую очистку неактивных клиентов
func (h *Hub) cleanupInactiveClients() {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
h.mu.Lock()
for userID, client := range h.clients {
if time.Since(client.LastSeen) > 10*time.Minute {
log.Printf("Cleaning up inactive client: %d", userID)
close(client.Send)
delete(h.clients, userID)
}
}
h.mu.Unlock()
}
}
}
func (h *Hub) RegisterClient(client *Client) {
client.LastSeen = time.Now()
h.register <- client
}
func (h *Hub) UnregisterClient(client *Client) {
h.unregister <- client
}
func (h *Hub) Broadcast(message *domain.Message) {
if message == nil || message.SenderID == 0 {
log.Println("Attempt to broadcast invalid message")
return
}
if message.ReceiverID == 0 {
log.Printf("Message %d has no receiver", message.ID)
return
}
h.broadcast <- message
}
func (h *Hub) Run() {
for {
select {
case client := <-h.register:
h.mu.Lock()
// Обновляем LastSeen при регистрации
client.LastSeen = time.Now()
// Закрываем предыдущее соединение если есть
if existing, ok := h.clients[client.UserID]; ok {
select {
case existing.CloseChan <- true: // Уведомляем о закрытии
default:
}
close(existing.Send)
}
h.clients[client.UserID] = client
h.mu.Unlock()
case client := <-h.unregister:
h.mu.Lock()
if c, ok := h.clients[client.UserID]; ok && c == client {
close(c.Send)
delete(h.clients, client.UserID)
}
h.mu.Unlock()
case message := <-h.broadcast:
h.mu.RLock()
// Отправляем всем клиентам, кто участвует в этом чате
for _, client := range h.clients {
if client.UserID == message.SenderID || client.UserID == message.ReceiverID {
client.LastSeen = time.Now() // Обновляем время активности
select {
case client.Send <- message:
// Сообщение успешно отправлено
default:
log.Printf("Client %d channel busy, skipping message", client.UserID)
// Не закрываем соединение, просто пропускаем сообщение
}
}
}
h.mu.RUnlock()
}
}
}

877
proto/messages.pb.go Normal file
View File

@ -0,0 +1,877 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.6
// protoc v3.21.12
// source: messages.proto
package proto
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type CreateChatRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
User1Id int32 `protobuf:"varint,1,opt,name=user1_id,json=user1Id,proto3" json:"user1_id,omitempty"`
User2Id int32 `protobuf:"varint,2,opt,name=user2_id,json=user2Id,proto3" json:"user2_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *CreateChatRequest) Reset() {
*x = CreateChatRequest{}
mi := &file_messages_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CreateChatRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CreateChatRequest) ProtoMessage() {}
func (x *CreateChatRequest) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CreateChatRequest.ProtoReflect.Descriptor instead.
func (*CreateChatRequest) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{0}
}
func (x *CreateChatRequest) GetUser1Id() int32 {
if x != nil {
return x.User1Id
}
return 0
}
func (x *CreateChatRequest) GetUser2Id() int32 {
if x != nil {
return x.User2Id
}
return 0
}
type SendMessageRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
ChatId int32 `protobuf:"varint,1,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"`
SenderId int32 `protobuf:"varint,2,opt,name=sender_id,json=senderId,proto3" json:"sender_id,omitempty"`
Content string `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SendMessageRequest) Reset() {
*x = SendMessageRequest{}
mi := &file_messages_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SendMessageRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendMessageRequest) ProtoMessage() {}
func (x *SendMessageRequest) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendMessageRequest.ProtoReflect.Descriptor instead.
func (*SendMessageRequest) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{1}
}
func (x *SendMessageRequest) GetChatId() int32 {
if x != nil {
return x.ChatId
}
return 0
}
func (x *SendMessageRequest) GetSenderId() int32 {
if x != nil {
return x.SenderId
}
return 0
}
func (x *SendMessageRequest) GetContent() string {
if x != nil {
return x.Content
}
return ""
}
type GetChatRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
User1Id int32 `protobuf:"varint,1,opt,name=user1_id,json=user1Id,proto3" json:"user1_id,omitempty"`
User2Id int32 `protobuf:"varint,2,opt,name=user2_id,json=user2Id,proto3" json:"user2_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetChatRequest) Reset() {
*x = GetChatRequest{}
mi := &file_messages_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetChatRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetChatRequest) ProtoMessage() {}
func (x *GetChatRequest) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetChatRequest.ProtoReflect.Descriptor instead.
func (*GetChatRequest) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{2}
}
func (x *GetChatRequest) GetUser1Id() int32 {
if x != nil {
return x.User1Id
}
return 0
}
func (x *GetChatRequest) GetUser2Id() int32 {
if x != nil {
return x.User2Id
}
return 0
}
type GetChatMessagesRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
ChatId int32 `protobuf:"varint,1,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"`
Limit int32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"`
Offset int32 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetChatMessagesRequest) Reset() {
*x = GetChatMessagesRequest{}
mi := &file_messages_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetChatMessagesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetChatMessagesRequest) ProtoMessage() {}
func (x *GetChatMessagesRequest) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetChatMessagesRequest.ProtoReflect.Descriptor instead.
func (*GetChatMessagesRequest) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{3}
}
func (x *GetChatMessagesRequest) GetChatId() int32 {
if x != nil {
return x.ChatId
}
return 0
}
func (x *GetChatMessagesRequest) GetLimit() int32 {
if x != nil {
return x.Limit
}
return 0
}
func (x *GetChatMessagesRequest) GetOffset() int32 {
if x != nil {
return x.Offset
}
return 0
}
type GetUserChatsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
UserId int32 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetUserChatsRequest) Reset() {
*x = GetUserChatsRequest{}
mi := &file_messages_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetUserChatsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetUserChatsRequest) ProtoMessage() {}
func (x *GetUserChatsRequest) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetUserChatsRequest.ProtoReflect.Descriptor instead.
func (*GetUserChatsRequest) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{4}
}
func (x *GetUserChatsRequest) GetUserId() int32 {
if x != nil {
return x.UserId
}
return 0
}
type UpdateMessageStatusRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
MessageId int32 `protobuf:"varint,1,opt,name=message_id,json=messageId,proto3" json:"message_id,omitempty"`
Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UpdateMessageStatusRequest) Reset() {
*x = UpdateMessageStatusRequest{}
mi := &file_messages_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UpdateMessageStatusRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UpdateMessageStatusRequest) ProtoMessage() {}
func (x *UpdateMessageStatusRequest) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UpdateMessageStatusRequest.ProtoReflect.Descriptor instead.
func (*UpdateMessageStatusRequest) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{5}
}
func (x *UpdateMessageStatusRequest) GetMessageId() int32 {
if x != nil {
return x.MessageId
}
return 0
}
func (x *UpdateMessageStatusRequest) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type StreamMessagesRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
UserId int32 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *StreamMessagesRequest) Reset() {
*x = StreamMessagesRequest{}
mi := &file_messages_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *StreamMessagesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StreamMessagesRequest) ProtoMessage() {}
func (x *StreamMessagesRequest) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StreamMessagesRequest.ProtoReflect.Descriptor instead.
func (*StreamMessagesRequest) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{6}
}
func (x *StreamMessagesRequest) GetUserId() int32 {
if x != nil {
return x.UserId
}
return 0
}
type Message struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
ChatId int32 `protobuf:"varint,2,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"`
SenderId int32 `protobuf:"varint,3,opt,name=sender_id,json=senderId,proto3" json:"sender_id,omitempty"`
Content string `protobuf:"bytes,4,opt,name=content,proto3" json:"content,omitempty"`
Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"`
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Message) Reset() {
*x = Message{}
mi := &file_messages_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Message) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Message) ProtoMessage() {}
func (x *Message) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Message.ProtoReflect.Descriptor instead.
func (*Message) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{7}
}
func (x *Message) GetId() int32 {
if x != nil {
return x.Id
}
return 0
}
func (x *Message) GetChatId() int32 {
if x != nil {
return x.ChatId
}
return 0
}
func (x *Message) GetSenderId() int32 {
if x != nil {
return x.SenderId
}
return 0
}
func (x *Message) GetContent() string {
if x != nil {
return x.Content
}
return ""
}
func (x *Message) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
func (x *Message) GetCreatedAt() *timestamppb.Timestamp {
if x != nil {
return x.CreatedAt
}
return nil
}
type Chat struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
User1Id int32 `protobuf:"varint,2,opt,name=user1_id,json=user1Id,proto3" json:"user1_id,omitempty"`
User2Id int32 `protobuf:"varint,3,opt,name=user2_id,json=user2Id,proto3" json:"user2_id,omitempty"`
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
LastMessage *Message `protobuf:"bytes,6,opt,name=last_message,json=lastMessage,proto3" json:"last_message,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Chat) Reset() {
*x = Chat{}
mi := &file_messages_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Chat) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Chat) ProtoMessage() {}
func (x *Chat) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Chat.ProtoReflect.Descriptor instead.
func (*Chat) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{8}
}
func (x *Chat) GetId() int32 {
if x != nil {
return x.Id
}
return 0
}
func (x *Chat) GetUser1Id() int32 {
if x != nil {
return x.User1Id
}
return 0
}
func (x *Chat) GetUser2Id() int32 {
if x != nil {
return x.User2Id
}
return 0
}
func (x *Chat) GetCreatedAt() *timestamppb.Timestamp {
if x != nil {
return x.CreatedAt
}
return nil
}
func (x *Chat) GetUpdatedAt() *timestamppb.Timestamp {
if x != nil {
return x.UpdatedAt
}
return nil
}
func (x *Chat) GetLastMessage() *Message {
if x != nil {
return x.LastMessage
}
return nil
}
type MessageResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Message *Message `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *MessageResponse) Reset() {
*x = MessageResponse{}
mi := &file_messages_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *MessageResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MessageResponse) ProtoMessage() {}
func (x *MessageResponse) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MessageResponse.ProtoReflect.Descriptor instead.
func (*MessageResponse) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{9}
}
func (x *MessageResponse) GetMessage() *Message {
if x != nil {
return x.Message
}
return nil
}
type MessagesResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Messages []*Message `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *MessagesResponse) Reset() {
*x = MessagesResponse{}
mi := &file_messages_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *MessagesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MessagesResponse) ProtoMessage() {}
func (x *MessagesResponse) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MessagesResponse.ProtoReflect.Descriptor instead.
func (*MessagesResponse) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{10}
}
func (x *MessagesResponse) GetMessages() []*Message {
if x != nil {
return x.Messages
}
return nil
}
type ChatResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Chat *Chat `protobuf:"bytes,1,opt,name=chat,proto3" json:"chat,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ChatResponse) Reset() {
*x = ChatResponse{}
mi := &file_messages_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ChatResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChatResponse) ProtoMessage() {}
func (x *ChatResponse) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[11]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChatResponse.ProtoReflect.Descriptor instead.
func (*ChatResponse) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{11}
}
func (x *ChatResponse) GetChat() *Chat {
if x != nil {
return x.Chat
}
return nil
}
type UserChatsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Chats []*Chat `protobuf:"bytes,1,rep,name=chats,proto3" json:"chats,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UserChatsResponse) Reset() {
*x = UserChatsResponse{}
mi := &file_messages_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UserChatsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UserChatsResponse) ProtoMessage() {}
func (x *UserChatsResponse) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[12]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UserChatsResponse.ProtoReflect.Descriptor instead.
func (*UserChatsResponse) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{12}
}
func (x *UserChatsResponse) GetChats() []*Chat {
if x != nil {
return x.Chats
}
return nil
}
var File_messages_proto protoreflect.FileDescriptor
const file_messages_proto_rawDesc = "" +
"\n" +
"\x0emessages.proto\x12\x05proto\x1a\x1fgoogle/protobuf/timestamp.proto\"I\n" +
"\x11CreateChatRequest\x12\x19\n" +
"\buser1_id\x18\x01 \x01(\x05R\auser1Id\x12\x19\n" +
"\buser2_id\x18\x02 \x01(\x05R\auser2Id\"d\n" +
"\x12SendMessageRequest\x12\x17\n" +
"\achat_id\x18\x01 \x01(\x05R\x06chatId\x12\x1b\n" +
"\tsender_id\x18\x02 \x01(\x05R\bsenderId\x12\x18\n" +
"\acontent\x18\x03 \x01(\tR\acontent\"F\n" +
"\x0eGetChatRequest\x12\x19\n" +
"\buser1_id\x18\x01 \x01(\x05R\auser1Id\x12\x19\n" +
"\buser2_id\x18\x02 \x01(\x05R\auser2Id\"_\n" +
"\x16GetChatMessagesRequest\x12\x17\n" +
"\achat_id\x18\x01 \x01(\x05R\x06chatId\x12\x14\n" +
"\x05limit\x18\x02 \x01(\x05R\x05limit\x12\x16\n" +
"\x06offset\x18\x03 \x01(\x05R\x06offset\".\n" +
"\x13GetUserChatsRequest\x12\x17\n" +
"\auser_id\x18\x01 \x01(\x05R\x06userId\"S\n" +
"\x1aUpdateMessageStatusRequest\x12\x1d\n" +
"\n" +
"message_id\x18\x01 \x01(\x05R\tmessageId\x12\x16\n" +
"\x06status\x18\x02 \x01(\tR\x06status\"0\n" +
"\x15StreamMessagesRequest\x12\x17\n" +
"\auser_id\x18\x01 \x01(\x05R\x06userId\"\xbc\x01\n" +
"\aMessage\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x05R\x02id\x12\x17\n" +
"\achat_id\x18\x02 \x01(\x05R\x06chatId\x12\x1b\n" +
"\tsender_id\x18\x03 \x01(\x05R\bsenderId\x12\x18\n" +
"\acontent\x18\x04 \x01(\tR\acontent\x12\x16\n" +
"\x06status\x18\x05 \x01(\tR\x06status\x129\n" +
"\n" +
"created_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\"\xf5\x01\n" +
"\x04Chat\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x05R\x02id\x12\x19\n" +
"\buser1_id\x18\x02 \x01(\x05R\auser1Id\x12\x19\n" +
"\buser2_id\x18\x03 \x01(\x05R\auser2Id\x129\n" +
"\n" +
"created_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" +
"\n" +
"updated_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\x121\n" +
"\flast_message\x18\x06 \x01(\v2\x0e.proto.MessageR\vlastMessage\";\n" +
"\x0fMessageResponse\x12(\n" +
"\amessage\x18\x01 \x01(\v2\x0e.proto.MessageR\amessage\">\n" +
"\x10MessagesResponse\x12*\n" +
"\bmessages\x18\x01 \x03(\v2\x0e.proto.MessageR\bmessages\"/\n" +
"\fChatResponse\x12\x1f\n" +
"\x04chat\x18\x01 \x01(\v2\v.proto.ChatR\x04chat\"6\n" +
"\x11UserChatsResponse\x12!\n" +
"\x05chats\x18\x01 \x03(\v2\v.proto.ChatR\x05chats2\xf3\x03\n" +
"\x0eMessageService\x12;\n" +
"\n" +
"CreateChat\x12\x18.proto.CreateChatRequest\x1a\x13.proto.ChatResponse\x12@\n" +
"\vSendMessage\x12\x19.proto.SendMessageRequest\x1a\x16.proto.MessageResponse\x125\n" +
"\aGetChat\x12\x15.proto.GetChatRequest\x1a\x13.proto.ChatResponse\x12I\n" +
"\x0fGetChatMessages\x12\x1d.proto.GetChatMessagesRequest\x1a\x17.proto.MessagesResponse\x12D\n" +
"\fGetUserChats\x12\x1a.proto.GetUserChatsRequest\x1a\x18.proto.UserChatsResponse\x12P\n" +
"\x13UpdateMessageStatus\x12!.proto.UpdateMessageStatusRequest\x1a\x16.proto.MessageResponse\x12H\n" +
"\x0eStreamMessages\x12\x1c.proto.StreamMessagesRequest\x1a\x16.proto.MessageResponse0\x01B\n" +
"Z\b./;protob\x06proto3"
var (
file_messages_proto_rawDescOnce sync.Once
file_messages_proto_rawDescData []byte
)
func file_messages_proto_rawDescGZIP() []byte {
file_messages_proto_rawDescOnce.Do(func() {
file_messages_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_messages_proto_rawDesc), len(file_messages_proto_rawDesc)))
})
return file_messages_proto_rawDescData
}
var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
var file_messages_proto_goTypes = []any{
(*CreateChatRequest)(nil), // 0: proto.CreateChatRequest
(*SendMessageRequest)(nil), // 1: proto.SendMessageRequest
(*GetChatRequest)(nil), // 2: proto.GetChatRequest
(*GetChatMessagesRequest)(nil), // 3: proto.GetChatMessagesRequest
(*GetUserChatsRequest)(nil), // 4: proto.GetUserChatsRequest
(*UpdateMessageStatusRequest)(nil), // 5: proto.UpdateMessageStatusRequest
(*StreamMessagesRequest)(nil), // 6: proto.StreamMessagesRequest
(*Message)(nil), // 7: proto.Message
(*Chat)(nil), // 8: proto.Chat
(*MessageResponse)(nil), // 9: proto.MessageResponse
(*MessagesResponse)(nil), // 10: proto.MessagesResponse
(*ChatResponse)(nil), // 11: proto.ChatResponse
(*UserChatsResponse)(nil), // 12: proto.UserChatsResponse
(*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp
}
var file_messages_proto_depIdxs = []int32{
13, // 0: proto.Message.created_at:type_name -> google.protobuf.Timestamp
13, // 1: proto.Chat.created_at:type_name -> google.protobuf.Timestamp
13, // 2: proto.Chat.updated_at:type_name -> google.protobuf.Timestamp
7, // 3: proto.Chat.last_message:type_name -> proto.Message
7, // 4: proto.MessageResponse.message:type_name -> proto.Message
7, // 5: proto.MessagesResponse.messages:type_name -> proto.Message
8, // 6: proto.ChatResponse.chat:type_name -> proto.Chat
8, // 7: proto.UserChatsResponse.chats:type_name -> proto.Chat
0, // 8: proto.MessageService.CreateChat:input_type -> proto.CreateChatRequest
1, // 9: proto.MessageService.SendMessage:input_type -> proto.SendMessageRequest
2, // 10: proto.MessageService.GetChat:input_type -> proto.GetChatRequest
3, // 11: proto.MessageService.GetChatMessages:input_type -> proto.GetChatMessagesRequest
4, // 12: proto.MessageService.GetUserChats:input_type -> proto.GetUserChatsRequest
5, // 13: proto.MessageService.UpdateMessageStatus:input_type -> proto.UpdateMessageStatusRequest
6, // 14: proto.MessageService.StreamMessages:input_type -> proto.StreamMessagesRequest
11, // 15: proto.MessageService.CreateChat:output_type -> proto.ChatResponse
9, // 16: proto.MessageService.SendMessage:output_type -> proto.MessageResponse
11, // 17: proto.MessageService.GetChat:output_type -> proto.ChatResponse
10, // 18: proto.MessageService.GetChatMessages:output_type -> proto.MessagesResponse
12, // 19: proto.MessageService.GetUserChats:output_type -> proto.UserChatsResponse
9, // 20: proto.MessageService.UpdateMessageStatus:output_type -> proto.MessageResponse
9, // 21: proto.MessageService.StreamMessages:output_type -> proto.MessageResponse
15, // [15:22] is the sub-list for method output_type
8, // [8:15] is the sub-list for method input_type
8, // [8:8] is the sub-list for extension type_name
8, // [8:8] is the sub-list for extension extendee
0, // [0:8] is the sub-list for field type_name
}
func init() { file_messages_proto_init() }
func file_messages_proto_init() {
if File_messages_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_messages_proto_rawDesc), len(file_messages_proto_rawDesc)),
NumEnums: 0,
NumMessages: 13,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_messages_proto_goTypes,
DependencyIndexes: file_messages_proto_depIdxs,
MessageInfos: file_messages_proto_msgTypes,
}.Build()
File_messages_proto = out.File
file_messages_proto_goTypes = nil
file_messages_proto_depIdxs = nil
}

86
proto/messages.proto Normal file
View File

@ -0,0 +1,86 @@
syntax = "proto3";
package proto;
option go_package = "./;proto";
import "google/protobuf/timestamp.proto";
service MessageService {
rpc CreateChat (CreateChatRequest) returns (ChatResponse);
rpc SendMessage (SendMessageRequest) returns (MessageResponse);
rpc GetChat (GetChatRequest) returns (ChatResponse);
rpc GetChatMessages (GetChatMessagesRequest) returns (MessagesResponse);
rpc GetUserChats (GetUserChatsRequest) returns (UserChatsResponse);
rpc UpdateMessageStatus (UpdateMessageStatusRequest) returns (MessageResponse);
rpc StreamMessages (StreamMessagesRequest) returns (stream MessageResponse);
}
message CreateChatRequest {
int32 user1_id = 1;
int32 user2_id = 2;
}
message SendMessageRequest {
int32 chat_id = 1;
int32 sender_id = 2;
string content = 3;
}
message GetChatRequest {
int32 user1_id = 1;
int32 user2_id = 2;
}
message GetChatMessagesRequest {
int32 chat_id = 1;
int32 limit = 2;
int32 offset = 3;
}
message GetUserChatsRequest {
int32 user_id = 1;
}
message UpdateMessageStatusRequest {
int32 message_id = 1;
string status = 2;
}
message StreamMessagesRequest {
int32 user_id = 1;
}
message Message {
int32 id = 1;
int32 chat_id = 2;
int32 sender_id = 3;
string content = 4;
string status = 5;
google.protobuf.Timestamp created_at = 6;
}
message Chat {
int32 id = 1;
int32 user1_id = 2;
int32 user2_id = 3;
google.protobuf.Timestamp created_at = 4;
google.protobuf.Timestamp updated_at = 5;
Message last_message = 6;
}
message MessageResponse {
Message message = 1;
}
message MessagesResponse {
repeated Message messages = 1;
}
message ChatResponse {
Chat chat = 1;
}
message UserChatsResponse {
repeated Chat chats = 1;
}

353
proto/messages_grpc.pb.go Normal file
View File

@ -0,0 +1,353 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v3.21.12
// source: messages.proto
package proto
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
MessageService_CreateChat_FullMethodName = "/proto.MessageService/CreateChat"
MessageService_SendMessage_FullMethodName = "/proto.MessageService/SendMessage"
MessageService_GetChat_FullMethodName = "/proto.MessageService/GetChat"
MessageService_GetChatMessages_FullMethodName = "/proto.MessageService/GetChatMessages"
MessageService_GetUserChats_FullMethodName = "/proto.MessageService/GetUserChats"
MessageService_UpdateMessageStatus_FullMethodName = "/proto.MessageService/UpdateMessageStatus"
MessageService_StreamMessages_FullMethodName = "/proto.MessageService/StreamMessages"
)
// MessageServiceClient is the client API for MessageService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type MessageServiceClient interface {
CreateChat(ctx context.Context, in *CreateChatRequest, opts ...grpc.CallOption) (*ChatResponse, error)
SendMessage(ctx context.Context, in *SendMessageRequest, opts ...grpc.CallOption) (*MessageResponse, error)
GetChat(ctx context.Context, in *GetChatRequest, opts ...grpc.CallOption) (*ChatResponse, error)
GetChatMessages(ctx context.Context, in *GetChatMessagesRequest, opts ...grpc.CallOption) (*MessagesResponse, error)
GetUserChats(ctx context.Context, in *GetUserChatsRequest, opts ...grpc.CallOption) (*UserChatsResponse, error)
UpdateMessageStatus(ctx context.Context, in *UpdateMessageStatusRequest, opts ...grpc.CallOption) (*MessageResponse, error)
StreamMessages(ctx context.Context, in *StreamMessagesRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[MessageResponse], error)
}
type messageServiceClient struct {
cc grpc.ClientConnInterface
}
func NewMessageServiceClient(cc grpc.ClientConnInterface) MessageServiceClient {
return &messageServiceClient{cc}
}
func (c *messageServiceClient) CreateChat(ctx context.Context, in *CreateChatRequest, opts ...grpc.CallOption) (*ChatResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ChatResponse)
err := c.cc.Invoke(ctx, MessageService_CreateChat_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *messageServiceClient) SendMessage(ctx context.Context, in *SendMessageRequest, opts ...grpc.CallOption) (*MessageResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(MessageResponse)
err := c.cc.Invoke(ctx, MessageService_SendMessage_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *messageServiceClient) GetChat(ctx context.Context, in *GetChatRequest, opts ...grpc.CallOption) (*ChatResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ChatResponse)
err := c.cc.Invoke(ctx, MessageService_GetChat_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *messageServiceClient) GetChatMessages(ctx context.Context, in *GetChatMessagesRequest, opts ...grpc.CallOption) (*MessagesResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(MessagesResponse)
err := c.cc.Invoke(ctx, MessageService_GetChatMessages_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *messageServiceClient) GetUserChats(ctx context.Context, in *GetUserChatsRequest, opts ...grpc.CallOption) (*UserChatsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(UserChatsResponse)
err := c.cc.Invoke(ctx, MessageService_GetUserChats_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *messageServiceClient) UpdateMessageStatus(ctx context.Context, in *UpdateMessageStatusRequest, opts ...grpc.CallOption) (*MessageResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(MessageResponse)
err := c.cc.Invoke(ctx, MessageService_UpdateMessageStatus_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *messageServiceClient) StreamMessages(ctx context.Context, in *StreamMessagesRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[MessageResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &MessageService_ServiceDesc.Streams[0], MessageService_StreamMessages_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[StreamMessagesRequest, MessageResponse]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type MessageService_StreamMessagesClient = grpc.ServerStreamingClient[MessageResponse]
// MessageServiceServer is the server API for MessageService service.
// All implementations must embed UnimplementedMessageServiceServer
// for forward compatibility.
type MessageServiceServer interface {
CreateChat(context.Context, *CreateChatRequest) (*ChatResponse, error)
SendMessage(context.Context, *SendMessageRequest) (*MessageResponse, error)
GetChat(context.Context, *GetChatRequest) (*ChatResponse, error)
GetChatMessages(context.Context, *GetChatMessagesRequest) (*MessagesResponse, error)
GetUserChats(context.Context, *GetUserChatsRequest) (*UserChatsResponse, error)
UpdateMessageStatus(context.Context, *UpdateMessageStatusRequest) (*MessageResponse, error)
StreamMessages(*StreamMessagesRequest, grpc.ServerStreamingServer[MessageResponse]) error
mustEmbedUnimplementedMessageServiceServer()
}
// UnimplementedMessageServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedMessageServiceServer struct{}
func (UnimplementedMessageServiceServer) CreateChat(context.Context, *CreateChatRequest) (*ChatResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateChat not implemented")
}
func (UnimplementedMessageServiceServer) SendMessage(context.Context, *SendMessageRequest) (*MessageResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendMessage not implemented")
}
func (UnimplementedMessageServiceServer) GetChat(context.Context, *GetChatRequest) (*ChatResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetChat not implemented")
}
func (UnimplementedMessageServiceServer) GetChatMessages(context.Context, *GetChatMessagesRequest) (*MessagesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetChatMessages not implemented")
}
func (UnimplementedMessageServiceServer) GetUserChats(context.Context, *GetUserChatsRequest) (*UserChatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserChats not implemented")
}
func (UnimplementedMessageServiceServer) UpdateMessageStatus(context.Context, *UpdateMessageStatusRequest) (*MessageResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateMessageStatus not implemented")
}
func (UnimplementedMessageServiceServer) StreamMessages(*StreamMessagesRequest, grpc.ServerStreamingServer[MessageResponse]) error {
return status.Errorf(codes.Unimplemented, "method StreamMessages not implemented")
}
func (UnimplementedMessageServiceServer) mustEmbedUnimplementedMessageServiceServer() {}
func (UnimplementedMessageServiceServer) testEmbeddedByValue() {}
// UnsafeMessageServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to MessageServiceServer will
// result in compilation errors.
type UnsafeMessageServiceServer interface {
mustEmbedUnimplementedMessageServiceServer()
}
func RegisterMessageServiceServer(s grpc.ServiceRegistrar, srv MessageServiceServer) {
// If the following call pancis, it indicates UnimplementedMessageServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&MessageService_ServiceDesc, srv)
}
func _MessageService_CreateChat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateChatRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MessageServiceServer).CreateChat(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MessageService_CreateChat_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MessageServiceServer).CreateChat(ctx, req.(*CreateChatRequest))
}
return interceptor(ctx, in, info, handler)
}
func _MessageService_SendMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendMessageRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MessageServiceServer).SendMessage(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MessageService_SendMessage_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MessageServiceServer).SendMessage(ctx, req.(*SendMessageRequest))
}
return interceptor(ctx, in, info, handler)
}
func _MessageService_GetChat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetChatRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MessageServiceServer).GetChat(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MessageService_GetChat_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MessageServiceServer).GetChat(ctx, req.(*GetChatRequest))
}
return interceptor(ctx, in, info, handler)
}
func _MessageService_GetChatMessages_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetChatMessagesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MessageServiceServer).GetChatMessages(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MessageService_GetChatMessages_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MessageServiceServer).GetChatMessages(ctx, req.(*GetChatMessagesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _MessageService_GetUserChats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetUserChatsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MessageServiceServer).GetUserChats(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MessageService_GetUserChats_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MessageServiceServer).GetUserChats(ctx, req.(*GetUserChatsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _MessageService_UpdateMessageStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateMessageStatusRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MessageServiceServer).UpdateMessageStatus(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MessageService_UpdateMessageStatus_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MessageServiceServer).UpdateMessageStatus(ctx, req.(*UpdateMessageStatusRequest))
}
return interceptor(ctx, in, info, handler)
}
func _MessageService_StreamMessages_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(StreamMessagesRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(MessageServiceServer).StreamMessages(m, &grpc.GenericServerStream[StreamMessagesRequest, MessageResponse]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type MessageService_StreamMessagesServer = grpc.ServerStreamingServer[MessageResponse]
// MessageService_ServiceDesc is the grpc.ServiceDesc for MessageService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var MessageService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "proto.MessageService",
HandlerType: (*MessageServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "CreateChat",
Handler: _MessageService_CreateChat_Handler,
},
{
MethodName: "SendMessage",
Handler: _MessageService_SendMessage_Handler,
},
{
MethodName: "GetChat",
Handler: _MessageService_GetChat_Handler,
},
{
MethodName: "GetChatMessages",
Handler: _MessageService_GetChatMessages_Handler,
},
{
MethodName: "GetUserChats",
Handler: _MessageService_GetUserChats_Handler,
},
{
MethodName: "UpdateMessageStatus",
Handler: _MessageService_UpdateMessageStatus_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "StreamMessages",
Handler: _MessageService_StreamMessages_Handler,
ServerStreams: true,
},
},
Metadata: "messages.proto",
}