Merge pull request #5 from Nekiiinkognito/feature/newRoutes

Milestone with back-end
This commit is contained in:
Maxim 2024-11-18 16:55:08 +03:00 committed by GitHub
commit f892faa180
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 390 additions and 107 deletions

View File

@ -0,0 +1,17 @@
package adminpolicies
import (
globalrules "enshi/ABAC/GlobalRules"
"enshi/ABAC/rules"
"github.com/gin-gonic/gin"
)
func AdminPolicies(c *gin.Context) (bool, []error) {
rulesToCheck := []rules.RuleFunction{
globalrules.AuthorizedRule,
globalrules.IsAdminRule,
}
return rules.CheckRules(c, rulesToCheck, rules.ALL_RULES_MUST_BE_COMPLETED)
}

View File

@ -9,7 +9,9 @@ import (
const ( const (
DELETE_POST = "delete_post" DELETE_POST = "delete_post"
DELETE_POST_BLOG = "delete_post_blog"
UPDATE_POST = "update_post" UPDATE_POST = "update_post"
UPDATE_POST_BLOG = "update_post_blog"
CREATE_POST = "create_post" CREATE_POST = "create_post"
GET_POST = "get_post" GET_POST = "get_post"
) )
@ -25,9 +27,15 @@ func PostsPolicies(c *gin.Context) (bool, []error) {
case DELETE_POST: case DELETE_POST:
return rules.CheckRule(c, postRules.DeleteRule) return rules.CheckRule(c, postRules.DeleteRule)
case DELETE_POST_BLOG:
return rules.CheckRule(c, postRules.DeletePostFromBlogRule)
case UPDATE_POST: case UPDATE_POST:
return rules.CheckRule(c, postRules.PostUpdateRule) return rules.CheckRule(c, postRules.PostUpdateRule)
case UPDATE_POST_BLOG:
return rules.CheckRule(c, postRules.UpdatePostBlogRule)
case GET_POST: case GET_POST:
return rules.CheckRule(c, postRules.PostReadRule) return rules.CheckRule(c, postRules.PostReadRule)

View File

@ -0,0 +1,24 @@
package postRules
import (
globalrules "enshi/ABAC/GlobalRules"
"enshi/ABAC/rules"
"github.com/gin-gonic/gin"
)
func DeletePostFromBlogRule(c *gin.Context) (bool, []error) {
rulesToCheck := []rules.RuleFunction{
globalrules.AuthorizedRule,
globalrules.IsOwnerOfThePostRule,
globalrules.IsOwnerOfTheBlogRule,
}
isAllowed, errors := rules.CheckRules(
c,
rulesToCheck,
RULES_NUMBER_TO_COMPLETE,
)
return isAllowed, errors
}

View File

@ -0,0 +1,25 @@
package postRules
import (
globalrules "enshi/ABAC/GlobalRules"
"enshi/ABAC/rules"
"github.com/gin-gonic/gin"
)
// Only user that own target post and blog can do that
func UpdatePostBlogRule(c *gin.Context) (bool, []error) {
rulesToCheck := []rules.RuleFunction{
globalrules.AuthorizedRule,
globalrules.IsOwnerOfThePostRule,
globalrules.IsOwnerOfTheBlogRule,
}
isAllowed, errors := rules.CheckRules(
c,
rulesToCheck,
rules.ALL_RULES_MUST_BE_COMPLETED,
)
return isAllowed, errors
}

View File

@ -11,6 +11,7 @@ import (
func PostUpdateRule(c *gin.Context) (bool, []error) { func PostUpdateRule(c *gin.Context) (bool, []error) {
rulesToCheck := []rules.RuleFunction{ rulesToCheck := []rules.RuleFunction{
globalrules.AuthorizedRule, globalrules.AuthorizedRule,
globalrules.IsOwnerOfThePostRule,
} }
isAllowed, errors := rules.CheckRules( isAllowed, errors := rules.CheckRules(

View File

@ -0,0 +1,28 @@
package rules
import (
rest_api_stuff "enshi/REST_API_stuff"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func ShouldAbortRequest(c *gin.Context, isAllowed bool, errors []error) bool {
var errorsMap = map[int]string{}
for i, error := range errors {
errorsMap[i] = error.Error()
}
if errors != nil {
c.IndentedJSON(http.StatusUnauthorized, errorsMap)
return true
}
if !isAllowed {
rest_api_stuff.UnauthorizedAnswer(c, fmt.Errorf("you have no permission"))
return true
}
return false
}

View File

@ -146,27 +146,38 @@ func (q *Queries) GetPostsByUserId(ctx context.Context, userID int64) ([]Post, e
return items, nil return items, nil
} }
const updatePostBlogId = `-- name: UpdatePostBlogId :exec
UPDATE public.posts
SET blog_id=$2, updated_at=CURRENT_TIMESTAMP
WHERE post_id = $1
RETURNING post_id, blog_id, user_id, title, content, created_at, updated_at
`
type UpdatePostBlogIdParams struct {
PostID int64 `json:"post_id"`
BlogID pgtype.Int8 `json:"blog_id"`
}
func (q *Queries) UpdatePostBlogId(ctx context.Context, arg UpdatePostBlogIdParams) error {
_, err := q.db.Exec(ctx, updatePostBlogId, arg.PostID, arg.BlogID)
return err
}
const updatePostByPostId = `-- name: UpdatePostByPostId :one const updatePostByPostId = `-- name: UpdatePostByPostId :one
UPDATE public.posts UPDATE public.posts
SET blog_id=$1, title=$2, "content"=$3, updated_at=CURRENT_TIMESTAMP SET title=$1, "content"=$2, updated_at=CURRENT_TIMESTAMP
WHERE post_id = $4 WHERE post_id = $3
RETURNING post_id, blog_id, user_id, title, content, created_at, updated_at RETURNING post_id, blog_id, user_id, title, content, created_at, updated_at
` `
type UpdatePostByPostIdParams struct { type UpdatePostByPostIdParams struct {
BlogID pgtype.Int8 `json:"blog_id"`
Title pgtype.Text `json:"title"` Title pgtype.Text `json:"title"`
Content pgtype.Text `json:"content"` Content pgtype.Text `json:"content"`
PostID int64 `json:"post_id"` PostID int64 `json:"post_id"`
} }
func (q *Queries) UpdatePostByPostId(ctx context.Context, arg UpdatePostByPostIdParams) (Post, error) { func (q *Queries) UpdatePostByPostId(ctx context.Context, arg UpdatePostByPostIdParams) (Post, error) {
row := q.db.QueryRow(ctx, updatePostByPostId, row := q.db.QueryRow(ctx, updatePostByPostId, arg.Title, arg.Content, arg.PostID)
arg.BlogID,
arg.Title,
arg.Content,
arg.PostID,
)
var i Post var i Post
err := row.Scan( err := row.Scan(
&i.PostID, &i.PostID,

View File

@ -21,10 +21,16 @@ RETURNING *;
-- name: UpdatePostByPostId :one -- name: UpdatePostByPostId :one
UPDATE public.posts UPDATE public.posts
SET blog_id=$1, title=$2, "content"=$3, updated_at=CURRENT_TIMESTAMP SET title=$1, "content"=$2, updated_at=CURRENT_TIMESTAMP
WHERE post_id = $4 WHERE post_id = $3
RETURNING *; RETURNING *;
-- name: DeletePostByPostId :exec -- name: DeletePostByPostId :exec
DELETE FROM public.posts DELETE FROM public.posts
WHERE post_id=$1; WHERE post_id=$1;
-- name: UpdatePostBlogId :exec
UPDATE public.posts
SET blog_id=$2, updated_at=CURRENT_TIMESTAMP
WHERE post_id = $1
RETURNING *;

View File

@ -2,9 +2,7 @@ package middleware
import ( import (
profilepolicies "enshi/ABAC/ProfilePolicies" profilepolicies "enshi/ABAC/ProfilePolicies"
rest_api_stuff "enshi/REST_API_stuff" "enshi/ABAC/rules"
"fmt"
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -18,19 +16,7 @@ func ProfileMiddleware() gin.HandlerFunc {
isAllowed, errors := profilepolicies.ProfilePolicies(c) isAllowed, errors := profilepolicies.ProfilePolicies(c)
var errorsMap = map[int]string{} if rules.ShouldAbortRequest(c, isAllowed, errors) {
for i, error := range errors {
errorsMap[i] = error.Error()
}
if errors != nil {
c.IndentedJSON(http.StatusUnauthorized, errorsMap)
c.Abort()
return
}
if !isAllowed {
rest_api_stuff.UnauthorizedAnswer(c, fmt.Errorf("you have no permission"))
c.Abort() c.Abort()
return return
} }

View File

@ -1,26 +1,19 @@
package middleware package middleware
import ( import (
rest_api_stuff "enshi/REST_API_stuff" adminpolicies "enshi/ABAC/AdminPolicies"
"enshi/middleware/checkRole" "enshi/ABAC/rules"
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func AdminMiddleware() gin.HandlerFunc { func AdminMiddleware() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
isAllowed, errors := adminpolicies.AdminPolicies(c)
isAdmin, err := checkRole.IsAdmin(c) if rules.ShouldAbortRequest(c, isAllowed, errors) {
if err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
c.Abort()
}
if !isAdmin {
rest_api_stuff.UnauthorizedAnswer(c, fmt.Errorf("not allowed"))
c.Abort() c.Abort()
return
} }
c.Next() c.Next()

View File

@ -2,9 +2,7 @@ package middleware
import ( import (
blogspolicies "enshi/ABAC/blogsPolicies" blogspolicies "enshi/ABAC/blogsPolicies"
rest_api_stuff "enshi/REST_API_stuff" "enshi/ABAC/rules"
"fmt"
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -25,19 +23,7 @@ func BlogsMiddleware() gin.HandlerFunc {
isAllowed, errors := blogspolicies.BlogPolicies(c) isAllowed, errors := blogspolicies.BlogPolicies(c)
var errorsMap = map[int]string{} if rules.ShouldAbortRequest(c, isAllowed, errors) {
for i, error := range errors {
errorsMap[i] = error.Error()
}
if errors != nil {
c.IndentedJSON(http.StatusUnauthorized, errorsMap)
c.Abort()
return
}
if !isAllowed {
rest_api_stuff.UnauthorizedAnswer(c, fmt.Errorf("you have no permission"))
c.Abort() c.Abort()
return return
} }

View File

@ -1,4 +1,4 @@
package utils package getters
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"

View File

@ -2,12 +2,14 @@ package getters
import ( import (
"strconv" "strconv"
"strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// Returns -1, error if there is no such param or value invalid
func GetInt64Param(c *gin.Context, paramName string) (int64, error) { func GetInt64Param(c *gin.Context, paramName string) (int64, error) {
int64ParamValue, err := strconv.ParseInt(c.Param(paramName), 10, 64) int64ParamValue, err := strconv.ParseInt(strings.Trim(c.Param(paramName), "/"), 10, 64)
if err != nil { if err != nil {
return -1, err return -1, err

View File

@ -2,21 +2,27 @@ package middleware
import ( import (
postspolicies "enshi/ABAC/PostsPolicies" postspolicies "enshi/ABAC/PostsPolicies"
rest_api_stuff "enshi/REST_API_stuff" "enshi/ABAC/rules"
"fmt" "enshi/middleware/getters"
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func PostsMiddleware() gin.HandlerFunc { func PostsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
blogId, _ := getters.GetInt64Param(c, "blog-id")
postId, _ := getters.GetInt64Param(c, "post-id")
switch c.Request.Method { switch c.Request.Method {
case "DELETE": case "DELETE":
c.Set("target", postspolicies.DELETE_POST) c.Set("target", postspolicies.DELETE_POST)
case "PUT": case "PUT":
if postId > 0 && blogId > 0 {
c.Set("target", postspolicies.UPDATE_POST_BLOG)
} else if postId > 0 {
c.Set("target", postspolicies.UPDATE_POST) c.Set("target", postspolicies.UPDATE_POST)
}
case "POST": case "POST":
c.Set("target", postspolicies.CREATE_POST) c.Set("target", postspolicies.CREATE_POST)
case "GET": case "GET":
@ -25,19 +31,7 @@ func PostsMiddleware() gin.HandlerFunc {
isAllowed, errors := postspolicies.PostsPolicies(c) isAllowed, errors := postspolicies.PostsPolicies(c)
var errorsMap = map[int]string{} if rules.ShouldAbortRequest(c, isAllowed, errors) {
for i, error := range errors {
errorsMap[i] = error.Error()
}
if errors != nil {
c.IndentedJSON(http.StatusUnauthorized, errorsMap)
c.Abort()
return
}
if !isAllowed {
rest_api_stuff.UnauthorizedAnswer(c, fmt.Errorf("you have no permission"))
c.Abort() c.Abort()
return return
} }

View File

@ -12,7 +12,7 @@ import (
) )
func CreateBlog(c *gin.Context) { func CreateBlog(c *gin.Context) {
blogParams, err := utils.GetContextPayload[db_repo.CreateBlogByUserIdParams](c) blogParams, err := getters.GetContextPayload[db_repo.CreateBlogByUserIdParams](c)
if err != nil { if err != nil {
rest_api_stuff.BadRequestAnswer(c, err) rest_api_stuff.BadRequestAnswer(c, err)
return return

View File

@ -0,0 +1,36 @@
package blogRoutes
import (
"context"
rest_api_stuff "enshi/REST_API_stuff"
db_repo "enshi/db/go_queries"
"enshi/db_connection"
"enshi/middleware/getters"
"github.com/gin-gonic/gin"
)
func DeleteBlog(c *gin.Context) {
blogId, err := getters.GetInt64Param(c, "blog-id")
if err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
return
}
transaction, err := db_connection.Dbx.Begin(context.Background())
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
defer transaction.Rollback(context.Background())
err = db_repo.New(transaction).
DeleteBlogByBlogId(context.Background(), blogId)
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
transaction.Commit(context.Background())
rest_api_stuff.OkAnswer(c, "blog has been deleted")
}

View File

@ -0,0 +1,29 @@
package blogRoutes
import (
"context"
rest_api_stuff "enshi/REST_API_stuff"
db_repo "enshi/db/go_queries"
"enshi/db_connection"
"enshi/middleware/getters"
"net/http"
"github.com/gin-gonic/gin"
)
func GetBlog(c *gin.Context) {
blogId, err := getters.GetInt64Param(c, "blog-id")
if err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
return
}
blogData, err := db_repo.New(db_connection.Dbx).
GetBlogByBlogId(context.Background(), blogId)
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
c.IndentedJSON(http.StatusOK, blogData)
}

View File

@ -0,0 +1,46 @@
package blogRoutes
import (
"context"
rest_api_stuff "enshi/REST_API_stuff"
db_repo "enshi/db/go_queries"
"enshi/db_connection"
"enshi/middleware/getters"
"github.com/gin-gonic/gin"
)
func UpdateBlog(c *gin.Context) {
newBlogParams, err :=
getters.GetContextPayload[db_repo.UpdateBlogInfoByBlogIdParams](c)
if err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
return
}
blogId, err := getters.GetInt64Param(c, "blog-id")
if err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
return
}
newBlogParams.BlogID = blogId
transaction, err := db_connection.Dbx.Begin(context.Background())
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
defer transaction.Rollback(context.Background())
_, err = db_repo.New(transaction).
UpdateBlogInfoByBlogId(context.Background(), newBlogParams)
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
transaction.Commit(context.Background())
rest_api_stuff.OkAnswer(c, "blog has been updated")
}

View File

@ -5,8 +5,6 @@ import (
rest_api_stuff "enshi/REST_API_stuff" rest_api_stuff "enshi/REST_API_stuff"
db_repo "enshi/db/go_queries" db_repo "enshi/db/go_queries"
"enshi/db_connection" "enshi/db_connection"
"enshi/middleware/getters"
"fmt"
"strconv" "strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -20,25 +18,7 @@ func DeletePost(c *gin.Context) {
return return
} }
userClaims, err := getters.GetClaimsFromContext(c)
if err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
return
}
query := db_repo.New(db_connection.Dbx) query := db_repo.New(db_connection.Dbx)
post, err := query.GetPostsByPostId(context.Background(), postId)
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
if post.UserID != userClaims.Id {
rest_api_stuff.UnauthorizedAnswer(c, fmt.Errorf("you are not the author"))
return
}
// TODO: Add block of code, so admin could delete anything
err = query.DeletePostByPostId(context.Background(), postId) err = query.DeletePostByPostId(context.Background(), postId)
if err != nil { if err != nil {

View File

@ -0,0 +1,35 @@
package postsRoutes
import (
"context"
rest_api_stuff "enshi/REST_API_stuff"
db_repo "enshi/db/go_queries"
"enshi/db_connection"
"strconv"
"github.com/gin-gonic/gin"
"github.com/jackc/pgx/v5/pgtype"
)
func DeletePostBlog(c *gin.Context) {
var queryParams db_repo.UpdatePostBlogIdParams
postId, err := strconv.ParseInt(c.Param("post-id"), 10, 64)
if err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
return
}
queryParams.BlogID = pgtype.Int8{}
queryParams.PostID = postId
query := db_repo.New(db_connection.Dbx)
err = query.UpdatePostBlogId(context.Background(), queryParams)
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
rest_api_stuff.OkAnswer(c, "post has been deleted")
}

View File

@ -0,0 +1,58 @@
package postsRoutes
import (
"context"
rest_api_stuff "enshi/REST_API_stuff"
db_repo "enshi/db/go_queries"
"enshi/db_connection"
"enshi/middleware/getters"
"github.com/gin-gonic/gin"
"github.com/jackc/pgx/v5/pgtype"
)
func UpdatePostBlog(c *gin.Context) {
var UpdatedPostParams db_repo.UpdatePostBlogIdParams
postId, err := getters.GetInt64Param(c, "post-id")
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
blogId, err := getters.GetInt64Param(c, "blog-id")
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
UpdatedPostParams.PostID = postId
UpdatedPostParams.BlogID = pgtype.Int8{
Valid: true,
Int64: blogId,
}
transaction, err := db_connection.Dbx.Begin(context.Background())
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
defer transaction.Rollback(context.Background())
err = db_repo.New(
transaction,
).UpdatePostBlogId(
context.Background(),
UpdatedPostParams,
)
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
transaction.Commit(context.Background())
rest_api_stuff.OkAnswer(c, "post has been updated")
}

View File

@ -49,6 +49,10 @@ func SetupRotes(g *gin.Engine) error {
"posts/:post-id", "posts/:post-id",
postsRoutes.UpdatePost, postsRoutes.UpdatePost,
) )
postsGroup.PUT(
"posts/:post-id/blogs/:blog-id",
postsRoutes.UpdatePostBlog,
)
postsGroup.POST( postsGroup.POST(
"posts", "posts",
postsRoutes.CreatePost, postsRoutes.CreatePost,
@ -57,6 +61,10 @@ func SetupRotes(g *gin.Engine) error {
"posts/:post-id", "posts/:post-id",
postsRoutes.DeletePost, postsRoutes.DeletePost,
) )
postsGroup.DELETE(
"posts/:post-id/blogs",
postsRoutes.DeletePostBlog,
)
blogGroup := g.Group("/") blogGroup := g.Group("/")
blogGroup.Use(middleware.BlogsMiddleware()) blogGroup.Use(middleware.BlogsMiddleware())
@ -66,6 +74,21 @@ func SetupRotes(g *gin.Engine) error {
blogRoutes.CreateBlog, blogRoutes.CreateBlog,
) )
blogGroup.PUT(
"blogs/:blog-id",
blogRoutes.UpdateBlog,
)
blogGroup.DELETE(
"blogs/:blog-id",
blogRoutes.DeleteBlog,
)
blogGroup.GET(
"blogs/:blog-id",
blogRoutes.GetBlog,
)
profilesGroup := g.Group("/") profilesGroup := g.Group("/")
profilesGroup.Use(middleware.ProfileMiddleware()) profilesGroup.Use(middleware.ProfileMiddleware())
@ -74,12 +97,8 @@ func SetupRotes(g *gin.Engine) error {
userProfileRoutes.UpdateUserProfile, userProfileRoutes.UpdateUserProfile,
) )
// Auth group routes
authGroup := g.Group("/")
authGroup.Use(middleware.AuthMiddleware())
// Admin group routes // Admin group routes
adminGroup := authGroup.Group("/admin/") adminGroup := g.Group("/admin/")
adminGroup.Use(middleware.AdminMiddleware()) adminGroup.Use(middleware.AdminMiddleware())
adminGroup.GET("testAdmin", testAdmin) adminGroup.GET("testAdmin", testAdmin)

View File

@ -6,14 +6,13 @@ import (
db_repo "enshi/db/go_queries" db_repo "enshi/db/go_queries"
"enshi/db_connection" "enshi/db_connection"
"enshi/middleware/getters" "enshi/middleware/getters"
"enshi/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func UpdateUserProfile(c *gin.Context) { func UpdateUserProfile(c *gin.Context) {
newProfile, err := newProfile, err :=
utils.GetContextPayload[db_repo.UpdateProfileByUserIdParams](c) getters.GetContextPayload[db_repo.UpdateProfileByUserIdParams](c)
if err != nil { if err != nil {
rest_api_stuff.BadRequestAnswer(c, err) rest_api_stuff.BadRequestAnswer(c, err)