Added comments endpoint

This commit is contained in:
Max 2025-05-02 12:35:56 +03:00
parent aa5d1e4867
commit aec63ec28d
26 changed files with 242 additions and 46 deletions

View File

@ -5,6 +5,7 @@ import { useAtomValue } from "jotai";
import { useParams } from "react-router-dom";
import { axiosLocalhost } from "../../api/axios/axios";
import { userAtom } from "../../AtomStore/AtomStore";
import { MINUTE } from "../../constants/timeInMills";
import AddPostToBlogDialog from "../Dialogs/AddPostToBlogDialog/AddPostToBlogDialog";
import ChangePostButton from "./ChangePostButton/ChangePostButton";
import SkeletonPostLoader from "./SkeletonLoader/SkeletonLoader";
@ -19,7 +20,7 @@ export default function ArticleViewer(props: TArticleViewer) {
let queryParams = useParams();
const user = useAtomValue(userAtom);
const { data, isPending } = useQuery({
const { data: blogData, isPending } = useQuery({
queryKey: [`post_${queryParams["postId"]}`],
queryFn: async () => {
const response = await axiosLocalhost.get(
@ -28,7 +29,7 @@ export default function ArticleViewer(props: TArticleViewer) {
return response.data;
},
gcTime: 0,
gcTime: 2 * MINUTE,
refetchOnMount: true,
});
@ -38,7 +39,7 @@ export default function ArticleViewer(props: TArticleViewer) {
<ScrollArea className="p-0 mx-auto overflow-hidden ql-editor max-w-pc-width">
<Flex direction={"column"} className="overflow-hidden">
<Text className="mb-2" as="div" size={"9"}>
{data.title}
{blogData.title}
</Text>
<Flex
gap={"3"}
@ -58,20 +59,20 @@ export default function ArticleViewer(props: TArticleViewer) {
/>
</Flex>
<Box hidden={data.user_id != user?.id}>
<Box hidden={blogData.user_id != user?.id}>
<ChangePostButton
postId={queryParams["postId"] || ""}
/>
</Box>
{user ? <AddPostToBlogDialog /> : null}
{blogData.user_id == user?.id ? <AddPostToBlogDialog /> : null}
</Flex>
</Flex>
<Separator size={"4"} className="my-2" />
<Text>
<Interweave content={data.content} />
<Interweave content={blogData.content} />
</Text>
</ScrollArea>
);

View File

@ -0,0 +1,6 @@
export const SECOND = 1000;
export const MINUTE = 60 * SECOND;
export const HOUR = 60 * MINUTE;
export const DAY = 24 * HOUR;
export const WEEK = 7 * DAY;
export const MONTH = 30 * DAY;

View File

@ -0,0 +1,28 @@
package globalrules
import (
"context"
db_repo "enshi/db/go_queries"
"enshi/db_connection"
"enshi/middleware/getters"
"github.com/gin-gonic/gin"
)
func IsOwnerOfTheCommentRule(c *gin.Context) (bool, []error) {
commentId, err := getters.GetInt64Param(c, "comment-id")
if err != nil {
return false, []error{err}
}
contextUserId, err := getters.GetUserIdFromContext(c)
if err != nil {
return false, []error{err}
}
comment, err := db_repo.New(db_connection.Dbx).GetCommentById(context.Background(), commentId)
if err != nil {
return false, []error{err}
}
return contextUserId == comment.UserID, nil
}

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: badge_queries.sql
package db_repo

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: blogs_queries.sql
package db_repo

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: bookmarks_queries.sql
package db_repo

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: categories_queries.sql
package db_repo

View File

@ -1,14 +1,12 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: comments_queries.sql
package db_repo
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createComment = `-- name: CreateComment :one
@ -19,10 +17,10 @@ RETURNING comment_id, post_id, user_id, content, created_at
`
type CreateCommentParams struct {
CommentID int64 `json:"comment_id"`
PostID pgtype.Int8 `json:"post_id"`
UserID pgtype.Int8 `json:"user_id"`
Content pgtype.Text `json:"content"`
CommentID int64 `json:"comment_id"`
PostID int64 `json:"post_id"`
UserID int64 `json:"user_id"`
Content string `json:"content"`
}
func (q *Queries) CreateComment(ctx context.Context, arg CreateCommentParams) (Comment, error) {
@ -53,6 +51,25 @@ func (q *Queries) DeleteComment(ctx context.Context, commentID int64) error {
return err
}
const getCommentById = `-- name: GetCommentById :one
SELECT comment_id, post_id, user_id, "content", created_at
FROM public."comments"
WHERE comment_id=$1
`
func (q *Queries) GetCommentById(ctx context.Context, commentID int64) (Comment, error) {
row := q.db.QueryRow(ctx, getCommentById, commentID)
var i Comment
err := row.Scan(
&i.CommentID,
&i.PostID,
&i.UserID,
&i.Content,
&i.CreatedAt,
)
return i, err
}
const getCommentByUserId = `-- name: GetCommentByUserId :one
SELECT comment_id, post_id, user_id, "content", created_at
FROM public."comments"
@ -60,8 +77,8 @@ where public."comments".user_id = $1 and public."comments".post_id = $2
`
type GetCommentByUserIdParams struct {
UserID pgtype.Int8 `json:"user_id"`
PostID pgtype.Int8 `json:"post_id"`
UserID int64 `json:"user_id"`
PostID int64 `json:"post_id"`
}
func (q *Queries) GetCommentByUserId(ctx context.Context, arg GetCommentByUserIdParams) (Comment, error) {
@ -86,12 +103,12 @@ LIMIT 10 offset ($2 * 10)
`
type GetCommentsForPostAscParams struct {
PostID pgtype.Int8 `json:"post_id"`
Column2 interface{} `json:"column_2"`
PostID int64 `json:"post_id"`
Offset interface{} `json:"offset"`
}
func (q *Queries) GetCommentsForPostAsc(ctx context.Context, arg GetCommentsForPostAscParams) ([]Comment, error) {
rows, err := q.db.Query(ctx, getCommentsForPostAsc, arg.PostID, arg.Column2)
rows, err := q.db.Query(ctx, getCommentsForPostAsc, arg.PostID, arg.Offset)
if err != nil {
return nil, err
}
@ -125,7 +142,7 @@ LIMIT 10 offset ($2 * 10)
`
type GetCommentsForPostDescParams struct {
PostID pgtype.Int8 `json:"post_id"`
PostID int64 `json:"post_id"`
Column2 interface{} `json:"column_2"`
}
@ -163,8 +180,8 @@ RETURNING comment_id, post_id, user_id, content, created_at
`
type UpdateCommentByCommentIdParams struct {
CommentID int64 `json:"comment_id"`
Content pgtype.Text `json:"content"`
CommentID int64 `json:"comment_id"`
Content string `json:"content"`
}
func (q *Queries) UpdateCommentByCommentId(ctx context.Context, arg UpdateCommentByCommentIdParams) (Comment, error) {

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
package db_repo

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: favorites_queries.sql
package db_repo

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: likes_queries.sql
package db_repo

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
package db_repo
@ -38,9 +38,9 @@ type Category struct {
type Comment struct {
CommentID int64 `json:"comment_id"`
PostID pgtype.Int8 `json:"post_id"`
UserID pgtype.Int8 `json:"user_id"`
Content pgtype.Text `json:"content"`
PostID int64 `json:"post_id"`
UserID int64 `json:"user_id"`
Content string `json:"content"`
CreatedAt pgtype.Timestamp `json:"created_at"`
}

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: multi_queries.sql
package db_repo

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: post_tags_queries.sql
package db_repo

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: post_votes_queries.sql
package db_repo

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: posts_queries.sql
package db_repo

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: profiles_queries.sql
package db_repo

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: tags_queries.sql
package db_repo

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: users_queries.sql
package db_repo

View File

@ -57,9 +57,9 @@ CREATE TABLE IF NOT EXISTS "public"."bookmarks" (
-- Create "comments" table
CREATE TABLE IF NOT EXISTS "public"."comments" (
"comment_id" bigint NOT NULL,
"post_id" bigint NULL,
"user_id" bigint NULL,
"content" text NULL,
"post_id" bigint NOT NULL,
"user_id" bigint NOT NULL,
"content" text NOT NULL,
"created_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ("comment_id"),
CONSTRAINT "comments_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "public"."posts" ("post_id") ON UPDATE NO ACTION ON DELETE CASCADE,

View File

@ -20,7 +20,7 @@ SELECT comment_id, post_id, user_id, "content", created_at
FROM public."comments"
where public."comments".post_id = $1
order by created_at ASC
LIMIT 10 offset ($2 * 10);
LIMIT 10 offset (sqlc.arg('offset') * 10);
-- name: UpdateCommentByCommentId :one
UPDATE public."comments"
@ -32,3 +32,8 @@ RETURNING *;
SELECT comment_id, post_id, user_id, "content", created_at
FROM public."comments"
where public."comments".user_id = $1 and public."comments".post_id = $2;
-- name: GetCommentById :one
SELECT comment_id, post_id, user_id, "content", created_at
FROM public."comments"
WHERE comment_id=$1;

View File

@ -13,7 +13,6 @@ import (
"os"
"github.com/gin-gonic/gin"
"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
)
var (
@ -38,7 +37,7 @@ func main() {
defer cleanup(context.Background())
router := gin.Default()
router.Use(otelgin.Middleware(serviceName))
// router.Use(otelgin.Middleware(serviceName))
f, err := os.OpenFile("gin.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {

View File

@ -0,0 +1,63 @@
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 CreateComment(c *gin.Context) {
var CommentParams db_repo.CreateCommentParams
if err := c.BindJSON(&CommentParams); err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
return
}
userId, err := getters.GetUserIdFromContext(c)
if err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
return
}
if commentId, err := uuid.NewV7(); err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
} else {
CommentParams.CommentID = -int64(binary.BigEndian.Uint64(commentId[8:]))
}
CommentParams.UserID = userId
postId, err := getters.GetInt64Param(c, "post-id")
if err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
return
}
CommentParams.PostID = postId
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).CreateComment(
context.Background(),
CommentParams,
)
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
transaction.Commit(context.Background())
rest_api_stuff.OkAnswer(c, "comment has been created")
}

View File

@ -0,0 +1,42 @@
package routes
import (
"context"
rest_api_stuff "enshi/REST_API_stuff"
db_repo "enshi/db/go_queries"
"enshi/db_connection"
"enshi/middleware/getters"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
func GetCommentsForPost(c *gin.Context) {
var params db_repo.GetCommentsForPostAscParams
postId, err := getters.GetInt64Param(c, "post-id")
if err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
return
}
offset, err := strconv.Atoi(c.DefaultQuery("offset", "10"))
if err != nil {
params.Offset = int32(0)
}
params.Offset = int32(offset)
params.PostID = postId
comments, err := db_repo.New(db_connection.Dbx).GetCommentsForPostAsc(
context.Background(),
params,
)
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
c.JSON(http.StatusOK, comments)
}

View File

@ -15,6 +15,7 @@ const (
POST_VOTE_MIDDLEWARE = "POST_VOTE_MIDDLEWARE"
POST_VOTES_MIDDLEWARE = "POST_VOTES_MIDDLEWARE"
USER_MIDDLEWARE = "USER_MIDDLEWARE"
COMMENT_MIDDLEWARE = "COMMENT_MIDDLEWARE"
)
var MiddlewareProvider = middleware.MiddlewareProvider{
@ -172,6 +173,26 @@ var policiesToRegister = map[string]middleware.RulesToCheck{
MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
},
},
COMMENT_MIDDLEWARE: {
middleware.GET: {
Rules: make([]rules.RuleFunction, 0),
MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
},
middleware.POST: {
Rules: []rules.RuleFunction{
globalrules.AuthorizedRule,
},
MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
},
middleware.DELETE: {
Rules: []rules.RuleFunction{
globalrules.AuthorizedRule,
globalrules.IsOwnerOfTheCommentRule,
},
MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
},
},
}
func InitMiddlewareProvider() {

View File

@ -7,6 +7,7 @@ import (
"enshi/routes/authRoutes"
"enshi/routes/blogRoutes"
bookmarksroutes "enshi/routes/bookmarksRoutes"
routes "enshi/routes/commentRoutes"
"enshi/routes/postsRoutes"
"enshi/routes/userProfileRoutes"
userroutes "enshi/routes/userRoutes"
@ -280,6 +281,19 @@ func SetupRotes(g *gin.Engine) error {
authRoutes.Logout,
)
commentGroup := g.Group("/comments/")
commentGroup.Use(MiddlewareProvider.GetMiddleware(COMMENT_MIDDLEWARE))
commentGroup.GET(
":post-id",
routes.GetCommentsForPost,
)
commentGroup.POST(
":post-id",
routes.CreateComment,
)
temporal := g.Group("/")
temporal.Use(middleware.AuthMiddleware())