mirror of
https://github.com/hexolan/panels.git
synced 2026-03-26 12:40:21 +00:00
init gateway-service
This commit is contained in:
41
services/gateway-service/internal/api/api.go
Normal file
41
services/gateway-service/internal/api/api.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
|
||||
"github.com/hexolan/panels/gateway-service/internal"
|
||||
"github.com/hexolan/panels/gateway-service/internal/api/handlers"
|
||||
)
|
||||
|
||||
func NewAPIApp(cfg internal.Config) *fiber.App {
|
||||
app := fiber.New(fiber.Config{
|
||||
AppName: "Panels REST Gateway",
|
||||
ErrorHandler: handlers.ErrorHandler,
|
||||
|
||||
// Swap out the JSON encoder for faster marshaling
|
||||
JSONEncoder: json.Marshal,
|
||||
JSONDecoder: json.Unmarshal,
|
||||
})
|
||||
|
||||
// Middleware
|
||||
handlers.NewAuthMiddleware(cfg)
|
||||
app.Use(cors.New())
|
||||
app.Use(logger.New())
|
||||
|
||||
// Register the routes
|
||||
RegisterRoutes(app)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func ServeAPIApp(app *fiber.App) {
|
||||
err := app.Listen(":3000")
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to serve API: %v", err))
|
||||
}
|
||||
}
|
||||
59
services/gateway-service/internal/api/handlers/auth.go
Normal file
59
services/gateway-service/internal/api/handlers/auth.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/keyauth"
|
||||
|
||||
"github.com/hexolan/panels/gateway-service/internal"
|
||||
)
|
||||
|
||||
var AuthMiddleware fiber.Handler
|
||||
|
||||
type TokenClaims struct {
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type tokenValidator struct {
|
||||
pubKey *rsa.PublicKey
|
||||
}
|
||||
|
||||
func NewAuthMiddleware(cfg internal.Config) {
|
||||
tokenValidator := tokenValidator{pubKey: cfg.JWTPubKey}
|
||||
AuthMiddleware = keyauth.New(keyauth.Config{
|
||||
AuthScheme: "Bearer",
|
||||
Validator: tokenValidator.ValidateToken,
|
||||
})
|
||||
}
|
||||
|
||||
func GetTokenClaims(c *fiber.Ctx) (TokenClaims, error) {
|
||||
var tokenClaims TokenClaims
|
||||
tokenClaims, ok := c.Locals("tokenClaims").(TokenClaims)
|
||||
if !ok {
|
||||
return TokenClaims{}, fiber.NewError(fiber.StatusUnauthorized, "unable to access token claims")
|
||||
}
|
||||
return tokenClaims, nil
|
||||
}
|
||||
|
||||
func (tv tokenValidator) validateToken(token *jwt.Token) (interface{}, error) {
|
||||
// Ensure token is signed with RSA
|
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||
return nil, keyauth.ErrMissingOrMalformedAPIKey
|
||||
}
|
||||
|
||||
// Validate token with public key
|
||||
return tv.pubKey, nil
|
||||
}
|
||||
|
||||
func (tv tokenValidator) ValidateToken(c *fiber.Ctx, userToken string) (bool, error) {
|
||||
claims := TokenClaims{}
|
||||
_, err := jwt.ParseWithClaims(userToken, &claims, tv.validateToken)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
c.Locals("tokenClaims", claims)
|
||||
return true, nil
|
||||
}
|
||||
55
services/gateway-service/internal/api/handlers/error.go
Normal file
55
services/gateway-service/internal/api/handlers/error.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func ErrorHandler(c *fiber.Ctx, err error) error {
|
||||
c.Set(fiber.HeaderContentType, fiber.MIMEApplicationJSONCharsetUTF8)
|
||||
code := fiber.StatusInternalServerError
|
||||
msg := err.Error()
|
||||
|
||||
// Retrieval of codes from fiber.Errors
|
||||
var e *fiber.Error
|
||||
if errors.As(err, &e) {
|
||||
code = e.Code
|
||||
} else {
|
||||
// Retrival of codes from gRPC errors.
|
||||
status, ok := status.FromError(err)
|
||||
if ok {
|
||||
msg = status.Message()
|
||||
|
||||
switch status.Code() {
|
||||
case codes.NotFound:
|
||||
code = fiber.StatusNotFound
|
||||
case codes.InvalidArgument:
|
||||
code = fiber.StatusUnprocessableEntity
|
||||
case codes.AlreadyExists:
|
||||
code = fiber.StatusConflict
|
||||
case codes.PermissionDenied:
|
||||
code = fiber.StatusForbidden
|
||||
case codes.Unauthenticated:
|
||||
code = fiber.StatusUnauthorized
|
||||
case codes.Internal:
|
||||
code = fiber.StatusInternalServerError
|
||||
case codes.Unavailable:
|
||||
code = fiber.StatusBadGateway
|
||||
msg = "Service unavaliable for request."
|
||||
default:
|
||||
code = fiber.StatusInternalServerError
|
||||
msg = "Something went wrong."
|
||||
log.Error(err)
|
||||
}
|
||||
} else {
|
||||
msg = "Something unexpected went wrong."
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
return c.Status(code).JSON(fiber.Map{"status": "failure", "msg": msg})
|
||||
}
|
||||
66
services/gateway-service/internal/api/router.go
Normal file
66
services/gateway-service/internal/api/router.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/hexolan/panels/gateway-service/internal/api/v1"
|
||||
"github.com/hexolan/panels/gateway-service/internal/api/handlers"
|
||||
)
|
||||
|
||||
func RegisterRoutes(app *fiber.App) {
|
||||
apiV1 := app.Group("/v1")
|
||||
panelV1 := apiV1.Group("/panels")
|
||||
postV1 := apiV1.Group("/posts")
|
||||
userV1 := apiV1.Group("/users")
|
||||
authV1 := apiV1.Group("/auth")
|
||||
commentV1 := postV1.Group("/:post_id/comments")
|
||||
|
||||
// Panel Service Routes
|
||||
panelV1.Post("/", handlers.AuthMiddleware, v1.CreatePanel)
|
||||
|
||||
panelV1.Get("/id/:id", v1.GetPanelById)
|
||||
panelV1.Patch("/id/:id", handlers.AuthMiddleware, v1.UpdatePanelById)
|
||||
panelV1.Delete("/id/:id", handlers.AuthMiddleware, v1.DeletePanelById)
|
||||
|
||||
panelV1.Get("/name/:name", v1.GetPanelByName)
|
||||
panelV1.Patch("/name/:name", handlers.AuthMiddleware, v1.UpdatePanelByName)
|
||||
panelV1.Delete("/name/:name", handlers.AuthMiddleware, v1.DeletePanelByName)
|
||||
|
||||
// Post Service Routes
|
||||
postV1.Get("/feed", v1.GetFeedPosts)
|
||||
postV1.Patch("/:id", handlers.AuthMiddleware, v1.UpdatePost)
|
||||
postV1.Delete("/:id", handlers.AuthMiddleware, v1.DeletePost)
|
||||
|
||||
userV1.Get("/id/:user_id/posts", v1.GetUserPostsFromId)
|
||||
userV1.Get("/username/:username/posts", v1.GetUserPostsFromUsername)
|
||||
|
||||
panelV1.Get("/id/:panel_id/posts", v1.GetPanelPostsFromId)
|
||||
panelV1.Get("/name/:panel_name/posts", v1.GetPanelPostsFromName)
|
||||
|
||||
panelV1.Get("/id/:panel_id/posts/:id", v1.GetPanelPostFromId)
|
||||
panelV1.Get("/name/:panel_name/posts/:id", v1.GetPanelPostFromName)
|
||||
|
||||
panelV1.Post("/id/:panel_id", handlers.AuthMiddleware, v1.CreatePanelPostFromId)
|
||||
panelV1.Post("/name/:panel_name", handlers.AuthMiddleware, v1.CreatePanelPostFromName)
|
||||
|
||||
// User Service Routes
|
||||
userV1.Post("/", v1.UserSignup)
|
||||
|
||||
userV1.Get("/id/:id", v1.GetUserById)
|
||||
userV1.Delete("/id/:id", handlers.AuthMiddleware, v1.DeleteUserById)
|
||||
|
||||
userV1.Get("/username/:username", v1.GetUserByUsername)
|
||||
userV1.Delete("/username/:username", handlers.AuthMiddleware, v1.DeleteUserByUsername)
|
||||
|
||||
userV1.Get("/me", handlers.AuthMiddleware, v1.GetCurrentUser)
|
||||
userV1.Delete("/me", handlers.AuthMiddleware, v1.DeleteCurrentUser)
|
||||
|
||||
// Auth Service Routes
|
||||
authV1.Post("/login", v1.LoginWithPassword)
|
||||
|
||||
// Comment Service Routes
|
||||
commentV1.Get("/", v1.GetPostComments)
|
||||
commentV1.Post("/", handlers.AuthMiddleware, v1.CreateComment)
|
||||
commentV1.Patch("/:id", handlers.AuthMiddleware, v1.UpdateComment)
|
||||
commentV1.Delete("/:id", handlers.AuthMiddleware, v1.DeleteComment)
|
||||
}
|
||||
70
services/gateway-service/internal/api/v1/auth.go
Normal file
70
services/gateway-service/internal/api/v1/auth.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"time"
|
||||
"context"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/hexolan/panels/gateway-service/internal/rpc"
|
||||
"github.com/hexolan/panels/gateway-service/internal/rpc/authv1"
|
||||
)
|
||||
|
||||
type userLoginForm struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func setAuthMethod(userId string, password string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
_, err := rpc.Svcs.GetAuthSvc().SetPasswordAuth(
|
||||
ctx,
|
||||
&authv1.SetPasswordAuthMethod{
|
||||
UserId: userId,
|
||||
Password: password,
|
||||
},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func authWithPassword(userId string, password string) (*authv1.AuthToken, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
token, err := rpc.Svcs.GetAuthSvc().AuthWithPassword(
|
||||
ctx,
|
||||
&authv1.PasswordAuthRequest{
|
||||
UserId: userId,
|
||||
Password: password,
|
||||
},
|
||||
)
|
||||
return token, err
|
||||
}
|
||||
|
||||
func LoginWithPassword(c *fiber.Ctx) error {
|
||||
// Parse the body data
|
||||
form := new(userLoginForm)
|
||||
if err := c.BodyParser(form); err != nil {
|
||||
fiber.NewError(fiber.StatusBadRequest, "malformed request")
|
||||
}
|
||||
|
||||
// username -> user ID
|
||||
user, err := getUserByUsername(form.Username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// attempt to auth
|
||||
token, err := authWithPassword(user.Id, form.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"status": "success",
|
||||
"data": fiber.Map{
|
||||
"user": user,
|
||||
"token": token,
|
||||
},
|
||||
})
|
||||
}
|
||||
143
services/gateway-service/internal/api/v1/comment.go
Normal file
143
services/gateway-service/internal/api/v1/comment.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/hexolan/panels/gateway-service/internal/api/handlers"
|
||||
"github.com/hexolan/panels/gateway-service/internal/rpc"
|
||||
"github.com/hexolan/panels/gateway-service/internal/rpc/commentv1"
|
||||
)
|
||||
|
||||
func getComment(id string) (*commentv1.Comment, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
comment, err := rpc.Svcs.GetCommentSvc().GetComment(
|
||||
ctx,
|
||||
&commentv1.GetCommentRequest{Id: id},
|
||||
)
|
||||
|
||||
return comment, err
|
||||
}
|
||||
|
||||
func GetPostComments(c *fiber.Ctx) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
comments, err := rpc.Svcs.GetCommentSvc().GetPostComments(
|
||||
ctx,
|
||||
&commentv1.GetPostCommentsRequest{PostId: c.Params("post_id")},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": comments})
|
||||
}
|
||||
|
||||
func UpdateComment(c *fiber.Ctx) error {
|
||||
// check if user has permissions to update the comment
|
||||
currentUser, err := getCurrentUser(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
comment, err := getComment(c.Params("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if (comment.AuthorId != currentUser.Id) {
|
||||
return fiber.NewError(fiber.StatusForbidden, "no permissions to update that comment")
|
||||
}
|
||||
|
||||
// Parse the body data
|
||||
updatedComment := new(commentv1.CommentMutable)
|
||||
if err := c.BodyParser(updatedComment); err != nil {
|
||||
fiber.NewError(fiber.StatusBadRequest, "malformed request")
|
||||
}
|
||||
|
||||
// Update the comment
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
comment, err = rpc.Svcs.GetCommentSvc().UpdateComment(
|
||||
ctx,
|
||||
&commentv1.UpdateCommentRequest{
|
||||
Id: c.Params("id"),
|
||||
Data: updatedComment,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": comment})
|
||||
}
|
||||
|
||||
func DeleteComment(c *fiber.Ctx) error {
|
||||
// check if user has permissions to delete the comment
|
||||
currentUser, err := getCurrentUser(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
comment, err := getComment(c.Params("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if (comment.AuthorId != currentUser.Id && !currentUser.IsAdmin) {
|
||||
return fiber.NewError(fiber.StatusForbidden, "no permissions to delete that comment")
|
||||
}
|
||||
|
||||
// Delete the comment
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
_, err = rpc.Svcs.GetCommentSvc().DeleteComment(
|
||||
ctx,
|
||||
&commentv1.DeleteCommentRequest{Id: c.Params("id")},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "msg": "comment deleted"})
|
||||
}
|
||||
|
||||
func CreateComment(c *fiber.Ctx) error {
|
||||
// Parse the body data
|
||||
newComment := new(commentv1.CommentMutable)
|
||||
if err := c.BodyParser(newComment); err != nil {
|
||||
fiber.NewError(fiber.StatusBadRequest, "malformed request")
|
||||
}
|
||||
|
||||
// check post is real
|
||||
post, err := getPostById(c.Params("post_id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// access token claims
|
||||
tokenClaims, err := handlers.GetTokenClaims(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the comment
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
comment, err := rpc.Svcs.GetCommentSvc().CreateComment(
|
||||
ctx,
|
||||
&commentv1.CreateCommentRequest{
|
||||
PostId: post.Id,
|
||||
AuthorId: tokenClaims.Subject,
|
||||
Data: newComment,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": comment})
|
||||
}
|
||||
186
services/gateway-service/internal/api/v1/panel.go
Normal file
186
services/gateway-service/internal/api/v1/panel.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"time"
|
||||
"context"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/hexolan/panels/gateway-service/internal/rpc"
|
||||
"github.com/hexolan/panels/gateway-service/internal/rpc/panelv1"
|
||||
)
|
||||
|
||||
func getPanelById(id string) (*panelv1.Panel, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
return rpc.Svcs.GetPanelSvc().GetPanel(
|
||||
ctx,
|
||||
&panelv1.GetPanelByIdRequest{Id: id},
|
||||
)
|
||||
}
|
||||
|
||||
func getPanelIDFromName(name string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
panel, err := rpc.Svcs.GetPanelSvc().GetPanelByName(
|
||||
ctx,
|
||||
&panelv1.GetPanelByNameRequest{Name: name},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return panel.GetId(), nil
|
||||
}
|
||||
|
||||
func GetPanelById(c *fiber.Ctx) error {
|
||||
panel, err := getPanelById(c.Params("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": panel})
|
||||
}
|
||||
|
||||
func GetPanelByName(c *fiber.Ctx) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
panel, err := rpc.Svcs.GetPanelSvc().GetPanelByName(
|
||||
ctx,
|
||||
&panelv1.GetPanelByNameRequest{Name: c.Params("name")},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": panel})
|
||||
}
|
||||
|
||||
func UpdatePanelById(c *fiber.Ctx) error {
|
||||
// check user can update panels
|
||||
currentUser, err := getCurrentUser(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !currentUser.IsAdmin {
|
||||
return fiber.NewError(fiber.StatusForbidden, "no permissions to update that panel")
|
||||
}
|
||||
|
||||
// update the panel
|
||||
patchData := new(panelv1.PanelMutable)
|
||||
if err := c.BodyParser(patchData); err != nil {
|
||||
fiber.NewError(fiber.StatusBadRequest, "malformed request")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
panel, err := rpc.Svcs.GetPanelSvc().UpdatePanel(
|
||||
ctx,
|
||||
&panelv1.UpdatePanelByIdRequest{Id: c.Params("id"), Data: patchData},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": panel})
|
||||
}
|
||||
|
||||
func UpdatePanelByName(c *fiber.Ctx) error {
|
||||
// check user can update panels
|
||||
currentUser, err := getCurrentUser(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !currentUser.IsAdmin {
|
||||
return fiber.NewError(fiber.StatusForbidden, "no permissions to update that panel")
|
||||
}
|
||||
|
||||
// update the panel
|
||||
patchData := new(panelv1.PanelMutable)
|
||||
if err := c.BodyParser(patchData); err != nil {
|
||||
fiber.NewError(fiber.StatusBadRequest, "malformed request")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
panel, err := rpc.Svcs.GetPanelSvc().UpdatePanelByName(
|
||||
ctx,
|
||||
&panelv1.UpdatePanelByNameRequest{Name: c.Params("name"), Data: patchData},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": panel})
|
||||
}
|
||||
|
||||
func DeletePanelById(c *fiber.Ctx) error {
|
||||
// check user can delete panels
|
||||
currentUser, err := getCurrentUser(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !currentUser.IsAdmin {
|
||||
return fiber.NewError(fiber.StatusForbidden, "no permissions to delete that panel")
|
||||
}
|
||||
|
||||
// delete the panel
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
_, err = rpc.Svcs.GetPanelSvc().DeletePanel(
|
||||
ctx,
|
||||
&panelv1.DeletePanelByIdRequest{Id: c.Params("id")},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "msg": "panel deleted"})
|
||||
}
|
||||
|
||||
func DeletePanelByName(c *fiber.Ctx) error {
|
||||
// check user can delete panels
|
||||
currentUser, err := getCurrentUser(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !currentUser.IsAdmin {
|
||||
return fiber.NewError(fiber.StatusForbidden, "no permissions to update that panel")
|
||||
}
|
||||
|
||||
// delete the panel
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
_, err = rpc.Svcs.GetPanelSvc().DeletePanelByName(
|
||||
ctx,
|
||||
&panelv1.DeletePanelByNameRequest{Name: c.Params("name")},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "msg": "panel deleted"})
|
||||
}
|
||||
|
||||
func CreatePanel(c *fiber.Ctx) error {
|
||||
newPanel := new(panelv1.PanelMutable)
|
||||
if err := c.BodyParser(newPanel); err != nil {
|
||||
fiber.NewError(fiber.StatusBadRequest, "malformed request")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
panel, err := rpc.Svcs.GetPanelSvc().CreatePanel(
|
||||
ctx,
|
||||
&panelv1.CreatePanelRequest{Data: newPanel},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": panel})
|
||||
}
|
||||
284
services/gateway-service/internal/api/v1/post.go
Normal file
284
services/gateway-service/internal/api/v1/post.go
Normal file
@@ -0,0 +1,284 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"time"
|
||||
"context"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/hexolan/panels/gateway-service/internal/rpc"
|
||||
"github.com/hexolan/panels/gateway-service/internal/rpc/postv1"
|
||||
"github.com/hexolan/panels/gateway-service/internal/api/handlers"
|
||||
)
|
||||
|
||||
func getPostById(postId string) (*postv1.Post, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
return rpc.Svcs.GetPostSvc().GetPost(
|
||||
ctx,
|
||||
&postv1.GetPostRequest{Id: postId},
|
||||
)
|
||||
}
|
||||
|
||||
func GetFeedPosts(c *fiber.Ctx) error {
|
||||
// Make the request for feed posts
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
posts, err := rpc.Svcs.GetPostSvc().GetFeedPosts(
|
||||
ctx,
|
||||
&postv1.GetFeedPostsRequest{},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": posts})
|
||||
}
|
||||
|
||||
func GetPanelPostFromId(c *fiber.Ctx) error {
|
||||
// Make the request for the panel post
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
post, err := rpc.Svcs.GetPostSvc().GetPanelPost(
|
||||
ctx,
|
||||
&postv1.GetPanelPostRequest{Id: c.Params("id"), PanelId: c.Params("panel_id")},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": post})
|
||||
}
|
||||
|
||||
func GetPanelPostFromName(c *fiber.Ctx) error {
|
||||
// Get the panel ID from name.
|
||||
panelId, err := getPanelIDFromName(c.Params("panel_name"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make the request for the panel post
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
post, err := rpc.Svcs.GetPostSvc().GetPanelPost(
|
||||
ctx,
|
||||
&postv1.GetPanelPostRequest{Id: c.Params("id"), PanelId: panelId},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": post})
|
||||
}
|
||||
|
||||
func GetUserPostsFromId(c *fiber.Ctx) error {
|
||||
// Make the request for user posts
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
posts, err := rpc.Svcs.GetPostSvc().GetUserPosts(
|
||||
ctx,
|
||||
&postv1.GetUserPostsRequest{UserId: c.Params("user_id")},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": posts})
|
||||
}
|
||||
|
||||
func GetUserPostsFromUsername(c *fiber.Ctx) error {
|
||||
// Get the user ID from username.
|
||||
user, err := getUserByUsername(c.Params("username"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make the request for user posts
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
posts, err := rpc.Svcs.GetPostSvc().GetUserPosts(
|
||||
ctx,
|
||||
&postv1.GetUserPostsRequest{UserId: user.Id},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": posts})
|
||||
}
|
||||
|
||||
func GetPanelPostsFromId(c *fiber.Ctx) error {
|
||||
// Make the request for panel posts
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
posts, err := rpc.Svcs.GetPostSvc().GetPanelPosts(
|
||||
ctx,
|
||||
&postv1.GetPanelPostsRequest{PanelId: c.Params("panel_id")},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": posts})
|
||||
}
|
||||
|
||||
func GetPanelPostsFromName(c *fiber.Ctx) error {
|
||||
// Get the panel ID from name.
|
||||
panelId, err := getPanelIDFromName(c.Params("panel_name"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make the request for panel posts
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
posts, err := rpc.Svcs.GetPostSvc().GetPanelPosts(
|
||||
ctx,
|
||||
&postv1.GetPanelPostsRequest{PanelId: panelId},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": posts})
|
||||
}
|
||||
|
||||
func UpdatePost(c *fiber.Ctx) error {
|
||||
// check if user has permissions to update the post
|
||||
currentUser, err := getCurrentUser(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
post, err := getPostById(c.Params("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if (post.AuthorId != currentUser.Id) {
|
||||
return fiber.NewError(fiber.StatusForbidden, "no permissions to update that post")
|
||||
}
|
||||
|
||||
// form patch data for update request
|
||||
patchData := new(postv1.PostMutable)
|
||||
if err := c.BodyParser(patchData); err != nil {
|
||||
fiber.NewError(fiber.StatusBadRequest, "malformed request")
|
||||
}
|
||||
|
||||
// update the post
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
post, err = rpc.Svcs.GetPostSvc().UpdatePost(
|
||||
ctx,
|
||||
&postv1.UpdatePostRequest{Id: c.Params("id"), Data: patchData},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": post})
|
||||
}
|
||||
|
||||
func DeletePost(c *fiber.Ctx) error {
|
||||
// check if user has permissions to delete the post
|
||||
currentUser, err := getCurrentUser(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
post, err := getPostById(c.Params("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if (post.AuthorId != currentUser.Id && !currentUser.IsAdmin) {
|
||||
return fiber.NewError(fiber.StatusForbidden, "no permissions to delete that post")
|
||||
}
|
||||
|
||||
// delete the post
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
_, err = rpc.Svcs.GetPostSvc().DeletePost(
|
||||
ctx,
|
||||
&postv1.DeletePostRequest{Id: c.Params("id")},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "msg": "post deleted"})
|
||||
}
|
||||
|
||||
func CreatePanelPostFromId(c *fiber.Ctx) error {
|
||||
// Parse the body data
|
||||
newPost := new(postv1.PostMutable)
|
||||
if err := c.BodyParser(newPost); err != nil {
|
||||
fiber.NewError(fiber.StatusBadRequest, "malformed request")
|
||||
}
|
||||
|
||||
// Get the panel ID from provided panel name
|
||||
panel, err := getPanelById(c.Params("panel_id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// access token claims
|
||||
tokenClaims, err := handlers.GetTokenClaims(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make rpc call
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
post, err := rpc.Svcs.GetPostSvc().CreatePost(
|
||||
ctx,
|
||||
&postv1.CreatePostRequest{
|
||||
PanelId: panel.Id,
|
||||
UserId: tokenClaims.Subject,
|
||||
Data: newPost,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": post})
|
||||
}
|
||||
|
||||
func CreatePanelPostFromName(c *fiber.Ctx) error {
|
||||
// Parse the body data
|
||||
newPost := new(postv1.PostMutable)
|
||||
if err := c.BodyParser(newPost); err != nil {
|
||||
fiber.NewError(fiber.StatusBadRequest, "malformed request")
|
||||
}
|
||||
|
||||
// Get the panel ID from provided panel name
|
||||
panelId, err := getPanelIDFromName(c.Params("panel_name"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// access token claims
|
||||
tokenClaims, err := handlers.GetTokenClaims(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make rpc call
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
post, err := rpc.Svcs.GetPostSvc().CreatePost(
|
||||
ctx,
|
||||
&postv1.CreatePostRequest{
|
||||
PanelId: panelId,
|
||||
UserId: tokenClaims.Subject,
|
||||
Data: newPost,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": post})
|
||||
}
|
||||
196
services/gateway-service/internal/api/v1/user.go
Normal file
196
services/gateway-service/internal/api/v1/user.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"time"
|
||||
"context"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/hexolan/panels/gateway-service/internal/rpc"
|
||||
"github.com/hexolan/panels/gateway-service/internal/rpc/userv1"
|
||||
"github.com/hexolan/panels/gateway-service/internal/api/handlers"
|
||||
)
|
||||
|
||||
type userSignupForm struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func getCurrentUser(c *fiber.Ctx) (*userv1.User, error) {
|
||||
// access token claims
|
||||
tokenClaims, err := handlers.GetTokenClaims(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// fetch current user
|
||||
return getUserById(tokenClaims.Subject)
|
||||
}
|
||||
|
||||
func getUserById(id string) (*userv1.User, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
user, err := rpc.Svcs.GetUserSvc().GetUser(
|
||||
ctx,
|
||||
&userv1.GetUserByIdRequest{Id: id},
|
||||
)
|
||||
|
||||
return user, err
|
||||
}
|
||||
|
||||
func getUserByUsername(username string) (*userv1.User, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
user, err := rpc.Svcs.GetUserSvc().GetUserByName(
|
||||
ctx,
|
||||
&userv1.GetUserByNameRequest{Username: username},
|
||||
)
|
||||
|
||||
return user, err
|
||||
}
|
||||
|
||||
func GetUserById(c *fiber.Ctx) error {
|
||||
user, err := getUserById(c.Params("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": user})
|
||||
}
|
||||
|
||||
func GetUserByUsername(c *fiber.Ctx) error {
|
||||
user, err := getUserByUsername(c.Params("username"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": user})
|
||||
}
|
||||
|
||||
func GetCurrentUser(c *fiber.Ctx) error {
|
||||
user, err := getCurrentUser(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "data": user})
|
||||
}
|
||||
|
||||
func DeleteUserById(c *fiber.Ctx) error {
|
||||
// get current user info
|
||||
currentUser, err := getCurrentUser(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check current user has permission to delete user
|
||||
if currentUser.Id != c.Params("id") && !currentUser.IsAdmin {
|
||||
return fiber.NewError(fiber.StatusForbidden, "no permissions to delete that user")
|
||||
}
|
||||
|
||||
// delete the user
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
_, err = rpc.Svcs.GetUserSvc().DeleteUser(
|
||||
ctx,
|
||||
&userv1.DeleteUserByIdRequest{Id: c.Params("id")},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "msg": "user deleted"})
|
||||
}
|
||||
|
||||
func DeleteUserByUsername(c *fiber.Ctx) error {
|
||||
// get current user info
|
||||
currentUser, err := getCurrentUser(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check current user has permission to delete user
|
||||
if currentUser.Id != c.Params("id") && !currentUser.IsAdmin {
|
||||
return fiber.NewError(fiber.StatusForbidden, "no permissions to delete that user")
|
||||
}
|
||||
|
||||
// delete the user
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
_, err = rpc.Svcs.GetUserSvc().DeleteUserByName(
|
||||
ctx,
|
||||
&userv1.DeleteUserByNameRequest{Username: c.Params("username")},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "msg": "user deleted"})
|
||||
}
|
||||
|
||||
func DeleteCurrentUser(c *fiber.Ctx) error {
|
||||
// View access token claims
|
||||
tokenClaims, err := handlers.GetTokenClaims(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// RPC call
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
_, err = rpc.Svcs.GetUserSvc().DeleteUser(
|
||||
ctx,
|
||||
&userv1.DeleteUserByIdRequest{Id: tokenClaims.Subject},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "success", "msg": "user deleted"})
|
||||
}
|
||||
|
||||
func UserSignup(c *fiber.Ctx) error {
|
||||
// Parse the body data
|
||||
form := new(userSignupForm)
|
||||
if err := c.BodyParser(form); err != nil {
|
||||
fiber.NewError(fiber.StatusBadRequest, "malformed request")
|
||||
}
|
||||
|
||||
// Attempt to create the user
|
||||
// todo: defer this logic away from gateway-service in future (potentially into seperate registration-service)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
user, err := rpc.Svcs.GetUserSvc().CreateUser(
|
||||
ctx,
|
||||
&userv1.CreateUserRequest{
|
||||
Data: &userv1.UserMutable{
|
||||
Username: &form.Username,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Attempt to set password auth method for the user
|
||||
err = setAuthMethod(user.Id, form.Password)
|
||||
if err != nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
_, _ = rpc.Svcs.GetUserSvc().DeleteUser(
|
||||
ctx,
|
||||
&userv1.DeleteUserByIdRequest{Id: user.Id},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// Signup success - attempt to get auth token
|
||||
token, _ := authWithPassword(user.Id, form.Password)
|
||||
return c.JSON(fiber.Map{
|
||||
"status": "success",
|
||||
"data": fiber.Map{
|
||||
"user": user,
|
||||
"token": token,
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user