Skip to content
This repository has been archived by the owner on Dec 4, 2024. It is now read-only.

Develop #102

Merged
merged 23 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e7ea464
feat: Add notification model
devkcud Nov 1, 2024
39315e9
feat: Create notification repository and usecase
devkcud Nov 1, 2024
cbe42f5
chore: Add new rule in validator
devkcud Nov 1, 2024
006264b
feat: Add notifications translations
devkcud Nov 1, 2024
9e70231
feat: Add routes for notification
devkcud Nov 1, 2024
58a92d1
fix: Remove unnecessary logs
devkcud Nov 1, 2024
8e661b0
feat: Create util for creating notifications
devkcud Nov 1, 2024
4fe690f
Merge remote-tracking branch 'origin' into feat/notifications
devkcud Nov 3, 2024
0c184d4
chore!: Remove obsolete translations
devkcud Nov 4, 2024
77fb7a6
chore: Remove unused ctx param from `CreateNotification`
devkcud Nov 4, 2024
32c36af
chore(notifications): Add translations notify-on-act
devkcud Nov 4, 2024
768c53f
feat: Add redirects configuration
devkcud Nov 4, 2024
20d4eb2
feat: Add user related notifications
devkcud Nov 4, 2024
2bdcf4e
feat: Add project related notifications
devkcud Nov 4, 2024
39dc8d8
feat: Add notifications for when user is (un)assigned to project
devkcud Nov 4, 2024
b3046f4
feat: Add notifications for component
devkcud Nov 5, 2024
1b6af82
fix: Correct issuer name on notification
devkcud Nov 5, 2024
5c3c73f
feat: Add unread only flag for get notifications
devkcud Nov 5, 2024
f91be80
feat: Add get unread notification count
devkcud Nov 5, 2024
c0ca4c3
feat: Add unread notifications field for user profile
devkcud Nov 5, 2024
c44948d
fix: Add missing onlyUnread parameter
devkcud Nov 5, 2024
17e7c6e
fix: Get the reference to redirect string in user security tab
devkcud Nov 5, 2024
ccf4ee4
Merge pull request #101 from swibly/feat/notifications
devkcud Nov 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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")
}

Expand Down
3 changes: 3 additions & 0 deletions config/redirects.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
security: /settings?tab=security
profile: /profile/%s
project: /projects/%d
14 changes: 14 additions & 0 deletions internal/controller/http/v1/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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})
}

Expand Down Expand Up @@ -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})
}
}
Expand Down
44 changes: 41 additions & 3 deletions internal/controller/http/v1/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package v1

import (
"errors"
"fmt"
"log"
"net/http"
"strconv"
Expand All @@ -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"
)
Expand Down Expand Up @@ -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})
}

Expand Down Expand Up @@ -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
Expand All @@ -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})
}

Expand Down Expand Up @@ -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})
}

Expand Down Expand Up @@ -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})
}

Expand All @@ -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})
}
116 changes: 116 additions & 0 deletions internal/controller/http/v1/notification.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package v1

import (
"errors"
"log"
"net/http"
"strconv"
"strings"

"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
onlyUnread := false

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
}

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, onlyUnread, 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
}

ctx.JSON(http.StatusOK, gin.H{"message": dict.NotificationMarkedAsUnread})
}
Loading
Loading