create/delete post routes were added
This commit is contained in:
parent
2b306c687c
commit
e2450e4860
@ -33,3 +33,10 @@ func ConflictAnswer(c *gin.Context, err error) {
|
||||
gin.H{"error": err.Error()},
|
||||
)
|
||||
}
|
||||
|
||||
func UnauthorizedAnswer(c *gin.Context, err error) {
|
||||
c.IndentedJSON(
|
||||
http.StatusUnauthorized,
|
||||
gin.H{"error": err.Error()},
|
||||
)
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
@ -12,10 +13,16 @@ var (
|
||||
SecretKey string
|
||||
)
|
||||
|
||||
type UserInfoJWT struct {
|
||||
Id int64
|
||||
Username string
|
||||
IsAdmin bool
|
||||
}
|
||||
|
||||
// Generating new token with user info
|
||||
//
|
||||
// userInfo = { "id": int, "name": string }
|
||||
func CreateToken(userInfo map[string]interface{}) (string, error) {
|
||||
func CreateToken(userInfo UserInfoJWT) (string, error) {
|
||||
// Create new token
|
||||
token := jwt.New(jwt.SigningMethodHS256)
|
||||
|
||||
@ -23,8 +30,9 @@ func CreateToken(userInfo map[string]interface{}) (string, error) {
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
|
||||
// Add some info to claims
|
||||
claims["name"] = userInfo["name"]
|
||||
claims["id"] = userInfo["id"]
|
||||
claims["username"] = userInfo.Username
|
||||
claims["id"] = strconv.FormatInt(userInfo.Id, 10)
|
||||
claims["isAdmin"] = userInfo.IsAdmin
|
||||
claims["exp"] = time.Now().Add(time.Hour * 1).Unix()
|
||||
|
||||
// Get string token that will be passed to user
|
||||
|
||||
@ -52,7 +52,7 @@ type Like struct {
|
||||
type Post struct {
|
||||
PostID int64 `json:"post_id"`
|
||||
BlogID pgtype.Int8 `json:"blog_id"`
|
||||
UserID pgtype.Int8 `json:"user_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Title pgtype.Text `json:"title"`
|
||||
Content pgtype.Text `json:"content"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
|
||||
@ -21,7 +21,7 @@ RETURNING post_id, blog_id, user_id, title, content, created_at, updated_at
|
||||
type CreatePostParams struct {
|
||||
PostID int64 `json:"post_id"`
|
||||
BlogID pgtype.Int8 `json:"blog_id"`
|
||||
UserID pgtype.Int8 `json:"user_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Title pgtype.Text `json:"title"`
|
||||
Content pgtype.Text `json:"content"`
|
||||
}
|
||||
@ -91,13 +91,34 @@ func (q *Queries) GetPostsByBlogId(ctx context.Context, blogID pgtype.Int8) ([]P
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getPostsByPostId = `-- name: GetPostsByPostId :one
|
||||
SELECT post_id, blog_id, user_id, title, content, created_at, updated_at
|
||||
FROM public.posts posts
|
||||
where posts.post_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetPostsByPostId(ctx context.Context, postID int64) (Post, error) {
|
||||
row := q.db.QueryRow(ctx, getPostsByPostId, postID)
|
||||
var i Post
|
||||
err := row.Scan(
|
||||
&i.PostID,
|
||||
&i.BlogID,
|
||||
&i.UserID,
|
||||
&i.Title,
|
||||
&i.Content,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getPostsByUserId = `-- name: GetPostsByUserId :many
|
||||
SELECT post_id, blog_id, user_id, title, content, created_at, updated_at
|
||||
FROM public.posts posts
|
||||
where posts.user_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetPostsByUserId(ctx context.Context, userID pgtype.Int8) ([]Post, error) {
|
||||
func (q *Queries) GetPostsByUserId(ctx context.Context, userID int64) ([]Post, error) {
|
||||
rows, err := q.db.Query(ctx, getPostsByUserId, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -134,7 +155,7 @@ RETURNING post_id, blog_id, user_id, title, content, created_at, updated_at
|
||||
|
||||
type UpdatePostByPostIdParams struct {
|
||||
BlogID pgtype.Int8 `json:"blog_id"`
|
||||
UserID pgtype.Int8 `json:"user_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Title pgtype.Text `json:"title"`
|
||||
Content pgtype.Text `json:"content"`
|
||||
PostID int64 `json:"post_id"`
|
||||
|
||||
@ -9,7 +9,7 @@ CREATE TABLE "public"."users" ("user_id" bigint NOT NULL, "username" character v
|
||||
-- Create "blogs" table
|
||||
CREATE TABLE "public"."blogs" ("blog_id" bigint NOT NULL, "user_id" bigint NOT NULL, "title" character varying(255) NULL, "description" text NULL, "category_id" integer NULL, "created_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY ("blog_id"), CONSTRAINT "blogs_category_id_fkey" FOREIGN KEY ("category_id") REFERENCES "public"."categories" ("category_id") ON UPDATE NO ACTION ON DELETE NO ACTION, CONSTRAINT "blogs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE);
|
||||
-- Create "posts" table
|
||||
CREATE TABLE "public"."posts" ("post_id" bigint NOT NULL, "blog_id" bigint NULL, "user_id" bigint NULL, "title" character varying(255) NULL, "content" text NULL, "created_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY ("post_id"), CONSTRAINT "posts_blog_id_fkey" FOREIGN KEY ("blog_id") REFERENCES "public"."blogs" ("blog_id") ON UPDATE NO ACTION ON DELETE CASCADE, CONSTRAINT "posts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE);
|
||||
CREATE TABLE "public"."posts" ("post_id" bigint NOT NULL, "blog_id" bigint NULL, "user_id" bigint NOT NULL, "title" character varying(255) NULL, "content" text NULL, "created_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY ("post_id"), CONSTRAINT "posts_blog_id_fkey" FOREIGN KEY ("blog_id") REFERENCES "public"."blogs" ("blog_id") ON UPDATE NO ACTION ON DELETE CASCADE, CONSTRAINT "posts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE);
|
||||
-- Create "bookmarks" table
|
||||
CREATE TABLE "public"."bookmarks" ("user_id" bigint NOT NULL, "post_id" bigint NOT NULL, "bookmarked_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY ("user_id", "post_id"), CONSTRAINT "bookmarks_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "public"."posts" ("post_id") ON UPDATE NO ACTION ON DELETE CASCADE, CONSTRAINT "bookmarks_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE);
|
||||
-- Create "comments" table
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
-- name: GetPostsByPostId :one
|
||||
SELECT *
|
||||
FROM public.posts posts
|
||||
where posts.post_id = $1;
|
||||
|
||||
-- name: GetPostsByUserId :many
|
||||
SELECT *
|
||||
FROM public.posts posts
|
||||
|
||||
@ -9,6 +9,7 @@ sql:
|
||||
package: "db_repo"
|
||||
out: "./go_queries"
|
||||
sql_package: "pgx/v5"
|
||||
|
||||
overrides:
|
||||
- column: users.password
|
||||
go_struct_tag: validate:"required"
|
||||
@ -16,8 +17,8 @@ sql:
|
||||
go_struct_tag: validate:"required"
|
||||
- column: users.email
|
||||
go_struct_tag: validate:"required,email"
|
||||
|
||||
- db_type: "uuid"
|
||||
go_type:
|
||||
import: 'github.com/google/uuid'
|
||||
type: 'UUID'
|
||||
|
||||
type: 'UUID'
|
||||
10
enshi_back/middleware/adminMiddleware.go
Normal file
10
enshi_back/middleware/adminMiddleware.go
Normal file
@ -0,0 +1,10 @@
|
||||
package middleware
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func AdminMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@ -10,19 +10,26 @@ import (
|
||||
func AuthMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
|
||||
token := c.GetHeader("Authorization")
|
||||
// tokenFromCoolies := c.Request.CookiesNamed("auth_cookie")
|
||||
// token := c.GetHeader("Authorization")
|
||||
|
||||
claims, err := auth.ValidateToken(token)
|
||||
tokenFromCookies := c.Request.CookiesNamed("auth_cookie")[0].Value
|
||||
cookieClimes, err := auth.ValidateToken(tokenFromCookies)
|
||||
if err != nil {
|
||||
c.IndentedJSON(http.StatusUnauthorized, gin.H{"error auth": err.Error()})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// claims, err := auth.ValidateToken(token)
|
||||
// if err != nil {
|
||||
// c.IndentedJSON(http.StatusUnauthorized, gin.H{"error auth": err.Error()})
|
||||
// c.Abort()
|
||||
// return
|
||||
// }
|
||||
|
||||
// Claims -> data stored in token
|
||||
c.Set("id", claims["id"])
|
||||
c.Set("claims", claims)
|
||||
c.Set(ContextUserId, cookieClimes["id"])
|
||||
c.Set(ContextTokenData, cookieClimes)
|
||||
c.Next()
|
||||
|
||||
}
|
||||
|
||||
@ -6,7 +6,12 @@ func CORSMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, authorization, Authorization, accept, origin, Cache-Control, X-Requested-With, Cookie")
|
||||
c.Writer.Header().Set(
|
||||
"Access-Control-Allow-Headers",
|
||||
"Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, "+
|
||||
"authorization, Authorization, accept, origin, Cache-Control, "+
|
||||
"X-Requested-With, Cookie",
|
||||
)
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
|
||||
c.Writer.Header().Set("Access-Control-Expose-Headers", "Access-Token, Uid, Authorization")
|
||||
|
||||
|
||||
44
enshi_back/middleware/getters/claims.go
Normal file
44
enshi_back/middleware/getters/claims.go
Normal file
@ -0,0 +1,44 @@
|
||||
package getters
|
||||
|
||||
import (
|
||||
"enshi/auth"
|
||||
"enshi/global"
|
||||
"enshi/middleware"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
func GetClaimsFromContext(c *gin.Context) (auth.UserInfoJWT, error) {
|
||||
var UserInfo auth.UserInfoJWT
|
||||
|
||||
claims, exists := c.Get(middleware.ContextTokenData)
|
||||
|
||||
if !exists {
|
||||
return auth.UserInfoJWT{}, fmt.Errorf("error getting user id")
|
||||
}
|
||||
|
||||
parsedUserId, err := strconv.ParseInt(
|
||||
claims.(jwt.MapClaims)["id"].(string),
|
||||
10,
|
||||
64,
|
||||
)
|
||||
if err != nil {
|
||||
return auth.UserInfoJWT{}, fmt.Errorf("error parsing user id")
|
||||
}
|
||||
|
||||
UserInfo.Id = parsedUserId
|
||||
UserInfo.Username = claims.(jwt.MapClaims)["username"].(string)
|
||||
isAdmin, err := strconv.ParseBool(claims.(jwt.MapClaims)["isAdmin"].(string))
|
||||
if err != nil {
|
||||
UserInfo.IsAdmin = false
|
||||
fmt.Println(global.RedColor + "isAdmin prop corrupted" + global.ResetColor)
|
||||
} else {
|
||||
UserInfo.IsAdmin = isAdmin
|
||||
}
|
||||
|
||||
return UserInfo, nil
|
||||
|
||||
}
|
||||
23
enshi_back/middleware/getters/userId.go
Normal file
23
enshi_back/middleware/getters/userId.go
Normal file
@ -0,0 +1,23 @@
|
||||
package getters
|
||||
|
||||
import (
|
||||
"enshi/middleware"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func GetUserIdFromContext(c *gin.Context) (int64, error) {
|
||||
userId, exists := c.Get(middleware.ContextUserId)
|
||||
|
||||
if !exists {
|
||||
return -1, fmt.Errorf("error getting user id")
|
||||
}
|
||||
|
||||
if parsedUserId, err := strconv.ParseInt(userId.(string), 10, 64); err != nil {
|
||||
return -1, fmt.Errorf("error parsing user id")
|
||||
} else {
|
||||
return parsedUserId, nil
|
||||
}
|
||||
}
|
||||
5
enshi_back/middleware/keys.go
Normal file
5
enshi_back/middleware/keys.go
Normal file
@ -0,0 +1,5 @@
|
||||
package middleware
|
||||
|
||||
var ContextUserId = "id"
|
||||
var ContextIsAdmin = "isAdmin"
|
||||
var ContextTokenData = "tokenData"
|
||||
44
enshi_back/routes/createPost.go
Normal file
44
enshi_back/routes/createPost.go
Normal file
@ -0,0 +1,44 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
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/google/uuid"
|
||||
)
|
||||
|
||||
func CreatePost(c *gin.Context) {
|
||||
var postParams db_repo.CreatePostParams
|
||||
|
||||
if err := c.BindJSON(&postParams); err != nil {
|
||||
rest_api_stuff.BadRequestAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
userId, err := getters.GetUserIdFromContext(c)
|
||||
if err != nil {
|
||||
rest_api_stuff.BadRequestAnswer(c, err)
|
||||
return
|
||||
}
|
||||
postParams.UserID = userId
|
||||
|
||||
if uuidForPost, err := uuid.NewV7(); err != nil {
|
||||
rest_api_stuff.InternalErrorAnswer(c, err)
|
||||
return
|
||||
} else {
|
||||
postParams.PostID = -int64(binary.BigEndian.Uint64(uuidForPost[8:]))
|
||||
}
|
||||
|
||||
query := db_repo.New(db_connection.Dbx)
|
||||
if _, err := query.CreatePost(context.Background(), postParams); err != nil {
|
||||
rest_api_stuff.InternalErrorAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
rest_api_stuff.OkAnswer(c, "Post has been created!")
|
||||
}
|
||||
51
enshi_back/routes/deletePost.go
Normal file
51
enshi_back/routes/deletePost.go
Normal file
@ -0,0 +1,51 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"context"
|
||||
rest_api_stuff "enshi/REST_API_stuff"
|
||||
db_repo "enshi/db/go_queries"
|
||||
"enshi/db_connection"
|
||||
"enshi/middleware/getters"
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func DeletePost(c *gin.Context) {
|
||||
var deletePostId struct {
|
||||
postId int64
|
||||
}
|
||||
|
||||
if err := c.BindJSON(&deletePostId); err != nil {
|
||||
rest_api_stuff.BadRequestAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
userClaims, err := getters.GetClaimsFromContext(c)
|
||||
if err != nil {
|
||||
rest_api_stuff.BadRequestAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
query := db_repo.New(db_connection.Dbx)
|
||||
post, err := query.GetPostsByPostId(context.Background(), deletePostId.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(), deletePostId.postId)
|
||||
if err != nil {
|
||||
rest_api_stuff.InternalErrorAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
rest_api_stuff.OkAnswer(c, "post has been deleted")
|
||||
}
|
||||
@ -8,7 +8,6 @@ import (
|
||||
"enshi/global"
|
||||
"enshi/hasher"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -16,7 +15,7 @@ import (
|
||||
|
||||
func Login(c *gin.Context) {
|
||||
type content struct {
|
||||
Nickname string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
@ -29,7 +28,7 @@ func Login(c *gin.Context) {
|
||||
}
|
||||
|
||||
repo := db_repo.New(db_connection.Dbx)
|
||||
user, err := repo.GetUserByUsername(context.Background(), body.Nickname)
|
||||
user, err := repo.GetUserByUsername(context.Background(), body.Username)
|
||||
if err != nil {
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
@ -47,20 +46,20 @@ func Login(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
user_info := map[string]interface{}{
|
||||
"id": user.UserID,
|
||||
"name": user.Username,
|
||||
userInfo := auth.UserInfoJWT{
|
||||
Id: user.UserID,
|
||||
Username: user.Username,
|
||||
IsAdmin: user.IsAdmin,
|
||||
}
|
||||
|
||||
token, err := auth.CreateToken(user_info)
|
||||
token, err := auth.CreateToken(userInfo)
|
||||
if err != nil {
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
cookieName := "auth_cookie"
|
||||
cookieValue := "id=" + strconv.FormatInt(user_info["id"].(int64), 10) +
|
||||
"_nickname=" + user_info["name"].(string)
|
||||
cookieValue := token
|
||||
maxAge := int(2 * time.Hour.Seconds()) // Cookie expiry time in seconds (1 hour)
|
||||
path := global.PathForCookies // Cookie path
|
||||
domain := global.DomainForCookies // Set domain (localhost for testing)
|
||||
|
||||
@ -94,9 +94,10 @@ func RegisterUser(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
tokenParams := map[string]interface{}{
|
||||
"id": userParams.UserID,
|
||||
"username": userParams.Username,
|
||||
tokenParams := auth.UserInfoJWT{
|
||||
Id: userParams.UserID,
|
||||
Username: userParams.Username,
|
||||
IsAdmin: false,
|
||||
}
|
||||
|
||||
token, err := auth.CreateToken(tokenParams)
|
||||
@ -108,7 +109,7 @@ func RegisterUser(c *gin.Context) {
|
||||
cookieParams := &rest_api_stuff.CookieParams{
|
||||
Name: "auth_cookie",
|
||||
Value: token,
|
||||
MaxAge: int(time.Hour.Seconds() * 2),
|
||||
MaxAge: int(2 * time.Hour.Seconds()),
|
||||
Path: global.PathForCookies,
|
||||
Domain: global.DomainForCookies,
|
||||
Secure: global.SecureForCookies,
|
||||
|
||||
@ -28,6 +28,11 @@ func SetupRotes(g *gin.Engine) error {
|
||||
// Auth group routes
|
||||
authGroup := g.Group("/")
|
||||
authGroup.Use(middleware.AuthMiddleware())
|
||||
authGroup.POST("createPost", routes.CreatePost)
|
||||
authGroup.POST("deletePost", routes.DeletePost)
|
||||
|
||||
adminGroup := authGroup.Group("/admin/")
|
||||
adminGroup.Use(middleware.AdminMiddleware())
|
||||
|
||||
authGroup.POST("changeUserProfile", routes.ChangeUserProfile)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user