⬆️ Switch to Paperclip

This commit is contained in:
2024-05-26 23:01:20 +08:00
parent f38aab68cd
commit b3b1ec4585
23 changed files with 314 additions and 377 deletions

View File

@ -1,13 +1,14 @@
package main
import (
"git.solsynth.dev/hydrogen/messaging/pkg/external"
"git.solsynth.dev/hydrogen/messaging/pkg/services"
"github.com/robfig/cron/v3"
"os"
"os/signal"
"syscall"
"git.solsynth.dev/hydrogen/messaging/pkg/external"
"git.solsynth.dev/hydrogen/messaging/pkg/services"
"github.com/robfig/cron/v3"
"git.solsynth.dev/hydrogen/messaging/pkg/grpc"
"git.solsynth.dev/hydrogen/messaging/pkg/server"
@ -45,7 +46,10 @@ func main() {
// Connect other services
external.SetupLiveKit()
if err := grpc.ConnectPassport(); err != nil {
log.Fatal().Err(err).Msg("An error occurred when connecting to identity grpc endpoint...")
log.Fatal().Err(err).Msg("An error occurred when connecting to passport...")
}
if err := grpc.ConnectPaperclip(); err != nil {
log.Fatal().Err(err).Msg("An error occurred when connecting to paperclip...")
}
// Server

View File

@ -12,7 +12,6 @@ var DatabaseAutoActionRange = []any{
&models.ChannelMember{},
&models.Call{},
&models.Message{},
&models.Attachment{},
}
func RunMigration(source *gorm.DB) error {

View File

@ -1,6 +1,7 @@
package grpc
import (
pcpb "git.solsynth.dev/hydrogen/paperclip/pkg/grpc/proto"
idpb "git.solsynth.dev/hydrogen/passport/pkg/grpc/proto"
"google.golang.org/grpc/credentials/insecure"
@ -8,13 +9,28 @@ import (
"google.golang.org/grpc"
)
var Realms idpb.RealmsClient
var Friendships idpb.FriendshipsClient
var Notify idpb.NotifyClient
var Auth idpb.AuthClient
var Attachments pcpb.AttachmentsClient
func ConnectPaperclip() error {
addr := viper.GetString("paperclip.grpc_endpoint")
if conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials())); err != nil {
return err
} else {
Attachments = pcpb.NewAttachmentsClient(conn)
}
return nil
}
var (
Realms idpb.RealmsClient
Friendships idpb.FriendshipsClient
Notify idpb.NotifyClient
Auth idpb.AuthClient
)
func ConnectPassport() error {
addr := viper.GetString("identity.grpc_endpoint")
addr := viper.GetString("passport.grpc_endpoint")
if conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials())); err != nil {
return err
} else {

View File

@ -6,14 +6,13 @@ package models
type Account struct {
BaseModel
Name string `json:"name"`
Nick string `json:"nick"`
Avatar string `json:"avatar"`
Banner string `json:"banner"`
Description string `json:"description"`
EmailAddress string `json:"email_address"`
PowerLevel int `json:"power_level"`
Attachments []Attachment `json:"attachments" gorm:"foreignKey:AuthorID"`
Channels []Channel `json:"channels"`
ExternalID uint `json:"external_id"`
Name string `json:"name"`
Nick string `json:"nick"`
Avatar string `json:"avatar"`
Banner string `json:"banner"`
Description string `json:"description"`
EmailAddress string `json:"email_address"`
PowerLevel int `json:"power_level"`
Channels []Channel `json:"channels"`
ExternalID uint `json:"external_id"`
}

View File

@ -1,41 +0,0 @@
package models
import (
"fmt"
"path/filepath"
"github.com/spf13/viper"
)
type AttachmentType = uint8
const (
AttachmentOthers = AttachmentType(iota)
AttachmentPhoto
AttachmentVideo
AttachmentAudio
)
type Attachment struct {
BaseModel
FileID string `json:"file_id"`
Filesize int64 `json:"filesize"`
Filename string `json:"filename"`
Mimetype string `json:"mimetype"`
Hashcode string `json:"hashcode"`
Type AttachmentType `json:"type"`
ExternalUrl string `json:"external_url"`
Author Account `json:"author"`
MessageID *uint `json:"message_id"`
AuthorID uint `json:"author_id"`
}
func (v Attachment) GetStoragePath() string {
basepath := viper.GetString("content")
return filepath.Join(basepath, v.FileID)
}
func (v Attachment) GetAccessPath() string {
return fmt.Sprintf("/api/attachments/o/%s", v.FileID)
}

View File

@ -3,8 +3,8 @@ package models
type ChannelType = uint8
const (
ChannelTypeDirect = ChannelType(iota)
ChannelTypeRealm
ChannelTypeCommon = ChannelType(iota)
ChannelTypeDirect
)
type Channel struct {
@ -36,11 +36,12 @@ const (
type ChannelMember struct {
BaseModel
ChannelID uint `json:"channel_id"`
AccountID uint `json:"account_id"`
Channel Channel `json:"channel"`
Account Account `json:"account"`
Notify NotifyLevel `json:"notify"`
ChannelID uint `json:"channel_id"`
AccountID uint `json:"account_id"`
Channel Channel `json:"channel"`
Account Account `json:"account"`
Notify NotifyLevel `json:"notify"`
PowerLevel int `json:"power_level"`
Calls []Call `json:"calls" gorm:"foreignKey:FounderID"`
Messages []Message `json:"messages" gorm:"foreignKey:SenderID"`

View File

@ -1,15 +1,18 @@
package models
import "gorm.io/datatypes"
type Message struct {
BaseModel
Content []byte `json:"content"`
Type string `json:"type"`
Attachments []Attachment `json:"attachments"`
Channel Channel `json:"channel"`
Sender ChannelMember `json:"sender"`
ReplyID *uint `json:"reply_id"`
ReplyTo *Message `json:"reply_to" gorm:"foreignKey:ReplyID"`
ChannelID uint `json:"channel_id"`
SenderID uint `json:"sender_id"`
Uuid string `json:"uuid"`
Content datatypes.JSONMap `json:"content"`
Type string `json:"type"`
Attachments datatypes.JSONSlice[uint] `json:"attachments"`
Channel Channel `json:"channel"`
Sender ChannelMember `json:"sender"`
ReplyID *uint `json:"reply_id"`
ReplyTo *Message `json:"reply_to" gorm:"foreignKey:ReplyID"`
ChannelID uint `json:"channel_id"`
SenderID uint `json:"sender_id"`
}

View File

@ -1,61 +0,0 @@
package server
import (
"path/filepath"
"git.solsynth.dev/hydrogen/messaging/pkg/models"
"git.solsynth.dev/hydrogen/messaging/pkg/services"
"github.com/gofiber/fiber/v2"
"github.com/spf13/viper"
)
func readAttachment(c *fiber.Ctx) error {
id := c.Params("fileId")
basepath := viper.GetString("content")
return c.SendFile(filepath.Join(basepath, id))
}
func uploadAttachment(c *fiber.Ctx) error {
user := c.Locals("principal").(models.Account)
hashcode := c.FormValue("hashcode")
if len(hashcode) != 64 {
return fiber.NewError(fiber.StatusBadRequest, "please provide a SHA256 hashcode, length should be 64 characters")
}
file, err := c.FormFile("attachment")
if err != nil {
return err
}
attachment, err := services.NewAttachment(user, file, hashcode)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
if err := c.SaveFile(file, attachment.GetStoragePath()); err != nil {
return err
}
return c.JSON(fiber.Map{
"info": attachment,
"url": attachment.GetAccessPath(),
})
}
func deleteAttachment(c *fiber.Ctx) error {
id, _ := c.ParamsInt("id", 0)
user := c.Locals("principal").(models.Account)
attachment, err := services.GetAttachmentByID(uint(id))
if err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
} else if attachment.AuthorID != user.ID {
return fiber.NewError(fiber.StatusNotFound, "record not created by you")
}
if err := services.DeleteAttachment(attachment); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else {
return c.SendStatus(fiber.StatusOK)
}
}

View File

@ -2,6 +2,7 @@ package server
import (
"fmt"
"git.solsynth.dev/hydrogen/messaging/pkg/database"
"git.solsynth.dev/hydrogen/messaging/pkg/models"
"git.solsynth.dev/hydrogen/messaging/pkg/services"
@ -43,10 +44,13 @@ func addChannelMember(c *fiber.Ctx) error {
var channel models.Channel
if err := database.C.Where(&models.Channel{
Alias: alias,
AccountID: user.ID,
Alias: alias,
}).First(&channel).Error; err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
} else if member, err := services.GetChannelMember(user, channel.ID); err != nil {
return fiber.NewError(fiber.StatusForbidden, err.Error())
} else if member.PowerLevel < 50 {
return fiber.NewError(fiber.StatusForbidden, "you must be a moderator of a channel to add member into it")
}
var account models.Account
@ -81,6 +85,10 @@ func removeChannelMember(c *fiber.Ctx) error {
AccountID: user.ID,
}).First(&channel).Error; err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
} else if member, err := services.GetChannelMember(user, channel.ID); err != nil {
return fiber.NewError(fiber.StatusForbidden, err.Error())
} else if member.PowerLevel < 50 {
return fiber.NewError(fiber.StatusForbidden, "you must be a moderator of a channel to remove member into it")
}
var account models.Account

View File

@ -2,6 +2,7 @@ package server
import (
"fmt"
"git.solsynth.dev/hydrogen/messaging/pkg/database"
"git.solsynth.dev/hydrogen/messaging/pkg/models"
"git.solsynth.dev/hydrogen/messaging/pkg/services"
@ -111,22 +112,31 @@ func createChannel(c *fiber.Ctx) error {
var realm *models.Realm
if val, ok := c.Locals("realm").(models.Realm); ok {
if info, err := services.GetRealmMember(val.ExternalID, user.ExternalID); err != nil {
return fmt.Errorf("you must be a part of that realm then can create channel related to it")
return fiber.NewError(fiber.StatusForbidden, "you must be a part of that realm then can create channel related to it")
} else if info.GetPowerLevel() < 50 {
return fmt.Errorf("you must be a moderator of that realm then can create channel related to it")
return fiber.NewError(fiber.StatusForbidden, "you must be a moderator of that realm then can create channel related to it")
} else {
realm = &val
}
}
var err error
var channel models.Channel
if realm != nil {
channel, err = services.NewChannel(user, data.Alias, data.Name, data.Description, data.IsEncrypted, realm.ID)
} else {
channel, err = services.NewChannel(user, data.Alias, data.Name, data.Description, data.IsEncrypted)
channel := models.Channel{
Alias: data.Alias,
Name: data.Name,
Description: data.Description,
IsEncrypted: data.IsEncrypted,
AccountID: user.ID,
Type: models.ChannelTypeCommon,
Members: []models.ChannelMember{
{AccountID: user.ID, PowerLevel: 100},
},
}
if realm != nil {
channel.RealmID = &realm.ID
}
channel, err := services.NewChannel(channel)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
@ -153,14 +163,14 @@ func editChannel(c *fiber.Ctx) error {
if val, ok := c.Locals("realm").(models.Realm); ok {
if info, err := services.GetRealmMember(val.ExternalID, user.ExternalID); err != nil {
return fmt.Errorf("you must be a part of that realm then can edit channel related to it")
return fiber.NewError(fiber.StatusForbidden, "you must be a part of that realm then can edit channel related to it")
} else if info.GetPowerLevel() < 50 {
return fmt.Errorf("you must be a moderator of that realm then can edit channel related to it")
return fiber.NewError(fiber.StatusForbidden, "you must be a moderator of that realm then can edit channel related to it")
} else {
tx = tx.Where("realm_id = ?", val.ID)
}
} else {
tx = tx.Where("account_id = ? AND realm_id IS NULL", user.ID)
tx = tx.Where("realm_id IS NULL")
}
var channel models.Channel
@ -168,6 +178,14 @@ func editChannel(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
if channel.RealmID != nil {
if member, err := services.GetChannelMember(user, channel.ID); err != nil {
return fiber.NewError(fiber.StatusForbidden, "you must be a part of this channel to edit it")
} else if member.PowerLevel < 100 {
return fiber.NewError(fiber.StatusForbidden, "you must be channel admin to edit it")
}
}
channel, err := services.EditChannel(channel, data.Alias, data.Name, data.Description, data.IsEncrypted)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())

View File

@ -0,0 +1,68 @@
package server
import (
"git.solsynth.dev/hydrogen/messaging/pkg/database"
"git.solsynth.dev/hydrogen/messaging/pkg/models"
"git.solsynth.dev/hydrogen/messaging/pkg/services"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
)
func createDirectChannel(c *fiber.Ctx) error {
user := c.Locals("principal").(models.Account)
var data struct {
Alias string `json:"alias" validate:"required,lowercase,min=4,max=32"`
Name string `json:"name" validate:"required"`
Description string `json:"description"`
Members []uint `json:"members"`
IsEncrypted bool `json:"is_encrypted"`
}
if err := BindAndValidate(c, &data); err != nil {
return err
} else if err = services.GetChannelAliasAvailability(data.Alias); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
var realm *models.Realm
if val, ok := c.Locals("realm").(models.Realm); ok {
if info, err := services.GetRealmMember(val.ExternalID, user.ExternalID); err != nil {
return fiber.NewError(fiber.StatusForbidden, "you must be a part of that realm then can create channel related to it")
} else if info.GetPowerLevel() < 50 {
return fiber.NewError(fiber.StatusForbidden, "you must be a moderator of that realm then can create channel related to it")
} else {
realm = &val
}
}
var members []models.Account
if err := database.C.Where("id IN ?", data.Members).Find(&members).Error; err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
channel := models.Channel{
Alias: data.Alias,
Name: data.Name,
Description: data.Description,
IsEncrypted: data.IsEncrypted,
AccountID: user.ID,
Type: models.ChannelTypeDirect,
Members: append([]models.ChannelMember{
{AccountID: user.ID, PowerLevel: 100},
}, lo.Map(members, func(item models.Account, idx int) models.ChannelMember {
return models.ChannelMember{AccountID: item.ID, PowerLevel: 100}
})...),
}
if realm != nil {
channel.RealmID = &realm.ID
}
channel, err := services.NewChannel(channel)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return c.JSON(channel)
}

View File

@ -1,8 +1,8 @@
package server
import (
"encoding/json"
"fmt"
"git.solsynth.dev/hydrogen/messaging/pkg/database"
"git.solsynth.dev/hydrogen/messaging/pkg/models"
"git.solsynth.dev/hydrogen/messaging/pkg/services"
@ -45,16 +45,25 @@ func newMessage(c *fiber.Ctx) error {
alias := c.Params("channel")
var data struct {
Type string `json:"type" validate:"required"`
Content map[string]any `json:"content"`
Attachments []models.Attachment `json:"attachments"`
ReplyTo *uint `json:"reply_to"`
Uuid string `json:"uuid" validate:"required"`
Type string `json:"type" validate:"required"`
Content map[string]any `json:"content"`
Attachments []uint `json:"attachments"`
ReplyTo *uint `json:"reply_to"`
}
if err := BindAndValidate(c, &data); err != nil {
return err
} else if len(data.Attachments) == 0 && len(data.Content) == 0 {
return fmt.Errorf("you must write or upload some content in a single message")
return fiber.NewError(fiber.StatusBadRequest, "you must write or upload some content in a single message")
} else if len(data.Uuid) < 36 {
return fiber.NewError(fiber.StatusBadRequest, "message uuid was not valid")
}
for _, attachment := range data.Attachments {
if !services.CheckAttachmentByIDExists(attachment, "m.attachment") {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("attachment %d not found", attachment))
}
}
var err error
@ -72,13 +81,9 @@ func newMessage(c *fiber.Ctx) error {
}
}
rawContent, err := json.Marshal(data.Content)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("invalid message content, unable to encode: %v", err))
}
message := models.Message{
Content: rawContent,
Uuid: data.Uuid,
Content: data.Content,
Sender: member,
Channel: channel,
ChannelID: channel.ID,
@ -110,16 +115,22 @@ func editMessage(c *fiber.Ctx) error {
messageId, _ := c.ParamsInt("messageId", 0)
var data struct {
Type string `json:"type" validate:"required"`
Content map[string]any `json:"content"`
Attachments []models.Attachment `json:"attachments"`
ReplyTo *uint `json:"reply_to"`
Type string `json:"type" validate:"required"`
Content map[string]any `json:"content"`
Attachments []uint `json:"attachments"`
ReplyTo *uint `json:"reply_to"`
}
if err := BindAndValidate(c, &data); err != nil {
return err
}
for _, attachment := range data.Attachments {
if !services.CheckAttachmentByIDExists(attachment, "m.attachment") {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("attachment %d not found", attachment))
}
}
var err error
var channel models.Channel
var member models.ChannelMember
@ -140,13 +151,8 @@ func editMessage(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
rawContent, err := json.Marshal(data.Content)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("invalid message content, unable to encode: %v", err))
}
message.Attachments = data.Attachments
message.Content = rawContent
message.Content = data.Content
message.Type = data.Type
message, err = services.EditMessage(message)

View File

@ -5,6 +5,7 @@ import (
"git.solsynth.dev/hydrogen/messaging/pkg/services"
"github.com/gofiber/contrib/websocket"
jsoniter "github.com/json-iterator/go"
"github.com/rs/zerolog/log"
)
func messageGateway(c *websocket.Conn) {
@ -12,6 +13,7 @@ func messageGateway(c *websocket.Conn) {
// Push connection
services.ClientRegister(user, c)
log.Debug().Uint("user", user.ID).Msg("New websocket connection established...")
// Event loop
var task models.UnifiedCommand
@ -42,4 +44,5 @@ func messageGateway(c *websocket.Conn) {
// Pop connection
services.ClientUnregister(user, c)
log.Debug().Uint("user", user.ID).Msg("A websocket connection disconnected...")
}

View File

@ -3,14 +3,12 @@ package server
import (
"net/http"
"strings"
"time"
"git.solsynth.dev/hydrogen/messaging/pkg"
"github.com/gofiber/contrib/websocket"
"github.com/gofiber/fiber/v2/middleware/favicon"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cache"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/idempotency"
"github.com/gofiber/fiber/v2/middleware/logger"
@ -68,22 +66,16 @@ func NewServer() {
api.Get("/users/me", authMiddleware, getUserinfo)
api.Get("/users/:accountId", getOthersInfo)
api.Get("/attachments/o/:fileId", cache.New(cache.Config{
Expiration: 365 * 24 * time.Hour,
CacheControl: true,
}), readAttachment)
api.Post("/attachments", authMiddleware, uploadAttachment)
api.Delete("/attachments/:id", authMiddleware, deleteAttachment)
channels := api.Group("/channels/:realm").Use(realmMiddleware).Name("Channels API")
{
channels.Get("/", listChannel)
channels.Get("/:channel", getChannel)
channels.Get("/:channel/availability", authMiddleware, getChannelAvailability)
channels.Get("/me", authMiddleware, listOwnedChannel)
channels.Get("/me/available", authMiddleware, listAvailableChannel)
channels.Get("/:channel", getChannel)
channels.Get("/:channel/availability", authMiddleware, getChannelAvailability)
channels.Post("/", authMiddleware, createChannel)
channels.Post("/dm", authMiddleware, createDirectChannel)
channels.Put("/:channelId", authMiddleware, editChannel)
channels.Delete("/:channelId", authMiddleware, deleteChannel)

View File

@ -1,127 +1,30 @@
package services
import (
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strings"
"context"
"git.solsynth.dev/hydrogen/messaging/pkg/database"
"git.solsynth.dev/hydrogen/messaging/pkg/models"
"github.com/google/uuid"
"github.com/spf13/viper"
"git.solsynth.dev/hydrogen/messaging/pkg/grpc"
pcpb "git.solsynth.dev/hydrogen/paperclip/pkg/grpc/proto"
"github.com/samber/lo"
)
func GetAttachmentByID(id uint) (models.Attachment, error) {
var attachment models.Attachment
if err := database.C.Where(models.Attachment{
BaseModel: models.BaseModel{ID: id},
}).First(&attachment).Error; err != nil {
return attachment, err
}
return attachment, nil
func GetAttachmentByID(id uint) (*pcpb.Attachment, error) {
return grpc.Attachments.GetAttachment(context.Background(), &pcpb.AttachmentLookupRequest{
Id: lo.ToPtr(uint64(id)),
})
}
func GetAttachmentByUUID(fileId string) (models.Attachment, error) {
var attachment models.Attachment
if err := database.C.Where(models.Attachment{
FileID: fileId,
}).First(&attachment).Error; err != nil {
return attachment, err
}
return attachment, nil
func GetAttachmentByUUID(uuid string) (*pcpb.Attachment, error) {
return grpc.Attachments.GetAttachment(context.Background(), &pcpb.AttachmentLookupRequest{
Uuid: &uuid,
})
}
func GetAttachmentByHashcode(hashcode string) (models.Attachment, error) {
var attachment models.Attachment
if err := database.C.Where(models.Attachment{
Hashcode: hashcode,
}).First(&attachment).Error; err != nil {
return attachment, err
}
return attachment, nil
}
func NewAttachment(user models.Account, header *multipart.FileHeader, hashcode string) (models.Attachment, error) {
var attachment models.Attachment
existsAttachment, err := GetAttachmentByHashcode(hashcode)
if err != nil {
// Upload the new file
attachment = models.Attachment{
FileID: uuid.NewString(),
Filesize: header.Size,
Filename: header.Filename,
Hashcode: hashcode,
Mimetype: "unknown/unknown",
Type: models.AttachmentOthers,
AuthorID: user.ID,
}
// Open file
file, err := header.Open()
if err != nil {
return attachment, err
}
defer file.Close()
// Detect mimetype
fileHeader := make([]byte, 512)
_, err = file.Read(fileHeader)
if err != nil {
return attachment, err
}
attachment.Mimetype = http.DetectContentType(fileHeader)
switch strings.Split(attachment.Mimetype, "/")[0] {
case "image":
attachment.Type = models.AttachmentPhoto
case "video":
attachment.Type = models.AttachmentVideo
case "audio":
attachment.Type = models.AttachmentAudio
default:
attachment.Type = models.AttachmentOthers
}
} else {
// Instant upload, build link with the exists file
attachment = models.Attachment{
FileID: existsAttachment.FileID,
Filesize: header.Size,
Filename: header.Filename,
Hashcode: hashcode,
Mimetype: existsAttachment.Mimetype,
Type: existsAttachment.Type,
AuthorID: user.ID,
}
}
// Save into database
err = database.C.Save(&attachment).Error
return attachment, err
}
func DeleteAttachment(item models.Attachment) error {
var dupeCount int64
if err := database.C.
Where(&models.Attachment{Hashcode: item.Hashcode}).
Model(&models.Attachment{}).
Count(&dupeCount).Error; err != nil {
dupeCount = -1
}
if err := database.C.Delete(&item).Error; err != nil {
return err
}
if dupeCount != -1 && dupeCount <= 1 {
// Safe for deletion the physics file
basepath := viper.GetString("content")
fullpath := filepath.Join(basepath, item.FileID)
os.Remove(fullpath)
}
return nil
func CheckAttachmentByIDExists(id uint, usage string) bool {
_, err := grpc.Attachments.CheckAttachmentExists(context.Background(), &pcpb.AttachmentLookupRequest{
Id: lo.ToPtr(uint64(id)),
Usage: &usage,
})
return err == nil
}

View File

@ -2,6 +2,7 @@ package services
import (
"fmt"
"git.solsynth.dev/hydrogen/messaging/pkg/database"
"git.solsynth.dev/hydrogen/messaging/pkg/models"
)
@ -19,6 +20,18 @@ func ListChannelMember(channelId uint) ([]models.ChannelMember, error) {
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")

View File

@ -80,11 +80,9 @@ func GetAvailableChannel(id uint, user models.Account) (models.Channel, models.C
func ListChannel(realmId ...uint) ([]models.Channel, error) {
var channels []models.Channel
tx := database.C.Preload("Account")
tx := database.C.Preload("Account").Preload("Realm")
if len(realmId) > 0 {
tx = tx.Where("realm_id = ?", realmId)
} else {
tx = tx.Where("realm_id IS NULL")
}
if err := tx.Find(&channels).Error; err != nil {
return channels, err
@ -95,7 +93,7 @@ func ListChannel(realmId ...uint) ([]models.Channel, error) {
func ListChannelWithUser(user models.Account, realmId ...uint) ([]models.Channel, error) {
var channels []models.Channel
tx := database.C.Where(&models.Channel{AccountID: user.ID})
tx := database.C.Where(&models.Channel{AccountID: user.ID}).Preload("Realm")
if len(realmId) > 0 {
tx = tx.Where("realm_id = ?", realmId)
} else {
@ -121,7 +119,7 @@ func ListAvailableChannel(user models.Account, realmId ...uint) ([]models.Channe
return item.ChannelID
})
tx := database.C.Where("id IN ?", idx)
tx := database.C.Preload("Realm").Where("id IN ?", idx)
if len(realmId) > 0 {
tx = tx.Where("realm_id = ?", realmId)
} else {
@ -134,23 +132,8 @@ func ListAvailableChannel(user models.Account, realmId ...uint) ([]models.Channe
return channels, nil
}
func NewChannel(user models.Account, alias, name, description string, isEncrypted bool, realmId ...uint) (models.Channel, error) {
channel := models.Channel{
Alias: alias,
Name: name,
Description: description,
IsEncrypted: isEncrypted,
AccountID: user.ID,
Members: []models.ChannelMember{
{AccountID: user.ID},
},
}
if len(realmId) > 0 {
channel.RealmID = &realmId[0]
}
func NewChannel(channel models.Channel) (models.Channel, error) {
err := database.C.Save(&channel).Error
return channel, err
}

View File

@ -1,10 +1,10 @@
package services
import (
"git.solsynth.dev/hydrogen/messaging/pkg/database"
"git.solsynth.dev/hydrogen/messaging/pkg/models"
"github.com/rs/zerolog/log"
"time"
"git.solsynth.dev/hydrogen/messaging/pkg/database"
"github.com/rs/zerolog/log"
)
func DoAutoDatabaseCleanup() {
@ -16,18 +16,10 @@ func DoAutoDatabaseCleanup() {
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 auth context cleanup...")
log.Error().Err(tx.Error).Msg("An error occurred when running database cleanup...")
}
count += tx.RowsAffected
}
// Clean up outdated chat history
tx := database.C.Unscoped().Delete(&models.Message{}, "created_at < ?", time.Now().Add(30*24*time.Hour))
if tx.Error != nil {
log.Error().Err(tx.Error).Msg("An error occurred when running auth context cleanup...")
} else {
count += tx.RowsAffected
}
log.Debug().Int64("affected", count).Msg("Clean up entire database accomplished.")
}

View File

@ -1,24 +1,33 @@
package services
import (
"sync"
"git.solsynth.dev/hydrogen/messaging/pkg/models"
"github.com/gofiber/contrib/websocket"
)
var wsConn = make(map[uint]map[*websocket.Conn]bool)
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) {

View File

@ -2,11 +2,11 @@ package services
import (
"fmt"
"git.solsynth.dev/hydrogen/messaging/pkg/database"
"git.solsynth.dev/hydrogen/messaging/pkg/models"
"github.com/gofiber/fiber/v2"
jsoniter "github.com/json-iterator/go"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
)
func CountMessage(channel models.Channel) int64 {
@ -31,7 +31,6 @@ func ListMessage(channel models.Channel, take int, offset int) ([]models.Message
ChannelID: channel.ID,
}).Limit(take).Offset(offset).
Order("created_at DESC").
Preload("Attachments").
Preload("ReplyTo").
Preload("ReplyTo.Sender").
Preload("ReplyTo.Sender.Account").
@ -54,7 +53,6 @@ func GetMessage(channel models.Channel, id uint) (models.Message, error) {
Preload("ReplyTo").
Preload("ReplyTo.Sender").
Preload("ReplyTo.Sender.Account").
Preload("Attachments").
Preload("Sender").
Preload("Sender.Account").
First(&message).Error; err != nil {
@ -78,9 +76,6 @@ func GetMessageWithPrincipal(channel models.Channel, member models.ChannelMember
}
func NewMessage(message models.Message) (models.Message, error) {
var decodedContent fiber.Map
_ = jsoniter.Unmarshal(message.Content, &decodedContent)
var members []models.ChannelMember
if err := database.C.Save(&message).Error; err != nil {
return message, err
@ -91,20 +86,39 @@ func NewMessage(message models.Message) (models.Message, error) {
message, _ = GetMessage(message.Channel, message.ID)
for _, member := range members {
if member.ID != message.Sender.ID {
// TODO Check the mentioned status
if member.Notify == models.NotifyLevelAll {
displayText := "*encrypted message*"
if decodedContent["algorithm"] == "plain" {
displayText, _ = decodedContent["value"].(string)
}
err = NotifyAccount(member.Account,
fmt.Sprintf("New Message #%s", channel.Alias),
fmt.Sprintf("%s: %s", message.Sender.Account.Name, displayText),
true,
)
if err != nil {
log.Warn().Err(err).Msg("An error occurred when trying notify user.")
switch member.Notify {
case models.NotifyLevelNone:
continue
case models.NotifyLevelMentioned:
if val, ok := message.Content["metioned_users"]; ok {
if usernames, ok := val.([]string); ok {
if lo.Contains(usernames, member.Account.Name) {
break
}
}
}
continue
}
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 = NotifyAccount(member.Account,
fmt.Sprintf("New Message #%s", channel.Alias),
fmt.Sprintf("%s: %s", message.Sender.Account.Name, displayText),
true,
)
if err != nil {
log.Warn().Err(err).Msg("An error occurred when trying notify user.")
}
}
PushCommand(member.AccountID, models.UnifiedCommand{