🚚 Rename http package to web
This commit is contained in:
232
pkg/internal/web/api/calls_api.go
Normal file
232
pkg/internal/web/api/calls_api.go
Normal file
@ -0,0 +1,232 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
|
||||
authm "git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"sync"
|
||||
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/web/exts"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/models"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var callLocks sync.Map
|
||||
|
||||
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 if res, err := services.GetCallParticipants(call); err != nil {
|
||||
return c.JSON(call)
|
||||
} else {
|
||||
call.Participants = res
|
||||
return c.JSON(call)
|
||||
}
|
||||
}
|
||||
|
||||
func startCall(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureGrantedPerm(c, "CreateCalls", true); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.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())
|
||||
} else if membership.PowerLevel < 0 {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you have not enough permission to create a call")
|
||||
}
|
||||
|
||||
if _, ok := callLocks.Load(channel.ID); ok {
|
||||
return fiber.NewError(fiber.StatusLocked, "there is already a call in creation progress for this channel")
|
||||
} else {
|
||||
callLocks.Store(channel.ID, true)
|
||||
}
|
||||
|
||||
call, err := services.NewCall(channel, membership)
|
||||
if err != nil {
|
||||
callLocks.Delete(channel.ID)
|
||||
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,
|
||||
})
|
||||
|
||||
callLocks.Delete(channel.ID)
|
||||
return c.JSON(call)
|
||||
}
|
||||
}
|
||||
|
||||
func endCall(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.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 != membership.ID && membership.PowerLevel < 50 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "only call founder or channel moderator can end this call")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func kickParticipantInCall(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.Account)
|
||||
alias := c.Params("channel")
|
||||
|
||||
var data struct {
|
||||
Username string `json:"username" 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())
|
||||
}
|
||||
|
||||
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 && membership.PowerLevel < 50 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "only call founder or channel admin can kick participant in this call")
|
||||
}
|
||||
|
||||
if err = services.KickParticipantInCall(call, data.Username); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func exchangeCallToken(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.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"),
|
||||
})
|
||||
}
|
||||
}
|
267
pkg/internal/web/api/channel_members_api.go
Normal file
267
pkg/internal/web/api/channel_members_api.go
Normal file
@ -0,0 +1,267 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/gap"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/web/exts"
|
||||
"git.solsynth.dev/hypernet/nexus/pkg/nex/cruda"
|
||||
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit"
|
||||
authm "git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"strconv"
|
||||
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/models"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func listChannelMembers(c *fiber.Ctx) error {
|
||||
alias := c.Params("channel")
|
||||
take := c.QueryInt("take", 0)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
|
||||
var err error
|
||||
var channel models.Channel
|
||||
if val, ok := c.Locals("realm").(authm.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())
|
||||
}
|
||||
|
||||
count, err := services.CountChannelMember(channel.ID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if members, err := services.ListChannelMember(channel.ID, take, offset); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
return c.JSON(fiber.Map{
|
||||
"count": count,
|
||||
"data": members,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func addChannelMember(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.Account)
|
||||
alias := c.Params("channel")
|
||||
|
||||
var data struct {
|
||||
Related string `json:"related" 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 !channel.IsPublic {
|
||||
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 err error
|
||||
var account authm.Account
|
||||
var numericId int
|
||||
if numericId, err = strconv.Atoi(data.Related); err == nil {
|
||||
account, err = authkit.GetUser(gap.Nx, uint(numericId))
|
||||
} else {
|
||||
account, err = authkit.GetUserByName(gap.Nx, data.Related)
|
||||
}
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.AddChannelMemberWithCheck(account, user, channel); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func removeChannelMember(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.Account)
|
||||
alias := c.Params("channel")
|
||||
memberId := c.Params("memberId")
|
||||
|
||||
numericId, err := strconv.Atoi(memberId)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid member id")
|
||||
}
|
||||
|
||||
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")
|
||||
} else if channel.AccountID == user.ID {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "you cannot remove yourself from your own channel")
|
||||
}
|
||||
|
||||
var member models.ChannelMember
|
||||
if me, err := services.GetChannelMember(user, channel.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusForbidden, err.Error())
|
||||
} else if me.PowerLevel < 50 {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you must be a moderator of a channel to remove member from it")
|
||||
}
|
||||
|
||||
if err := database.C.Where(&models.ChannelMember{
|
||||
BaseModel: cruda.BaseModel{ID: uint(numericId)},
|
||||
ChannelID: channel.ID,
|
||||
}).First(&member).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.RemoveChannelMember(member, channel); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteChannelIdentity(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.Account)
|
||||
alias := c.Params("channel")
|
||||
|
||||
var err error
|
||||
var channel models.Channel
|
||||
if val, ok := c.Locals("realm").(authm.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())
|
||||
}
|
||||
|
||||
if err = services.RemoveChannelMember(membership, channel); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(membership)
|
||||
}
|
||||
}
|
||||
|
||||
func editChannelIdentity(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.Account)
|
||||
alias := c.Params("channel")
|
||||
|
||||
var data struct {
|
||||
Nick string `json:"nick"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
var channel models.Channel
|
||||
if val, ok := c.Locals("realm").(authm.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.Name = user.Name
|
||||
if len(data.Nick) > 0 {
|
||||
membership.Nick = data.Nick
|
||||
} else {
|
||||
membership.Nick = user.Nick
|
||||
}
|
||||
|
||||
if membership, err := services.EditChannelMember(membership); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(membership)
|
||||
}
|
||||
}
|
||||
|
||||
func editChannelNotifyLevel(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.Account)
|
||||
alias := c.Params("channel")
|
||||
|
||||
var data struct {
|
||||
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").(authm.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 membership, err := services.EditChannelMember(membership); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(membership)
|
||||
}
|
||||
}
|
348
pkg/internal/web/api/channels_api.go
Normal file
348
pkg/internal/web/api/channels_api.go
Normal file
@ -0,0 +1,348 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/gap"
|
||||
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit"
|
||||
authm "git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/web/exts"
|
||||
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/models"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func getChannel(c *fiber.Ctx) error {
|
||||
alias := c.Params("channel")
|
||||
|
||||
var err error
|
||||
var channel models.Channel
|
||||
if val, ok := c.Locals("realm").(authm.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 {
|
||||
alias := c.Params("channel")
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.Account)
|
||||
|
||||
var err error
|
||||
var channel models.Channel
|
||||
if val, ok := c.Locals("realm").(authm.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 listChannel(c *fiber.Ctx) error {
|
||||
var user *authm.Account
|
||||
if err := sec.EnsureAuthenticated(c); err == nil {
|
||||
user = lo.ToPtr(c.Locals("user").(authm.Account))
|
||||
}
|
||||
|
||||
var err error
|
||||
var channels []models.Channel
|
||||
if val, ok := c.Locals("realm").(authm.Realm); ok {
|
||||
channels, err = services.ListChannel(user, val.ID)
|
||||
} else {
|
||||
channels, err = services.ListChannel(user)
|
||||
}
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(channels)
|
||||
}
|
||||
|
||||
func listPublicChannel(c *fiber.Ctx) error {
|
||||
var err error
|
||||
var channels []models.Channel
|
||||
if val, ok := c.Locals("realm").(authm.Realm); ok {
|
||||
channels, err = services.ListChannelPublic(val.ID)
|
||||
} else {
|
||||
channels, err = services.ListChannelPublic()
|
||||
}
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(channels)
|
||||
}
|
||||
|
||||
func listOwnedChannel(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.Account)
|
||||
|
||||
var err error
|
||||
var channels []models.Channel
|
||||
if val, ok := c.Locals("realm").(authm.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 listOwnedChannelGlobalWide(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.Account)
|
||||
|
||||
channels, err := services.ListChannelWithUser(user, 0)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(channels)
|
||||
}
|
||||
|
||||
func listAvailableChannel(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.Account)
|
||||
|
||||
tx := database.C
|
||||
isDirect := c.QueryBool("direct", false)
|
||||
if isDirect {
|
||||
tx = tx.Where("type = ?", models.ChannelTypeDirect)
|
||||
} else {
|
||||
tx = tx.Where("type = ?", models.ChannelTypeCommon)
|
||||
}
|
||||
|
||||
var err error
|
||||
var channels []models.Channel
|
||||
if val, ok := c.Locals("realm").(authm.Realm); ok {
|
||||
channels, err = services.ListAvailableChannel(tx, user, val.ID)
|
||||
} else {
|
||||
channels, err = services.ListAvailableChannel(tx, user)
|
||||
}
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(channels)
|
||||
}
|
||||
|
||||
func listAvailableChannelGlobalWide(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.Account)
|
||||
|
||||
channels, err := services.ListAvailableChannel(database.C, user, 0)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(channels)
|
||||
}
|
||||
|
||||
func createChannel(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureGrantedPerm(c, "CreateChannels", true); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.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"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
IsCommunity bool `json:"is_community"`
|
||||
}
|
||||
|
||||
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 *authm.Realm
|
||||
if val, ok := c.Locals("realm").(authm.Realm); ok {
|
||||
if info, err := authkit.GetRealmMember(gap.Nx, val.ID, user.ID); 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.PowerLevel < 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,
|
||||
AccountID: user.ID,
|
||||
Type: models.ChannelTypeCommon,
|
||||
IsPublic: data.IsPublic,
|
||||
IsCommunity: data.IsCommunity,
|
||||
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 {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.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"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
IsCommunity bool `json:"is_community"`
|
||||
NewBelongsRealm *string `json:"new_belongs_realm"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx := database.C.Where("id = ?", id)
|
||||
|
||||
if val, ok := c.Locals("realm").(authm.Realm); ok {
|
||||
if info, err := authkit.GetRealmMember(gap.Nx, val.ID, user.ID); 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.PowerLevel < 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")
|
||||
}
|
||||
}
|
||||
|
||||
if data.NewBelongsRealm != nil {
|
||||
if *data.NewBelongsRealm == "global" {
|
||||
channel.RealmID = nil
|
||||
} else {
|
||||
realm, err := authkit.GetRealmByAlias(gap.Nx, *data.NewBelongsRealm)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("requested channel with realm, but realm was not found: %v", err))
|
||||
} else {
|
||||
if info, err := authkit.GetRealmMember(gap.Nx, realm.ID, user.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you must be a part of that realm then can transfer channel related to it")
|
||||
} else if info.PowerLevel < 50 {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you must be a moderator of that realm then can transfer channel related to it")
|
||||
} else {
|
||||
channel.RealmID = &realm.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
channel.Alias = data.Alias
|
||||
channel.Name = data.Name
|
||||
channel.Description = data.Description
|
||||
channel.IsPublic = data.IsPublic
|
||||
channel.IsCommunity = data.IsCommunity
|
||||
|
||||
channel, err := services.EditChannel(channel)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(channel)
|
||||
}
|
||||
|
||||
func deleteChannel(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.Account)
|
||||
id, _ := c.ParamsInt("channelId", 0)
|
||||
|
||||
tx := database.C.Where("id = ?", id)
|
||||
|
||||
if val, ok := c.Locals("realm").(authm.Realm); ok {
|
||||
if info, err := authkit.GetRealmMember(gap.Nx, val.ID, user.ID); err != nil {
|
||||
return fmt.Errorf("you must be a part of that realm then can delete channel related to it")
|
||||
} else if info.PowerLevel < 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)
|
||||
}
|
84
pkg/internal/web/api/direct_channels_api.go
Normal file
84
pkg/internal/web/api/direct_channels_api.go
Normal file
@ -0,0 +1,84 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/gap"
|
||||
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit"
|
||||
authm "git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/web/exts"
|
||||
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/models"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func createDirectChannel(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.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"`
|
||||
}
|
||||
|
||||
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 *authm.Realm
|
||||
if val, ok := c.Locals("realm").(authm.Realm); ok {
|
||||
if info, err := authkit.GetRealmMember(gap.Nx, val.ID, user.ID); 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.PowerLevel < 50 {
|
||||
return fiber.NewError(fiber.StatusForbidden, "you must be a moderator of that realm then can create channel related to it")
|
||||
} else {
|
||||
realm = &val
|
||||
}
|
||||
}
|
||||
|
||||
relatedUser, err := authkit.GetUser(gap.Nx, data.RelatedUser)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable to find related user: %v", err))
|
||||
}
|
||||
|
||||
if ch, err := services.GetDirectChannelByUser(user, relatedUser); err == nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("you already have a direct with that user #%d", ch.ID))
|
||||
}
|
||||
|
||||
if err := authkit.EnsureUserPermGranted(gap.Nx, user.ID, relatedUser.ID, "ChannelAdd", true); err != nil {
|
||||
return fmt.Errorf("unable to add user into your channel due to access denied: %v", err)
|
||||
}
|
||||
|
||||
channel := models.Channel{
|
||||
Alias: data.Alias,
|
||||
Name: data.Name,
|
||||
Description: data.Description,
|
||||
IsPublic: false,
|
||||
IsCommunity: false,
|
||||
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)
|
||||
}
|
168
pkg/internal/web/api/events_api.go
Normal file
168
pkg/internal/web/api/events_api.go
Normal file
@ -0,0 +1,168 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
|
||||
authm "git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"github.com/samber/lo"
|
||||
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/web/exts"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/models"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func getEvent(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.Account)
|
||||
alias := c.Params("channel")
|
||||
id, _ := c.ParamsInt("eventId")
|
||||
|
||||
var err error
|
||||
var channel models.Channel
|
||||
if val, ok := c.Locals("realm").(authm.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.ID, uint(id))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(event)
|
||||
}
|
||||
|
||||
func listEvent(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.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").(authm.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 checkHasNewEvent(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.Account)
|
||||
pivot := c.QueryInt("pivot", 0)
|
||||
alias := c.Params("channel")
|
||||
|
||||
if pivot < 1 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "pivot must be greater than zero")
|
||||
}
|
||||
|
||||
var err error
|
||||
var channel models.Channel
|
||||
if val, ok := c.Locals("realm").(authm.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))
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err = database.C.
|
||||
Where("channel_id = ?", channel.ID).
|
||||
Where("id > ?", pivot).
|
||||
Model(&models.Event{}).Count(&count).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
return c.JSON(fiber.Map{
|
||||
"up_to_date": lo.Ternary(count > 0, false, true),
|
||||
"count": count,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newRawEvent(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureGrantedPerm(c, "CreateMessagingRawEvent", true); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.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").(authm.Realm); ok {
|
||||
channel, member, err = services.GetChannelIdentity(alias, user.ID, val)
|
||||
} else {
|
||||
channel, member, err = services.GetChannelIdentity(alias, user.ID)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
160
pkg/internal/web/api/events_message_api.go
Normal file
160
pkg/internal/web/api/events_message_api.go
Normal file
@ -0,0 +1,160 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
|
||||
authm "git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"strings"
|
||||
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/web/exts"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/models"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
func newMessageEvent(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.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")
|
||||
}
|
||||
|
||||
data.Body.Text = strings.TrimSpace(data.Body.Text)
|
||||
if len(data.Body.Text) == 0 && len(data.Body.Attachments) == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "empty message was not allowed")
|
||||
}
|
||||
|
||||
var err error
|
||||
var channel models.Channel
|
||||
var member models.ChannelMember
|
||||
|
||||
if val, ok := c.Locals("realm").(authm.Realm); ok {
|
||||
channel, member, err = services.GetChannelIdentity(alias, user.ID, val)
|
||||
} else {
|
||||
channel, member, err = services.GetChannelIdentity(alias, user.ID)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else if member.PowerLevel < 0 {
|
||||
return fiber.NewError(fiber.StatusForbidden, "unable to send message, access denied")
|
||||
}
|
||||
|
||||
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,
|
||||
QuoteEventID: data.Body.QuoteEventID,
|
||||
RelatedEventID: data.Body.RelatedEventID,
|
||||
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 := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.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").(authm.Realm); ok {
|
||||
channel, member, err = services.GetChannelIdentity(alias, user.ID, val)
|
||||
} else {
|
||||
channel, member, err = services.GetChannelIdentity(alias, user.ID)
|
||||
}
|
||||
|
||||
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 := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.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").(authm.Realm); ok {
|
||||
channel, member, err = services.GetChannelIdentity(alias, user.ID, val)
|
||||
} else {
|
||||
channel, member, err = services.GetChannelIdentity(alias, user.ID)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
59
pkg/internal/web/api/index.go
Normal file
59
pkg/internal/web/api/index.go
Normal file
@ -0,0 +1,59 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func MapAPIs(app *fiber.App, baseURL string) {
|
||||
api := app.Group(baseURL).Name("API")
|
||||
{
|
||||
quick := api.Group("/quick")
|
||||
{
|
||||
quick.Post("/:channelId/reply/:eventId", quickReply)
|
||||
}
|
||||
|
||||
api.Get("/channels/me", listOwnedChannelGlobalWide)
|
||||
api.Get("/channels/me/available", listAvailableChannelGlobalWide)
|
||||
|
||||
channels := api.Group("/channels/:realm").Use(realmMiddleware).Name("Channels API")
|
||||
{
|
||||
channels.Get("/", listChannel)
|
||||
channels.Get("/public", listPublicChannel)
|
||||
channels.Get("/me", listOwnedChannel)
|
||||
channels.Get("/me/available", listAvailableChannel)
|
||||
channels.Get("/:channel", getChannel)
|
||||
channels.Get("/:channel/me", getChannelIdentity)
|
||||
channels.Get("/:channel/members/me", getChannelIdentity)
|
||||
channels.Put("/:channel/me", editChannelIdentity)
|
||||
channels.Put("/:channel/me/notify", editChannelNotifyLevel)
|
||||
channels.Put("/:channel/members/me/notify", editChannelNotifyLevel)
|
||||
channels.Delete("/:channel/me", deleteChannelIdentity)
|
||||
|
||||
channels.Post("/", createChannel)
|
||||
channels.Post("/dm", createDirectChannel)
|
||||
channels.Put("/:channelId", editChannel)
|
||||
channels.Delete("/:channelId", deleteChannel)
|
||||
channels.Get("/:channel/members", listChannelMembers)
|
||||
channels.Post("/:channel/members", addChannelMember)
|
||||
channels.Delete("/:channel/members/:memberId", removeChannelMember)
|
||||
|
||||
channels.Get("/:channel/events", listEvent)
|
||||
channels.Get("/:channel/events/update", checkHasNewEvent)
|
||||
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)
|
||||
channels.Post("/:channel/calls", startCall)
|
||||
channels.Delete("/:channel/calls/ongoing", endCall)
|
||||
channels.Delete("/:channel/calls/ongoing/participant", kickParticipantInCall)
|
||||
channels.Post("/:channel/calls/ongoing/token", exchangeCallToken)
|
||||
}
|
||||
|
||||
api.Get("/whats-new", getWhatsNew)
|
||||
}
|
||||
}
|
79
pkg/internal/web/api/quick_actions_api.go
Normal file
79
pkg/internal/web/api/quick_actions_api.go
Normal file
@ -0,0 +1,79 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/web/exts"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/models"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// quickReply is a simplified API for replying to a message
|
||||
// It used in the iOS notification action and others
|
||||
// It did not support all the features of the message event
|
||||
// But it just works
|
||||
func quickReply(c *fiber.Ctx) error {
|
||||
replyTk := c.Query("replyToken")
|
||||
if len(replyTk) == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "reply token is required")
|
||||
}
|
||||
|
||||
claims, err := services.ParseReplyToken(replyTk)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("reply token is invaild: %v", err))
|
||||
}
|
||||
|
||||
channelId, _ := c.ParamsInt("channelId", 0)
|
||||
eventId, _ := c.ParamsInt("eventId", 0)
|
||||
|
||||
if claims.EventID != uint(eventId) {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "reply token is invaild, event id mismatch")
|
||||
}
|
||||
|
||||
var data struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
Body models.EventMessageBody `json:"body"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
} else {
|
||||
data.Body.QuoteEventID = lo.ToPtr(uint(eventId))
|
||||
}
|
||||
|
||||
data.Body.Text = strings.TrimSpace(data.Body.Text)
|
||||
if len(data.Body.Text) == 0 && len(data.Body.Attachments) == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "empty message was not allowed")
|
||||
}
|
||||
|
||||
channel, member, err := services.GetChannelIdentityWithID(uint(channelId), claims.UserID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("channel / member not found: %v", err.Error()))
|
||||
}
|
||||
|
||||
var parsed map[string]any
|
||||
raw, _ := jsoniter.Marshal(data.Body)
|
||||
_ = jsoniter.Unmarshal(raw, &parsed)
|
||||
|
||||
event, err := services.NewEvent(models.Event{
|
||||
Uuid: uuid.NewString(),
|
||||
Body: parsed,
|
||||
Type: data.Type,
|
||||
Sender: member,
|
||||
Channel: channel,
|
||||
QuoteEventID: data.Body.QuoteEventID,
|
||||
RelatedEventID: data.Body.RelatedEventID,
|
||||
ChannelID: channel.ID,
|
||||
SenderID: member.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(event)
|
||||
}
|
23
pkg/internal/web/api/realms_api.go
Normal file
23
pkg/internal/web/api/realms_api.go
Normal file
@ -0,0 +1,23 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/gap"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func realmMiddleware(c *fiber.Ctx) error {
|
||||
realmAlias := c.Params("realm")
|
||||
if len(realmAlias) > 0 && realmAlias != "global" {
|
||||
realm, err := authkit.GetRealmByAlias(gap.Nx, 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()
|
||||
}
|
31
pkg/internal/web/api/whats_new_api.go
Normal file
31
pkg/internal/web/api/whats_new_api.go
Normal file
@ -0,0 +1,31 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
|
||||
authm "git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/database"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func getWhatsNew(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(authm.Account)
|
||||
|
||||
var result []struct {
|
||||
ChannelID uint `json:"channel_id"`
|
||||
UnreadMessageCount int `json:"count"`
|
||||
}
|
||||
if err := database.C.Table("channel_members cm").
|
||||
Select("cm.channel_id, COUNT(m.id) AS unread_message_count").
|
||||
Joins("JOIN events m ON m.channel_id = cm.channel_id").
|
||||
Where("m.id > cm.reading_anchor AND cm.account_id = ?", user.ID).
|
||||
Group("cm.channel_id").
|
||||
Scan(&result).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(result)
|
||||
}
|
22
pkg/internal/web/exts/utils.go
Normal file
22
pkg/internal/web/exts/utils.go
Normal file
@ -0,0 +1,22 @@
|
||||
package exts
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
var validation = validator.New(validator.WithRequiredStructEnabled())
|
||||
|
||||
func BindAndValidate(c *fiber.Ctx, out any) error {
|
||||
if err := c.BodyParser(out); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else if err := validation.Struct(out); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateStruct(in any) error {
|
||||
return validation.Struct(in)
|
||||
}
|
75
pkg/internal/web/server.go
Normal file
75
pkg/internal/web/server.go
Normal file
@ -0,0 +1,75 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit"
|
||||
|
||||
"git.solsynth.dev/hypernet/messaging/pkg/internal/web/api"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/idempotency"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var IReader *sec.InternalTokenReader
|
||||
|
||||
type App struct {
|
||||
app *fiber.App
|
||||
}
|
||||
|
||||
func NewServer() *App {
|
||||
app := fiber.New(fiber.Config{
|
||||
DisableStartupMessage: true,
|
||||
EnableIPValidation: true,
|
||||
ServerHeader: "Hypernet.Messaging",
|
||||
AppName: "Hypernet.Messaging",
|
||||
ProxyHeader: fiber.HeaderXForwardedFor,
|
||||
JSONEncoder: jsoniter.ConfigCompatibleWithStandardLibrary.Marshal,
|
||||
JSONDecoder: jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal,
|
||||
BodyLimit: 50 * 1024 * 1024,
|
||||
ReadBufferSize: 5 * 1024 * 1024, // 5MB for large JWT
|
||||
EnablePrintRoutes: viper.GetBool("debug.print_routes"),
|
||||
})
|
||||
|
||||
app.Use(idempotency.New())
|
||||
app.Use(cors.New(cors.Config{
|
||||
AllowCredentials: true,
|
||||
AllowMethods: strings.Join([]string{
|
||||
fiber.MethodGet,
|
||||
fiber.MethodPost,
|
||||
fiber.MethodHead,
|
||||
fiber.MethodOptions,
|
||||
fiber.MethodPut,
|
||||
fiber.MethodDelete,
|
||||
fiber.MethodPatch,
|
||||
}, ","),
|
||||
AllowOriginsFunc: func(origin string) bool {
|
||||
return true
|
||||
},
|
||||
}))
|
||||
|
||||
app.Use(logger.New(logger.Config{
|
||||
Format: "${status} | ${latency} | ${method} ${path}\n",
|
||||
Output: log.Logger,
|
||||
}))
|
||||
|
||||
app.Use(sec.ContextMiddleware(IReader))
|
||||
app.Use(authkit.ParseAccountMiddleware)
|
||||
|
||||
api.MapAPIs(app, "/api")
|
||||
|
||||
return &App{
|
||||
app: app,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *App) Listen() {
|
||||
if err := v.app.Listen(viper.GetString("bind")); err != nil {
|
||||
log.Fatal().Err(err).Msg("An error occurred when starting http...")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user