✨ 基于事件的消息构成 #1
@@ -11,7 +11,7 @@ var DatabaseAutoActionRange = []any{
 | 
			
		||||
	&models.Channel{},
 | 
			
		||||
	&models.ChannelMember{},
 | 
			
		||||
	&models.Call{},
 | 
			
		||||
	&models.Message{},
 | 
			
		||||
	&models.Event{},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RunMigration(source *gorm.DB) error {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
// Account profiles basically fetched from Hydrogen.Identity
 | 
			
		||||
// But cache at here for better usage
 | 
			
		||||
// At the same time this model can make relations between local models
 | 
			
		||||
// But cached at here for better usage
 | 
			
		||||
// At the same time, this model can make relations between local models
 | 
			
		||||
type Account struct {
 | 
			
		||||
	BaseModel
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ type Channel struct {
 | 
			
		||||
	Name        string          `json:"name"`
 | 
			
		||||
	Description string          `json:"description"`
 | 
			
		||||
	Members     []ChannelMember `json:"members"`
 | 
			
		||||
	Messages    []Message       `json:"messages"`
 | 
			
		||||
	Messages    []Event         `json:"messages"`
 | 
			
		||||
	Calls       []Call          `json:"calls"`
 | 
			
		||||
	Type        ChannelType     `json:"type"`
 | 
			
		||||
	Account     Account         `json:"account"`
 | 
			
		||||
@@ -45,5 +45,5 @@ type ChannelMember struct {
 | 
			
		||||
	PowerLevel int         `json:"power_level"`
 | 
			
		||||
 | 
			
		||||
	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/services"
 | 
			
		||||
	"github.com/gofiber/fiber/v2"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"github.com/spf13/viper"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -71,6 +72,16 @@ func startCall(c *fiber.Ctx) error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
 | 
			
		||||
	} 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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -107,6 +118,16 @@ func endCall(c *fiber.Ctx) error {
 | 
			
		||||
	if call, err := services.EndCall(call); err != nil {
 | 
			
		||||
		return fiber.NewError(fiber.StatusInternalServerError, err.Error())
 | 
			
		||||
	} 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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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/me", leaveChannel)
 | 
			
		||||
 | 
			
		||||
			channels.Get("/:channel/messages", listMessage)
 | 
			
		||||
			channels.Post("/:channel/messages", newMessage)
 | 
			
		||||
			channels.Put("/:channel/messages/:messageId", editMessage)
 | 
			
		||||
			channels.Delete("/:channel/messages/:messageId", deleteMessage)
 | 
			
		||||
			channels.Get("/:channel/events", listEvent)
 | 
			
		||||
			channels.Get("/:channel/events/:eventId", getEvent)
 | 
			
		||||
			channels.Post("/:channel/events", newRawEvent)
 | 
			
		||||
 | 
			
		||||
			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/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
 | 
			
		||||
 | 
			
		||||
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"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	jsoniter "github.com/json-iterator/go"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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 EncodeMessageBody(body models.EventMessageBody) map[string]any {
 | 
			
		||||
	var parsed map[string]any
 | 
			
		||||
	raw, _ := jsoniter.Marshal(body)
 | 
			
		||||
	_ = jsoniter.Unmarshal(raw, &parsed)
 | 
			
		||||
	return parsed
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
				)
 | 
			
		||||
func EditMessage(event models.Event, body models.EventMessageBody) (models.Event, error) {
 | 
			
		||||
	event.Body = EncodeMessageBody(body)
 | 
			
		||||
	event, err := EditEvent(event)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
					log.Warn().Err(err).Msg("An error occurred when trying notify user.")
 | 
			
		||||
		return event, err
 | 
			
		||||
	}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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,
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return message, nil
 | 
			
		||||
	return event, 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,
 | 
			
		||||
func DeleteMessage(event models.Event) (models.Event, error) {
 | 
			
		||||
	_, err := DeleteEvent(event)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return event, err
 | 
			
		||||
	}
 | 
			
		||||
	_, err = NewEvent(models.Event{
 | 
			
		||||
		Uuid: uuid.NewString(),
 | 
			
		||||
		Body: EncodeMessageBody(models.EventMessageBody{
 | 
			
		||||
			RelatedEvent: event.ID,
 | 
			
		||||
		}),
 | 
			
		||||
		Type:      models.EventMessageDelete,
 | 
			
		||||
		Channel:   event.Channel,
 | 
			
		||||
		Sender:    event.Sender,
 | 
			
		||||
		ChannelID: event.ChannelID,
 | 
			
		||||
		SenderID:  event.SenderID,
 | 
			
		||||
	})
 | 
			
		||||
		}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return event, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return message, nil
 | 
			
		||||
	return event, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user