first commit

This commit is contained in:
Ymnuk 2023-06-23 14:54:47 +03:00
commit 3097759c96
23 changed files with 589 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/dist

27
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,27 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go",
"args":[]
},
{
"name": "Generate backend",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go",
"args": [
"--metafile","example.yml",
"--outdir-backend","dist/backend"
]
}
]
}

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
The MIT License (MIT)
Copyright © 2023 <YmnukTech>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# Генератор приложений
Утилита предназначена для генерирования некоторых частей кода на основе файла с описанием для облегчения и ускорения написания приложений.

8
example.yml Normal file
View File

@ -0,0 +1,8 @@
name: test
db:
tables:
- name: test1
pk: uuid
fields:
- name: f1

8
go.mod Normal file
View File

@ -0,0 +1,8 @@
module git.ymnuktech.ru/ymnuk/yt-gen-app
go 1.20
require (
github.com/satori/go.uuid v1.2.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

5
go.sum Normal file
View File

@ -0,0 +1,5 @@
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

43
lib/backend.go Normal file
View File

@ -0,0 +1,43 @@
package lib
import (
"log"
"os"
"path/filepath"
"strings"
)
func Backend() {
generateDB()
}
func generateDB() {
// TODO
if err := os.MkdirAll(filepath.Join(AppConfig.OutdirBackend, "db", "model"), 0775); err != nil {
log.Fatal(err)
}
generateModelBase()
}
func generateModelBase() {
if err := WriteTmplFile("tmpl/backend/db/model/base.tmpl", filepath.Join(AppConfig.OutdirBackend, "db", "model", "base.go")); err != nil {
log.Fatal(err)
}
if err := WriteTmplFile("tmpl/backend/db/model/base-int.tmpl", filepath.Join(AppConfig.OutdirBackend, "db", "model", "base-int.go")); err != nil {
log.Fatal(err)
}
if err := WriteTmplFile("tmpl/backend/db/transaction.tmpl", filepath.Join(AppConfig.OutdirBackend, "db", "transaction.go")); err != nil {
log.Fatal(err)
}
if err := PrepareTmplFile("tmpl/backend/db/db.tmpl", Project, filepath.Join(AppConfig.OutdirBackend, "db", "db.go")); err != nil {
log.Fatal(err)
}
if len(Project.DB.Tables) > 0 {
for _, table := range Project.DB.Tables {
if err := PrepareTmplFile("tmpl/backend/db/model/model.tmpl", table, filepath.Join(AppConfig.OutdirBackend, "db", "model", strings.ToLower(table.Name)+".go")); err != nil {
log.Fatal(err)
}
}
}
}

50
lib/config.go Normal file
View File

@ -0,0 +1,50 @@
package lib
import (
"flag"
"fmt"
"os"
"git.ymnuktech.ru/ymnuk/yt-gen-app/structs"
)
var AppConfig struct {
IsYaml bool
IsJson bool
Help bool // Вывести справку на экран
Filename string // Файл с метоописанием в формате YAML или JSON
OutdirBackend string // Директория для сохранения кода сервера
OutdirFrontend string // Директория для сохранения кода клиента
}
var Project *structs.Project
func PrepareParams() {
parseArgs()
}
func parseArgs() {
flag.StringVar(&AppConfig.Filename, "metafile", "", "Путь к файлу с описанием для генерации кода")
flag.StringVar(&AppConfig.OutdirBackend, "outdir-backend", "", "Путь к директории для сохранения генерируемого кода сервера")
flag.StringVar(&AppConfig.OutdirFrontend, "outdir-frontend", "", "Путь к директории для сохранения генерируемого кода клиенат")
help := flag.Bool("help", false, "Справка")
flag.Parse()
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", os.Args[0])
flag.PrintDefaults()
}
if help != nil && *help {
flag.Usage()
os.Exit(0)
}
if AppConfig.Filename == "" {
flag.Usage()
os.Exit(0)
}
}
func HelpPrint() {
fmt.Println(`Утилита предназначена для генерирования некоторых частей кода на основе файла с описанием для облегчения и ускорения написания приложений. Использование:
yt-gen-app --metafile <Файл yaml> [--outdir <Директория для сохранения>]`)
}

5
lib/frontend.go Normal file
View File

@ -0,0 +1,5 @@
package lib
func Frontend() {
// TODO
}

24
lib/generate.go Normal file
View File

@ -0,0 +1,24 @@
package lib
import (
"log"
"os"
)
func Generate() {
if AppConfig.OutdirBackend != "" {
// Генерация серверной части
if err := os.MkdirAll(AppConfig.OutdirBackend, 0755); err != nil {
log.Fatal(err)
}
Backend()
}
if AppConfig.OutdirFrontend != "" {
// Генерация клиентской части
if err := os.MkdirAll(AppConfig.OutdirBackend, 0755); err != nil {
log.Fatal(err)
}
Frontend()
}
}

55
lib/prepare-metadata.go Normal file
View File

@ -0,0 +1,55 @@
package lib
import (
"log"
"git.ymnuktech.ru/ymnuk/yt-gen-app/structs"
uuid "github.com/satori/go.uuid"
)
func PrepareMetadata(project *structs.Project) {
if project == nil {
log.Fatal("Metadata is empty")
}
if project.Name == "" {
log.Fatal("Should be set project name")
}
// Подготовка БД
if len(project.DB.Tables) > 0 {
for i := range project.DB.Tables {
if project.DB.Tables[i].ID == uuid.Nil {
project.DB.Tables[i].ID = uuid.NewV4()
}
if len(project.DB.Tables[i].Fields) > 0 {
for j := range project.DB.Tables[i].Fields {
if project.DB.Tables[i].Fields[j].ID == uuid.Nil {
project.DB.Tables[i].Fields[j].ID = uuid.NewV4()
}
}
}
if len(project.DB.Tables[i].FKs) > 0 {
for j := range project.DB.Tables[i].FKs {
if project.DB.Tables[i].FKs[j].ID == uuid.Nil {
project.DB.Tables[i].FKs[j].ID = uuid.NewV4()
}
if project.DB.Tables[i].FKs[j].TableID == uuid.Nil && project.DB.Tables[i].FKs[j].TableName != "" {
for k := range project.DB.Tables {
if project.DB.Tables[i].Name == project.DB.Tables[i].FKs[j].TableName {
project.DB.Tables[i].FKs[j].TableID = project.DB.Tables[k].ID
}
}
}
if project.DB.Tables[i].FKs[j].TableID != uuid.Nil {
for k := range project.DB.Tables {
if project.DB.Tables[k].ID == project.DB.Tables[i].FKs[j].TableID {
project.DB.Tables[i].FKs[j].TableName = project.DB.Tables[k].Name
}
}
}
}
}
}
}
}

14
lib/random-hex.go Normal file
View File

@ -0,0 +1,14 @@
package lib
import (
"crypto/rand"
"encoding/hex"
)
func RandomHex(n int) (string, error) {
bytes := make([]byte, n)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}

82
lib/template.go Normal file
View File

@ -0,0 +1,82 @@
package lib
import (
"bufio"
"bytes"
"embed"
"log"
"os"
"strings"
"text/template"
)
//go:embed tmpl/*
var content embed.FS
var funcMap = template.FuncMap{
"fieldName": fieldName,
}
func fieldName(name string) (res string) {
res = strings.Trim(name, " ")
if res == "" {
log.Fatal("Shoudl be set name for field")
}
asRunes := []rune(res)
asRunes[0] = []rune(strings.ToUpper(string([]rune(res)[0])))[0]
res = string(asRunes)
i := strings.Index(res, "_")
for i != -1 {
asRunes := []rune(res)
if i >= len(res) {
break
}
asRunes[i+1] = []rune(strings.ToUpper(string(asRunes[i+1])))[0]
asRunes = append(asRunes[:i-1], asRunes[i+1:]...)
res = string(asRunes)
i = strings.Index(res, "_")
}
return
}
func WriteTmplFile(filename string, outname string) error {
if buff, err := content.ReadFile(filename); err != nil {
return err
} else {
if err = os.WriteFile(outname, buff, 0755); err != nil {
return err
}
}
return nil
}
// Использование шаблона
func PrepareTmplFile(filename string, data interface{}, outname string) (err error) {
var (
buff []byte
)
if buff, err = content.ReadFile(filename); err != nil {
return err
}
var (
tmp string
tmpl *template.Template
)
if tmp, err = RandomHex(4); err != nil {
return err
}
if tmpl, err = template.New(tmp).Funcs(funcMap).Parse(string(buff)); err != nil {
return err
}
var b bytes.Buffer
w := bufio.NewWriter(&b)
if err = tmpl.Execute(w, data); err != nil {
return err
}
w.Flush()
os.WriteFile(outname, b.Bytes(), 0755)
return
}

View File

@ -0,0 +1,97 @@
// This file generated automatic. Do not change this!
package db
import (
"database/sql"
"{{.Name}}/db/model"
"{{.Name}}/lib"
"log"
"os"
"time"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var DB *gorm.DB
func DBConnect() {
var err error
var newLogger logger.Interface
if lib.AppConfig.ENV == "dev" {
newLogger = logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
logger.Config{
SlowThreshold: time.Second, // Slow SQL threshold
LogLevel: logger.Info, // Log level
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
ParameterizedQueries: true, // Don't include params in the SQL log
Colorful: true, // Disable color
},
)
}
var conf = gorm.Config{
Logger: newLogger,
PrepareStmt: true,
IgnoreRelationshipsWhenMigrating: true,
CreateBatchSize: 1000,
}
switch lib.AppConfig.DB {
case "mysql":
dsn := lib.AppConfig.DBLogin + ":" + lib.AppConfig.DBPwd + "@tcp(" + lib.AppConfig.DBHost + ":" + lib.AppConfig.DBPort + ")/" + lib.AppConfig.DBName + "?charset=utf8mb4&parseTime=True&loc=Local"
DB, err = gorm.Open(mysql.Open(dsn), &conf)
if err != nil {
log.Fatal(err)
}
case "postgres":
dsn := "host=" + lib.AppConfig.DBHost + " user=" + lib.AppConfig.DBLogin + " password=" + lib.AppConfig.DBPwd + " dbname=" + lib.AppConfig.DBName + " port=" + lib.AppConfig.DBPort + " sslmode=disable TimeZone=Europe/Moscow"
DB, err = gorm.Open(postgres.Open(dsn), &conf)
if err != nil {
log.Fatal(err)
}
default:
log.Fatal("Server DB not set")
}
var sqlDB *sql.DB
sqlDB, err = DB.DB()
if err != nil {
log.Fatal(err)
}
// SetMaxIdleConns устанавливает максимальное количество подключений в пуле незанятых подключений.
sqlDB.SetMaxIdleConns(100)
// SetMaxOpenConns устанавливает максимальное количество открытых подключений к базе данных.
sqlDB.SetMaxOpenConns(100)
// SetConnMaxLifetime устанавливает максимальное время, в течение которого соединение может использоваться повторно.
sqlDB.SetConnMaxLifetime(time.Hour)
tx := BeginTransation()
err = tx.AutoMigrate(
model.Stock{},
model.Tick{},
model.Candle1m{},
model.Candle5m{},
model.Candle10m{},
model.Candle15m{},
model.Candle30m{},
model.Candle1d{},
model.Candle1w{},
model.Candle1Month{},
)
EndTransation(tx, err)
if err != nil {
log.Fatal(err)
}
}

View File

@ -0,0 +1,8 @@
package models
type BaseInt struct {
ID uint64 `gorm:"type:autoIncrement;primaryKey" json:"id,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
UpdatedAt time.Time `json:"updatedAt,omitempty"`
// DeletedAt gorm.DeletedAt `sql:"index" json:"deletedAt"`
}

View File

@ -0,0 +1,23 @@
package models
import (
"time"
uuid "github.com/satori/go.uuid"
"gorm.io/gorm"
)
type Base struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
UpdatedAt time.Time `json:"updatedAt,omitempty"`
// DeletedAt gorm.DeletedAt `sql:"index" json:"deletedAt"`
}
// BeforeCreate will set a UUID rather than numeric ID.
func (base *Base) BeforeCreate(db *gorm.DB) (err error) {
if base.ID == uuid.Nil {
base.ID = uuid.NewV4()
}
return nil
}

View File

@ -0,0 +1,18 @@
package model
import (
"time"
uuid "github.com/satori/go.uuid"
)
type {{.Name}} struct {
{{ if eq .Pk "uuid" }}
Base
{{ else }}
BaseInt
{{ end }}
{{ range $index, $field := .Fields }}
{{ fieldName $field.Name }} {{ $field.Type }}
{{ end }}
}

View File

@ -0,0 +1,17 @@
// This file generated automatic. Do not change this!
package db
import "gorm.io/gorm"
func BeginTransation() *gorm.DB {
return DB.Begin()
}
func EndTransation(tx *gorm.DB, err error) {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}

View File

@ -0,0 +1,5 @@
github.com/satori/go.uuid
gorm.io/gorm
gorm.io/driver/mysql
gorm.io/driver/postgres
gorm.io/gorm/logger

41
main.go Normal file
View File

@ -0,0 +1,41 @@
package main
import (
"encoding/json"
"log"
"os"
"git.ymnuktech.ru/ymnuk/yt-gen-app/lib"
"git.ymnuktech.ru/ymnuk/yt-gen-app/structs"
"gopkg.in/yaml.v3"
)
func init() {
lib.PrepareParams()
}
func main() {
lib.Project = &structs.Project{}
if _, err := os.Stat(lib.AppConfig.Filename); os.IsNotExist(err) {
log.Fatal(`Метафайл не найден`)
} else if err != nil {
log.Fatal(err)
}
var buff []byte
var err error
if buff, err = os.ReadFile(lib.AppConfig.Filename); err != nil {
log.Fatal(err)
}
if err = yaml.Unmarshal(buff, lib.Project); err == nil {
lib.AppConfig.IsYaml = true
} else if err = json.Unmarshal(buff, lib.Project); err == nil {
lib.AppConfig.IsJson = true
} else {
log.Fatal("Ошибка открытия файла")
}
lib.Generate()
}

39
structs/db.go Normal file
View File

@ -0,0 +1,39 @@
package structs
import uuid "github.com/satori/go.uuid"
type DB struct {
Tables []Table `yaml:"tables,omitempty"`
Description string `yaml:"description,omitempty" json:"nescription,omitempty"`
}
// Описание таблицы. Поле ID (индентификатор и первичный ключ) присутствуют всегда
type Table struct {
ID uuid.UUID `yaml:"id,omitempty" json:"id,omitempty"` // Идентификатор таблицы. Если пустой, то генерируется автоматически для дальнейше работы
Name string `yaml:"name,omitempty" json:"name,omitempty"` // Название таблицы
Schema string `yaml:"schema,omitempty" json:"schema,omitempty"` // Схема таблицы (для PostgreSQL либо префикс для остальных)
Pk string `yaml:"pk,omitempty" json:"pk,omitempty"` // Тип первичного ключа. Если uuid, то генерируется V4, int и bigint - используется bigint с автоинкрементом, string - первичный ключ как текстовое поле. Если не указано, то поумолчанию используется UUID
Description string `yaml:"description,omitempty" json:"description,omitempty"` // Описание таблицы
Fields []Field `yaml:"fields,omitempty" json:"fields,omitempty"` // Поля таблицы
FKs []FK `yaml:"fks,omitempty" json:"fks,omitempty"` // Внешние ключи
}
// Описание поля
type Field struct {
ID uuid.UUID `yaml:"id,omitempty" json:"id,omitempty"` // Идентификатор поля. Если пустой, то генерируется автоматически для дальнейше работы
Name string `yaml:"name,omitempty" json:"name,omitempty"` // Название поля
Recursive bool `yaml:"recursive,omitempty" json:"recursive,omitempty"` // Рекурсивная таблица
Type string `yaml:"type,omitempty" json:"type,omitempty"` // Тип поля. По умолчанию text
Length *int `yaml:"length,omitempty" json:"len,omitempty"` // Размер поля для строковых типов и Decimal
Accuracy *int `yaml:"accuracy,omitempty" json:"accuracy,omitempty"` // Точность поля для Decimal
Description string `yaml:"description,omitempty" json:"description,omitempty"` // Описание поля
}
// Описание внешних ключей
type FK struct {
ID uuid.UUID `yaml:"id,omitempty" json:"id,omitempty"` // Идентификатор внешнего ключа
Name string `yaml:"name,omitempty" json:"name,omitempty"` // Название ключа
TableID uuid.UUID `yaml:"idTable,omitempty" json:"idTable,omitempty"` // Идентификатор таблицы, на которую ссылаемся
TableName string `yaml:"tableName,omitempty" json:"tableName,omitempty"` // Название таблицы, на которую ссылаемся
Description string `yaml:"description,omitempty" json:"description,omitempty"` // Описание внешнего ключа
}

7
structs/project.go Normal file
View File

@ -0,0 +1,7 @@
package structs
type Project struct {
// Language string `yaml:"lang"` // Язык генерации. Поддерживаются go (серверная часть) и angular (клиентская часть)
Name string `yaml:"name"`
DB DB `yaml:"db"` // Структура БД
}