chore: coded post req for create posts on db
All checks were successful
Backend ci / build (push) Successful in 7m43s

This commit is contained in:
d3m0k1d
2026-02-03 23:21:01 +03:00
parent 44406b02d3
commit 9b9d45e0a6
14 changed files with 254 additions and 67 deletions

1
backend/.gitignore vendored
View File

@@ -1 +1,2 @@
/bin
/data

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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