diff --git a/pkg/internal/models/subscriptions.go b/pkg/internal/models/subscriptions.go index a904260..1bda417 100644 --- a/pkg/internal/models/subscriptions.go +++ b/pkg/internal/models/subscriptions.go @@ -13,4 +13,6 @@ type Subscription struct { Tag Tag `json:"tag,omitempty"` CategoryID *uint `json:"category_id,omitempty"` Category Category `json:"category,omitempty"` + RealmID *uint `json:"realm_id,omitempty"` + Realm Realm `json:"realm,omitempty"` } diff --git a/pkg/internal/server/api/index.go b/pkg/internal/server/api/index.go index bfaa696..88612f9 100644 --- a/pkg/internal/server/api/index.go +++ b/pkg/internal/server/api/index.go @@ -51,12 +51,15 @@ func MapAPIs(app *fiber.App, baseURL string) { subscriptions.Get("/users/:userId", getSubscriptionOnUser) subscriptions.Get("/tags/:tagId", getSubscriptionOnTag) subscriptions.Get("/categories/:categoryId", getSubscriptionOnCategory) + subscriptions.Get("/realms/:realmId", getSubscriptionOnRealm) subscriptions.Post("/users/:userId", subscribeToUser) subscriptions.Post("/tags/:tagId", subscribeToTag) subscriptions.Post("/categories/:categoryId", subscribeToCategory) + subscriptions.Post("/realms/:realmId", subscribeToRealm) subscriptions.Delete("/users/:userId", unsubscribeFromUser) subscriptions.Delete("/tags/:tagId", unsubscribeFromTag) subscriptions.Delete("/categories/:categoryId", unsubscribeFromCategory) + subscriptions.Delete("/realms/:realmId", unsubscribeFromRealm) } api.Get("/categories", listCategories) diff --git a/pkg/internal/server/api/subscriptions_api.go b/pkg/internal/server/api/subscriptions_api.go index 5b417fb..e976416 100644 --- a/pkg/internal/server/api/subscriptions_api.go +++ b/pkg/internal/server/api/subscriptions_api.go @@ -76,6 +76,28 @@ func getSubscriptionOnCategory(c *fiber.Ctx) error { return c.JSON(subscription) } +func getSubscriptionOnRealm(c *fiber.Ctx) error { + if err := gap.H.EnsureAuthenticated(c); err != nil { + return err + } + user := c.Locals("user").(models.Account) + + realmId, err := c.ParamsInt("realmId", 0) + realm, err := services.GetRealmWithID(uint(realmId)) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable to get realm: %v", err)) + } + + subscription, err := services.GetSubscriptionOnRealm(user, realm) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable to get subscription: %v", err)) + } else if subscription == nil { + return fiber.NewError(fiber.StatusNotFound, "subscription does not exist") + } + + return c.JSON(subscription) +} + func subscribeToUser(c *fiber.Ctx) error { if err := gap.H.EnsureAuthenticated(c); err != nil { return err @@ -160,6 +182,34 @@ func subscribeToCategory(c *fiber.Ctx) error { return c.JSON(subscription) } +func subscribeToRealm(c *fiber.Ctx) error { + if err := gap.H.EnsureAuthenticated(c); err != nil { + return err + } + user := c.Locals("user").(models.Account) + + realmId, err := c.ParamsInt("realmId", 0) + realm, err := services.GetRealmWithID(uint(realmId)) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable to get realm: %v", err)) + } + + subscription, err := services.SubscribeToRealm(user, realm) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable to subscribe to realm: %v", err)) + } + + _ = gap.H.RecordAuditLog( + user.ID, + "posts.subscribe.realms", + strconv.Itoa(int(realm.ID)), + c.IP(), + c.Get(fiber.HeaderUserAgent), + ) + + return c.JSON(subscription) +} + func unsubscribeFromUser(c *fiber.Ctx) error { if err := gap.H.EnsureAuthenticated(c); err != nil { return err @@ -243,3 +293,31 @@ func unsubscribeFromCategory(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) } + +func unsubscribeFromRealm(c *fiber.Ctx) error { + if err := gap.H.EnsureAuthenticated(c); err != nil { + return err + } + user := c.Locals("user").(models.Account) + + realmId, err := c.ParamsInt("realmId", 0) + realm, err := services.GetRealmWithID(uint(realmId)) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable to get realm: %v", err)) + } + + err = services.UnsubscribeFromRealm(user, realm) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable to unsubscribe from realm: %v", err)) + } + + _ = gap.H.RecordAuditLog( + user.ID, + "posts.unsubscribe.realms", + strconv.Itoa(int(realm.ID)), + c.IP(), + c.Get(fiber.HeaderUserAgent), + ) + + return c.SendStatus(fiber.StatusOK) +} diff --git a/pkg/internal/services/posts.go b/pkg/internal/services/posts.go index 5dee170..131a370 100644 --- a/pkg/internal/services/posts.go +++ b/pkg/internal/services/posts.go @@ -491,3 +491,14 @@ func TruncatePostContent(post models.Post) models.Post { return post } + +const TruncatePostContentShortThreshold = 80 + +func TruncatePostContentShort(content string) string { + length := TruncatePostContentShortThreshold + if len([]rune(content)) >= length { + return string([]rune(content)[:length]) + "..." + } else { + return content + } +} diff --git a/pkg/internal/services/realms.go b/pkg/internal/services/realms.go index b327b18..49df4bc 100644 --- a/pkg/internal/services/realms.go +++ b/pkg/internal/services/realms.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/viper" ) -func GetRealmWithExtID(id uint) (models.Realm, error) { +func GetRealmWithID(id uint) (models.Realm, error) { var realm models.Realm pc, err := gap.H.GetServiceGrpcConn(hyper.ServiceTypeAuthProvider) if err != nil { diff --git a/pkg/internal/services/subscriptions.go b/pkg/internal/services/subscriptions.go index 704cf0f..7aec6cc 100644 --- a/pkg/internal/services/subscriptions.go +++ b/pkg/internal/services/subscriptions.go @@ -47,6 +47,17 @@ func GetSubscriptionOnCategory(user models.Account, target models.Category) (*mo return &subscription, nil } +func GetSubscriptionOnRealm(user models.Account, target models.Realm) (*models.Subscription, error) { + var subscription models.Subscription + if err := database.C.Where("follower_id = ? AND realm_id = ?", user.ID, target.ID).First(&subscription).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, fmt.Errorf("unable to get subscription: %v", err) + } + return &subscription, nil +} + func SubscribeToUser(user models.Account, target models.Account) (models.Subscription, error) { var subscription models.Subscription if err := database.C.Where("follower_id = ? AND account_id = ?", user.ID, target.ID).First(&subscription).Error; err != nil { @@ -98,6 +109,23 @@ func SubscribeToCategory(user models.Account, target models.Category) (models.Su return subscription, err } +func SubscribeToRealm(user models.Account, target models.Realm) (models.Subscription, error) { + var subscription models.Subscription + if err := database.C.Where("follower_id = ? AND realm_id = ?", user.ID, target.ID).First(&subscription).Error; err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return subscription, fmt.Errorf("subscription already exists") + } + } + + subscription = models.Subscription{ + FollowerID: user.ID, + RealmID: &target.ID, + } + + err := database.C.Save(&subscription).Error + return subscription, err +} + func UnsubscribeFromUser(user models.Account, target models.Account) error { var subscription models.Subscription if err := database.C.Where("follower_id = ? AND account_id = ?", user.ID, target.ID).First(&subscription).Error; err != nil { @@ -137,6 +165,19 @@ func UnsubscribeFromCategory(user models.Account, target models.Category) error return err } +func UnsubscribeFromRealm(user models.Account, target models.Realm) error { + var subscription models.Subscription + if err := database.C.Where("follower_id = ? AND realm_id = ?", user.ID, target.ID).First(&subscription).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fmt.Errorf("subscription does not exist") + } + return fmt.Errorf("unable to check subscription is exists or not: %v", err) + } + + err := database.C.Delete(&subscription).Error + return err +} + func NotifyUserSubscription(poster models.Account, content string, title *string) error { var subscriptions []models.Subscription if err := database.C.Where("account_id = ?", poster.ID).Preload("Follower").Find(&subscriptions).Error; err != nil { @@ -146,12 +187,7 @@ func NotifyUserSubscription(poster models.Account, content string, title *string nTitle := fmt.Sprintf("New post from %s (%s)", poster.Nick, poster.Name) nSubtitle := "From your subscription" - var body string - if len(content) > 80 { - body = content[:80] - } else { - body = content - } + body := TruncatePostContentShort(content) if title != nil { body = fmt.Sprintf("%s\n%s", *title, body) } @@ -193,12 +229,7 @@ func NotifyTagSubscription(poster models.Tag, og models.Account, content string, nTitle := fmt.Sprintf("New post in %s by %s (%s)", poster.Name, og.Nick, og.Name) nSubtitle := "From your subscription" - var body string - if len(content) > 80 { - body = content[:80] - } else { - body = content - } + body := TruncatePostContentShort(content) if title != nil { body = fmt.Sprintf("%s\n%s", *title, body) } @@ -240,12 +271,49 @@ func NotifyCategorySubscription(poster models.Category, og models.Account, conte nTitle := fmt.Sprintf("New post in %s by %s (%s)", poster.Name, og.Nick, og.Name) nSubtitle := "From your subscription" - var body string - if len(content) > 80 { - body = content[:80] - } else { - body = content - } + body := TruncatePostContentShort(content) + if title != nil { + body = fmt.Sprintf("%s\n%s", *title, body) + } + + userIDs := make([]uint64, 0, len(subscriptions)) + for _, subscription := range subscriptions { + userIDs = append(userIDs, uint64(subscription.Follower.ID)) + } + + pc, err := gap.H.GetServiceGrpcConn(hyper.ServiceTypeAuthProvider) + if err != nil { + return err + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + _, err = proto.NewNotifierClient(pc).NotifyUserBatch(ctx, &proto.NotifyUserBatchRequest{ + UserId: userIDs, + Notify: &proto.NotifyRequest{ + Topic: "interactive.subscription", + Title: nTitle, + Subtitle: &nSubtitle, + Body: body, + IsRealtime: false, + IsForcePush: true, + }, + }) + + return err +} + +func NotifyRealmSubscription(poster models.Realm, og models.Account, content string, title *string) error { + var subscriptions []models.Subscription + if err := database.C.Where("realm_id = ?", poster.ID).Preload("Follower").Find(&subscriptions).Error; err != nil { + return fmt.Errorf("unable to get subscriptions: %v", err) + } + + nTitle := fmt.Sprintf("New post in %s by %s (%s)", poster.Name, og.Nick, og.Name) + nSubtitle := "From your subscription" + + body := TruncatePostContentShort(content) if title != nil { body = fmt.Sprintf("%s\n%s", *title, body) }