develop #5

Merged
d3m0k1d merged 4 commits from develop into master 2026-02-09 16:14:55 +00:00
9 changed files with 197 additions and 17 deletions

View File

@@ -6,7 +6,7 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
deploy-frontend: deploy-backend:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@@ -44,4 +44,4 @@ jobs:
docker login -u d3m0k1d -p ${{ steps.import-secrets.outputs.GITEA_TOKEN }} gitea.d3m0k1d.ru docker login -u d3m0k1d -p ${{ steps.import-secrets.outputs.GITEA_TOKEN }} gitea.d3m0k1d.ru
docker pull gitea.d3m0k1d.ru/d3m0k1d/backend:latest docker pull gitea.d3m0k1d.ru/d3m0k1d/backend:latest
docker rm -f d3m0k1d-backend || true docker rm -f d3m0k1d-backend || true
docker run --name d3m0k1d-backend -d -p 80:80 --restart unless-stopped gitea.d3m0k1d.ru/d3m0k1d/backend:latest docker run --name d3m0k1d-backend -d -p 8080:8080 --restart unless-stopped gitea.d3m0k1d.ru/d3m0k1d/backend:latest

View File

@@ -92,12 +92,39 @@ const docTemplate = `{
"posts" "posts"
], ],
"summary": "Get post by id", "summary": "Get post by id",
"parameters": [
{
"type": "integer",
"description": "Post ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq" "$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq"
} }
},
"400": {
"description": "Invalid ID format",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
} }
} }
}, },
@@ -113,11 +140,29 @@ const docTemplate = `{
"posts" "posts"
], ],
"summary": "Update post", "summary": "Update post",
"parameters": [
{
"type": "integer",
"description": "Post ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Post data",
"name": "post",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostCreate"
}
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post" "$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostCreate"
} }
} }
} }
@@ -184,6 +229,9 @@ const docTemplate = `{
"content": { "content": {
"type": "string" "type": "string"
}, },
"id": {
"type": "integer"
},
"title": { "title": {
"type": "string" "type": "string"
} }

View File

@@ -81,12 +81,39 @@
"posts" "posts"
], ],
"summary": "Get post by id", "summary": "Get post by id",
"parameters": [
{
"type": "integer",
"description": "Post ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq" "$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq"
} }
},
"400": {
"description": "Invalid ID format",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
} }
} }
}, },
@@ -102,11 +129,29 @@
"posts" "posts"
], ],
"summary": "Update post", "summary": "Update post",
"parameters": [
{
"type": "integer",
"description": "Post ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Post data",
"name": "post",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostCreate"
}
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post" "$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostCreate"
} }
} }
} }
@@ -173,6 +218,9 @@
"content": { "content": {
"type": "string" "type": "string"
}, },
"id": {
"type": "integer"
},
"title": { "title": {
"type": "string" "type": "string"
} }

View File

@@ -24,6 +24,8 @@ definitions:
properties: properties:
content: content:
type: string type: string
id:
type: integer
title: title:
type: string type: string
type: object type: object
@@ -91,6 +93,12 @@ paths:
consumes: consumes:
- application/json - application/json
description: Get post by id description: Get post by id
parameters:
- description: Post ID
in: path
name: id
required: true
type: integer
produces: produces:
- application/json - application/json
responses: responses:
@@ -98,6 +106,18 @@ paths:
description: OK description: OK
schema: schema:
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq' $ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq'
"400":
description: Invalid ID format
schema:
additionalProperties:
type: string
type: object
"500":
description: Internal server error
schema:
additionalProperties:
type: string
type: object
summary: Get post by id summary: Get post by id
tags: tags:
- posts - posts
@@ -105,13 +125,25 @@ paths:
consumes: consumes:
- application/json - application/json
description: Update post description: Update post
parameters:
- description: Post ID
in: path
name: id
required: true
type: integer
- description: Post data
in: body
name: post
required: true
schema:
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostCreate'
produces: produces:
- application/json - application/json
responses: responses:
"200": "200":
description: OK description: OK
schema: schema:
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post' $ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostCreate'
summary: Update post summary: Update post
tags: tags:
- posts - posts

View File

@@ -1,6 +1,8 @@
package handlers package handlers
import ( import (
"strconv"
"gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/logger" "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/repositories"
"gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/storage" "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/storage"
@@ -41,11 +43,28 @@ func (h *PostHandlers) GetPosts(c *gin.Context) {
// @Description Get post by id // @Description Get post by id
// @Tags posts // @Tags posts
// @Accept json // @Accept json
// @Param id path int true "Post ID"
// @Produce json // @Produce json
// @Success 200 {object} storage.PostReq // @Success 200 {object} storage.PostReq
// @Failure 400 {object} map[string]string "Invalid ID format"
// @Failure 500 {object} map[string]string "Internal server error"
// @Router /posts/{id} [get] // @Router /posts/{id} [get]
func GetPost(c *gin.Context) { func (h *PostHandlers) GetPost(c *gin.Context) {
log.Info("GetPost") var result storage.PostReq
id_p := c.Param("id")
id, err := strconv.Atoi(id_p)
if err != nil {
log.Error("error request: " + err.Error())
c.Status(500)
}
result, err = h.repo.GetByID(c.Request.Context(), id)
if err != nil {
log.Error("error request: " + err.Error())
c.Status(500)
}
log.Info("200 OK GET /posts/" + id_p)
c.JSON(200, result)
// TODO: added validaton for 400 response
} }
// CreatePost godoc // CreatePost godoc
@@ -84,11 +103,31 @@ func (h *PostHandlers) CreatePost(c *gin.Context) {
// @Description Update post // @Description Update post
// @Tags posts // @Tags posts
// @Accept json // @Accept json
// @Param id path int true "Post ID"
// @Param post body storage.PostCreate true "Post data"
// @Produce json // @Produce json
// @Success 200 {object} storage.Post // @Success 200 {object} storage.PostCreate
// @Router /posts/{id} [put] // @Router /posts/{id} [put]
func UpdatePost(c *gin.Context) { func (h *PostHandlers) UpdatePost(c *gin.Context) {
log.Info("UpdatePost") id_p := c.Param("id")
id, err := strconv.Atoi(id_p)
if err != nil {
log.Error("error request: " + err.Error())
c.Status(500)
}
var req storage.Post
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
err = h.repo.Update(c.Request.Context(), id, req)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, req)
log.Info("200 OK PUT /posts/" + id_p)
} }
// DeletePost godoc // DeletePost godoc

View File

@@ -13,9 +13,9 @@ func Register(router *gin.Engine, db *sql.DB) {
posts := v1.Group("posts") posts := v1.Group("posts")
{ {
posts.GET("/", handler_posts.GetPosts) posts.GET("/", handler_posts.GetPosts)
posts.GET("/:id", GetPost) posts.GET("/:id", handler_posts.GetPost)
posts.POST("/", handler_posts.CreatePost) posts.POST("/", handler_posts.CreatePost)
posts.PUT("/:id", UpdatePost) posts.PUT("/:id", handler_posts.UpdatePost)
posts.DELETE("/:id", DeletePost) posts.DELETE("/:id", DeletePost)
} }
} }

View File

@@ -21,7 +21,7 @@ func NewPostRepository(db *sql.DB) PostRepository {
} }
func (p *postRepository) GetAll(ctx context.Context) ([]storage.PostReq, error) { func (p *postRepository) GetAll(ctx context.Context) ([]storage.PostReq, error) {
var result []storage.PostReq var result []storage.PostReq
rows, err := p.db.Query("SELECT title content FROM posts") rows, err := p.db.Query("SELECT id, title, content FROM posts")
if err != nil { if err != nil {
p.logger.Error(err.Error()) p.logger.Error(err.Error())
return nil, err return nil, err
@@ -29,11 +29,13 @@ func (p *postRepository) GetAll(ctx context.Context) ([]storage.PostReq, error)
for rows.Next() { for rows.Next() {
var title string var title string
var content string var content string
err := rows.Scan(&title, &content) var id int
err := rows.Scan(&id, &title, &content)
if err != nil { if err != nil {
p.logger.Error("error scan: " + err.Error()) p.logger.Error("error scan: " + err.Error())
} }
result = append(result, storage.PostReq{ result = append(result, storage.PostReq{
ID: id,
Title: title, Title: title,
Content: content, Content: content,
}) })
@@ -43,7 +45,7 @@ func (p *postRepository) GetAll(ctx context.Context) ([]storage.PostReq, error)
func (p *postRepository) GetByID(ctx context.Context, id int) (storage.PostReq, error) { func (p *postRepository) GetByID(ctx context.Context, id int) (storage.PostReq, error) {
var result storage.PostReq var result storage.PostReq
row := p.db.QueryRow("SELECT title content FROM posts WHERE id = ?", id) row := p.db.QueryRow("SELECT title, content FROM posts WHERE id = ?", id)
var title string var title string
var content string var content string
err := row.Scan(&title, &content) err := row.Scan(&title, &content)
@@ -51,6 +53,7 @@ func (p *postRepository) GetByID(ctx context.Context, id int) (storage.PostReq,
p.logger.Error("error scan: " + err.Error()) p.logger.Error("error scan: " + err.Error())
} }
result = storage.PostReq{ result = storage.PostReq{
ID: id,
Title: title, Title: title,
Content: content, Content: content,
} }
@@ -75,7 +78,16 @@ func (p *postRepository) Create(ctx context.Context, post storage.Post) error {
} }
func (p *postRepository) Update(ctx context.Context, id int, post storage.Post) error { func (p *postRepository) Update(ctx context.Context, id int, post storage.Post) error {
_, err := p.db.Exec(
"UPDATE posts SET title = ?, content = ? WHERE id = ?",
post.Title,
post.Content,
id,
)
if err != nil {
return err
}
p.logger.Info("Post updated:", "id", id)
return nil return nil
} }

View File

@@ -8,6 +8,7 @@ type Post struct {
} }
type PostReq struct { type PostReq struct {
ID int `json:"id"`
Title string `json:"title"` Title string `json:"title"`
Content string `json:"content"` Content string `json:"content"`
} }

View File

@@ -64,7 +64,7 @@ export default function About() {
<div> <div>
<h3 className="font-mono font-semibold mb-1">Languages:</h3> <h3 className="font-mono font-semibold mb-1">Languages:</h3>
<p className="text-base-content/70"> <p className="text-base-content/70">
Golang, Python, Bash, C (Base level), HTML/CSS, Typescript Golang, Python, Bash, C (Base level), HTML/CSS, Typescript, SQL
</p> </p>
</div> </div>