From 9b9d45e0a6de6eae3469a92e77cf8506ea8f5950 Mon Sep 17 00:00:00 2001 From: d3m0k1d Date: Tue, 3 Feb 2026 23:21:01 +0300 Subject: [PATCH] chore: coded post req for create posts on db --- backend/.gitignore | 1 + ...s=NORMAL&_cache_size=2000&_foreign_keys=ON | Bin 0 -> 12288 bytes backend/Dockerfile | 1 + backend/cmd/main.go | 27 ++++++-- backend/docs/docs.go | 61 ++++++++++++++--- backend/docs/swagger.json | 61 ++++++++++++++--- backend/docs/swagger.yaml | 46 ++++++++++--- backend/internal/handlers/post_handlers.go | 62 ++++++++++++++---- .../internal/handlers/registry_handlers.go | 10 ++- backend/internal/logger/logger.go | 4 +- .../internal/repositories/post_repository.go | 14 +++- backend/internal/storage/db.go | 23 +++---- backend/internal/storage/migrations.go | 6 +- backend/internal/storage/models.go | 5 ++ 14 files changed, 254 insertions(+), 67 deletions(-) create mode 100644 backend/?_journal_mode=WAL&_busy_timeout=5000&_synchronous=NORMAL&_cache_size=2000&_foreign_keys=ON diff --git a/backend/.gitignore b/backend/.gitignore index 5e56e04..634b0a2 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1 +1,2 @@ /bin +/data diff --git a/backend/?_journal_mode=WAL&_busy_timeout=5000&_synchronous=NORMAL&_cache_size=2000&_foreign_keys=ON b/backend/?_journal_mode=WAL&_busy_timeout=5000&_synchronous=NORMAL&_cache_size=2000&_foreign_keys=ON new file mode 100644 index 0000000000000000000000000000000000000000..c7747582431b6d7dcdcef7441d886fc23107ddc2 GIT binary patch literal 12288 zcmeI&O>fgM7zc2>FB{3!nOm}(4?DD4H)TmX+MEcjLEX}>WUjQHs%{IYQkSyDT#?|; zozKDN;A0@Z0S7qYw3@V+R=XjA{*PiO_T$Hiem6@wc^ytv%-_teFUN{|Q+M_iX%FY>>Urze{>mZu1vmN4i5m00Izz00bZa0SG_<0uX=z1pcN# zKeuM>?9ibapH1WRYN}7cqcpyHA1CMchxt|{0x7r*UW9_*TU$=XmvL2BRzA9P-LfqD zsWsp8-7HnVbV&k_|U>cRZtRqatAd0T(8>~sZhiFBE9J-n>>+0w{IGi z>}+msekbe)`^LVqFYE)mV28|M*(3r25P$##AOHafKmY;|fB*y_@DB-Wn?9Wj$UvFgRf69ImeIXzK0SG_<0uX=z1Rwwb2tWV=5cr=2 M44Sv-{67Tq4{xBEaR2}S literal 0 HcmV?d00001 diff --git a/backend/Dockerfile b/backend/Dockerfile index 5b83b4d..f9c0a02 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -5,6 +5,7 @@ WORKDIR /app COPY . . ENV CGO_ENABLED=0 +ENV GIN_MODE=release RUN go mod tidy RUN go build -ldflags "-s -w" -o backend ./cmd/main.go diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 032f6e7..bcde071 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -3,8 +3,8 @@ package main import ( "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/docs" "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/handlers" - "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/logger" + "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/storage" "github.com/gin-gonic/gin" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" @@ -13,6 +13,22 @@ import ( func main() { log := logger.New(false) + db, err := storage.OpenSession() + if err != nil { + log.Error("Failed to open database", "error", err) + panic(err) + } + defer func() { + err := db.Close() + if err != nil { + log.Error("Failed to close database", "error", err) + } + }() + if err := storage.CreateTables(db); err != nil { + log.Error("Failed to create tables", "error", err) + panic(err) + } + router := gin.Default() // Swagger config @@ -20,13 +36,14 @@ func main() { docs.SwaggerInfo.Title = "d3m0k1d.ru API" docs.SwaggerInfo.Version = "1.0" docs.SwaggerInfo.Description = "API for d3m0k1d.ru" - docs.SwaggerInfo.Host = "d3m0k1d.ru" - docs.SwaggerInfo.Schemes = []string{"https"} + docs.SwaggerInfo.Schemes = []string{"http"} router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) - handlers.Register(router) + + handlers.Register(router, db) + log.Info("Starting server on :8080...") if err := router.Run(":8080"); err != nil { - log.Error("Error starting server: " + err.Error()) + log.Error("Error starting server", "error", err) panic(err) } } diff --git a/backend/docs/docs.go b/backend/docs/docs.go index b0ce8f7..25c54ed 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -15,7 +15,7 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { - "/api/v1/posts": { + "/posts": { "get": { "description": "Get all posts", "consumes": [ @@ -34,14 +34,14 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/storage.Post" + "$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq" } } } } }, "post": { - "description": "Create post", + "description": "Create new post", "consumes": [ "application/json" ], @@ -52,17 +52,34 @@ const docTemplate = `{ "posts" ], "summary": "Create post", + "parameters": [ + { + "description": "Post data", + "name": "post", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostCreate" + } + } + ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/storage.Post" + "$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/gin.H" } } } } }, - "/api/v1/posts/{id}": { + "/posts/{id}": { "get": { "description": "Get post by id", "consumes": [ @@ -79,7 +96,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/storage.Post" + "$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq" } } } @@ -100,7 +117,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/storage.Post" + "$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post" } } } @@ -121,7 +138,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/storage.Post" + "$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post" } } } @@ -129,7 +146,11 @@ const docTemplate = `{ } }, "definitions": { - "storage.Post": { + "gin.H": { + "type": "object", + "additionalProperties": {} + }, + "gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post": { "type": "object", "properties": { "content": { @@ -145,6 +166,28 @@ const docTemplate = `{ "type": "string" } } + }, + "gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostCreate": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "title": { + "type": "string" + } + } } } }` diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 590959c..ca1073b 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -4,7 +4,7 @@ "contact": {} }, "paths": { - "/api/v1/posts": { + "/posts": { "get": { "description": "Get all posts", "consumes": [ @@ -23,14 +23,14 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/storage.Post" + "$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq" } } } } }, "post": { - "description": "Create post", + "description": "Create new post", "consumes": [ "application/json" ], @@ -41,17 +41,34 @@ "posts" ], "summary": "Create post", + "parameters": [ + { + "description": "Post data", + "name": "post", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostCreate" + } + } + ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/storage.Post" + "$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/gin.H" } } } } }, - "/api/v1/posts/{id}": { + "/posts/{id}": { "get": { "description": "Get post by id", "consumes": [ @@ -68,7 +85,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/storage.Post" + "$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq" } } } @@ -89,7 +106,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/storage.Post" + "$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post" } } } @@ -110,7 +127,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/storage.Post" + "$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post" } } } @@ -118,7 +135,11 @@ } }, "definitions": { - "storage.Post": { + "gin.H": { + "type": "object", + "additionalProperties": {} + }, + "gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post": { "type": "object", "properties": { "content": { @@ -134,6 +155,28 @@ "type": "string" } } + }, + "gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostCreate": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "title": { + "type": "string" + } + } } } } \ No newline at end of file diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index ec2e8cf..ff0700f 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -1,5 +1,8 @@ definitions: - storage.Post: + gin.H: + additionalProperties: {} + type: object + gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post: properties: content: type: string @@ -10,10 +13,24 @@ definitions: title: type: string type: object + gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostCreate: + properties: + content: + type: string + title: + type: string + type: object + gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq: + properties: + content: + type: string + title: + type: string + type: object info: contact: {} paths: - /api/v1/posts: + /posts: get: consumes: - application/json @@ -25,7 +42,7 @@ paths: description: OK schema: items: - $ref: '#/definitions/storage.Post' + $ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq' type: array summary: Get all posts tags: @@ -33,18 +50,29 @@ paths: post: consumes: - application/json - description: Create post + description: Create new post + parameters: + - description: Post data + in: body + name: post + required: true + schema: + $ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostCreate' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/storage.Post' + $ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq' + "400": + description: Bad Request + schema: + $ref: '#/definitions/gin.H' summary: Create post tags: - posts - /api/v1/posts/{id}: + /posts/{id}: delete: consumes: - application/json @@ -55,7 +83,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/storage.Post' + $ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post' summary: Delete post tags: - posts @@ -69,7 +97,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/storage.Post' + $ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq' summary: Get post by id tags: - posts @@ -83,7 +111,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/storage.Post' + $ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post' summary: Update post tags: - posts diff --git a/backend/internal/handlers/post_handlers.go b/backend/internal/handlers/post_handlers.go index 3658b8e..d5bf7a7 100644 --- a/backend/internal/handlers/post_handlers.go +++ b/backend/internal/handlers/post_handlers.go @@ -2,9 +2,19 @@ package handlers import ( "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/logger" + "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/repositories" + "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/storage" "github.com/gin-gonic/gin" ) +type PostHandlers struct { + repo repositories.PostRepository +} + +func NewPostHandler(repo repositories.PostRepository) *PostHandlers { + return &PostHandlers{repo: repo} +} + var log = logger.New(false) // GetPosts godoc @@ -14,9 +24,16 @@ var log = logger.New(false) // @Accept json // @Produce json // @Success 200 {object} []storage.PostReq -// @Router /api/v1/posts [get] -func GetPosts(c *gin.Context) { - log.Info("GetPosts") +// @Router /posts [get] +func (h *PostHandlers) GetPosts(c *gin.Context) { + var result []storage.PostReq + result, err := h.repo.GetAll(c.Request.Context()) + if err != nil { + log.Error("error request: " + err.Error()) + c.Status(500) + } + log.Info("200 OK GET /posts") + c.JSON(200, result) } // GetPost godoc @@ -26,21 +43,40 @@ func GetPosts(c *gin.Context) { // @Accept json // @Produce json // @Success 200 {object} storage.PostReq -// @Router /api/v1/posts/{id} [get] +// @Router /posts/{id} [get] func GetPost(c *gin.Context) { log.Info("GetPost") } // CreatePost godoc // @Summary Create post -// @Description Create post +// @Description Create new post // @Tags posts -// @Accept json -// @Produce json -// @Success 200 {object} storage.Post -// @Router /api/v1/posts [post] -func CreatePost(c *gin.Context) { - log.Info("CreatePost") +// @Accept json +// @Produce json +// @Param post body storage.PostCreate true "Post data" +// @Success 200 {object} storage.PostReq +// @Failure 400 {object} gin.H +// @Router /posts [post] +func (h *PostHandlers) CreatePost(c *gin.Context) { + var req storage.PostCreate + + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + + post := storage.Post{ + Title: req.Title, + Content: req.Content, + } + + if err := h.repo.Create(c.Request.Context(), post); err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + + c.JSON(200, post) } // UpdatePost godoc @@ -50,7 +86,7 @@ func CreatePost(c *gin.Context) { // @Accept json // @Produce json // @Success 200 {object} storage.Post -// @Router /api/v1/posts/{id} [put] +// @Router /posts/{id} [put] func UpdatePost(c *gin.Context) { log.Info("UpdatePost") } @@ -62,7 +98,7 @@ func UpdatePost(c *gin.Context) { // @Accept json // @Produce json // @Success 200 {object} storage.Post -// @Router /api/v1/posts/{id} [delete] +// @Router /posts/{id} [delete] func DeletePost(c *gin.Context) { log.Info("DeletePost") } diff --git a/backend/internal/handlers/registry_handlers.go b/backend/internal/handlers/registry_handlers.go index 6f785b9..4a8ff7b 100644 --- a/backend/internal/handlers/registry_handlers.go +++ b/backend/internal/handlers/registry_handlers.go @@ -1,16 +1,20 @@ package handlers import ( + "database/sql" + + "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/repositories" "github.com/gin-gonic/gin" ) -func Register(router *gin.Engine) { +func Register(router *gin.Engine, db *sql.DB) { + handler_posts := NewPostHandler(repositories.NewPostRepository(db)) v1 := router.Group("api/v1") posts := v1.Group("posts") { - posts.GET("/", GetPosts) + posts.GET("/", handler_posts.GetPosts) posts.GET("/:id", GetPost) - posts.POST("/", CreatePost) + posts.POST("/", handler_posts.CreatePost) posts.PUT("/:id", UpdatePost) posts.DELETE("/:id", DeletePost) } diff --git a/backend/internal/logger/logger.go b/backend/internal/logger/logger.go index ffa8233..640336d 100644 --- a/backend/internal/logger/logger.go +++ b/backend/internal/logger/logger.go @@ -14,13 +14,13 @@ type Logger struct { } func New(debug bool) *Logger { - logDir := "/var/log/banforge" + logDir := "/var/log/backend" if err := os.MkdirAll(logDir, 0750); err != nil { return nil } fileWriter := &lumberjack.Logger{ - Filename: filepath.Join(logDir, "banforge.log"), + Filename: filepath.Join(logDir, "backend.log"), MaxSize: 500, MaxBackups: 3, MaxAge: 28, diff --git a/backend/internal/repositories/post_repository.go b/backend/internal/repositories/post_repository.go index 7e60742..0ee0db3 100644 --- a/backend/internal/repositories/post_repository.go +++ b/backend/internal/repositories/post_repository.go @@ -58,7 +58,19 @@ func (p *postRepository) GetByID(ctx context.Context, id int) (storage.PostReq, } func (p *postRepository) Create(ctx context.Context, post storage.Post) error { - + query, err := p.db.Exec( + "INSERT INTO posts(title, content) VALUES(?, ?)", + post.Title, + post.Content, + ) + if err != nil { + return err + } + id, err := query.LastInsertId() + if err != nil { + return err + } + p.logger.Info("Post created:", "id", id) return nil } diff --git a/backend/internal/storage/db.go b/backend/internal/storage/db.go index 80672e4..94c53af 100644 --- a/backend/internal/storage/db.go +++ b/backend/internal/storage/db.go @@ -12,24 +12,21 @@ var db_path = os.Getenv( "DB_PATH", ) + "?_journal_mode=WAL&_busy_timeout=5000&_synchronous=NORMAL&_cache_size=2000&_foreign_keys=ON" -func CreateTables() error { +func CreateTables(db *sql.DB) error { logger := logger.New(false) - db, err := sql.Open("sqlite", db_path) - if err != nil { - logger.Error(err.Error()) - return err - } - _, err = db.Exec(Migrations) + _, err := db.Exec(Migrations) if err != nil { logger.Error(err.Error()) return err } - defer func() { - err = db.Close() - if err != nil { - logger.Error(err.Error()) - } - }() return nil } + +func OpenSession() (*sql.DB, error) { + db, err := sql.Open("sqlite", db_path) + if err != nil { + return nil, err + } + return db, nil +} diff --git a/backend/internal/storage/migrations.go b/backend/internal/storage/migrations.go index 288f888..5cdf8da 100644 --- a/backend/internal/storage/migrations.go +++ b/backend/internal/storage/migrations.go @@ -2,9 +2,9 @@ package storage const Migrations = ` CREATE TABLE IF NOT EXISTS posts( - id INTEGER PRIMARY KEY AUTOINCREMENT - title TEXT NOT NULL - content TEXT NOT NULL + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + content TEXT NOT NULL, CREATED_AT DATETIME DEFAULT CURRENT_TIMESTAMP ); ` diff --git a/backend/internal/storage/models.go b/backend/internal/storage/models.go index 667956f..4023e90 100644 --- a/backend/internal/storage/models.go +++ b/backend/internal/storage/models.go @@ -11,3 +11,8 @@ type PostReq struct { Title string `json:"title"` Content string `json:"content"` } + +type PostCreate struct { + Title string `json:"title"` + Content string `json:"content"` +}