♻️ Refactored relation system
⬆️ Support new realm & relation api
This commit is contained in:
@ -10,7 +10,7 @@ var AutoMaintainRange = []any{
|
||||
&models.AuthFactor{},
|
||||
&models.AccountProfile{},
|
||||
&models.AccountContact{},
|
||||
&models.AccountFriendship{},
|
||||
&models.AccountRelationship{},
|
||||
&models.Status{},
|
||||
&models.Badge{},
|
||||
&models.Realm{},
|
||||
|
@ -2,23 +2,24 @@ package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
|
||||
|
||||
exproto "git.solsynth.dev/hydrogen/dealer/pkg/proto"
|
||||
"git.solsynth.dev/hydrogen/dealer/pkg/proto"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
func (v *Server) Authenticate(_ context.Context, in *exproto.AuthRequest) (*exproto.AuthReply, error) {
|
||||
func (v *Server) Authenticate(_ context.Context, in *proto.AuthRequest) (*proto.AuthReply, error) {
|
||||
ctx, perms, atk, rtk, err := services.Authenticate(in.GetAccessToken(), in.GetRefreshToken(), 0)
|
||||
if err != nil {
|
||||
return &exproto.AuthReply{
|
||||
return &proto.AuthReply{
|
||||
IsValid: false,
|
||||
}, nil
|
||||
} else {
|
||||
user := ctx.Account
|
||||
rawPerms, _ := jsoniter.Marshal(perms)
|
||||
|
||||
userinfo := &exproto.UserInfo{
|
||||
userinfo := &proto.UserInfo{
|
||||
Id: uint64(user.ID),
|
||||
Name: user.Name,
|
||||
Nick: user.Nick,
|
||||
@ -33,9 +34,9 @@ func (v *Server) Authenticate(_ context.Context, in *exproto.AuthRequest) (*expr
|
||||
userinfo.Banner = *user.GetBanner()
|
||||
}
|
||||
|
||||
return &exproto.AuthReply{
|
||||
return &proto.AuthReply{
|
||||
IsValid: true,
|
||||
Info: &exproto.AuthInfo{
|
||||
Info: &proto.AuthInfo{
|
||||
NewAccessToken: &atk,
|
||||
NewRefreshToken: &rtk,
|
||||
Permissions: rawPerms,
|
||||
@ -46,7 +47,7 @@ func (v *Server) Authenticate(_ context.Context, in *exproto.AuthRequest) (*expr
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Server) EnsurePermGranted(_ context.Context, in *exproto.CheckPermRequest) (*exproto.CheckPermReply, error) {
|
||||
func (v *Server) EnsurePermGranted(_ context.Context, in *proto.CheckPermRequest) (*proto.CheckPermResponse, error) {
|
||||
claims, err := services.DecodeJwt(in.GetToken())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -65,7 +66,26 @@ func (v *Server) EnsurePermGranted(_ context.Context, in *exproto.CheckPermReque
|
||||
perms := services.FilterPermNodes(heldPerms, ctx.Ticket.Claims)
|
||||
valid := services.HasPermNode(perms, in.GetKey(), value)
|
||||
|
||||
return &exproto.CheckPermReply{
|
||||
return &proto.CheckPermResponse{
|
||||
IsValid: valid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *Server) EnsureUserPermGranted(_ context.Context, in *proto.CheckUserPermRequest) (*proto.CheckUserPermResponse, error) {
|
||||
relation, err := services.GetRelationWithTwoNode(uint(in.GetUserId()), uint(in.GetOtherId()))
|
||||
if err != nil {
|
||||
return &proto.CheckUserPermResponse{
|
||||
IsValid: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
defaultPerm := relation.Status == models.RelationshipFriend
|
||||
|
||||
var value any
|
||||
_ = jsoniter.Unmarshal(in.GetValue(), &value)
|
||||
valid := services.HasPermNodeWithDefault(relation.PermNodes, in.GetKey(), value, defaultPerm)
|
||||
|
||||
return &proto.CheckUserPermResponse{
|
||||
IsValid: valid,
|
||||
}, nil
|
||||
}
|
||||
|
@ -1,46 +0,0 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/proto"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func (v *Server) ListFriendship(_ context.Context, request *proto.FriendshipLookupRequest) (*proto.ListFriendshipResponse, error) {
|
||||
account, err := services.GetAccount(uint(request.GetAccountId()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
friends, err := services.ListFriend(account, models.FriendshipStatus(request.GetStatus()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &proto.ListFriendshipResponse{
|
||||
Data: lo.Map(friends, func(item models.AccountFriendship, index int) *proto.FriendshipResponse {
|
||||
return &proto.FriendshipResponse{
|
||||
AccountId: uint64(item.AccountID),
|
||||
RelatedId: uint64(item.RelatedID),
|
||||
Status: uint32(item.Status),
|
||||
}
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *Server) GetFriendship(ctx context.Context, request *proto.FriendshipTwoSideLookupRequest) (*proto.FriendshipResponse, error) {
|
||||
friend, err := services.GetFriendWithTwoSides(uint(request.GetAccountId()), uint(request.GetRelatedId()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if friend.Status != models.FriendshipStatus(request.GetStatus()) {
|
||||
return nil, fmt.Errorf("status mismatch")
|
||||
}
|
||||
|
||||
return &proto.FriendshipResponse{
|
||||
AccountId: uint64(friend.AccountID),
|
||||
RelatedId: uint64(friend.RelatedID),
|
||||
Status: uint32(friend.Status),
|
||||
}, nil
|
||||
}
|
47
pkg/internal/grpc/notifier.go
Normal file
47
pkg/internal/grpc/notifier.go
Normal file
@ -0,0 +1,47 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
|
||||
"git.solsynth.dev/hydrogen/dealer/pkg/proto"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
|
||||
)
|
||||
|
||||
func (v *Server) NotifyUser(_ context.Context, in *proto.NotifyUserRequest) (*proto.NotifyResponse, error) {
|
||||
var err error
|
||||
var user models.Account
|
||||
if user, err = services.GetAccount(uint(in.GetUserId())); err != nil {
|
||||
return nil, fmt.Errorf("unable to get account: %v", err)
|
||||
}
|
||||
|
||||
var metadata map[string]any
|
||||
_ = jsoniter.Unmarshal(in.GetNotify().GetMetadata(), &metadata)
|
||||
|
||||
notification := models.Notification{
|
||||
Topic: in.GetNotify().GetTopic(),
|
||||
Title: in.GetNotify().GetTitle(),
|
||||
Subtitle: in.GetNotify().Subtitle,
|
||||
Body: in.GetNotify().GetBody(),
|
||||
Metadata: metadata,
|
||||
IsRealtime: in.GetNotify().GetIsRealtime(),
|
||||
IsForcePush: in.GetNotify().GetIsForcePush(),
|
||||
UserID: user.ID,
|
||||
}
|
||||
|
||||
if notification.IsRealtime {
|
||||
if err := services.PushNotification(notification); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := services.NewNotification(notification); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &proto.NotifyResponse{
|
||||
IsSuccess: true,
|
||||
}, nil
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/proto"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func (v *Server) NotifyUser(_ context.Context, in *proto.NotifyRequest) (*proto.NotifyReply, error) {
|
||||
client, err := services.GetThirdClientWithSecret(in.GetClientId(), in.GetClientSecret())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var user models.Account
|
||||
if user, err = services.GetAccount(uint(in.GetRecipientId())); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var metadata map[string]any
|
||||
_ = jsoniter.Unmarshal(in.GetMetadata(), &metadata)
|
||||
|
||||
links := lo.Map(in.GetLinks(), func(item *proto.NotifyLink, index int) models.NotificationLink {
|
||||
return models.NotificationLink{
|
||||
Label: item.Label,
|
||||
Url: item.Url,
|
||||
}
|
||||
})
|
||||
|
||||
notification := models.Notification{
|
||||
Type: lo.Ternary(len(in.GetType()) > 0, in.GetType(), "common"),
|
||||
Subject: in.GetSubject(),
|
||||
Content: in.GetContent(),
|
||||
Metadata: metadata,
|
||||
Links: links,
|
||||
IsRealtime: in.GetIsRealtime(),
|
||||
IsForcePush: in.GetIsForcePush(),
|
||||
RecipientID: user.ID,
|
||||
SenderID: &client.ID,
|
||||
}
|
||||
|
||||
if in.GetIsRealtime() {
|
||||
if err := services.PushNotification(notification); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := services.NewNotification(notification); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &proto.NotifyReply{IsSent: true}, nil
|
||||
}
|
@ -3,23 +3,22 @@ package grpc
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.solsynth.dev/hydrogen/dealer/pkg/proto"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/proto"
|
||||
"github.com/samber/lo"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
func (v *Server) ListCommunityRealm(ctx context.Context, empty *emptypb.Empty) (*proto.ListRealmResponse, error) {
|
||||
func (v *Server) ListCommunityRealm(ctx context.Context, empty *proto.ListRealmRequest) (*proto.ListRealmResponse, error) {
|
||||
realms, err := services.ListCommunityRealm()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &proto.ListRealmResponse{
|
||||
Data: lo.Map(realms, func(item models.Realm, index int) *proto.RealmResponse {
|
||||
return &proto.RealmResponse{
|
||||
Data: lo.Map(realms, func(item models.Realm, index int) *proto.RealmInfo {
|
||||
return &proto.RealmInfo{
|
||||
Id: uint64(item.ID),
|
||||
Alias: item.Alias,
|
||||
Name: item.Name,
|
||||
@ -31,7 +30,7 @@ func (v *Server) ListCommunityRealm(ctx context.Context, empty *emptypb.Empty) (
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *Server) ListAvailableRealm(ctx context.Context, request *proto.RealmLookupWithUserRequest) (*proto.ListRealmResponse, error) {
|
||||
func (v *Server) ListAvailableRealm(ctx context.Context, request *proto.LookupUserRealmRequest) (*proto.ListRealmResponse, error) {
|
||||
account, err := services.GetAccount(uint(request.GetUserId()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to find target account: %v", err)
|
||||
@ -42,8 +41,8 @@ func (v *Server) ListAvailableRealm(ctx context.Context, request *proto.RealmLoo
|
||||
}
|
||||
|
||||
return &proto.ListRealmResponse{
|
||||
Data: lo.Map(realms, func(item models.Realm, index int) *proto.RealmResponse {
|
||||
return &proto.RealmResponse{
|
||||
Data: lo.Map(realms, func(item models.Realm, index int) *proto.RealmInfo {
|
||||
return &proto.RealmInfo{
|
||||
Id: uint64(item.ID),
|
||||
Alias: item.Alias,
|
||||
Name: item.Name,
|
||||
@ -55,7 +54,7 @@ func (v *Server) ListAvailableRealm(ctx context.Context, request *proto.RealmLoo
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *Server) ListOwnedRealm(ctx context.Context, request *proto.RealmLookupWithUserRequest) (*proto.ListRealmResponse, error) {
|
||||
func (v *Server) ListOwnedRealm(ctx context.Context, request *proto.LookupUserRealmRequest) (*proto.ListRealmResponse, error) {
|
||||
account, err := services.GetAccount(uint(request.GetUserId()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to find target account: %v", err)
|
||||
@ -66,8 +65,8 @@ func (v *Server) ListOwnedRealm(ctx context.Context, request *proto.RealmLookupW
|
||||
}
|
||||
|
||||
return &proto.ListRealmResponse{
|
||||
Data: lo.Map(realms, func(item models.Realm, index int) *proto.RealmResponse {
|
||||
return &proto.RealmResponse{
|
||||
Data: lo.Map(realms, func(item models.Realm, index int) *proto.RealmInfo {
|
||||
return &proto.RealmInfo{
|
||||
Id: uint64(item.ID),
|
||||
Alias: item.Alias,
|
||||
Name: item.Name,
|
||||
@ -79,7 +78,7 @@ func (v *Server) ListOwnedRealm(ctx context.Context, request *proto.RealmLookupW
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *Server) GetRealm(ctx context.Context, request *proto.RealmLookupRequest) (*proto.RealmResponse, error) {
|
||||
func (v *Server) GetRealm(ctx context.Context, request *proto.LookupRealmRequest) (*proto.RealmInfo, error) {
|
||||
var realm models.Realm
|
||||
|
||||
tx := database.C.Model(&models.Realm{})
|
||||
@ -100,7 +99,7 @@ func (v *Server) GetRealm(ctx context.Context, request *proto.RealmLookupRequest
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &proto.RealmResponse{
|
||||
return &proto.RealmInfo{
|
||||
Id: uint64(realm.ID),
|
||||
Alias: realm.Alias,
|
||||
Name: realm.Name,
|
||||
@ -122,8 +121,8 @@ func (v *Server) ListRealmMember(ctx context.Context, request *proto.RealmMember
|
||||
}
|
||||
|
||||
return &proto.ListRealmMemberResponse{
|
||||
Data: lo.Map(members, func(item models.RealmMember, index int) *proto.RealmMemberResponse {
|
||||
return &proto.RealmMemberResponse{
|
||||
Data: lo.Map(members, func(item models.RealmMember, index int) *proto.MemberInfo {
|
||||
return &proto.MemberInfo{
|
||||
RealmId: uint64(item.RealmID),
|
||||
UserId: uint64(item.AccountID),
|
||||
PowerLevel: int32(item.PowerLevel),
|
||||
@ -132,7 +131,7 @@ func (v *Server) ListRealmMember(ctx context.Context, request *proto.RealmMember
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *Server) GetRealmMember(ctx context.Context, request *proto.RealmMemberLookupRequest) (*proto.RealmMemberResponse, error) {
|
||||
func (v *Server) GetRealmMember(ctx context.Context, request *proto.RealmMemberLookupRequest) (*proto.MemberInfo, error) {
|
||||
var member models.RealmMember
|
||||
tx := database.C.Where("realm_id = ?", request.GetRealmId())
|
||||
if request.UserId != nil {
|
||||
@ -143,9 +142,26 @@ func (v *Server) GetRealmMember(ctx context.Context, request *proto.RealmMemberL
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &proto.RealmMemberResponse{
|
||||
return &proto.MemberInfo{
|
||||
RealmId: uint64(member.RealmID),
|
||||
UserId: uint64(member.AccountID),
|
||||
PowerLevel: int32(member.PowerLevel),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *Server) CheckRealmMemberPerm(ctx context.Context, request *proto.CheckRealmPermRequest) (*proto.CheckRealmPermResponse, error) {
|
||||
var member models.RealmMember
|
||||
tx := database.C.
|
||||
Where("realm_id = ?", request.GetRealmId()).
|
||||
Where("account_id = ?", request.GetUserId())
|
||||
|
||||
if err := tx.First(&member).Error; err != nil {
|
||||
return &proto.CheckRealmPermResponse{
|
||||
IsSuccess: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &proto.CheckRealmPermResponse{
|
||||
IsSuccess: member.PowerLevel >= int(request.GetPowerLevel()),
|
||||
}, nil
|
||||
}
|
||||
|
@ -4,8 +4,7 @@ import (
|
||||
"google.golang.org/grpc/reflection"
|
||||
"net"
|
||||
|
||||
exproto "git.solsynth.dev/hydrogen/dealer/pkg/proto"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/proto"
|
||||
"git.solsynth.dev/hydrogen/dealer/pkg/proto"
|
||||
"github.com/spf13/viper"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
@ -13,10 +12,9 @@ import (
|
||||
import health "google.golang.org/grpc/health/grpc_health_v1"
|
||||
|
||||
type Server struct {
|
||||
exproto.UnimplementedAuthServer
|
||||
proto.UnimplementedNotifyServer
|
||||
proto.UnimplementedFriendshipsServer
|
||||
proto.UnimplementedRealmsServer
|
||||
proto.UnimplementedAuthServer
|
||||
proto.UnimplementedNotifierServer
|
||||
proto.UnimplementedRealmServer
|
||||
health.UnimplementedHealthServer
|
||||
|
||||
srv *grpc.Server
|
||||
@ -27,11 +25,10 @@ func NewServer() *Server {
|
||||
srv: grpc.NewServer(),
|
||||
}
|
||||
|
||||
exproto.RegisterAuthServer(server.srv, &Server{})
|
||||
proto.RegisterNotifyServer(server.srv, &Server{})
|
||||
proto.RegisterFriendshipsServer(server.srv, &Server{})
|
||||
proto.RegisterRealmsServer(server.srv, &Server{})
|
||||
health.RegisterHealthServer(server.srv, &Server{})
|
||||
proto.RegisterAuthServer(server.srv, server)
|
||||
proto.RegisterNotifierServer(server.srv, server)
|
||||
proto.RegisterRealmServer(server.srv, server)
|
||||
health.RegisterHealthServer(server.srv, server)
|
||||
|
||||
reflection.Register(server.srv)
|
||||
|
||||
|
@ -21,12 +21,12 @@ type Account struct {
|
||||
SuspendedAt *time.Time `json:"suspended_at"`
|
||||
PermNodes datatypes.JSONMap `json:"perm_nodes"`
|
||||
|
||||
Profile AccountProfile `json:"profile,omitempty"`
|
||||
Statuses []Status `json:"statuses,omitempty"`
|
||||
Badges []Badge `json:"badges,omitempty"`
|
||||
Profile AccountProfile `json:"profile,omitempty"`
|
||||
Contacts []AccountContact `json:"contacts,omitempty"`
|
||||
Statuses []Status `json:"statuses,omitempty"`
|
||||
Badges []Badge `json:"badges,omitempty"`
|
||||
|
||||
Contacts []AccountContact `json:"contacts,omitempty"`
|
||||
RealmIdentities []RealmMember `json:"realm_identities,omitempty"`
|
||||
Identities []RealmMember `json:"identities,omitempty"`
|
||||
|
||||
Tickets []AuthTicket `json:"tickets,omitempty"`
|
||||
Factors []AuthFactor `json:"factors,omitempty"`
|
||||
@ -36,11 +36,10 @@ type Account struct {
|
||||
|
||||
ThirdClients []ThirdClient `json:"clients,omitempty"`
|
||||
|
||||
Notifications []Notification `json:"notifications,omitempty" gorm:"foreignKey:RecipientID"`
|
||||
Notifications []Notification `json:"notifications,omitempty"`
|
||||
NotifySubscribers []NotificationSubscriber `json:"notify_subscribers,omitempty"`
|
||||
|
||||
Friendships []AccountFriendship `json:"friendships,omitempty" gorm:"foreignKey:AccountID"`
|
||||
RelatedFriendships []AccountFriendship `json:"related_friendships,omitempty" gorm:"foreignKey:RelatedID"`
|
||||
Relations []AccountRelationship `json:"relations,omitempty" gorm:"foreignKey:AccountID"`
|
||||
}
|
||||
|
||||
func (v Account) GetAvatar() *string {
|
||||
|
@ -5,14 +5,12 @@ import "gorm.io/datatypes"
|
||||
type ThirdClient struct {
|
||||
BaseModel
|
||||
|
||||
Alias string `json:"alias" gorm:"uniqueIndex"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Secret string `json:"secret"`
|
||||
Urls datatypes.JSONSlice[string] `json:"urls"`
|
||||
Callbacks datatypes.JSONSlice[string] `json:"callbacks"`
|
||||
Sessions []AuthTicket `json:"tickets" gorm:"foreignKey:ClientID"`
|
||||
Notifications []Notification `json:"notifications" gorm:"foreignKey:SenderID"`
|
||||
IsDraft bool `json:"is_draft"`
|
||||
AccountID *uint `json:"account_id"`
|
||||
Alias string `json:"alias" gorm:"uniqueIndex"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Secret string `json:"secret"`
|
||||
Urls datatypes.JSONSlice[string] `json:"urls"`
|
||||
Callbacks datatypes.JSONSlice[string] `json:"callbacks"`
|
||||
IsDraft bool `json:"is_draft"`
|
||||
AccountID *uint `json:"account_id"`
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
package models
|
||||
|
||||
type FriendshipStatus = int8
|
||||
|
||||
const (
|
||||
FriendshipPending = FriendshipStatus(iota)
|
||||
FriendshipActive
|
||||
FriendshipBlocked
|
||||
)
|
||||
|
||||
type AccountFriendship struct {
|
||||
BaseModel
|
||||
|
||||
AccountID uint `json:"account_id"`
|
||||
RelatedID uint `json:"related_id"`
|
||||
BlockedBy *uint `json:"blocked_by"`
|
||||
Account Account `json:"account"`
|
||||
Related Account `json:"related"`
|
||||
Status FriendshipStatus `json:"status"`
|
||||
}
|
@ -7,15 +7,16 @@ import (
|
||||
type Notification struct {
|
||||
BaseModel
|
||||
|
||||
Type string `json:"type"`
|
||||
Subject string `json:"subject"`
|
||||
Content string `json:"content"`
|
||||
Metadata datatypes.JSONMap `json:"metadata"`
|
||||
Links datatypes.JSONSlice[NotificationLink] `json:"links"`
|
||||
IsRealtime bool `json:"is_realtime" gorm:"-"`
|
||||
IsForcePush bool `json:"is_force_push" gorm:"-"`
|
||||
SenderID *uint `json:"sender_id"`
|
||||
RecipientID uint `json:"recipient_id"`
|
||||
Topic string `json:"topic"`
|
||||
Title string `json:"title"`
|
||||
Subtitle *string `json:"subtitle"`
|
||||
Body string `json:"body"`
|
||||
Metadata datatypes.JSONMap `json:"metadata"`
|
||||
UserID uint `json:"user_id"`
|
||||
SenderID *uint `json:"sender_id"`
|
||||
|
||||
IsRealtime bool `json:"is_realtime" gorm:"-"`
|
||||
IsForcePush bool `json:"is_force_push" gorm:"-"`
|
||||
}
|
||||
|
||||
// NotificationLink Used to embed into notify and render actions
|
||||
|
22
pkg/internal/models/relationships.go
Normal file
22
pkg/internal/models/relationships.go
Normal file
@ -0,0 +1,22 @@
|
||||
package models
|
||||
|
||||
import "gorm.io/datatypes"
|
||||
|
||||
type RelationshipStatus = int8
|
||||
|
||||
const (
|
||||
RelationshipPending = RelationshipStatus(iota)
|
||||
RelationshipFriend
|
||||
RelationshipBlocked
|
||||
)
|
||||
|
||||
type AccountRelationship struct {
|
||||
BaseModel
|
||||
|
||||
AccountID uint `json:"account_id"`
|
||||
RelatedID uint `json:"related_id"`
|
||||
Account Account `json:"account"`
|
||||
Related Account `json:"related"`
|
||||
Status RelationshipStatus `json:"status"`
|
||||
PermNodes datatypes.JSONMap `json:"perm_nodes"`
|
||||
}
|
@ -11,13 +11,13 @@ import (
|
||||
|
||||
func notifyAllUser(c *fiber.Ctx) error {
|
||||
var data struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
Subject string `json:"subject" validate:"required,max=1024"`
|
||||
Content string `json:"content" validate:"required,max=4096"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
Links []models.NotificationLink `json:"links"`
|
||||
IsForcePush bool `json:"is_force_push"`
|
||||
IsRealtime bool `json:"is_realtime"`
|
||||
Topic string `json:"type" validate:"required"`
|
||||
Title string `json:"subject" validate:"required,max=1024"`
|
||||
Subtitle *string `json:"subtitle" validate:"max=1024"`
|
||||
Body string `json:"content" validate:"required,max=4096"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
IsForcePush bool `json:"is_force_push"`
|
||||
IsRealtime bool `json:"is_realtime"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
@ -41,13 +41,13 @@ func notifyAllUser(c *fiber.Ctx) error {
|
||||
go func() {
|
||||
for _, user := range users {
|
||||
notification := models.Notification{
|
||||
Type: data.Type,
|
||||
Subject: data.Subject,
|
||||
Content: data.Content,
|
||||
Links: data.Links,
|
||||
Topic: data.Topic,
|
||||
Subtitle: data.Subtitle,
|
||||
Title: data.Title,
|
||||
Body: data.Body,
|
||||
IsRealtime: data.IsRealtime,
|
||||
IsForcePush: data.IsForcePush,
|
||||
RecipientID: user.ID,
|
||||
UserID: user.ID,
|
||||
}
|
||||
|
||||
if data.IsRealtime {
|
||||
@ -67,14 +67,14 @@ func notifyAllUser(c *fiber.Ctx) error {
|
||||
|
||||
func notifyOneUser(c *fiber.Ctx) error {
|
||||
var data struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
Subject string `json:"subject" validate:"required,max=1024"`
|
||||
Content string `json:"content" validate:"required,max=4096"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
Links []models.NotificationLink `json:"links"`
|
||||
IsForcePush bool `json:"is_force_push"`
|
||||
IsRealtime bool `json:"is_realtime"`
|
||||
UserID uint `json:"user_id"`
|
||||
Topic string `json:"type" validate:"required"`
|
||||
Title string `json:"subject" validate:"required,max=1024"`
|
||||
Subtitle *string `json:"subtitle" validate:"max=1024"`
|
||||
Body string `json:"content" validate:"required,max=4096"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
IsForcePush bool `json:"is_force_push"`
|
||||
IsRealtime bool `json:"is_realtime"`
|
||||
UserID uint `json:"user_id" validate:"required"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
@ -97,13 +97,13 @@ func notifyOneUser(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
notification := models.Notification{
|
||||
Type: data.Type,
|
||||
Subject: data.Subject,
|
||||
Content: data.Content,
|
||||
Links: data.Links,
|
||||
Topic: data.Topic,
|
||||
Subtitle: data.Subtitle,
|
||||
Title: data.Title,
|
||||
Body: data.Body,
|
||||
IsRealtime: data.IsRealtime,
|
||||
IsForcePush: data.IsForcePush,
|
||||
RecipientID: user.ID,
|
||||
UserID: user.ID,
|
||||
}
|
||||
|
||||
if data.IsRealtime {
|
||||
|
@ -42,14 +42,14 @@ func MapAPIs(app *fiber.App) {
|
||||
me.Put("/status", editStatus)
|
||||
me.Delete("/status", clearStatus)
|
||||
|
||||
friends := me.Group("/friends").Name("Friends")
|
||||
friends := me.Group("/relations").Name("Relations")
|
||||
{
|
||||
friends.Get("/", listFriendship)
|
||||
friends.Get("/:relatedId", getFriendship)
|
||||
friends.Get("/", listRelationship)
|
||||
friends.Get("/:relatedId", getRelationship)
|
||||
friends.Post("/", makeFriendship)
|
||||
friends.Post("/:relatedId", makeFriendship)
|
||||
friends.Put("/:relatedId", editFriendship)
|
||||
friends.Delete("/:relatedId", deleteFriendship)
|
||||
friends.Put("/:relatedId", editRelationship)
|
||||
friends.Delete("/:relatedId", deleteRelationship)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ func getNotifications(c *fiber.Ctx) error {
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
tx := database.C.Where(&models.Notification{RecipientID: user.ID}).Model(&models.Notification{})
|
||||
tx := database.C.Where(&models.Notification{UserID: user.ID}).Model(&models.Notification{})
|
||||
|
||||
var count int64
|
||||
var notifications []models.Notification
|
||||
@ -52,8 +52,8 @@ func markNotificationRead(c *fiber.Ctx) error {
|
||||
|
||||
var notify models.Notification
|
||||
if err := database.C.Where(&models.Notification{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
RecipientID: user.ID,
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
UserID: user.ID,
|
||||
}).First(¬ify).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
@ -9,16 +9,16 @@ import (
|
||||
|
||||
func notifyUser(c *fiber.Ctx) error {
|
||||
var data struct {
|
||||
ClientID string `json:"client_id" validate:"required"`
|
||||
ClientSecret string `json:"client_secret" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
Subject string `json:"subject" validate:"required,max=1024"`
|
||||
Content string `json:"content" validate:"required,max=4096"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
Links []models.NotificationLink `json:"links"`
|
||||
IsForcePush bool `json:"is_force_push"`
|
||||
IsRealtime bool `json:"is_realtime"`
|
||||
UserID uint `json:"user_id" validate:"required"`
|
||||
ClientID string `json:"client_id" validate:"required"`
|
||||
ClientSecret string `json:"client_secret" validate:"required"`
|
||||
Topic string `json:"type" validate:"required"`
|
||||
Title string `json:"subject" validate:"required,max=1024"`
|
||||
Subtitle *string `json:"subtitle" validate:"max=1024"`
|
||||
Body string `json:"content" validate:"required,max=4096"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
IsForcePush bool `json:"is_force_push"`
|
||||
IsRealtime bool `json:"is_realtime"`
|
||||
UserID uint `json:"user_id" validate:"required"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
@ -36,13 +36,13 @@ func notifyUser(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
notification := models.Notification{
|
||||
Type: data.Type,
|
||||
Subject: data.Subject,
|
||||
Content: data.Content,
|
||||
Links: data.Links,
|
||||
Topic: data.Topic,
|
||||
Subtitle: data.Subtitle,
|
||||
Title: data.Title,
|
||||
Body: data.Body,
|
||||
IsRealtime: data.IsRealtime,
|
||||
IsForcePush: data.IsForcePush,
|
||||
RecipientID: user.ID,
|
||||
UserID: user.ID,
|
||||
SenderID: &client.ID,
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func listFriendship(c *fiber.Ctx) error {
|
||||
func listRelationship(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -15,13 +15,13 @@ func listFriendship(c *fiber.Ctx) error {
|
||||
status := c.QueryInt("status", -1)
|
||||
|
||||
var err error
|
||||
var friends []models.AccountFriendship
|
||||
var friends []models.AccountRelationship
|
||||
if status < 0 {
|
||||
if friends, err = services.ListAllFriend(user); err != nil {
|
||||
if friends, err = services.ListAllRelationship(user); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
} else {
|
||||
if friends, err = services.ListFriend(user, models.FriendshipStatus(status)); err != nil {
|
||||
if friends, err = services.ListRelationshipWithFilter(user, models.RelationshipStatus(status)); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
}
|
||||
@ -29,7 +29,7 @@ func listFriendship(c *fiber.Ctx) error {
|
||||
return c.JSON(friends)
|
||||
}
|
||||
|
||||
func getFriendship(c *fiber.Ctx) error {
|
||||
func getRelationship(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -41,7 +41,7 @@ func getFriendship(c *fiber.Ctx) error {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if friend, err := services.GetFriendWithTwoSides(user.ID, related.ID); err != nil {
|
||||
if friend, err := services.GetRelationWithTwoNode(user.ID, related.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else {
|
||||
return c.JSON(friend)
|
||||
@ -72,7 +72,7 @@ func makeFriendship(c *fiber.Ctx) error {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "must one of username or user id")
|
||||
}
|
||||
|
||||
friend, err := services.NewFriend(user, related, models.FriendshipPending)
|
||||
friend, err := services.NewFriend(user, related)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
@ -80,7 +80,7 @@ func makeFriendship(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
|
||||
func editFriendship(c *fiber.Ctx) error {
|
||||
func editRelationship(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -88,33 +88,30 @@ func editFriendship(c *fiber.Ctx) error {
|
||||
relatedId, _ := c.ParamsInt("relatedId", 0)
|
||||
|
||||
var data struct {
|
||||
Status uint8 `json:"status"`
|
||||
Status uint8 `json:"status"`
|
||||
PermNodes map[string]any `json:"perm_nodes"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
related, err := services.GetAccount(uint(relatedId))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
friendship, err := services.GetFriendWithTwoSides(user.ID, related.ID)
|
||||
relationship, err := services.GetRelationWithTwoNode(user.ID, uint(relatedId))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
originalStatus := friendship.Status
|
||||
friendship.Status = models.FriendshipStatus(data.Status)
|
||||
relationship.Status = models.RelationshipStatus(data.Status)
|
||||
relationship.PermNodes = data.PermNodes
|
||||
|
||||
if friendship, err := services.EditFriendWithCheck(friendship, user, originalStatus); err != nil {
|
||||
if friendship, err := services.EditRelationship(relationship); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(friendship)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteFriendship(c *fiber.Ctx) error {
|
||||
func deleteRelationship(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -125,14 +122,14 @@ func deleteFriendship(c *fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
friendship, err := services.GetFriendWithTwoSides(user.ID, related.ID)
|
||||
relationship, err := services.GetRelationWithTwoNode(user.ID, related.ID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.DeleteFriend(friendship); err != nil {
|
||||
if err := services.DeleteRelationship(relationship); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(friendship)
|
||||
return c.JSON(relationship)
|
||||
}
|
||||
}
|
@ -209,7 +209,7 @@ func DeleteAccount(id uint) error {
|
||||
&models.MagicToken{},
|
||||
&models.ThirdClient{},
|
||||
&models.NotificationSubscriber{},
|
||||
&models.AccountFriendship{},
|
||||
&models.AccountRelationship{},
|
||||
} {
|
||||
if err := tx.Delete(model, "account_id = ?", id).Error; err != nil {
|
||||
tx.Rollback()
|
||||
|
@ -1,125 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func ListAllFriend(anyside models.Account) ([]models.AccountFriendship, error) {
|
||||
var relationships []models.AccountFriendship
|
||||
if err := database.C.
|
||||
Where("account_id = ? OR related_id = ?", anyside.ID, anyside.ID).
|
||||
Preload("Account").
|
||||
Preload("Related").
|
||||
Find(&relationships).Error; err != nil {
|
||||
return relationships, err
|
||||
}
|
||||
|
||||
return relationships, nil
|
||||
}
|
||||
|
||||
func ListFriend(anyside models.Account, status models.FriendshipStatus) ([]models.AccountFriendship, error) {
|
||||
var relationships []models.AccountFriendship
|
||||
if err := database.C.
|
||||
Where("(account_id = ? OR related_id = ?) AND status = ?", anyside.ID, anyside.ID, status).
|
||||
Preload("Account").
|
||||
Preload("Related").
|
||||
Find(&relationships).Error; err != nil {
|
||||
return relationships, err
|
||||
}
|
||||
|
||||
return relationships, nil
|
||||
}
|
||||
|
||||
func GetFriend(anysideId uint) (models.AccountFriendship, error) {
|
||||
var relationship models.AccountFriendship
|
||||
if err := database.C.
|
||||
Where(&models.AccountFriendship{AccountID: anysideId}).
|
||||
Or(&models.AccountFriendship{RelatedID: anysideId}).
|
||||
Preload("Account").
|
||||
Preload("Related").
|
||||
First(&relationship).Error; err != nil {
|
||||
return relationship, err
|
||||
}
|
||||
|
||||
return relationship, nil
|
||||
}
|
||||
|
||||
func GetFriendWithTwoSides(userId, relatedId uint, noPreload ...bool) (models.AccountFriendship, error) {
|
||||
var tx *gorm.DB
|
||||
if len(noPreload) > 0 && noPreload[0] {
|
||||
tx = database.C
|
||||
} else {
|
||||
tx = database.C.Preload("Account").Preload("Related")
|
||||
}
|
||||
|
||||
var relationship models.AccountFriendship
|
||||
if err := tx.
|
||||
Where(&models.AccountFriendship{AccountID: userId, RelatedID: relatedId}).
|
||||
Or(&models.AccountFriendship{RelatedID: userId, AccountID: relatedId}).
|
||||
First(&relationship).Error; err != nil {
|
||||
return relationship, err
|
||||
}
|
||||
|
||||
return relationship, nil
|
||||
}
|
||||
|
||||
func NewFriend(user models.Account, related models.Account, status models.FriendshipStatus) (models.AccountFriendship, error) {
|
||||
relationship := models.AccountFriendship{
|
||||
AccountID: user.ID,
|
||||
RelatedID: related.ID,
|
||||
Status: status,
|
||||
}
|
||||
|
||||
if user.ID == related.ID {
|
||||
return relationship, fmt.Errorf("you cannot make friendship with yourself")
|
||||
} else if _, err := GetFriendWithTwoSides(user.ID, related.ID, true); err == nil || !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return relationship, fmt.Errorf("you already have a friendship with him or her")
|
||||
}
|
||||
|
||||
if err := database.C.Save(&relationship).Error; err != nil {
|
||||
return relationship, err
|
||||
} else {
|
||||
_ = NewNotification(models.Notification{
|
||||
Subject: fmt.Sprintf("New friend request from %s", user.Name),
|
||||
Content: fmt.Sprintf("You got a new friend request from %s. Go to your settings and decide how to deal it.", user.Nick),
|
||||
RecipientID: related.ID,
|
||||
})
|
||||
}
|
||||
|
||||
return relationship, nil
|
||||
}
|
||||
|
||||
func EditFriendWithCheck(relationship models.AccountFriendship, user models.Account, originalStatus models.FriendshipStatus) (models.AccountFriendship, error) {
|
||||
if relationship.Status != originalStatus {
|
||||
if originalStatus == models.FriendshipBlocked && relationship.BlockedBy != nil && user.ID != *relationship.BlockedBy {
|
||||
return relationship, fmt.Errorf("the friendship has been blocked by the otherside, you cannot modify it status")
|
||||
}
|
||||
if relationship.Status == models.FriendshipPending && relationship.RelatedID != user.ID {
|
||||
return relationship, fmt.Errorf("only related person can accept friendship")
|
||||
}
|
||||
}
|
||||
if originalStatus != models.FriendshipBlocked && relationship.Status == models.FriendshipBlocked {
|
||||
relationship.BlockedBy = &user.ID
|
||||
}
|
||||
|
||||
return EditFriend(relationship)
|
||||
}
|
||||
|
||||
func EditFriend(relationship models.AccountFriendship) (models.AccountFriendship, error) {
|
||||
if err := database.C.Save(&relationship).Error; err != nil {
|
||||
return relationship, err
|
||||
}
|
||||
return relationship, nil
|
||||
}
|
||||
|
||||
func DeleteFriend(relationship models.AccountFriendship) error {
|
||||
if err := database.C.Delete(&relationship).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -61,15 +61,15 @@ func NewNotification(notification models.Notification) error {
|
||||
}
|
||||
|
||||
// PushNotification will push the notification whatever it exists record in the
|
||||
// database Recommend push another goroutine when you need to push a lot of
|
||||
// database Recommend pushing another goroutine when you need to push a lot of
|
||||
// notifications And just use a block statement when you just push one
|
||||
// notification, the time of create a new subprocess is much more than push
|
||||
// notification
|
||||
// notification.
|
||||
// The time of creating a new subprocess is much more than push notification.
|
||||
func PushNotification(notification models.Notification) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
_, err := proto.NewStreamControllerClient(gap.H.GetDealerGrpcConn()).PushStream(ctx, &proto.PushStreamRequest{
|
||||
UserId: uint64(notification.RecipientID),
|
||||
UserId: uint64(notification.UserID),
|
||||
Body: models.UnifiedCommand{
|
||||
Action: "notifications.new",
|
||||
Payload: notification,
|
||||
@ -80,13 +80,13 @@ func PushNotification(notification models.Notification) error {
|
||||
}
|
||||
|
||||
// Skip push notification
|
||||
if GetStatusDisturbable(notification.RecipientID) != nil {
|
||||
if GetStatusDisturbable(notification.UserID) != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var subscribers []models.NotificationSubscriber
|
||||
if err := database.C.Where(&models.NotificationSubscriber{
|
||||
AccountID: notification.RecipientID,
|
||||
AccountID: notification.UserID,
|
||||
}).Find(&subscribers).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
@ -104,8 +104,8 @@ func PushNotification(notification models.Notification) error {
|
||||
|
||||
message := &messaging.Message{
|
||||
Notification: &messaging.Notification{
|
||||
Title: notification.Subject,
|
||||
Body: notification.Content,
|
||||
Title: notification.Title,
|
||||
Body: notification.Body,
|
||||
},
|
||||
Token: subscriber.DeviceToken,
|
||||
}
|
||||
@ -123,10 +123,10 @@ func PushNotification(notification models.Notification) error {
|
||||
if ExtAPNS != nil {
|
||||
data, err := payload2.
|
||||
NewPayload().
|
||||
AlertTitle(notification.Subject).
|
||||
AlertBody(notification.Content).
|
||||
AlertTitle(notification.Title).
|
||||
AlertBody(notification.Body).
|
||||
Sound("default").
|
||||
Category(notification.Type).
|
||||
Category(notification.Topic).
|
||||
MarshalJSON()
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("An error occurred when preparing to notify subscriber via APNs...")
|
||||
|
@ -14,6 +14,13 @@ func HasPermNode(perms map[string]any, requiredKey string, requiredValue any) bo
|
||||
return false
|
||||
}
|
||||
|
||||
func HasPermNodeWithDefault(perms map[string]any, requiredKey string, requiredValue any, defaultValue any) bool {
|
||||
if heldValue, ok := perms[requiredKey]; ok {
|
||||
return ComparePermNode(heldValue, requiredValue)
|
||||
}
|
||||
return ComparePermNode(defaultValue, requiredValue)
|
||||
}
|
||||
|
||||
func ComparePermNode(held any, required any) bool {
|
||||
heldValue := reflect.ValueOf(held)
|
||||
requiredValue := reflect.ValueOf(required)
|
||||
|
@ -97,9 +97,14 @@ func AddRealmMember(user models.Account, affected models.Account, target models.
|
||||
} else if member.PowerLevel < 50 {
|
||||
return fmt.Errorf("only realm moderator can add people")
|
||||
}
|
||||
friendship, err := GetFriendWithTwoSides(affected.ID, user.ID)
|
||||
if err != nil || friendship.Status != models.FriendshipActive {
|
||||
return fmt.Errorf("you only can add your friends to your realm")
|
||||
rel, err := GetRelationWithTwoNode(affected.ID, user.ID)
|
||||
if err != nil || HasPermNodeWithDefault(
|
||||
rel.PermNodes,
|
||||
"RealmAdd",
|
||||
true,
|
||||
rel.Status == models.RelationshipFriend,
|
||||
) {
|
||||
return fmt.Errorf("you unable to add this user to your realm")
|
||||
}
|
||||
}
|
||||
|
||||
|
118
pkg/internal/services/relationships.go
Normal file
118
pkg/internal/services/relationships.go
Normal file
@ -0,0 +1,118 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func ListAllRelationship(user models.Account) ([]models.AccountRelationship, error) {
|
||||
var relationships []models.AccountRelationship
|
||||
if err := database.C.
|
||||
Where("account_id = ?", user.ID).
|
||||
Preload("Account").
|
||||
Preload("Related").
|
||||
Find(&relationships).Error; err != nil {
|
||||
return relationships, err
|
||||
}
|
||||
|
||||
return relationships, nil
|
||||
}
|
||||
|
||||
func ListRelationshipWithFilter(user models.Account, status models.RelationshipStatus) ([]models.AccountRelationship, error) {
|
||||
var relationships []models.AccountRelationship
|
||||
if err := database.C.
|
||||
Where("account_id = ? AND status = ?", user.ID, status).
|
||||
Preload("Account").
|
||||
Preload("Related").
|
||||
Find(&relationships).Error; err != nil {
|
||||
return relationships, err
|
||||
}
|
||||
|
||||
return relationships, nil
|
||||
}
|
||||
|
||||
func GetRelationship(otherId uint) (models.AccountRelationship, error) {
|
||||
var relationship models.AccountRelationship
|
||||
if err := database.C.
|
||||
Where(&models.AccountRelationship{AccountID: otherId}).
|
||||
Preload("Account").
|
||||
Preload("Related").
|
||||
First(&relationship).Error; err != nil {
|
||||
return relationship, err
|
||||
}
|
||||
|
||||
return relationship, nil
|
||||
}
|
||||
|
||||
func GetRelationWithTwoNode(userId, relatedId uint, noPreload ...bool) (models.AccountRelationship, error) {
|
||||
var tx *gorm.DB
|
||||
if len(noPreload) > 0 && noPreload[0] {
|
||||
tx = database.C
|
||||
} else {
|
||||
tx = database.C.Preload("Account").Preload("Related")
|
||||
}
|
||||
|
||||
var relationship models.AccountRelationship
|
||||
if err := tx.
|
||||
Where(&models.AccountRelationship{AccountID: userId, RelatedID: relatedId}).
|
||||
First(&relationship).Error; err != nil {
|
||||
return relationship, err
|
||||
}
|
||||
|
||||
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),
|
||||
UserID: 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
|
||||
}
|
||||
return relationship, nil
|
||||
}
|
||||
|
||||
func DeleteRelationship(relationship models.AccountRelationship) error {
|
||||
if err := database.C.Delete(&relationship).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user