From d514c317246c231ea48d25f464e88fd1f9054993 Mon Sep 17 00:00:00 2001 From: Win-10 Date: Wed, 3 Sep 2025 11:47:35 +0300 Subject: [PATCH 1/9] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BD=D0=B5=D0=B4=D0=BE=D1=87=D0=B5=D1=82=D1=8B=20...?= =?UTF-8?q?1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/repo/repo.go | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/internal/repo/repo.go b/internal/repo/repo.go index dd9deba..b3bc3a3 100644 --- a/internal/repo/repo.go +++ b/internal/repo/repo.go @@ -65,51 +65,33 @@ type CreateCommentRequest struct { func (r *Repository) GetUserByID(ctx context.Context, userID int) (*User, error) { var user User - rows, err := r.db.Query(ctx, "SELECT name, email, is_admin, created_at, updated_at FROM users WHERE user_id = $1", userID) + err := r.db.QueryRow(ctx, "SELECT name, email, is_admin, created_at, updated_at FROM users WHERE user_id = $1", userID).Scan(&user.name, &user.email, &user.isAdmin, &user.createdAt, &user.updatedAt) if err != nil { return nil, fmt.Errorf("error User r.db.Query: %w", err) } - for rows.Next() { - err := rows.Scan(&user.name, &user.email, &user.isAdmin, &user.createdAt, &user.updatedAt) - if err != nil { - return nil, fmt.Errorf("error User Scan: %w", err) - } - } return &user, nil } func (r *Repository) GetPostByID(ctx context.Context, postID int) (*Post, error) { var post Post - rows, err := r.db.Query(ctx, "SELECT user_id, title, body, views, created_at, updated_at FROM posts WHERE user_id = $1", postID) + err := r.db.QueryRow(ctx, "SELECT user_id, title, body, views, created_at, updated_at FROM posts WHERE user_id = $1", postID).Scan(&post.userID, &post.title, &post.body, &post.views, &post.createdAt, &post.updatedAt) if err != nil { return nil, fmt.Errorf("error User r.db.Query: %w", err) } - for rows.Next() { - err := rows.Scan(&post.userID, &post.title, &post.body, &post.views, &post.createdAt, &post.updatedAt) - if err != nil { - return nil, fmt.Errorf("error User Scan: %w", err) - } - } return &post, nil } func (r *Repository) GetCommentByID(ctx context.Context, commentID int) (*Comment, error) { var comment Comment - rows, err := r.db.Query(ctx, "SELECT user_id, post_id, body, created_at, updated_at FROM posts WHERE user_id = $1", commentID) + err := r.db.QueryRow(ctx, "SELECT user_id, post_id, body, created_at, updated_at FROM posts WHERE user_id = $1", commentID).Scan(&comment.userID, &comment.postID, &comment.body, &comment.createdAt, &comment.updatedAt) if err != nil { return nil, fmt.Errorf("error User r.db.Query: %w", err) } - for rows.Next() { - err := rows.Scan(&comment.userID, &comment.postID, &comment.body, &comment.createdAt, &comment.updatedAt) - if err != nil { - return nil, fmt.Errorf("error User Scan: %w", err) - } - } return &comment, nil } @@ -123,7 +105,7 @@ func (r *Repository) CreateUser(ctx context.Context, params CreateUserRequest) e } func (r *Repository) CreatePost(ctx context.Context, params CreatePostRequest) error { - _, err := r.db.Exec(ctx, "INSERT INTO posts (user_id, title, body) VALUES ($1, $2)", params.userID, params.title, params.body) + _, err := r.db.Exec(ctx, "INSERT INTO posts (user_id, title, body) VALUES ($1, $2, $3)", params.userID, params.title, params.body) if err != nil { return fmt.Errorf("error CreatePost Exec: %w", err) } @@ -132,7 +114,7 @@ func (r *Repository) CreatePost(ctx context.Context, params CreatePostRequest) e } func (r *Repository) CreateComment(ctx context.Context, params CreateCommentRequest) error { - _, err := r.db.Exec(ctx, "INSERT INTO posts (user_id, title, body) VALUES ($1, $2)", params.userID, params.postID, params.body) + _, err := r.db.Exec(ctx, "INSERT INTO comments (user_id, title, body) VALUES ($1, $2, $3)", params.userID, params.postID, params.body) if err != nil { return fmt.Errorf("error CreateComment Exec: %w", err) } From 89d3872eaa2e75064b30e5a0514ff0cc25494a20 Mon Sep 17 00:00:00 2001 From: Win-10 Date: Wed, 3 Sep 2025 20:25:32 +0300 Subject: [PATCH 2/9] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8E=20CreatePostsComm?= =?UTF-8?q?ents?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/repo/repo.go | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/internal/repo/repo.go b/internal/repo/repo.go index b3bc3a3..98e35f2 100644 --- a/internal/repo/repo.go +++ b/internal/repo/repo.go @@ -87,7 +87,9 @@ func (r *Repository) GetPostByID(ctx context.Context, postID int) (*Post, error) func (r *Repository) GetCommentByID(ctx context.Context, commentID int) (*Comment, error) { var comment Comment - err := r.db.QueryRow(ctx, "SELECT user_id, post_id, body, created_at, updated_at FROM posts WHERE user_id = $1", commentID).Scan(&comment.userID, &comment.postID, &comment.body, &comment.createdAt, &comment.updatedAt) + err := r.db.QueryRow(ctx, + "SELECT user_id, post_id, body, created_at, updated_at FROM posts WHERE user_id = $1", + commentID).Scan(&comment.userID, &comment.postID, &comment.body, &comment.createdAt, &comment.updatedAt) if err != nil { return nil, fmt.Errorf("error User r.db.Query: %w", err) } @@ -121,3 +123,24 @@ func (r *Repository) CreateComment(ctx context.Context, params CreateCommentRequ return nil } + +func (r *Repository) CreatePostsComments(ctx context.Context, postID string) ([]Comment, error) { + + rows, err := r.db.Query(ctx, "SELECT user_id, post_id, body, created_at, updated_at FROM posts WHERE post_id = $1", postID) + + var comments []Comment + for rows.Next() { + var comment Comment + err = rows.Scan(&comment.userID, &comment.postID, &comment.body, &comment.createdAt, &comment.updatedAt) + if err != nil { + return nil, err + } + comments = append(comments, comment) + } + + if err = rows.Err(); err != nil { + return nil, err + } + + return comments, nil +} From 75d7389b06f36b32d9e7e7c1227cbb1383f36c34 Mon Sep 17 00:00:00 2001 From: Win-10 Date: Wed, 3 Sep 2025 22:28:57 +0300 Subject: [PATCH 3/9] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8E=20GetUserPosts=20?= =?UTF-8?q?=D0=B8=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BD=D0=B5=D0=B4=D0=BE=D1=87=D0=B5=D1=82=D1=8B=20=D0=B2=20Get?= =?UTF-8?q?PostComments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/repo/.gitignore => .gitignore | 0 internal/repo/repo.go | 27 +++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) rename internal/repo/.gitignore => .gitignore (100%) diff --git a/internal/repo/.gitignore b/.gitignore similarity index 100% rename from internal/repo/.gitignore rename to .gitignore diff --git a/internal/repo/repo.go b/internal/repo/repo.go index 98e35f2..efd3377 100644 --- a/internal/repo/repo.go +++ b/internal/repo/repo.go @@ -124,14 +124,14 @@ func (r *Repository) CreateComment(ctx context.Context, params CreateCommentRequ return nil } -func (r *Repository) CreatePostsComments(ctx context.Context, postID string) ([]Comment, error) { +func (r *Repository) GetPostComments(ctx context.Context, postID string) ([]Comment, error) { - rows, err := r.db.Query(ctx, "SELECT user_id, post_id, body, created_at, updated_at FROM posts WHERE post_id = $1", postID) + rows, err := r.db.Query(ctx, "SELECT comment_id, post_id, user_id, body, created_at, updated_at FROM comments WHERE post_id = $1", postID) var comments []Comment for rows.Next() { var comment Comment - err = rows.Scan(&comment.userID, &comment.postID, &comment.body, &comment.createdAt, &comment.updatedAt) + err = rows.Scan(&comment.commentID, &comment.postID, &comment.userID, &comment.body, &comment.createdAt, &comment.updatedAt) if err != nil { return nil, err } @@ -144,3 +144,24 @@ func (r *Repository) CreatePostsComments(ctx context.Context, postID string) ([] return comments, nil } + +func (r *Repository) GetUserPosts(ctx context.Context, userID string) ([]Post, error) { + + rows, err := r.db.Query(ctx, "SELECT post_id, title, body, views, created_at, updated_at FROM posts WHERE post_id = $1", userID) + + var posts []Post + for rows.Next() { + var post Post + err = rows.Scan(&post.postID, &post.title, &post.body, post.views, &post.createdAt, &post.updatedAt) + if err != nil { + return nil, err + } + posts = append(posts, post) + } + + if err = rows.Err(); err != nil { + return nil, err + } + + return posts, nil +} From ce409fe20362bb33735abc47cc975362fcc801ca Mon Sep 17 00:00:00 2001 From: Win-10 Date: Thu, 11 Sep 2025 01:35:51 +0300 Subject: [PATCH 4/9] =?UTF-8?q?=D0=94=D0=BE=D0=BF=D0=B8=D1=81=D0=B0=D0=BB?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=B4=20=D0=B2=20repo,=20service,=20handler,=20?= =?UTF-8?q?main?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 8196 -> 6148 bytes TODO.md | 31 +++ internal/cache/cache.go | 1 - internal/handler/handler.go | 59 ++++++ internal/manager/manager.go | 1 - internal/model/model.go | 37 ++++ internal/repo/repo.go | 198 +++++++++++------- internal/service/service.go | 43 ++++ main.go | 35 ++++ migrations/20250901111339_init.sql | 1 + ...0909170255_add_email_unique_constraint.sql | 10 + ...50909170513_add_hashed_password_column.sql | 10 + 12 files changed, 353 insertions(+), 73 deletions(-) create mode 100644 TODO.md delete mode 100644 internal/cache/cache.go delete mode 100644 internal/manager/manager.go create mode 100644 internal/model/model.go create mode 100644 migrations/20250909170255_add_email_unique_constraint.sql create mode 100644 migrations/20250909170513_add_hashed_password_column.sql diff --git a/.DS_Store b/.DS_Store index 95add43efcc11063d22156247bc229817f31bc31..560cb5dc815f84477ba5c22359860dce8ccfbbe6 100644 GIT binary patch delta 171 zcmZp1XfcprU|?W$DortDU=RQ@Ie-{MGjUEV6q~50$jb(j2aDx1q-=~@&&bF&*;y!B zkb@zUA&;SiA(f#BNGCGnOqLRmpL{@AZ?cSt2`4*42!lU^3xogUSP_ZISpvKp%O0>S uX6N7#WCofC1OnVZ!WCrC#=`H+llf&FK@I`g!~&w3AT9xmZI0)e!wdkj{TsOe literal 8196 zcmeHM%Wl&^6ur}?i990Vu?ANUgedu#-k8ij*af zf8ZC`A-;rvVFl;TI8{7pTCu4>%t$kLVxKcJKKIVJ*CirRAGjMtMIy3L*%nsOoKyHd z*SWHkTv>+|;1hM8_+i`eLuU?aE*J%j0!9I&fKk9G@OLPHcQzMm!FylbHLX#=DDYn@ zz~_UF%C?r+iL&zOKqFHC*bFYqfJseEwBf`^?;A*osJiGG<;Uh5@E)O;`G?!vxZ&6 zS{NBnAFKc~H8GEm=kh4fxIh1d>kQ*w@a99rqfg4M1L*20Q zR=!!dJsRz7Y?tiHxMYuZ_t4v}?vBTK>(<@-4-cEi{z(viz|)|Ji1du5xoo5 zapSh{V8zV15O~^%0zVA8!0}{R>O$}`92%0FmRJVPmzltsVq*>f4a;FNi_IYrThfIb zT2UG;gX9AAjJv*Hxx0#^(N?uuvNs=Wv%~hPm6E-+xuZGk6P_1kp~E<|H4QZ7bI@D{ zHq?g?G=-R-R*2C7YWEyHc{9wSuVHKt$O!m8dc38y&BxvqYct->(;*$;>OMV%<%rPZ z0X|MKG8n&u4XUplM?eaf!$HAE%_&yW){iGX1X5^L)x7_qf+^Ju7SpM`+Nc~AT*S1mLpmJf{L|I8eBhztMnU2Gr{lgG_8&Kxd V5<5{AS5VA{04alMi~?7xz;6j}QWF3G diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..6cb4cb8 --- /dev/null +++ b/TODO.md @@ -0,0 +1,31 @@ +# Задачи на проект + +### Общее +1. [X] Удалить cache +2. [X Удалить manager +3. [X] Занести нужные структуры в model.go +4. [X] Подумать, какие будут эндпоинты +5. [ ] Поправить main.go +6. [X] Добавить у User поле hashed_password +7. [X] Добавить constraint unique у email + +### Задачи в handlers +1. [X] Создание нового юзера +2. [ ] Получение нового юзера +3. [ ] Создание нового поста +4. [ ] Получение поста по id +5. [ ] Получение всех постов юзера по user_id + +### Задачи в service +1. [X] Создать метод для создания юзера +2. [X] Создать метод для получения юзера +3. [ ] Создание нового поста +4. [ ] Получение поста по id +5. [ ] Получение всех постов юзера по user_id + +### Задачи в repo +1. [X] Создать метод для создания юзера +2. [X] Создать метод для получения юзера +3. [ ] Создание нового поста +4. [ ] Получение поста по id +5. [ ] Получение всех постов юзера по user_id diff --git a/internal/cache/cache.go b/internal/cache/cache.go deleted file mode 100644 index 08bf029..0000000 --- a/internal/cache/cache.go +++ /dev/null @@ -1 +0,0 @@ -package cache diff --git a/internal/handler/handler.go b/internal/handler/handler.go index abeebd1..0f3666e 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -1 +1,60 @@ package handler + +import ( + "fmt" + "github.com/berduk-dev/blog/internal/model" + "github.com/berduk-dev/blog/internal/service" + "github.com/gin-gonic/gin" + "net/http" + "strconv" +) + +type Handler struct { + service service.Service +} + +func New(service service.Service) Handler { + return Handler{ + service: service, + } +} + +func (h *Handler) CreateUser(c *gin.Context) { + var req model.CreateUserReq + err := c.ShouldBindJSON(&req) + if err != nil { + c.JSON(http.StatusBadRequest, "У вас невалидный запрос") + return + } + + err = h.service.CreateUser(c, req) + if err != nil { + c.JSON(http.StatusInternalServerError, "Попробуйте позже") + return + } + + c.Status(http.StatusOK) +} + +func (h *Handler) GetUser(c *gin.Context) { + userID, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, fmt.Errorf("error GetUser strconv.Atoi: %w", err)) + return + } + + user, err := h.service.GetUser(c, userID) + if err != nil { + c.JSON(http.StatusInternalServerError, fmt.Errorf("error service.GetUser: %w", err)) + return + } + + c.JSON(http.StatusOK, gin.H{ + "user_id": user.UserID, + "name": user.Name, + "email": user.Email, + "is_admin": user.IsAdmin, + "created_at": user.CreatedAt, + "updated_at": user.UpdatedAt, + }) +} diff --git a/internal/manager/manager.go b/internal/manager/manager.go deleted file mode 100644 index 5d04392..0000000 --- a/internal/manager/manager.go +++ /dev/null @@ -1 +0,0 @@ -package manager diff --git a/internal/model/model.go b/internal/model/model.go new file mode 100644 index 0000000..0855458 --- /dev/null +++ b/internal/model/model.go @@ -0,0 +1,37 @@ +package model + +import "time" + +type User struct { + UserID int `json:"user_id"` + Name string `json:"name"` + Email string `json:"email"` + IsAdmin bool `json:"is_admin"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type CreateUserReq struct { + Name string `json:"name"` + Password string `json:"password"` + Email string `json:"email"` +} + +type Post struct { + PostID int + UserID int + Title string + Body string + Views int + CreatedAt time.Time + UpdatedAt time.Time +} + +type Comment struct { + CommentID int + PostID int + UserID int + Body string + CreatedAt time.Time + UpdatedAt time.Time +} diff --git a/internal/repo/repo.go b/internal/repo/repo.go index efd3377..9a055ee 100644 --- a/internal/repo/repo.go +++ b/internal/repo/repo.go @@ -3,8 +3,8 @@ package repo import ( "context" "fmt" + "github.com/berduk-dev/blog/internal/model" "github.com/jackc/pgx/v5" - "time" ) type Repository struct { @@ -17,28 +17,17 @@ func New(db *pgx.Conn) Repository { } } -type User struct { - userID int - name string - email string - isAdmin bool - createdAt time.Time - updatedAt time.Time -} - type CreateUserRequest struct { - name string - email string + Name string + Email string + HashedPassword string + IsAdmin bool } -type Post struct { - postID int - userID int - title string - body string - views int - createdAt time.Time - updatedAt time.Time +type UpdateUser struct { + Name *string + Email *string + IsAdmin *bool } type CreatePostRequest struct { @@ -47,13 +36,8 @@ type CreatePostRequest struct { body string } -type Comment struct { - commentID int - postID int - userID int - body string - createdAt time.Time - updatedAt time.Time +type UpdatePost struct { + Title int } type CreateCommentRequest struct { @@ -62,43 +46,89 @@ type CreateCommentRequest struct { body string } -func (r *Repository) GetUserByID(ctx context.Context, userID int) (*User, error) { - var user User +type UpdateComment struct { + body string +} - err := r.db.QueryRow(ctx, "SELECT name, email, is_admin, created_at, updated_at FROM users WHERE user_id = $1", userID).Scan(&user.name, &user.email, &user.isAdmin, &user.createdAt, &user.updatedAt) +func (r *Repository) GetUser(ctx context.Context, userID int) (model.User, error) { + var user model.User + + err := r.db.QueryRow(ctx, "SELECT id, name, email, is_admin, created_at, updated_at FROM users WHERE user_id = $1", userID).Scan(&user.UserID, &user.Name, &user.Email, &user.IsAdmin, &user.CreatedAt, &user.UpdatedAt) if err != nil { - return nil, fmt.Errorf("error User r.db.Query: %w", err) + return model.User{}, fmt.Errorf("error User r.db.Query: %w", err) } - return &user, nil + return user, nil } -func (r *Repository) GetPostByID(ctx context.Context, postID int) (*Post, error) { - var post Post +func (r *Repository) GetPost(ctx context.Context, postID int) (model.Post, error) { + var post model.Post - err := r.db.QueryRow(ctx, "SELECT user_id, title, body, views, created_at, updated_at FROM posts WHERE user_id = $1", postID).Scan(&post.userID, &post.title, &post.body, &post.views, &post.createdAt, &post.updatedAt) + err := r.db.QueryRow(ctx, "SELECT user_id, title, body, views, created_at, updated_at FROM posts WHERE user_id = $1", postID).Scan(&post.UserID, &post.Title, &post.Body, &post.Views, &post.CreatedAt, &post.UpdatedAt) if err != nil { - return nil, fmt.Errorf("error User r.db.Query: %w", err) + return model.Post{}, fmt.Errorf("error User r.db.Query: %w", err) } - return &post, nil + return post, nil } -func (r *Repository) GetCommentByID(ctx context.Context, commentID int) (*Comment, error) { - var comment Comment +func (r *Repository) GetComment(ctx context.Context, commentID int) (model.Comment, error) { + var comment model.Comment err := r.db.QueryRow(ctx, "SELECT user_id, post_id, body, created_at, updated_at FROM posts WHERE user_id = $1", - commentID).Scan(&comment.userID, &comment.postID, &comment.body, &comment.createdAt, &comment.updatedAt) + commentID).Scan(&comment.UserID, &comment.PostID, &comment.Body, &comment.CreatedAt, &comment.UpdatedAt) if err != nil { - return nil, fmt.Errorf("error User r.db.Query: %w", err) + return model.Comment{}, fmt.Errorf("error User r.db.Query: %w", err) } - return &comment, nil + return comment, nil } -func (r *Repository) CreateUser(ctx context.Context, params CreateUserRequest) error { - _, err := r.db.Exec(ctx, "INSERT INTO users (name, email) VALUES ($1, $2)", params.name, params.email) +func (r *Repository) GetPostComments(ctx context.Context, postID string) ([]model.Comment, error) { + + rows, err := r.db.Query(ctx, "SELECT comment_id, post_id, user_id, body, created_at, updated_at FROM comments WHERE post_id = $1", postID) + + var comments []model.Comment + for rows.Next() { + var comment model.Comment + err = rows.Scan(&comment.CommentID, &comment.PostID, &comment.UserID, &comment.Body, &comment.CreatedAt, &comment.UpdatedAt) + if err != nil { + return nil, err + } + comments = append(comments, comment) + } + + if err = rows.Err(); err != nil { + return nil, err + } + + return comments, nil +} + +func (r *Repository) GetUserPosts(ctx context.Context, userID string) ([]model.Post, error) { + + rows, err := r.db.Query(ctx, "SELECT post_id, title, body, views, created_at, updated_at FROM posts WHERE post_id = $1", userID) + + var posts []model.Post + for rows.Next() { + var post model.Post + err = rows.Scan(&post.PostID, &post.Title, &post.Body, post.Views, &post.CreatedAt, &post.UpdatedAt) + if err != nil { + return nil, err + } + posts = append(posts, post) + } + + if err = rows.Err(); err != nil { + return nil, err + } + + return posts, nil +} + +func (r *Repository) CreateUser(ctx context.Context, user CreateUserRequest) error { + _, err := r.db.Exec(ctx, "INSERT INTO users (name, email, hashed_password, is_admin) VALUES ($1, $2, $3, $4)", user.Name, user.Email, user.HashedPassword, user.IsAdmin) if err != nil { return fmt.Errorf("error CreateUser Exec: %w", err) } @@ -124,44 +154,70 @@ func (r *Repository) CreateComment(ctx context.Context, params CreateCommentRequ return nil } -func (r *Repository) GetPostComments(ctx context.Context, postID string) ([]Comment, error) { +func (r *Repository) DeleteUser(ctx context.Context, userID int) error { + _, err := r.db.Exec(ctx, "DELETE * FROM users WHERE id = $1", userID) + if err != nil { + return fmt.Errorf("error DeleteUser Exec: %w", err) + } - rows, err := r.db.Query(ctx, "SELECT comment_id, post_id, user_id, body, created_at, updated_at FROM comments WHERE post_id = $1", postID) + return nil +} - var comments []Comment - for rows.Next() { - var comment Comment - err = rows.Scan(&comment.commentID, &comment.postID, &comment.userID, &comment.body, &comment.createdAt, &comment.updatedAt) - if err != nil { - return nil, err - } - comments = append(comments, comment) +func (r *Repository) DeletePost(ctx context.Context, postID int) error { + _, err := r.db.Exec(ctx, "DELETE * FROM posts WHERE id = $1", postID) + if err != nil { + return fmt.Errorf("error DeletePost Exec: %w", err) } - if err = rows.Err(); err != nil { - return nil, err + return nil +} + +func (r *Repository) DeleteComment(ctx context.Context, commentID int) error { + _, err := r.db.Exec(ctx, "DELETE * FROM comments WHERE id = $1", commentID) + if err != nil { + return fmt.Errorf("error DeleteComment Exec: %w", err) } - return comments, nil + return nil } -func (r *Repository) GetUserPosts(ctx context.Context, userID string) ([]Post, error) { +func (r *Repository) UpdateUser(ctx context.Context, userID int, user UpdateUser) (model.User, error) { + updatedUser := model.User{} + err := r.db.QueryRow( + ctx, + `UPDATE users + set name=COALESCE($1, name), + email=COALESCE($2, email), + is_admin=COALESCE($3, is_admin), + updated_at=now() WHERE id=$4 + returning id, + name, + email, + is_admin, + created_at, + updated_at`, + user.Name, + user.Email, + user.IsAdmin, + userID, + ).Scan(updatedUser.UserID, updatedUser.Name, updatedUser.Email, updatedUser.IsAdmin, updatedUser.CreatedAt, updatedUser.UpdatedAt) + if err != nil { + return model.User{}, fmt.Errorf("error UpdateUser: %w", err) + } - rows, err := r.db.Query(ctx, "SELECT post_id, title, body, views, created_at, updated_at FROM posts WHERE post_id = $1", userID) + return updatedUser, nil +} - var posts []Post - for rows.Next() { - var post Post - err = rows.Scan(&post.postID, &post.title, &post.body, post.views, &post.createdAt, &post.updatedAt) - if err != nil { - return nil, err - } - posts = append(posts, post) - } +func (r *Repository) UpdatePost() { - if err = rows.Err(); err != nil { - return nil, err +} + +func (r *Repository) UpdateComment(ctx context.Context, commentID int) (UpdateComment, error) { + var updatedComment UpdateComment + err := r.db.QueryRow(ctx, `UPDATE comments set body=$1 WHERE id=$2`, commentID).Scan(updatedComment.body) + if err != nil { + return UpdateComment{}, fmt.Errorf("error UpdateComment Exec: %w", err) } - return posts, nil + return updatedComment, nil } diff --git a/internal/service/service.go b/internal/service/service.go index 6d43c33..4609c67 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -1 +1,44 @@ package service + +import ( + "context" + "fmt" + "github.com/berduk-dev/blog/internal/model" + "github.com/berduk-dev/blog/internal/repo" + "golang.org/x/crypto/bcrypt" +) + +type Service struct { + repo repo.Repository +} + +func New(repo repo.Repository) *Service { + return &Service{ + repo: repo, + } +} + +func (s *Service) CreateUser(ctx context.Context, user model.CreateUserReq) error { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) + if err != nil { + return fmt.Errorf("error bcrypt.GenerateFromPassword: %w", err) + } + + err = s.repo.CreateUser(ctx, repo.CreateUserRequest{ + Name: user.Name, + HashedPassword: string(hashedPassword), + Email: user.Email, + }) + if err != nil { + return fmt.Errorf("error repo.CreateUser: %w", err) + } + return nil +} + +func (s *Service) GetUser(ctx context.Context, userID int) (model.User, error) { + user, err := s.repo.GetUser(ctx, userID) + if err != nil { + return model.User{}, fmt.Errorf("error repo.GetUser: %w", err) + } + return user, nil +} diff --git a/main.go b/main.go index 06ab7d0..ee4377e 100644 --- a/main.go +++ b/main.go @@ -1 +1,36 @@ package main + +import ( + "context" + "github.com/berduk-dev/blog/internal/handler" + "github.com/berduk-dev/blog/internal/repo" + "github.com/berduk-dev/blog/internal/service" + + "log" + + "github.com/gin-gonic/gin" + "github.com/jackc/pgx/v5" +) + +func main() { + + connString := "postgres://admin:admin@localhost:5433/postgres" + conn, err := pgx.Connect(context.Background(), connString) + if err != nil { + log.Fatal("Ошибка при подключении к БД: ", err) + } + + r := gin.Default() + + blogRepository := repo.New(conn) + blogService := service.New(blogRepository) + blogHandler := handler.New(*blogService) + + r.POST("/create", blogHandler.CreateUser) + r.GET("/user/:id", blogHandler.GetUser) + //r.POST("/shorten/:custom", linksHandler.CreateLink) // можно убрать, если перешёл на JSON-поле "custom" + //r.GET("/analytics/:short_url", linksHandler.GetAnalytics) + //r.GET("/:path", linksHandler.Redirect) + + r.Run() +} diff --git a/migrations/20250901111339_init.sql b/migrations/20250901111339_init.sql index cec5d71..fd9e525 100644 --- a/migrations/20250901111339_init.sql +++ b/migrations/20250901111339_init.sql @@ -14,6 +14,7 @@ create table users ( id bigserial primary key, name text not null, email text not null, + is_admin boolean not null default false, created_at timestamp not null default now(), updated_at timestamp not null default now() diff --git a/migrations/20250909170255_add_email_unique_constraint.sql b/migrations/20250909170255_add_email_unique_constraint.sql new file mode 100644 index 0000000..035336f --- /dev/null +++ b/migrations/20250909170255_add_email_unique_constraint.sql @@ -0,0 +1,10 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE users + add constraint users_email_key unique (email); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +SELECT 'down SQL query'; +-- +goose StatementEnd diff --git a/migrations/20250909170513_add_hashed_password_column.sql b/migrations/20250909170513_add_hashed_password_column.sql new file mode 100644 index 0000000..f92a6f0 --- /dev/null +++ b/migrations/20250909170513_add_hashed_password_column.sql @@ -0,0 +1,10 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE users + ADD COLUMN hashed_password TEXT NOT NULL; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +SELECT 'down SQL query'; +-- +goose StatementEnd From a4a355a8c09aa3973afc7b6fad7702fd4c8e9d53 Mon Sep 17 00:00:00 2001 From: Win-10 Date: Thu, 11 Sep 2025 02:35:18 +0300 Subject: [PATCH 5/9] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B1=D0=B0=D0=B3=20=D1=81=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=D0=BC=20=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=D0=BC=20=D1=8E?= =?UTF-8?q?=D0=B7=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/handler/handler.go | 7 ++++++- internal/model/model.go | 2 +- internal/repo/repo.go | 10 +++++----- main.go | 9 +++------ .../20250909170255_add_email_unique_constraint.sql | 1 - .../20250909170513_add_hashed_password_column.sql | 1 - 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 0f3666e..2ca5686 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -5,6 +5,7 @@ import ( "github.com/berduk-dev/blog/internal/model" "github.com/berduk-dev/blog/internal/service" "github.com/gin-gonic/gin" + "log" "net/http" "strconv" ) @@ -24,12 +25,14 @@ func (h *Handler) CreateUser(c *gin.Context) { err := c.ShouldBindJSON(&req) if err != nil { c.JSON(http.StatusBadRequest, "У вас невалидный запрос") + log.Println("error ShouldBindJSON: ", err) return } err = h.service.CreateUser(c, req) if err != nil { c.JSON(http.StatusInternalServerError, "Попробуйте позже") + log.Println("error service.CreateUser: ", err) return } @@ -40,17 +43,19 @@ func (h *Handler) GetUser(c *gin.Context) { userID, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, fmt.Errorf("error GetUser strconv.Atoi: %w", err)) + log.Println("error GetUser strconv.Atoi: ", err) return } user, err := h.service.GetUser(c, userID) if err != nil { c.JSON(http.StatusInternalServerError, fmt.Errorf("error service.GetUser: %w", err)) + log.Println("error service.GetUser: ", err) return } c.JSON(http.StatusOK, gin.H{ - "user_id": user.UserID, + "id": user.UserID, "name": user.Name, "email": user.Email, "is_admin": user.IsAdmin, diff --git a/internal/model/model.go b/internal/model/model.go index 0855458..c9f53af 100644 --- a/internal/model/model.go +++ b/internal/model/model.go @@ -3,7 +3,7 @@ package model import "time" type User struct { - UserID int `json:"user_id"` + UserID int `json:"id"` Name string `json:"name"` Email string `json:"email"` IsAdmin bool `json:"is_admin"` diff --git a/internal/repo/repo.go b/internal/repo/repo.go index 9a055ee..8b1971a 100644 --- a/internal/repo/repo.go +++ b/internal/repo/repo.go @@ -53,7 +53,7 @@ type UpdateComment struct { func (r *Repository) GetUser(ctx context.Context, userID int) (model.User, error) { var user model.User - err := r.db.QueryRow(ctx, "SELECT id, name, email, is_admin, created_at, updated_at FROM users WHERE user_id = $1", userID).Scan(&user.UserID, &user.Name, &user.Email, &user.IsAdmin, &user.CreatedAt, &user.UpdatedAt) + err := r.db.QueryRow(ctx, "SELECT id, name, email, is_admin, created_at, updated_at FROM users WHERE id = $1", userID).Scan(&user.UserID, &user.Name, &user.Email, &user.IsAdmin, &user.CreatedAt, &user.UpdatedAt) if err != nil { return model.User{}, fmt.Errorf("error User r.db.Query: %w", err) } @@ -64,7 +64,7 @@ func (r *Repository) GetUser(ctx context.Context, userID int) (model.User, error func (r *Repository) GetPost(ctx context.Context, postID int) (model.Post, error) { var post model.Post - err := r.db.QueryRow(ctx, "SELECT user_id, title, body, views, created_at, updated_at FROM posts WHERE user_id = $1", postID).Scan(&post.UserID, &post.Title, &post.Body, &post.Views, &post.CreatedAt, &post.UpdatedAt) + err := r.db.QueryRow(ctx, "SELECT id, title, body, views, created_at, updated_at FROM posts WHERE id = $1", postID).Scan(&post.UserID, &post.Title, &post.Body, &post.Views, &post.CreatedAt, &post.UpdatedAt) if err != nil { return model.Post{}, fmt.Errorf("error User r.db.Query: %w", err) } @@ -76,8 +76,8 @@ func (r *Repository) GetComment(ctx context.Context, commentID int) (model.Comme var comment model.Comment err := r.db.QueryRow(ctx, - "SELECT user_id, post_id, body, created_at, updated_at FROM posts WHERE user_id = $1", - commentID).Scan(&comment.UserID, &comment.PostID, &comment.Body, &comment.CreatedAt, &comment.UpdatedAt) + "SELECT id, user_id, post_id, body, created_at, updated_at FROM posts WHERE id = $1", + commentID).Scan(&comment.CommentID, &comment.UserID, &comment.PostID, &comment.Body, &comment.CreatedAt, &comment.UpdatedAt) if err != nil { return model.Comment{}, fmt.Errorf("error User r.db.Query: %w", err) } @@ -87,7 +87,7 @@ func (r *Repository) GetComment(ctx context.Context, commentID int) (model.Comme func (r *Repository) GetPostComments(ctx context.Context, postID string) ([]model.Comment, error) { - rows, err := r.db.Query(ctx, "SELECT comment_id, post_id, user_id, body, created_at, updated_at FROM comments WHERE post_id = $1", postID) + rows, err := r.db.Query(ctx, "SELECT id, post_id, id, body, created_at, updated_at FROM comments WHERE post_id = $1", postID) var comments []model.Comment for rows.Next() { diff --git a/main.go b/main.go index ee4377e..7e0e767 100644 --- a/main.go +++ b/main.go @@ -14,7 +14,7 @@ import ( func main() { - connString := "postgres://admin:admin@localhost:5433/postgres" + connString := "postgres://admin:admin@localhost:5433/blog" conn, err := pgx.Connect(context.Background(), connString) if err != nil { log.Fatal("Ошибка при подключении к БД: ", err) @@ -26,11 +26,8 @@ func main() { blogService := service.New(blogRepository) blogHandler := handler.New(*blogService) - r.POST("/create", blogHandler.CreateUser) + r.POST("/users", blogHandler.CreateUser) r.GET("/user/:id", blogHandler.GetUser) - //r.POST("/shorten/:custom", linksHandler.CreateLink) // можно убрать, если перешёл на JSON-поле "custom" - //r.GET("/analytics/:short_url", linksHandler.GetAnalytics) - //r.GET("/:path", linksHandler.Redirect) - r.Run() + r.Run(":8088") } diff --git a/migrations/20250909170255_add_email_unique_constraint.sql b/migrations/20250909170255_add_email_unique_constraint.sql index 035336f..a12bdae 100644 --- a/migrations/20250909170255_add_email_unique_constraint.sql +++ b/migrations/20250909170255_add_email_unique_constraint.sql @@ -6,5 +6,4 @@ ALTER TABLE users -- +goose Down -- +goose StatementBegin -SELECT 'down SQL query'; -- +goose StatementEnd diff --git a/migrations/20250909170513_add_hashed_password_column.sql b/migrations/20250909170513_add_hashed_password_column.sql index f92a6f0..f0f920f 100644 --- a/migrations/20250909170513_add_hashed_password_column.sql +++ b/migrations/20250909170513_add_hashed_password_column.sql @@ -6,5 +6,4 @@ ALTER TABLE users -- +goose Down -- +goose StatementBegin -SELECT 'down SQL query'; -- +goose StatementEnd From 0a38772f6d36bca2de784baf4795f39d3f802aff Mon Sep 17 00:00:00 2001 From: Win-10 Date: Fri, 12 Sep 2025 02:23:30 +0300 Subject: [PATCH 6/9] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8=20=D0=B2?= =?UTF-8?q?=D0=B7=D0=B0=D0=B8=D0=BC=D0=BE=D0=B4=D0=B5=D0=B9=D1=81=D1=82?= =?UTF-8?q?=D0=B2=D0=B8=D1=8F=20=D1=81=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D1=8F=D0=BC=D0=B8=20=D0=B8=20?= =?UTF-8?q?=D0=BD=D0=B5=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B5=20=D0=B4?= =?UTF-8?q?=D1=80=D1=83=D0=B3=D0=B8=D0=B5=20=D0=BD=D0=B5=D0=B4=D0=BE=D1=87?= =?UTF-8?q?=D0=B5=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO.md | 6 ++-- internal/handler/handler.go | 2 +- internal/model/model.go | 28 +++++++++---------- internal/repo/repo.go | 56 +++++++++++++++++++++++++------------ internal/service/service.go | 4 +-- main.go | 2 +- 6 files changed, 59 insertions(+), 39 deletions(-) diff --git a/TODO.md b/TODO.md index 6cb4cb8..8da9621 100644 --- a/TODO.md +++ b/TODO.md @@ -2,16 +2,16 @@ ### Общее 1. [X] Удалить cache -2. [X Удалить manager +2. [X] Удалить manager 3. [X] Занести нужные структуры в model.go 4. [X] Подумать, какие будут эндпоинты -5. [ ] Поправить main.go +5. [X] Поправить main.go 6. [X] Добавить у User поле hashed_password 7. [X] Добавить constraint unique у email ### Задачи в handlers 1. [X] Создание нового юзера -2. [ ] Получение нового юзера +2. [X] Получение нового юзера 3. [ ] Создание нового поста 4. [ ] Получение поста по id 5. [ ] Получение всех постов юзера по user_id diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 2ca5686..be362f3 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -55,7 +55,7 @@ func (h *Handler) GetUser(c *gin.Context) { } c.JSON(http.StatusOK, gin.H{ - "id": user.UserID, + "id": user.ID, "name": user.Name, "email": user.Email, "is_admin": user.IsAdmin, diff --git a/internal/model/model.go b/internal/model/model.go index c9f53af..5abc957 100644 --- a/internal/model/model.go +++ b/internal/model/model.go @@ -3,7 +3,7 @@ package model import "time" type User struct { - UserID int `json:"id"` + ID int `json:"id"` Name string `json:"name"` Email string `json:"email"` IsAdmin bool `json:"is_admin"` @@ -18,20 +18,20 @@ type CreateUserReq struct { } type Post struct { - PostID int - UserID int - Title string - Body string - Views int - CreatedAt time.Time - UpdatedAt time.Time + ID int `json:"id"` + UserID int `json:"user_id"` + Title string `json:"title"` + Body string `json:"body"` + Views int `json:"views"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } type Comment struct { - CommentID int - PostID int - UserID int - Body string - CreatedAt time.Time - UpdatedAt time.Time + ID int `json:"id"` + PostID int `json:"post_id"` + UserID int `json:"user_id"` + Body string `json:"body"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } diff --git a/internal/repo/repo.go b/internal/repo/repo.go index 8b1971a..d90f813 100644 --- a/internal/repo/repo.go +++ b/internal/repo/repo.go @@ -53,9 +53,9 @@ type UpdateComment struct { func (r *Repository) GetUser(ctx context.Context, userID int) (model.User, error) { var user model.User - err := r.db.QueryRow(ctx, "SELECT id, name, email, is_admin, created_at, updated_at FROM users WHERE id = $1", userID).Scan(&user.UserID, &user.Name, &user.Email, &user.IsAdmin, &user.CreatedAt, &user.UpdatedAt) + err := r.db.QueryRow(ctx, "SELECT id, name, email, is_admin, created_at, updated_at FROM users WHERE id = $1", userID).Scan(&user.ID, &user.Name, &user.Email, &user.IsAdmin, &user.CreatedAt, &user.UpdatedAt) if err != nil { - return model.User{}, fmt.Errorf("error User r.db.Query: %w", err) + return model.User{}, fmt.Errorf("error GetUser QueryRow: %w", err) } return user, nil @@ -64,9 +64,17 @@ func (r *Repository) GetUser(ctx context.Context, userID int) (model.User, error func (r *Repository) GetPost(ctx context.Context, postID int) (model.Post, error) { var post model.Post - err := r.db.QueryRow(ctx, "SELECT id, title, body, views, created_at, updated_at FROM posts WHERE id = $1", postID).Scan(&post.UserID, &post.Title, &post.Body, &post.Views, &post.CreatedAt, &post.UpdatedAt) + err := r.db.QueryRow(ctx, + `SELECT id, + user_id, + title, + body, + views, + created_at, + updated_at FROM posts WHERE id = $1`, postID, + ).Scan(&post.ID, &post.UserID, &post.Title, &post.Views, &post.Body, &post.CreatedAt, &post.UpdatedAt) if err != nil { - return model.Post{}, fmt.Errorf("error User r.db.Query: %w", err) + return model.Post{}, fmt.Errorf("error GetPost QueryRow: %w", err) } return post, nil @@ -77,9 +85,9 @@ func (r *Repository) GetComment(ctx context.Context, commentID int) (model.Comme err := r.db.QueryRow(ctx, "SELECT id, user_id, post_id, body, created_at, updated_at FROM posts WHERE id = $1", - commentID).Scan(&comment.CommentID, &comment.UserID, &comment.PostID, &comment.Body, &comment.CreatedAt, &comment.UpdatedAt) + commentID).Scan(&comment.ID, &comment.UserID, &comment.PostID, &comment.Body, &comment.CreatedAt, &comment.UpdatedAt) if err != nil { - return model.Comment{}, fmt.Errorf("error User r.db.Query: %w", err) + return model.Comment{}, fmt.Errorf("error GetComment QueryRow: %w", err) } return comment, nil @@ -87,12 +95,12 @@ func (r *Repository) GetComment(ctx context.Context, commentID int) (model.Comme func (r *Repository) GetPostComments(ctx context.Context, postID string) ([]model.Comment, error) { - rows, err := r.db.Query(ctx, "SELECT id, post_id, id, body, created_at, updated_at FROM comments WHERE post_id = $1", postID) + rows, err := r.db.Query(ctx, "SELECT id, post_id, user_id, body, created_at, updated_at FROM comments WHERE post_id = $1", postID) var comments []model.Comment for rows.Next() { var comment model.Comment - err = rows.Scan(&comment.CommentID, &comment.PostID, &comment.UserID, &comment.Body, &comment.CreatedAt, &comment.UpdatedAt) + err = rows.Scan(&comment.ID, &comment.PostID, &comment.UserID, &comment.Body, &comment.CreatedAt, &comment.UpdatedAt) if err != nil { return nil, err } @@ -113,7 +121,7 @@ func (r *Repository) GetUserPosts(ctx context.Context, userID string) ([]model.P var posts []model.Post for rows.Next() { var post model.Post - err = rows.Scan(&post.PostID, &post.Title, &post.Body, post.Views, &post.CreatedAt, &post.UpdatedAt) + err = rows.Scan(&post.ID, &post.Title, &post.Body, post.Views, &post.CreatedAt, &post.UpdatedAt) if err != nil { return nil, err } @@ -146,7 +154,7 @@ func (r *Repository) CreatePost(ctx context.Context, params CreatePostRequest) e } func (r *Repository) CreateComment(ctx context.Context, params CreateCommentRequest) error { - _, err := r.db.Exec(ctx, "INSERT INTO comments (user_id, title, body) VALUES ($1, $2, $3)", params.userID, params.postID, params.body) + _, err := r.db.Exec(ctx, "INSERT INTO comments (user_id, post_id, body) VALUES ($1, $2, $3)", params.userID, params.postID, params.body) if err != nil { return fmt.Errorf("error CreateComment Exec: %w", err) } @@ -155,7 +163,7 @@ func (r *Repository) CreateComment(ctx context.Context, params CreateCommentRequ } func (r *Repository) DeleteUser(ctx context.Context, userID int) error { - _, err := r.db.Exec(ctx, "DELETE * FROM users WHERE id = $1", userID) + _, err := r.db.Exec(ctx, "DELETE FROM users WHERE id = $1", userID) if err != nil { return fmt.Errorf("error DeleteUser Exec: %w", err) } @@ -164,7 +172,7 @@ func (r *Repository) DeleteUser(ctx context.Context, userID int) error { } func (r *Repository) DeletePost(ctx context.Context, postID int) error { - _, err := r.db.Exec(ctx, "DELETE * FROM posts WHERE id = $1", postID) + _, err := r.db.Exec(ctx, "DELETE FROM posts WHERE id = $1", postID) if err != nil { return fmt.Errorf("error DeletePost Exec: %w", err) } @@ -173,7 +181,7 @@ func (r *Repository) DeletePost(ctx context.Context, postID int) error { } func (r *Repository) DeleteComment(ctx context.Context, commentID int) error { - _, err := r.db.Exec(ctx, "DELETE * FROM comments WHERE id = $1", commentID) + _, err := r.db.Exec(ctx, "DELETE FROM comments WHERE id = $1", commentID) if err != nil { return fmt.Errorf("error DeleteComment Exec: %w", err) } @@ -200,7 +208,7 @@ func (r *Repository) UpdateUser(ctx context.Context, userID int, user UpdateUser user.Email, user.IsAdmin, userID, - ).Scan(updatedUser.UserID, updatedUser.Name, updatedUser.Email, updatedUser.IsAdmin, updatedUser.CreatedAt, updatedUser.UpdatedAt) + ).Scan(updatedUser.ID, updatedUser.Name, updatedUser.Email, updatedUser.IsAdmin, updatedUser.CreatedAt, updatedUser.UpdatedAt) if err != nil { return model.User{}, fmt.Errorf("error UpdateUser: %w", err) } @@ -212,11 +220,23 @@ func (r *Repository) UpdatePost() { } -func (r *Repository) UpdateComment(ctx context.Context, commentID int) (UpdateComment, error) { - var updatedComment UpdateComment - err := r.db.QueryRow(ctx, `UPDATE comments set body=$1 WHERE id=$2`, commentID).Scan(updatedComment.body) +func (r *Repository) UpdateComment(ctx context.Context, commentID int, comment UpdateComment) (model.Comment, error) { + var updatedComment model.Comment + err := r.db.QueryRow( + ctx, + `UPDATE comments + set body=$1 WHERE id=$2 + returning id, + post_id, + user_id + body + created_at + updated_at`, + comment.body, + commentID, + ).Scan(updatedComment.ID, updatedComment.PostID, updatedComment.UserID, updatedComment.Body, updatedComment.CreatedAt, updatedComment.UpdatedAt) if err != nil { - return UpdateComment{}, fmt.Errorf("error UpdateComment Exec: %w", err) + return model.Comment{}, fmt.Errorf("error UpdateComment: %w", err) } return updatedComment, nil diff --git a/internal/service/service.go b/internal/service/service.go index 4609c67..e023538 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -12,8 +12,8 @@ type Service struct { repo repo.Repository } -func New(repo repo.Repository) *Service { - return &Service{ +func New(repo repo.Repository) Service { + return Service{ repo: repo, } } diff --git a/main.go b/main.go index 7e0e767..f99d259 100644 --- a/main.go +++ b/main.go @@ -24,7 +24,7 @@ func main() { blogRepository := repo.New(conn) blogService := service.New(blogRepository) - blogHandler := handler.New(*blogService) + blogHandler := handler.New(blogService) r.POST("/users", blogHandler.CreateUser) r.GET("/user/:id", blogHandler.GetUser) From 55bb802b9dd6db738a7c263c59f06e1a5fa2275e Mon Sep 17 00:00:00 2001 From: Win-10 Date: Sat, 13 Sep 2025 01:00:27 +0300 Subject: [PATCH 7/9] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D1=8D=D0=BD=D0=B4=D0=BF=D0=BE=D0=B8=D0=BD=D1=82=D1=8B=20Get?= =?UTF-8?q?=20=D0=B8=20Post=20=D0=B4=D0=BB=D1=8F=20user,=20post,=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO.md | 18 ++--- docs/endpoints.md | 10 +++ internal/handler/handler.go | 137 +++++++++++++++++++++++++++++++----- internal/model/model.go | 9 +++ internal/repo/repo.go | 75 ++++++++++++++------ internal/service/service.go | 62 ++++++++++++++++ main.go | 10 ++- 7 files changed, 275 insertions(+), 46 deletions(-) create mode 100644 docs/endpoints.md diff --git a/TODO.md b/TODO.md index 8da9621..a50a3ca 100644 --- a/TODO.md +++ b/TODO.md @@ -8,24 +8,26 @@ 5. [X] Поправить main.go 6. [X] Добавить у User поле hashed_password 7. [X] Добавить constraint unique у email +8. [ ] Добавить возможность отвечать на другие комментарии +9. [ ] Добавить Register и Log in и Refresh ### Задачи в handlers 1. [X] Создание нового юзера 2. [X] Получение нового юзера -3. [ ] Создание нового поста +3. [X] Создание нового поста 4. [ ] Получение поста по id -5. [ ] Получение всех постов юзера по user_id +5. [X] Получение всех постов юзера по user_id ### Задачи в service 1. [X] Создать метод для создания юзера 2. [X] Создать метод для получения юзера -3. [ ] Создание нового поста -4. [ ] Получение поста по id -5. [ ] Получение всех постов юзера по user_id +3. [X] Создание нового поста +4. [X] Получение поста по id +5. [X] Получение всех постов юзера по user_id ### Задачи в repo 1. [X] Создать метод для создания юзера 2. [X] Создать метод для получения юзера -3. [ ] Создание нового поста -4. [ ] Получение поста по id -5. [ ] Получение всех постов юзера по user_id +3. [X] Создание нового поста +4. [X] Получение поста по id +5. [X] Получение всех постов юзера по user_id diff --git a/docs/endpoints.md b/docs/endpoints.md new file mode 100644 index 0000000..337b350 --- /dev/null +++ b/docs/endpoints.md @@ -0,0 +1,10 @@ +| Status | HTTP Method | Endpoint | Method Name | Description | +|--------|-------------|----------------------------------------|---------------------|-------------------------------------------| +| ✅ | POST | `/users/` | CreateUser | Создание нового пользователя | +| ✅ | GET | `/users/:id/` | GetUser | Получение информации о пользователе по ID | +| ✅ | GET | `/users/:id/posts/` | GetPostsByUserID | Получение списка всех постов пользователя | +| ✅ | POST | `/posts/` | CreatePost | Создание нового поста | +| ✅ | GET | `/posts/` | GetPosts | Получение списка всех постов | +| ✅ | GET | `/posts/:id/` | GetPost | Получение информации о посте по ID | +| ✅ | POST | `/posts/:id/comments?reply_to=231e119` | CreateComment | Создание комментария к посту | +| ⏳ | GET | `/posts/:id/comments/` | GetCommentsByPostID | Получение всех комментариев к посту | diff --git a/internal/handler/handler.go b/internal/handler/handler.go index be362f3..13b1299 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -1,7 +1,6 @@ package handler import ( - "fmt" "github.com/berduk-dev/blog/internal/model" "github.com/berduk-dev/blog/internal/service" "github.com/gin-gonic/gin" @@ -22,17 +21,65 @@ func New(service service.Service) Handler { func (h *Handler) CreateUser(c *gin.Context) { var req model.CreateUserReq + err := c.ShouldBindJSON(&req) if err != nil { c.JSON(http.StatusBadRequest, "У вас невалидный запрос") - log.Println("error ShouldBindJSON: ", err) + log.Println("error ShouldBindJSON:", err) return } err = h.service.CreateUser(c, req) if err != nil { - c.JSON(http.StatusInternalServerError, "Попробуйте позже") - log.Println("error service.CreateUser: ", err) + c.JSON(http.StatusInternalServerError, "Произошла ошибка! Попробуйте позже") + log.Println("error service.CreateUser:", err) + return + } + + c.Status(http.StatusOK) +} + +func (h *Handler) CreatePost(c *gin.Context) { + var req model.CreatePostReq + + err := c.ShouldBindJSON(&req) + if err != nil { + c.JSON(http.StatusBadRequest, "У вас невалидный запрос") + log.Println("error ShouldBindJSON:", err) + return + } + + err = h.service.CreatePost(c, req) + if err != nil { + c.JSON(http.StatusInternalServerError, "Произошла ошибка! Попробуйте позже") + log.Println("error service.CreatePost:", err) + return + } + + c.Status(http.StatusOK) +} + +func (h *Handler) CreateComment(c *gin.Context) { + var req model.CreateCommentReq + + err := c.ShouldBindJSON(&req) + if err != nil { + c.JSON(http.StatusBadRequest, "У вас невалидный запрос") + log.Println("error ShouldBindJSON:", err) + return + } + + postID, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, "У вас невалидный запрос") + log.Println("error CreateComment, strconv.Atoi:", err) + return + } + + err = h.service.CreateComment(c, postID, req) + if err != nil { + c.JSON(http.StatusInternalServerError, "Произошла ошибка! Попробуйте позже") + log.Println("error service.CreateComment:", err) return } @@ -42,24 +89,82 @@ func (h *Handler) CreateUser(c *gin.Context) { func (h *Handler) GetUser(c *gin.Context) { userID, err := strconv.Atoi(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, fmt.Errorf("error GetUser strconv.Atoi: %w", err)) - log.Println("error GetUser strconv.Atoi: ", err) + c.JSON(http.StatusBadRequest, "У вас невалидный запрос") + log.Println("error GetUser, strconv.Atoi:", err) return } user, err := h.service.GetUser(c, userID) if err != nil { - c.JSON(http.StatusInternalServerError, fmt.Errorf("error service.GetUser: %w", err)) - log.Println("error service.GetUser: ", err) + c.JSON(http.StatusInternalServerError, "Произошла ошибка! Попробуйте позже") + log.Println("error service.GetUser:", err) + return + } + + c.JSON(http.StatusOK, user) +} + +func (h *Handler) GetPostsByUserID(c *gin.Context) { + userID, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, "У вас невалидный запрос") + log.Println("error GetPostsByUserID, strconv.Atoi:", err) + return + } + + posts, err := h.service.GetPostsByUserID(c, userID) + if err != nil { + c.JSON(http.StatusInternalServerError, "Произошла ошибка! Попробуйте позже") + log.Println("error service.GetPostsByUserID:", err) + return + } + + c.JSON(http.StatusOK, posts) +} + +func (h *Handler) GetPosts(c *gin.Context) { + posts, err := h.service.GetPosts(c) + if err != nil { + c.JSON(http.StatusInternalServerError, "Произошла ошибка! Попробуйте позже") + log.Println("error service.GetPosts:", err) + return + } + + c.JSON(http.StatusOK, posts) +} + +func (h *Handler) GetPost(c *gin.Context) { + postID, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, "У вас невалидный запрос") + log.Println("error GetPostsByUserID, strconv.Atoi:", err) + return + } + + post, err := h.service.GetPost(c, postID) + if err != nil { + c.JSON(http.StatusInternalServerError, "Произошла ошибка! Попробуйте позже") + log.Println("error service.GetPosts:", err) + return + } + + c.JSON(http.StatusOK, post) +} + +func (h *Handler) GetCommentsByPostID(c *gin.Context) { + postID, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, "У вас невалидный запрос") + log.Println("error GetCommentsByPostID, strconv.Atoi:", err) + return + } + + comments, err := h.service.GetCommentsByPostID(c, postID) + if err != nil { + c.JSON(http.StatusInternalServerError, "Произошла ошибка! Попробуйте позже") + log.Println("error service.GetCommentsByPostID:", err) return } - c.JSON(http.StatusOK, gin.H{ - "id": user.ID, - "name": user.Name, - "email": user.Email, - "is_admin": user.IsAdmin, - "created_at": user.CreatedAt, - "updated_at": user.UpdatedAt, - }) + c.JSON(http.StatusOK, comments) } diff --git a/internal/model/model.go b/internal/model/model.go index 5abc957..0fa4758 100644 --- a/internal/model/model.go +++ b/internal/model/model.go @@ -27,6 +27,11 @@ type Post struct { UpdatedAt time.Time `json:"updated_at"` } +type CreatePostReq struct { + Title string `json:"title"` + Body string `json:"body"` +} + type Comment struct { ID int `json:"id"` PostID int `json:"post_id"` @@ -35,3 +40,7 @@ type Comment struct { CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } + +type CreateCommentReq struct { + Body string `json:"body"` +} diff --git a/internal/repo/repo.go b/internal/repo/repo.go index d90f813..a7e9ede 100644 --- a/internal/repo/repo.go +++ b/internal/repo/repo.go @@ -30,24 +30,24 @@ type UpdateUser struct { IsAdmin *bool } -type CreatePostRequest struct { - userID int - title string - body string -} - type UpdatePost struct { Title int } +type CreatePostRequest struct { + UserID int + Title string + Body string +} + type CreateCommentRequest struct { - postID int - userID int - body string + PostID int + UserID int + Body string } type UpdateComment struct { - body string + Body string } func (r *Repository) GetUser(ctx context.Context, userID int) (model.User, error) { @@ -72,7 +72,7 @@ func (r *Repository) GetPost(ctx context.Context, postID int) (model.Post, error views, created_at, updated_at FROM posts WHERE id = $1`, postID, - ).Scan(&post.ID, &post.UserID, &post.Title, &post.Views, &post.Body, &post.CreatedAt, &post.UpdatedAt) + ).Scan(&post.ID, &post.UserID, &post.Title, &post.Body, &post.Views, &post.CreatedAt, &post.UpdatedAt) if err != nil { return model.Post{}, fmt.Errorf("error GetPost QueryRow: %w", err) } @@ -93,9 +93,9 @@ func (r *Repository) GetComment(ctx context.Context, commentID int) (model.Comme return comment, nil } -func (r *Repository) GetPostComments(ctx context.Context, postID string) ([]model.Comment, error) { - +func (r *Repository) GetCommentsByPostID(ctx context.Context, postID int) ([]model.Comment, error) { rows, err := r.db.Query(ctx, "SELECT id, post_id, user_id, body, created_at, updated_at FROM comments WHERE post_id = $1", postID) + defer rows.Close() var comments []model.Comment for rows.Next() { @@ -114,14 +114,21 @@ func (r *Repository) GetPostComments(ctx context.Context, postID string) ([]mode return comments, nil } -func (r *Repository) GetUserPosts(ctx context.Context, userID string) ([]model.Post, error) { - - rows, err := r.db.Query(ctx, "SELECT post_id, title, body, views, created_at, updated_at FROM posts WHERE post_id = $1", userID) +func (r *Repository) GetPostsByUserID(ctx context.Context, userID int) ([]model.Post, error) { + rows, err := r.db.Query(ctx, ` + SELECT id, user_id, title, body, views, created_at, updated_at + FROM posts WHERE user_id = $1 + ORDER BY created_at desc`, + userID) + if err != nil { + return nil, fmt.Errorf("query failed: %w", err) + } + defer rows.Close() var posts []model.Post for rows.Next() { var post model.Post - err = rows.Scan(&post.ID, &post.Title, &post.Body, post.Views, &post.CreatedAt, &post.UpdatedAt) + err = rows.Scan(&post.ID, &post.UserID, &post.Title, &post.Body, &post.Views, &post.CreatedAt, &post.UpdatedAt) if err != nil { return nil, err } @@ -135,6 +142,34 @@ func (r *Repository) GetUserPosts(ctx context.Context, userID string) ([]model.P return posts, nil } +func (r *Repository) GetPosts(ctx context.Context) ([]model.Post, error) { + rows, err := r.db.Query(ctx, ` + SELECT id, user_id, title, body, views, created_at, updated_at + FROM posts + ORDER BY created_at DESC + `) + if err != nil { + return nil, fmt.Errorf("query failed: %w", err) + } + defer rows.Close() + + var posts []model.Post + for rows.Next() { + var post model.Post + err = rows.Scan(&post.ID, &post.UserID, &post.Title, &post.Body, &post.Views, &post.CreatedAt, &post.UpdatedAt) + if err != nil { + return nil, fmt.Errorf("scan failed: %w", err) + } + posts = append(posts, post) + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("rows iteration failed: %w", err) + } + + return posts, nil +} + func (r *Repository) CreateUser(ctx context.Context, user CreateUserRequest) error { _, err := r.db.Exec(ctx, "INSERT INTO users (name, email, hashed_password, is_admin) VALUES ($1, $2, $3, $4)", user.Name, user.Email, user.HashedPassword, user.IsAdmin) if err != nil { @@ -145,7 +180,7 @@ func (r *Repository) CreateUser(ctx context.Context, user CreateUserRequest) err } func (r *Repository) CreatePost(ctx context.Context, params CreatePostRequest) error { - _, err := r.db.Exec(ctx, "INSERT INTO posts (user_id, title, body) VALUES ($1, $2, $3)", params.userID, params.title, params.body) + _, err := r.db.Exec(ctx, "INSERT INTO posts (user_id, title, body) VALUES ($1, $2, $3)", params.UserID, params.Title, params.Body) if err != nil { return fmt.Errorf("error CreatePost Exec: %w", err) } @@ -154,7 +189,7 @@ func (r *Repository) CreatePost(ctx context.Context, params CreatePostRequest) e } func (r *Repository) CreateComment(ctx context.Context, params CreateCommentRequest) error { - _, err := r.db.Exec(ctx, "INSERT INTO comments (user_id, post_id, body) VALUES ($1, $2, $3)", params.userID, params.postID, params.body) + _, err := r.db.Exec(ctx, "INSERT INTO comments (user_id, post_id, body) VALUES ($1, $2, $3)", params.UserID, params.PostID, params.Body) if err != nil { return fmt.Errorf("error CreateComment Exec: %w", err) } @@ -232,7 +267,7 @@ func (r *Repository) UpdateComment(ctx context.Context, commentID int, comment U body created_at updated_at`, - comment.body, + comment.Body, commentID, ).Scan(updatedComment.ID, updatedComment.PostID, updatedComment.UserID, updatedComment.Body, updatedComment.CreatedAt, updatedComment.UpdatedAt) if err != nil { diff --git a/internal/service/service.go b/internal/service/service.go index e023538..461079c 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -32,6 +32,31 @@ func (s *Service) CreateUser(ctx context.Context, user model.CreateUserReq) erro if err != nil { return fmt.Errorf("error repo.CreateUser: %w", err) } + + return nil +} + +func (s *Service) CreatePost(ctx context.Context, post model.CreatePostReq) error { + err := s.repo.CreatePost(ctx, repo.CreatePostRequest{ + Title: post.Title, + Body: post.Body, + }) + if err != nil { + return fmt.Errorf("error repo.CreatePost: %w", err) + } + + return nil +} + +func (s *Service) CreateComment(ctx context.Context, PostID int, comment model.CreateCommentReq) error { + err := s.repo.CreateComment(ctx, repo.CreateCommentRequest{ + PostID: PostID, + Body: comment.Body, + }) + if err != nil { + return fmt.Errorf("error repo.CreateComment: %w", err) + } + return nil } @@ -40,5 +65,42 @@ func (s *Service) GetUser(ctx context.Context, userID int) (model.User, error) { if err != nil { return model.User{}, fmt.Errorf("error repo.GetUser: %w", err) } + return user, nil } + +func (s *Service) GetPostsByUserID(ctx context.Context, userID int) ([]model.Post, error) { + posts, err := s.repo.GetPostsByUserID(ctx, userID) + if err != nil { + return nil, fmt.Errorf("error repo.GetPostsByUserID: %w", err) + } + + return posts, nil +} + +func (s *Service) GetPosts(ctx context.Context) ([]model.Post, error) { + posts, err := s.repo.GetPosts(ctx) + if err != nil { + return nil, fmt.Errorf("error repo.GetPosts: %w", err) + } + + return posts, nil +} + +func (s *Service) GetPost(ctx context.Context, postID int) (model.Post, error) { + posts, err := s.repo.GetPost(ctx, postID) + if err != nil { + return model.Post{}, fmt.Errorf("error repo.GetPost: %w", err) + } + + return posts, nil +} + +func (s *Service) GetCommentsByPostID(ctx context.Context, postID int) ([]model.Comment, error) { + comments, err := s.repo.GetCommentsByPostID(ctx, postID) + if err != nil { + return nil, fmt.Errorf("error repo.GetCommentsByPostID: %w", err) + } + + return comments, nil +} diff --git a/main.go b/main.go index f99d259..bb62930 100644 --- a/main.go +++ b/main.go @@ -26,8 +26,14 @@ func main() { blogService := service.New(blogRepository) blogHandler := handler.New(blogService) - r.POST("/users", blogHandler.CreateUser) - r.GET("/user/:id", blogHandler.GetUser) + r.POST("/users/", blogHandler.CreateUser) + r.GET("/users/:id/", blogHandler.GetUser) + r.GET("/users/:id/posts/", blogHandler.GetPostsByUserID) + r.POST("/posts/", blogHandler.CreatePost) + r.GET("/posts/", blogHandler.GetPosts) + r.GET("/posts/:id/", blogHandler.GetPost) + r.POST("/posts/:id/comments/", blogHandler.CreateComment) + r.GET("/posts/:id/comments/", blogHandler.GetCommentsByPostID) r.Run(":8088") } From 8e6117371fd997c2ed31aa648d56fd61e1cc7374 Mon Sep 17 00:00:00 2001 From: Amadi Azdaev Date: Wed, 17 Sep 2025 21:50:13 +0300 Subject: [PATCH 8/9] session auth --- .DS_Store | Bin 6148 -> 0 bytes .env | 2 +- TODO.md | 36 +- docker-compose.yaml | 23 + go.mod | 31 +- go.sum | 127 +---- index.html | 725 ++++++++++++++++++++++++++++ internal/cache/sessions.go | 50 ++ internal/handler/handler.go | 54 +-- internal/handler/users.go | 132 +++++ internal/middlewares/middlewares.go | 48 ++ internal/model/errs/errors.go | 5 + internal/model/model.go | 21 +- internal/repo/repo.go | 46 +- internal/service/service.go | 36 +- internal/service/sessions.go | 30 ++ main.go | 47 +- 17 files changed, 1202 insertions(+), 211 deletions(-) delete mode 100644 .DS_Store create mode 100644 docker-compose.yaml create mode 100644 index.html create mode 100644 internal/cache/sessions.go create mode 100644 internal/handler/users.go create mode 100644 internal/middlewares/middlewares.go create mode 100644 internal/model/errs/errors.go create mode 100644 internal/service/sessions.go diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 560cb5dc815f84477ba5c22359860dce8ccfbbe6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKOHRWu5Pegs55RsB4x>e+#}5Bx_4 z^z24>L5&X})D5<*J{$4fzc0TO7I0MdrGjNm) zux6`d7lv+~0cXG&*fXHthd@;<5;lhM(?Juj0K_4Oqp+>Nf*KPN76}_eKB0((5-n8H z6+h + + + + + Блог + + + +
+ +
+ +
+ + +
+
+

Последние посты

+ +
+
Загрузка постов...
+ +
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/internal/cache/sessions.go b/internal/cache/sessions.go new file mode 100644 index 0000000..1437b05 --- /dev/null +++ b/internal/cache/sessions.go @@ -0,0 +1,50 @@ +package cache + +import ( + "context" + "errors" + "fmt" + "strconv" + "time" + + "github.com/go-redis/redis" +) + +const SessionTtlHours = time.Hour * 24 * 7 + +type SessionsCache struct { + rdb *redis.Client +} + +func New(rdb *redis.Client) SessionsCache { + return SessionsCache{ + rdb: rdb, + } +} + +func (c *SessionsCache) StoreSession(ctx context.Context, userID int, sessionID string) error { + cmd := c.rdb.Set(fmt.Sprintf("session_%s", sessionID), userID, SessionTtlHours) + if cmd.Err() != nil { + return fmt.Errorf("error rdb.Set: %w", cmd.Err()) + } + + return nil +} + +func (c *SessionsCache) GetUserIDBySessionID(ctx context.Context, sessionID string) (bool, int, error) { + cmd := c.rdb.Get(fmt.Sprintf("session_%s", sessionID)) + if cmd.Err() != nil { + if errors.Is(cmd.Err(), redis.Nil) { + return false, 0, nil + } + return false, 0, fmt.Errorf("error rdb.Get: %w", cmd.Err()) + } + + userIdRaw := cmd.Val() + userID, err := strconv.Atoi(userIdRaw) + if err != nil { + return false, 0, fmt.Errorf("error strconv.Atoi: %w", err) + } + + return true, userID, nil +} diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 13b1299..0df9863 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -1,12 +1,13 @@ package handler import ( - "github.com/berduk-dev/blog/internal/model" - "github.com/berduk-dev/blog/internal/service" - "github.com/gin-gonic/gin" "log" "net/http" "strconv" + + "github.com/berduk-dev/blog/internal/model" + "github.com/berduk-dev/blog/internal/service" + "github.com/gin-gonic/gin" ) type Handler struct { @@ -19,29 +20,10 @@ func New(service service.Service) Handler { } } -func (h *Handler) CreateUser(c *gin.Context) { - var req model.CreateUserReq - - err := c.ShouldBindJSON(&req) - if err != nil { - c.JSON(http.StatusBadRequest, "У вас невалидный запрос") - log.Println("error ShouldBindJSON:", err) - return - } - - err = h.service.CreateUser(c, req) - if err != nil { - c.JSON(http.StatusInternalServerError, "Произошла ошибка! Попробуйте позже") - log.Println("error service.CreateUser:", err) - return - } - - c.Status(http.StatusOK) -} - func (h *Handler) CreatePost(c *gin.Context) { - var req model.CreatePostReq + user := c.Value("user").(model.User) + var req model.CreatePostReq err := c.ShouldBindJSON(&req) if err != nil { c.JSON(http.StatusBadRequest, "У вас невалидный запрос") @@ -49,7 +31,7 @@ func (h *Handler) CreatePost(c *gin.Context) { return } - err = h.service.CreatePost(c, req) + err = h.service.CreatePost(c, user.ID, req) if err != nil { c.JSON(http.StatusInternalServerError, "Произошла ошибка! Попробуйте позже") log.Println("error service.CreatePost:", err) @@ -60,6 +42,8 @@ func (h *Handler) CreatePost(c *gin.Context) { } func (h *Handler) CreateComment(c *gin.Context) { + user := c.Value("user").(model.User) + var req model.CreateCommentReq err := c.ShouldBindJSON(&req) @@ -76,7 +60,7 @@ func (h *Handler) CreateComment(c *gin.Context) { return } - err = h.service.CreateComment(c, postID, req) + err = h.service.CreateComment(c, user.ID, postID, req) if err != nil { c.JSON(http.StatusInternalServerError, "Произошла ошибка! Попробуйте позже") log.Println("error service.CreateComment:", err) @@ -86,24 +70,6 @@ func (h *Handler) CreateComment(c *gin.Context) { c.Status(http.StatusOK) } -func (h *Handler) GetUser(c *gin.Context) { - userID, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, "У вас невалидный запрос") - log.Println("error GetUser, strconv.Atoi:", err) - return - } - - user, err := h.service.GetUser(c, userID) - if err != nil { - c.JSON(http.StatusInternalServerError, "Произошла ошибка! Попробуйте позже") - log.Println("error service.GetUser:", err) - return - } - - c.JSON(http.StatusOK, user) -} - func (h *Handler) GetPostsByUserID(c *gin.Context) { userID, err := strconv.Atoi(c.Param("id")) if err != nil { diff --git a/internal/handler/users.go b/internal/handler/users.go new file mode 100644 index 0000000..4860eca --- /dev/null +++ b/internal/handler/users.go @@ -0,0 +1,132 @@ +package handler + +import ( + "errors" + "log" + "net/http" + "strconv" + "time" + + "github.com/berduk-dev/blog/internal/model" + "github.com/berduk-dev/blog/internal/model/errs" + "github.com/gin-gonic/gin" +) + +func (h *Handler) Register(c *gin.Context) { + var req model.CreateUserReq + + err := c.ShouldBindJSON(&req) + if err != nil { + c.JSON(http.StatusBadRequest, "У вас невалидный запрос") + log.Println("error ShouldBindJSON:", err) + return + } + + err = h.service.CreateUser(c, req) + if err != nil { + if errors.Is(err, errs.ErrorEmailAlreadyExists) { + c.JSON(http.StatusInternalServerError, "Эта почта уже используется") + return + } + + c.JSON(http.StatusInternalServerError, "Произошла ошибка! Попробуйте позже") + log.Println("error service.CreateUser:", err) + return + } + + c.Status(http.StatusOK) +} + +func (h *Handler) Login(c *gin.Context) { + var req model.LoginReq + + err := c.ShouldBindJSON(&req) + if err != nil { + c.JSON(http.StatusBadRequest, "У вас невалидный запрос") + log.Println("error ShouldBindJSON:", err) + return + } + + user, err := h.service.AuthenticateUser(c, req.Email, req.Password) + if err != nil { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + sessionID, err := h.service.CreateSession(c, user.ID) + if err != nil { + log.Println("error service.CreateSession:", err) + c.JSON(http.StatusInternalServerError, "Ошибка. Попробуйте позже") + return + } + + c.SetSameSite(http.SameSiteStrictMode) + c.SetCookie( + "session_id", // name + sessionID, // value + int(24*time.Hour.Seconds()), // maxAge + "/", // path + "", // domain + false, // secure (только HTTPS) + false, // httpOnly (недоступен для JS) + ) + + c.JSON(200, model.LoginResp{ + ID: user.ID, + Name: user.Name, + Email: user.Email, + IsAdmin: user.IsAdmin, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + }) +} + +func (h *Handler) GetCurrentUser(c *gin.Context) { + user := c.Value("user").(model.User) + c.JSON(http.StatusOK, model.LoginResp{ + ID: user.ID, + Name: user.Name, + Email: user.Email, + IsAdmin: user.IsAdmin, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + }) +} + +func (h *Handler) GetUser(c *gin.Context) { + userID, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, "У вас невалидный запрос") + log.Println("error GetUser, strconv.Atoi:", err) + return + } + + user, err := h.service.GetUser(c, userID) + if err != nil { + c.JSON(http.StatusInternalServerError, "Произошла ошибка! Попробуйте позже") + log.Println("error service.GetUser:", err) + return + } + + c.JSON(http.StatusOK, user) +} + +func (h *Handler) CreateUser(c *gin.Context) { + var req model.CreateUserReq + + err := c.ShouldBindJSON(&req) + if err != nil { + c.JSON(http.StatusBadRequest, "У вас невалидный запрос") + log.Println("error ShouldBindJSON:", err) + return + } + + err = h.service.CreateUser(c, req) + if err != nil { + c.JSON(http.StatusInternalServerError, "Произошла ошибка! Попробуйте позже") + log.Println("error service.CreateUser:", err) + return + } + + c.Status(http.StatusOK) +} diff --git a/internal/middlewares/middlewares.go b/internal/middlewares/middlewares.go new file mode 100644 index 0000000..6338082 --- /dev/null +++ b/internal/middlewares/middlewares.go @@ -0,0 +1,48 @@ +package middlewares + +import ( + "log" + "net/http" + + "github.com/berduk-dev/blog/internal/service" + "github.com/gin-gonic/gin" +) + +type Middleware struct { + service service.Service +} + +func New(service service.Service) Middleware { + return Middleware{ + service: service, + } +} + +func (m *Middleware) SessionAuthMiddleware(c *gin.Context) { + sessionID, err := c.Cookie("session_id") + if err != nil { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + found, userID, err := m.service.GetUserIDBySessionID(c, sessionID) + if err != nil { + log.Println("error service.GetUserIDBySessionID:", err) + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + if !found { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + user, err := m.service.GetUser(c, userID) + if err != nil { + log.Println("error service.GetUser:", err) + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + c.Set("user", user) +} diff --git a/internal/model/errs/errors.go b/internal/model/errs/errors.go new file mode 100644 index 0000000..2ca4e28 --- /dev/null +++ b/internal/model/errs/errors.go @@ -0,0 +1,5 @@ +package errs + +import "errors" + +var ErrorEmailAlreadyExists = errors.New("email уже существует") diff --git a/internal/model/model.go b/internal/model/model.go index 0fa4758..f697f23 100644 --- a/internal/model/model.go +++ b/internal/model/model.go @@ -3,6 +3,22 @@ package model import "time" type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + HashedPassword string + IsAdmin bool `json:"is_admin"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type CreateUserReq struct { + Name string `json:"name"` + Password string `json:"password"` + Email string `json:"email"` +} + +type LoginResp struct { ID int `json:"id"` Name string `json:"name"` Email string `json:"email"` @@ -11,10 +27,9 @@ type User struct { UpdatedAt time.Time `json:"updated_at"` } -type CreateUserReq struct { - Name string `json:"name"` - Password string `json:"password"` +type LoginReq struct { Email string `json:"email"` + Password string `json:"password"` } type Post struct { diff --git a/internal/repo/repo.go b/internal/repo/repo.go index a7e9ede..3a960a7 100644 --- a/internal/repo/repo.go +++ b/internal/repo/repo.go @@ -2,9 +2,15 @@ package repo import ( "context" + "errors" "fmt" + "log" + "strings" + "github.com/berduk-dev/blog/internal/model" + "github.com/berduk-dev/blog/internal/model/errs" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" ) type Repository struct { @@ -173,6 +179,13 @@ func (r *Repository) GetPosts(ctx context.Context) ([]model.Post, error) { func (r *Repository) CreateUser(ctx context.Context, user CreateUserRequest) error { _, err := r.db.Exec(ctx, "INSERT INTO users (name, email, hashed_password, is_admin) VALUES ($1, $2, $3, $4)", user.Name, user.Email, user.HashedPassword, user.IsAdmin) if err != nil { + log.Println("repo.CreateUser: ", err) + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) && pgErr.Code == "23505" { // unique_violation + if strings.Contains(pgErr.ConstraintName, "email") { + return errs.ErrorEmailAlreadyExists + } + } return fmt.Errorf("error CreateUser Exec: %w", err) } @@ -265,7 +278,7 @@ func (r *Repository) UpdateComment(ctx context.Context, commentID int, comment U post_id, user_id body - created_at + created_at updated_at`, comment.Body, commentID, @@ -276,3 +289,34 @@ func (r *Repository) UpdateComment(ctx context.Context, commentID int, comment U return updatedComment, nil } + +func (r *Repository) GetUserByEmail(ctx context.Context, email string) (model.User, error) { + var user model.User + + err := r.db.QueryRow( + ctx, + `SELECT + id, + name, + hashed_password, + email, + is_admin, + created_at, + updated_at + FROM users + WHERE email = $1`, + email).Scan( + &user.ID, + &user.Name, + &user.HashedPassword, + &user.Email, + &user.IsAdmin, + &user.CreatedAt, + &user.UpdatedAt, + ) + if err != nil { + return model.User{}, fmt.Errorf("error GetUserByEmail Scan: %w", err) + } + + return user, nil +} diff --git a/internal/service/service.go b/internal/service/service.go index 461079c..9b731a0 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -3,18 +3,22 @@ package service import ( "context" "fmt" + + "github.com/berduk-dev/blog/internal/cache" "github.com/berduk-dev/blog/internal/model" "github.com/berduk-dev/blog/internal/repo" "golang.org/x/crypto/bcrypt" ) type Service struct { - repo repo.Repository + repo repo.Repository + sessionsManger cache.SessionsCache } -func New(repo repo.Repository) Service { +func New(repo repo.Repository, sessionsManger cache.SessionsCache) Service { return Service{ - repo: repo, + repo: repo, + sessionsManger: sessionsManger, } } @@ -30,16 +34,17 @@ func (s *Service) CreateUser(ctx context.Context, user model.CreateUserReq) erro Email: user.Email, }) if err != nil { - return fmt.Errorf("error repo.CreateUser: %w", err) + return fmt.Errorf("error repo.CreateUser: %w", err) // "error repo.CreateUser: email уже существует" } return nil } -func (s *Service) CreatePost(ctx context.Context, post model.CreatePostReq) error { +func (s *Service) CreatePost(ctx context.Context, userID int, post model.CreatePostReq) error { err := s.repo.CreatePost(ctx, repo.CreatePostRequest{ - Title: post.Title, - Body: post.Body, + UserID: userID, + Title: post.Title, + Body: post.Body, }) if err != nil { return fmt.Errorf("error repo.CreatePost: %w", err) @@ -48,8 +53,9 @@ func (s *Service) CreatePost(ctx context.Context, post model.CreatePostReq) erro return nil } -func (s *Service) CreateComment(ctx context.Context, PostID int, comment model.CreateCommentReq) error { +func (s *Service) CreateComment(ctx context.Context, userID int, PostID int, comment model.CreateCommentReq) error { err := s.repo.CreateComment(ctx, repo.CreateCommentRequest{ + UserID: userID, PostID: PostID, Body: comment.Body, }) @@ -69,6 +75,20 @@ func (s *Service) GetUser(ctx context.Context, userID int) (model.User, error) { return user, nil } +func (s *Service) AuthenticateUser(ctx context.Context, email string, password string) (model.User, error) { + user, err := s.repo.GetUserByEmail(ctx, email) + if err != nil { + return model.User{}, fmt.Errorf("error repo.GetUserByEmail: %w", err) + } + + err = bcrypt.CompareHashAndPassword([]byte(user.HashedPassword), []byte(password)) + if err != nil { + return model.User{}, fmt.Errorf("invalid email or password") // TODO: вынести ошибку в пакет + } + + return user, nil +} + func (s *Service) GetPostsByUserID(ctx context.Context, userID int) ([]model.Post, error) { posts, err := s.repo.GetPostsByUserID(ctx, userID) if err != nil { diff --git a/internal/service/sessions.go b/internal/service/sessions.go new file mode 100644 index 0000000..143d2eb --- /dev/null +++ b/internal/service/sessions.go @@ -0,0 +1,30 @@ +package service + +import ( + "context" + "crypto/rand" + "encoding/base64" + "fmt" +) + +func (s *Service) CreateSession(ctx context.Context, userID int) (string, error) { + b := make([]byte, 32) + rand.Read(b) + sessionID := base64.URLEncoding.EncodeToString(b)[:32] + + err := s.sessionsManger.StoreSession(ctx, userID, sessionID) + if err != nil { + return "", fmt.Errorf("error sessionsManger.StoreSession: %w", err) + } + + return sessionID, nil +} + +func (s *Service) GetUserIDBySessionID(ctx context.Context, sessionID string) (bool, int, error) { + ok, userID, err := s.sessionsManger.GetUserIDBySessionID(ctx, sessionID) + if err != nil { + return false, 0, fmt.Errorf("error sessionsManger.GetUserIDBySessionID: %w", err) + } + + return ok, userID, nil +} diff --git a/main.go b/main.go index bb62930..6f4cef7 100644 --- a/main.go +++ b/main.go @@ -2,19 +2,35 @@ package main import ( "context" + "time" + + "log" + + "github.com/berduk-dev/blog/internal/cache" "github.com/berduk-dev/blog/internal/handler" + "github.com/berduk-dev/blog/internal/middlewares" "github.com/berduk-dev/blog/internal/repo" "github.com/berduk-dev/blog/internal/service" - - "log" + "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" + "github.com/go-redis/redis" "github.com/jackc/pgx/v5" ) func main() { + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6380", + Password: "", + DB: 0, + }) - connString := "postgres://admin:admin@localhost:5433/blog" + _, err := rdb.Ping().Result() + if err != nil { + log.Fatalf("Ошибка подключения к Redis: %v", err) + } + + connString := "postgres://postgres:password@localhost:5432/blog" conn, err := pgx.Connect(context.Background(), connString) if err != nil { log.Fatal("Ошибка при подключении к БД: ", err) @@ -22,17 +38,34 @@ func main() { r := gin.Default() + sessionsCache := cache.New(rdb) blogRepository := repo.New(conn) - blogService := service.New(blogRepository) + blogService := service.New(blogRepository, sessionsCache) blogHandler := handler.New(blogService) - r.POST("/users/", blogHandler.CreateUser) + blogMiddlewares := middlewares.New(blogService) + + r.Use(cors.New(cors.Config{ + AllowOriginFunc: func(origin string) bool { + return true + }, + AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With", "User-ID"}, + ExposeHeaders: []string{"Content-Length"}, + AllowCredentials: true, + MaxAge: 12 * time.Hour, + })) + + r.POST("/register/", blogHandler.Register) + r.POST("/login/", blogHandler.Login) + // r.POST("/users/", blogHandler.CreateUser) r.GET("/users/:id/", blogHandler.GetUser) + r.GET("/users/me", blogMiddlewares.SessionAuthMiddleware, blogHandler.GetCurrentUser) r.GET("/users/:id/posts/", blogHandler.GetPostsByUserID) - r.POST("/posts/", blogHandler.CreatePost) + r.POST("/posts/", blogMiddlewares.SessionAuthMiddleware, blogHandler.CreatePost) r.GET("/posts/", blogHandler.GetPosts) r.GET("/posts/:id/", blogHandler.GetPost) - r.POST("/posts/:id/comments/", blogHandler.CreateComment) + r.POST("/posts/:id/comments/", blogMiddlewares.SessionAuthMiddleware, blogHandler.CreateComment) r.GET("/posts/:id/comments/", blogHandler.GetCommentsByPostID) r.Run(":8088") From ccebc31dc6db12bf60aa5961a5a5c8e474e2321e Mon Sep 17 00:00:00 2001 From: Amadi Azdaev Date: Tue, 4 Nov 2025 09:21:16 +0300 Subject: [PATCH 9/9] add interfaces --- .gitignore | 1 + Dockerfile | 15 ++++++++++++ docker-compose.yaml | 15 ++++++++---- internal/handler/dto/dto.go | 32 +++++++++++++++++++++++++ internal/handler/handler.go | 21 ++++++++++++---- internal/middlewares/middlewares.go | 11 ++++++--- internal/model/model.go | 29 ---------------------- internal/repo/repo.go | 30 +++++------------------ internal/service/dto/dto.go | 20 ++++++++++++++++ internal/service/service.go | 37 ++++++++++++++++++++--------- main.go | 5 ++-- 11 files changed, 137 insertions(+), 79 deletions(-) create mode 100644 Dockerfile create mode 100644 internal/handler/dto/dto.go create mode 100644 internal/service/dto/dto.go diff --git a/.gitignore b/.gitignore index 51a45e1..190c15b 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ go.work.sum # --- PostgreSQL data --- /db_data/ +.DS_Store \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5a04d43 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM golang:1.24-alpine + +WORKDIR /app + +COPY go.mod go.sum ./ + +RUN go mod download + +COPY . . + +RUN go build -o main . + +EXPOSE 8080 + +CMD ["./main"] diff --git a/docker-compose.yaml b/docker-compose.yaml index c4c0f98..2a0d552 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,4 +1,15 @@ services: + api: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8088" + container_name: blog-api + depends_on: + - redis + - postgres + postgres: image: postgres:15 container_name: blog-postgres @@ -6,8 +17,6 @@ services: POSTGRES_DB: blog POSTGRES_USER: postgres POSTGRES_PASSWORD: password - ports: - - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data @@ -15,8 +24,6 @@ services: image: redis:7-alpine container_name: blog-redis restart: unless-stopped - ports: - - "6380:6379" command: redis-server --appendonly yes volumes: diff --git a/internal/handler/dto/dto.go b/internal/handler/dto/dto.go new file mode 100644 index 0000000..bf08313 --- /dev/null +++ b/internal/handler/dto/dto.go @@ -0,0 +1,32 @@ +package dto + +import "time" + +type CreatePostReq struct { + Title string `json:"title"` + Body string `json:"body"` +} + +type CreateCommentReq struct { + Body string `json:"body"` +} + +type LoginReq struct { + Email string `json:"email"` + Password string `json:"password"` +} + +type LoginResp struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + IsAdmin bool `json:"is_admin"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type CreateUserReq struct { + Name string `json:"name"` + Password string `json:"password"` + Email string `json:"email"` +} diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 0df9863..e15cb6a 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -1,20 +1,31 @@ package handler import ( + "context" "log" "net/http" "strconv" + handlerDto "github.com/berduk-dev/blog/internal/handler/dto" "github.com/berduk-dev/blog/internal/model" - "github.com/berduk-dev/blog/internal/service" + "github.com/gin-gonic/gin" ) +type Service interface { + CreatePost(ctx context.Context, userID int, post handlerDto.CreatePostReq) error + CreateComment(ctx context.Context, userID int, PostID int, comment handlerDto.CreateCommentReq) error + GetPost(ctx context.Context, postID int) (model.Post, error) + GetCommentsByPostID(ctx context.Context, postID int) ([]model.Comment, error) + GetPostsByUserID(ctx context.Context, userID int) ([]model.Post, error) + GetPosts(ctx context.Context) ([]model.Post, error) +} + type Handler struct { - service service.Service + service Service } -func New(service service.Service) Handler { +func New(service Service) Handler { return Handler{ service: service, } @@ -23,7 +34,7 @@ func New(service service.Service) Handler { func (h *Handler) CreatePost(c *gin.Context) { user := c.Value("user").(model.User) - var req model.CreatePostReq + var req handlerDto.CreatePostReq err := c.ShouldBindJSON(&req) if err != nil { c.JSON(http.StatusBadRequest, "У вас невалидный запрос") @@ -44,7 +55,7 @@ func (h *Handler) CreatePost(c *gin.Context) { func (h *Handler) CreateComment(c *gin.Context) { user := c.Value("user").(model.User) - var req model.CreateCommentReq + var req handlerDto.CreateCommentReq err := c.ShouldBindJSON(&req) if err != nil { diff --git a/internal/middlewares/middlewares.go b/internal/middlewares/middlewares.go index 6338082..93cc0f5 100644 --- a/internal/middlewares/middlewares.go +++ b/internal/middlewares/middlewares.go @@ -1,18 +1,23 @@ package middlewares import ( + "context" + "github.com/berduk-dev/blog/internal/model" "log" "net/http" - "github.com/berduk-dev/blog/internal/service" "github.com/gin-gonic/gin" ) +type UserService interface { + GetUserIDBySessionID(ctx context.Context, sessionID string) (bool, int, error) + GetUser(ctx context.Context, userID int) (model.User, error) +} type Middleware struct { - service service.Service + service UserService } -func New(service service.Service) Middleware { +func New(service UserService) Middleware { return Middleware{ service: service, } diff --git a/internal/model/model.go b/internal/model/model.go index f697f23..97f3a25 100644 --- a/internal/model/model.go +++ b/internal/model/model.go @@ -12,26 +12,6 @@ type User struct { UpdatedAt time.Time `json:"updated_at"` } -type CreateUserReq struct { - Name string `json:"name"` - Password string `json:"password"` - Email string `json:"email"` -} - -type LoginResp struct { - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - IsAdmin bool `json:"is_admin"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -type LoginReq struct { - Email string `json:"email"` - Password string `json:"password"` -} - type Post struct { ID int `json:"id"` UserID int `json:"user_id"` @@ -42,11 +22,6 @@ type Post struct { UpdatedAt time.Time `json:"updated_at"` } -type CreatePostReq struct { - Title string `json:"title"` - Body string `json:"body"` -} - type Comment struct { ID int `json:"id"` PostID int `json:"post_id"` @@ -55,7 +30,3 @@ type Comment struct { CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } - -type CreateCommentReq struct { - Body string `json:"body"` -} diff --git a/internal/repo/repo.go b/internal/repo/repo.go index 3a960a7..58a85e8 100644 --- a/internal/repo/repo.go +++ b/internal/repo/repo.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + serviceDto "github.com/berduk-dev/blog/internal/service/dto" "log" "strings" @@ -17,19 +18,12 @@ type Repository struct { db *pgx.Conn } -func New(db *pgx.Conn) Repository { - return Repository{ +func New(db *pgx.Conn) *Repository { + return &Repository{ db: db, } } -type CreateUserRequest struct { - Name string - Email string - HashedPassword string - IsAdmin bool -} - type UpdateUser struct { Name *string Email *string @@ -40,18 +34,6 @@ type UpdatePost struct { Title int } -type CreatePostRequest struct { - UserID int - Title string - Body string -} - -type CreateCommentRequest struct { - PostID int - UserID int - Body string -} - type UpdateComment struct { Body string } @@ -176,7 +158,7 @@ func (r *Repository) GetPosts(ctx context.Context) ([]model.Post, error) { return posts, nil } -func (r *Repository) CreateUser(ctx context.Context, user CreateUserRequest) error { +func (r *Repository) CreateUser(ctx context.Context, user serviceDto.CreateUserRequest) error { _, err := r.db.Exec(ctx, "INSERT INTO users (name, email, hashed_password, is_admin) VALUES ($1, $2, $3, $4)", user.Name, user.Email, user.HashedPassword, user.IsAdmin) if err != nil { log.Println("repo.CreateUser: ", err) @@ -192,7 +174,7 @@ func (r *Repository) CreateUser(ctx context.Context, user CreateUserRequest) err return nil } -func (r *Repository) CreatePost(ctx context.Context, params CreatePostRequest) error { +func (r *Repository) CreatePost(ctx context.Context, params serviceDto.CreatePostRequest) error { _, err := r.db.Exec(ctx, "INSERT INTO posts (user_id, title, body) VALUES ($1, $2, $3)", params.UserID, params.Title, params.Body) if err != nil { return fmt.Errorf("error CreatePost Exec: %w", err) @@ -201,7 +183,7 @@ func (r *Repository) CreatePost(ctx context.Context, params CreatePostRequest) e return nil } -func (r *Repository) CreateComment(ctx context.Context, params CreateCommentRequest) error { +func (r *Repository) CreateComment(ctx context.Context, params serviceDto.CreateCommentRequest) error { _, err := r.db.Exec(ctx, "INSERT INTO comments (user_id, post_id, body) VALUES ($1, $2, $3)", params.UserID, params.PostID, params.Body) if err != nil { return fmt.Errorf("error CreateComment Exec: %w", err) diff --git a/internal/service/dto/dto.go b/internal/service/dto/dto.go new file mode 100644 index 0000000..b0cc264 --- /dev/null +++ b/internal/service/dto/dto.go @@ -0,0 +1,20 @@ +package dto + +type CreatePostRequest struct { + UserID int + Title string + Body string +} + +type CreateCommentRequest struct { + PostID int + UserID int + Body string +} + +type CreateUserRequest struct { + Name string + Email string + HashedPassword string + IsAdmin bool +} diff --git a/internal/service/service.go b/internal/service/service.go index 9b731a0..cb0f7da 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -3,32 +3,47 @@ package service import ( "context" "fmt" + handlerDto "github.com/berduk-dev/blog/internal/handler/dto" + "golang.org/x/crypto/bcrypt" "github.com/berduk-dev/blog/internal/cache" "github.com/berduk-dev/blog/internal/model" - "github.com/berduk-dev/blog/internal/repo" - "golang.org/x/crypto/bcrypt" + serviceDto "github.com/berduk-dev/blog/internal/service/dto" ) +type Repository interface { + CreatePost(ctx context.Context, params serviceDto.CreatePostRequest) error + GetPost(ctx context.Context, postID int) (model.Post, error) + GetPosts(ctx context.Context) ([]model.Post, error) + GetPostsByUserID(ctx context.Context, userID int) ([]model.Post, error) + + CreateComment(ctx context.Context, params serviceDto.CreateCommentRequest) error + GetCommentsByPostID(ctx context.Context, postID int) ([]model.Comment, error) + + CreateUser(ctx context.Context, user serviceDto.CreateUserRequest) error + GetUser(ctx context.Context, userID int) (model.User, error) + GetUserByEmail(ctx context.Context, email string) (model.User, error) +} + type Service struct { - repo repo.Repository + repo Repository sessionsManger cache.SessionsCache } -func New(repo repo.Repository, sessionsManger cache.SessionsCache) Service { - return Service{ +func New(repo Repository, sessionsManger cache.SessionsCache) *Service { + return &Service{ repo: repo, sessionsManger: sessionsManger, } } -func (s *Service) CreateUser(ctx context.Context, user model.CreateUserReq) error { +func (s *Service) CreateUser(ctx context.Context, user handlerDto.CreateUserReq) error { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) if err != nil { return fmt.Errorf("error bcrypt.GenerateFromPassword: %w", err) } - err = s.repo.CreateUser(ctx, repo.CreateUserRequest{ + err = s.repo.CreateUser(ctx, serviceDto.CreateUserRequest{ Name: user.Name, HashedPassword: string(hashedPassword), Email: user.Email, @@ -40,8 +55,8 @@ func (s *Service) CreateUser(ctx context.Context, user model.CreateUserReq) erro return nil } -func (s *Service) CreatePost(ctx context.Context, userID int, post model.CreatePostReq) error { - err := s.repo.CreatePost(ctx, repo.CreatePostRequest{ +func (s *Service) CreatePost(ctx context.Context, userID int, post handlerDto.CreatePostReq) error { + err := s.repo.CreatePost(ctx, serviceDto.CreatePostRequest{ UserID: userID, Title: post.Title, Body: post.Body, @@ -53,8 +68,8 @@ func (s *Service) CreatePost(ctx context.Context, userID int, post model.CreateP return nil } -func (s *Service) CreateComment(ctx context.Context, userID int, PostID int, comment model.CreateCommentReq) error { - err := s.repo.CreateComment(ctx, repo.CreateCommentRequest{ +func (s *Service) CreateComment(ctx context.Context, userID int, PostID int, comment handlerDto.CreateCommentReq) error { + err := s.repo.CreateComment(ctx, serviceDto.CreateCommentRequest{ UserID: userID, PostID: PostID, Body: comment.Body, diff --git a/main.go b/main.go index 6f4cef7..bfcffde 100644 --- a/main.go +++ b/main.go @@ -20,7 +20,7 @@ import ( func main() { rdb := redis.NewClient(&redis.Options{ - Addr: "localhost:6380", + Addr: "redis:6379", Password: "", DB: 0, }) @@ -30,7 +30,7 @@ func main() { log.Fatalf("Ошибка подключения к Redis: %v", err) } - connString := "postgres://postgres:password@localhost:5432/blog" + connString := "postgres://postgres:password@postgres:5432/blog" conn, err := pgx.Connect(context.Background(), connString) if err != nil { log.Fatal("Ошибка при подключении к БД: ", err) @@ -42,7 +42,6 @@ func main() { blogRepository := repo.New(conn) blogService := service.New(blogRepository, sessionsCache) blogHandler := handler.New(blogService) - blogMiddlewares := middlewares.New(blogService) r.Use(cors.New(cors.Config{