From cb0fefecfbc4f4c763615aaf27594cc1c28a57f2 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Tue, 23 Jul 2024 23:50:05 +0800 Subject: [PATCH] :sparkles: More relationship APIs --- pkg/internal/models/relationships.go | 1 + pkg/internal/server/api/accounts_api.go | 7 +- pkg/internal/server/api/index.go | 7 +- pkg/internal/server/api/relationships_api.go | 104 +++++++++++------ pkg/internal/services/relationships.go | 113 +++++++++++++------ 5 files changed, 158 insertions(+), 74 deletions(-) diff --git a/pkg/internal/models/relationships.go b/pkg/internal/models/relationships.go index 7884908..0b84ac5 100644 --- a/pkg/internal/models/relationships.go +++ b/pkg/internal/models/relationships.go @@ -8,6 +8,7 @@ const ( RelationshipPending = RelationshipStatus(iota) RelationshipFriend RelationshipBlocked + RelationshipWaiting ) type AccountRelationship struct { diff --git a/pkg/internal/server/api/accounts_api.go b/pkg/internal/server/api/accounts_api.go index d986f4b..b5b3281 100644 --- a/pkg/internal/server/api/accounts_api.go +++ b/pkg/internal/server/api/accounts_api.go @@ -2,10 +2,11 @@ package api import ( "fmt" - "git.solsynth.dev/hydrogen/passport/pkg/internal/server/exts" "strconv" "time" + "git.solsynth.dev/hydrogen/passport/pkg/internal/server/exts" + "git.solsynth.dev/hydrogen/passport/pkg/internal/database" "git.solsynth.dev/hydrogen/passport/pkg/internal/models" "git.solsynth.dev/hydrogen/passport/pkg/internal/services" @@ -139,8 +140,8 @@ func editUserinfo(c *fiber.Ctx) error { func doRegister(c *fiber.Ctx) error { var data struct { - Name string `json:"name" validate:"required,lowercase,alphanum,min=4,max=16"` - Nick string `json:"nick" validate:"required,min=4,max=24"` + Name string `json:"name" validate:"required,lowercase,alphanum,min=2,max=16"` + Nick string `json:"nick" validate:"required,min=2,max=24"` Email string `json:"email" validate:"required,email"` Password string `json:"password" validate:"required,min=4,max=32"` MagicToken string `json:"magic_token"` diff --git a/pkg/internal/server/api/index.go b/pkg/internal/server/api/index.go index c073b40..ce10c53 100644 --- a/pkg/internal/server/api/index.go +++ b/pkg/internal/server/api/index.go @@ -46,10 +46,13 @@ func MapAPIs(app *fiber.App, baseURL string) { { relations.Get("/", listRelationship) relations.Get("/:relatedId", getRelationship) - relations.Post("/", makeFriendship) - relations.Post("/:relatedId", makeFriendship) relations.Put("/:relatedId", editRelationship) relations.Delete("/:relatedId", deleteRelationship) + + relations.Post("/", makeFriendship) + relations.Post("/:relatedId", makeFriendship) + relations.Put("/:relatedId", acceptFriend) + relations.Delete("/:relatedId", declineFriend) } } diff --git a/pkg/internal/server/api/relationships_api.go b/pkg/internal/server/api/relationships_api.go index da3cece..8175460 100644 --- a/pkg/internal/server/api/relationships_api.go +++ b/pkg/internal/server/api/relationships_api.go @@ -48,38 +48,6 @@ func getRelationship(c *fiber.Ctx) error { } } -func makeFriendship(c *fiber.Ctx) error { - if err := exts.EnsureAuthenticated(c); err != nil { - return err - } - user := c.Locals("user").(models.Account) - relatedName := c.Query("related") - relatedId, _ := c.ParamsInt("relatedId", 0) - - var err error - var related models.Account - if relatedId > 0 { - related, err = services.GetAccount(uint(relatedId)) - if err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - } else if len(relatedName) > 0 { - related, err = services.LookupAccount(relatedName) - if err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - } else { - return fiber.NewError(fiber.StatusBadRequest, "must one of username or user id") - } - - friend, err := services.NewFriend(user, related) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } else { - return c.JSON(friend) - } -} - func editRelationship(c *fiber.Ctx) error { if err := exts.EnsureAuthenticated(c); err != nil { return err @@ -133,3 +101,75 @@ func deleteRelationship(c *fiber.Ctx) error { return c.JSON(relationship) } } + +// Friends stuff + +func makeFriendship(c *fiber.Ctx) error { + if err := exts.EnsureAuthenticated(c); err != nil { + return err + } + user := c.Locals("user").(models.Account) + relatedName := c.Query("related") + relatedId, _ := c.ParamsInt("relatedId", 0) + + var err error + var related models.Account + if relatedId > 0 { + related, err = services.GetAccount(uint(relatedId)) + if err != nil { + return fiber.NewError(fiber.StatusNotFound, err.Error()) + } + } else if len(relatedName) > 0 { + related, err = services.LookupAccount(relatedName) + if err != nil { + return fiber.NewError(fiber.StatusNotFound, err.Error()) + } + } else { + return fiber.NewError(fiber.StatusBadRequest, "must one of username or user id") + } + + friend, err := services.NewFriend(user, related) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else { + return c.JSON(friend) + } +} + +func acceptFriend(c *fiber.Ctx) error { + if err := exts.EnsureAuthenticated(c); err != nil { + return err + } + user := c.Locals("user").(models.Account) + relatedId, _ := c.ParamsInt("relatedId", 0) + + related, err := services.GetAccount(uint(relatedId)) + if err != nil { + return fiber.NewError(fiber.StatusNotFound, err.Error()) + } + + if err := services.HandleFriend(user, related, true); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else { + return c.SendStatus(fiber.StatusOK) + } +} + +func declineFriend(c *fiber.Ctx) error { + if err := exts.EnsureAuthenticated(c); err != nil { + return err + } + user := c.Locals("user").(models.Account) + relatedId, _ := c.ParamsInt("relatedId", 0) + + related, err := services.GetAccount(uint(relatedId)) + if err != nil { + return fiber.NewError(fiber.StatusNotFound, err.Error()) + } + + if err := services.HandleFriend(user, related, false); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else { + return c.SendStatus(fiber.StatusOK) + } +} diff --git a/pkg/internal/services/relationships.go b/pkg/internal/services/relationships.go index 200d57e..e18b189 100644 --- a/pkg/internal/services/relationships.go +++ b/pkg/internal/services/relationships.go @@ -6,6 +6,7 @@ import ( "git.solsynth.dev/hydrogen/passport/pkg/internal/database" "git.solsynth.dev/hydrogen/passport/pkg/internal/models" + "github.com/samber/lo" "gorm.io/gorm" ) @@ -66,43 +67,6 @@ func GetRelationWithTwoNode(userId, relatedId uint, noPreload ...bool) (models.A return relationship, nil } -func NewFriend(userA models.Account, userB models.Account, skipPending ...bool) (models.AccountRelationship, error) { - relA := models.AccountRelationship{ - AccountID: userA.ID, - RelatedID: userB.ID, - Status: models.RelationshipFriend, - } - relB := models.AccountRelationship{ - AccountID: userB.ID, - RelatedID: userA.ID, - Status: models.RelationshipPending, - } - - if len(skipPending) > 0 && skipPending[0] { - relB.Status = models.RelationshipFriend - } - - if userA.ID == userB.ID { - return relA, fmt.Errorf("you cannot make friendship with yourself") - } else if _, err := GetRelationWithTwoNode(userA.ID, userB.ID, true); err == nil || !errors.Is(err, gorm.ErrRecordNotFound) { - return relA, fmt.Errorf("you already have a friendship with him or her") - } - - if err := database.C.Save(&relA).Error; err != nil { - return relA, err - } else if err = database.C.Save(&relB).Error; err != nil { - return relA, err - } else { - _ = NewNotification(models.Notification{ - Title: fmt.Sprintf("New friend request from %s", userA.Name), - Body: fmt.Sprintf("You got a new friend request from %s. Go to your settings and decide how to deal it.", userA.Nick), - AccountID: userB.ID, - }) - } - - return relA, nil -} - func EditRelationship(relationship models.AccountRelationship) (models.AccountRelationship, error) { if err := database.C.Save(&relationship).Error; err != nil { return relationship, err @@ -116,3 +80,78 @@ func DeleteRelationship(relationship models.AccountRelationship) error { } return nil } + +func NewFriend(userA models.Account, userB models.Account, skipPending ...bool) (models.AccountRelationship, error) { + relA := models.AccountRelationship{ + AccountID: userA.ID, + RelatedID: userB.ID, + Status: models.RelationshipWaiting, + } + relB := models.AccountRelationship{ + AccountID: userB.ID, + RelatedID: userA.ID, + Status: models.RelationshipPending, + } + + if len(skipPending) > 0 && skipPending[0] { + relA.Status = models.RelationshipFriend + relB.Status = models.RelationshipFriend + } + + if userA.ID == userB.ID { + return relA, fmt.Errorf("unable to make relationship with yourself") + } else if _, err := GetRelationWithTwoNode(userA.ID, userB.ID, true); err == nil || !errors.Is(err, gorm.ErrRecordNotFound) { + return relA, fmt.Errorf("unable to recreate a relationship with that user") + } + + if err := database.C.Save(&relA).Error; err != nil { + return relA, err + } else if err = database.C.Save(&relB).Error; err != nil { + return relA, err + } else { + _ = NewNotification(models.Notification{ + Title: "New Friend Request", + Subtitle: lo.ToPtr(fmt.Sprintf("New friend request from %s", userA.Name)), + Body: fmt.Sprintf("You got a new friend request from %s. Go to your account page and decide how to deal it.", userA.Nick), + AccountID: userB.ID, + }) + } + + return relA, nil +} + +func HandleFriend(userA models.Account, userB models.Account, isAccept bool) error { + relA, err := GetRelationWithTwoNode(userA.ID, userB.ID, true) + if err != nil { + return fmt.Errorf("relationship was not found: %v", err) + } else if relA.Status != models.RelationshipPending { + return fmt.Errorf("relationship already handled") + } + + if isAccept { + relA.Status = models.RelationshipFriend + } else { + relA.Status = models.RelationshipBlocked + } + + if err := database.C.Save(&relA).Error; err != nil { + return err + } + + relB, err := GetRelationWithTwoNode(userB.ID, userA.ID, true) + if err == nil && relB.Status == models.RelationshipWaiting { + relB.Status = models.RelationshipFriend + if err := database.C.Save(&relB).Error; err != nil { + return err + } + + _ = NewNotification(models.Notification{ + Title: "Friend Request Processed", + Subtitle: lo.ToPtr(fmt.Sprintf("Your friend request to %s has been processsed.", userA.Name)), + Body: fmt.Sprintf("Your relationship status with %s has been updated, go check it out!", userA.Nick), + AccountID: userB.ID, + }) + } + + return nil +}