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

Commit

Permalink
Merge pull request #101 from swibly/feat/notifications
Browse files Browse the repository at this point in the history
feat: Notifications
  • Loading branch information
devkcud authored Nov 5, 2024
2 parents 36f1225 + 17e7c6e commit ccf4ee4
Show file tree
Hide file tree
Showing 23 changed files with 810 additions and 10 deletions.
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

0 comments on commit ccf4ee4

Please sign in to comment.