From e7ea4642a578f8924ec2e2c786a9dd2a9bb5f0d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Fri, 1 Nov 2024 02:29:07 -0300 Subject: [PATCH 01/21] feat: Add notification model --- internal/model/dto/notification.go | 29 ++++++++++++++++++++++ internal/model/notification.go | 40 ++++++++++++++++++++++++++++++ pkg/db/postgres.go | 9 +++++++ pkg/notification/general.go | 14 +++++++++++ 4 files changed, 92 insertions(+) create mode 100644 internal/model/dto/notification.go create mode 100644 internal/model/notification.go create mode 100644 pkg/notification/general.go diff --git a/internal/model/dto/notification.go b/internal/model/dto/notification.go new file mode 100644 index 0000000..43c2146 --- /dev/null +++ b/internal/model/dto/notification.go @@ -0,0 +1,29 @@ +package dto + +import ( + "time" + + "github.com/swibly/swibly-api/pkg/notification" +) + +type CreateNotification struct { + Title string `json:"title" validate:"required,max=255"` + Message string `json:"message" validate:"required"` + Type notification.NotificationType `json:"type" validate:"required,mustbenotificationtype"` + Redirect *string `json:"redirect" validate:"omitempty,url"` +} + +type NotificationInfo struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + + Title string `json:"title"` + Message string `json:"message"` + + Type notification.NotificationType `json:"type"` + Redirect *string `json:"redirect"` + + ReadAt *time.Time `json:"read_at"` + IsRead bool `json:"is_read"` +} diff --git a/internal/model/notification.go b/internal/model/notification.go new file mode 100644 index 0000000..5672dda --- /dev/null +++ b/internal/model/notification.go @@ -0,0 +1,40 @@ +package model + +import ( + "time" + + "github.com/swibly/swibly-api/pkg/notification" + "gorm.io/gorm" +) + +type Notification struct { + ID uint `gorm:"primaryKey"` + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index"` + + Title string + Message string + + Type notification.NotificationType `gorm:"type:notification_type;default:'information'"` + + Redirect *string +} + +type NotificationUser struct { + ID uint `gorm:"primaryKey"` + CreatedAt time.Time + UpdatedAt time.Time + + NotificationID uint `gorm:"not null;index"` + UserID uint `gorm:"not null;index"` +} + +type NotificationUserRead struct { + ID uint `gorm:"primaryKey"` + CreatedAt time.Time + UpdatedAt time.Time + + NotificationID uint `gorm:"not null;index"` + UserID uint `gorm:"not null;index"` +} diff --git a/pkg/db/postgres.go b/pkg/db/postgres.go index 11dd336..4988ba8 100644 --- a/pkg/db/postgres.go +++ b/pkg/db/postgres.go @@ -11,6 +11,7 @@ import ( "github.com/swibly/swibly-api/config" "github.com/swibly/swibly-api/internal/model" "github.com/swibly/swibly-api/pkg/language" + "github.com/swibly/swibly-api/pkg/notification" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/clause" @@ -83,6 +84,10 @@ func Load() { log.Fatal(err) } + if err := typeCheckAndCreate(db, "notification_type", notification.ArrayString); err != nil { + log.Fatal(err) + } + models := []any{ &model.APIKey{}, &model.User{}, @@ -101,6 +106,10 @@ func Load() { &model.ComponentOwner{}, &model.ComponentHolder{}, &model.ComponentPublication{}, + + &model.Notification{}, + &model.NotificationUser{}, + &model.NotificationUserRead{}, } if err := db.AutoMigrate(models...); err != nil { diff --git a/pkg/notification/general.go b/pkg/notification/general.go new file mode 100644 index 0000000..5a138c5 --- /dev/null +++ b/pkg/notification/general.go @@ -0,0 +1,14 @@ +package notification + +type NotificationType string + +const ( + Information NotificationType = "information" + Warning NotificationType = "warning" + Danger NotificationType = "danger" +) + +var ( + Array = []NotificationType{Information, Warning, Danger} + ArrayString = []string{string(Information), string(Warning), string(Danger)} +) From 39315e91ae2e684ac40f8593778f9f377b972db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Fri, 1 Nov 2024 02:30:13 -0300 Subject: [PATCH 02/21] feat: Create notification repository and usecase --- internal/service/init.go | 2 + internal/service/repository/notification.go | 212 ++++++++++++++++++++ internal/service/usecase/notification.go | 46 +++++ 3 files changed, 260 insertions(+) create mode 100644 internal/service/repository/notification.go create mode 100644 internal/service/usecase/notification.go diff --git a/internal/service/init.go b/internal/service/init.go index 3cb0637..57a03aa 100644 --- a/internal/service/init.go +++ b/internal/service/init.go @@ -10,6 +10,7 @@ var ( Project usecase.ProjectUseCase Component usecase.ComponentUseCase PasswordReset usecase.PasswordResetUseCase + Notification usecase.NotificationUseCase ) func Init() { @@ -20,4 +21,5 @@ func Init() { Project = usecase.NewProjectUseCase() Component = usecase.NewComponentUseCase() PasswordReset = usecase.NewPasswordResetUseCase() + Notification = usecase.NewNotificationUseCase() } diff --git a/internal/service/repository/notification.go b/internal/service/repository/notification.go new file mode 100644 index 0000000..2092222 --- /dev/null +++ b/internal/service/repository/notification.go @@ -0,0 +1,212 @@ +package repository + +import ( + "errors" + + "github.com/swibly/swibly-api/internal/model" + "github.com/swibly/swibly-api/internal/model/dto" + "github.com/swibly/swibly-api/pkg/db" + "github.com/swibly/swibly-api/pkg/pagination" + "gorm.io/gorm" +) + +type notificationRepository struct { + db *gorm.DB +} + +type NotificationRepository interface { + Create(createModel dto.CreateNotification) (uint, error) + + GetForUser(userID uint, page, perPage int) (*dto.Pagination[dto.NotificationInfo], error) + + SendToAll(notificationID uint) error + SendToIDs(notificationID uint, usersID []uint) error + + UnsendToAll(notificationID uint) error + UnsendToIDs(notificationID uint, usersID []uint) error + + MarkAsRead(issuer dto.UserProfile, notificationID uint) error + MarkAsUnread(issuer dto.UserProfile, notificationID uint) error +} + +var ( + ErrNotificationAlreadyRead = errors.New("notification already marked as read") + ErrNotificationNotRead = errors.New("notification is not marked as read") + ErrNotificationNotAssigned = errors.New("notification not assigned to user") +) + +func NewNotificationRepository() NotificationRepository { + return ¬ificationRepository{db: db.Postgres} +} + +func (nr *notificationRepository) Create(createModel dto.CreateNotification) (uint, error) { + notification := &model.Notification{ + Title: createModel.Title, + Message: createModel.Message, + Type: createModel.Type, + Redirect: createModel.Redirect, + } + if err := nr.db.Create(notification).Error; err != nil { + return 0, err + } + + return notification.ID, nil +} + +func (nr *notificationRepository) GetForUser(userID uint, page, perPage int) (*dto.Pagination[dto.NotificationInfo], error) { + query := nr.db.Table("notification_users AS nu"). + Select(` + n.id AS id, + n.created_at AS created_at, + n.updated_at AS updated_at, + n.title AS title, + n.message AS message, + n.type AS type, + n.redirect AS redirect, + nur.created_at AS read_at, + CASE WHEN nur.created_at IS NOT NULL THEN true ELSE false END AS is_read + `). + Joins("JOIN notifications AS n ON n.id = nu.notification_id"). + Joins("LEFT JOIN notification_user_reads AS nur ON n.id = nur.notification_id AND nur.user_id = ?", userID). + Where("nu.user_id = ?", userID). + Order("n.created_at DESC") + + paginationResult, err := pagination.Generate[dto.NotificationInfo](query, page, perPage) + if err != nil { + return nil, err + } + + return paginationResult, nil +} + +func (nr *notificationRepository) SendToAll(notificationID uint) error { + tx := nr.db.Begin() + + var userIDs []uint + if err := tx.Model(&model.User{}).Select("id").Find(&userIDs).Error; err != nil { + tx.Rollback() + return err + } + + notifications := make([]model.NotificationUser, len(userIDs)) + for i, userID := range userIDs { + notifications[i] = model.NotificationUser{ + NotificationID: notificationID, + UserID: userID, + } + } + + if err := tx.Create(¬ifications).Error; err != nil { + tx.Rollback() + return err + } + + return tx.Commit().Error +} + +func (nr *notificationRepository) SendToIDs(notificationID uint, userIDs []uint) error { + tx := nr.db.Begin() + + notifications := make([]model.NotificationUser, len(userIDs)) + for i, userID := range userIDs { + notifications[i] = model.NotificationUser{ + NotificationID: notificationID, + UserID: userID, + } + } + + if err := tx.Create(¬ifications).Error; err != nil { + tx.Rollback() + return err + } + + return tx.Commit().Error +} + +func (nr *notificationRepository) UnsendToAll(notificationID uint) error { + if err := nr.db.Where("notification_id = ?", notificationID).Delete(&model.NotificationUser{}).Error; err != nil { + return err + } + + if err := nr.db.Where("notification_id = ?", notificationID).Delete(&model.NotificationUserRead{}).Error; err != nil { + return err + } + + return nil +} + +func (nr *notificationRepository) UnsendToIDs(notificationID uint, userIDs []uint) error { + tx := nr.db.Begin() + + if len(userIDs) == 0 { + return nil + } + + if err := tx.Where("notification_id = ? AND user_id IN ?", notificationID, userIDs).Delete(&model.NotificationUser{}).Error; err != nil { + return err + } + + if err := tx.Where("notification_id = ? AND user_id IN ?", notificationID, userIDs).Delete(&model.NotificationUserRead{}).Error; err != nil { + return err + } + + return nil +} + +func (nr *notificationRepository) MarkAsRead(issuer dto.UserProfile, notificationID uint) error { + tx := nr.db.Begin() + + var notificationUser model.NotificationUser + if err := tx.Where("notification_id = ? AND user_id = ?", notificationID, issuer.ID).First(¬ificationUser).Error; err == gorm.ErrRecordNotFound { + tx.Rollback() + return ErrUserNotAssigned + } else if err != nil { + tx.Rollback() + return err + } + + var existingRead model.NotificationUserRead + if err := tx.Where("notification_id = ? AND user_id = ?", notificationID, issuer.ID).First(&existingRead).Error; err == nil { + tx.Rollback() + return ErrNotificationAlreadyRead + } else if err != gorm.ErrRecordNotFound { + tx.Rollback() + return err + } + + if err := tx.Create(&model.NotificationUserRead{NotificationID: notificationID, UserID: issuer.ID}).Error; err != nil { + tx.Rollback() + return err + } + + return tx.Commit().Error +} + +func (nr *notificationRepository) MarkAsUnread(issuer dto.UserProfile, notificationID uint) error { + tx := nr.db.Begin() + + var notificationUser model.NotificationUser + if err := tx.Where("notification_id = ? AND user_id = ?", notificationID, issuer.ID).First(¬ificationUser).Error; err == gorm.ErrRecordNotFound { + tx.Rollback() + return ErrUserNotAssigned + } else if err != nil { + tx.Rollback() + return err + } + + var existingRead model.NotificationUserRead + if err := tx.Where("notification_id = ? AND user_id = ?", notificationID, issuer.ID).First(&existingRead).Error; err == gorm.ErrRecordNotFound { + tx.Rollback() + return ErrNotificationNotRead + } else if err != nil { + tx.Rollback() + return err + } + + if err := tx.Where("notification_id = ? AND user_id = ?", notificationID, issuer.ID).Delete(&model.NotificationUserRead{}).Error; err != nil { + tx.Rollback() + return err + } + + return tx.Commit().Error +} diff --git a/internal/service/usecase/notification.go b/internal/service/usecase/notification.go new file mode 100644 index 0000000..6f7aec0 --- /dev/null +++ b/internal/service/usecase/notification.go @@ -0,0 +1,46 @@ +package usecase + +import ( + "github.com/swibly/swibly-api/internal/model/dto" + "github.com/swibly/swibly-api/internal/service/repository" +) + +type NotificationUseCase struct { + nr repository.NotificationRepository +} + +func NewNotificationUseCase() NotificationUseCase { + return NotificationUseCase{nr: repository.NewNotificationRepository()} +} + +func (nuc *NotificationUseCase) Create(createModel dto.CreateNotification) (uint, error) { + return nuc.nr.Create(createModel) +} + +func (nuc *NotificationUseCase) GetForUser(userID uint, page, perPage int) (*dto.Pagination[dto.NotificationInfo], error) { + return nuc.nr.GetForUser(userID, page, perPage) +} + +func (nuc *NotificationUseCase) SendToAll(notificationID uint) error { + return nuc.nr.SendToAll(notificationID) +} + +func (nuc *NotificationUseCase) SendToIDs(notificationID uint, usersID []uint) error { + return nuc.nr.SendToIDs(notificationID, usersID) +} + +func (nuc *NotificationUseCase) UnsendToAll(notificationID uint) error { + return nuc.nr.UnsendToAll(notificationID) +} + +func (nuc *NotificationUseCase) UnsendToIDs(notificationID uint, usersID []uint) error { + return nuc.nr.UnsendToIDs(notificationID, usersID) +} + +func (nuc *NotificationUseCase) MarkAsRead(issuer dto.UserProfile, notificationID uint) error { + return nuc.nr.MarkAsRead(issuer, notificationID) +} + +func (nuc *NotificationUseCase) MarkAsUnread(issuer dto.UserProfile, notificationID uint) error { + return nuc.nr.MarkAsUnread(issuer, notificationID) +} From cbe42f5c676d88925c309e53b44b7056cdd99a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Fri, 1 Nov 2024 02:30:51 -0300 Subject: [PATCH 03/21] chore: Add new rule in validator --- pkg/utils/validator.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pkg/utils/validator.go b/pkg/utils/validator.go index 22012d9..24e384f 100644 --- a/pkg/utils/validator.go +++ b/pkg/utils/validator.go @@ -5,10 +5,11 @@ import ( "regexp" "strings" - "github.com/swibly/swibly-api/pkg/language" - "github.com/swibly/swibly-api/translations" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" + "github.com/swibly/swibly-api/pkg/language" + "github.com/swibly/swibly-api/pkg/notification" + "github.com/swibly/swibly-api/translations" ) var Validate *validator.Validate = newValidator() @@ -31,6 +32,16 @@ func newValidator() *validator.Validate { return nn == 1 || nn == 0 || nn == -1 }) + vv.RegisterValidation("mustbenotificationtype", func(fl validator.FieldLevel) bool { + _, valid := fl.Field().Interface().(notification.NotificationType) + + if !valid { + return false + } + + return true + }) + vv.RegisterValidation("mustbesupportedlanguage", func(fl validator.FieldLevel) bool { lang := fl.Field().String() From 006264bdb1f6f06dc121717b4561d832c144d4f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Fri, 1 Nov 2024 02:31:21 -0300 Subject: [PATCH 04/21] feat: Add notifications translations --- translations/configure.go | 10 ++++++++++ translations/en.yaml | 9 +++++++++ translations/pt.yaml | 9 +++++++++ translations/ru.yaml | 9 +++++++++ 4 files changed, 37 insertions(+) diff --git a/translations/configure.go b/translations/configure.go index 35a3de2..ceafc06 100644 --- a/translations/configure.go +++ b/translations/configure.go @@ -29,6 +29,16 @@ type Translation struct { AuthUserUpdated string `yaml:"auth_user_updated"` AuthWrongCredentials string `yaml:"auth_wrong_credentials"` + NotificationWelcome string `yaml:"notification_welcome"` + NotificationWelcomeBody string `yaml:"notification_welcome_body"` + NotificationNewLoginDetected string `yaml:"notification_new_login_detected"` + NotificationInvalid string `yaml:"notification_invalid"` + NotificationAlreadyRead string `yaml:"notification_already_read"` + NotificationNotRead string `yaml:"notification_not_read"` + NotificationNotAssigned string `yaml:"notification_not_assigned"` + NotificationMarkedAsRead string `yaml:"notification_marked_as_read"` + NotificationMarkedAsUnread string `yaml:"notification_marked_as_unread"` + SearchIncorrect string `yaml:"search_incorrect"` SearchNoResults string `yaml:"search_no_results"` diff --git a/translations/en.yaml b/translations/en.yaml index 8769b46..d4f309c 100644 --- a/translations/en.yaml +++ b/translations/en.yaml @@ -5,6 +5,15 @@ auth_user_deleted: User deleted. auth_user_updated: User updated. auth_wrong_credentials: Email, username or password are incorrect or don't exist. hello: Hello! +notification_welcome: Welcome, %s to Arkhon! +notification_welcome_body: Welcome to Arkhon, your architecture platform! Dive in to explore tools, insights, and a thriving community that will elevate your designs and ideas to new heights. +notification_new_login_detected: A new login to your account was detected. If this wasn’t you, please review your account security. +notification_invalid: The provided ID is invalid. Please check and try again. +notification_already_read: This notification has already been read. +notification_not_read: This notification has not been read yet. +notification_not_assigned: This notification is not assigned. +notification_marked_as_read: The notification has been marked as read. +notification_marked_as_unread: The notification has been marked as unread. internal_server_error: Internal server error. Please, try again later. invalid_api_key: Invalid API key. invalid_body: diff --git a/translations/pt.yaml b/translations/pt.yaml index 6841a1c..2c174a6 100644 --- a/translations/pt.yaml +++ b/translations/pt.yaml @@ -5,6 +5,15 @@ auth_user_deleted: Usuário deletado. auth_user_updated: Usuário atualizado. auth_wrong_credentials: Email, nome de usuário ou senha estão incorretos ou não existem. hello: Olá! +notification_welcome: Bem-vindo, %s ao Arkhon! +notification_welcome_body: Bem-vindo ao Arkhon, sua plataforma de arquitetura! Explore ferramentas, insights e uma comunidade vibrante que levará seus projetos e ideias a um novo patamar. +notification_new_login_detected: Uma nova entrada na sua conta foi detectada. Se não foi você, revise a segurança da sua conta. +notification_invalid: O ID fornecido é inválido. Verifique e tente novamente. +notification_already_read: Esta notificação já foi lida. +notification_not_read: Esta notificação ainda não foi lida. +notification_not_assigned: Esta notificação não está atribuída. +notification_marked_as_read: A notificação foi marcada como lida. +notification_marked_as_unread: A notificação foi marcada como não lida. internal_server_error: Erro interno de servidor. Por favor, tente novamente mais tarde. invalid_api_key: Chave de API inválida. invalid_body: diff --git a/translations/ru.yaml b/translations/ru.yaml index 4523dc2..950bfe8 100644 --- a/translations/ru.yaml +++ b/translations/ru.yaml @@ -5,6 +5,15 @@ auth_user_deleted: Пользователь удален. auth_user_updated: Пользователь обновлен. auth_wrong_credentials: Электронная почта, имя пользователя или пароль неверны или не существуют. hello: Привет! +notification_welcome: Добро пожаловать, %s в Arkhon! +notification_welcome_body: Добро пожаловать в Arkhon, вашу платформу для архитекторов! Откройте для себя инструменты, идеи и активное сообщество, которые помогут поднять ваши проекты и идеи на новый уровень. +notification_new_login_detected: Обнаружен новый вход в ваш аккаунт. Если это были не вы, пожалуйста, проверьте безопасность своей учетной записи. +notification_invalid: Указанный идентификатор недействителен. Пожалуйста, проверьте и попробуйте снова. +notification_already_read: Это уведомление уже было прочитано. +notification_not_read: Это уведомление ещё не прочитано. +notification_not_assigned: Это уведомление не назначено. +notification_marked_as_read: Уведомление помечено как прочитанное. +notification_marked_as_unread: Уведомление помечено как непрочитанное. internal_server_error: Внутренняя ошибка сервера. Пожалуйста, попробуйте позже. invalid_api_key: Неверный ключ API. invalid_body: From 9e702313d021f9253feec8c3b260238e746f18a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Fri, 1 Nov 2024 02:31:57 -0300 Subject: [PATCH 05/21] feat: Add routes for notification --- internal/controller/http/v1/notification.go | 110 ++++++++++++++++++++ internal/controller/http/v1/router.go | 1 + 2 files changed, 111 insertions(+) create mode 100644 internal/controller/http/v1/notification.go diff --git a/internal/controller/http/v1/notification.go b/internal/controller/http/v1/notification.go new file mode 100644 index 0000000..b1903b5 --- /dev/null +++ b/internal/controller/http/v1/notification.go @@ -0,0 +1,110 @@ +package v1 + +import ( + "errors" + "log" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + "github.com/swibly/swibly-api/internal/model/dto" + "github.com/swibly/swibly-api/internal/service" + "github.com/swibly/swibly-api/internal/service/repository" + "github.com/swibly/swibly-api/pkg/middleware" + "github.com/swibly/swibly-api/translations" +) + +func newNotificationRoutes(handler *gin.RouterGroup) { + h := handler.Group("/notification") + h.Use(middleware.APIKeyHasEnabledUserFetch, middleware.Auth) + { + h.GET("", GetOwnNotificationsHandler) + + specific := h.Group("/:id") + { + specific.POST("/read", PostReadNotificationHandler) + specific.DELETE("/unread", DeleteUnreadNotificationHandler) + } + } +} + +func GetOwnNotificationsHandler(ctx *gin.Context) { + dict := translations.GetTranslation(ctx) + + page := 1 + perPage := 10 + + if i, e := strconv.Atoi(ctx.Query("page")); e == nil && ctx.Query("page") != "" { + page = i + } + + if i, e := strconv.Atoi(ctx.Query("perpage")); e == nil && ctx.Query("perpage") != "" { + perPage = i + } + + issuer := ctx.Keys["auth_user"].(*dto.UserProfile) + + notifications, err := service.Notification.GetForUser(issuer.ID, page, perPage) + if err != nil { + log.Print(err) + ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError}) + return + } + + ctx.JSON(http.StatusOK, notifications) +} + +func PostReadNotificationHandler(ctx *gin.Context) { + dict := translations.GetTranslation(ctx) + issuer := ctx.Keys["auth_user"].(*dto.UserProfile) + + notificationID, err := strconv.ParseUint(ctx.Param("id"), 10, 64) + if err != nil { + log.Print(err) + ctx.JSON(http.StatusBadRequest, gin.H{"error": dict.NotificationInvalid}) + return + } + + if err := service.Notification.MarkAsRead(*issuer, uint(notificationID)); err != nil { + switch { + case errors.Is(err, repository.ErrNotificationNotAssigned): + ctx.JSON(http.StatusForbidden, gin.H{"error": dict.NotificationNotAssigned}) + case errors.Is(err, repository.ErrNotificationAlreadyRead): + ctx.JSON(http.StatusConflict, gin.H{"error": dict.NotificationAlreadyRead}) + default: + log.Print(err) + ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError}) + } + return + } + + ctx.JSON(http.StatusOK, gin.H{"message": dict.NotificationMarkedAsRead}) +} + +func DeleteUnreadNotificationHandler(ctx *gin.Context) { + dict := translations.GetTranslation(ctx) + issuer := ctx.Keys["auth_user"].(*dto.UserProfile) + + notificationID, err := strconv.ParseUint(ctx.Param("id"), 10, 64) + if err != nil { + log.Print(err) + ctx.JSON(http.StatusBadRequest, gin.H{"error": dict.NotificationInvalid}) + return + } + + if err := service.Notification.MarkAsUnread(*issuer, uint(notificationID)); err != nil { + switch { + case errors.Is(err, repository.ErrNotificationNotAssigned): + ctx.JSON(http.StatusForbidden, gin.H{"error": dict.NotificationNotAssigned}) + case errors.Is(err, repository.ErrNotificationNotRead): + ctx.JSON(http.StatusConflict, gin.H{"error": dict.NotificationNotRead}) + default: + log.Print(err) + ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError}) + } + return + } + + log.Print("reached") + ctx.JSON(http.StatusOK, gin.H{"message": dict.NotificationMarkedAsUnread}) +} diff --git a/internal/controller/http/v1/router.go b/internal/controller/http/v1/router.go index dccc4d2..1684abc 100644 --- a/internal/controller/http/v1/router.go +++ b/internal/controller/http/v1/router.go @@ -12,5 +12,6 @@ func NewRouter(handler *gin.Engine) { newSearchRoutes(g) newProjectRoutes(g) newComponentRoutes(g) + newNotificationRoutes(g) } } From 58a92d10edf0f45557e261a9487fbcd4d13e1b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Fri, 1 Nov 2024 02:32:55 -0300 Subject: [PATCH 06/21] fix: Remove unnecessary logs --- internal/controller/http/v1/notification.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/controller/http/v1/notification.go b/internal/controller/http/v1/notification.go index b1903b5..ab28438 100644 --- a/internal/controller/http/v1/notification.go +++ b/internal/controller/http/v1/notification.go @@ -105,6 +105,5 @@ func DeleteUnreadNotificationHandler(ctx *gin.Context) { return } - log.Print("reached") ctx.JSON(http.StatusOK, gin.H{"message": dict.NotificationMarkedAsUnread}) } From 8e661b0486665e90ed98a544db17ceba9a796fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Fri, 1 Nov 2024 19:15:31 -0300 Subject: [PATCH 07/21] feat: Create util for creating notifications --- internal/service/utils.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 internal/service/utils.go diff --git a/internal/service/utils.go b/internal/service/utils.go new file mode 100644 index 0000000..1e273b1 --- /dev/null +++ b/internal/service/utils.go @@ -0,0 +1,19 @@ +package service + +import ( + "github.com/gin-gonic/gin" + "github.com/swibly/swibly-api/internal/model/dto" +) + +func CreateNotification(ctx *gin.Context, createModel dto.CreateNotification, ids ...uint) error { + notification, err := Notification.Create(createModel) + if err != nil { + return err + } + + if err := Notification.SendToIDs(notification, ids); err != nil { + return err + } + + return nil +} From 0c184d410584cc1225fc1a3107eb07b4defd9a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Mon, 4 Nov 2024 06:53:42 -0300 Subject: [PATCH 08/21] chore!: Remove obsolete translations --- translations/configure.go | 15 ++++++--------- translations/en.yaml | 3 --- translations/pt.yaml | 3 --- translations/ru.yaml | 3 --- 4 files changed, 6 insertions(+), 18 deletions(-) diff --git a/translations/configure.go b/translations/configure.go index ceafc06..094dd37 100644 --- a/translations/configure.go +++ b/translations/configure.go @@ -29,15 +29,12 @@ type Translation struct { AuthUserUpdated string `yaml:"auth_user_updated"` AuthWrongCredentials string `yaml:"auth_wrong_credentials"` - NotificationWelcome string `yaml:"notification_welcome"` - NotificationWelcomeBody string `yaml:"notification_welcome_body"` - NotificationNewLoginDetected string `yaml:"notification_new_login_detected"` - NotificationInvalid string `yaml:"notification_invalid"` - NotificationAlreadyRead string `yaml:"notification_already_read"` - NotificationNotRead string `yaml:"notification_not_read"` - NotificationNotAssigned string `yaml:"notification_not_assigned"` - NotificationMarkedAsRead string `yaml:"notification_marked_as_read"` - NotificationMarkedAsUnread string `yaml:"notification_marked_as_unread"` + NotificationInvalid string `yaml:"notification_invalid"` + NotificationAlreadyRead string `yaml:"notification_already_read"` + NotificationNotRead string `yaml:"notification_not_read"` + NotificationNotAssigned string `yaml:"notification_not_assigned"` + NotificationMarkedAsRead string `yaml:"notification_marked_as_read"` + NotificationMarkedAsUnread string `yaml:"notification_marked_as_unread"` SearchIncorrect string `yaml:"search_incorrect"` SearchNoResults string `yaml:"search_no_results"` diff --git a/translations/en.yaml b/translations/en.yaml index d4f309c..7bb60c7 100644 --- a/translations/en.yaml +++ b/translations/en.yaml @@ -5,9 +5,6 @@ auth_user_deleted: User deleted. auth_user_updated: User updated. auth_wrong_credentials: Email, username or password are incorrect or don't exist. hello: Hello! -notification_welcome: Welcome, %s to Arkhon! -notification_welcome_body: Welcome to Arkhon, your architecture platform! Dive in to explore tools, insights, and a thriving community that will elevate your designs and ideas to new heights. -notification_new_login_detected: A new login to your account was detected. If this wasn’t you, please review your account security. notification_invalid: The provided ID is invalid. Please check and try again. notification_already_read: This notification has already been read. notification_not_read: This notification has not been read yet. diff --git a/translations/pt.yaml b/translations/pt.yaml index 2c174a6..cbb7787 100644 --- a/translations/pt.yaml +++ b/translations/pt.yaml @@ -5,9 +5,6 @@ auth_user_deleted: Usuário deletado. auth_user_updated: Usuário atualizado. auth_wrong_credentials: Email, nome de usuário ou senha estão incorretos ou não existem. hello: Olá! -notification_welcome: Bem-vindo, %s ao Arkhon! -notification_welcome_body: Bem-vindo ao Arkhon, sua plataforma de arquitetura! Explore ferramentas, insights e uma comunidade vibrante que levará seus projetos e ideias a um novo patamar. -notification_new_login_detected: Uma nova entrada na sua conta foi detectada. Se não foi você, revise a segurança da sua conta. notification_invalid: O ID fornecido é inválido. Verifique e tente novamente. notification_already_read: Esta notificação já foi lida. notification_not_read: Esta notificação ainda não foi lida. diff --git a/translations/ru.yaml b/translations/ru.yaml index 950bfe8..fd99f12 100644 --- a/translations/ru.yaml +++ b/translations/ru.yaml @@ -5,9 +5,6 @@ auth_user_deleted: Пользователь удален. auth_user_updated: Пользователь обновлен. auth_wrong_credentials: Электронная почта, имя пользователя или пароль неверны или не существуют. hello: Привет! -notification_welcome: Добро пожаловать, %s в Arkhon! -notification_welcome_body: Добро пожаловать в Arkhon, вашу платформу для архитекторов! Откройте для себя инструменты, идеи и активное сообщество, которые помогут поднять ваши проекты и идеи на новый уровень. -notification_new_login_detected: Обнаружен новый вход в ваш аккаунт. Если это были не вы, пожалуйста, проверьте безопасность своей учетной записи. notification_invalid: Указанный идентификатор недействителен. Пожалуйста, проверьте и попробуйте снова. notification_already_read: Это уведомление уже было прочитано. notification_not_read: Это уведомление ещё не прочитано. From 77fb7a638b67f512d5b13f723cc98c774233e9f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Mon, 4 Nov 2024 07:28:24 -0300 Subject: [PATCH 09/21] chore: Remove unused ctx param from `CreateNotification` --- internal/service/utils.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/internal/service/utils.go b/internal/service/utils.go index 1e273b1..1c74b79 100644 --- a/internal/service/utils.go +++ b/internal/service/utils.go @@ -1,11 +1,8 @@ package service -import ( - "github.com/gin-gonic/gin" - "github.com/swibly/swibly-api/internal/model/dto" -) +import "github.com/swibly/swibly-api/internal/model/dto" -func CreateNotification(ctx *gin.Context, createModel dto.CreateNotification, ids ...uint) error { +func CreateNotification(createModel dto.CreateNotification, ids ...uint) error { notification, err := Notification.Create(createModel) if err != nil { return err From 32c36af47543075053e2006cb53f55b1ca349ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Mon, 4 Nov 2024 07:28:42 -0300 Subject: [PATCH 10/21] chore(notifications): Add translations notify-on-act --- translations/configure.go | 24 ++++++++++++++++++++++++ translations/en.yaml | 22 ++++++++++++++++++++++ translations/pt.yaml | 22 ++++++++++++++++++++++ translations/ru.yaml | 22 ++++++++++++++++++++++ 4 files changed, 90 insertions(+) diff --git a/translations/configure.go b/translations/configure.go index 094dd37..63e73f9 100644 --- a/translations/configure.go +++ b/translations/configure.go @@ -16,6 +16,11 @@ type Translation struct { MaximumAPIKey string `yaml:"maximum_api_key"` RequirePermissionAPIKey string `yaml:"require_permission_api_key"` + CategoryAuth string `yaml:"category_auth"` + CategoryFollowers string `yaml:"category_followers"` + CategoryProject string `yaml:"category_project"` + CategoryComponent string `yaml:"category_component"` + InternalServerError string `yaml:"internal_server_error"` Unauthorized string `yaml:"unauthorized"` InvalidBody string `yaml:"invalid_body"` @@ -29,6 +34,25 @@ type Translation struct { AuthUserUpdated string `yaml:"auth_user_updated"` AuthWrongCredentials string `yaml:"auth_wrong_credentials"` + NotificationWelcomeUserRegister string `yaml:"notification_welcome_user_register"` + NotificationNewLoginDetected string `yaml:"notification_new_login_detected"` + NotificationUserFollowedYou string `yaml:"notification_user_followed_you"` + NotificationNewProjectCreated string `yaml:"notification_new_project_created"` + NotificationUserClonedYourProject string `yaml:"notification_user_cloned_your_project"` + NotificationYourProjectPublished string `yaml:"notification_your_project_published"` + NotificationYourProjectFavorited string `yaml:"notification_your_project_favorited"` + NotificationDeletedProjectFromTrash string `yaml:"notification_deleted_project_from_trash"` + NotificationRestoredProjectFromTrash string `yaml:"notification_restored_project_from_trash"` + NotificationAddedUserToProject string `yaml:"notification_added_user_to_project"` + NotificationRemovedUserFromProject string `yaml:"notification_removed_user_from_project"` + NotificationUserLeftProject string `yaml:"notification_user_left_project"` + NotificationNewComponentCreated string `yaml:"notification_new_component_created"` + NotificationYourComponentPublished string `yaml:"notification_your_component_published"` + NotificationDeletedComponentFromTrash string `yaml:"notification_deleted_component_from_trash"` + NotificationRestoredComponentFromTrash string `yaml:"notification_restored_component_from_trash"` + NotificationYourComponentBought string `yaml:"notification_your_component_bought"` + NotificationYouBoughtComponent string `yaml:"notification_you_bought_component"` + NotificationInvalid string `yaml:"notification_invalid"` NotificationAlreadyRead string `yaml:"notification_already_read"` NotificationNotRead string `yaml:"notification_not_read"` diff --git a/translations/en.yaml b/translations/en.yaml index 7bb60c7..c716071 100644 --- a/translations/en.yaml +++ b/translations/en.yaml @@ -5,6 +5,28 @@ auth_user_deleted: User deleted. auth_user_updated: User updated. auth_wrong_credentials: Email, username or password are incorrect or don't exist. hello: Hello! +category_auth: Authentication +category_followers: Followers +category_project: Project +category_component: Component +notification_welcome_user_register: Welcome, %s! Thank you for registering. +notification_new_login_detected: New login detected from a different device. +notification_user_followed_you: "%s has started following you." +notification_new_project_created: The project "%s" has been created. +notification_user_cloned_your_project: '%s has cloned your project "%s."' +notification_your_project_published: Your project "%s" has been published. +notification_your_project_favorited: Your project "%s" has been added to favorites by %s. +notification_deleted_project_from_trash: The project "%s" has been deleted from trash. +notification_restored_project_from_trash: The project "%s" has been restored from trash. +notification_added_user_to_project: '%s has been added to the project "%s."' +notification_removed_user_from_project: '%s has been removed from the project "%s."' +notification_user_left_project: '%s has left the project "%s."' +notification_new_component_created: The component "%s" has been created. +notification_your_component_published: Your component "%s" has been published. +notification_deleted_component_from_trash: The component "%s" has been deleted from trash. +notification_restored_component_from_trash: The component "%s" has been restored from trash. +notification_your_component_bought: Your component "%s" has been purchased by %s. +notification_you_bought_component: You have purchased the component "%s." notification_invalid: The provided ID is invalid. Please check and try again. notification_already_read: This notification has already been read. notification_not_read: This notification has not been read yet. diff --git a/translations/pt.yaml b/translations/pt.yaml index cbb7787..91d0972 100644 --- a/translations/pt.yaml +++ b/translations/pt.yaml @@ -5,6 +5,28 @@ auth_user_deleted: Usuário deletado. auth_user_updated: Usuário atualizado. auth_wrong_credentials: Email, nome de usuário ou senha estão incorretos ou não existem. hello: Olá! +category_auth: Autenticação +category_followers: Seguidores +category_project: Projeto +category_component: Componente +notification_welcome_user_register: Bem-vindo(a), %s! Obrigado por se registrar. +notification_new_login_detected: Novo login detectado a partir de outro dispositivo. +notification_user_followed_you: "%s começou a seguir você." +notification_new_project_created: O projeto "%s" foi criado. +notification_user_cloned_your_project: '%s clonou o seu projeto "%s."' +notification_your_project_published: Seu projeto "%s" foi publicado. +notification_your_project_favorited: Seu projeto "%s" foi adicionado aos favoritos de %s. +notification_deleted_project_from_trash: O projeto "%s" foi excluído da lixeira. +notification_restored_project_from_trash: O projeto "%s" foi restaurado da lixeira. +notification_added_user_to_project: '%s foi adicionado(a) ao projeto "%s."' +notification_removed_user_from_project: '%s foi removido(a) do projeto "%s."' +notification_user_left_project: '%s saiu do projeto "%s."' +notification_new_component_created: O componente "%s" foi criado. +notification_your_component_published: Seu componente "%s" foi publicado. +notification_deleted_component_from_trash: O componente "%s" foi excluído da lixeira. +notification_restored_component_from_trash: O componente "%s" foi restaurado da lixeira. +notification_your_component_bought: Seu componente "%s" foi comprado por %s. +notification_you_bought_component: Você comprou o componente "%s." notification_invalid: O ID fornecido é inválido. Verifique e tente novamente. notification_already_read: Esta notificação já foi lida. notification_not_read: Esta notificação ainda não foi lida. diff --git a/translations/ru.yaml b/translations/ru.yaml index fd99f12..604b625 100644 --- a/translations/ru.yaml +++ b/translations/ru.yaml @@ -5,6 +5,28 @@ auth_user_deleted: Пользователь удален. auth_user_updated: Пользователь обновлен. auth_wrong_credentials: Электронная почта, имя пользователя или пароль неверны или не существуют. hello: Привет! +category_auth: Авторизация +category_followers: Подписчики +category_project: Проект +category_component: Компонент +notification_welcome_user_register: Добро пожаловать, %s! Спасибо за регистрацию. +notification_new_login_detected: Обнаружен новый вход с другого устройства. +notification_user_followed_you: "%s начал(а) следовать за вами." +notification_new_project_created: Проект "%s" был создан. +notification_user_cloned_your_project: '%s склонировал ваш проект "%s."' +notification_your_project_published: Ваш проект "%s" был опубликован. +notification_your_project_favorited: Ваш проект "%s" добавлен в избранное пользователем %s. +notification_deleted_project_from_trash: Проект "%s" был удален из корзины. +notification_restored_project_from_trash: Проект "%s" был восстановлен из корзины. +notification_added_user_to_project: '%s был добавлен в проект "%s."' +notification_removed_user_from_project: '%s был удален из проекта "%s."' +notification_user_left_project: '%s покинул проект "%s."' +notification_new_component_created: Компонент "%s" был создан. +notification_your_component_published: Ваш компонент "%s" был опубликован. +notification_deleted_component_from_trash: Компонент "%s" был удален из корзины. +notification_restored_component_from_trash: Компонент "%s" был восстановлен из корзины. +notification_your_component_bought: Ваш компонент "%s" был приобретен пользователем %s. +notification_you_bought_component: Вы приобрели компонент "%s." notification_invalid: Указанный идентификатор недействителен. Пожалуйста, проверьте и попробуйте снова. notification_already_read: Это уведомление уже было прочитано. notification_not_read: Это уведомление ещё не прочитано. From 768c53f69d1d6429601b5aae46f61eaff2f4417a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Mon, 4 Nov 2024 12:40:18 -0300 Subject: [PATCH 11/21] feat: Add redirects configuration --- config/config.go | 10 ++++++++++ config/redirects.yaml | 3 +++ 2 files changed, 13 insertions(+) create mode 100644 config/redirects.yaml diff --git a/config/config.go b/config/config.go index 8b1e368..656e44c 100644 --- a/config/config.go +++ b/config/config.go @@ -52,6 +52,12 @@ var ( ManageProjects string `yaml:"manage_projects"` ManageStore string `yaml:"manage_store"` } + + Redirects struct { + SecurityTab string `yaml:"security"` + Profile string `yaml:"profile"` + Project string `yaml:"project"` + } ) func Parse() { @@ -99,6 +105,10 @@ func Parse() { log.Fatalf("error: %v", err) } + if err := yaml.Unmarshal(read("redirects.yaml"), &Redirects); err != nil { + log.Fatalf("error: %v", err) + } + log.Print("Loaded config files") } diff --git a/config/redirects.yaml b/config/redirects.yaml new file mode 100644 index 0000000..658800e --- /dev/null +++ b/config/redirects.yaml @@ -0,0 +1,3 @@ +security: /settings?tab=security +profile: /profile/%s +project: /projects/%d From 20d4eb2f5d31ffd48a8252a5fa91ae6022992e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Mon, 4 Nov 2024 12:40:44 -0300 Subject: [PATCH 12/21] feat: Add user related notifications --- internal/controller/http/v1/auth.go | 14 ++++++++++++++ internal/controller/http/v1/user.go | 10 ++++++++++ 2 files changed, 24 insertions(+) diff --git a/internal/controller/http/v1/auth.go b/internal/controller/http/v1/auth.go index 711e291..9fef4ca 100644 --- a/internal/controller/http/v1/auth.go +++ b/internal/controller/http/v1/auth.go @@ -12,6 +12,7 @@ import ( "github.com/swibly/swibly-api/internal/service" "github.com/swibly/swibly-api/pkg/aws" "github.com/swibly/swibly-api/pkg/middleware" + "github.com/swibly/swibly-api/pkg/notification" "github.com/swibly/swibly-api/pkg/utils" "github.com/swibly/swibly-api/translations" "golang.org/x/crypto/bcrypt" @@ -72,6 +73,12 @@ func RegisterHandler(ctx *gin.Context) { log.Print(err) ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError}) } else { + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryAuth, + Message: dict.NotificationWelcomeUserRegister, + Type: notification.Information, + }, user.ID) + ctx.JSON(http.StatusOK, gin.H{"token": token}) } @@ -150,6 +157,13 @@ func LoginHandler(ctx *gin.Context) { ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError}) } else { + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryAuth, + Message: dict.NotificationNewLoginDetected, + Type: notification.Warning, + Redirect: config.Redirects.SecurityTab, + }, user.ID) + ctx.JSON(http.StatusOK, gin.H{"token": token}) } } diff --git a/internal/controller/http/v1/user.go b/internal/controller/http/v1/user.go index dd00aa6..89d3887 100644 --- a/internal/controller/http/v1/user.go +++ b/internal/controller/http/v1/user.go @@ -7,9 +7,12 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/swibly/swibly-api/config" "github.com/swibly/swibly-api/internal/model/dto" "github.com/swibly/swibly-api/internal/service" "github.com/swibly/swibly-api/pkg/middleware" + "github.com/swibly/swibly-api/pkg/notification" + "github.com/swibly/swibly-api/pkg/utils" "github.com/swibly/swibly-api/translations" ) @@ -116,6 +119,13 @@ func FollowUserHandler(ctx *gin.Context) { return } + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryFollowers, + Message: fmt.Sprintf(dict.NotificationUserFollowedYou, issuer.FirstName+issuer.LastName), + Type: notification.Information, + Redirect: utils.ToPtr(fmt.Sprintf(config.Redirects.Profile, issuer.Username)), + }, receiver.ID) + ctx.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf(dict.UserFollowingStarted, receiver.Username)}) } From 2bdcf4e30f1b2aca4a053bcbdc2ad52e512fb89a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Mon, 4 Nov 2024 12:41:22 -0300 Subject: [PATCH 13/21] feat: Add project related notifications --- internal/controller/http/v1/project.go | 69 +++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/internal/controller/http/v1/project.go b/internal/controller/http/v1/project.go index 9bbc938..1f2bbcf 100644 --- a/internal/controller/http/v1/project.go +++ b/internal/controller/http/v1/project.go @@ -2,17 +2,20 @@ package v1 import ( "errors" + "fmt" "log" "net/http" "strconv" "strings" "github.com/gin-gonic/gin" + "github.com/swibly/swibly-api/config" "github.com/swibly/swibly-api/internal/model/dto" "github.com/swibly/swibly-api/internal/service" "github.com/swibly/swibly-api/internal/service/repository" "github.com/swibly/swibly-api/pkg/aws" "github.com/swibly/swibly-api/pkg/middleware" + "github.com/swibly/swibly-api/pkg/notification" "github.com/swibly/swibly-api/pkg/utils" "github.com/swibly/swibly-api/translations" ) @@ -52,7 +55,7 @@ func newProjectRoutes(handler *gin.RouterGroup) { specific.DELETE("/unpublish", middleware.ProjectIsAllowed(dto.Allow{Publish: true}), UnpublishProjectHandler) specific.DELETE("/unfavorite", middleware.ProjectIsAllowed(dto.Allow{View: true}), UnfavoriteProjectHandler) specific.DELETE("/fork", middleware.ProjectIsAllowed(dto.Allow{Manage: dto.AllowManage{Metadata: true}}), UnlinkProjectHandler) - specific.DELETE("/leave", middleware.ProjectIsMember, LeaveProjectHandler) + specific.DELETE("/leave", middleware.ProjectIsMember, LeaveProjectHandler) trashActions := specific.Group("/trash") { @@ -177,6 +180,13 @@ func CreateProjectHandler(ctx *gin.Context) { ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError}) return } else { + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryProject, + Message: fmt.Sprintf(dict.NotificationNewProjectCreated, project.Name), + Type: notification.Information, + Redirect: utils.ToPtr(fmt.Sprintf(config.Redirects.Project, id)), + }, issuer.ID) + ctx.JSON(http.StatusOK, gin.H{"message": dict.ProjectCreated, "project": id}) } } @@ -276,6 +286,20 @@ func ForkProjectHandler(ctx *gin.Context) { ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError}) return } else { + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryProject, + Message: fmt.Sprintf(dict.NotificationUserClonedYourProject, issuer.FirstName+issuer.LastName, project.Name), + Type: notification.Information, + Redirect: utils.ToPtr(fmt.Sprintf(config.Redirects.Project, id)), + }, project.OwnerID) + + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryProject, + Message: fmt.Sprintf(dict.NotificationNewProjectCreated, project.Name), + Type: notification.Information, + Redirect: utils.ToPtr(fmt.Sprintf(config.Redirects.Project, id)), + }, issuer.ID) + ctx.JSON(http.StatusOK, gin.H{"message": dict.ProjectForked, "project": id}) } } @@ -428,6 +452,18 @@ func PublishProjectHandler(ctx *gin.Context) { return } + ids := []uint{project.OwnerID} + + for _, user := range project.AllowedUsers { + ids = append(ids, user.ID) + } + + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryProject, + Message: fmt.Sprintf(dict.NotificationYourProjectPublished, project.Name), + Type: notification.Warning, + }, ids...) + ctx.JSON(http.StatusOK, gin.H{"message": dict.ProjectPublished}) } @@ -467,6 +503,12 @@ func FavoriteProjectHandler(ctx *gin.Context) { return } + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryProject, + Message: fmt.Sprintf(dict.NotificationYourProjectFavorited, project.Name, issuer.FirstName+issuer.LastName), + Type: notification.Information, + }, project.OwnerID) + ctx.JSON(http.StatusOK, gin.H{"message": dict.ProjectFavorited}) } @@ -525,6 +567,13 @@ func RestoreProjectHandler(ctx *gin.Context) { return } + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryProject, + Message: fmt.Sprintf(dict.NotificationRestoredProjectFromTrash, project.Name), + Type: notification.Warning, + Redirect: utils.ToPtr(fmt.Sprintf(config.Redirects.Project, project.ID)), + }, project.OwnerID) + ctx.JSON(http.StatusOK, gin.H{"message": dict.ProjectRestored}) } @@ -544,6 +593,12 @@ func DeleteProjectForceHandler(ctx *gin.Context) { return } + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryProject, + Message: fmt.Sprintf(dict.NotificationDeletedProjectFromTrash, project.Name), + Type: notification.Danger, + }, project.OwnerID) + ctx.JSON(http.StatusOK, gin.H{"message": dict.ProjectDeleted}) } @@ -595,6 +650,12 @@ func AssignProjectHandler(ctx *gin.Context) { return } + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryProject, + Message: fmt.Sprintf(dict.NotificationAddedUserToProject, user.FirstName+user.LastName, project.Name), + Type: notification.Danger, + }, project.OwnerID) + ctx.JSON(http.StatusOK, gin.H{"message": dict.ProjectAssignedUser}) } @@ -615,5 +676,11 @@ func UnassignProjectHandler(ctx *gin.Context) { return } + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryProject, + Message: fmt.Sprintf(dict.NotificationRemovedUserFromProject, user.FirstName+user.LastName, project.Name), + Type: notification.Danger, + }, project.OwnerID) + ctx.JSON(http.StatusOK, gin.H{"message": dict.ProjectUnassignedUser}) } From 39dc8d884b9573b4e5918ccb82b04bac7a6c6ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Mon, 4 Nov 2024 12:45:01 -0300 Subject: [PATCH 14/21] feat: Add notifications for when user is (un)assigned to project --- internal/controller/http/v1/project.go | 14 ++++++++++++++ translations/configure.go | 2 ++ translations/en.yaml | 2 ++ translations/pt.yaml | 2 ++ translations/ru.yaml | 2 ++ 5 files changed, 22 insertions(+) diff --git a/internal/controller/http/v1/project.go b/internal/controller/http/v1/project.go index 1f2bbcf..38936b0 100644 --- a/internal/controller/http/v1/project.go +++ b/internal/controller/http/v1/project.go @@ -656,6 +656,13 @@ func AssignProjectHandler(ctx *gin.Context) { Type: notification.Danger, }, project.OwnerID) + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryProject, + Message: fmt.Sprintf(dict.NotificationAddedYouToProject, project.Name), + Type: notification.Information, + Redirect: utils.ToPtr(fmt.Sprintf(config.Redirects.Project, project.ID)), + }, user.ID) + ctx.JSON(http.StatusOK, gin.H{"message": dict.ProjectAssignedUser}) } @@ -682,5 +689,12 @@ func UnassignProjectHandler(ctx *gin.Context) { Type: notification.Danger, }, project.OwnerID) + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryProject, + Message: fmt.Sprintf(dict.NotificationRemovedYouFromProject, project.Name), + Type: notification.Information, + Redirect: utils.ToPtr(fmt.Sprintf(config.Redirects.Project, project.ID)), + }, user.ID) + ctx.JSON(http.StatusOK, gin.H{"message": dict.ProjectUnassignedUser}) } diff --git a/translations/configure.go b/translations/configure.go index 63e73f9..e1c8e23 100644 --- a/translations/configure.go +++ b/translations/configure.go @@ -45,6 +45,8 @@ type Translation struct { NotificationRestoredProjectFromTrash string `yaml:"notification_restored_project_from_trash"` NotificationAddedUserToProject string `yaml:"notification_added_user_to_project"` NotificationRemovedUserFromProject string `yaml:"notification_removed_user_from_project"` + NotificationAddedYouToProject string `yaml:"notification_added_you_to_project"` + NotificationRemovedYouFromProject string `yaml:"notification_removed_you_from_project"` NotificationUserLeftProject string `yaml:"notification_user_left_project"` NotificationNewComponentCreated string `yaml:"notification_new_component_created"` NotificationYourComponentPublished string `yaml:"notification_your_component_published"` diff --git a/translations/en.yaml b/translations/en.yaml index c716071..58ad9f9 100644 --- a/translations/en.yaml +++ b/translations/en.yaml @@ -20,6 +20,8 @@ notification_deleted_project_from_trash: The project "%s" has been deleted from notification_restored_project_from_trash: The project "%s" has been restored from trash. notification_added_user_to_project: '%s has been added to the project "%s."' notification_removed_user_from_project: '%s has been removed from the project "%s."' +notification_added_you_to_project: You have been added to the project "%s." +notification_removed_you_from_project: You have been removed from the project "%s." notification_user_left_project: '%s has left the project "%s."' notification_new_component_created: The component "%s" has been created. notification_your_component_published: Your component "%s" has been published. diff --git a/translations/pt.yaml b/translations/pt.yaml index 91d0972..343ad43 100644 --- a/translations/pt.yaml +++ b/translations/pt.yaml @@ -20,6 +20,8 @@ notification_deleted_project_from_trash: O projeto "%s" foi excluído da lixeira notification_restored_project_from_trash: O projeto "%s" foi restaurado da lixeira. notification_added_user_to_project: '%s foi adicionado(a) ao projeto "%s."' notification_removed_user_from_project: '%s foi removido(a) do projeto "%s."' +notification_added_you_to_project: Você foi adicionado(a) ao projeto "%s." +notification_removed_you_from_project: Você foi removido(a) do projeto "%s." notification_user_left_project: '%s saiu do projeto "%s."' notification_new_component_created: O componente "%s" foi criado. notification_your_component_published: Seu componente "%s" foi publicado. diff --git a/translations/ru.yaml b/translations/ru.yaml index 604b625..467f1ba 100644 --- a/translations/ru.yaml +++ b/translations/ru.yaml @@ -20,6 +20,8 @@ notification_deleted_project_from_trash: Проект "%s" был удален notification_restored_project_from_trash: Проект "%s" был восстановлен из корзины. notification_added_user_to_project: '%s был добавлен в проект "%s."' notification_removed_user_from_project: '%s был удален из проекта "%s."' +notification_added_you_to_project: Вы были добавлены в проект "%s." +notification_removed_you_from_project: Вы были удалены из проекта "%s." notification_user_left_project: '%s покинул проект "%s."' notification_new_component_created: Компонент "%s" был создан. notification_your_component_published: Ваш компонент "%s" был опубликован. From b3046f40210d55b07bd20d56380e90153ee07fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Tue, 5 Nov 2024 06:42:33 -0300 Subject: [PATCH 15/21] feat: Add notifications for component --- internal/controller/http/v1/component.go | 44 ++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/internal/controller/http/v1/component.go b/internal/controller/http/v1/component.go index 04092f7..cdf099e 100644 --- a/internal/controller/http/v1/component.go +++ b/internal/controller/http/v1/component.go @@ -2,6 +2,7 @@ package v1 import ( "errors" + "fmt" "log" "net/http" "strconv" @@ -12,6 +13,7 @@ import ( "github.com/swibly/swibly-api/internal/service" "github.com/swibly/swibly-api/internal/service/repository" "github.com/swibly/swibly-api/pkg/middleware" + "github.com/swibly/swibly-api/pkg/notification" "github.com/swibly/swibly-api/pkg/utils" "github.com/swibly/swibly-api/translations" ) @@ -145,6 +147,12 @@ func CreateComponentHandler(ctx *gin.Context) { return } + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryComponent, + Message: fmt.Sprintf(dict.NotificationNewComponentCreated, component.Name), + Type: notification.Information, + }, issuer.ID) + ctx.JSON(http.StatusOK, gin.H{"message": dict.ComponentCreated}) } @@ -251,10 +259,10 @@ func UpdateComponentHandler(ctx *gin.Context) { func BuyComponentHandler(ctx *gin.Context) { dict := translations.GetTranslation(ctx) - issuerID := ctx.Keys["auth_user"].(*dto.UserProfile).ID - componentID := ctx.Keys["component_lookup"].(*dto.ComponentInfo).ID + issuer := ctx.Keys["auth_user"].(*dto.UserProfile) + component := ctx.Keys["component_lookup"].(*dto.ComponentInfo) - if err := service.Component.Buy(issuerID, componentID); err != nil { + if err := service.Component.Buy(issuer.ID, component.ID); err != nil { if errors.Is(err, repository.ErrInsufficientArkhoins) { ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": dict.InsufficientArkhoins}) return @@ -273,6 +281,18 @@ func BuyComponentHandler(ctx *gin.Context) { return } + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryComponent, + Message: fmt.Sprintf(dict.NotificationYourComponentBought, component.Name, issuer.FirstName+issuer.LastName), + Type: notification.Information, + }, component.OwnerID) + + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryComponent, + Message: fmt.Sprintf(dict.NotificationYouBoughtComponent, component.Name), + Type: notification.Information, + }, issuer.ID) + ctx.JSON(http.StatusOK, gin.H{"message": dict.ComponentBought}) } @@ -316,6 +336,12 @@ func PublishComponentHandler(ctx *gin.Context) { return } + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryComponent, + Message: fmt.Sprintf(dict.NotificationYourComponentPublished, component.Name), + Type: notification.Warning, + }, component.OwnerID) + ctx.JSON(http.StatusOK, gin.H{"message": dict.ComponentPublished}) } @@ -373,6 +399,12 @@ func DeleteComponenttForceHandler(ctx *gin.Context) { return } + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryComponent, + Message: fmt.Sprintf(dict.NotificationDeletedComponentFromTrash, component.Name), + Type: notification.Danger, + }, component.OwnerID) + ctx.JSON(http.StatusOK, gin.H{"message": dict.ComponentDeleted}) } @@ -392,5 +424,11 @@ func RestoreComponentHandler(ctx *gin.Context) { return } + service.CreateNotification(dto.CreateNotification{ + Title: dict.CategoryComponent, + Message: fmt.Sprintf(dict.NotificationRestoredComponentFromTrash, component.Name), + Type: notification.Warning, + }, component.OwnerID) + ctx.JSON(http.StatusOK, gin.H{"message": dict.ComponentRestored}) } From 1b6af8288e9d2902b362169feab16354375c180d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Tue, 5 Nov 2024 06:44:26 -0300 Subject: [PATCH 16/21] fix: Correct issuer name on notification --- internal/controller/http/v1/component.go | 2 +- internal/controller/http/v1/project.go | 4 ++-- internal/controller/http/v1/user.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/controller/http/v1/component.go b/internal/controller/http/v1/component.go index cdf099e..ea6feb6 100644 --- a/internal/controller/http/v1/component.go +++ b/internal/controller/http/v1/component.go @@ -283,7 +283,7 @@ func BuyComponentHandler(ctx *gin.Context) { service.CreateNotification(dto.CreateNotification{ Title: dict.CategoryComponent, - Message: fmt.Sprintf(dict.NotificationYourComponentBought, component.Name, issuer.FirstName+issuer.LastName), + Message: fmt.Sprintf(dict.NotificationYourComponentBought, component.Name, issuer.FirstName+" "+issuer.LastName), Type: notification.Information, }, component.OwnerID) diff --git a/internal/controller/http/v1/project.go b/internal/controller/http/v1/project.go index 38936b0..5e74c74 100644 --- a/internal/controller/http/v1/project.go +++ b/internal/controller/http/v1/project.go @@ -288,7 +288,7 @@ func ForkProjectHandler(ctx *gin.Context) { } else { service.CreateNotification(dto.CreateNotification{ Title: dict.CategoryProject, - Message: fmt.Sprintf(dict.NotificationUserClonedYourProject, issuer.FirstName+issuer.LastName, project.Name), + Message: fmt.Sprintf(dict.NotificationUserClonedYourProject, issuer.FirstName+" "+issuer.LastName, project.Name), Type: notification.Information, Redirect: utils.ToPtr(fmt.Sprintf(config.Redirects.Project, id)), }, project.OwnerID) @@ -505,7 +505,7 @@ func FavoriteProjectHandler(ctx *gin.Context) { service.CreateNotification(dto.CreateNotification{ Title: dict.CategoryProject, - Message: fmt.Sprintf(dict.NotificationYourProjectFavorited, project.Name, issuer.FirstName+issuer.LastName), + Message: fmt.Sprintf(dict.NotificationYourProjectFavorited, project.Name, issuer.FirstName+" "+issuer.LastName), Type: notification.Information, }, project.OwnerID) diff --git a/internal/controller/http/v1/user.go b/internal/controller/http/v1/user.go index 89d3887..c9e6fa0 100644 --- a/internal/controller/http/v1/user.go +++ b/internal/controller/http/v1/user.go @@ -121,7 +121,7 @@ func FollowUserHandler(ctx *gin.Context) { service.CreateNotification(dto.CreateNotification{ Title: dict.CategoryFollowers, - Message: fmt.Sprintf(dict.NotificationUserFollowedYou, issuer.FirstName+issuer.LastName), + Message: fmt.Sprintf(dict.NotificationUserFollowedYou, issuer.FirstName+" "+issuer.LastName), Type: notification.Information, Redirect: utils.ToPtr(fmt.Sprintf(config.Redirects.Profile, issuer.Username)), }, receiver.ID) From 5c3c73f0fdd15322c0ff99388a5ca1bd0c705384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Tue, 5 Nov 2024 06:48:19 -0300 Subject: [PATCH 17/21] feat: Add unread only flag for get notifications --- internal/service/repository/notification.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/service/repository/notification.go b/internal/service/repository/notification.go index 2092222..b44ce69 100644 --- a/internal/service/repository/notification.go +++ b/internal/service/repository/notification.go @@ -17,7 +17,7 @@ type notificationRepository struct { type NotificationRepository interface { Create(createModel dto.CreateNotification) (uint, error) - GetForUser(userID uint, page, perPage int) (*dto.Pagination[dto.NotificationInfo], error) + GetForUser(userID uint, onlyUnread bool, page, perPage int) (*dto.Pagination[dto.NotificationInfo], error) SendToAll(notificationID uint) error SendToIDs(notificationID uint, usersID []uint) error @@ -53,7 +53,7 @@ func (nr *notificationRepository) Create(createModel dto.CreateNotification) (ui return notification.ID, nil } -func (nr *notificationRepository) GetForUser(userID uint, page, perPage int) (*dto.Pagination[dto.NotificationInfo], error) { +func (nr *notificationRepository) GetForUser(userID uint, onlyUnread bool, page, perPage int) (*dto.Pagination[dto.NotificationInfo], error) { query := nr.db.Table("notification_users AS nu"). Select(` n.id AS id, @@ -71,6 +71,10 @@ func (nr *notificationRepository) GetForUser(userID uint, page, perPage int) (*d Where("nu.user_id = ?", userID). Order("n.created_at DESC") + if onlyUnread { + query = query.Where("is_read IS false") + } + paginationResult, err := pagination.Generate[dto.NotificationInfo](query, page, perPage) if err != nil { return nil, err From f91be80d0af2e4904a6aade34f24ef98d1ca3db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Tue, 5 Nov 2024 06:58:09 -0300 Subject: [PATCH 18/21] feat: Add get unread notification count --- internal/service/repository/notification.go | 45 +++++++++++++-------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/internal/service/repository/notification.go b/internal/service/repository/notification.go index b44ce69..eeacc61 100644 --- a/internal/service/repository/notification.go +++ b/internal/service/repository/notification.go @@ -18,6 +18,7 @@ type NotificationRepository interface { Create(createModel dto.CreateNotification) (uint, error) GetForUser(userID uint, onlyUnread bool, page, perPage int) (*dto.Pagination[dto.NotificationInfo], error) + GetUnreadCount(userID uint) (int64, error) SendToAll(notificationID uint) error SendToIDs(notificationID uint, usersID []uint) error @@ -39,6 +40,24 @@ func NewNotificationRepository() NotificationRepository { return ¬ificationRepository{db: db.Postgres} } +func (nr *notificationRepository) baseNotificationQuery(userID uint) *gorm.DB { + return nr.db.Table("notification_users AS nu"). + Select(` + n.id AS id, + n.created_at AS created_at, + n.updated_at AS updated_at, + n.title AS title, + n.message AS message, + n.type AS type, + n.redirect AS redirect, + nur.created_at AS read_at, + CASE WHEN nur.created_at IS NOT NULL THEN true ELSE false END AS is_read + `). + Joins("JOIN notifications AS n ON n.id = nu.notification_id"). + Joins("LEFT JOIN notification_user_reads AS nur ON n.id = nur.notification_id AND nur.user_id = ?", userID). + Where("nu.user_id = ?", userID) +} + func (nr *notificationRepository) Create(createModel dto.CreateNotification) (uint, error) { notification := &model.Notification{ Title: createModel.Title, @@ -54,22 +73,7 @@ func (nr *notificationRepository) Create(createModel dto.CreateNotification) (ui } func (nr *notificationRepository) GetForUser(userID uint, onlyUnread bool, page, perPage int) (*dto.Pagination[dto.NotificationInfo], error) { - query := nr.db.Table("notification_users AS nu"). - Select(` - n.id AS id, - n.created_at AS created_at, - n.updated_at AS updated_at, - n.title AS title, - n.message AS message, - n.type AS type, - n.redirect AS redirect, - nur.created_at AS read_at, - CASE WHEN nur.created_at IS NOT NULL THEN true ELSE false END AS is_read - `). - Joins("JOIN notifications AS n ON n.id = nu.notification_id"). - Joins("LEFT JOIN notification_user_reads AS nur ON n.id = nur.notification_id AND nur.user_id = ?", userID). - Where("nu.user_id = ?", userID). - Order("n.created_at DESC") + query := nr.baseNotificationQuery(userID).Order("n.created_at DESC") if onlyUnread { query = query.Where("is_read IS false") @@ -83,6 +87,15 @@ func (nr *notificationRepository) GetForUser(userID uint, onlyUnread bool, page, return paginationResult, nil } +func (nr *notificationRepository) GetUnreadCount(userID uint) (int64, error) { + count := int64(0) + if err := nr.baseNotificationQuery(userID).Where("is_read IS false").Count(&count).Error; err != nil { + return 0, err + } + + return count, nil +} + func (nr *notificationRepository) SendToAll(notificationID uint) error { tx := nr.db.Begin() From c0ca4c305740d75c3ece0150691a2730fc392964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Tue, 5 Nov 2024 07:05:20 -0300 Subject: [PATCH 19/21] feat: Add unread notifications field for user profile --- internal/model/dto/user.go | 3 ++- internal/service/repository/user.go | 13 ++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/internal/model/dto/user.go b/internal/model/dto/user.go index fb8212a..4100e33 100644 --- a/internal/model/dto/user.go +++ b/internal/model/dto/user.go @@ -98,7 +98,8 @@ type UserProfile struct { Country string `json:"country"` Language string `json:"language"` - Permissions []string `gorm:"-" json:"permissions"` + Permissions []string `gorm:"-" json:"permissions"` + UnreadNotifications int64 `gorm:"-" json:"unread_notifications"` ProfilePicture string `json:"pfp"` } diff --git a/internal/service/repository/user.go b/internal/service/repository/user.go index 8716e6d..9605abe 100644 --- a/internal/service/repository/user.go +++ b/internal/service/repository/user.go @@ -13,8 +13,9 @@ import ( type userRepository struct { db *gorm.DB - followRepo FollowRepository - permissionRepo PermissionRepository + followRepo FollowRepository + permissionRepo PermissionRepository + notificationRepo NotificationRepository } type UserRepository interface { @@ -31,7 +32,7 @@ type UserRepository interface { } func NewUserRepository() UserRepository { - return &userRepository{db: db.Postgres, followRepo: NewFollowRepository(), permissionRepo: NewPermissionRepository()} + return &userRepository{db: db.Postgres, followRepo: NewFollowRepository(), permissionRepo: NewPermissionRepository(), notificationRepo: NewNotificationRepository()} } func (u userRepository) Create(createModel *model.User) error { @@ -81,6 +82,12 @@ func (u userRepository) Get(searchModel *model.User) (*dto.UserProfile, error) { } } + if count, err := u.notificationRepo.GetUnreadCount(user.ID); err != nil { + return nil, err + } else { + user.UnreadNotifications = count + } + return user, nil } From c44948dea775de762e3a08e708a132aec41c2680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Tue, 5 Nov 2024 07:12:36 -0300 Subject: [PATCH 20/21] fix: Add missing onlyUnread parameter --- internal/controller/http/v1/notification.go | 9 ++++++++- internal/service/usecase/notification.go | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/internal/controller/http/v1/notification.go b/internal/controller/http/v1/notification.go index ab28438..c581867 100644 --- a/internal/controller/http/v1/notification.go +++ b/internal/controller/http/v1/notification.go @@ -5,6 +5,7 @@ import ( "log" "net/http" "strconv" + "strings" "github.com/gin-gonic/gin" "github.com/swibly/swibly-api/internal/model/dto" @@ -33,6 +34,7 @@ func GetOwnNotificationsHandler(ctx *gin.Context) { page := 1 perPage := 10 + onlyUnread := false if i, e := strconv.Atoi(ctx.Query("page")); e == nil && ctx.Query("page") != "" { page = i @@ -42,9 +44,14 @@ func GetOwnNotificationsHandler(ctx *gin.Context) { perPage = i } + unreadFlag := strings.ToLower(ctx.Query("unread")) + if unreadFlag == "true" || unreadFlag == "t" || unreadFlag == "1" { + onlyUnread = true + } + issuer := ctx.Keys["auth_user"].(*dto.UserProfile) - notifications, err := service.Notification.GetForUser(issuer.ID, page, perPage) + notifications, err := service.Notification.GetForUser(issuer.ID, onlyUnread, page, perPage) if err != nil { log.Print(err) ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError}) diff --git a/internal/service/usecase/notification.go b/internal/service/usecase/notification.go index 6f7aec0..16f5c1e 100644 --- a/internal/service/usecase/notification.go +++ b/internal/service/usecase/notification.go @@ -17,8 +17,8 @@ func (nuc *NotificationUseCase) Create(createModel dto.CreateNotification) (uint return nuc.nr.Create(createModel) } -func (nuc *NotificationUseCase) GetForUser(userID uint, page, perPage int) (*dto.Pagination[dto.NotificationInfo], error) { - return nuc.nr.GetForUser(userID, page, perPage) +func (nuc *NotificationUseCase) GetForUser(userID uint, onlyUnread bool, page, perPage int) (*dto.Pagination[dto.NotificationInfo], error) { + return nuc.nr.GetForUser(userID, onlyUnread, page, perPage) } func (nuc *NotificationUseCase) SendToAll(notificationID uint) error { From 17e7c6ec7072a1d2159f7c95a84e81df19d31132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20devkcud=20Albanese?= Date: Tue, 5 Nov 2024 07:14:44 -0300 Subject: [PATCH 21/21] fix: Get the reference to redirect string in user security tab --- internal/controller/http/v1/auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/http/v1/auth.go b/internal/controller/http/v1/auth.go index 9fef4ca..9df37a8 100644 --- a/internal/controller/http/v1/auth.go +++ b/internal/controller/http/v1/auth.go @@ -161,7 +161,7 @@ func LoginHandler(ctx *gin.Context) { Title: dict.CategoryAuth, Message: dict.NotificationNewLoginDetected, Type: notification.Warning, - Redirect: config.Redirects.SecurityTab, + Redirect: &config.Redirects.SecurityTab, }, user.ID) ctx.JSON(http.StatusOK, gin.H{"token": token})