develop merge 3 #4
47
.gitea/workflows/cd-back.yml
Normal file
47
.gitea/workflows/cd-back.yml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
name: Backend deploy
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-frontend:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Import Secrets
|
||||||
|
id: import-secrets
|
||||||
|
uses: hashicorp/vault-action@v3.4.0
|
||||||
|
with:
|
||||||
|
url: https://vault.d3m0k1d.ru
|
||||||
|
token: ${{ secrets.VAULT }}
|
||||||
|
secrets: |
|
||||||
|
secrets/site/prod/data/gitea TOKEN | GITEA_TOKEN ;
|
||||||
|
secrets/site/prod/data/server SSH_KEY | SSH_KEY ;
|
||||||
|
secrets/site/prod/data/server USER | SERVER_USER ;
|
||||||
|
secrets/site/prod/data/server HOST | SERVER_HOST ;
|
||||||
|
secrets/site/prod/data/server PORT | SERVER_PORT
|
||||||
|
|
||||||
|
- name: Login to registry
|
||||||
|
run: echo "${{ steps.import-secrets.outputs.GITEA_TOKEN }}" | docker login gitea.d3m0k1d.ru -u d3m0k1d --password-stdin
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
run: |
|
||||||
|
docker build -t gitea.d3m0k1d.ru/d3m0k1d/backend:latest ./backend
|
||||||
|
docker push gitea.d3m0k1d.ru/d3m0k1d/backend:latest
|
||||||
|
|
||||||
|
- name: Deploy at server
|
||||||
|
uses: appleboy/ssh-action@v1.2.0
|
||||||
|
with:
|
||||||
|
host: ${{ steps.import-secrets.outputs.SERVER_HOST }}
|
||||||
|
port: ${{ steps.import-secrets.outputs.SERVER_PORT }}
|
||||||
|
username: ${{ steps.import-secrets.outputs.SERVER_USER }}
|
||||||
|
key: ${{ steps.import-secrets.outputs.SSH_KEY }}
|
||||||
|
script: |
|
||||||
|
docker login -u d3m0k1d -p ${{ steps.import-secrets.outputs.GITEA_TOKEN }} gitea.d3m0k1d.ru
|
||||||
|
docker pull gitea.d3m0k1d.ru/d3m0k1d/backend:latest
|
||||||
|
docker rm -f d3m0k1d-backend || true
|
||||||
|
docker run --name d3m0k1d-backend -d -p 80:80 --restart unless-stopped gitea.d3m0k1d.ru/d3m0k1d/backend:latest
|
||||||
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
/bin
|
/bin
|
||||||
|
/data
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
|
ENV GIN_MODE=release
|
||||||
RUN go mod tidy
|
RUN go mod tidy
|
||||||
RUN go build -ldflags "-s -w" -o backend ./cmd/main.go
|
RUN go build -ldflags "-s -w" -o backend ./cmd/main.go
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: test build clean lint
|
.PHONY: test build clean lint dev
|
||||||
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@@ -15,3 +15,7 @@ clean:
|
|||||||
|
|
||||||
lint:
|
lint:
|
||||||
golangci-lint run --fix
|
golangci-lint run --fix
|
||||||
|
|
||||||
|
dev:
|
||||||
|
swag init -g ./cmd/main.go --parseDependency --parseInternal
|
||||||
|
go run ./cmd/main.go
|
||||||
|
|||||||
@@ -1,18 +1,49 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
docs "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/docs"
|
"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"
|
"github.com/gin-gonic/gin"
|
||||||
|
swaggerFiles "github.com/swaggo/files"
|
||||||
|
ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
router := gin.Default()
|
log := logger.New(false)
|
||||||
docs.SwaggerInfo.BasePath = "/api/v1"
|
|
||||||
docs.SwaggerInfo.Title = "d3m0k1d.ru"
|
db, err := storage.OpenSession()
|
||||||
docs.SwaggerInfo.Version = "1.0"
|
|
||||||
err := router.Run()
|
|
||||||
if err != nil {
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
// Swagger config
|
||||||
|
docs.SwaggerInfo.BasePath = "/api/v1"
|
||||||
|
docs.SwaggerInfo.Title = "d3m0k1d.ru API"
|
||||||
|
docs.SwaggerInfo.Version = "1.0"
|
||||||
|
docs.SwaggerInfo.Description = "API for d3m0k1d.ru"
|
||||||
|
docs.SwaggerInfo.Schemes = []string{"http"}
|
||||||
|
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||||
|
|
||||||
|
handlers.Register(router, db)
|
||||||
|
|
||||||
|
log.Info("Starting server on :8080...")
|
||||||
|
if err := router.Run(":8080"); err != nil {
|
||||||
|
log.Error("Error starting server", "error", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,182 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"host": "{{.Host}}",
|
"host": "{{.Host}}",
|
||||||
"basePath": "{{.BasePath}}",
|
"basePath": "{{.BasePath}}",
|
||||||
"paths": {}
|
"paths": {
|
||||||
|
"/posts": {
|
||||||
|
"get": {
|
||||||
|
"description": "Get all posts",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"posts"
|
||||||
|
],
|
||||||
|
"summary": "Get all posts",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"description": "Create new post",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"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/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gin.H"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/posts/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Get post by id",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"posts"
|
||||||
|
],
|
||||||
|
"summary": "Get post by id",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"description": "Update post",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"posts"
|
||||||
|
],
|
||||||
|
"summary": "Update post",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"description": "Delete post",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"posts"
|
||||||
|
],
|
||||||
|
"summary": "Delete post",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"gin.H": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {}
|
||||||
|
},
|
||||||
|
"gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||||
|
|||||||
@@ -3,5 +3,180 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"contact": {}
|
"contact": {}
|
||||||
},
|
},
|
||||||
"paths": {}
|
"paths": {
|
||||||
|
"/posts": {
|
||||||
|
"get": {
|
||||||
|
"description": "Get all posts",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"posts"
|
||||||
|
],
|
||||||
|
"summary": "Get all posts",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"description": "Create new post",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"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/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gin.H"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/posts/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Get post by id",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"posts"
|
||||||
|
],
|
||||||
|
"summary": "Get post by id",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"description": "Update post",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"posts"
|
||||||
|
],
|
||||||
|
"summary": "Update post",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"description": "Delete post",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"posts"
|
||||||
|
],
|
||||||
|
"summary": "Delete post",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"gin.H": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {}
|
||||||
|
},
|
||||||
|
"gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,118 @@
|
|||||||
|
definitions:
|
||||||
|
gin.H:
|
||||||
|
additionalProperties: {}
|
||||||
|
type: object
|
||||||
|
gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post:
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
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:
|
info:
|
||||||
contact: {}
|
contact: {}
|
||||||
paths: {}
|
paths:
|
||||||
|
/posts:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Get all posts
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq'
|
||||||
|
type: array
|
||||||
|
summary: Get all posts
|
||||||
|
tags:
|
||||||
|
- posts
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
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/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gin.H'
|
||||||
|
summary: Create post
|
||||||
|
tags:
|
||||||
|
- posts
|
||||||
|
/posts/{id}:
|
||||||
|
delete:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Delete post
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post'
|
||||||
|
summary: Delete post
|
||||||
|
tags:
|
||||||
|
- posts
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Get post by id
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.PostReq'
|
||||||
|
summary: Get post by id
|
||||||
|
tags:
|
||||||
|
- posts
|
||||||
|
put:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Update post
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_d3m0k1d_ru_backend_internal_storage.Post'
|
||||||
|
summary: Update post
|
||||||
|
tags:
|
||||||
|
- posts
|
||||||
swagger: "2.0"
|
swagger: "2.0"
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ require (
|
|||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.33.0 // indirect
|
||||||
golang.org/x/tools v0.41.0 // indirect
|
golang.org/x/tools v0.41.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
modernc.org/libc v1.67.6 // indirect
|
modernc.org/libc v1.67.6 // indirect
|
||||||
|
|||||||
@@ -169,6 +169,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
104
backend/internal/handlers/post_handlers.go
Normal file
104
backend/internal/handlers/post_handlers.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
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
|
||||||
|
// @Summary Get all posts
|
||||||
|
// @Description Get all posts
|
||||||
|
// @Tags posts
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} []storage.PostReq
|
||||||
|
// @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
|
||||||
|
// @Summary Get post by id
|
||||||
|
// @Description Get post by id
|
||||||
|
// @Tags posts
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} storage.PostReq
|
||||||
|
// @Router /posts/{id} [get]
|
||||||
|
func GetPost(c *gin.Context) {
|
||||||
|
log.Info("GetPost")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePost godoc
|
||||||
|
// @Summary Create post
|
||||||
|
// @Description Create new post
|
||||||
|
// @Tags posts
|
||||||
|
// @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
|
||||||
|
// @Summary Update post
|
||||||
|
// @Description Update post
|
||||||
|
// @Tags posts
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} storage.Post
|
||||||
|
// @Router /posts/{id} [put]
|
||||||
|
func UpdatePost(c *gin.Context) {
|
||||||
|
log.Info("UpdatePost")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePost godoc
|
||||||
|
// @Summary Delete post
|
||||||
|
// @Description Delete post
|
||||||
|
// @Tags posts
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} storage.Post
|
||||||
|
// @Router /posts/{id} [delete]
|
||||||
|
func DeletePost(c *gin.Context) {
|
||||||
|
log.Info("DeletePost")
|
||||||
|
}
|
||||||
21
backend/internal/handlers/registry_handlers.go
Normal file
21
backend/internal/handlers/registry_handlers.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/repositories"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
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("/", handler_posts.GetPosts)
|
||||||
|
posts.GET("/:id", GetPost)
|
||||||
|
posts.POST("/", handler_posts.CreatePost)
|
||||||
|
posts.PUT("/:id", UpdatePost)
|
||||||
|
posts.DELETE("/:id", DeletePost)
|
||||||
|
}
|
||||||
|
}
|
||||||
45
backend/internal/logger/logger.go
Normal file
45
backend/internal/logger/logger.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
*slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(debug bool) *Logger {
|
||||||
|
logDir := "/var/log/backend"
|
||||||
|
if err := os.MkdirAll(logDir, 0750); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fileWriter := &lumberjack.Logger{
|
||||||
|
Filename: filepath.Join(logDir, "backend.log"),
|
||||||
|
MaxSize: 500,
|
||||||
|
MaxBackups: 3,
|
||||||
|
MaxAge: 28,
|
||||||
|
Compress: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var level slog.Level
|
||||||
|
if debug {
|
||||||
|
level = slog.LevelDebug
|
||||||
|
} else {
|
||||||
|
level = slog.LevelInfo
|
||||||
|
}
|
||||||
|
multiWriter := io.MultiWriter(fileWriter, os.Stdout)
|
||||||
|
|
||||||
|
handler := slog.NewTextHandler(multiWriter, &slog.HandlerOptions{
|
||||||
|
Level: level,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &Logger{
|
||||||
|
Logger: slog.New(handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
15
backend/internal/repositories/interface.go
Normal file
15
backend/internal/repositories/interface.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PostRepository interface {
|
||||||
|
GetAll(ctx context.Context) ([]storage.PostReq, error)
|
||||||
|
GetByID(ctx context.Context, id int) (storage.PostReq, error)
|
||||||
|
Create(ctx context.Context, post storage.Post) error
|
||||||
|
Update(ctx context.Context, id int, post storage.Post) error
|
||||||
|
Delete(ctx context.Context, id int) error
|
||||||
|
}
|
||||||
85
backend/internal/repositories/post_repository.go
Normal file
85
backend/internal/repositories/post_repository.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/logger"
|
||||||
|
"gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type postRepository struct {
|
||||||
|
db *sql.DB
|
||||||
|
logger *logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPostRepository(db *sql.DB) PostRepository {
|
||||||
|
return &postRepository{
|
||||||
|
db: db,
|
||||||
|
logger: logger.New(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (p *postRepository) GetAll(ctx context.Context) ([]storage.PostReq, error) {
|
||||||
|
var result []storage.PostReq
|
||||||
|
rows, err := p.db.Query("SELECT title content FROM posts")
|
||||||
|
if err != nil {
|
||||||
|
p.logger.Error(err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
var title string
|
||||||
|
var content string
|
||||||
|
err := rows.Scan(&title, &content)
|
||||||
|
if err != nil {
|
||||||
|
p.logger.Error("error scan: " + err.Error())
|
||||||
|
}
|
||||||
|
result = append(result, storage.PostReq{
|
||||||
|
Title: title,
|
||||||
|
Content: content,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *postRepository) GetByID(ctx context.Context, id int) (storage.PostReq, error) {
|
||||||
|
var result storage.PostReq
|
||||||
|
row := p.db.QueryRow("SELECT title content FROM posts WHERE id = ?", id)
|
||||||
|
var title string
|
||||||
|
var content string
|
||||||
|
err := row.Scan(&title, &content)
|
||||||
|
if err != nil {
|
||||||
|
p.logger.Error("error scan: " + err.Error())
|
||||||
|
}
|
||||||
|
result = storage.PostReq{
|
||||||
|
Title: title,
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *postRepository) Update(ctx context.Context, id int, post storage.Post) error {
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *postRepository) Delete(ctx context.Context, id int) error {
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
32
backend/internal/storage/db.go
Normal file
32
backend/internal/storage/db.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/logger"
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
var db_path = os.Getenv(
|
||||||
|
"DB_PATH",
|
||||||
|
) + "?_journal_mode=WAL&_busy_timeout=5000&_synchronous=NORMAL&_cache_size=2000&_foreign_keys=ON"
|
||||||
|
|
||||||
|
func CreateTables(db *sql.DB) error {
|
||||||
|
logger := logger.New(false)
|
||||||
|
_, err := db.Exec(Migrations)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func OpenSession() (*sql.DB, error) {
|
||||||
|
db, err := sql.Open("sqlite", db_path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
10
backend/internal/storage/migrations.go
Normal file
10
backend/internal/storage/migrations.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
const Migrations = `
|
||||||
|
CREATE TABLE IF NOT EXISTS posts(
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
CREATED_AT DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
`
|
||||||
18
backend/internal/storage/models.go
Normal file
18
backend/internal/storage/models.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
type Post struct {
|
||||||
|
ID int `db:"id"`
|
||||||
|
Title string `db:"title"`
|
||||||
|
Content string `db:"content"`
|
||||||
|
CreatedAt string `db:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostReq struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostCreate struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
@@ -1,13 +1,42 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
<head>
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||||
<title>d3m0k1d - DevOps Engineer & InfoSec Student</title>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
</head>
|
<meta name="title" content="d3m0k1d - DevOps Engineer & InfoSec Student | Go Backend Developer" />
|
||||||
<body>
|
<meta name="keywords"
|
||||||
<div id="root"></div>
|
content="DevOps, InfoSec, Backend Developer, Go, Linux, Security, Portfolio, Programming, Personal Website, Personal blog, DSTU, Don State Technical Unversity, Unix" />
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="application/ld+json">
|
||||||
</body>
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "Person",
|
||||||
|
"name": "d3m0k1d",
|
||||||
|
"url": "https://d3m0k1d.ru",
|
||||||
|
"jobTitle": "DevOps Engineer",
|
||||||
|
"description": "DevOps Engineer, InfoSec student at DSTU, and Go backend developer",
|
||||||
|
"alumniOf": {
|
||||||
|
"@type": "EducationalOrganization",
|
||||||
|
"name": "Don State Technical University"
|
||||||
|
},
|
||||||
|
"knowsAbout": ["DevOps", "Information Security", "Backend Development", "Go", "Linux", "Infrastructure Automation"],
|
||||||
|
"sameAs": [
|
||||||
|
"https://github.com/d3m0k1d",
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<link rel="canonical" href="https://d3m0k1d.ru" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
|
||||||
|
<meta name="author" content="d3m0k1d" />
|
||||||
|
<meta name="robots" content="index, follow" />
|
||||||
|
<title>d3m0k1d - DevOps Engineer & InfoSec Student</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
BIN
frontend/public/favicon.png
Normal file
BIN
frontend/public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
Reference in New Issue
Block a user