create/delete post routes were added

This commit is contained in:
Max 2024-11-09 22:51:36 +03:00
parent 2b306c687c
commit e2450e4860
18 changed files with 265 additions and 29 deletions

View File

@ -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()},
)
}

View File

@ -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

View File

@ -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"`

View File

@ -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"`

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -0,0 +1,10 @@
package middleware
import "github.com/gin-gonic/gin"
func AdminMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
}
}

View File

@ -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()
}

View File

@ -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")

View 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
}

View 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
}
}

View File

@ -0,0 +1,5 @@
package middleware
var ContextUserId = "id"
var ContextIsAdmin = "isAdmin"
var ContextTokenData = "tokenData"

View 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!")
}

View 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")
}

View File

@ -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)

View File

@ -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,

View File

@ -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)