🎉 Initial Commit
This commit is contained in:
61
pkg/server/attachments_api.go
Normal file
61
pkg/server/attachments_api.go
Normal file
@ -0,0 +1,61 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/models"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func readAttachment(c *fiber.Ctx) error {
|
||||
id := c.Params("fileId")
|
||||
basepath := viper.GetString("content")
|
||||
|
||||
return c.SendFile(filepath.Join(basepath, id))
|
||||
}
|
||||
|
||||
func uploadAttachment(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
hashcode := c.FormValue("hashcode")
|
||||
if len(hashcode) != 64 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "please provide a SHA256 hashcode, length should be 64 characters")
|
||||
}
|
||||
file, err := c.FormFile("attachment")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attachment, err := services.NewAttachment(user, file, hashcode)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
if err := c.SaveFile(file, attachment.GetStoragePath()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"info": attachment,
|
||||
"url": attachment.GetAccessPath(),
|
||||
})
|
||||
}
|
||||
|
||||
func deleteAttachment(c *fiber.Ctx) error {
|
||||
id, _ := c.ParamsInt("id", 0)
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
attachment, err := services.GetAttachmentByID(uint(id))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else if attachment.AuthorID != user.ID {
|
||||
return fiber.NewError(fiber.StatusNotFound, "record not created by you")
|
||||
}
|
||||
|
||||
if err := services.DeleteAttachment(attachment); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
52
pkg/server/auth.go
Normal file
52
pkg/server/auth.go
Normal file
@ -0,0 +1,52 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/security"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func authMiddleware(c *fiber.Ctx) error {
|
||||
var token string
|
||||
if cookie := c.Cookies(security.CookieAccessKey); len(cookie) > 0 {
|
||||
token = cookie
|
||||
}
|
||||
if header := c.Get(fiber.HeaderAuthorization); len(header) > 0 {
|
||||
tk := strings.Replace(header, "Bearer", "", 1)
|
||||
token = strings.TrimSpace(tk)
|
||||
}
|
||||
|
||||
c.Locals("token", token)
|
||||
|
||||
if err := authFunc(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
func authFunc(c *fiber.Ctx, overrides ...string) error {
|
||||
var token string
|
||||
if len(overrides) > 0 {
|
||||
token = overrides[0]
|
||||
} else {
|
||||
if tk, ok := c.Locals("token").(string); !ok {
|
||||
return fiber.NewError(fiber.StatusUnauthorized)
|
||||
} else {
|
||||
token = tk
|
||||
}
|
||||
}
|
||||
|
||||
rtk := c.Cookies(security.CookieRefreshKey)
|
||||
if user, atk, rtk, err := services.Authenticate(token, rtk); err == nil {
|
||||
if atk != token {
|
||||
security.SetJwtCookieSet(c, atk, rtk)
|
||||
}
|
||||
c.Locals("principal", user)
|
||||
return nil
|
||||
} else {
|
||||
return fiber.NewError(fiber.StatusUnauthorized, err.Error())
|
||||
}
|
||||
}
|
86
pkg/server/channel_members_api.go
Normal file
86
pkg/server/channel_members_api.go
Normal file
@ -0,0 +1,86 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/database"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/models"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func listChannelMembers(c *fiber.Ctx) error {
|
||||
channelId, _ := c.ParamsInt("channelId", 0)
|
||||
|
||||
if members, err := services.ListChannelMember(uint(channelId)); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
return c.JSON(members)
|
||||
}
|
||||
}
|
||||
|
||||
func inviteChannel(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
channelId, _ := c.ParamsInt("channelId", 0)
|
||||
|
||||
var data struct {
|
||||
AccountName string `json:"account_name" validate:"required"`
|
||||
}
|
||||
|
||||
if err := BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var channel models.Channel
|
||||
if err := database.C.Where(&models.Channel{
|
||||
BaseModel: models.BaseModel{ID: uint(channelId)},
|
||||
AccountID: user.ID,
|
||||
}).First(&channel).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
var account models.Account
|
||||
if err := database.C.Where(&models.Account{
|
||||
Name: data.AccountName,
|
||||
}).First(&account).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.InviteChannelMember(account, channel); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func kickChannel(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
channelId, _ := c.ParamsInt("channelId", 0)
|
||||
|
||||
var data struct {
|
||||
AccountName string `json:"account_name" validate:"required"`
|
||||
}
|
||||
|
||||
if err := BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var channel models.Channel
|
||||
if err := database.C.Where(&models.Channel{
|
||||
BaseModel: models.BaseModel{ID: uint(channelId)},
|
||||
AccountID: user.ID,
|
||||
}).First(&channel).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
var account models.Account
|
||||
if err := database.C.Where(&models.Account{
|
||||
Name: data.AccountName,
|
||||
}).First(&account).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.KickChannelMember(account, channel); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
123
pkg/server/channels_api.go
Normal file
123
pkg/server/channels_api.go
Normal file
@ -0,0 +1,123 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/database"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/models"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func getChannel(c *fiber.Ctx) error {
|
||||
id, _ := c.ParamsInt("channelId", 0)
|
||||
|
||||
var channel models.Channel
|
||||
if err := database.C.Where(&models.Channel{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
}).First(&channel).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(channel)
|
||||
}
|
||||
|
||||
func listChannel(c *fiber.Ctx) error {
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
channels, err := services.ListChannelIsAvailable(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)
|
||||
if user.PowerLevel < 10 {
|
||||
return fiber.NewError(fiber.StatusForbidden, "require power level 10 to create channels")
|
||||
}
|
||||
|
||||
var data struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
if err := BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
channel, err := services.NewChannel(user, data.Name, data.Description)
|
||||
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 {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
if err := BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var channel models.Channel
|
||||
if err := database.C.Where(&models.Channel{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AccountID: user.ID,
|
||||
}).First(&channel).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
channel, err := services.EditChannel(channel, data.Name, data.Description)
|
||||
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)
|
||||
|
||||
var channel models.Channel
|
||||
if err := database.C.Where(&models.Channel{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AccountID: user.ID,
|
||||
}).First(&channel).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.DeleteChannel(channel); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
101
pkg/server/startup.go
Normal file
101
pkg/server/startup.go
Normal file
@ -0,0 +1,101 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/views"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cache"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/filesystem"
|
||||
"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 A *fiber.App
|
||||
|
||||
func NewServer() {
|
||||
A = fiber.New(fiber.Config{
|
||||
DisableStartupMessage: true,
|
||||
EnableIPValidation: true,
|
||||
ServerHeader: "Hydrogen.Messaging",
|
||||
AppName: "Hydrogen.Messaging",
|
||||
ProxyHeader: fiber.HeaderXForwardedFor,
|
||||
JSONEncoder: jsoniter.ConfigCompatibleWithStandardLibrary.Marshal,
|
||||
JSONDecoder: jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal,
|
||||
BodyLimit: 50 * 1024 * 1024,
|
||||
EnablePrintRoutes: viper.GetBool("debug.print_routes"),
|
||||
})
|
||||
|
||||
A.Use(idempotency.New())
|
||||
A.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
|
||||
},
|
||||
}))
|
||||
|
||||
A.Use(logger.New(logger.Config{
|
||||
Format: "${status} | ${latency} | ${method} ${path}\n",
|
||||
Output: log.Logger,
|
||||
}))
|
||||
|
||||
A.Get("/.well-known", getMetadata)
|
||||
|
||||
api := A.Group("/api").Name("API")
|
||||
{
|
||||
api.Get("/users/me", authMiddleware, getUserinfo)
|
||||
api.Get("/users/:accountId", getOthersInfo)
|
||||
|
||||
api.Get("/attachments/o/:fileId", cache.New(cache.Config{
|
||||
Expiration: 365 * 24 * time.Hour,
|
||||
CacheControl: true,
|
||||
}), readAttachment)
|
||||
api.Post("/attachments", authMiddleware, uploadAttachment)
|
||||
api.Delete("/attachments/:id", authMiddleware, deleteAttachment)
|
||||
|
||||
channels := api.Group("/channels").Name("Channels API")
|
||||
{
|
||||
channels.Get("/", listChannel)
|
||||
channels.Get("/me", authMiddleware, listOwnedChannel)
|
||||
channels.Get("/me/available", authMiddleware, listAvailableChannel)
|
||||
channels.Get("/:channelId", getChannel)
|
||||
channels.Get("/:channelId/members", listChannelMembers)
|
||||
channels.Post("/", authMiddleware, createChannel)
|
||||
channels.Post("/:channelId/invite", authMiddleware, inviteChannel)
|
||||
channels.Post("/:channelId/kick", authMiddleware, kickChannel)
|
||||
channels.Put("/:channelId", authMiddleware, editChannel)
|
||||
channels.Delete("/:channelId", authMiddleware, deleteChannel)
|
||||
}
|
||||
}
|
||||
|
||||
A.Use("/", cache.New(cache.Config{
|
||||
Expiration: 24 * time.Hour,
|
||||
CacheControl: true,
|
||||
}), filesystem.New(filesystem.Config{
|
||||
Root: http.FS(views.FS),
|
||||
PathPrefix: "dist",
|
||||
Index: "index.html",
|
||||
NotFoundFile: "dist/index.html",
|
||||
}))
|
||||
}
|
||||
|
||||
func Listen() {
|
||||
if err := A.Listen(viper.GetString("bind")); err != nil {
|
||||
log.Fatal().Err(err).Msg("An error occurred when starting server...")
|
||||
}
|
||||
}
|
33
pkg/server/users_api.go
Normal file
33
pkg/server/users_api.go
Normal file
@ -0,0 +1,33 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/database"
|
||||
"git.solsynth.dev/hydrogen/messaging/pkg/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)
|
||||
}
|
18
pkg/server/utils.go
Normal file
18
pkg/server/utils.go
Normal file
@ -0,0 +1,18 @@
|
||||
package server
|
||||
|
||||
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
|
||||
}
|
16
pkg/server/well_known_api.go
Normal file
16
pkg/server/well_known_api.go
Normal file
@ -0,0 +1,16 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func getMetadata(c *fiber.Ctx) error {
|
||||
return c.JSON(fiber.Map{
|
||||
"name": viper.GetString("name"),
|
||||
"domain": viper.GetString("domain"),
|
||||
"components": fiber.Map{
|
||||
"identity": viper.GetString("identity.endpoint"),
|
||||
},
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user