✨ 基于事件的消息构成 #1
@@ -11,7 +11,7 @@ var DatabaseAutoActionRange = []any{
 | 
				
			|||||||
	&models.Channel{},
 | 
						&models.Channel{},
 | 
				
			||||||
	&models.ChannelMember{},
 | 
						&models.ChannelMember{},
 | 
				
			||||||
	&models.Call{},
 | 
						&models.Call{},
 | 
				
			||||||
	&models.Message{},
 | 
						&models.Event{},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func RunMigration(source *gorm.DB) error {
 | 
					func RunMigration(source *gorm.DB) error {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
package models
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Account profiles basically fetched from Hydrogen.Identity
 | 
					// Account profiles basically fetched from Hydrogen.Identity
 | 
				
			||||||
// But cache at here for better usage
 | 
					// But cached at here for better usage
 | 
				
			||||||
// At the same time this model can make relations between local models
 | 
					// At the same time, this model can make relations between local models
 | 
				
			||||||
type Account struct {
 | 
					type Account struct {
 | 
				
			||||||
	BaseModel
 | 
						BaseModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,7 @@ type Channel struct {
 | 
				
			|||||||
	Name        string          `json:"name"`
 | 
						Name        string          `json:"name"`
 | 
				
			||||||
	Description string          `json:"description"`
 | 
						Description string          `json:"description"`
 | 
				
			||||||
	Members     []ChannelMember `json:"members"`
 | 
						Members     []ChannelMember `json:"members"`
 | 
				
			||||||
	Messages    []Message       `json:"messages"`
 | 
						Messages    []Event         `json:"messages"`
 | 
				
			||||||
	Calls       []Call          `json:"calls"`
 | 
						Calls       []Call          `json:"calls"`
 | 
				
			||||||
	Type        ChannelType     `json:"type"`
 | 
						Type        ChannelType     `json:"type"`
 | 
				
			||||||
	Account     Account         `json:"account"`
 | 
						Account     Account         `json:"account"`
 | 
				
			||||||
@@ -44,6 +44,6 @@ type ChannelMember struct {
 | 
				
			|||||||
	Notify     NotifyLevel `json:"notify"`
 | 
						Notify     NotifyLevel `json:"notify"`
 | 
				
			||||||
	PowerLevel int         `json:"power_level"`
 | 
						PowerLevel int         `json:"power_level"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Calls    []Call    `json:"calls" gorm:"foreignKey:FounderID"`
 | 
						Calls  []Call  `json:"calls" gorm:"foreignKey:FounderID"`
 | 
				
			||||||
	Messages []Message `json:"messages" gorm:"foreignKey:SenderID"`
 | 
						Events []Event `json:"events" gorm:"foreignKey:SenderID"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										33
									
								
								pkg/internal/models/events.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								pkg/internal/models/events.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "gorm.io/datatypes"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						EventMessageNew    = "messages.new"
 | 
				
			||||||
 | 
						EventMessageEdit   = "messages.edit"
 | 
				
			||||||
 | 
						EventMessageDelete = "messages.delete"
 | 
				
			||||||
 | 
						EventSystemChanges = "system.changes"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Event struct {
 | 
				
			||||||
 | 
						BaseModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Uuid      string            `json:"uuid"`
 | 
				
			||||||
 | 
						Body      datatypes.JSONMap `json:"body"`
 | 
				
			||||||
 | 
						Type      string            `json:"type"`
 | 
				
			||||||
 | 
						Channel   Channel           `json:"channel"`
 | 
				
			||||||
 | 
						Sender    ChannelMember     `json:"sender"`
 | 
				
			||||||
 | 
						ChannelID uint              `json:"channel_id"`
 | 
				
			||||||
 | 
						SenderID  uint              `json:"sender_id"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Event Payloads
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type EventMessageBody struct {
 | 
				
			||||||
 | 
						Text         string `json:"text,omitempty"`
 | 
				
			||||||
 | 
						Algorithm    string `json:"algorithm,omitempty"`
 | 
				
			||||||
 | 
						Attachments  []uint `json:"attachments,omitempty"`
 | 
				
			||||||
 | 
						QuoteEvent   uint   `json:"quote_event,omitempty"`
 | 
				
			||||||
 | 
						RelatedEvent uint   `json:"related_event,omitempty"`
 | 
				
			||||||
 | 
						RelatedUsers []uint `json:"related_users,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,22 +0,0 @@
 | 
				
			|||||||
package models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "gorm.io/datatypes"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const (
 | 
					 | 
				
			||||||
	MessageTextType = "m.text"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Message struct {
 | 
					 | 
				
			||||||
	BaseModel
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	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"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -6,6 +6,7 @@ import (
 | 
				
			|||||||
	"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
 | 
						"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
 | 
				
			||||||
	"git.solsynth.dev/hydrogen/messaging/pkg/internal/services"
 | 
						"git.solsynth.dev/hydrogen/messaging/pkg/internal/services"
 | 
				
			||||||
	"github.com/gofiber/fiber/v2"
 | 
						"github.com/gofiber/fiber/v2"
 | 
				
			||||||
 | 
						"github.com/google/uuid"
 | 
				
			||||||
	"github.com/spf13/viper"
 | 
						"github.com/spf13/viper"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -71,6 +72,16 @@ func startCall(c *fiber.Ctx) error {
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
 | 
							return fiber.NewError(fiber.StatusBadRequest, err.Error())
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
 | 
							_, _ = services.NewEvent(models.Event{
 | 
				
			||||||
 | 
								Uuid:      uuid.NewString(),
 | 
				
			||||||
 | 
								Body:      map[string]any{},
 | 
				
			||||||
 | 
								Type:      "calls.start",
 | 
				
			||||||
 | 
								Channel:   channel,
 | 
				
			||||||
 | 
								Sender:    membership,
 | 
				
			||||||
 | 
								ChannelID: channel.ID,
 | 
				
			||||||
 | 
								SenderID:  membership.ID,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return c.JSON(call)
 | 
							return c.JSON(call)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -107,6 +118,16 @@ func endCall(c *fiber.Ctx) error {
 | 
				
			|||||||
	if call, err := services.EndCall(call); err != nil {
 | 
						if call, err := services.EndCall(call); err != nil {
 | 
				
			||||||
		return fiber.NewError(fiber.StatusInternalServerError, err.Error())
 | 
							return fiber.NewError(fiber.StatusInternalServerError, err.Error())
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
 | 
							_, _ = services.NewEvent(models.Event{
 | 
				
			||||||
 | 
								Uuid:      uuid.NewString(),
 | 
				
			||||||
 | 
								Body:      map[string]any{"last": call.EndedAt.Unix() - call.CreatedAt.Unix()},
 | 
				
			||||||
 | 
								Type:      "calls.end",
 | 
				
			||||||
 | 
								Channel:   channel,
 | 
				
			||||||
 | 
								Sender:    membership,
 | 
				
			||||||
 | 
								ChannelID: channel.ID,
 | 
				
			||||||
 | 
								SenderID:  membership.ID,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return c.JSON(call)
 | 
							return c.JSON(call)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										126
									
								
								pkg/internal/server/api/events_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								pkg/internal/server/api/events_api.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,126 @@
 | 
				
			|||||||
 | 
					package api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"git.solsynth.dev/hydrogen/messaging/pkg/internal/gap"
 | 
				
			||||||
 | 
						"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
 | 
				
			||||||
 | 
						"git.solsynth.dev/hydrogen/messaging/pkg/internal/server/exts"
 | 
				
			||||||
 | 
						"git.solsynth.dev/hydrogen/messaging/pkg/internal/services"
 | 
				
			||||||
 | 
						"github.com/gofiber/fiber/v2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getEvent(c *fiber.Ctx) error {
 | 
				
			||||||
 | 
						if err := gap.H.EnsureAuthenticated(c); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						user := c.Locals("user").(models.Account)
 | 
				
			||||||
 | 
						alias := c.Params("channel")
 | 
				
			||||||
 | 
						id, _ := c.ParamsInt("eventId")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var channel models.Channel
 | 
				
			||||||
 | 
						if val, ok := c.Locals("realm").(models.Realm); ok {
 | 
				
			||||||
 | 
							channel, err = services.GetChannelWithAlias(alias, val.ID)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							channel, err = services.GetChannelWithAlias(alias)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
				
			||||||
 | 
						} else if _, _, err := services.GetAvailableChannel(channel.ID, user); err != nil {
 | 
				
			||||||
 | 
							return fiber.NewError(fiber.StatusForbidden, fmt.Sprintf("you need join the channel before you read the messages: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						event, err := services.GetEvent(channel, uint(id))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c.JSON(event)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func listEvent(c *fiber.Ctx) error {
 | 
				
			||||||
 | 
						if err := gap.H.EnsureAuthenticated(c); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						user := c.Locals("user").(models.Account)
 | 
				
			||||||
 | 
						take := c.QueryInt("take", 0)
 | 
				
			||||||
 | 
						offset := c.QueryInt("offset", 0)
 | 
				
			||||||
 | 
						alias := c.Params("channel")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var channel models.Channel
 | 
				
			||||||
 | 
						if val, ok := c.Locals("realm").(models.Realm); ok {
 | 
				
			||||||
 | 
							channel, err = services.GetChannelWithAlias(alias, val.ID)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							channel, err = services.GetChannelWithAlias(alias)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
				
			||||||
 | 
						} else if _, _, err := services.GetAvailableChannel(channel.ID, user); err != nil {
 | 
				
			||||||
 | 
							return fiber.NewError(fiber.StatusForbidden, fmt.Sprintf("you need join the channel before you read the messages: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						count := services.CountEvent(channel)
 | 
				
			||||||
 | 
						events, err := services.ListEvent(channel, take, offset)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c.JSON(fiber.Map{
 | 
				
			||||||
 | 
							"count": count,
 | 
				
			||||||
 | 
							"data":  events,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func newRawEvent(c *fiber.Ctx) error {
 | 
				
			||||||
 | 
						if err := gap.H.EnsureGrantedPerm(c, "CreateMessagingRawEvent", true); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						user := c.Locals("user").(models.Account)
 | 
				
			||||||
 | 
						alias := c.Params("channel")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var data struct {
 | 
				
			||||||
 | 
							Uuid string         `json:"uuid" validate:"required"`
 | 
				
			||||||
 | 
							Type string         `json:"type" validate:"required"`
 | 
				
			||||||
 | 
							Body map[string]any `json:"body"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := exts.BindAndValidate(c, &data); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						} else if len(data.Uuid) < 36 {
 | 
				
			||||||
 | 
							return fiber.NewError(fiber.StatusBadRequest, "message uuid was not valid")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var channel models.Channel
 | 
				
			||||||
 | 
						var member models.ChannelMember
 | 
				
			||||||
 | 
						if val, ok := c.Locals("realm").(models.Realm); ok {
 | 
				
			||||||
 | 
							channel, member, err = services.GetAvailableChannelWithAlias(alias, user, val.ID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							channel, member, err = services.GetAvailableChannelWithAlias(alias, user)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
				
			||||||
 | 
							} else if member.PowerLevel < 0 {
 | 
				
			||||||
 | 
								return fiber.NewError(fiber.StatusForbidden, "you have not enough permission to send message")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						event := models.Event{
 | 
				
			||||||
 | 
							Uuid:      data.Uuid,
 | 
				
			||||||
 | 
							Body:      data.Body,
 | 
				
			||||||
 | 
							Type:      data.Type,
 | 
				
			||||||
 | 
							Sender:    member,
 | 
				
			||||||
 | 
							Channel:   channel,
 | 
				
			||||||
 | 
							ChannelID: channel.ID,
 | 
				
			||||||
 | 
							SenderID:  member.ID,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if event, err = services.NewEvent(event); err != nil {
 | 
				
			||||||
 | 
							return fiber.NewError(fiber.StatusBadRequest, err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c.JSON(event)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										157
									
								
								pkg/internal/server/api/events_message_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								pkg/internal/server/api/events_message_api.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
				
			|||||||
 | 
					package api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"git.solsynth.dev/hydrogen/messaging/pkg/internal/gap"
 | 
				
			||||||
 | 
						"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
 | 
				
			||||||
 | 
						"git.solsynth.dev/hydrogen/messaging/pkg/internal/server/exts"
 | 
				
			||||||
 | 
						"git.solsynth.dev/hydrogen/messaging/pkg/internal/services"
 | 
				
			||||||
 | 
						"github.com/gofiber/fiber/v2"
 | 
				
			||||||
 | 
						jsoniter "github.com/json-iterator/go"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func newMessageEvent(c *fiber.Ctx) error {
 | 
				
			||||||
 | 
						if err := gap.H.EnsureAuthenticated(c); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						user := c.Locals("user").(models.Account)
 | 
				
			||||||
 | 
						alias := c.Params("channel")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var data struct {
 | 
				
			||||||
 | 
							Uuid string                  `json:"uuid" validate:"required"`
 | 
				
			||||||
 | 
							Type string                  `json:"type" validate:"required"`
 | 
				
			||||||
 | 
							Body models.EventMessageBody `json:"body"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := exts.BindAndValidate(c, &data); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						} else if len(data.Uuid) < 36 {
 | 
				
			||||||
 | 
							return fiber.NewError(fiber.StatusBadRequest, "message uuid was not valid")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(data.Body.Text) == 0 && len(data.Body.Attachments) == 0 {
 | 
				
			||||||
 | 
							return fiber.NewError(fiber.StatusBadRequest, "you cannot send an empty message")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var channel models.Channel
 | 
				
			||||||
 | 
						var member models.ChannelMember
 | 
				
			||||||
 | 
						if val, ok := c.Locals("realm").(models.Realm); ok {
 | 
				
			||||||
 | 
							channel, member, err = services.GetAvailableChannelWithAlias(alias, user, val.ID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							channel, member, err = services.GetAvailableChannelWithAlias(alias, user)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
				
			||||||
 | 
							} else if member.PowerLevel < 0 {
 | 
				
			||||||
 | 
								return fiber.NewError(fiber.StatusForbidden, "you have not enough permission to send message")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var parsed map[string]any
 | 
				
			||||||
 | 
						raw, _ := jsoniter.Marshal(data.Body)
 | 
				
			||||||
 | 
						_ = jsoniter.Unmarshal(raw, &parsed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						event := models.Event{
 | 
				
			||||||
 | 
							Uuid:      data.Uuid,
 | 
				
			||||||
 | 
							Body:      parsed,
 | 
				
			||||||
 | 
							Type:      data.Type,
 | 
				
			||||||
 | 
							Sender:    member,
 | 
				
			||||||
 | 
							Channel:   channel,
 | 
				
			||||||
 | 
							ChannelID: channel.ID,
 | 
				
			||||||
 | 
							SenderID:  member.ID,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if event, err = services.NewEvent(event); err != nil {
 | 
				
			||||||
 | 
							return fiber.NewError(fiber.StatusBadRequest, err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c.JSON(event)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func editMessageEvent(c *fiber.Ctx) error {
 | 
				
			||||||
 | 
						if err := gap.H.EnsureAuthenticated(c); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						user := c.Locals("user").(models.Account)
 | 
				
			||||||
 | 
						alias := c.Params("channel")
 | 
				
			||||||
 | 
						messageId, _ := c.ParamsInt("messageId", 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var data struct {
 | 
				
			||||||
 | 
							Uuid string                  `json:"uuid" validate:"required"`
 | 
				
			||||||
 | 
							Type string                  `json:"type" validate:"required"`
 | 
				
			||||||
 | 
							Body models.EventMessageBody `json:"body"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := exts.BindAndValidate(c, &data); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(data.Body.Text) == 0 && len(data.Body.Attachments) == 0 {
 | 
				
			||||||
 | 
							return fiber.NewError(fiber.StatusBadRequest, "you cannot send an empty message")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var channel models.Channel
 | 
				
			||||||
 | 
						var member models.ChannelMember
 | 
				
			||||||
 | 
						if val, ok := c.Locals("realm").(models.Realm); ok {
 | 
				
			||||||
 | 
							channel, member, err = services.GetAvailableChannelWithAlias(alias, user, val.ID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							channel, member, err = services.GetAvailableChannelWithAlias(alias, user)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var event models.Event
 | 
				
			||||||
 | 
						if event, err = services.GetEventWithSender(channel, member, uint(messageId)); err != nil {
 | 
				
			||||||
 | 
							return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						event, err = services.EditMessage(event, data.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fiber.NewError(fiber.StatusBadRequest, err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c.JSON(event)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func deleteMessageEvent(c *fiber.Ctx) error {
 | 
				
			||||||
 | 
						if err := gap.H.EnsureAuthenticated(c); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						user := c.Locals("user").(models.Account)
 | 
				
			||||||
 | 
						alias := c.Params("channel")
 | 
				
			||||||
 | 
						messageId, _ := c.ParamsInt("messageId", 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var channel models.Channel
 | 
				
			||||||
 | 
						var member models.ChannelMember
 | 
				
			||||||
 | 
						if val, ok := c.Locals("realm").(models.Realm); ok {
 | 
				
			||||||
 | 
							channel, member, err = services.GetAvailableChannelWithAlias(alias, user, val.ID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							channel, member, err = services.GetAvailableChannelWithAlias(alias, user)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var event models.Event
 | 
				
			||||||
 | 
						if event, err = services.GetEventWithSender(channel, member, uint(messageId)); err != nil {
 | 
				
			||||||
 | 
							return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						event, err = services.DeleteMessage(event)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fiber.NewError(fiber.StatusBadRequest, err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c.JSON(event)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -33,10 +33,13 @@ func MapAPIs(app *fiber.App) {
 | 
				
			|||||||
			channels.Delete("/:channel/members", removeChannelMember)
 | 
								channels.Delete("/:channel/members", removeChannelMember)
 | 
				
			||||||
			channels.Delete("/:channel/members/me", leaveChannel)
 | 
								channels.Delete("/:channel/members/me", leaveChannel)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			channels.Get("/:channel/messages", listMessage)
 | 
								channels.Get("/:channel/events", listEvent)
 | 
				
			||||||
			channels.Post("/:channel/messages", newMessage)
 | 
								channels.Get("/:channel/events/:eventId", getEvent)
 | 
				
			||||||
			channels.Put("/:channel/messages/:messageId", editMessage)
 | 
								channels.Post("/:channel/events", newRawEvent)
 | 
				
			||||||
			channels.Delete("/:channel/messages/:messageId", deleteMessage)
 | 
					
 | 
				
			||||||
 | 
								channels.Post("/:channel/messages", newMessageEvent)
 | 
				
			||||||
 | 
								channels.Put("/:channel/messages/:messageId", editMessageEvent)
 | 
				
			||||||
 | 
								channels.Delete("/:channel/messages/:messageId", deleteMessageEvent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			channels.Get("/:channel/calls", listCall)
 | 
								channels.Get("/:channel/calls", listCall)
 | 
				
			||||||
			channels.Get("/:channel/calls/ongoing", getOngoingCall)
 | 
								channels.Get("/:channel/calls/ongoing", getOngoingCall)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,221 +0,0 @@
 | 
				
			|||||||
package api
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"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/messaging/pkg/internal/server/exts"
 | 
					 | 
				
			||||||
	"git.solsynth.dev/hydrogen/messaging/pkg/internal/services"
 | 
					 | 
				
			||||||
	"github.com/gofiber/fiber/v2"
 | 
					 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func listMessage(c *fiber.Ctx) error {
 | 
					 | 
				
			||||||
	if err := gap.H.EnsureAuthenticated(c); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	user := c.Locals("user").(models.Account)
 | 
					 | 
				
			||||||
	take := c.QueryInt("take", 0)
 | 
					 | 
				
			||||||
	offset := c.QueryInt("offset", 0)
 | 
					 | 
				
			||||||
	alias := c.Params("channel")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var err error
 | 
					 | 
				
			||||||
	var channel models.Channel
 | 
					 | 
				
			||||||
	if val, ok := c.Locals("realm").(models.Realm); ok {
 | 
					 | 
				
			||||||
		channel, err = services.GetChannelWithAlias(alias, val.ID)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		channel, err = services.GetChannelWithAlias(alias)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
					 | 
				
			||||||
	} else if _, _, err := services.GetAvailableChannel(channel.ID, user); err != nil {
 | 
					 | 
				
			||||||
		return fiber.NewError(fiber.StatusForbidden, fmt.Sprintf("you need join the channel before you read the messages: %v", err))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	count := services.CountMessage(channel)
 | 
					 | 
				
			||||||
	messages, err := services.ListMessage(channel, take, offset)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return c.JSON(fiber.Map{
 | 
					 | 
				
			||||||
		"count": count,
 | 
					 | 
				
			||||||
		"data":  messages,
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func newMessage(c *fiber.Ctx) error {
 | 
					 | 
				
			||||||
	if err := gap.H.EnsureAuthenticated(c); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	user := c.Locals("user").(models.Account)
 | 
					 | 
				
			||||||
	alias := c.Params("channel")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var data struct {
 | 
					 | 
				
			||||||
		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 := exts.BindAndValidate(c, &data); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	} else if len(data.Uuid) < 36 {
 | 
					 | 
				
			||||||
		return fiber.NewError(fiber.StatusBadRequest, "message uuid was not valid")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if len(data.Attachments) == 0 {
 | 
					 | 
				
			||||||
		if data.Type == models.MessageTextType {
 | 
					 | 
				
			||||||
			if val, ok := data.Content["value"].(string); ok && len(strings.TrimSpace(val)) == 0 {
 | 
					 | 
				
			||||||
				return fiber.NewError(fiber.StatusBadRequest, "you cannot send an empty message")
 | 
					 | 
				
			||||||
			} else if !ok {
 | 
					 | 
				
			||||||
				return fiber.NewError(fiber.StatusBadRequest, "invalid content of text message")
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	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
 | 
					 | 
				
			||||||
	if val, ok := c.Locals("realm").(models.Realm); ok {
 | 
					 | 
				
			||||||
		channel, member, err = services.GetAvailableChannelWithAlias(alias, user, val.ID)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		channel, member, err = services.GetAvailableChannelWithAlias(alias, user)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
					 | 
				
			||||||
		} else if member.PowerLevel < 0 {
 | 
					 | 
				
			||||||
			return fiber.NewError(fiber.StatusForbidden, "you have not enough permission to send message")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	message := models.Message{
 | 
					 | 
				
			||||||
		Uuid:        data.Uuid,
 | 
					 | 
				
			||||||
		Content:     data.Content,
 | 
					 | 
				
			||||||
		Sender:      member,
 | 
					 | 
				
			||||||
		Channel:     channel,
 | 
					 | 
				
			||||||
		ChannelID:   channel.ID,
 | 
					 | 
				
			||||||
		SenderID:    member.ID,
 | 
					 | 
				
			||||||
		Attachments: data.Attachments,
 | 
					 | 
				
			||||||
		Type:        data.Type,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var replyTo models.Message
 | 
					 | 
				
			||||||
	if data.ReplyTo != nil {
 | 
					 | 
				
			||||||
		if err := database.C.Where("id = ?", data.ReplyTo).First(&replyTo).Error; err != nil {
 | 
					 | 
				
			||||||
			return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("message to reply was not found: %v", err))
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			message.ReplyTo = &replyTo
 | 
					 | 
				
			||||||
			message.ReplyID = &replyTo.ID
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if message, err = services.NewMessage(message); err != nil {
 | 
					 | 
				
			||||||
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return c.JSON(message)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func editMessage(c *fiber.Ctx) error {
 | 
					 | 
				
			||||||
	if err := gap.H.EnsureAuthenticated(c); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	user := c.Locals("user").(models.Account)
 | 
					 | 
				
			||||||
	alias := c.Params("channel")
 | 
					 | 
				
			||||||
	messageId, _ := c.ParamsInt("messageId", 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var data struct {
 | 
					 | 
				
			||||||
		Type        string         `json:"type" validate:"required"`
 | 
					 | 
				
			||||||
		Content     map[string]any `json:"content"`
 | 
					 | 
				
			||||||
		Attachments []uint         `json:"attachments"`
 | 
					 | 
				
			||||||
		ReplyTo     *uint          `json:"reply_to"`
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := exts.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
 | 
					 | 
				
			||||||
	if val, ok := c.Locals("realm").(models.Realm); ok {
 | 
					 | 
				
			||||||
		channel, member, err = services.GetAvailableChannelWithAlias(alias, user, val.ID)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		channel, member, err = services.GetAvailableChannelWithAlias(alias, user)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var message models.Message
 | 
					 | 
				
			||||||
	if message, err = services.GetMessageWithPrincipal(channel, member, uint(messageId)); err != nil {
 | 
					 | 
				
			||||||
		return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	message.Attachments = data.Attachments
 | 
					 | 
				
			||||||
	message.Content = data.Content
 | 
					 | 
				
			||||||
	message.Type = data.Type
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	message, err = services.EditMessage(message)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return c.JSON(message)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func deleteMessage(c *fiber.Ctx) error {
 | 
					 | 
				
			||||||
	if err := gap.H.EnsureAuthenticated(c); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	user := c.Locals("user").(models.Account)
 | 
					 | 
				
			||||||
	alias := c.Params("channel")
 | 
					 | 
				
			||||||
	messageId, _ := c.ParamsInt("messageId", 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var err error
 | 
					 | 
				
			||||||
	var channel models.Channel
 | 
					 | 
				
			||||||
	var member models.ChannelMember
 | 
					 | 
				
			||||||
	if val, ok := c.Locals("realm").(models.Realm); ok {
 | 
					 | 
				
			||||||
		channel, member, err = services.GetAvailableChannelWithAlias(alias, user, val.ID)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		channel, member, err = services.GetAvailableChannelWithAlias(alias, user)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var message models.Message
 | 
					 | 
				
			||||||
	if message, err = services.GetMessageWithPrincipal(channel, member, uint(messageId)); err != nil {
 | 
					 | 
				
			||||||
		return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	message, err = services.DeleteMessage(message)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return c.JSON(message)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										152
									
								
								pkg/internal/services/events.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								pkg/internal/services/events.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,152 @@
 | 
				
			|||||||
 | 
					package services
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"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/rs/zerolog/log"
 | 
				
			||||||
 | 
						"github.com/samber/lo"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func CountEvent(channel models.Channel) int64 {
 | 
				
			||||||
 | 
						var count int64
 | 
				
			||||||
 | 
						if err := database.C.Where(models.Event{
 | 
				
			||||||
 | 
							ChannelID: channel.ID,
 | 
				
			||||||
 | 
						}).Model(&models.Event{}).Count(&count).Error; err != nil {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return count
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ListEvent(channel models.Channel, take int, offset int) ([]models.Event, error) {
 | 
				
			||||||
 | 
						if take > 100 {
 | 
				
			||||||
 | 
							take = 100
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var events []models.Event
 | 
				
			||||||
 | 
						if err := database.C.
 | 
				
			||||||
 | 
							Where(models.Event{
 | 
				
			||||||
 | 
								ChannelID: channel.ID,
 | 
				
			||||||
 | 
							}).Limit(take).Offset(offset).
 | 
				
			||||||
 | 
							Order("created_at DESC").
 | 
				
			||||||
 | 
							Preload("Sender").
 | 
				
			||||||
 | 
							Preload("Sender.Account").
 | 
				
			||||||
 | 
							Find(&events).Error; err != nil {
 | 
				
			||||||
 | 
							return events, err
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return events, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetEvent(channel models.Channel, id uint) (models.Event, error) {
 | 
				
			||||||
 | 
						var event models.Event
 | 
				
			||||||
 | 
						if err := database.C.
 | 
				
			||||||
 | 
							Where(models.Event{
 | 
				
			||||||
 | 
								BaseModel: models.BaseModel{ID: id},
 | 
				
			||||||
 | 
								ChannelID: channel.ID,
 | 
				
			||||||
 | 
							}).
 | 
				
			||||||
 | 
							Preload("Sender").
 | 
				
			||||||
 | 
							Preload("Sender.Account").
 | 
				
			||||||
 | 
							First(&event).Error; err != nil {
 | 
				
			||||||
 | 
							return event, err
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return event, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetEventWithSender(channel models.Channel, member models.ChannelMember, id uint) (models.Event, error) {
 | 
				
			||||||
 | 
						var event models.Event
 | 
				
			||||||
 | 
						if err := database.C.Where(models.Event{
 | 
				
			||||||
 | 
							BaseModel: models.BaseModel{ID: id},
 | 
				
			||||||
 | 
							ChannelID: channel.ID,
 | 
				
			||||||
 | 
							SenderID:  member.ID,
 | 
				
			||||||
 | 
						}).First(&event).Error; err != nil {
 | 
				
			||||||
 | 
							return event, err
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return event, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewEvent(event models.Event) (models.Event, error) {
 | 
				
			||||||
 | 
						var members []models.ChannelMember
 | 
				
			||||||
 | 
						if err := database.C.Save(&event).Error; err != nil {
 | 
				
			||||||
 | 
							return event, err
 | 
				
			||||||
 | 
						} else if err = database.C.Where(models.ChannelMember{
 | 
				
			||||||
 | 
							ChannelID: event.ChannelID,
 | 
				
			||||||
 | 
						}).Preload("Account").Find(&members).Error; err != nil {
 | 
				
			||||||
 | 
							// Couldn't get channel members, skip notifying
 | 
				
			||||||
 | 
							return event, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						event, _ = GetEvent(event.Channel, event.ID)
 | 
				
			||||||
 | 
						for _, member := range members {
 | 
				
			||||||
 | 
							PushCommand(member.AccountID, models.UnifiedCommand{
 | 
				
			||||||
 | 
								Action:  "events.new",
 | 
				
			||||||
 | 
								Payload: event,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if strings.HasPrefix(event.Type, "messages") {
 | 
				
			||||||
 | 
							NotifyMessageEvent(members, event)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return event, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NotifyMessageEvent(members []models.ChannelMember, event models.Event) {
 | 
				
			||||||
 | 
						var body models.EventMessageBody
 | 
				
			||||||
 | 
						raw, _ := jsoniter.Marshal(event.Body)
 | 
				
			||||||
 | 
						_ = jsoniter.Unmarshal(raw, &body)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, member := range members {
 | 
				
			||||||
 | 
							if member.ID != event.SenderID {
 | 
				
			||||||
 | 
								switch member.Notify {
 | 
				
			||||||
 | 
								case models.NotifyLevelNone:
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								case models.NotifyLevelMentioned:
 | 
				
			||||||
 | 
									if len(body.RelatedUsers) == 0 || !lo.Contains(body.RelatedUsers, member.AccountID) {
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								var displayText string
 | 
				
			||||||
 | 
								if body.Algorithm == "plain" {
 | 
				
			||||||
 | 
									displayText = body.Text
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if len(displayText) == 0 {
 | 
				
			||||||
 | 
									displayText = fmt.Sprintf("%d attachment(s)", len(body.Attachments))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								err := NotifyAccountMessager(member.Account,
 | 
				
			||||||
 | 
									"incomingMessage",
 | 
				
			||||||
 | 
									fmt.Sprintf("%s in #%s", event.Sender.Account.Nick, event.Channel.Alias),
 | 
				
			||||||
 | 
									fmt.Sprintf("%s", displayText),
 | 
				
			||||||
 | 
									true,
 | 
				
			||||||
 | 
									false,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Warn().Err(err).Msg("An error occurred when trying notify user.")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func EditEvent(event models.Event) (models.Event, error) {
 | 
				
			||||||
 | 
						if err := database.C.Save(&event).Error; err != nil {
 | 
				
			||||||
 | 
							return event, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return event, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func DeleteEvent(event models.Event) (models.Event, error) {
 | 
				
			||||||
 | 
						if err := database.C.Delete(&event).Error; err != nil {
 | 
				
			||||||
 | 
							return event, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return event, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,181 +1,60 @@
 | 
				
			|||||||
package services
 | 
					package services
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"git.solsynth.dev/hydrogen/messaging/pkg/internal/database"
 | 
					 | 
				
			||||||
	"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
 | 
						"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
 | 
				
			||||||
	"github.com/rs/zerolog/log"
 | 
						"github.com/google/uuid"
 | 
				
			||||||
	"github.com/samber/lo"
 | 
						jsoniter "github.com/json-iterator/go"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func CountMessage(channel models.Channel) int64 {
 | 
					func EncodeMessageBody(body models.EventMessageBody) map[string]any {
 | 
				
			||||||
	var count int64
 | 
						var parsed map[string]any
 | 
				
			||||||
	if err := database.C.Where(models.Message{
 | 
						raw, _ := jsoniter.Marshal(body)
 | 
				
			||||||
		ChannelID: channel.ID,
 | 
						_ = jsoniter.Unmarshal(raw, &parsed)
 | 
				
			||||||
	}).Model(&models.Message{}).Count(&count).Error; err != nil {
 | 
						return parsed
 | 
				
			||||||
		return 0
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		return count
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ListMessage(channel models.Channel, take int, offset int) ([]models.Message, error) {
 | 
					func EditMessage(event models.Event, body models.EventMessageBody) (models.Event, error) {
 | 
				
			||||||
	if take > 100 {
 | 
						event.Body = EncodeMessageBody(body)
 | 
				
			||||||
		take = 100
 | 
						event, err := EditEvent(event)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return event, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						body.RelatedEvent = event.ID
 | 
				
			||||||
 | 
						_, err = NewEvent(models.Event{
 | 
				
			||||||
 | 
							Uuid:      uuid.NewString(),
 | 
				
			||||||
 | 
							Body:      EncodeMessageBody(body),
 | 
				
			||||||
 | 
							Type:      models.EventMessageEdit,
 | 
				
			||||||
 | 
							Channel:   event.Channel,
 | 
				
			||||||
 | 
							Sender:    event.Sender,
 | 
				
			||||||
 | 
							ChannelID: event.ChannelID,
 | 
				
			||||||
 | 
							SenderID:  event.SenderID,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return event, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var messages []models.Message
 | 
						return event, nil
 | 
				
			||||||
	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) {
 | 
					func DeleteMessage(event models.Event) (models.Event, error) {
 | 
				
			||||||
	var message models.Message
 | 
						_, err := DeleteEvent(event)
 | 
				
			||||||
	if err := database.C.
 | 
						if err != nil {
 | 
				
			||||||
		Where(models.Message{
 | 
							return event, err
 | 
				
			||||||
			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
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
						_, err = NewEvent(models.Event{
 | 
				
			||||||
 | 
							Uuid: uuid.NewString(),
 | 
				
			||||||
func GetMessageWithPrincipal(channel models.Channel, member models.ChannelMember, id uint) (models.Message, error) {
 | 
							Body: EncodeMessageBody(models.EventMessageBody{
 | 
				
			||||||
	var message models.Message
 | 
								RelatedEvent: event.ID,
 | 
				
			||||||
	if err := database.C.Where(models.Message{
 | 
							}),
 | 
				
			||||||
		BaseModel: models.BaseModel{ID: id},
 | 
							Type:      models.EventMessageDelete,
 | 
				
			||||||
		ChannelID: channel.ID,
 | 
							Channel:   event.Channel,
 | 
				
			||||||
		SenderID:  member.ID,
 | 
							Sender:    event.Sender,
 | 
				
			||||||
	}).First(&message).Error; err != nil {
 | 
							ChannelID: event.ChannelID,
 | 
				
			||||||
		return message, err
 | 
							SenderID:  event.SenderID,
 | 
				
			||||||
	} else {
 | 
						})
 | 
				
			||||||
		return message, nil
 | 
						if err != nil {
 | 
				
			||||||
	}
 | 
							return event, err
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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
 | 
						return event, 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
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user