⬆️ Upgrade to support the latest version Hydrogen Project standard
This commit is contained in:
140
pkg/internal/server/api/calls_api.go
Normal file
140
pkg/internal/server/api/calls_api.go
Normal file
@ -0,0 +1,140 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/database"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func listCall(c *fiber.Ctx) error {
|
||||
take := c.QueryInt("take", 0)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
alias := c.Params("channel")
|
||||
|
||||
var channel models.Channel
|
||||
if err := database.C.Where(&models.Channel{
|
||||
Alias: alias,
|
||||
}).First(&channel).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if calls, err := services.ListCall(channel, take, offset); err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else {
|
||||
return c.JSON(calls)
|
||||
}
|
||||
}
|
||||
|
||||
func getOngoingCall(c *fiber.Ctx) error {
|
||||
alias := c.Params("channel")
|
||||
|
||||
var channel models.Channel
|
||||
if err := database.C.Where(&models.Channel{
|
||||
Alias: alias,
|
||||
}).First(&channel).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if call, err := services.GetOngoingCall(channel); err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else {
|
||||
return c.JSON(call)
|
||||
}
|
||||
}
|
||||
|
||||
func startCall(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
alias := c.Params("channel")
|
||||
|
||||
var channel models.Channel
|
||||
if err := database.C.Where(&models.Channel{
|
||||
Alias: alias,
|
||||
}).First(&channel).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
var membership models.ChannelMember
|
||||
if err := database.C.Where(&models.ChannelMember{
|
||||
ChannelID: channel.ID,
|
||||
AccountID: user.ID,
|
||||
}).Find(&membership).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
call, err := services.NewCall(channel, membership)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(call)
|
||||
}
|
||||
}
|
||||
|
||||
func endCall(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
alias := c.Params("channel")
|
||||
|
||||
var channel models.Channel
|
||||
if err := database.C.Where(&models.Channel{
|
||||
Alias: alias,
|
||||
}).First(&channel).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
var membership models.ChannelMember
|
||||
if err := database.C.Where(&models.ChannelMember{
|
||||
ChannelID: channel.ID,
|
||||
AccountID: user.ID,
|
||||
}).Find(&membership).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
call, err := services.GetOngoingCall(channel)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else if call.FounderID != user.ID && channel.AccountID != user.ID {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "only call founder or channel owner can end this call")
|
||||
}
|
||||
|
||||
if call, err := services.EndCall(call); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
return c.JSON(call)
|
||||
}
|
||||
}
|
||||
|
||||
func exchangeCallToken(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
alias := c.Params("channel")
|
||||
|
||||
var channel models.Channel
|
||||
if err := database.C.Where(&models.Channel{
|
||||
Alias: alias,
|
||||
}).First(&channel).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
var membership models.ChannelMember
|
||||
if err := database.C.Where(&models.ChannelMember{
|
||||
ChannelID: channel.ID,
|
||||
AccountID: user.ID,
|
||||
}).Find(&membership).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
call, err := services.GetOngoingCall(channel)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
tk, err := services.EncodeCallToken(user, call)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
return c.JSON(fiber.Map{
|
||||
"token": tk,
|
||||
"endpoint": viper.GetString("calling.endpoint"),
|
||||
})
|
||||
}
|
||||
}
|
231
pkg/internal/server/api/channel_members_api.go
Normal file
231
pkg/internal/server/api/channel_members_api.go
Normal file
@ -0,0 +1,231 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/server/exts"
|
||||
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/database"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func listChannelMembers(c *fiber.Ctx) error {
|
||||
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())
|
||||
}
|
||||
|
||||
if members, err := services.ListChannelMember(channel.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
return c.JSON(members)
|
||||
}
|
||||
}
|
||||
|
||||
func getMyChannelMembership(c *fiber.Ctx) error {
|
||||
alias := c.Params("channel")
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
if member, err := services.GetChannelMember(user, channel.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else {
|
||||
return c.JSON(member)
|
||||
}
|
||||
}
|
||||
|
||||
func addChannelMember(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
alias := c.Params("channel")
|
||||
|
||||
var data struct {
|
||||
Target string `json:"target" validate:"required"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var channel models.Channel
|
||||
if err := database.C.Where(&models.Channel{
|
||||
Alias: alias,
|
||||
}).First(&channel).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else if channel.Type == models.ChannelTypeDirect {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "direct message member changes was not allowed")
|
||||
}
|
||||
|
||||
if member, err := services.GetChannelMember(user, channel.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusForbidden, err.Error())
|
||||
} else if member.PowerLevel < 50 {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you must be a moderator of a channel to add member into it")
|
||||
}
|
||||
|
||||
var account models.Account
|
||||
if err := database.C.Where(&models.Account{
|
||||
Name: data.Target,
|
||||
}).First(&account).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.AddChannelMemberWithCheck(account, channel); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func removeChannelMember(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
alias := c.Params("channel")
|
||||
|
||||
var data struct {
|
||||
Target string `json:"target" validate:"required"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var channel models.Channel
|
||||
if err := database.C.Where(&models.Channel{
|
||||
Alias: alias,
|
||||
AccountID: user.ID,
|
||||
}).First(&channel).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else if channel.Type == models.ChannelTypeDirect {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "direct message member changes was not allowed")
|
||||
}
|
||||
|
||||
if member, err := services.GetChannelMember(user, channel.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusForbidden, err.Error())
|
||||
} else if member.PowerLevel < 50 {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you must be a moderator of a channel to remove member into it")
|
||||
}
|
||||
|
||||
var account models.Account
|
||||
if err := database.C.Where(&models.Account{
|
||||
Name: data.Target,
|
||||
}).First(&account).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.RemoveChannelMember(account, channel); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func editMyChannelMembership(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
alias := c.Params("channel")
|
||||
|
||||
var data struct {
|
||||
Nick string `json:"nick"`
|
||||
NotifyLevel int8 `json:"notify_level"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
var membership models.ChannelMember
|
||||
if err := database.C.Where(&models.ChannelMember{
|
||||
ChannelID: channel.ID,
|
||||
AccountID: user.ID,
|
||||
}).First(&membership).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
membership.Notify = data.NotifyLevel
|
||||
if len(data.Nick) > 0 {
|
||||
membership.Nick = &data.Nick
|
||||
} else {
|
||||
membership.Nick = nil
|
||||
}
|
||||
|
||||
if membership, err := services.EditChannelMember(membership); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(membership)
|
||||
}
|
||||
}
|
||||
|
||||
func joinChannel(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
alias := c.Params("channel")
|
||||
|
||||
var channel models.Channel
|
||||
if err := database.C.Where(&models.Channel{
|
||||
Alias: alias,
|
||||
}).Preload("Realm").First(&channel).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else if _, _, err := services.GetAvailableChannel(channel.ID, user); err == nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "you already joined the channel")
|
||||
} else if channel.RealmID == nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "you was impossible to join a channel without related realm")
|
||||
}
|
||||
|
||||
if realm, err := services.GetRealm(channel.Realm.ExternalID); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("invalid channel, related realm was not found: %v", err))
|
||||
} else if _, err := services.GetRealmMember(realm.ExternalID, user.ExternalID); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("you are not a part of the realm: %v", err))
|
||||
}
|
||||
|
||||
if err := services.AddChannelMember(user, channel); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func leaveChannel(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
alias := c.Params("channel")
|
||||
|
||||
var channel models.Channel
|
||||
if err := database.C.Where(&models.Channel{
|
||||
Alias: alias,
|
||||
}).First(&channel).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else if user.ID == channel.AccountID {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "you cannot leave your own channel")
|
||||
}
|
||||
|
||||
if err := services.RemoveChannelMember(user, channel); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
234
pkg/internal/server/api/channels_api.go
Normal file
234
pkg/internal/server/api/channels_api.go
Normal file
@ -0,0 +1,234 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/server/exts"
|
||||
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/database"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func getChannel(c *fiber.Ctx) error {
|
||||
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())
|
||||
}
|
||||
|
||||
return c.JSON(channel)
|
||||
}
|
||||
|
||||
func getChannelIdentity(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
alias := c.Params("channel")
|
||||
|
||||
var err error
|
||||
var member models.ChannelMember
|
||||
if val, ok := c.Locals("realm").(models.Realm); ok {
|
||||
_, member, err = services.GetAvailableChannelWithAlias(alias, user, val.ID)
|
||||
} else {
|
||||
_, member, err = services.GetAvailableChannelWithAlias(alias, user)
|
||||
}
|
||||
if err != nil {
|
||||
return c.SendStatus(fiber.StatusForbidden)
|
||||
}
|
||||
|
||||
return c.JSON(member)
|
||||
}
|
||||
|
||||
func listChannel(c *fiber.Ctx) error {
|
||||
var err error
|
||||
var channels []models.Channel
|
||||
if val, ok := c.Locals("realm").(models.Realm); ok {
|
||||
channels, err = services.ListChannel(val.ID)
|
||||
} else {
|
||||
channels, err = services.ListChannel()
|
||||
}
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(channels)
|
||||
}
|
||||
|
||||
func listOwnedChannel(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
var err error
|
||||
var channels []models.Channel
|
||||
if val, ok := c.Locals("realm").(models.Realm); ok {
|
||||
channels, err = services.ListChannelWithUser(user, val.ID)
|
||||
} else {
|
||||
channels, err = services.ListChannelWithUser(user)
|
||||
}
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(channels)
|
||||
}
|
||||
|
||||
func listAvailableChannel(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
var err error
|
||||
var channels []models.Channel
|
||||
if val, ok := c.Locals("realm").(models.Realm); ok {
|
||||
channels, err = services.ListAvailableChannel(user, val.ID)
|
||||
} else {
|
||||
channels, err = services.ListAvailableChannel(user)
|
||||
}
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(channels)
|
||||
}
|
||||
|
||||
func createChannel(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Alias string `json:"alias" validate:"required,lowercase,min=4,max=32"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
IsEncrypted bool `json:"is_encrypted"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
} else if err = services.GetChannelAliasAvailability(data.Alias); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
var realm *models.Realm
|
||||
if val, ok := c.Locals("realm").(models.Realm); ok {
|
||||
if info, err := services.GetRealmMember(val.ExternalID, user.ExternalID); err != nil {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you must be a part of that realm then can create channel related to it")
|
||||
} else if info.GetPowerLevel() < 50 {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you must be a moderator of that realm then can create channel related to it")
|
||||
} else {
|
||||
realm = &val
|
||||
}
|
||||
}
|
||||
|
||||
channel := models.Channel{
|
||||
Alias: data.Alias,
|
||||
Name: data.Name,
|
||||
Description: data.Description,
|
||||
IsEncrypted: data.IsEncrypted,
|
||||
AccountID: user.ID,
|
||||
Type: models.ChannelTypeCommon,
|
||||
Members: []models.ChannelMember{
|
||||
{AccountID: user.ID, PowerLevel: 100},
|
||||
},
|
||||
}
|
||||
|
||||
if realm != nil {
|
||||
channel.RealmID = &realm.ID
|
||||
}
|
||||
|
||||
channel, err := services.NewChannel(channel)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(channel)
|
||||
}
|
||||
|
||||
func editChannel(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
id, _ := c.ParamsInt("channelId", 0)
|
||||
|
||||
var data struct {
|
||||
Alias string `json:"alias" validate:"required,min=4,max=32"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
IsEncrypted bool `json:"is_encrypted"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx := database.C.Where(&models.Channel{BaseModel: models.BaseModel{ID: uint(id)}})
|
||||
|
||||
if val, ok := c.Locals("realm").(models.Realm); ok {
|
||||
if info, err := services.GetRealmMember(val.ExternalID, user.ExternalID); err != nil {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you must be a part of that realm then can edit channel related to it")
|
||||
} else if info.GetPowerLevel() < 50 {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you must be a moderator of that realm then can edit channel related to it")
|
||||
} else {
|
||||
tx = tx.Where("realm_id = ?", val.ID)
|
||||
}
|
||||
} else {
|
||||
tx = tx.Where("realm_id IS NULL")
|
||||
}
|
||||
|
||||
var channel models.Channel
|
||||
if err := tx.First(&channel).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if channel.RealmID != nil {
|
||||
if member, err := services.GetChannelMember(user, channel.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you must be a part of this channel to edit it")
|
||||
} else if member.PowerLevel < 100 {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you must be channel admin to edit it")
|
||||
}
|
||||
}
|
||||
|
||||
channel, err := services.EditChannel(channel, data.Alias, data.Name, data.Description, data.IsEncrypted)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(channel)
|
||||
}
|
||||
|
||||
func deleteChannel(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
id, _ := c.ParamsInt("channelId", 0)
|
||||
|
||||
tx := database.C.Where(&models.Channel{BaseModel: models.BaseModel{ID: uint(id)}})
|
||||
|
||||
if val, ok := c.Locals("realm").(models.Realm); ok {
|
||||
if info, err := services.GetRealmMember(val.ExternalID, user.ExternalID); err != nil {
|
||||
return fmt.Errorf("you must be a part of that realm then can delete channel related to it")
|
||||
} else if info.GetPowerLevel() < 50 {
|
||||
return fmt.Errorf("you must be a moderator of that realm then can delete channel related to it")
|
||||
} else {
|
||||
tx = tx.Where("realm_id = ?", val.ID)
|
||||
}
|
||||
} else {
|
||||
tx = tx.Where("(account_id = ? OR type = ?) AND realm_id IS NULL", user.ID, models.ChannelTypeDirect)
|
||||
}
|
||||
|
||||
var channel models.Channel
|
||||
if err := tx.First(&channel).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if channel.Type == models.ChannelTypeDirect {
|
||||
if member, err := services.GetChannelMember(user, channel.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you must related to this direct message if you want delete it")
|
||||
} else if member.PowerLevel < 100 {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you must be a moderator of this direct message if you want delete it")
|
||||
}
|
||||
}
|
||||
|
||||
if err := services.DeleteChannel(channel); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
71
pkg/internal/server/api/direct_channels_api.go
Normal file
71
pkg/internal/server/api/direct_channels_api.go
Normal file
@ -0,0 +1,71 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/server/exts"
|
||||
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/database"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func createDirectChannel(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Alias string `json:"alias" validate:"required,lowercase,min=4,max=32"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
RelatedUser uint `json:"related_user"`
|
||||
IsEncrypted bool `json:"is_encrypted"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
} else if err = services.GetChannelAliasAvailability(data.Alias); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
var realm *models.Realm
|
||||
if val, ok := c.Locals("realm").(models.Realm); ok {
|
||||
if info, err := services.GetRealmMember(val.ExternalID, user.ExternalID); err != nil {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you must be a part of that realm then can create channel related to it")
|
||||
} else if info.GetPowerLevel() < 50 {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you must be a moderator of that realm then can create channel related to it")
|
||||
} else {
|
||||
realm = &val
|
||||
}
|
||||
}
|
||||
|
||||
var relatedUser models.Account
|
||||
if err := database.C.
|
||||
Where("external_id = ?", data.RelatedUser).
|
||||
First(&relatedUser).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable to find related user: %v", err))
|
||||
}
|
||||
|
||||
channel := models.Channel{
|
||||
Alias: data.Alias,
|
||||
Name: data.Name,
|
||||
Description: data.Description,
|
||||
IsEncrypted: data.IsEncrypted,
|
||||
AccountID: user.ID,
|
||||
Type: models.ChannelTypeDirect,
|
||||
Members: []models.ChannelMember{
|
||||
{AccountID: user.ID, PowerLevel: 100},
|
||||
{AccountID: relatedUser.ID, PowerLevel: 100},
|
||||
},
|
||||
}
|
||||
|
||||
if realm != nil {
|
||||
channel.RealmID = &realm.ID
|
||||
}
|
||||
|
||||
channel, err := services.NewChannel(channel)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(channel)
|
||||
}
|
49
pkg/internal/server/api/index.go
Normal file
49
pkg/internal/server/api/index.go
Normal file
@ -0,0 +1,49 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gofiber/contrib/websocket"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func MapAPIs(app *fiber.App) {
|
||||
api := app.Group("/api").Name("API")
|
||||
{
|
||||
api.Get("/users/me", getUserinfo)
|
||||
api.Get("/users/:accountId", getOthersInfo)
|
||||
|
||||
channels := api.Group("/channels/:realm").Use(realmMiddleware).Name("Channels API")
|
||||
{
|
||||
channels.Get("/", listChannel)
|
||||
channels.Get("/me", listOwnedChannel)
|
||||
channels.Get("/me/available", listAvailableChannel)
|
||||
channels.Get("/:channel", getChannel)
|
||||
channels.Get("/:channel/me", getChannelIdentity)
|
||||
|
||||
channels.Post("/", createChannel)
|
||||
channels.Post("/dm", createDirectChannel)
|
||||
channels.Put("/:channelId", editChannel)
|
||||
channels.Delete("/:channelId", deleteChannel)
|
||||
|
||||
channels.Get("/:channel/members", listChannelMembers)
|
||||
channels.Get("/:channel/members/me", getMyChannelMembership)
|
||||
channels.Put("/:channel/members/me", editMyChannelMembership)
|
||||
channels.Post("/:channel/members", addChannelMember)
|
||||
channels.Post("/:channel/members/me", joinChannel)
|
||||
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/calls", listCall)
|
||||
channels.Get("/:channel/calls/ongoing", getOngoingCall)
|
||||
channels.Post("/:channel/calls", startCall)
|
||||
channels.Delete("/:channel/calls/ongoing", endCall)
|
||||
channels.Post("/:channel/calls/ongoing/token", exchangeCallToken)
|
||||
}
|
||||
|
||||
api.Get("/ws", websocket.New(messageGateway))
|
||||
}
|
||||
}
|
208
pkg/internal/server/api/messages_api.go
Normal file
208
pkg/internal/server/api/messages_api.go
Normal file
@ -0,0 +1,208 @@
|
||||
package api
|
||||
|
||||
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/server/exts"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func listMessage(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(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 {
|
||||
user := c.Locals("principal").(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 {
|
||||
user := c.Locals("principal").(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 {
|
||||
user := c.Locals("principal").(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)
|
||||
}
|
48
pkg/internal/server/api/messages_ws.go
Normal file
48
pkg/internal/server/api/messages_ws.go
Normal file
@ -0,0 +1,48 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/services"
|
||||
"github.com/gofiber/contrib/websocket"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func messageGateway(c *websocket.Conn) {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
// Push connection
|
||||
services.ClientRegister(user, c)
|
||||
log.Debug().Uint("user", user.ID).Msg("New websocket connection established...")
|
||||
|
||||
// Event loop
|
||||
var task models.UnifiedCommand
|
||||
|
||||
var messageType int
|
||||
var packet []byte
|
||||
var err error
|
||||
|
||||
for {
|
||||
if messageType, packet, err = c.ReadMessage(); err != nil {
|
||||
break
|
||||
} else if err := jsoniter.Unmarshal(packet, &task); err != nil {
|
||||
_ = c.WriteMessage(messageType, models.UnifiedCommand{
|
||||
Action: "error",
|
||||
Message: "unable to unmarshal your command, requires json request",
|
||||
}.Marshal())
|
||||
continue
|
||||
}
|
||||
|
||||
message := services.DealCommand(task, user)
|
||||
|
||||
if message != nil {
|
||||
if err = c.WriteMessage(messageType, message.Marshal()); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pop connection
|
||||
services.ClientUnregister(user, c)
|
||||
log.Debug().Uint("user", user.ID).Msg("A websocket connection disconnected...")
|
||||
}
|
21
pkg/internal/server/api/realms_api.go
Normal file
21
pkg/internal/server/api/realms_api.go
Normal file
@ -0,0 +1,21 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func realmMiddleware(c *fiber.Ctx) error {
|
||||
realmAlias := c.Params("realm")
|
||||
if len(realmAlias) > 0 && realmAlias != "global" {
|
||||
realm, err := services.GetRealmWithAlias(realmAlias)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("requested channel with realm, but realm was not found: %v", err))
|
||||
} else {
|
||||
c.Locals("realm", realm)
|
||||
}
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
33
pkg/internal/server/api/users_api.go
Normal file
33
pkg/internal/server/api/users_api.go
Normal file
@ -0,0 +1,33 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/database"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/internal/models"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func getUserinfo(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
var data models.Account
|
||||
if err := database.C.
|
||||
Where(&models.Account{BaseModel: models.BaseModel{ID: user.ID}}).
|
||||
First(&data).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(data)
|
||||
}
|
||||
|
||||
func getOthersInfo(c *fiber.Ctx) error {
|
||||
accountId := c.Params("accountId")
|
||||
|
||||
var data models.Account
|
||||
if err := database.C.
|
||||
Where(&models.Account{Name: accountId}).
|
||||
First(&data).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(data)
|
||||
}
|
Reference in New Issue
Block a user