⬆️ Upgrade to support the latest version Hydrogen Project standard

This commit is contained in:
2024-06-22 18:05:41 +08:00
parent fa50baf927
commit 9366f6e56e
55 changed files with 603 additions and 385 deletions

View File

@ -0,0 +1,60 @@
package services
import (
"context"
"fmt"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/database"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/gap"
"time"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/proto"
"github.com/spf13/viper"
)
func GetAccountFriend(userId, relatedId uint, status int) (*proto.FriendshipResponse, error) {
var user models.Account
if err := database.C.Where("id = ?", userId).First(&user).Error; err != nil {
return nil, err
}
var related models.Account
if err := database.C.Where("id = ?", relatedId).First(&related).Error; err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
pc, err := gap.H.DiscoverServiceGRPC("Hydrogen.Passport")
if err != nil {
return nil, err
}
return proto.NewFriendshipsClient(pc).GetFriendship(ctx, &proto.FriendshipTwoSideLookupRequest{
AccountId: uint64(user.ExternalID),
RelatedId: uint64(related.ExternalID),
Status: uint32(status),
})
}
func NotifyAccountMessager(user models.Account, t, s, c string, realtime bool, forcePush bool, links ...*proto.NotifyLink) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
pc, err := gap.H.DiscoverServiceGRPC("Hydrogen.Passport")
if err != nil {
return err
}
_, err = proto.NewNotifyClient(pc).NotifyUser(ctx, &proto.NotifyRequest{
ClientId: viper.GetString("passport.client_id"),
ClientSecret: viper.GetString("passport.client_secret"),
Type: fmt.Sprintf("messaging.%s", t),
Subject: s,
Content: c,
Links: links,
RecipientId: uint64(user.ExternalID),
IsRealtime: realtime,
IsForcePush: forcePush,
})
return err
}

View File

@ -0,0 +1,22 @@
package services
import (
"context"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/gap"
"git.solsynth.dev/hydrogen/paperclip/pkg/proto"
"github.com/samber/lo"
)
func CheckAttachmentByIDExists(id uint, usage string) bool {
pc, err := gap.H.DiscoverServiceGRPC("Hydrogen.Passport")
if err != nil {
return false
}
_, err = proto.NewAttachmentsClient(pc).CheckAttachmentExists(context.Background(), &proto.AttachmentLookupRequest{
Id: lo.ToPtr(uint64(id)),
Usage: &usage,
})
return err == nil
}

View File

@ -0,0 +1,51 @@
package services
import (
"errors"
"fmt"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/database"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/proto"
"gorm.io/gorm"
"reflect"
)
func LinkAccount(userinfo *proto.Userinfo) (models.Account, error) {
var account models.Account
if userinfo == nil {
return account, fmt.Errorf("remote userinfo was not found")
}
if err := database.C.Where(&models.Account{
ExternalID: uint(userinfo.Id),
}).First(&account).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
account = models.Account{
Name: userinfo.Name,
Nick: userinfo.Nick,
Avatar: userinfo.Avatar,
Banner: userinfo.Banner,
Description: userinfo.GetDescription(),
EmailAddress: userinfo.Email,
PowerLevel: 0,
ExternalID: uint(userinfo.Id),
}
return account, database.C.Save(&account).Error
}
return account, err
}
prev := account
account.Name = userinfo.Name
account.Nick = userinfo.Nick
account.Avatar = userinfo.Avatar
account.Banner = userinfo.Banner
account.Description = userinfo.GetDescription()
account.EmailAddress = userinfo.Email
var err error
if !reflect.DeepEqual(account, prev) {
err = database.C.Save(&account).Error
}
return account, err
}

View File

@ -0,0 +1,172 @@
package services
import (
"context"
"errors"
"fmt"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/database"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
jsoniter "github.com/json-iterator/go"
"github.com/livekit/protocol/auth"
"github.com/livekit/protocol/livekit"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
"github.com/spf13/viper"
"gorm.io/gorm"
"time"
)
func ListCall(channel models.Channel, take, offset int) ([]models.Call, error) {
var calls []models.Call
if err := database.C.
Where(models.Call{ChannelID: channel.ID}).
Limit(take).
Offset(offset).
Preload("Founder").
Preload("Founder.Account").
Preload("Channel").
Order("created_at DESC").
Find(&calls).Error; err != nil {
return calls, err
} else {
return calls, nil
}
}
func GetCall(channel models.Channel, id uint) (models.Call, error) {
var call models.Call
if err := database.C.
Where(models.Call{
BaseModel: models.BaseModel{ID: id},
ChannelID: channel.ID,
}).
Preload("Founder").
Preload("Founder.Account").
Preload("Channel").
Order("created_at DESC").
First(&call).Error; err != nil {
return call, err
} else {
return call, nil
}
}
func GetOngoingCall(channel models.Channel) (models.Call, error) {
var call models.Call
if err := database.C.
Where(models.Call{ChannelID: channel.ID}).
Where("ended_at IS NULL").
Preload("Founder").
Preload("Channel").
Order("created_at DESC").
First(&call).Error; err != nil {
return call, err
} else {
return call, nil
}
}
func NewCall(channel models.Channel, founder models.ChannelMember) (models.Call, error) {
call := models.Call{
ExternalID: channel.Alias,
FounderID: founder.ID,
ChannelID: channel.ID,
Founder: founder,
Channel: channel,
}
if _, err := GetOngoingCall(channel); err == nil || !errors.Is(err, gorm.ErrRecordNotFound) {
return call, fmt.Errorf("this channel already has an ongoing call")
}
_, err := Lk.CreateRoom(context.Background(), &livekit.CreateRoomRequest{
Name: call.ExternalID,
EmptyTimeout: viper.GetUint32("calling.empty_timeout_duration"),
MaxParticipants: viper.GetUint32("calling.max_participants"),
})
if err != nil {
return call, fmt.Errorf("remote livekit error: %v", err)
}
var members []models.ChannelMember
if err := database.C.Save(&call).Error; err != nil {
return call, err
} else if err = database.C.Where(models.ChannelMember{
ChannelID: call.ChannelID,
}).Preload("Account").Find(&members).Error; err == nil {
channel := call.Channel
call, _ = GetCall(call.Channel, call.ID)
for _, member := range members {
if member.ID != call.Founder.ID {
err = NotifyAccountMessager(member.Account,
"incomingCall",
fmt.Sprintf("Call in #%s", channel.Alias),
fmt.Sprintf("%s started a new call", call.Founder.Account.Name),
false,
true,
)
if err != nil {
log.Warn().Err(err).Msg("An error occurred when trying notify user.")
}
}
PushCommand(member.AccountID, models.UnifiedCommand{
Action: "calls.new",
Payload: call,
})
}
}
return call, nil
}
func EndCall(call models.Call) (models.Call, error) {
call.EndedAt = lo.ToPtr(time.Now())
if _, err := Lk.DeleteRoom(context.Background(), &livekit.DeleteRoomRequest{
Room: call.ExternalID,
}); err != nil {
log.Error().Err(err).Msg("Unable to delete room at livekit side")
}
var members []models.ChannelMember
if err := database.C.Save(&call).Error; err != nil {
return call, err
} else if err = database.C.Where(models.ChannelMember{
ChannelID: call.ChannelID,
}).Preload("Account").Find(&members).Error; err == nil {
call, _ = GetCall(call.Channel, call.ID)
for _, member := range members {
PushCommand(member.AccountID, models.UnifiedCommand{
Action: "calls.end",
Payload: call,
})
}
}
return call, nil
}
func EncodeCallToken(user models.Account, call models.Call) (string, error) {
isAdmin := false
if user.ID == call.FounderID || user.ID == call.Channel.AccountID {
isAdmin = true
}
grant := &auth.VideoGrant{
Room: call.ExternalID,
RoomJoin: true,
RoomAdmin: isAdmin,
}
metadata, _ := jsoniter.Marshal(user)
duration := time.Second * time.Duration(viper.GetInt("calling.token_duration"))
tk := auth.NewAccessToken(viper.GetString("calling.api_key"), viper.GetString("calling.api_secret"))
tk.AddGrant(grant).
SetIdentity(user.Name).
SetName(user.Nick).
SetMetadata(string(metadata)).
SetValidFor(duration)
return tk.ToJWT()
}

View File

@ -0,0 +1,77 @@
package services
import (
"fmt"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/database"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
)
func ListChannelMember(channelId uint) ([]models.ChannelMember, error) {
var members []models.ChannelMember
if err := database.C.
Where(&models.ChannelMember{ChannelID: channelId}).
Preload("Account").
Find(&members).Error; err != nil {
return members, err
}
return members, nil
}
func GetChannelMember(user models.Account, channelId uint) (models.ChannelMember, error) {
var member models.ChannelMember
if err := database.C.
Where(&models.ChannelMember{AccountID: user.ID, ChannelID: channelId}).
Find(&member).Error; err != nil {
return member, err
}
return member, nil
}
func AddChannelMemberWithCheck(user models.Account, target models.Channel) error {
if _, err := GetAccountFriend(user.ID, target.AccountID, 1); err != nil {
return fmt.Errorf("you only can invite your friends to your channel")
}
member := models.ChannelMember{
ChannelID: target.ID,
AccountID: user.ID,
}
err := database.C.Save(&member).Error
return err
}
func AddChannelMember(user models.Account, target models.Channel) error {
member := models.ChannelMember{
ChannelID: target.ID,
AccountID: user.ID,
}
err := database.C.Save(&member).Error
return err
}
func EditChannelMember(membership models.ChannelMember) (models.ChannelMember, error) {
if err := database.C.Save(&membership).Error; err != nil {
return membership, err
}
return membership, nil
}
func RemoveChannelMember(user models.Account, target models.Channel) error {
var member models.ChannelMember
if err := database.C.Where(&models.ChannelMember{
ChannelID: target.ID,
AccountID: user.ID,
}).First(&member).Error; err != nil {
return err
}
return database.C.Delete(&member).Error
}

View File

@ -0,0 +1,177 @@
package services
import (
"fmt"
"regexp"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/database"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
"github.com/samber/lo"
"github.com/spf13/viper"
"gorm.io/gorm"
)
func GetChannelAliasAvailability(alias string) error {
if !regexp.MustCompile("^[a-z0-9-]+$").MatchString(alias) {
return fmt.Errorf("channel alias should only contains lowercase letters, numbers, and hyphens")
}
return nil
}
func GetChannel(id uint) (models.Channel, error) {
var channel models.Channel
tx := database.C.Where(models.Channel{
BaseModel: models.BaseModel{ID: id},
}).Preload("Account").Preload("Realm")
tx = PreloadDirectChannelMembers(tx)
if err := tx.First(&channel).Error; err != nil {
return channel, err
}
return channel, nil
}
func GetChannelWithAlias(alias string, realmId ...uint) (models.Channel, error) {
var channel models.Channel
tx := database.C.Where(models.Channel{Alias: alias}).Preload("Account").Preload("Realm")
if len(realmId) > 0 {
tx = tx.Where("realm_id = ?", realmId)
} else {
tx = tx.Where("realm_id IS NULL")
}
tx = PreloadDirectChannelMembers(tx)
if err := tx.First(&channel).Error; err != nil {
return channel, err
}
return channel, nil
}
func GetAvailableChannelWithAlias(alias string, user models.Account, realmId ...uint) (models.Channel, models.ChannelMember, error) {
var err error
var member models.ChannelMember
var channel models.Channel
if channel, err = GetChannelWithAlias(alias, realmId...); err != nil {
return channel, member, err
}
if err := database.C.Where(models.ChannelMember{
AccountID: user.ID,
ChannelID: channel.ID,
}).First(&member).Error; err != nil {
return channel, member, fmt.Errorf("channel principal not found: %v", err.Error())
}
return channel, member, nil
}
func GetAvailableChannel(id uint, user models.Account) (models.Channel, models.ChannelMember, error) {
var err error
var member models.ChannelMember
var channel models.Channel
if channel, err = GetChannel(id); err != nil {
return channel, member, err
}
tx := database.C.Where(models.ChannelMember{
AccountID: user.ID,
ChannelID: channel.ID,
})
if err := tx.First(&member).Error; err != nil {
return channel, member, fmt.Errorf("channel principal not found: %v", err.Error())
}
return channel, member, nil
}
func PreloadDirectChannelMembers(tx *gorm.DB) *gorm.DB {
return tx.Preload("Members", func(db *gorm.DB) *gorm.DB {
return db.Joins(
fmt.Sprintf(
"JOIN %schannels AS c ON c.type = ?",
viper.GetString("database.prefix"),
),
models.ChannelTypeDirect,
)
}).Preload("Members.Account")
}
func ListChannel(realmId ...uint) ([]models.Channel, error) {
var channels []models.Channel
tx := database.C.Preload("Account").Preload("Realm")
if len(realmId) > 0 {
tx = tx.Where("realm_id = ?", realmId)
}
tx = PreloadDirectChannelMembers(tx)
if err := tx.Find(&channels).Error; err != nil {
return channels, err
}
return channels, nil
}
func ListChannelWithUser(user models.Account, realmId ...uint) ([]models.Channel, error) {
var channels []models.Channel
tx := database.C.Where(&models.Channel{AccountID: user.ID}).Preload("Realm")
if len(realmId) > 0 {
tx = tx.Where("realm_id = ?", realmId)
}
tx = PreloadDirectChannelMembers(tx)
if err := tx.Find(&channels).Error; err != nil {
return channels, err
}
return channels, nil
}
func ListAvailableChannel(user models.Account, realmId ...uint) ([]models.Channel, error) {
var channels []models.Channel
var members []models.ChannelMember
if err := database.C.Where(&models.ChannelMember{
AccountID: user.ID,
}).Find(&members).Error; err != nil {
return channels, err
}
idx := lo.Map(members, func(item models.ChannelMember, index int) uint {
return item.ChannelID
})
tx := database.C.Preload("Realm").Where("id IN ?", idx)
if len(realmId) > 0 {
tx = tx.Where("realm_id = ?", realmId)
}
tx = PreloadDirectChannelMembers(tx)
if err := tx.Find(&channels).Error; err != nil {
return channels, err
}
return channels, nil
}
func NewChannel(channel models.Channel) (models.Channel, error) {
err := database.C.Save(&channel).Error
return channel, err
}
func EditChannel(channel models.Channel, alias, name, description string, isEncrypted bool) (models.Channel, error) {
channel.Alias = alias
channel.Name = name
channel.Description = description
if !channel.IsEncrypted {
channel.IsEncrypted = isEncrypted
}
err := database.C.Save(&channel).Error
return channel, err
}
func DeleteChannel(channel models.Channel) error {
return database.C.Delete(&channel).Error
}

View File

@ -0,0 +1,25 @@
package services
import (
"time"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/database"
"github.com/rs/zerolog/log"
)
func DoAutoDatabaseCleanup() {
deadline := time.Now().Add(60 * time.Minute)
log.Debug().Time("deadline", deadline).Msg("Now cleaning up entire database...")
// Deal soft-deletion
var count int64
for _, model := range database.DatabaseAutoActionRange {
tx := database.C.Unscoped().Delete(model, "deleted_at >= ?", deadline)
if tx.Error != nil {
log.Error().Err(tx.Error).Msg("An error occurred when running database cleanup...")
}
count += tx.RowsAffected
}
log.Debug().Int64("affected", count).Msg("Clean up entire database accomplished.")
}

View File

@ -0,0 +1,47 @@
package services
import (
"sync"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
"github.com/gofiber/contrib/websocket"
)
var (
wsMutex sync.Mutex
wsConn = make(map[uint]map[*websocket.Conn]bool)
)
func ClientRegister(user models.Account, conn *websocket.Conn) {
wsMutex.Lock()
if wsConn[user.ID] == nil {
wsConn[user.ID] = make(map[*websocket.Conn]bool)
}
wsConn[user.ID][conn] = true
wsMutex.Unlock()
}
func ClientUnregister(user models.Account, conn *websocket.Conn) {
wsMutex.Lock()
if wsConn[user.ID] == nil {
wsConn[user.ID] = make(map[*websocket.Conn]bool)
}
delete(wsConn[user.ID], conn)
wsMutex.Unlock()
}
func PushCommand(userId uint, task models.UnifiedCommand) {
for conn := range wsConn[userId] {
_ = conn.WriteMessage(1, task.Marshal())
}
}
func DealCommand(task models.UnifiedCommand, user models.Account) *models.UnifiedCommand {
switch task.Action {
default:
return &models.UnifiedCommand{
Action: "error",
Message: "command not found",
}
}
}

View File

@ -0,0 +1,12 @@
package services
import "golang.org/x/crypto/bcrypt"
func HashPassword(raw string) string {
data, _ := bcrypt.GenerateFromPassword([]byte(raw), 12)
return string(data)
}
func VerifyPassword(text string, password string) bool {
return bcrypt.CompareHashAndPassword([]byte(password), []byte(text)) == nil
}

View File

@ -0,0 +1,81 @@
package services
import (
"fmt"
"github.com/gofiber/fiber/v2"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/spf13/viper"
)
type PayloadClaims struct {
jwt.RegisteredClaims
Type string `json:"typ"`
}
const (
JwtAccessType = "access"
JwtRefreshType = "refresh"
)
const (
CookieAccessKey = "identity_auth_key"
CookieRefreshKey = "identity_refresh_key"
)
func EncodeJwt(id string, typ, sub string, aud []string, exp time.Time) (string, error) {
tk := jwt.NewWithClaims(jwt.SigningMethodHS512, PayloadClaims{
jwt.RegisteredClaims{
Subject: sub,
Audience: aud,
Issuer: fmt.Sprintf("https://%s", viper.GetString("domain")),
ExpiresAt: jwt.NewNumericDate(exp),
NotBefore: jwt.NewNumericDate(time.Now()),
IssuedAt: jwt.NewNumericDate(time.Now()),
ID: id,
},
typ,
})
return tk.SignedString([]byte(viper.GetString("secret")))
}
func DecodeJwt(str string) (PayloadClaims, error) {
var claims PayloadClaims
tk, err := jwt.ParseWithClaims(str, &claims, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(viper.GetString("secret")), nil
})
if err != nil {
return claims, err
}
if data, ok := tk.Claims.(*PayloadClaims); ok {
return *data, nil
} else {
return claims, fmt.Errorf("unexpected token payload: not payload claims type")
}
}
func SetJwtCookieSet(c *fiber.Ctx, access, refresh string) {
c.Cookie(&fiber.Cookie{
Name: CookieAccessKey,
Value: access,
Domain: viper.GetString("security.cookie_domain"),
SameSite: viper.GetString("security.cookie_samesite"),
Expires: time.Now().Add(60 * time.Minute),
Path: "/",
})
c.Cookie(&fiber.Cookie{
Name: CookieRefreshKey,
Value: refresh,
Domain: viper.GetString("security.cookie_domain"),
SameSite: viper.GetString("security.cookie_samesite"),
Expires: time.Now().Add(24 * 30 * time.Hour),
Path: "/",
})
}

View File

@ -0,0 +1,18 @@
package services
import (
lksdk "github.com/livekit/server-sdk-go"
"github.com/spf13/viper"
)
var Lk *lksdk.RoomServiceClient
func SetupLiveKit() {
host := "https://" + viper.GetString("calling.endpoint")
Lk = lksdk.NewRoomServiceClient(
host,
viper.GetString("calling.api_key"),
viper.GetString("calling.api_secret"),
)
}

View File

@ -0,0 +1,51 @@
package services
import (
"crypto/tls"
"fmt"
"net/smtp"
"net/textproto"
"github.com/jordan-wright/email"
"github.com/spf13/viper"
)
func SendMail(target string, subject string, content string) error {
mail := &email.Email{
To: []string{target},
From: viper.GetString("mailer.name"),
Subject: subject,
Text: []byte(content),
Headers: textproto.MIMEHeader{},
}
return mail.SendWithTLS(
fmt.Sprintf("%s:%d", viper.GetString("mailer.smtp_host"), viper.GetInt("mailer.smtp_port")),
smtp.PlainAuth(
"",
viper.GetString("mailer.username"),
viper.GetString("mailer.password"),
viper.GetString("mailer.smtp_host"),
),
&tls.Config{ServerName: viper.GetString("mailer.smtp_host")},
)
}
func SendMailHTML(target string, subject string, content string) error {
mail := &email.Email{
To: []string{target},
From: viper.GetString("mailer.name"),
Subject: subject,
HTML: []byte(content),
Headers: textproto.MIMEHeader{},
}
return mail.SendWithTLS(
fmt.Sprintf("%s:%d", viper.GetString("mailer.smtp_host"), viper.GetInt("mailer.smtp_port")),
smtp.PlainAuth(
"",
viper.GetString("mailer.username"),
viper.GetString("mailer.password"),
viper.GetString("mailer.smtp_host"),
),
&tls.Config{ServerName: viper.GetString("mailer.smtp_host")},
)
}

View File

@ -0,0 +1,181 @@
package services
import (
"fmt"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/database"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
)
func CountMessage(channel models.Channel) int64 {
var count int64
if err := database.C.Where(models.Message{
ChannelID: channel.ID,
}).Model(&models.Message{}).Count(&count).Error; err != nil {
return 0
} else {
return count
}
}
func ListMessage(channel models.Channel, take int, offset int) ([]models.Message, error) {
if take > 100 {
take = 100
}
var messages []models.Message
if err := database.C.
Where(models.Message{
ChannelID: channel.ID,
}).Limit(take).Offset(offset).
Order("created_at DESC").
Preload("ReplyTo").
Preload("ReplyTo.Sender").
Preload("ReplyTo.Sender.Account").
Preload("Sender").
Preload("Sender.Account").
Find(&messages).Error; err != nil {
return messages, err
} else {
return messages, nil
}
}
func GetMessage(channel models.Channel, id uint) (models.Message, error) {
var message models.Message
if err := database.C.
Where(models.Message{
BaseModel: models.BaseModel{ID: id},
ChannelID: channel.ID,
}).
Preload("ReplyTo").
Preload("ReplyTo.Sender").
Preload("ReplyTo.Sender.Account").
Preload("Sender").
Preload("Sender.Account").
First(&message).Error; err != nil {
return message, err
} else {
return message, nil
}
}
func GetMessageWithPrincipal(channel models.Channel, member models.ChannelMember, id uint) (models.Message, error) {
var message models.Message
if err := database.C.Where(models.Message{
BaseModel: models.BaseModel{ID: id},
ChannelID: channel.ID,
SenderID: member.ID,
}).First(&message).Error; err != nil {
return message, err
} else {
return message, nil
}
}
func NewMessage(message models.Message) (models.Message, error) {
var members []models.ChannelMember
if err := database.C.Save(&message).Error; err != nil {
return message, err
} else if err = database.C.Where(models.ChannelMember{
ChannelID: message.ChannelID,
}).Preload("Account").Find(&members).Error; err == nil {
channel := message.Channel
message, _ = GetMessage(message.Channel, message.ID)
for _, member := range members {
PushCommand(member.AccountID, models.UnifiedCommand{
Action: "messages.new",
Payload: message,
})
if member.ID != message.SenderID {
switch member.Notify {
case models.NotifyLevelNone:
continue
case models.NotifyLevelMentioned:
if message.ReplyTo != nil && member.ID == message.ReplyTo.SenderID {
break
}
if val, ok := message.Content["mentioned_users"]; ok {
if usernames, ok := val.([]string); ok {
if lo.Contains(usernames, member.Account.Name) {
break
}
}
}
default:
break
}
var displayText string
if message.Content["algorithm"] == "plain" {
displayText, _ = message.Content["value"].(string)
} else {
displayText = "*encrypted message*"
}
if len(displayText) == 0 {
displayText = fmt.Sprintf("%d attachment(s)", len(message.Attachments))
}
err = NotifyAccountMessager(member.Account,
"incomingMessage",
fmt.Sprintf("%s in #%s", message.Sender.Account.Nick, channel.Alias),
fmt.Sprintf("%s", displayText),
true,
false,
)
if err != nil {
log.Warn().Err(err).Msg("An error occurred when trying notify user.")
}
}
}
}
return message, nil
}
func EditMessage(message models.Message) (models.Message, error) {
var members []models.ChannelMember
if err := database.C.Save(&message).Error; err != nil {
return message, err
} else if err = database.C.Where(models.ChannelMember{
ChannelID: message.ChannelID,
}).Find(&members).Error; err == nil {
message, _ = GetMessage(models.Channel{
BaseModel: models.BaseModel{ID: message.Channel.ID},
}, message.ID)
for _, member := range members {
PushCommand(member.AccountID, models.UnifiedCommand{
Action: "messages.update",
Payload: message,
})
}
}
return message, nil
}
func DeleteMessage(message models.Message) (models.Message, error) {
prev, _ := GetMessage(models.Channel{
BaseModel: models.BaseModel{ID: message.Channel.ID},
}, message.ID)
var members []models.ChannelMember
if err := database.C.Delete(&message).Error; err != nil {
return message, err
} else if err = database.C.Where(models.ChannelMember{
ChannelID: message.ChannelID,
}).Find(&members).Error; err == nil {
for _, member := range members {
PushCommand(member.AccountID, models.UnifiedCommand{
Action: "messages.burnt",
Payload: prev,
})
}
}
return message, nil
}

View File

@ -0,0 +1,112 @@
package services
import (
"context"
"errors"
"fmt"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/database"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/gap"
"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/proto"
"github.com/samber/lo"
"gorm.io/gorm"
"reflect"
)
func GetRealm(id uint) (models.Realm, error) {
var realm models.Realm
pc, err := gap.H.DiscoverServiceGRPC("Hydrogen.Passport")
if err != nil {
return realm, err
}
response, err := proto.NewRealmsClient(pc).GetRealm(context.Background(), &proto.RealmLookupRequest{
Id: lo.ToPtr(uint64(id)),
})
if err != nil {
return realm, err
}
return LinkRealm(response)
}
func GetRealmWithAlias(alias string) (models.Realm, error) {
var realm models.Realm
pc, err := gap.H.DiscoverServiceGRPC("Hydrogen.Passport")
if err != nil {
return realm, err
}
response, err := proto.NewRealmsClient(pc).GetRealm(context.Background(), &proto.RealmLookupRequest{
Alias: &alias,
})
if err != nil {
return realm, err
}
return LinkRealm(response)
}
func GetRealmMember(realmId uint, userId uint) (*proto.RealmMemberResponse, error) {
pc, err := gap.H.DiscoverServiceGRPC("Hydrogen.Passport")
if err != nil {
return nil, err
}
response, err := proto.NewRealmsClient(pc).GetRealmMember(context.Background(), &proto.RealmMemberLookupRequest{
RealmId: uint64(realmId),
UserId: lo.ToPtr(uint64(userId)),
})
if err != nil {
return nil, err
} else {
return response, nil
}
}
func ListRealmMember(realmId uint) ([]*proto.RealmMemberResponse, error) {
pc, err := gap.H.DiscoverServiceGRPC("Hydrogen.Passport")
if err != nil {
return nil, err
}
response, err := proto.NewRealmsClient(pc).ListRealmMember(context.Background(), &proto.RealmMemberLookupRequest{
RealmId: uint64(realmId),
})
if err != nil {
return nil, err
} else {
return response.Data, nil
}
}
func LinkRealm(info *proto.RealmResponse) (models.Realm, error) {
var realm models.Realm
if info == nil {
return realm, fmt.Errorf("remote realm info was not found")
}
if err := database.C.Where(&models.Realm{
ExternalID: uint(info.Id),
}).First(&realm).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
realm = models.Realm{
Alias: info.Alias,
Name: info.Name,
Description: info.Description,
IsPublic: info.IsPublic,
IsCommunity: info.IsCommunity,
ExternalID: uint(info.Id),
}
return realm, database.C.Save(&realm).Error
}
return realm, err
}
prev := realm
realm.Alias = info.Alias
realm.Name = info.Name
realm.Description = info.Description
realm.IsPublic = info.IsPublic
realm.IsCommunity = info.IsCommunity
var err error
if !reflect.DeepEqual(prev, realm) {
err = database.C.Save(&realm).Error
}
return realm, err
}