This commit is contained in:
parent
ecf62c7346
commit
222469e40a
2
.env
2
.env
@ -13,4 +13,4 @@ SMTP_FROM=info@tailly.ru
|
|||||||
AppURL="https://tailly.ru"
|
AppURL="https://tailly.ru"
|
||||||
MESSAGE_SERVICE_ADDRESS="tailly_messages:50052"
|
MESSAGE_SERVICE_ADDRESS="tailly_messages:50052"
|
||||||
SUBSCRIBE_SERVICE_ADDRESS="tailly_subscribers:50053"
|
SUBSCRIBE_SERVICE_ADDRESS="tailly_subscribers:50053"
|
||||||
CLIP_SERVICE_ADDRESS="tailly_clips:50054"
|
CLIP_SERVICE_ADDRESS="tailly_clips:50054"
|
||||||
|
|||||||
@ -132,7 +132,7 @@ func main() {
|
|||||||
userService := service.NewUserService(userRepo)
|
userService := service.NewUserService(userRepo)
|
||||||
postService := service.NewPostService(postRepo)
|
postService := service.NewPostService(postRepo)
|
||||||
commentService := service.NewCommentService(commentRepo, postRepo)
|
commentService := service.NewCommentService(commentRepo, postRepo)
|
||||||
likeService := service.NewLikeService(likeRepo, postRepo, userRepo)
|
likeService := service.NewLikeService(likeRepo, postRepo)
|
||||||
auditService := service.NewAuditService(auditRepo)
|
auditService := service.NewAuditService(auditRepo)
|
||||||
recoveryService := service.NewRecoveryService(recoveryRepo, userRepo, sessionRepo, deviceRepo, mailService)
|
recoveryService := service.NewRecoveryService(recoveryRepo, userRepo, sessionRepo, deviceRepo, mailService)
|
||||||
sessionService := service.NewSessionService(sessionRepo, deviceRepo, userRepo, mailService)
|
sessionService := service.NewSessionService(sessionRepo, deviceRepo, userRepo, mailService)
|
||||||
|
|||||||
@ -50,6 +50,5 @@ models:
|
|||||||
ClipComment:
|
ClipComment:
|
||||||
model: tailly_back_v2/internal/domain.ClipComment
|
model: tailly_back_v2/internal/domain.ClipComment
|
||||||
|
|
||||||
|
|
||||||
autobind:
|
autobind:
|
||||||
- "tailly_back_v2/internal/domain"
|
- "tailly_back_v2/internal/domain"
|
||||||
@ -23,7 +23,6 @@ type Post struct {
|
|||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
AuthorID int `json:"authorId"`
|
AuthorID int `json:"authorId"`
|
||||||
Author *User `json:"author,omitempty"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
}
|
}
|
||||||
@ -40,46 +39,10 @@ type Comment struct {
|
|||||||
|
|
||||||
// Лайк к посту
|
// Лайк к посту
|
||||||
type Like struct {
|
type Like struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
PostID int `json:"postId"`
|
PostID int `json:"postId"`
|
||||||
UserID int `json:"userId"`
|
UserID int `json:"userId"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
IsRead bool `json:"isRead"`
|
|
||||||
NotifiedAt time.Time `json:"notifiedAt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LikeNotification struct {
|
|
||||||
ID int `json:"id"`
|
|
||||||
LikerID int `json:"-"` // Для внутреннего использования
|
|
||||||
Liker *User `json:"liker"` // Для GraphQL
|
|
||||||
LikerUsername string `json:"likerUsername"` // Добавьте это поле
|
|
||||||
LikerAvatar string `json:"likerAvatar"` // Добавьте это поле
|
|
||||||
PostID int `json:"-"` // Для внутреннего использования
|
|
||||||
Post *Post `json:"post"` // Для GraphQL
|
|
||||||
PostTitle string `json:"postTitle"` // Добавьте это поле
|
|
||||||
PostAuthorID int `json:"postAuthorId"` // Добавьте это поле
|
|
||||||
IsRead bool `json:"isRead"`
|
|
||||||
CreatedAt time.Time `json:"-"`
|
|
||||||
CreatedAtStr string `json:"createdAt"` // Для GraphQL
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вспомогательные методы
|
|
||||||
func (n *LikeNotification) SetLiker(user *User) {
|
|
||||||
n.Liker = user
|
|
||||||
n.LikerID = user.ID
|
|
||||||
n.LikerUsername = user.Username // Добавьте эту строку
|
|
||||||
n.LikerAvatar = user.Avatar // Добавьте эту строку
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *LikeNotification) SetPost(post *Post) {
|
|
||||||
n.Post = post
|
|
||||||
n.PostID = post.ID
|
|
||||||
n.PostTitle = post.Title // Добавьте эту строку
|
|
||||||
n.PostAuthorID = post.AuthorID // Добавьте эту строку
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *LikeNotification) SetCreatedAtStr() {
|
|
||||||
n.CreatedAtStr = n.CreatedAt.Format(time.RFC3339)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Токены для аутентификации
|
// Токены для аутентификации
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -23,134 +23,12 @@ func (r *likeResolver) Post(ctx context.Context, obj *domain.Like) (*domain.Post
|
|||||||
|
|
||||||
// User is the resolver for the user field.
|
// User is the resolver for the user field.
|
||||||
func (r *likeResolver) User(ctx context.Context, obj *domain.Like) (*domain.User, error) {
|
func (r *likeResolver) User(ctx context.Context, obj *domain.Like) (*domain.User, error) {
|
||||||
user, err := r.Services.User.GetByID(ctx, obj.UserID)
|
// This would typically use a UserService to fetch the user
|
||||||
if err != nil {
|
// For now, we'll return nil as the user service isn't shown in the provided code
|
||||||
return nil, fmt.Errorf("failed to get user: %w", err)
|
return nil, nil
|
||||||
}
|
|
||||||
return user, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAt is the resolver for the createdAt field.
|
// CreatedAt is the resolver for the createdAt field.
|
||||||
func (r *likeResolver) CreatedAt(ctx context.Context, obj *domain.Like) (string, error) {
|
func (r *likeResolver) CreatedAt(ctx context.Context, obj *domain.Like) (string, error) {
|
||||||
return obj.CreatedAt.Format(time.RFC3339), nil
|
return obj.CreatedAt.Format(time.RFC3339), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRead is the resolver for the isRead field.
|
|
||||||
func (r *likeResolver) IsRead(ctx context.Context, obj *domain.Like) (bool, error) {
|
|
||||||
return obj.IsRead, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotifiedAt is the resolver for the notifiedAt field.
|
|
||||||
func (r *likeResolver) NotifiedAt(ctx context.Context, obj *domain.Like) (*string, error) {
|
|
||||||
if obj.NotifiedAt.IsZero() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
formatted := obj.NotifiedAt.Format(time.RFC3339)
|
|
||||||
return &formatted, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkLikeNotificationAsRead is the resolver for the markLikeNotificationAsRead field.
|
|
||||||
func (r *mutationResolver) MarkLikeNotificationAsRead(ctx context.Context, notificationID int) (*MarkLikeNotificationReadResult, error) {
|
|
||||||
userID, err := getUserIDFromContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("authentication required: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = r.Services.Like.MarkLikeNotificationAsRead(ctx, notificationID, userID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to mark notification as read: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &MarkLikeNotificationReadResult{
|
|
||||||
Success: true,
|
|
||||||
Message: "Notification marked as read successfully",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkAllLikeNotificationsAsRead is the resolver for the markAllLikeNotificationsAsRead field.
|
|
||||||
func (r *mutationResolver) MarkAllLikeNotificationsAsRead(ctx context.Context) (*MarkLikeNotificationReadResult, error) {
|
|
||||||
userID, err := getUserIDFromContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("authentication required: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = r.Services.Like.MarkAllLikeNotificationsAsRead(ctx, userID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to mark all notifications as read: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &MarkLikeNotificationReadResult{
|
|
||||||
Success: true,
|
|
||||||
Message: "All notifications marked as read successfully",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
func (r *queryResolver) GetLikeNotifications(ctx context.Context, unreadOnly *bool, limit *int, offset *int) (*LikeNotificationsResponse, error) {
|
|
||||||
userID, err := getUserIDFromContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("authentication required: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
unreadOnlyVal := false
|
|
||||||
if unreadOnly != nil {
|
|
||||||
unreadOnlyVal = *unreadOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
limitVal := 20
|
|
||||||
if limit != nil {
|
|
||||||
limitVal = *limit
|
|
||||||
}
|
|
||||||
|
|
||||||
offsetVal := 0
|
|
||||||
if offset != nil {
|
|
||||||
offsetVal = *offset
|
|
||||||
}
|
|
||||||
|
|
||||||
notifications, totalCount, unreadCount, err := r.Services.Like.GetLikeNotifications(ctx, userID, unreadOnlyVal, limitVal, offsetVal)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get like notifications: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Заполняем дополнительные поля для GraphQL
|
|
||||||
for _, notification := range notifications {
|
|
||||||
// Получаем информацию о пользователе, который поставил лайк
|
|
||||||
likerUser, err := r.Services.User.GetByID(ctx, notification.LikerID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get liker user: %w", err)
|
|
||||||
}
|
|
||||||
notification.SetLiker(likerUser)
|
|
||||||
|
|
||||||
// Получаем информацию о посте
|
|
||||||
post, err := r.Services.Post.GetByID(ctx, notification.PostID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get post: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Получаем автора поста
|
|
||||||
postAuthor, err := r.Services.User.GetByID(ctx, post.AuthorID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get post author: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Устанавливаем автора поста
|
|
||||||
post.AuthorID = postAuthor.ID
|
|
||||||
notification.SetPost(post)
|
|
||||||
|
|
||||||
// Форматируем дату
|
|
||||||
notification.SetCreatedAtStr()
|
|
||||||
}
|
|
||||||
|
|
||||||
return &LikeNotificationsResponse{
|
|
||||||
Notifications: notifications,
|
|
||||||
TotalCount: totalCount,
|
|
||||||
UnreadCount: unreadCount,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LikeNotification returns LikeNotificationResolver implementation.
|
|
||||||
func (r *Resolver) LikeNotification() LikeNotificationResolver { return &likeNotificationResolver{r} }
|
|
||||||
|
|
||||||
type likeNotificationResolver struct{ *Resolver }
|
|
||||||
|
|
||||||
func (r *likeNotificationResolver) CreatedAt(ctx context.Context, obj *domain.LikeNotification) (string, error) {
|
|
||||||
return obj.CreatedAt.Format(time.RFC3339), nil
|
|
||||||
}
|
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"tailly_back_v2/internal/domain"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type FollowResult struct {
|
type FollowResult struct {
|
||||||
@ -39,17 +38,6 @@ type FollowingResponse struct {
|
|||||||
TotalCount int `json:"totalCount"`
|
TotalCount int `json:"totalCount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LikeNotificationsResponse struct {
|
|
||||||
Notifications []*domain.LikeNotification `json:"notifications"`
|
|
||||||
TotalCount int `json:"totalCount"`
|
|
||||||
UnreadCount int `json:"unreadCount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MarkLikeNotificationReadResult struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MarkNotificationReadResult struct {
|
type MarkNotificationReadResult struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
|
|||||||
@ -17,11 +17,6 @@ type User {
|
|||||||
limit: Int = 20
|
limit: Int = 20
|
||||||
offset: Int = 0
|
offset: Int = 0
|
||||||
): NotificationsResponse!
|
): NotificationsResponse!
|
||||||
likeNotifications(
|
|
||||||
unreadOnly: Boolean = false
|
|
||||||
limit: Int = 20
|
|
||||||
offset: Int = 0
|
|
||||||
): LikeNotificationsResponse!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Пост в блоге
|
# Пост в блоге
|
||||||
@ -50,12 +45,10 @@ type Comment {
|
|||||||
|
|
||||||
# Лайк к посту
|
# Лайк к посту
|
||||||
type Like {
|
type Like {
|
||||||
id: Int! # Уникальный идентификатор
|
id: Int! # Уникальный идентификатор
|
||||||
post: Post! # Пост, который лайкнули
|
post: Post! # Пост, который лайкнули
|
||||||
user: User! # Пользователь, который поставил лайк
|
user: User! # Пользователь, который поставил лайк
|
||||||
createdAt: String! # Дата создания
|
createdAt: String! # Дата создания
|
||||||
isRead: Boolean! # Прочитано ли уведомление
|
|
||||||
notifiedAt: String # Когда было отправлено уведомление
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tokens {
|
type Tokens {
|
||||||
@ -177,23 +170,7 @@ type MarkNotificationReadResult {
|
|||||||
message: String!
|
message: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
type LikeNotification {
|
|
||||||
id: Int!
|
|
||||||
liker: User!
|
|
||||||
post: Post!
|
|
||||||
isRead: Boolean!
|
|
||||||
createdAt: String!
|
|
||||||
}
|
|
||||||
type LikeNotificationsResponse {
|
|
||||||
notifications: [LikeNotification!]!
|
|
||||||
totalCount: Int!
|
|
||||||
unreadCount: Int!
|
|
||||||
}
|
|
||||||
|
|
||||||
type MarkLikeNotificationReadResult {
|
|
||||||
success: Boolean!
|
|
||||||
message: String!
|
|
||||||
}
|
|
||||||
# Клип
|
# Клип
|
||||||
type Clip {
|
type Clip {
|
||||||
id: Int!
|
id: Int!
|
||||||
@ -269,12 +246,6 @@ type Query {
|
|||||||
clipComments(clipId: Int!, limit: Int, offset: Int): [ClipComment!]! # Комментарии клипа
|
clipComments(clipId: Int!, limit: Int, offset: Int): [ClipComment!]! # Комментарии клипа
|
||||||
clipLikes(clipId: Int!, limit: Int, offset: Int): [ClipLike!]! # Лайки клипа
|
clipLikes(clipId: Int!, limit: Int, offset: Int): [ClipLike!]! # Лайки клипа
|
||||||
isLiked(clipId: Int!): Boolean! # Проверить лайк текущего пользователя
|
isLiked(clipId: Int!): Boolean! # Проверить лайк текущего пользователя
|
||||||
|
|
||||||
getLikeNotifications(
|
|
||||||
unreadOnly: Boolean = false
|
|
||||||
limit: Int = 20
|
|
||||||
offset: Int = 0
|
|
||||||
): LikeNotificationsResponse!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -323,8 +294,6 @@ type Mutation {
|
|||||||
unlikeClip(clipId: Int!): Boolean! # Убрать лайк
|
unlikeClip(clipId: Int!): Boolean! # Убрать лайк
|
||||||
createClipComment(clipId: Int!, content: String!): ClipComment! # Создать комментарий
|
createClipComment(clipId: Int!, content: String!): ClipComment! # Создать комментарий
|
||||||
deleteClipComment(commentId: Int!): Boolean! # Удалить комментарий
|
deleteClipComment(commentId: Int!): Boolean! # Удалить комментарий
|
||||||
markLikeNotificationAsRead(notificationId: Int!): MarkLikeNotificationReadResult!
|
|
||||||
markAllLikeNotificationsAsRead: MarkLikeNotificationReadResult!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Subscription {
|
type Subscription {
|
||||||
|
|||||||
@ -256,71 +256,3 @@ func (r *userResolver) SubscriptionNotifications(ctx context.Context, obj *domai
|
|||||||
UnreadCount: int(res.UnreadCount),
|
UnreadCount: int(res.UnreadCount),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LikeNotifications is the resolver for the likeNotifications field.
|
|
||||||
func (r *userResolver) LikeNotifications(ctx context.Context, obj *domain.User, unreadOnly *bool, limit *int, offset *int) (*LikeNotificationsResponse, error) {
|
|
||||||
currentUserID, err := getUserIDFromContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("authentication required: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Можно смотреть только свои уведомления
|
|
||||||
if obj.ID != currentUserID {
|
|
||||||
return nil, fmt.Errorf("access denied: can only view your own notifications")
|
|
||||||
}
|
|
||||||
|
|
||||||
unreadOnlyVal := false
|
|
||||||
if unreadOnly != nil {
|
|
||||||
unreadOnlyVal = *unreadOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
limitVal := 20
|
|
||||||
if limit != nil {
|
|
||||||
limitVal = *limit
|
|
||||||
}
|
|
||||||
|
|
||||||
offsetVal := 0
|
|
||||||
if offset != nil {
|
|
||||||
offsetVal = *offset
|
|
||||||
}
|
|
||||||
|
|
||||||
notifications, totalCount, unreadCount, err := r.Services.Like.GetLikeNotifications(ctx, obj.ID, unreadOnlyVal, limitVal, offsetVal)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get like notifications: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Заполняем дополнительные поля для GraphQL
|
|
||||||
for _, notification := range notifications {
|
|
||||||
// Получаем информацию о пользователе, который поставил лайк
|
|
||||||
likerUser, err := r.Services.User.GetByID(ctx, notification.LikerID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get liker user: %w", err)
|
|
||||||
}
|
|
||||||
notification.SetLiker(likerUser)
|
|
||||||
|
|
||||||
// Получаем информацию о посте
|
|
||||||
post, err := r.Services.Post.GetByID(ctx, notification.PostID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get post: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Получаем автора поста
|
|
||||||
postAuthor, err := r.Services.User.GetByID(ctx, post.AuthorID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get post author: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Устанавливаем автора поста
|
|
||||||
post.Author = postAuthor
|
|
||||||
notification.SetPost(post)
|
|
||||||
|
|
||||||
// Форматируем дату
|
|
||||||
notification.SetCreatedAtStr()
|
|
||||||
}
|
|
||||||
|
|
||||||
return &LikeNotificationsResponse{
|
|
||||||
Notifications: notifications,
|
|
||||||
TotalCount: totalCount,
|
|
||||||
UnreadCount: unreadCount,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@ -19,18 +19,22 @@ type LikeRepository interface {
|
|||||||
GetByUserAndPost(ctx context.Context, userID, postID int) (*domain.Like, error)
|
GetByUserAndPost(ctx context.Context, userID, postID int) (*domain.Like, error)
|
||||||
Delete(ctx context.Context, id int) error
|
Delete(ctx context.Context, id int) error
|
||||||
DeleteByUserAndPost(ctx context.Context, userID, postID int) error
|
DeleteByUserAndPost(ctx context.Context, userID, postID int) error
|
||||||
// Новые методы для уведомлений
|
|
||||||
GetUnreadLikeNotifications(ctx context.Context, userID int, limit, offset int) ([]*domain.LikeNotification, error)
|
|
||||||
GetAllLikeNotifications(ctx context.Context, userID int, limit, offset int) ([]*domain.LikeNotification, error)
|
|
||||||
MarkLikeNotificationAsRead(ctx context.Context, notificationID, userID int) error
|
|
||||||
MarkAllLikeNotificationsAsRead(ctx context.Context, userID int) error
|
|
||||||
GetLikeNotificationCounts(ctx context.Context, userID int) (totalCount, unreadCount int, err error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type likeRepository struct {
|
type likeRepository struct {
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *likeRepository) GetByID(ctx context.Context, id int) (*domain.Like, error) {
|
||||||
|
//TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *likeRepository) Delete(ctx context.Context, id int) error {
|
||||||
|
//TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
func NewLikeRepository(db *sql.DB) *likeRepository {
|
func NewLikeRepository(db *sql.DB) *likeRepository {
|
||||||
return &likeRepository{db: db}
|
return &likeRepository{db: db}
|
||||||
}
|
}
|
||||||
@ -45,8 +49,8 @@ func (r *likeRepository) Create(ctx context.Context, like *domain.Like) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := `
|
query := `
|
||||||
INSERT INTO likes (post_id, user_id, created_at, is_read, notified_at)
|
INSERT INTO likes (post_id, user_id, created_at)
|
||||||
VALUES ($1, $2, $3, $4, $5)
|
VALUES ($1, $2, $3)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -54,43 +58,14 @@ func (r *likeRepository) Create(ctx context.Context, like *domain.Like) error {
|
|||||||
like.PostID,
|
like.PostID,
|
||||||
like.UserID,
|
like.UserID,
|
||||||
like.CreatedAt,
|
like.CreatedAt,
|
||||||
like.IsRead,
|
|
||||||
like.NotifiedAt,
|
|
||||||
).Scan(&like.ID)
|
).Scan(&like.ID)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *likeRepository) GetByID(ctx context.Context, id int) (*domain.Like, error) {
|
|
||||||
query := `
|
|
||||||
SELECT id, post_id, user_id, created_at, is_read, notified_at
|
|
||||||
FROM likes
|
|
||||||
WHERE id = $1
|
|
||||||
`
|
|
||||||
|
|
||||||
like := &domain.Like{}
|
|
||||||
err := r.db.QueryRowContext(ctx, query, id).Scan(
|
|
||||||
&like.ID,
|
|
||||||
&like.PostID,
|
|
||||||
&like.UserID,
|
|
||||||
&like.CreatedAt,
|
|
||||||
&like.IsRead,
|
|
||||||
&like.NotifiedAt,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return nil, ErrLikeNotFound
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return like, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *likeRepository) GetByPostID(ctx context.Context, postID int) ([]*domain.Like, error) {
|
func (r *likeRepository) GetByPostID(ctx context.Context, postID int) ([]*domain.Like, error) {
|
||||||
query := `
|
query := `
|
||||||
SELECT id, post_id, user_id, created_at, is_read, notified_at
|
SELECT id, post_id, user_id, created_at
|
||||||
FROM likes
|
FROM likes
|
||||||
WHERE post_id = $1
|
WHERE post_id = $1
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
@ -110,8 +85,6 @@ func (r *likeRepository) GetByPostID(ctx context.Context, postID int) ([]*domain
|
|||||||
&like.PostID,
|
&like.PostID,
|
||||||
&like.UserID,
|
&like.UserID,
|
||||||
&like.CreatedAt,
|
&like.CreatedAt,
|
||||||
&like.IsRead,
|
|
||||||
&like.NotifiedAt,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -122,9 +95,18 @@ func (r *likeRepository) GetByPostID(ctx context.Context, postID int) ([]*domain
|
|||||||
return likes, nil
|
return likes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *likeRepository) DeleteByUserAndPost(ctx context.Context, userID, postID int) error {
|
||||||
|
query := `
|
||||||
|
DELETE FROM likes
|
||||||
|
WHERE user_id = $1 AND post_id = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
_, err := r.db.ExecContext(ctx, query, userID, postID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
func (r *likeRepository) GetByUserAndPost(ctx context.Context, userID, postID int) (*domain.Like, error) {
|
func (r *likeRepository) GetByUserAndPost(ctx context.Context, userID, postID int) (*domain.Like, error) {
|
||||||
query := `
|
query := `
|
||||||
SELECT id, post_id, user_id, created_at, is_read, notified_at
|
SELECT id, post_id, user_id, created_at
|
||||||
FROM likes
|
FROM likes
|
||||||
WHERE user_id = $1 AND post_id = $2
|
WHERE user_id = $1 AND post_id = $2
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
@ -136,8 +118,6 @@ func (r *likeRepository) GetByUserAndPost(ctx context.Context, userID, postID in
|
|||||||
&like.PostID,
|
&like.PostID,
|
||||||
&like.UserID,
|
&like.UserID,
|
||||||
&like.CreatedAt,
|
&like.CreatedAt,
|
||||||
&like.IsRead,
|
|
||||||
&like.NotifiedAt,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -149,166 +129,3 @@ func (r *likeRepository) GetByUserAndPost(ctx context.Context, userID, postID in
|
|||||||
|
|
||||||
return like, nil
|
return like, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *likeRepository) Delete(ctx context.Context, id int) error {
|
|
||||||
query := `DELETE FROM likes WHERE id = $1`
|
|
||||||
_, err := r.db.ExecContext(ctx, query, id)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *likeRepository) DeleteByUserAndPost(ctx context.Context, userID, postID int) error {
|
|
||||||
query := `DELETE FROM likes WHERE user_id = $1 AND post_id = $2`
|
|
||||||
_, err := r.db.ExecContext(ctx, query, userID, postID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Новые методы для уведомлений
|
|
||||||
|
|
||||||
func (r *likeRepository) GetUnreadLikeNotifications(ctx context.Context, userID int, limit, offset int) ([]*domain.LikeNotification, error) {
|
|
||||||
query := `
|
|
||||||
SELECT l.id, l.post_id, l.user_id as liker_id, l.created_at, l.is_read,
|
|
||||||
u.username as liker_username, u.avatar as liker_avatar,
|
|
||||||
p.title as post_title, p.author_id as post_author_id
|
|
||||||
FROM likes l
|
|
||||||
JOIN users u ON l.user_id = u.id
|
|
||||||
JOIN posts p ON l.post_id = p.id
|
|
||||||
WHERE p.author_id = $1 AND l.is_read = false
|
|
||||||
ORDER BY l.created_at DESC
|
|
||||||
LIMIT $2 OFFSET $3
|
|
||||||
`
|
|
||||||
|
|
||||||
rows, err := r.db.QueryContext(ctx, query, userID, limit, offset)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var notifications []*domain.LikeNotification
|
|
||||||
for rows.Next() {
|
|
||||||
notification := &domain.LikeNotification{}
|
|
||||||
err := rows.Scan(
|
|
||||||
¬ification.ID,
|
|
||||||
¬ification.PostID,
|
|
||||||
¬ification.LikerID,
|
|
||||||
¬ification.CreatedAt,
|
|
||||||
¬ification.IsRead,
|
|
||||||
¬ification.LikerUsername,
|
|
||||||
¬ification.LikerAvatar,
|
|
||||||
¬ification.PostTitle,
|
|
||||||
¬ification.PostAuthorID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
notifications = append(notifications, notification)
|
|
||||||
}
|
|
||||||
|
|
||||||
return notifications, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *likeRepository) GetAllLikeNotifications(ctx context.Context, userID int, limit, offset int) ([]*domain.LikeNotification, error) {
|
|
||||||
query := `
|
|
||||||
SELECT l.id, l.post_id, l.user_id as liker_id, l.created_at, l.is_read,
|
|
||||||
u.username as liker_username, u.avatar as liker_avatar,
|
|
||||||
p.title as post_title, p.author_id as post_author_id
|
|
||||||
FROM likes l
|
|
||||||
JOIN users u ON l.user_id = u.id
|
|
||||||
JOIN posts p ON l.post_id = p.id
|
|
||||||
WHERE p.author_id = $1
|
|
||||||
ORDER BY l.created_at DESC
|
|
||||||
LIMIT $2 OFFSET $3
|
|
||||||
`
|
|
||||||
|
|
||||||
rows, err := r.db.QueryContext(ctx, query, userID, limit, offset)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var notifications []*domain.LikeNotification
|
|
||||||
for rows.Next() {
|
|
||||||
notification := &domain.LikeNotification{}
|
|
||||||
err := rows.Scan(
|
|
||||||
¬ification.ID,
|
|
||||||
¬ification.PostID,
|
|
||||||
¬ification.LikerID,
|
|
||||||
¬ification.CreatedAt,
|
|
||||||
¬ification.IsRead,
|
|
||||||
¬ification.LikerUsername,
|
|
||||||
¬ification.LikerAvatar,
|
|
||||||
¬ification.PostTitle,
|
|
||||||
¬ification.PostAuthorID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
notifications = append(notifications, notification)
|
|
||||||
}
|
|
||||||
|
|
||||||
return notifications, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *likeRepository) MarkLikeNotificationAsRead(ctx context.Context, notificationID, userID int) error {
|
|
||||||
query := `
|
|
||||||
UPDATE likes
|
|
||||||
SET is_read = true
|
|
||||||
WHERE id = $1 AND post_id IN (
|
|
||||||
SELECT id FROM posts WHERE author_id = $2
|
|
||||||
)
|
|
||||||
`
|
|
||||||
result, err := r.db.ExecContext(ctx, query, notificationID, userID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rowsAffected, err := result.RowsAffected()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if rowsAffected == 0 {
|
|
||||||
return ErrLikeNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *likeRepository) MarkAllLikeNotificationsAsRead(ctx context.Context, userID int) error {
|
|
||||||
query := `
|
|
||||||
UPDATE likes
|
|
||||||
SET is_read = true
|
|
||||||
WHERE post_id IN (
|
|
||||||
SELECT id FROM posts WHERE author_id = $1
|
|
||||||
) AND is_read = false
|
|
||||||
`
|
|
||||||
_, err := r.db.ExecContext(ctx, query, userID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *likeRepository) GetLikeNotificationCounts(ctx context.Context, userID int) (totalCount, unreadCount int, err error) {
|
|
||||||
queryTotal := `
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM likes l
|
|
||||||
JOIN posts p ON l.post_id = p.id
|
|
||||||
WHERE p.author_id = $1
|
|
||||||
`
|
|
||||||
|
|
||||||
queryUnread := `
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM likes l
|
|
||||||
JOIN posts p ON l.post_id = p.id
|
|
||||||
WHERE p.author_id = $1 AND l.is_read = false
|
|
||||||
`
|
|
||||||
|
|
||||||
err = r.db.QueryRowContext(ctx, queryTotal, userID).Scan(&totalCount)
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = r.db.QueryRowContext(ctx, queryUnread, userID).Scan(&unreadCount)
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalCount, unreadCount, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@ -15,33 +15,26 @@ type LikeService interface {
|
|||||||
GetByPostID(ctx context.Context, postID int) ([]*domain.Like, error)
|
GetByPostID(ctx context.Context, postID int) ([]*domain.Like, error)
|
||||||
GetCountForPost(ctx context.Context, postID int) (int, error)
|
GetCountForPost(ctx context.Context, postID int) (int, error)
|
||||||
CheckIfLiked(ctx context.Context, userID, postID int) (bool, error)
|
CheckIfLiked(ctx context.Context, userID, postID int) (bool, error)
|
||||||
// Новые методы для уведомлений
|
|
||||||
GetLikeNotifications(ctx context.Context, userID int, unreadOnly bool, limit, offset int) ([]*domain.LikeNotification, int, int, error)
|
|
||||||
MarkLikeNotificationAsRead(ctx context.Context, notificationID, userID int) error
|
|
||||||
MarkAllLikeNotificationsAsRead(ctx context.Context, userID int) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Реализация сервиса лайков
|
// Реализация сервиса лайков
|
||||||
type likeService struct {
|
type likeService struct {
|
||||||
likeRepo repository.LikeRepository
|
likeRepo repository.LikeRepository
|
||||||
postRepo repository.PostRepository
|
postRepo repository.PostRepository // Для проверки существования поста
|
||||||
userRepo repository.UserRepository // Добавляем для получения информации о пользователях
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Конструктор сервиса
|
// Конструктор сервиса
|
||||||
func NewLikeService(likeRepo repository.LikeRepository, postRepo repository.PostRepository, userRepo repository.UserRepository) LikeService {
|
func NewLikeService(likeRepo repository.LikeRepository, postRepo repository.PostRepository) LikeService {
|
||||||
return &likeService{
|
return &likeService{
|
||||||
likeRepo: likeRepo,
|
likeRepo: likeRepo,
|
||||||
postRepo: postRepo,
|
postRepo: postRepo,
|
||||||
userRepo: userRepo,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Поставить лайк посту
|
// Поставить лайк посту
|
||||||
func (s *likeService) LikePost(ctx context.Context, userID, postID int) (*domain.Like, error) {
|
func (s *likeService) LikePost(ctx context.Context, userID, postID int) (*domain.Like, error) {
|
||||||
// Проверяем существование поста
|
// Проверяем существование поста
|
||||||
post, err := s.postRepo.GetByID(ctx, postID)
|
if _, err := s.postRepo.GetByID(ctx, postID); err != nil {
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, repository.ErrPostNotFound) {
|
if errors.Is(err, repository.ErrPostNotFound) {
|
||||||
return nil, errors.New("post not found")
|
return nil, errors.New("post not found")
|
||||||
}
|
}
|
||||||
@ -55,17 +48,10 @@ func (s *likeService) LikePost(ctx context.Context, userID, postID int) (*domain
|
|||||||
return nil, errors.New("you have already liked this post")
|
return nil, errors.New("you have already liked this post")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что пользователь не лайкает свой собственный пост
|
|
||||||
if post.AuthorID == userID {
|
|
||||||
return nil, errors.New("cannot like your own post")
|
|
||||||
}
|
|
||||||
|
|
||||||
like := &domain.Like{
|
like := &domain.Like{
|
||||||
PostID: postID,
|
PostID: postID,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
IsRead: false,
|
|
||||||
NotifiedAt: time.Now(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.likeRepo.Create(ctx, like); err != nil {
|
if err := s.likeRepo.Create(ctx, like); err != nil {
|
||||||
@ -110,6 +96,7 @@ func (s *likeService) GetByPostID(ctx context.Context, postID int) ([]*domain.Li
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Возвращаем пустой слайс вместо nil
|
||||||
if likes == nil {
|
if likes == nil {
|
||||||
return []*domain.Like{}, nil
|
return []*domain.Like{}, nil
|
||||||
}
|
}
|
||||||
@ -137,35 +124,3 @@ func (s *likeService) CheckIfLiked(ctx context.Context, userID, postID int) (boo
|
|||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Новые методы для уведомлений
|
|
||||||
|
|
||||||
func (s *likeService) GetLikeNotifications(ctx context.Context, userID int, unreadOnly bool, limit, offset int) ([]*domain.LikeNotification, int, int, error) {
|
|
||||||
var notifications []*domain.LikeNotification
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if unreadOnly {
|
|
||||||
notifications, err = s.likeRepo.GetUnreadLikeNotifications(ctx, userID, limit, offset)
|
|
||||||
} else {
|
|
||||||
notifications, err = s.likeRepo.GetAllLikeNotifications(ctx, userID, limit, offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
totalCount, unreadCount, err := s.likeRepo.GetLikeNotificationCounts(ctx, userID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return notifications, totalCount, unreadCount, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *likeService) MarkLikeNotificationAsRead(ctx context.Context, notificationID, userID int) error {
|
|
||||||
return s.likeRepo.MarkLikeNotificationAsRead(ctx, notificationID, userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *likeService) MarkAllLikeNotificationsAsRead(ctx context.Context, userID int) error {
|
|
||||||
return s.likeRepo.MarkAllLikeNotificationsAsRead(ctx, userID)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE likes ADD COLUMN is_read BOOLEAN NOT NULL DEFAULT FALSE;
|
|
||||||
ALTER TABLE likes ADD COLUMN notified_at TIMESTAMP WITH TIME ZONE DEFAULT NOW();
|
|
||||||
Loading…
x
Reference in New Issue
Block a user