More relationship APIs

This commit is contained in:
LittleSheep 2024-07-23 23:50:05 +08:00
parent 3cab122b76
commit cb0fefecfb
5 changed files with 158 additions and 74 deletions

View File

@ -8,6 +8,7 @@ const (
RelationshipPending = RelationshipStatus(iota) RelationshipPending = RelationshipStatus(iota)
RelationshipFriend RelationshipFriend
RelationshipBlocked RelationshipBlocked
RelationshipWaiting
) )
type AccountRelationship struct { type AccountRelationship struct {

View File

@ -2,10 +2,11 @@ package api
import ( import (
"fmt" "fmt"
"git.solsynth.dev/hydrogen/passport/pkg/internal/server/exts"
"strconv" "strconv"
"time" "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/database"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models" "git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/internal/services" "git.solsynth.dev/hydrogen/passport/pkg/internal/services"
@ -139,8 +140,8 @@ func editUserinfo(c *fiber.Ctx) error {
func doRegister(c *fiber.Ctx) error { func doRegister(c *fiber.Ctx) error {
var data struct { var data struct {
Name string `json:"name" validate:"required,lowercase,alphanum,min=4,max=16"` Name string `json:"name" validate:"required,lowercase,alphanum,min=2,max=16"`
Nick string `json:"nick" validate:"required,min=4,max=24"` Nick string `json:"nick" validate:"required,min=2,max=24"`
Email string `json:"email" validate:"required,email"` Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=4,max=32"` Password string `json:"password" validate:"required,min=4,max=32"`
MagicToken string `json:"magic_token"` MagicToken string `json:"magic_token"`

View File

@ -46,10 +46,13 @@ func MapAPIs(app *fiber.App, baseURL string) {
{ {
relations.Get("/", listRelationship) relations.Get("/", listRelationship)
relations.Get("/:relatedId", getRelationship) relations.Get("/:relatedId", getRelationship)
relations.Post("/", makeFriendship)
relations.Post("/:relatedId", makeFriendship)
relations.Put("/:relatedId", editRelationship) relations.Put("/:relatedId", editRelationship)
relations.Delete("/:relatedId", deleteRelationship) relations.Delete("/:relatedId", deleteRelationship)
relations.Post("/", makeFriendship)
relations.Post("/:relatedId", makeFriendship)
relations.Put("/:relatedId", acceptFriend)
relations.Delete("/:relatedId", declineFriend)
} }
} }

View File

@ -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 { func editRelationship(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil { if err := exts.EnsureAuthenticated(c); err != nil {
return err return err
@ -133,3 +101,75 @@ func deleteRelationship(c *fiber.Ctx) error {
return c.JSON(relationship) 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)
}
}

View File

@ -6,6 +6,7 @@ import (
"git.solsynth.dev/hydrogen/passport/pkg/internal/database" "git.solsynth.dev/hydrogen/passport/pkg/internal/database"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models" "git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"github.com/samber/lo"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -66,43 +67,6 @@ func GetRelationWithTwoNode(userId, relatedId uint, noPreload ...bool) (models.A
return relationship, nil 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) { func EditRelationship(relationship models.AccountRelationship) (models.AccountRelationship, error) {
if err := database.C.Save(&relationship).Error; err != nil { if err := database.C.Save(&relationship).Error; err != nil {
return relationship, err return relationship, err
@ -116,3 +80,78 @@ func DeleteRelationship(relationship models.AccountRelationship) error {
} }
return nil 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
}