Compare commits
80 Commits
main
...
pagination
Author | SHA1 | Date | |
---|---|---|---|
![]() |
123e0daa7d | ||
![]() |
b4575f53e8 | ||
![]() |
525994f956 | ||
![]() |
c0e91951c2 | ||
![]() |
40df103023 | ||
![]() |
ae8e0898cc | ||
![]() |
0567abe346 | ||
![]() |
f012a6b031 | ||
![]() |
aead19ae97 | ||
![]() |
9c66efbada | ||
![]() |
f686525eac | ||
![]() |
c34dc73294 | ||
![]() |
a35df3ecc6 | ||
![]() |
7164b56099 | ||
![]() |
86e7319452 | ||
![]() |
7f0edaa9f6 | ||
![]() |
7a46b40c3f | ||
![]() |
47d8ed5e43 | ||
![]() |
fe5c898aeb | ||
![]() |
0da3c8004e | ||
![]() |
1e2c045bcd | ||
![]() |
abd73327ad | ||
![]() |
08e5db900c | ||
![]() |
524cc86ea9 | ||
![]() |
3ea9617ab4 | ||
![]() |
90dff5e262 | ||
![]() |
46b3111e53 | ||
![]() |
100696f35c | ||
![]() |
45bf4f6bb5 | ||
![]() |
ca3f7ef292 | ||
![]() |
5386195bda | ||
![]() |
5ec42b681a | ||
9226ec1e5e | |||
cef7a97724 | |||
bb4adf590b | |||
c0b1412fdf | |||
3eaefe7d82 | |||
41daab9a94 | |||
0dce9424e5 | |||
11f34fa4b3 | |||
5de742ed05 | |||
638c427939 | |||
c3a0b6df56 | |||
ecd2fa15f8 | |||
2fee8935be | |||
38dfd11f85 | |||
d96eff32db | |||
82a891787a | |||
ff60de838b | |||
84a7f64231 | |||
a66b186b05 | |||
220dd43a4f | |||
79695ceac0 | |||
47cfd6c008 | |||
c49ffb1d51 | |||
c08258f612 | |||
6d93f92fe2 | |||
aa5df3fadc | |||
ee7fa89369 | |||
24e798578e | |||
3d4901e746 | |||
da46f5a267 | |||
039ed007e4 | |||
7ceae9b8dc | |||
e19edb346b | |||
ae4e40fc6b | |||
be0cd54df8 | |||
a9d511e5c6 | |||
70afb2781c | |||
1a73a25af5 | |||
4e609e9126 | |||
47b62b5e07 | |||
8f9030f943 | |||
91264c021a | |||
17bc98db5e | |||
c35fb1b761 | |||
![]() |
663ac8a298 | ||
![]() |
e64e32874f | ||
![]() |
293b0b783a | ||
![]() |
fee55ca007 |
83
README.md
83
README.md
@ -62,13 +62,17 @@ blueprint
|
|||||||
|
|
||||||
### Настройки
|
### Настройки
|
||||||
`path` - альтернативное расположение папки.\
|
`path` - альтернативное расположение папки.\
|
||||||
`vars` - хеш-таблица значений, доступных в шаблонах. Пример в шаблоне: `{{.Vars.SomeValue}}`
|
`vars` - хеш-таблица значений, доступных в шаблонах. Пример в шаблоне: `{{.Vars.SomeValue}}`\
|
||||||
|
Для `vars` есть предопределенные значения:
|
||||||
|
- `ProjectRoot` - Корневая папка проекта. Абсолютный путь вызова приложения проекта.
|
||||||
|
- `ProjectModule` - Название модуля проекта.
|
||||||
|
|
||||||
## Модули их настройки и функции
|
## Модули их настройки и функции
|
||||||
Если модуль не будет указан в файле настроек, то он не будет вызван и не сможет использоваться в шаблонизаторе.\
|
Если модуль не будет указан в файле настроек, то он не будет вызван и не сможет использоваться в шаблонизаторе.\
|
||||||
Доступные модули:
|
Доступные модули:
|
||||||
- logger
|
- logger
|
||||||
- env
|
- env
|
||||||
|
- openapi
|
||||||
|
|
||||||
## logger
|
## logger
|
||||||
Модуль логгера для шаблонизатора
|
Модуль логгера для шаблонизатора
|
||||||
@ -77,9 +81,9 @@ blueprint
|
|||||||
`name` - Название логгера. Допустимые значения: `zap`
|
`name` - Название логгера. Допустимые значения: `zap`
|
||||||
|
|
||||||
### Функции модуля для шаблонизатора
|
### Функции модуля для шаблонизатора
|
||||||
`logger.Import()` - возвращает строку для импорта выбранного логгера. Например: `{{.Modules.logger.Import}}` для zap вернет `"go.uber.org/zap"`.\
|
`logger.Import()` - возвращает строку для импорта выбранного логгера. Например: `{{.Modules.logger.Import}}` для zap вернет `go.uber.org/zap`.\
|
||||||
`logger.ImportCore()` - возвращает строку для импорта ядра выбранного логгера. Например: `{{.Modules.logger.ImportCore}}` для zap вернет `"go.uber.org/zap/core"`.\
|
`logger.ImportCore()` - возвращает строку для импорта ядра выбранного логгера. Например: `{{.Modules.logger.ImportCore}}` для zap вернет `go.uber.org/zap/core`.\
|
||||||
`logger.Declaration()` - возвращает декларирование выбранного логгера. Например: `{{.Modules.logger.Declaration}}` для zap вернет:
|
`logger.Declaration(varName string)` - возвращает декларирование выбранного логгера. Например: `{{.Modules.logger.Declaration "logger"}}` для zap вернет:
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
cfgLogger := zap.NewDevelopmentConfig()
|
cfgLogger := zap.NewDevelopmentConfig()
|
||||||
@ -104,7 +108,7 @@ logger, err := cfgLogger.Build()
|
|||||||
- `name` - имя переменной окружения, в структуре именуется в CamelCase. **Обязательное значение.**
|
- `name` - имя переменной окружения, в структуре именуется в CamelCase. **Обязательное значение.**
|
||||||
- `type` - тип переменной для структуры. **Обязательное значение.**
|
- `type` - тип переменной для структуры. **Обязательное значение.**
|
||||||
- `default` - стандартное значение.
|
- `default` - стандартное значение.
|
||||||
- `required` - является ли значение обязательным. По умолчанию: false
|
- `required` - является ли значение обязательным. **По умолчанию: false**
|
||||||
|
|
||||||
Пример:
|
Пример:
|
||||||
```yaml
|
```yaml
|
||||||
@ -116,8 +120,8 @@ vars:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Функции модуля для шаблонизатора
|
### Функции модуля для шаблонизатора
|
||||||
`env.Import()` - возвращает строку для импорта env. `{{.Modules.env.Import}}` вернет `"github.com/caarlos0/env/v8"`\
|
`env.Import()` - возвращает строку для импорта env. `{{.Modules.env.Import}}` вернет `github.com/caarlos0/env/v8`\
|
||||||
`env.Declaration(v string)` - возвращает декларирование переменной с заполненной структурой переменных окружения. `{{.Modules.env.Declaration config}}` вернет
|
`env.Declaration(varName string)` - возвращает декларирование переменной с заполненной структурой переменных окружения. `{{.Modules.env.Declaration "config"}}` вернет
|
||||||
```golang
|
```golang
|
||||||
err = env.Parse(config)
|
err = env.Parse(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -131,3 +135,68 @@ type Config struct {
|
|||||||
AppName string `env:"APP_NAME,required" envDefault:"Some app name"`
|
AppName string `env:"APP_NAME,required" envDefault:"Some app name"`
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
## openapi
|
||||||
|
Модуль для генерации файлов Golang моделей и файла `database.puml` - диаграммы базы данных. Читает файл Openapi 3 спецификации из места вызова проекта.\
|
||||||
|
Рекомендуемое имя файла спецификации `Openapi.yaml`.\
|
||||||
|
Допустимые имена файла: `Openapi.yaml`, `Openapi.yml`, `openapi.yaml`, `penapi.yml`.\
|
||||||
|
Также для генерации слоев контроллера, сервиса, репозитория и сервера, инициализация всех слоев выводиться в app.\
|
||||||
|
Для добавления этого функционала стоит просто указать путь до желаемой директории, имя пакета будет имя директории.\
|
||||||
|
```yaml
|
||||||
|
openapi:
|
||||||
|
model_save_path: ./internal/models
|
||||||
|
controller_save_path: ./internal/controllers
|
||||||
|
service_save_path: ./internal/service
|
||||||
|
repository_save_path: ./internal/repository
|
||||||
|
server_save_path: ./internal/server/http
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Настройки
|
||||||
|
`full_path` - Полный путь к файлу OpenAPI. **По умолчанию: ./**\
|
||||||
|
`model_save_path` - Путь к папке сохранения моделей. **По умолчанию: ./models**\
|
||||||
|
`kroki_save_path` - Путь к папке сохранения диаграммы базы данных. **По умолчанию: ./**\
|
||||||
|
`db` - Название базы данных. Пока только добавляет теги к модели. **По умолчанию: nil**\
|
||||||
|
Допустимые значения:
|
||||||
|
- mongo
|
||||||
|
- sql
|
||||||
|
|
||||||
|
|
||||||
|
### Функции модуля для шаблонизатора
|
||||||
|
`openapi.KrokiPUML()` - возвращает диаграмму базы данных в формате PUML. Например `{{.Modules.openapi.KrokiPUML}}` вернет:
|
||||||
|
```plantuml
|
||||||
|
@startuml Database
|
||||||
|
|
||||||
|
map Category {
|
||||||
|
_id => **string** //primary_key//
|
||||||
|
name => **string**
|
||||||
|
products => **[]Product**
|
||||||
|
}
|
||||||
|
|
||||||
|
map Color {
|
||||||
|
r => **integer**
|
||||||
|
a => **integer**
|
||||||
|
b => **integer**
|
||||||
|
g => **integer**
|
||||||
|
}
|
||||||
|
|
||||||
|
map Product {
|
||||||
|
_id => **string** //primary_key//
|
||||||
|
color => **color**
|
||||||
|
name => **string**
|
||||||
|
status => **integer** //enum//
|
||||||
|
tags => **[]string**
|
||||||
|
}
|
||||||
|
|
||||||
|
map status {
|
||||||
|
0 => in_stock
|
||||||
|
1 => in reserve
|
||||||
|
2 => out-of-stock
|
||||||
|
}
|
||||||
|
|
||||||
|
Category::products -> Product
|
||||||
|
Product::color -> Color
|
||||||
|
Product::status -> status
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
@ -2,10 +2,14 @@ package blueprint
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint/modules/env"
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint/modules/env"
|
||||||
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint/modules/logger"
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint/modules/logger"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint/modules/openapi"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint/modules/openapi/layers"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BlueprintCreator struct {
|
type BlueprintCreator struct {
|
||||||
@ -17,7 +21,7 @@ type BlueprintCreator struct {
|
|||||||
type BlueprintModule interface {
|
type BlueprintModule interface {
|
||||||
Name() string
|
Name() string
|
||||||
SetSettings(settings any) error
|
SetSettings(settings any) error
|
||||||
Execute() error
|
Execute() (*layers.AllData, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBlueprintCreator(blueprintPath, projectName string) (*BlueprintCreator, error) {
|
func NewBlueprintCreator(blueprintPath, projectName string) (*BlueprintCreator, error) {
|
||||||
@ -34,6 +38,7 @@ func NewBlueprintCreator(blueprintPath, projectName string) (*BlueprintCreator,
|
|||||||
blueprintModules := []BlueprintModule{
|
blueprintModules := []BlueprintModule{
|
||||||
logger.NewLoggerModule(),
|
logger.NewLoggerModule(),
|
||||||
env.NewEnvModule(),
|
env.NewEnvModule(),
|
||||||
|
openapi.NewOpenAPIModule(),
|
||||||
}
|
}
|
||||||
|
|
||||||
templateModules := make(map[string]BlueprintModule, 0)
|
templateModules := make(map[string]BlueprintModule, 0)
|
||||||
@ -63,6 +68,8 @@ func NewBlueprintCreator(blueprintPath, projectName string) (*BlueprintCreator,
|
|||||||
template.SetVar("ProjectName", file.ProjectName)
|
template.SetVar("ProjectName", file.ProjectName)
|
||||||
template.SetVar("Author", file.Author)
|
template.SetVar("Author", file.Author)
|
||||||
template.SetVar("Description", file.Description)
|
template.SetVar("Description", file.Description)
|
||||||
|
template.SetVar("ProjectRoot", absExecPath)
|
||||||
|
template.SetVar("ProjectModule", filepath.Base(absExecPath))
|
||||||
|
|
||||||
return &BlueprintCreator{
|
return &BlueprintCreator{
|
||||||
template: template,
|
template: template,
|
||||||
@ -72,15 +79,52 @@ func NewBlueprintCreator(blueprintPath, projectName string) (*BlueprintCreator,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *BlueprintCreator) Create() error {
|
func (r *BlueprintCreator) Create() error {
|
||||||
|
if _, err := os.Stat("go.mod"); os.IsNotExist(err) {
|
||||||
|
cmd := exec.Command("go", "mod", "init")
|
||||||
|
|
||||||
|
log.Info().Msg("execCommands: go mod init")
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("execCommands: go mod init. %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute modules
|
||||||
|
var layersData *layers.AllData
|
||||||
|
for _, module := range r.modules {
|
||||||
|
data, err := module.Execute()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("module execute (%v). %v", module.Name(), err)
|
||||||
|
}
|
||||||
|
if data != nil {
|
||||||
|
layersData = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.template.settings.LayersData = layersData
|
||||||
|
|
||||||
|
// execute template
|
||||||
if err := r.template.Execute(); err != nil {
|
if err := r.template.Execute(); err != nil {
|
||||||
return fmt.Errorf("template execute. %v", err)
|
return fmt.Errorf("template execute. %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, module := range r.modules {
|
err := layers.InstallGoimports()
|
||||||
if err := module.Execute(); err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("module execute (%v). %v", module.Name(), err)
|
return fmt.Errorf("failed install go imports. %v", err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = layers.RunGoimports()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed linter go imports. %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// go mod tidy
|
||||||
|
cmd := exec.Command("go", "mod", "tidy")
|
||||||
|
|
||||||
|
log.Info().Msg("execCommands: go mod tidy")
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("execCommands: go mod tidy. %v", err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
27
blueprint/modules/env/env.go
vendored
27
blueprint/modules/env/env.go
vendored
@ -3,11 +3,11 @@ package env
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint/modules/openapi/layers"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EnvModule struct {
|
type EnvModule struct {
|
||||||
@ -36,13 +36,11 @@ func (r Var) code() string {
|
|||||||
def = fmt.Sprintf(` envDefault:"%v"`, r.Default)
|
def = fmt.Sprintf(` envDefault:"%v"`, r.Default)
|
||||||
}
|
}
|
||||||
|
|
||||||
caser := cases.Title(language.English)
|
|
||||||
|
|
||||||
for _, item := range strings.Split(r.Name, "_") {
|
for _, item := range strings.Split(r.Name, "_") {
|
||||||
fieldName += caser.String(item)
|
fieldName += utils.TextToTitle(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf(" %v %v `env:\"%v%v\"%v`", fieldName, r.Type, r.Name, req, def)
|
return fmt.Sprintf("\t%v %v `env:\"%v%v\"%v`", fieldName, r.Type, r.Name, req, def)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEnvModule() *EnvModule {
|
func NewEnvModule() *EnvModule {
|
||||||
@ -77,26 +75,25 @@ func (r *EnvModule) SetSettings(settings any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *EnvModule) Execute() error {
|
func (r *EnvModule) Execute() (*layers.AllData, error) {
|
||||||
value := strings.Replace(r.Import(), `"`, "", -1)
|
cmd := exec.Command("go", "get", r.Import())
|
||||||
cmd := exec.Command("go", "get", value)
|
|
||||||
|
|
||||||
return cmd.Run()
|
return nil, cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *EnvModule) Import() string {
|
func (r *EnvModule) Import() string {
|
||||||
return `"github.com/caarlos0/env/v8"`
|
return "github.com/caarlos0/env/v8"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *EnvModule) Declaration(v string) string {
|
func (r *EnvModule) Declaration(varName string) string {
|
||||||
if v == "" {
|
if varName == "" {
|
||||||
return "//FIXME TEMPLATE ERROR: env.Declaration required with non-empty argument"
|
return utils.TemplateError("env.Declaration required with non-empty argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf(`err = env.Parse(%v)
|
return fmt.Sprintf(`err = env.Parse(%v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}`, v)
|
}`, varName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *EnvModule) Struct() string {
|
func (r *EnvModule) Struct() string {
|
||||||
|
@ -3,9 +3,10 @@ package logger
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint/modules/openapi/layers"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LoggerModule struct {
|
type LoggerModule struct {
|
||||||
@ -57,17 +58,16 @@ func (r *LoggerModule) SetSettings(settings any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LoggerModule) Execute() error {
|
func (r *LoggerModule) Execute() (*layers.AllData, error) {
|
||||||
value := strings.Replace(r.Import(), `"`, "", -1)
|
cmd := exec.Command("go", "get", r.Import())
|
||||||
cmd := exec.Command("go", "get", value)
|
|
||||||
|
|
||||||
return cmd.Run()
|
return nil, cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
Import() string
|
Import() string
|
||||||
ImportCore() string
|
ImportCore() string
|
||||||
Declaration() string
|
Declaration(varName string) string
|
||||||
Type() string
|
Type() string
|
||||||
Message(messageType string, message string, fields ...any) string
|
Message(messageType string, message string, fields ...any) string
|
||||||
}
|
}
|
||||||
@ -80,8 +80,11 @@ func (r *LoggerModule) ImportCore() string {
|
|||||||
return r.logger.ImportCore()
|
return r.logger.ImportCore()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LoggerModule) Init() string {
|
func (r *LoggerModule) Declaration(varName string) string {
|
||||||
return r.logger.Declaration()
|
if varName == "" {
|
||||||
|
return utils.TemplateError("logger.Declaration required with non-empty argument")
|
||||||
|
}
|
||||||
|
return r.logger.Declaration(varName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LoggerModule) Type() string {
|
func (r *LoggerModule) Type() string {
|
||||||
|
@ -3,8 +3,7 @@ package logger
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"golang.org/x/text/cases"
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/utils"
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ZapLogger struct {
|
type ZapLogger struct {
|
||||||
@ -15,22 +14,22 @@ func NewZapLogger() *ZapLogger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *ZapLogger) Import() string {
|
func (r *ZapLogger) Import() string {
|
||||||
return `"go.uber.org/zap"`
|
return "go.uber.org/zap"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ZapLogger) ImportCore() string {
|
func (r *ZapLogger) ImportCore() string {
|
||||||
return `"go.uber.org/zap/zapcore"`
|
return "go.uber.org/zap/zapcore"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ZapLogger) Declaration() string {
|
func (r *ZapLogger) Declaration(varName string) string {
|
||||||
return `cfgLogger := zap.NewDevelopmentConfig()
|
return fmt.Sprintf(`cfgLogger := zap.NewDevelopmentConfig()
|
||||||
cfgLogger.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
cfgLogger.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||||
cfgLogger.EncoderConfig.ConsoleSeparator = " "
|
cfgLogger.EncoderConfig.ConsoleSeparator = " "
|
||||||
|
|
||||||
logger, err := cfgLogger.Build()
|
%v, err := cfgLogger.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}`
|
}`, varName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ZapLogger) Type() string {
|
func (r *ZapLogger) Type() string {
|
||||||
@ -40,15 +39,14 @@ func (r *ZapLogger) Type() string {
|
|||||||
func (r *ZapLogger) Message(messageType, message string, fields ...any) string {
|
func (r *ZapLogger) Message(messageType, message string, fields ...any) string {
|
||||||
var fieldsToString string
|
var fieldsToString string
|
||||||
|
|
||||||
caser := cases.Title(language.English)
|
messageType = utils.TextToTitle(messageType)
|
||||||
messageType = caser.String(messageType)
|
|
||||||
|
|
||||||
if !r.isAllowedMessageType(messageType) {
|
if !r.isAllowedMessageType(messageType) {
|
||||||
return fmt.Sprintf(`//FIXME TEMPLATE ERROR: logger.Message messageType bad type. Allowed types: %v`, r.allowedMessageTypes())
|
return utils.TemplateError(fmt.Sprintf(`logger.Message messageType bad type. Allowed types: %v`, r.allowedMessageTypes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(fields)%2 != 0 {
|
if len(fields)%2 != 0 {
|
||||||
return `//FIXME TEMPLATE ERROR: logger.Message need even number of fields. Example: logger.Message <messageType> <some msg> <fieldName> <fieldVal>...`
|
return utils.TemplateError("logger.Message need even number of fields. Example: logger.Message <messageType> <some msg> <fieldName> <fieldVal>...")
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(fields); i += 2 {
|
for i := 0; i < len(fields); i += 2 {
|
||||||
|
27
blueprint/modules/openapi/db/mongo.go
Normal file
27
blueprint/modules/openapi/db/mongo.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import "penahub.gitlab.yandexcloud.net/pena-services/blueprint/types/dbtypes"
|
||||||
|
|
||||||
|
type Mongo struct {
|
||||||
|
dbType dbtypes.DBType
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMongo(dbType dbtypes.DBType) *Mongo {
|
||||||
|
return &Mongo{dbType: dbType}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Mongo) Tag() string {
|
||||||
|
return "bson"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Mongo) GenerateSettings() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Mongo) SetTemplate(methodName string) string {
|
||||||
|
return "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Mongo) DBType() dbtypes.DBType {
|
||||||
|
return r.dbType
|
||||||
|
}
|
51
blueprint/modules/openapi/db/sql.go
Normal file
51
blueprint/modules/openapi/db/sql.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/types/dbtypes"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SQL struct {
|
||||||
|
dbType dbtypes.DBType
|
||||||
|
queriesTemplates []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSQL(dbType dbtypes.DBType) *SQL {
|
||||||
|
return &SQL{dbType: dbType, queriesTemplates: make([]string, 0)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SQL) Tag() string {
|
||||||
|
return "sql"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SQL) GenerateSettings() error {
|
||||||
|
if err := os.WriteFile("sqlc.yaml", []byte(dbtypes.SqlcYamlTemplate), 0644); err != nil {
|
||||||
|
return fmt.Errorf("failed creating sqlc.yaml: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dirPath := dbtypes.DirSqlcPath
|
||||||
|
|
||||||
|
if err := os.MkdirAll(dirPath, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed create dir sqlc/db_query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(dbtypes.PathToQueriesFile, []byte(strings.Join(r.queriesTemplates, "/n")), 0644); err != nil {
|
||||||
|
return fmt.Errorf("failed create file - queries.sql: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SQL) SetTemplate(methodName string) string {
|
||||||
|
if strings.Contains(strings.ToLower(methodName), "getlist") {
|
||||||
|
r.queriesTemplates = append(r.queriesTemplates, fmt.Sprintf(dbtypes.PaginationSqlTemplate, methodName))
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SQL) DBType() dbtypes.DBType {
|
||||||
|
return r.dbType
|
||||||
|
}
|
177
blueprint/modules/openapi/kroki/kroki.go
Normal file
177
blueprint/modules/openapi/kroki/kroki.go
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
package kroki
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var krokiTempate = `@startuml Database
|
||||||
|
|
||||||
|
{{range .Tables -}}
|
||||||
|
{{.String}}
|
||||||
|
{{end -}}
|
||||||
|
|
||||||
|
{{range .Enums -}}
|
||||||
|
{{.String}}
|
||||||
|
{{end -}}
|
||||||
|
|
||||||
|
{{range .Refs -}}
|
||||||
|
{{.String}}
|
||||||
|
{{end}}
|
||||||
|
@enduml`
|
||||||
|
|
||||||
|
type Kroki struct {
|
||||||
|
Tables []Table
|
||||||
|
Enums []Enum
|
||||||
|
Refs []Ref
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ref struct {
|
||||||
|
ChildName string
|
||||||
|
ChildField string
|
||||||
|
ParentName string
|
||||||
|
ParentField string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Ref) String() string {
|
||||||
|
parentLink := r.ParentName
|
||||||
|
if r.ParentField != "" {
|
||||||
|
parentLink += fmt.Sprintf("::%v", r.ParentField)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%v::%v -> %v", r.ChildName, r.ChildField, parentLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Enum struct {
|
||||||
|
Name string
|
||||||
|
Items []any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Enum) String() string {
|
||||||
|
result := fmt.Sprintf("map %v {\n", r.Name)
|
||||||
|
for iterator, item := range r.Items {
|
||||||
|
result += fmt.Sprintf("\t%v => %v\n", iterator, item)
|
||||||
|
}
|
||||||
|
result += "}\n" //nolint
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
NotePrimaryKey = "primary_key"
|
||||||
|
NeedParentField = "need_parent_field"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewKroki() *Kroki {
|
||||||
|
return &Kroki{
|
||||||
|
Tables: make([]Table, 0),
|
||||||
|
Refs: make([]Ref, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Kroki) AddTables(tables ...Table) {
|
||||||
|
r.Tables = append(r.Tables, tables...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Kroki) AddRefs(refs ...Ref) {
|
||||||
|
r.Refs = append(r.Refs, refs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Kroki) AddEnums(enums ...Enum) {
|
||||||
|
r.Enums = append(r.Enums, enums...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Kroki) FillFromOpenapi(openapi *openapi3.T) {
|
||||||
|
for name, schema := range openapi.Components.Schemas {
|
||||||
|
if strings.HasSuffix(strings.ToUpper(name), "REQ") || strings.HasSuffix(strings.ToUpper(name), "RESP") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
krokiTable := NewKrokiTable(name)
|
||||||
|
krokiTable.FillFromSchema(schema.Value)
|
||||||
|
r.AddTables(*krokiTable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Kroki) Generate() (string, error) {
|
||||||
|
// Fill References from Tables
|
||||||
|
for _, table := range r.Tables {
|
||||||
|
r.AddRefs(table.Refs...)
|
||||||
|
r.AddEnums(table.Enums...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Refs for fill
|
||||||
|
for _, ref := range r.Refs {
|
||||||
|
if ref.ParentField == NeedParentField {
|
||||||
|
for _, table := range r.Tables {
|
||||||
|
if table.Name == ref.ParentName {
|
||||||
|
for _, field := range table.Fields {
|
||||||
|
if field.Note == NotePrimaryKey {
|
||||||
|
ref.ParentField = field.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := template.New("kroki").Parse(krokiTempate)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Kroki.Parse: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
if err = t.Execute(buffer, r); err != nil {
|
||||||
|
return "", fmt.Errorf("Kroki.Execute: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Kroki) SavePUMLFile(savePath string) error {
|
||||||
|
if savePath == "" {
|
||||||
|
return fmt.Errorf("Kroki: path cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(savePath, 0750); err != nil {
|
||||||
|
return fmt.Errorf("Kroki.MkDirAll: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
absPath, err := filepath.Abs(savePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Kroki.LogPath: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := filepath.Join(absPath, "database.puml")
|
||||||
|
|
||||||
|
file, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Kroki.CreateFile: %v", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err = file.Close(); err != nil {
|
||||||
|
log.Err(fmt.Errorf("Kroki.CloseFile: %v", err)).Str("filepath", filePath).Msg("cannot close file")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
text, err := r.Generate()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Kroki.Generate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = file.WriteString(text)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Kroki.WriteToFile: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Str("file", filePath).Msg("kroki: file generated")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
128
blueprint/modules/openapi/kroki/kroki_table.go
Normal file
128
blueprint/modules/openapi/kroki/kroki_table.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package kroki
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Table struct {
|
||||||
|
Name string
|
||||||
|
Fields []KrokiTableField
|
||||||
|
Refs []Ref
|
||||||
|
Enums []Enum
|
||||||
|
}
|
||||||
|
|
||||||
|
type KrokiTableField struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
Note string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKrokiTable(name string) *Table {
|
||||||
|
return &Table{
|
||||||
|
Name: name,
|
||||||
|
Fields: make([]KrokiTableField, 0),
|
||||||
|
Refs: make([]Ref, 0),
|
||||||
|
Enums: make([]Enum, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Table) AddFields(fields ...KrokiTableField) {
|
||||||
|
r.Fields = append(r.Fields, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Table) FillFromSchema(schema *openapi3.Schema) {
|
||||||
|
iterator := 0
|
||||||
|
for propertyName, property := range schema.Properties {
|
||||||
|
field := KrokiTableField{
|
||||||
|
Name: propertyName,
|
||||||
|
Type: property.Value.Type,
|
||||||
|
Note: property.Value.Format,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(property.Value.Enum) > 0 {
|
||||||
|
field.Type = "integer"
|
||||||
|
field.Note = "enum"
|
||||||
|
r.Enums = append(r.Enums, Enum{
|
||||||
|
Name: propertyName,
|
||||||
|
Items: property.Value.Enum,
|
||||||
|
})
|
||||||
|
|
||||||
|
r.Refs = append(r.Refs, Ref{
|
||||||
|
ChildName: r.Name,
|
||||||
|
ChildField: propertyName,
|
||||||
|
ParentName: propertyName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if property.Value.Type == openapi3.TypeObject {
|
||||||
|
field.Type = propertyName
|
||||||
|
if property.Ref != "" {
|
||||||
|
ref := Ref{
|
||||||
|
ChildName: r.Name,
|
||||||
|
ChildField: propertyName,
|
||||||
|
ParentName: utils.TextGetLastElement(property.Ref, "/"),
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Refs = append(r.Refs, ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if property.Value.Type == openapi3.TypeArray {
|
||||||
|
field.Type = fmt.Sprintf("[]%v", property.Value.Items.Value.Type)
|
||||||
|
|
||||||
|
if property.Value.Items.Ref != "" {
|
||||||
|
field.Type = "[]" + utils.TextGetLastElement(property.Value.Items.Ref, "/")
|
||||||
|
ref := Ref{
|
||||||
|
ChildName: r.Name,
|
||||||
|
ChildField: propertyName,
|
||||||
|
ParentName: utils.TextGetLastElement(property.Value.Items.Ref, "/"),
|
||||||
|
}
|
||||||
|
r.Refs = append(r.Refs, ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(propertyName, "_id") && !utils.IsPrimaryKey(propertyName) {
|
||||||
|
ref := Ref{
|
||||||
|
ChildName: r.Name,
|
||||||
|
ChildField: propertyName,
|
||||||
|
ParentName: strings.TrimSuffix(propertyName, "_id"),
|
||||||
|
ParentField: NeedParentField,
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Refs = append(r.Refs, ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
if utils.IsPrimaryKey(propertyName) {
|
||||||
|
field.Note = NotePrimaryKey
|
||||||
|
if iterator != 0 {
|
||||||
|
r.AddFields(r.Fields[0])
|
||||||
|
r.Fields[0] = field
|
||||||
|
|
||||||
|
iterator++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.AddFields(field)
|
||||||
|
|
||||||
|
iterator++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Table) String() string {
|
||||||
|
result := fmt.Sprintf("map %v {\n", r.Name)
|
||||||
|
for _, field := range r.Fields {
|
||||||
|
result += fmt.Sprintf("\t%v => **%v**", field.Name, field.Type)
|
||||||
|
if field.Note != "" {
|
||||||
|
result += fmt.Sprintf(" //%v//", field.Note)
|
||||||
|
}
|
||||||
|
|
||||||
|
result += "\n"
|
||||||
|
}
|
||||||
|
result += "}\n"
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
188
blueprint/modules/openapi/kroki/kroki_test.go
Normal file
188
blueprint/modules/openapi/kroki/kroki_test.go
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
package kroki
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KrokiTestSuite struct {
|
||||||
|
doc *openapi3.T
|
||||||
|
kroki *Kroki
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
const testFile = "../openapi_test.yaml"
|
||||||
|
|
||||||
|
func (r *KrokiTestSuite) SetupSuite() {
|
||||||
|
loader := openapi3.NewLoader()
|
||||||
|
|
||||||
|
doc, err := loader.LoadFromFile(testFile)
|
||||||
|
r.NoErrorf(err, "cannot load openapi file: %v")
|
||||||
|
|
||||||
|
err = doc.Validate(loader.Context)
|
||||||
|
r.NoErrorf(err, "cannot validate openapi: %s")
|
||||||
|
|
||||||
|
r.doc = doc
|
||||||
|
|
||||||
|
r.kroki = NewKroki()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *KrokiTestSuite) TearDownSuite() {}
|
||||||
|
|
||||||
|
func TestKrokiTableTest(t *testing.T) {
|
||||||
|
suite.Run(t, new(KrokiTestSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *KrokiTestSuite) TestFillFromSchema() {
|
||||||
|
type args struct {
|
||||||
|
modelName string
|
||||||
|
schema *openapi3.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
tableColor := Table{
|
||||||
|
Name: "Color",
|
||||||
|
Fields: []KrokiTableField{
|
||||||
|
{
|
||||||
|
Name: "r",
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "g",
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "b",
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "a",
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Refs: make([]Ref, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
tableProduct := Table{
|
||||||
|
Name: "Product",
|
||||||
|
Fields: []KrokiTableField{
|
||||||
|
{
|
||||||
|
Name: "_id",
|
||||||
|
Type: "string",
|
||||||
|
Note: NotePrimaryKey,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "name",
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "color",
|
||||||
|
Type: "color",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "tags",
|
||||||
|
Type: "[]string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "status",
|
||||||
|
Type: "integer",
|
||||||
|
Note: "enum",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Refs: []Ref{
|
||||||
|
{
|
||||||
|
ChildName: "Product",
|
||||||
|
ChildField: "color",
|
||||||
|
ParentName: "Color",
|
||||||
|
ParentField: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ChildName: "Product",
|
||||||
|
ChildField: "status",
|
||||||
|
ParentName: "status",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tableCategory := Table{
|
||||||
|
Name: "Category",
|
||||||
|
Fields: []KrokiTableField{
|
||||||
|
{
|
||||||
|
Name: "_id",
|
||||||
|
Type: "string",
|
||||||
|
Note: NotePrimaryKey,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "name",
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "products",
|
||||||
|
Type: "[]Product",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Refs: []Ref{
|
||||||
|
{
|
||||||
|
ChildName: "Category",
|
||||||
|
ChildField: "products",
|
||||||
|
ParentName: "Product",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
got *Table
|
||||||
|
args args
|
||||||
|
want Table
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Color",
|
||||||
|
got: NewKrokiTable("Color"),
|
||||||
|
args: args{
|
||||||
|
modelName: "Color",
|
||||||
|
schema: r.doc.Components.Schemas["Color"].Value,
|
||||||
|
},
|
||||||
|
want: tableColor,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Product",
|
||||||
|
got: NewKrokiTable("Product"),
|
||||||
|
args: args{
|
||||||
|
modelName: "Product",
|
||||||
|
schema: r.doc.Components.Schemas["Product"].Value,
|
||||||
|
},
|
||||||
|
want: tableProduct,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Category",
|
||||||
|
got: NewKrokiTable("Category"),
|
||||||
|
args: args{
|
||||||
|
modelName: "Category",
|
||||||
|
schema: r.doc.Components.Schemas["Category"].Value,
|
||||||
|
},
|
||||||
|
want: tableCategory,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
r.Run(tt.name, func() {
|
||||||
|
tt.got.FillFromSchema(tt.args.schema)
|
||||||
|
r.Equal(tt.want.Name, tt.got.Name)
|
||||||
|
r.ElementsMatch(tt.want.Fields, tt.got.Fields)
|
||||||
|
r.ElementsMatch(tt.want.Refs, tt.got.Refs)
|
||||||
|
|
||||||
|
r.kroki.AddTables(*tt.got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *KrokiTestSuite) TestGenerate() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *KrokiTestSuite) TestSavePUMLFile() {
|
||||||
|
if err := r.kroki.SavePUMLFile("./model_test"); err != nil {
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
}
|
115
blueprint/modules/openapi/layers/controller_utils.go
Normal file
115
blueprint/modules/openapi/layers/controller_utils.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package layers
|
||||||
|
|
||||||
|
var httpMethods = map[string]string{
|
||||||
|
"GET": "Get",
|
||||||
|
"HEAD": "Head",
|
||||||
|
"POST": "Post",
|
||||||
|
"PUT": "Put",
|
||||||
|
"PATCH": "Patch",
|
||||||
|
"DELETE": "Delete",
|
||||||
|
"CONNECT": "Connect",
|
||||||
|
"OPTIONS": "Options",
|
||||||
|
"TRACE": "Trace",
|
||||||
|
}
|
||||||
|
|
||||||
|
const controllerTemplate = `
|
||||||
|
package {{ .PackageName }}
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type {{ .ControllerName }}Controller struct {
|
||||||
|
{{ .ControllerName }}Service *{{ .PackageService }}.{{ .ControllerName }}Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func New{{ .ControllerName }}Controller(service *{{ .PackageService }}.{{ .ControllerName }}Service) *{{ .ControllerName }}Controller {
|
||||||
|
return &{{ .ControllerName }}Controller{
|
||||||
|
{{ .ControllerName }}Service: service,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *{{ .ControllerName }}Controller) Register(router fiber.Router) {
|
||||||
|
{{ range .Endpoints }}
|
||||||
|
router.{{ execute "method" .MethodName .EndpointData }}("{{ execute "endpoint" .MethodName .EndpointData }}", c.{{ .MethodName }})
|
||||||
|
{{ end }}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *{{ .ControllerName }}Controller) Name() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
{{ range .Endpoints }}
|
||||||
|
func (c *{{ $.ControllerName }}Controller) {{ .MethodName }}(ctx *fiber.Ctx) error {
|
||||||
|
// Обработчик для метода {{ .MethodName }}
|
||||||
|
{{ $modelName := execute "model" .MethodName .EndpointData }}
|
||||||
|
{{ $requestType := execute "requestType" .MethodName .EndpointData }}
|
||||||
|
{{ $response := execute "response" .MethodName .EndpointData }}
|
||||||
|
{{ $responseType := execute "responseType" .MethodName .EndpointData }}
|
||||||
|
{{ if $modelName }}
|
||||||
|
var request {{ $requestType }}{{ $.ModelPackageName }}.{{ $modelName }}
|
||||||
|
if err := ctx.BodyParser(&request); err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||||
|
}
|
||||||
|
{{ with $validationData := executeValidation .MethodName .EndpointData $.ValidationCode }}
|
||||||
|
{{ $validationData }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if eq $response "" }}
|
||||||
|
{{ if eq $modelName "" }}
|
||||||
|
err := c.{{ $.ControllerName }}Service.{{ .MethodName }}(ctx.Context())
|
||||||
|
{{ else }}
|
||||||
|
err := c.{{ $.ControllerName }}Service.{{ .MethodName }}(ctx.Context(), &request)
|
||||||
|
{{ end }}
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||||
|
}
|
||||||
|
return ctx.SendStatus(fiber.StatusOK)
|
||||||
|
{{ else }}
|
||||||
|
{{ if eq $modelName "" }}
|
||||||
|
response, err := c.{{ $.ControllerName }}Service.{{ .MethodName }}(ctx.Context())
|
||||||
|
{{ else }}
|
||||||
|
response, err := c.{{ $.ControllerName }}Service.{{ .MethodName }}(ctx.Context(), &request)
|
||||||
|
{{ end }}
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||||
|
}
|
||||||
|
return ctx.Status(fiber.StatusOK).JSON(response)
|
||||||
|
{{ end }}
|
||||||
|
}
|
||||||
|
{{ end }}
|
||||||
|
`
|
||||||
|
|
||||||
|
type ControllerTemplateData struct {
|
||||||
|
PackageName string
|
||||||
|
ModelPackageName string
|
||||||
|
ControllerName string
|
||||||
|
Endpoints []Endpoint
|
||||||
|
ValidationCode map[string]string
|
||||||
|
PackageService string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Endpoint struct {
|
||||||
|
MethodName string
|
||||||
|
EndpointData map[string]EndpointData
|
||||||
|
}
|
||||||
|
|
||||||
|
type EndpointData struct {
|
||||||
|
Response string
|
||||||
|
ResponseType string
|
||||||
|
ModelName string
|
||||||
|
ModelType string
|
||||||
|
HttpMethod string
|
||||||
|
Endpoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseData struct {
|
||||||
|
Response string
|
||||||
|
ResponseType string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestData struct {
|
||||||
|
Request string
|
||||||
|
RequestType string
|
||||||
|
}
|
323
blueprint/modules/openapi/layers/generate_tools.go
Normal file
323
blueprint/modules/openapi/layers/generate_tools.go
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
package layers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
"os/exec"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/types/dbtypes"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/utils"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateValidationCode(endpoints []Endpoint, openapi *openapi3.T) (map[string]string, map[string]RequestData, map[string]ResponseData) {
|
||||||
|
validationCodes := make(map[string]string)
|
||||||
|
respMap := make(map[string]ResponseData)
|
||||||
|
reqMap := make(map[string]RequestData)
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
|
||||||
|
response, responseType, err := getResponseSchema(endpoint.EndpointData[endpoint.MethodName].Endpoint, endpoint.EndpointData[endpoint.MethodName].HttpMethod, openapi)
|
||||||
|
request, requesttype, err := getRequestSchema(endpoint.EndpointData[endpoint.MethodName].Endpoint, endpoint.EndpointData[endpoint.MethodName].HttpMethod, openapi)
|
||||||
|
|
||||||
|
respMap[endpoint.MethodName] = ResponseData{Response: response, ResponseType: responseType}
|
||||||
|
|
||||||
|
reqMap[endpoint.MethodName] = RequestData{Request: request, RequestType: requesttype}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to find RequestSchema or ResponseSchema:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
schema, found := openapi.Components.Schemas[request]
|
||||||
|
if !found {
|
||||||
|
fmt.Println("Schema not found in components:", request)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var validationCode strings.Builder
|
||||||
|
for propName, prop := range schema.Value.Properties {
|
||||||
|
propName = utils.TextToCamelCase(propName)
|
||||||
|
switch prop.Value.Type {
|
||||||
|
case "string":
|
||||||
|
if prop.Value.Enum != nil {
|
||||||
|
enumList := make([]string, len(prop.Value.Enum))
|
||||||
|
for i, v := range prop.Value.Enum {
|
||||||
|
enumList[i] = v.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
validationCode.WriteString("statusValue := request." + propName + ".String()\n")
|
||||||
|
validationCode.WriteString("if statusValue == \"Unknown\" {\n")
|
||||||
|
validationCode.WriteString(fmt.Sprintf(" return fiber.NewError(fiber.StatusBadRequest, \"%s must be one of %s\")\n", propName, strings.Join(enumList, ", ")))
|
||||||
|
validationCode.WriteString("}\n")
|
||||||
|
}
|
||||||
|
if prop.Value.MaxLength != nil {
|
||||||
|
validationCode.WriteString(fmt.Sprintf("if len(request.%s) > %d {\n", propName, int(*prop.Value.MaxLength)))
|
||||||
|
validationCode.WriteString(fmt.Sprintf(" return fiber.NewError(fiber.StatusBadRequest, \"Maximum length exceeded for %s\")\n", propName))
|
||||||
|
validationCode.WriteString("}\n")
|
||||||
|
}
|
||||||
|
if prop.Value.MinLength != 0 {
|
||||||
|
validationCode.WriteString(fmt.Sprintf("if len(request.%s) < %d {\n", propName, int(prop.Value.MinLength)))
|
||||||
|
validationCode.WriteString(fmt.Sprintf(" return fiber.NewError(fiber.StatusBadRequest, \"Minimum length not met for %s\")\n", propName))
|
||||||
|
validationCode.WriteString("}\n")
|
||||||
|
}
|
||||||
|
case "integer":
|
||||||
|
if prop.Value.Max != nil {
|
||||||
|
validationCode.WriteString(fmt.Sprintf("if request.%s > %d {\n", propName, int(*prop.Value.Max)))
|
||||||
|
validationCode.WriteString(fmt.Sprintf(" return fiber.NewError(fiber.StatusBadRequest, \"Value for %s must be less than or equal to %d\")\n", propName, int(*prop.Value.Max)))
|
||||||
|
validationCode.WriteString("}\n")
|
||||||
|
}
|
||||||
|
if prop.Value.Min != nil {
|
||||||
|
validationCode.WriteString(fmt.Sprintf("if request.%s < %d {\n", propName, int(*prop.Value.Min)))
|
||||||
|
validationCode.WriteString(fmt.Sprintf(" return fiber.NewError(fiber.StatusBadRequest, \"Value for %s must be greater than or equal to %d\")\n", propName, int(*prop.Value.Min)))
|
||||||
|
validationCode.WriteString("}\n")
|
||||||
|
}
|
||||||
|
case "boolean":
|
||||||
|
if prop.Value.Default != nil {
|
||||||
|
defaultBool := prop.Value.Default.(bool)
|
||||||
|
validationCode.WriteString(fmt.Sprintf("if request.%s != %t {\n", propName, defaultBool))
|
||||||
|
validationCode.WriteString(fmt.Sprintf(" return fiber.NewError(fiber.StatusBadRequest, \"%s must be %t\")\n", propName, defaultBool))
|
||||||
|
validationCode.WriteString("}\n")
|
||||||
|
}
|
||||||
|
case "array":
|
||||||
|
if prop.Value.MinItems != 0 && prop.Value.MaxItems != nil {
|
||||||
|
validationCode.WriteString(fmt.Sprintf("if len(request.%s) < %d || len(request.%s) > %d {\n", propName, prop.Value.MinItems, propName, *prop.Value.MaxItems))
|
||||||
|
validationCode.WriteString(fmt.Sprintf(" return fiber.NewError(fiber.StatusBadRequest, \"Array %s must contain between %d and %d items\")\n", propName, prop.Value.MinItems, *prop.Value.MaxItems))
|
||||||
|
validationCode.WriteString("}\n")
|
||||||
|
} else if prop.Value.MinItems != 0 {
|
||||||
|
|
||||||
|
validationCode.WriteString(fmt.Sprintf("if len(request.%s) < %d {\n", propName, prop.Value.MinItems))
|
||||||
|
validationCode.WriteString(fmt.Sprintf(" return fiber.NewError(fiber.StatusBadRequest, \"Array %s must contain at least %d items\")\n", propName, prop.Value.MinItems))
|
||||||
|
validationCode.WriteString("}\n")
|
||||||
|
} else if prop.Value.MaxItems != nil {
|
||||||
|
validationCode.WriteString(fmt.Sprintf("if len(request.%s) > %d {\n", propName, *prop.Value.MaxItems))
|
||||||
|
validationCode.WriteString(fmt.Sprintf(" return fiber.NewError(fiber.StatusBadRequest, \"Array %s must contain at most %d items\")\n", propName, *prop.Value.MaxItems))
|
||||||
|
validationCode.WriteString("}\n")
|
||||||
|
}
|
||||||
|
//if prop.Value.UniqueItems {
|
||||||
|
// validationCode.WriteString(fmt.Sprintf("if !чтото.isUnique(request.%s) {\n", propName))
|
||||||
|
// validationCode.WriteString(fmt.Sprintf(" return fiber.NewError(fiber.StatusBadRequest, \"%s must contain unique items\")\n", propName))
|
||||||
|
// validationCode.WriteString("}\n")
|
||||||
|
//}
|
||||||
|
case "object":
|
||||||
|
for innerPropName, innerProp := range prop.Value.Properties {
|
||||||
|
innerPropName = utils.TextToCamelCase(innerPropName)
|
||||||
|
switch innerProp.Value.Type {
|
||||||
|
case "string":
|
||||||
|
if innerProp.Value.MaxLength != nil {
|
||||||
|
validationCode.WriteString(fmt.Sprintf("if len(request.%s.%s) > %d {\n", propName, innerPropName, int(*innerProp.Value.MaxLength)))
|
||||||
|
validationCode.WriteString(fmt.Sprintf(" return fiber.NewError(fiber.StatusBadRequest, \"Maximum length exceeded for %s\")\n", innerPropName))
|
||||||
|
validationCode.WriteString("}\n")
|
||||||
|
}
|
||||||
|
if innerProp.Value.MinLength != 0 {
|
||||||
|
validationCode.WriteString(fmt.Sprintf("if len(request.%s.%s) < %d {\n", propName, innerPropName, int(innerProp.Value.MinLength)))
|
||||||
|
validationCode.WriteString(fmt.Sprintf(" return fiber.NewError(fiber.StatusBadRequest, \"Minimum length not met for %s\")\n", innerPropName))
|
||||||
|
validationCode.WriteString("}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if validationCode.String() != "" {
|
||||||
|
key := fmt.Sprintf("%s_%s", endpoint.EndpointData[endpoint.MethodName].HttpMethod, endpoint.EndpointData[endpoint.MethodName].Endpoint)
|
||||||
|
validationCodes[key] = validationCode.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return validationCodes, reqMap, respMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRequestSchema(endpoint, httpMethod string, openapi *openapi3.T) (string, string, error) {
|
||||||
|
pathItem, ok := openapi.Paths.Map()[endpoint]
|
||||||
|
if !ok {
|
||||||
|
return "", "", fmt.Errorf("endpoint not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
operation, ok := pathItem.Operations()[strings.ToUpper(httpMethod)]
|
||||||
|
if !ok {
|
||||||
|
return "", "", fmt.Errorf("operation not found for method %s", httpMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
requestBody := operation.RequestBody
|
||||||
|
if requestBody != nil && requestBody.Value != nil && requestBody.Value.Content != nil {
|
||||||
|
for _, contentType := range requestBody.Value.Content {
|
||||||
|
if contentType.Schema.Ref != "" {
|
||||||
|
return strings.TrimPrefix(contentType.Schema.Ref, "#/components/schemas/"), contentType.Schema.Value.Type, nil
|
||||||
|
} else {
|
||||||
|
return strings.TrimPrefix(contentType.Schema.Value.Items.Ref, "#/components/schemas/"), contentType.Schema.Value.Type, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", "", fmt.Errorf("request schema not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getResponseSchema(endpoint, httpMethod string, openapi *openapi3.T) (string, string, error) {
|
||||||
|
pathItem, ok := openapi.Paths.Map()[endpoint]
|
||||||
|
if !ok {
|
||||||
|
return "", "", fmt.Errorf("endpoint not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
operation, ok := pathItem.Operations()[strings.ToUpper(httpMethod)]
|
||||||
|
if !ok {
|
||||||
|
return "", "", fmt.Errorf("operation not found for method %s", httpMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
responseBody := operation.Responses.Value("200")
|
||||||
|
if responseBody == nil {
|
||||||
|
return "", "", fmt.Errorf("response for status code 200 not found")
|
||||||
|
}
|
||||||
|
contentType := responseBody.Value.Content.Get("application/json")
|
||||||
|
if contentType == nil {
|
||||||
|
return "", "", fmt.Errorf("response content type application/json not found")
|
||||||
|
}
|
||||||
|
if contentType.Schema != nil {
|
||||||
|
if contentType.Schema.Ref != "" {
|
||||||
|
return strings.TrimPrefix(contentType.Schema.Ref, "#/components/schemas/"), "", nil
|
||||||
|
} else if contentType.Schema.Value != nil && contentType.Schema.Value.Items != nil && contentType.Schema.Value.Items.Ref != "" {
|
||||||
|
responseType := contentType.Schema.Value.Type
|
||||||
|
return strings.TrimPrefix(contentType.Schema.Value.Items.Ref, "#/components/schemas/"), responseType, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", "", fmt.Errorf("response schema not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func execute(action, methodName string, endpointData map[string]EndpointData) string {
|
||||||
|
switch action {
|
||||||
|
case "method":
|
||||||
|
methodData, ok := endpointData[methodName]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return httpMethods[strings.ToUpper(methodData.HttpMethod)]
|
||||||
|
case "endpoint":
|
||||||
|
methodData, ok := endpointData[methodName]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return methodData.Endpoint
|
||||||
|
case "model":
|
||||||
|
modelData, ok := endpointData[methodName]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return modelData.ModelName
|
||||||
|
case "response":
|
||||||
|
responseData, ok := endpointData[methodName]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return responseData.Response
|
||||||
|
case "responseType":
|
||||||
|
responseData, ok := endpointData[methodName]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if responseData.ResponseType == "array" {
|
||||||
|
return "[]"
|
||||||
|
}
|
||||||
|
return responseData.ResponseType
|
||||||
|
case "requestType":
|
||||||
|
requestData, ok := endpointData[methodName]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if requestData.ModelType == "array" {
|
||||||
|
return "[]"
|
||||||
|
} else if requestData.ModelType == "object" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return requestData.ModelType
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeValidation(methodName string, endpointData map[string]EndpointData, validation map[string]string) string {
|
||||||
|
data, ok := endpointData[methodName]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return validation[data.HttpMethod+"_"+data.Endpoint]
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunGoimports() error {
|
||||||
|
cmd := exec.Command("goimports", "-w", ".")
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstallGoimports() error {
|
||||||
|
cmd := exec.Command("go", "install", "golang.org/x/tools/cmd/goimports@latest")
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectRepoMethodTemplate(methodName string, dbType dbtypes.DBType) string {
|
||||||
|
if strings.Contains(strings.ToLower(methodName), "getlist") {
|
||||||
|
switch dbType {
|
||||||
|
case dbtypes.DBTypeSQL:
|
||||||
|
return fmt.Sprintf(dbtypes.PaginationSqlRepoTemplate, methodName, methodName)
|
||||||
|
case dbtypes.DBTypeMongo:
|
||||||
|
return dbtypes.PaginationMongoTemplate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "//TODO:IMPLEMENT ME"
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateReceiverRepoStruct(structType string, dbType dbtypes.DBType) string {
|
||||||
|
if structType == "deps" {
|
||||||
|
if dbType == dbtypes.DBTypeSQL {
|
||||||
|
return "Queries *sqlcgen.Queries\n\tPool *sql.DB\n"
|
||||||
|
}
|
||||||
|
if dbType == dbtypes.DBTypeMongo {
|
||||||
|
return "Mdb *mongo.Collection"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if structType == "main" {
|
||||||
|
if dbType == dbtypes.DBTypeSQL {
|
||||||
|
return "queries *sqlcgen.Queries\n\tpool *sql.DB\n"
|
||||||
|
}
|
||||||
|
if dbType == dbtypes.DBTypeMongo {
|
||||||
|
return "mdb *mongo.Collection"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if structType == "receiver" {
|
||||||
|
if dbType == dbtypes.DBTypeSQL {
|
||||||
|
return "queries:deps.Queries,\n\tpool:deps.Pool,"
|
||||||
|
}
|
||||||
|
if dbType == dbtypes.DBTypeMongo {
|
||||||
|
return "mdb:deps.Mdb,"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func imports(layerType LayerType, whatIt interface{}) string {
|
||||||
|
if layerType == RepositoryLayer {
|
||||||
|
if whatIt.(dbtypes.DBType) == dbtypes.DBTypeSQL {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if whatIt.(dbtypes.DBType) == dbtypes.DBTypeMongo {
|
||||||
|
return "\"go.mongodb.org/mongo-driver/bson\"\n\t\"go.mongodb.org/mongo-driver/bson/primitive\"\n\t\"go.mongodb.org/mongo-driver/mongo\"\n\t\"go.mongodb.org/mongo-driver/mongo/readpref\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if layerType == ServiceLayer {
|
||||||
|
// todo
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if layerType == ControllerLayer {
|
||||||
|
// todo
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
335
blueprint/modules/openapi/layers/layers.go
Normal file
335
blueprint/modules/openapi/layers/layers.go
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
package layers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/types/dbtypes"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/utils"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LayerType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ControllerLayer LayerType = "controller"
|
||||||
|
ServiceLayer LayerType = "service"
|
||||||
|
RepositoryLayer LayerType = "repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
SaveModelPath string
|
||||||
|
PackageName string
|
||||||
|
SavePathController string
|
||||||
|
SavePathService string
|
||||||
|
SavePathRepository string
|
||||||
|
SavePathServer string
|
||||||
|
DB dbtypes.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
type Layers struct {
|
||||||
|
SaveModelPath string
|
||||||
|
PackageName string
|
||||||
|
SavePathController string
|
||||||
|
SavePathService string
|
||||||
|
SavePathRepository string
|
||||||
|
SavePathServer string
|
||||||
|
Controllers map[string]ControllerData
|
||||||
|
Services map[string]ServiceData
|
||||||
|
Repositories map[string]RepositoryData
|
||||||
|
db dbtypes.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
type ControllerData struct {
|
||||||
|
Name string
|
||||||
|
PackageName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceData struct {
|
||||||
|
Name string
|
||||||
|
PackageName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RepositoryData struct {
|
||||||
|
Name string
|
||||||
|
PackageName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AllData struct {
|
||||||
|
Controllers map[string]ControllerData
|
||||||
|
Services map[string]ServiceData
|
||||||
|
Repositories map[string]RepositoryData
|
||||||
|
ServerData string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLayers(deps Deps) *Layers {
|
||||||
|
return &Layers{
|
||||||
|
SaveModelPath: deps.SaveModelPath,
|
||||||
|
PackageName: deps.PackageName,
|
||||||
|
SavePathController: deps.SavePathController,
|
||||||
|
SavePathService: deps.SavePathService,
|
||||||
|
SavePathRepository: deps.SavePathRepository,
|
||||||
|
SavePathServer: deps.SavePathServer,
|
||||||
|
Controllers: make(map[string]ControllerData),
|
||||||
|
Services: make(map[string]ServiceData),
|
||||||
|
Repositories: make(map[string]RepositoryData),
|
||||||
|
db: deps.DB,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Layers) GetData() AllData {
|
||||||
|
return AllData{
|
||||||
|
Controllers: l.Controllers,
|
||||||
|
Services: l.Services,
|
||||||
|
Repositories: l.Repositories,
|
||||||
|
ServerData: filepath.Base(l.SavePathServer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Layers) FillFromOpenapi(openapi *openapi3.T) {
|
||||||
|
|
||||||
|
// Генерация репозитория сервиса контроллера сервера
|
||||||
|
l.GenerateAll(openapi)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Layers) GenerateAll(openapi *openapi3.T) {
|
||||||
|
tags := make(map[string][]Endpoint)
|
||||||
|
for path, pathItem := range openapi.Paths.Map() {
|
||||||
|
for method, operation := range pathItem.Operations() {
|
||||||
|
for _, tag := range operation.Tags {
|
||||||
|
tags[tag] = append(tags[tag], Endpoint{
|
||||||
|
MethodName: utils.TextToCamelCase(operation.OperationID),
|
||||||
|
EndpointData: map[string]EndpointData{
|
||||||
|
utils.TextToCamelCase(operation.OperationID): {
|
||||||
|
HttpMethod: httpMethods[method],
|
||||||
|
Endpoint: path,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for tagName, endpoints := range tags {
|
||||||
|
controllerName := strings.Title(tagName)
|
||||||
|
|
||||||
|
err := l.GenerateControllerData(controllerName, endpoints, openapi)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to generate layers:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for tagName, endpoints := range tags {
|
||||||
|
serviceName := strings.Title(tagName)
|
||||||
|
|
||||||
|
err := l.GenerateServiceFiles(serviceName, endpoints)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to generate service:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
l.Services[strings.ToLower(serviceName)] = ServiceData{
|
||||||
|
Name: serviceName,
|
||||||
|
PackageName: filepath.Base(l.SavePathService),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for tagName, endpoints := range tags {
|
||||||
|
repositoryName := strings.Title(tagName)
|
||||||
|
|
||||||
|
err := l.GenerateRepositoryFiles(repositoryName, endpoints)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to generate repository:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
l.Repositories[strings.ToLower(repositoryName)] = RepositoryData{
|
||||||
|
Name: repositoryName,
|
||||||
|
PackageName: filepath.Base(l.SavePathRepository),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := l.GenerateServerFiles()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to generate Server Files:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.db.GenerateSettings()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to generate database settings:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Layers) GenerateControllerData(controllerName string, endpoints []Endpoint, openapi *openapi3.T) error {
|
||||||
|
validationCodes, reqMap, respMap := generateValidationCode(endpoints, openapi)
|
||||||
|
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
tipe := respMap[endpoint.MethodName].ResponseType
|
||||||
|
resp := respMap[endpoint.MethodName].Response
|
||||||
|
req := reqMap[endpoint.MethodName]
|
||||||
|
|
||||||
|
data := EndpointData{
|
||||||
|
Response: resp,
|
||||||
|
ResponseType: tipe,
|
||||||
|
ModelName: req.Request,
|
||||||
|
ModelType: req.RequestType,
|
||||||
|
HttpMethod: endpoint.EndpointData[endpoint.MethodName].HttpMethod,
|
||||||
|
Endpoint: endpoint.EndpointData[endpoint.MethodName].Endpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint.EndpointData[endpoint.MethodName] = data
|
||||||
|
}
|
||||||
|
|
||||||
|
err := l.GenerateControllerFiles(controllerName, endpoints, validationCodes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Controllers[strings.ToLower(controllerName)] = ControllerData{
|
||||||
|
Name: controllerName,
|
||||||
|
PackageName: filepath.Base(l.SavePathController),
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Layers) GenerateControllerFiles(controllerName string, endpoints []Endpoint, validationCodes map[string]string) error {
|
||||||
|
data := ControllerTemplateData{
|
||||||
|
PackageName: filepath.Base(l.SavePathController),
|
||||||
|
ControllerName: controllerName,
|
||||||
|
Endpoints: endpoints,
|
||||||
|
ValidationCode: validationCodes,
|
||||||
|
PackageService: filepath.Base(l.SavePathService),
|
||||||
|
ModelPackageName: l.PackageName,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.MkdirAll(filepath.Dir(filepath.Join(l.SavePathController, strings.ToLower(controllerName)+".go")), 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(filepath.Join(l.SavePathController, strings.ToLower(controllerName)+".go"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
tmpl, err := template.New("layers").Funcs(template.FuncMap{
|
||||||
|
"execute": execute,
|
||||||
|
"executeValidation": executeValidation,
|
||||||
|
}).Parse(controllerTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tmpl.Execute(file, data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Layers) GenerateRepositoryFiles(repositoryName string, endpoints []Endpoint) error {
|
||||||
|
|
||||||
|
data := Repository{
|
||||||
|
PackageName: filepath.Base(l.SavePathRepository),
|
||||||
|
ModelPackageName: l.PackageName,
|
||||||
|
RepoName: repositoryName,
|
||||||
|
Endpoints: endpoints,
|
||||||
|
DBType: l.db.DBType(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.MkdirAll(filepath.Dir(filepath.Join(l.SavePathRepository, strings.ToLower(repositoryName)+".go")), 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(filepath.Join(l.SavePathRepository, strings.ToLower(repositoryName)+".go"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
tmpl, err := template.New("repository").Funcs(template.FuncMap{
|
||||||
|
"execute": execute,
|
||||||
|
"executeValidation": executeValidation,
|
||||||
|
"selectRepoMethodTemplate": selectRepoMethodTemplate,
|
||||||
|
"generateReceiverRepoStruct": generateReceiverRepoStruct,
|
||||||
|
"imports": imports,
|
||||||
|
"setDBTemplate": l.db.SetTemplate,
|
||||||
|
}).Parse(repositoryTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tmpl.Execute(file, data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Layers) GenerateServiceFiles(serviceName string, endpoints []Endpoint) error {
|
||||||
|
data := Service{
|
||||||
|
PackageName: filepath.Base(l.SavePathService),
|
||||||
|
ModelPackageName: l.PackageName,
|
||||||
|
ServiceName: serviceName,
|
||||||
|
Endpoints: endpoints,
|
||||||
|
PackageRepo: filepath.Base(l.SavePathRepository),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.MkdirAll(filepath.Dir(filepath.Join(l.SavePathService, strings.ToLower(serviceName)+".go")), 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(filepath.Join(l.SavePathService, strings.ToLower(serviceName)+".go"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
tmpl, err := template.New("service").Funcs(template.FuncMap{
|
||||||
|
"execute": execute,
|
||||||
|
"executeValidation": executeValidation,
|
||||||
|
}).Parse(serviceTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tmpl.Execute(file, data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Layers) GenerateServerFiles() error {
|
||||||
|
data := Server{
|
||||||
|
PackageName: filepath.Base(l.SavePathServer),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.MkdirAll(filepath.Dir(filepath.Join(l.SavePathServer, strings.ToLower("http")+".go")), 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(filepath.Join(l.SavePathServer, strings.ToLower("http")+".go"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
tmpl, err := template.New("server").Parse(serverTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tmpl.Execute(file, data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
60
blueprint/modules/openapi/layers/layers_test.go
Normal file
60
blueprint/modules/openapi/layers/layers_test.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package layers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint/modules/openapi/db"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint/modules/openapi/kroki"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint/modules/openapi/model"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/types/dbtypes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testFile = "../openapi_test.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateRequestModelsFiles(t *testing.T) {
|
||||||
|
deps := Deps{
|
||||||
|
SaveModelPath: "../model/model_test",
|
||||||
|
PackageName: "model_test",
|
||||||
|
SavePathController: "./controller/",
|
||||||
|
SavePathService: "./service/",
|
||||||
|
SavePathRepository: "./repository/",
|
||||||
|
SavePathServer: "./server/http/",
|
||||||
|
DB: db.NewMongo(dbtypes.DBTypeSQL),
|
||||||
|
}
|
||||||
|
c := NewLayers(deps)
|
||||||
|
loader := openapi3.NewLoader()
|
||||||
|
doc, err := loader.LoadFromFile(testFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
c.FillFromOpenapi(doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateModels(t *testing.T) {
|
||||||
|
c := model.NewModel("../model/model_test")
|
||||||
|
loader := openapi3.NewLoader()
|
||||||
|
doc, err := loader.LoadFromFile(testFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
err = c.FillFromOpenapi(doc)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKroki(t *testing.T) {
|
||||||
|
c := kroki.NewKroki()
|
||||||
|
loader := openapi3.NewLoader()
|
||||||
|
doc, err := loader.LoadFromFile(testFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
c.FillFromOpenapi(doc)
|
||||||
|
if err = c.SavePUMLFile("../model/model_test"); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
61
blueprint/modules/openapi/layers/repository_utils.go
Normal file
61
blueprint/modules/openapi/layers/repository_utils.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package layers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/types/dbtypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
const repositoryTemplate = `
|
||||||
|
package {{ .PackageName }}
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
{{ imports "repository" .DBType }}
|
||||||
|
)
|
||||||
|
|
||||||
|
type {{ .RepoName }}Repository struct {
|
||||||
|
{{ generateReceiverRepoStruct "main" .DBType }}
|
||||||
|
}
|
||||||
|
|
||||||
|
type {{ .RepoName }}Deps struct {
|
||||||
|
{{ generateReceiverRepoStruct "deps" .DBType }}
|
||||||
|
}
|
||||||
|
|
||||||
|
func New{{ .RepoName }}Repository(deps {{ .RepoName }}Deps) *{{ .RepoName }}Repository {
|
||||||
|
return &{{ .RepoName }}Repository{
|
||||||
|
{{ generateReceiverRepoStruct "receiver" .DBType }}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{ range .Endpoints }}
|
||||||
|
{{ setDBTemplate .MethodName}}
|
||||||
|
{{ $modelName := execute "model" .MethodName .EndpointData }}
|
||||||
|
{{ $response := execute "response" .MethodName .EndpointData }}
|
||||||
|
{{ $responseType := execute "responseType" .MethodName .EndpointData }}
|
||||||
|
{{ $requestType := execute "requestType" .MethodName .EndpointData }}
|
||||||
|
func (r *{{ $.RepoName }}Repository) {{ .MethodName }}(ctx context.Context{{ if $modelName }}, request *{{ $requestType }}{{ $.ModelPackageName }}.{{ $modelName }}{{ end }}) ({{ if $response }}{{$responseType}}*{{ $.ModelPackageName }}.{{ $response }}, {{ end }}error) {
|
||||||
|
{{ selectRepoMethodTemplate .MethodName $.DBType }}
|
||||||
|
{{ if $modelName }}
|
||||||
|
{{ if $response }}
|
||||||
|
|
||||||
|
return &{{ $.ModelPackageName }}.{{ $response }}{}, nil
|
||||||
|
{{ else }}
|
||||||
|
return nil
|
||||||
|
{{ end }}
|
||||||
|
{{ else }}
|
||||||
|
{{ if $response }}
|
||||||
|
return &{{ $.ModelPackageName }}.{{ $response }}{}, nil
|
||||||
|
{{ else }}
|
||||||
|
return nil
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
}
|
||||||
|
{{ end }}
|
||||||
|
`
|
||||||
|
|
||||||
|
type Repository struct {
|
||||||
|
PackageName string
|
||||||
|
ModelPackageName string
|
||||||
|
RepoName string
|
||||||
|
Endpoints []Endpoint
|
||||||
|
DBType dbtypes.DBType
|
||||||
|
}
|
69
blueprint/modules/openapi/layers/server_utils.go
Normal file
69
blueprint/modules/openapi/layers/server_utils.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package layers
|
||||||
|
|
||||||
|
const serverTemplate = `
|
||||||
|
package {{ .PackageName }}
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerConfig struct {
|
||||||
|
Controllers []Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
Controllers []Controller
|
||||||
|
app *fiber.App
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(config ServerConfig) *Server {
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
s := &Server{
|
||||||
|
Controllers: config.Controllers,
|
||||||
|
app: app,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.registerRoutes()
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start(addr string) error {
|
||||||
|
if err := s.app.Listen(addr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Shutdown(ctx context.Context) error {
|
||||||
|
return s.app.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) registerRoutes() {
|
||||||
|
for _, c := range s.Controllers {
|
||||||
|
router := s.app.Group(c.Name())
|
||||||
|
c.Register(router)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Controller interface {
|
||||||
|
Register(router fiber.Router)
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListRoutes() {
|
||||||
|
fmt.Println("Registered routes:")
|
||||||
|
for _, stack := range s.app.Stack() {
|
||||||
|
for _, route := range stack {
|
||||||
|
fmt.Printf("%s %s\n", route.Method, route.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
PackageName string
|
||||||
|
}
|
65
blueprint/modules/openapi/layers/service_utils.go
Normal file
65
blueprint/modules/openapi/layers/service_utils.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package layers
|
||||||
|
|
||||||
|
const serviceTemplate = `
|
||||||
|
package {{ .PackageName }}
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type {{ .ServiceName }}Service struct {
|
||||||
|
{{ .ServiceName }}Repository *{{ .PackageRepo }}.{{ .ServiceName }}Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
func New{{ .ServiceName }}Service(repository *{{ .PackageRepo }}.{{ .ServiceName }}Repository) *{{ .ServiceName }}Service {
|
||||||
|
return &{{ .ServiceName }}Service{
|
||||||
|
{{ .ServiceName }}Repository: repository,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{ range .Endpoints }}
|
||||||
|
{{ $modelName := execute "model" .MethodName .EndpointData }}
|
||||||
|
{{ $response := execute "response" .MethodName .EndpointData }}
|
||||||
|
{{ $responseType := execute "responseType" .MethodName .EndpointData }}
|
||||||
|
{{ $requestType := execute "requestType" .MethodName .EndpointData }}
|
||||||
|
func (s *{{ $.ServiceName }}Service) {{ .MethodName }}(ctx context.Context{{ if $modelName }}, request *{{ $requestType }}{{ $.ModelPackageName }}.{{ $modelName }}{{ end }}) ({{ if $response }}{{$responseType}}*{{ $.ModelPackageName }}.{{ $response }}, {{ end }}error) {
|
||||||
|
{{ if $modelName }}
|
||||||
|
{{ if $response }}
|
||||||
|
response, err := s.{{ $.ServiceName }}Repository.{{ .MethodName }}(ctx,{{ if $modelName }}request{{ end }})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
{{ else }}
|
||||||
|
err := s.{{ $.ServiceName }}Repository.{{ .MethodName }}(ctx,{{ if $modelName }}request{{ end }})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
{{ end }}
|
||||||
|
{{ else }}
|
||||||
|
{{ if $response }}
|
||||||
|
response, err := s.{{ $.ServiceName }}Repository.{{ .MethodName }}(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
{{ else }}
|
||||||
|
err := s.{{ $.ServiceName }}Repository.{{ .MethodName }}(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
}
|
||||||
|
{{ end }}
|
||||||
|
`
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
PackageName string
|
||||||
|
ModelPackageName string
|
||||||
|
ServiceName string
|
||||||
|
Endpoints []Endpoint
|
||||||
|
PackageRepo string
|
||||||
|
}
|
94
blueprint/modules/openapi/model/model.go
Normal file
94
blueprint/modules/openapi/model/model.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Model struct {
|
||||||
|
SavePath string
|
||||||
|
Files []*File
|
||||||
|
Tags []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Model) AddTags(tags ...string) {
|
||||||
|
r.Tags = append(r.Tags, tags...)
|
||||||
|
|
||||||
|
for _, file := range r.Files {
|
||||||
|
file.AddTagsToFields(tags...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Model) SaveFiles() error {
|
||||||
|
for _, file := range r.Files {
|
||||||
|
if err := file.Save(r.SavePath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewModel(savePath string) *Model {
|
||||||
|
return &Model{
|
||||||
|
SavePath: savePath,
|
||||||
|
Files: make([]*File, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Model) FillFromOpenapi(openAPI *openapi3.T) error {
|
||||||
|
for name, schemaRef := range openAPI.Components.Schemas {
|
||||||
|
file := NewFile(name, filepath.Base(r.SavePath))
|
||||||
|
file.SetName(name)
|
||||||
|
file.FillFromSchema(schemaRef.Value)
|
||||||
|
|
||||||
|
if err := file.Save(filepath.Join(r.SavePath)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// создает структуры по ревесту и респонсу который не внутри компонентс
|
||||||
|
func (r *Model) FillFromOpenapiForLayers(openAPI *openapi3.T) error {
|
||||||
|
for _, pathItem := range openAPI.Paths.Map() {
|
||||||
|
for _, operation := range pathItem.Operations() {
|
||||||
|
if operation.RequestBody != nil {
|
||||||
|
if err := r.processSchema(operation.OperationID, operation.RequestBody.Value.Content, r.SavePath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, response := range operation.Responses.Map() {
|
||||||
|
if err := r.processSchema(operation.OperationID, response.Value.Content, r.SavePath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Model) processSchema(name string, content openapi3.Content, savePath string) error {
|
||||||
|
for _, contentType := range content {
|
||||||
|
if contentType.Schema != nil {
|
||||||
|
if contentType.Schema.Ref == "" && contentType.Schema.Value.Items == nil {
|
||||||
|
file := NewFile(name, filepath.Base(savePath))
|
||||||
|
file.SetName(name + "Req")
|
||||||
|
file.parseSchema(contentType.Schema)
|
||||||
|
if err := file.Save(filepath.Join(savePath)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if contentType.Schema.Value.Items != nil && contentType.Schema.Value.Items.Ref == "" {
|
||||||
|
file := NewFile(name, filepath.Base(savePath))
|
||||||
|
file.SetName(name + "Resp")
|
||||||
|
file.parseSchema(contentType.Schema.Value.Items)
|
||||||
|
if err := file.Save(filepath.Join(savePath)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
302
blueprint/modules/openapi/model/model_file.go
Normal file
302
blueprint/modules/openapi/model/model_file.go
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var FileTemplate = `package {{.PackageName}}
|
||||||
|
|
||||||
|
type {{.Name}} struct {
|
||||||
|
{{- range .Fields}}
|
||||||
|
/*{{.Title}} - {{.Description}}*/
|
||||||
|
{{.Name}} {{.Type}} {{.Tag.String -}}
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{range .Enums -}}
|
||||||
|
{{.String}}
|
||||||
|
{{end -}}
|
||||||
|
`
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
PackageName string
|
||||||
|
Name string
|
||||||
|
Fields []FileField
|
||||||
|
Enums []FileEnum
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileField struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
Tag *FileTag
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileTag struct {
|
||||||
|
Name string
|
||||||
|
Types []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTag(name string, types ...string) *FileTag {
|
||||||
|
return &FileTag{
|
||||||
|
Name: name,
|
||||||
|
Types: types,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FileTag) String() string {
|
||||||
|
var result string
|
||||||
|
if len(r.Types) == 0 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
for iterator, t := range r.Types {
|
||||||
|
result += fmt.Sprintf(`%v:"%v"`, t, r.Name)
|
||||||
|
if iterator < len(r.Types)-1 {
|
||||||
|
result += " "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("`%v`", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FileTag) AddTypes(types ...string) {
|
||||||
|
r.Types = append(r.Types, types...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileEnum struct {
|
||||||
|
Name string
|
||||||
|
Items []any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r FileEnum) String() string {
|
||||||
|
// constants
|
||||||
|
result := fmt.Sprintf("type %v int\n\nconst (\n", r.Name)
|
||||||
|
|
||||||
|
for iterator, item := range r.Items {
|
||||||
|
itemName := r.Name + utils.TextToCamelCase(item.(string))
|
||||||
|
if iterator == 0 {
|
||||||
|
result += fmt.Sprintf("\t%v %v = iota\n", itemName, r.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result += fmt.Sprintf("\t%v\n", itemName)
|
||||||
|
}
|
||||||
|
|
||||||
|
result += ")\n\n"
|
||||||
|
|
||||||
|
// string function
|
||||||
|
result += fmt.Sprintf("func (r %v) String() string {\n\tswitch r {\n", r.Name)
|
||||||
|
for _, item := range r.Items {
|
||||||
|
itemName := r.Name + utils.TextToCamelCase(item.(string))
|
||||||
|
result += fmt.Sprintf("\tcase %v:\n", itemName)
|
||||||
|
result += fmt.Sprintf("\t\treturn \"%v\"\n", item)
|
||||||
|
}
|
||||||
|
result += "\t}\n"
|
||||||
|
result += "\treturn \"Unknown\"\n}"
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFile(name, packageName string) *File {
|
||||||
|
return &File{
|
||||||
|
Name: name,
|
||||||
|
PackageName: packageName,
|
||||||
|
Fields: make([]FileField, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *File) SetName(name string) {
|
||||||
|
r.Name = name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *File) AddFields(fields ...FileField) {
|
||||||
|
r.Fields = append(r.Fields, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *File) AddTagsToFields(tag ...string) {
|
||||||
|
for i := range r.Fields {
|
||||||
|
r.Fields[i].Tag.AddTypes(tag...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *File) FillFromSchema(schema *openapi3.Schema) {
|
||||||
|
iterator := 0
|
||||||
|
for propertyName, property := range schema.Properties {
|
||||||
|
field := FileField{
|
||||||
|
Name: utils.TextToCamelCase(propertyName),
|
||||||
|
Type: openapiTypeToGolangType(property.Value.Type),
|
||||||
|
Tag: NewTag(propertyName, "json"),
|
||||||
|
Title: property.Value.Title,
|
||||||
|
Description: property.Value.Description,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(property.Value.Enum) > 0 {
|
||||||
|
field.Type = utils.TextToCamelCase(propertyName)
|
||||||
|
|
||||||
|
r.Enums = append(r.Enums, FileEnum{
|
||||||
|
Name: field.Type,
|
||||||
|
Items: property.Value.Enum,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if property.Value.Type == openapi3.TypeObject {
|
||||||
|
field.Type = utils.TextToCamelCase(propertyName)
|
||||||
|
if property.Ref != "" {
|
||||||
|
field.Type = utils.TextGetLastElement(property.Ref, "/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if property.Value.Type == openapi3.TypeArray {
|
||||||
|
field.Type = fmt.Sprintf("[]%v", openapiTypeToGolangType(property.Value.Items.Value.Type))
|
||||||
|
|
||||||
|
if property.Value.Items.Value.Type == openapi3.TypeObject {
|
||||||
|
field.Type = "[]any"
|
||||||
|
}
|
||||||
|
|
||||||
|
if property.Value.Items.Ref != "" {
|
||||||
|
field.Type = "[]" + utils.TextGetLastElement(property.Value.Items.Ref, "/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if utils.IsPrimaryKey(propertyName) {
|
||||||
|
if iterator != 0 {
|
||||||
|
r.AddFields(r.Fields[0])
|
||||||
|
r.Fields[0] = field
|
||||||
|
|
||||||
|
iterator++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.AddFields(field)
|
||||||
|
iterator++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *File) Generate() (string, error) {
|
||||||
|
t, err := template.New("model_file").Parse(FileTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Generate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
if err = t.Execute(buffer, r); err != nil {
|
||||||
|
return "", fmt.Errorf("Generate: %v", err)
|
||||||
|
}
|
||||||
|
return buffer.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *File) Save(savePath string) error {
|
||||||
|
if savePath == "" {
|
||||||
|
return fmt.Errorf("Model: path cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(savePath); os.IsNotExist(err) {
|
||||||
|
if err := os.MkdirAll(savePath, 0755); err != nil {
|
||||||
|
return fmt.Errorf("Model: cannot create directory: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := fmt.Sprintf("%v.go", utils.TextToLower(r.Name))
|
||||||
|
filePath := filepath.Join(savePath, fileName)
|
||||||
|
|
||||||
|
file, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Model.CreateFile: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err = file.Close(); err != nil {
|
||||||
|
log.Err(fmt.Errorf("Model.CloseFile: %v", err)).Str("filepath", filePath).Msg("cannot close file")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
text, err := r.Generate()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Model.%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = file.WriteString(text)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Model.WriteToFile: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
absPath, err := filepath.Abs(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Model.Abs: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Str("file", absPath).Msg("model: file generated")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func openapiTypeToGolangType(t string) string {
|
||||||
|
switch t {
|
||||||
|
case openapi3.TypeArray:
|
||||||
|
return "[]"
|
||||||
|
case openapi3.TypeBoolean:
|
||||||
|
return "bool"
|
||||||
|
case openapi3.TypeInteger:
|
||||||
|
return "int"
|
||||||
|
case openapi3.TypeObject:
|
||||||
|
return "any"
|
||||||
|
case openapi3.TypeString:
|
||||||
|
return "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *File) parseSchema(schema *openapi3.SchemaRef) {
|
||||||
|
if schema.Ref != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for propertyName, property := range schema.Value.Properties {
|
||||||
|
field := FileField{
|
||||||
|
Name: utils.TextToCamelCase(propertyName),
|
||||||
|
Type: openapiTypeToGolangType(property.Value.Type),
|
||||||
|
Tag: NewTag(propertyName, "json"),
|
||||||
|
Title: property.Value.Title,
|
||||||
|
Description: property.Value.Description,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(property.Value.Enum) > 0 {
|
||||||
|
field.Type = utils.TextToCamelCase(propertyName)
|
||||||
|
|
||||||
|
r.Enums = append(r.Enums, FileEnum{
|
||||||
|
Name: field.Type,
|
||||||
|
Items: property.Value.Enum,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if property.Value.Type == openapi3.TypeObject {
|
||||||
|
field.Type = utils.TextToCamelCase(propertyName)
|
||||||
|
if property.Ref != "" {
|
||||||
|
field.Type = utils.TextGetLastElement(property.Ref, "/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if property.Value.Type == openapi3.TypeArray {
|
||||||
|
field.Type = fmt.Sprintf("[]%v", openapiTypeToGolangType(property.Value.Items.Value.Type))
|
||||||
|
|
||||||
|
if property.Value.Items.Value.Type == openapi3.TypeObject {
|
||||||
|
field.Type = "[]any"
|
||||||
|
}
|
||||||
|
|
||||||
|
if property.Value.Items.Ref != "" {
|
||||||
|
field.Type = "[]" + utils.TextGetLastElement(property.Value.Items.Ref, "/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.AddFields(field)
|
||||||
|
}
|
||||||
|
}
|
196
blueprint/modules/openapi/model/model_file_test.go
Normal file
196
blueprint/modules/openapi/model/model_file_test.go
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testFile = "../openapi_test.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ModelFileTestSuite struct {
|
||||||
|
doc *openapi3.T
|
||||||
|
models []*File
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ModelFileTestSuite) SetupSuite() {
|
||||||
|
loader := openapi3.NewLoader()
|
||||||
|
|
||||||
|
doc, err := loader.LoadFromFile(testFile)
|
||||||
|
r.NoErrorf(err, "cannot load openapi file: %v")
|
||||||
|
|
||||||
|
err = doc.Validate(loader.Context)
|
||||||
|
r.NoErrorf(err, "cannot validate openapi: %s")
|
||||||
|
|
||||||
|
r.doc = doc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ModelFileTestSuite) TearDownSuite() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModelTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(ModelFileTestSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ModelFileTestSuite) TestFillFromSchema() {
|
||||||
|
type args struct {
|
||||||
|
modelName string
|
||||||
|
schema *openapi3.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
modelColor := &File{
|
||||||
|
Name: "Color",
|
||||||
|
Fields: []FileField{
|
||||||
|
{
|
||||||
|
Name: "R",
|
||||||
|
Type: "int",
|
||||||
|
Tag: NewTag("r", "json"),
|
||||||
|
Title: "Red",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "G",
|
||||||
|
Type: "int",
|
||||||
|
Tag: NewTag("g", "json"),
|
||||||
|
Title: "Green",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "int",
|
||||||
|
Tag: NewTag("b", "json"),
|
||||||
|
Title: "Blue",
|
||||||
|
Name: "B",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "A",
|
||||||
|
Type: "int",
|
||||||
|
Tag: NewTag("a", "json"),
|
||||||
|
Title: "Alpha",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
modelProduct := &File{
|
||||||
|
Name: "Product",
|
||||||
|
Fields: []FileField{
|
||||||
|
{
|
||||||
|
Name: "ID",
|
||||||
|
Type: "string",
|
||||||
|
Tag: NewTag("_id", "json"),
|
||||||
|
Title: "ID",
|
||||||
|
Description: "Auto generated ID",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Name",
|
||||||
|
Type: "string",
|
||||||
|
Tag: NewTag("name", "json"),
|
||||||
|
Title: "Product name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Color",
|
||||||
|
Type: "Color",
|
||||||
|
Tag: NewTag("color", "json"),
|
||||||
|
Title: "", // Title for refs cannot parsed
|
||||||
|
Description: "", // Description for refs cannot parsed
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Status",
|
||||||
|
Type: "Status",
|
||||||
|
Tag: NewTag("status", "json"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Tags",
|
||||||
|
Type: "[]string",
|
||||||
|
Tag: NewTag("tags", "json"),
|
||||||
|
Title: "Tags",
|
||||||
|
Description: "Array of tags",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Enums: []FileEnum{
|
||||||
|
{
|
||||||
|
Name: "Status",
|
||||||
|
Items: []any{"in_stock", "in reserve", "out-of-stock"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
modelCategory := &File{
|
||||||
|
Name: "Category",
|
||||||
|
Fields: []FileField{
|
||||||
|
{
|
||||||
|
Name: "ID",
|
||||||
|
Type: "string",
|
||||||
|
Tag: NewTag("_id", "json"),
|
||||||
|
Title: "ID",
|
||||||
|
Description: "Auto generated ID",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Name",
|
||||||
|
Type: "string",
|
||||||
|
Tag: NewTag("name", "json"),
|
||||||
|
Title: "Category name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Products",
|
||||||
|
Type: "[]Product",
|
||||||
|
Tag: NewTag("products", "json"),
|
||||||
|
Title: "Products",
|
||||||
|
Description: "Array of included products",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
got *File
|
||||||
|
args args
|
||||||
|
want *File
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Color",
|
||||||
|
got: NewFile("Color", "models"),
|
||||||
|
args: args{
|
||||||
|
modelName: "Color",
|
||||||
|
schema: r.doc.Components.Schemas["Color"].Value,
|
||||||
|
},
|
||||||
|
want: modelColor,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Product",
|
||||||
|
got: NewFile("Product", "models"),
|
||||||
|
args: args{
|
||||||
|
modelName: "Product",
|
||||||
|
schema: r.doc.Components.Schemas["Product"].Value,
|
||||||
|
},
|
||||||
|
want: modelProduct,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Category",
|
||||||
|
got: NewFile("Category", "models"),
|
||||||
|
args: args{
|
||||||
|
modelName: "Category",
|
||||||
|
schema: r.doc.Components.Schemas["Category"].Value,
|
||||||
|
},
|
||||||
|
want: modelCategory,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
r.Run(tt.name, func() {
|
||||||
|
tt.got.SetName(tt.args.modelName)
|
||||||
|
tt.got.FillFromSchema(tt.args.schema)
|
||||||
|
r.Equal(tt.want.Name, tt.got.Name)
|
||||||
|
r.ElementsMatch(tt.want.Fields, tt.got.Fields)
|
||||||
|
|
||||||
|
r.models = append(r.models, tt.got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ModelFileTestSuite) TestSave() {
|
||||||
|
for _, model := range r.models {
|
||||||
|
path := "./model_test"
|
||||||
|
err := model.Save(path)
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
}
|
211
blueprint/modules/openapi/openapi.go
Normal file
211
blueprint/modules/openapi/openapi.go
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
package openapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint/modules/openapi/layers"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/types/dbtypes"
|
||||||
|
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint/modules/openapi/db"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint/modules/openapi/kroki"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint/modules/openapi/model"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OpenAPIModule struct {
|
||||||
|
settings Settings
|
||||||
|
db dbtypes.DB
|
||||||
|
doc *openapi3.T
|
||||||
|
kroki *kroki.Kroki
|
||||||
|
layers *layers.Layers
|
||||||
|
}
|
||||||
|
|
||||||
|
type Settings struct {
|
||||||
|
// FullPath to the OpenAPI file
|
||||||
|
FullPath string `yaml:"full_path"`
|
||||||
|
// Path to the folder for save generated models
|
||||||
|
ModelSavePath string `yaml:"model_save_path"`
|
||||||
|
// Path to the folder for save generated kroki puml file
|
||||||
|
KrokiSavePath string `yaml:"kroki_save_path"`
|
||||||
|
// Database type
|
||||||
|
DB dbtypes.DBType `yaml:"db"`
|
||||||
|
// Path to the folder for save generated controllers
|
||||||
|
ControllerSavePath string `yaml:"controller_save_path"`
|
||||||
|
ServiceSavePath string `yaml:"service_save_path"`
|
||||||
|
RepositorySavePath string `yaml:"repository_save_path"`
|
||||||
|
ServerSavePath string `yaml:"server_save_path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
allowedFilenames = []string{"Openapi.yaml", "openapi.yaml", "Openapi.yml", "openapi.yml",
|
||||||
|
"Openapi.json", "openapi.json"}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewOpenAPIModule() *OpenAPIModule {
|
||||||
|
return &OpenAPIModule{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OpenAPIModule) Name() string {
|
||||||
|
return "openapi"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OpenAPIModule) SetSettings(settings any) error {
|
||||||
|
out, err := yaml.Marshal(settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = yaml.Unmarshal(out, &r.settings); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.settings.DB != "" {
|
||||||
|
if !r.settings.DB.Valid() {
|
||||||
|
return fmt.Errorf("invalid db type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r.settings.DB {
|
||||||
|
case dbtypes.DBTypeMongo:
|
||||||
|
r.db = db.NewMongo(r.settings.DB)
|
||||||
|
case dbtypes.DBTypeSQL:
|
||||||
|
r.db = db.NewSQL(r.settings.DB)
|
||||||
|
default:
|
||||||
|
r.db = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.settings.KrokiSavePath == "" {
|
||||||
|
r.settings.KrokiSavePath = "./"
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.settings.ModelSavePath == "" {
|
||||||
|
r.settings.ModelSavePath = "./models"
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.settings.FullPath == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fileStat, err := os.Stat(r.settings.FullPath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("bad path for openapi file")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileStat.Size() == 0 {
|
||||||
|
return fmt.Errorf("openapi file is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OpenAPIModule) Execute() (*layers.AllData, error) {
|
||||||
|
fullPath := r.settings.FullPath
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var fileStat fs.FileInfo
|
||||||
|
if fullPath == "" {
|
||||||
|
for _, filename := range allowedFilenames {
|
||||||
|
fullPath = filename
|
||||||
|
|
||||||
|
fileStat, err = os.Stat(fullPath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("openapi file not found")
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("cannot read openapi file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileStat.Size() == 0 {
|
||||||
|
return nil, fmt.Errorf("openapi file is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath, err = filepath.Abs(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get absolute path: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = r.setDocument(fullPath); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
modelModule := model.NewModel(r.settings.ModelSavePath)
|
||||||
|
err = modelModule.FillFromOpenapi(r.doc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
layerDeps := layers.Deps{
|
||||||
|
SaveModelPath: r.settings.ModelSavePath,
|
||||||
|
PackageName: filepath.Base(r.settings.ModelSavePath),
|
||||||
|
SavePathController: r.settings.ControllerSavePath,
|
||||||
|
SavePathService: r.settings.ServiceSavePath,
|
||||||
|
SavePathRepository: r.settings.RepositorySavePath,
|
||||||
|
SavePathServer: r.settings.ServerSavePath,
|
||||||
|
DB: r.db,
|
||||||
|
}
|
||||||
|
layersModule := layers.NewLayers(layerDeps)
|
||||||
|
layersModule.FillFromOpenapi(r.doc)
|
||||||
|
layerData := layersModule.GetData()
|
||||||
|
|
||||||
|
if r.db != nil {
|
||||||
|
modelModule.AddTags(r.db.Tag())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = modelModule.SaveFiles(); err != nil {
|
||||||
|
return nil, fmt.Errorf("saveGoFiles: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
krokiModule := kroki.NewKroki()
|
||||||
|
krokiModule.FillFromOpenapi(r.doc)
|
||||||
|
if err = krokiModule.SavePUMLFile(r.settings.KrokiSavePath); err != nil {
|
||||||
|
return nil, fmt.Errorf("saveUmlFile: %v", err)
|
||||||
|
}
|
||||||
|
r.kroki = krokiModule
|
||||||
|
|
||||||
|
return &layerData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OpenAPIModule) KrokiPUML() string {
|
||||||
|
result, err := r.kroki.Generate()
|
||||||
|
if err != nil {
|
||||||
|
return utils.TemplateError(fmt.Sprintf("openapi.KrokiUML got %v", err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OpenAPIModule) setDocument(fullPath string) error {
|
||||||
|
log.Info().Str("file", fullPath).Msg("Reading openapi file")
|
||||||
|
loader := openapi3.NewLoader()
|
||||||
|
|
||||||
|
doc, err := loader.LoadFromFile(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loadFromFile: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = doc.Validate(loader.Context); err != nil {
|
||||||
|
return fmt.Errorf("validate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.doc = doc
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
218
blueprint/modules/openapi/openapi_test.yaml
Normal file
218
blueprint/modules/openapi/openapi_test.yaml
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: Example test
|
||||||
|
version: ver.1.0
|
||||||
|
servers:
|
||||||
|
- url: http://localhost:8225
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- name: Product
|
||||||
|
description: Запросы к продукту
|
||||||
|
- name: Category
|
||||||
|
description: Запросы к категориям
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/product:
|
||||||
|
post:
|
||||||
|
operationId: createProduct
|
||||||
|
tags:
|
||||||
|
- Product
|
||||||
|
summary: Создать продукт
|
||||||
|
description: Создать продукт
|
||||||
|
requestBody:
|
||||||
|
description: Создаваемый продукт
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Product"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Product"
|
||||||
|
|
||||||
|
/category:
|
||||||
|
post:
|
||||||
|
operationId: createCategory
|
||||||
|
tags:
|
||||||
|
- Category
|
||||||
|
summary: Создать категорию
|
||||||
|
description: Создать категорию
|
||||||
|
requestBody:
|
||||||
|
description: Создаваемая категория
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Category"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Category"
|
||||||
|
|
||||||
|
get:
|
||||||
|
operationId: GetListCategories
|
||||||
|
tags:
|
||||||
|
- Category
|
||||||
|
summary: Получить список категорий
|
||||||
|
description: Получить список всех категорий
|
||||||
|
requestBody:
|
||||||
|
description: Создаваемая категория
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Color"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Color"
|
||||||
|
500:
|
||||||
|
description: PLOHO
|
||||||
|
|
||||||
|
/category/{categoryId}:
|
||||||
|
get:
|
||||||
|
operationId: getCategory
|
||||||
|
tags:
|
||||||
|
- Category
|
||||||
|
summary: Получить информацию о категории
|
||||||
|
description: Получить информацию о категории по идентификатору
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: categoryId
|
||||||
|
description: Идентификатор категории
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Category"
|
||||||
|
404:
|
||||||
|
description: Category not found
|
||||||
|
500:
|
||||||
|
description: Internal Server Error
|
||||||
|
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Product:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
_id:
|
||||||
|
title: ID
|
||||||
|
description: Auto generated ID
|
||||||
|
type: string
|
||||||
|
example: 65046106121361691e6861de
|
||||||
|
readOnly: true
|
||||||
|
name:
|
||||||
|
title: Product name
|
||||||
|
type: string
|
||||||
|
example: Black Knight
|
||||||
|
minLength: 3
|
||||||
|
maxLength: 50
|
||||||
|
color:
|
||||||
|
title: Product color
|
||||||
|
description: Main color for product in rgba
|
||||||
|
$ref: "#/components/schemas/Color"
|
||||||
|
status:
|
||||||
|
title: Product status
|
||||||
|
type: string
|
||||||
|
format: enum
|
||||||
|
enum:
|
||||||
|
- in_stock
|
||||||
|
- in reserve
|
||||||
|
- out-of-stock
|
||||||
|
tags:
|
||||||
|
title: Tags
|
||||||
|
description: Array of tags
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
example: in_black
|
||||||
|
maxItems: 10
|
||||||
|
uniqueItems: true
|
||||||
|
Color:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
r:
|
||||||
|
title: Red
|
||||||
|
type: integer
|
||||||
|
example: 0
|
||||||
|
minimum: 0
|
||||||
|
maximum: 255
|
||||||
|
g:
|
||||||
|
title: Green
|
||||||
|
type: integer
|
||||||
|
example: 0
|
||||||
|
minimum: 0
|
||||||
|
maximum: 255
|
||||||
|
b:
|
||||||
|
title: Blue
|
||||||
|
type: integer
|
||||||
|
example: 0
|
||||||
|
minimum: 0
|
||||||
|
maximum: 255
|
||||||
|
a:
|
||||||
|
title: Alpha
|
||||||
|
type: integer
|
||||||
|
example: 0
|
||||||
|
minimum: 0
|
||||||
|
maximum: 255
|
||||||
|
Category:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
_id:
|
||||||
|
title: ID
|
||||||
|
description: Auto generated ID
|
||||||
|
type: string
|
||||||
|
example: 65046106121361691e6861de
|
||||||
|
readOnly: true
|
||||||
|
name:
|
||||||
|
title: Category name
|
||||||
|
type: string
|
||||||
|
example: InBlack
|
||||||
|
minLength: 3
|
||||||
|
maxLength: 30
|
||||||
|
products:
|
||||||
|
title: Products
|
||||||
|
description: Array of included products
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Product"
|
||||||
|
minItems: 1
|
||||||
|
CategoryReq:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
title: ID
|
||||||
|
description: Auto generated ID
|
||||||
|
type: string
|
||||||
|
example: 65046106121361691e6861de
|
||||||
|
readOnly: true
|
||||||
|
name:
|
||||||
|
title: Category name
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: uint64
|
||||||
|
example: 1
|
||||||
|
products:
|
||||||
|
title: Products
|
||||||
|
description: Array of included products
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Product"
|
@ -5,8 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint/modules/openapi/layers"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
@ -26,6 +26,7 @@ type TemplateSettings struct {
|
|||||||
TemplatePath string `yaml:"path"`
|
TemplatePath string `yaml:"path"`
|
||||||
Vars map[string]any `yaml:"vars,omitempty"`
|
Vars map[string]any `yaml:"vars,omitempty"`
|
||||||
Modules map[string]BlueprintModule `yaml:"-"`
|
Modules map[string]BlueprintModule `yaml:"-"`
|
||||||
|
LayersData *layers.AllData
|
||||||
}
|
}
|
||||||
|
|
||||||
type Template struct {
|
type Template struct {
|
||||||
@ -67,12 +68,16 @@ func (r *Template) Execute() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.execCommands()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Template) templateFile(templatePath string) error {
|
func (r *Template) templateFile(templatePath string) error {
|
||||||
// Create output path - replace template path to exec path
|
// Create output path - replace template path to exec path
|
||||||
sp := strings.Split(templatePath, "template")
|
sp := strings.Split(templatePath, "template")
|
||||||
|
if len(sp) < 2 {
|
||||||
|
log.Info().Str("file", templatePath).Msg("file not in template folder")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
outputFile := r.settings.ExecPath + sp[1]
|
outputFile := r.settings.ExecPath + sp[1]
|
||||||
|
|
||||||
@ -121,26 +126,6 @@ func (r *Template) templateFile(templatePath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Template) execCommands() error {
|
|
||||||
cmd := exec.Command("go", "mod", "init")
|
|
||||||
|
|
||||||
log.Info().Msg("execCommands: go mod init")
|
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("execCommands: go mod init. %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd = exec.Command("go", "mod", "tidy")
|
|
||||||
|
|
||||||
log.Info().Msg("execCommands: go mod tidy")
|
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("execCommands: go mod tidy. %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Template) walkFunc(path string, d fs.DirEntry, err error) error {
|
func (r *Template) walkFunc(path string, d fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("walkFunc: %v", err)
|
return fmt.Errorf("walkFunc: %v", err)
|
||||||
|
@ -7,7 +7,9 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint"
|
"penahub.gitlab.yandexcloud.net/pena-services/blueprint/blueprint"
|
||||||
)
|
)
|
||||||
@ -34,6 +36,7 @@ func NewApp() *App {
|
|||||||
|
|
||||||
// Execute - Любой вызов приложения.
|
// Execute - Любой вызов приложения.
|
||||||
func (r *App) Execute() {
|
func (r *App) Execute() {
|
||||||
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.DateTime})
|
||||||
versionFlag := flag.Bool("v", false, "Show current version")
|
versionFlag := flag.Bool("v", false, "Show current version")
|
||||||
projectNameFlag := flag.String("n", "", "Set project name")
|
projectNameFlag := flag.String("n", "", "Set project name")
|
||||||
gitFlag := flag.String("g", "", "Get blueprint from git")
|
gitFlag := flag.String("g", "", "Get blueprint from git")
|
||||||
|
@ -12,3 +12,11 @@ Modules:
|
|||||||
- name: APP_NAME
|
- name: APP_NAME
|
||||||
type: string
|
type: string
|
||||||
default: "{{.ProjectName}}"
|
default: "{{.ProjectName}}"
|
||||||
|
openapi:
|
||||||
|
model_save_path: ./internal/models
|
||||||
|
controller_save_path: ./internal/controllers
|
||||||
|
service_save_path: ./internal/service
|
||||||
|
repository_save_path: ./internal/repository
|
||||||
|
server_save_path: ./internal/server/http
|
||||||
|
db: mongodb
|
||||||
|
# db: postgres
|
||||||
|
5
examples/default/template/README.md.tmpl
Normal file
5
examples/default/template/README.md.tmpl
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Example
|
||||||
|
|
||||||
|
```plantuml
|
||||||
|
{{.Modules.openapi.KrokiPUML}}
|
||||||
|
```
|
@ -7,14 +7,14 @@ import (
|
|||||||
|
|
||||||
"{{.Vars.ProjectName}}/internal/app"
|
"{{.Vars.ProjectName}}/internal/app"
|
||||||
|
|
||||||
{{.Modules.logger.Import}}
|
"{{.Modules.logger.Import}}"
|
||||||
{{.Modules.logger.ImportCore}}
|
"{{.Modules.logger.ImportCore}}"
|
||||||
|
|
||||||
{{.Modules.env.Import}}
|
"{{.Modules.env.Import}}"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
{{.Modules.logger.Init}}
|
{{.Modules.logger.Declaration "logger"}}
|
||||||
|
|
||||||
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
@ -3,17 +3,65 @@ package app
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
{{.Modules.logger.Import}}
|
"{{.Modules.logger.Import}}"
|
||||||
)
|
)
|
||||||
|
|
||||||
{{.Modules.env.Struct}}
|
{{.Modules.env.Struct}}
|
||||||
|
|
||||||
func Run(ctx context.Context, config Config, logger {{.Modules.logger.Type}}) error {
|
func Run(ctx context.Context, config Config, logger {{.Modules.logger.Type}}) error {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
logger.Error("Recovered from a panic", zap.Any("error", r))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
{{.Modules.logger.Message "info" "App started" "config" "config"}}
|
{{.Modules.logger.Message "info" "App started" "config" "config"}}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Инициализация репозиториев
|
||||||
|
{{range $key, $value := .LayersData.Repositories}}
|
||||||
|
{{$key}}Repository := {{$value.PackageName}}.New{{$value.Name}}Repository()
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
// Инициализация сервисов
|
||||||
|
{{range $key, $value := .LayersData.Services}}
|
||||||
|
{{$key}}Service := {{$value.PackageName}}.New{{$value.Name}}Service({{$key}}Repository)
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
// Инициализация контроллеров
|
||||||
|
{{range $key, $value := .LayersData.Controllers}}
|
||||||
|
{{$key}}Controller := {{$value.PackageName}}.New{{$value.Name}}Controller({{$key}}Service)
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
// Создание сервера
|
||||||
|
server := {{.LayersData.ServerData}}.NewServer({{.LayersData.ServerData}}.ServerConfig{
|
||||||
|
Controllers: []{{.LayersData.ServerData}}.Controller{
|
||||||
|
{{range $key, $value := .LayersData.Controllers}}
|
||||||
|
{{$key}}Controller,
|
||||||
|
{{end}}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := server.Start("Host + : + Port")
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Server startup error", zap.Error(err))
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Вывод маршрутов
|
||||||
|
server.ListRoutes()
|
||||||
|
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
|
|
||||||
logger.Info("App shutting down gracefully")
|
logger.Info("App shutting down gracefully")
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
// Остановка сервера
|
||||||
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
17
go.mod
17
go.mod
@ -3,13 +3,24 @@ module penahub.gitlab.yandexcloud.net/pena-services/blueprint
|
|||||||
go 1.21.0
|
go 1.21.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/rs/zerolog v1.31.0
|
github.com/getkin/kin-openapi v0.122.0 // обязательно этой версии должен быть опенапи если новее то сломается все...
|
||||||
|
github.com/rs/zerolog v1.33.0
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
|
golang.org/x/text v0.14.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||||
|
github.com/go-openapi/swag v0.23.0 // indirect
|
||||||
|
github.com/invopop/yaml v0.3.1 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
golang.org/x/sys v0.15.0 // indirect
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
golang.org/x/sys v0.17.0 // indirect
|
||||||
)
|
)
|
||||||
|
45
go.sum
45
go.sum
@ -1,25 +1,60 @@
|
|||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/getkin/kin-openapi v0.122.0 h1:WB9Jbl0Hp/T79/JF9xlSW5Kl9uYdk/AWD0yAd9HOM10=
|
||||||
|
github.com/getkin/kin-openapi v0.122.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw=
|
||||||
|
github.com/getkin/kin-openapi v0.128.0 h1:jqq3D9vC9pPq1dGcOCv7yOp1DaEe7c/T1vzcLbITSp4=
|
||||||
|
github.com/getkin/kin-openapi v0.128.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM=
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||||
|
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||||
|
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||||
|
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||||
|
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
|
||||||
|
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||||
|
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||||
|
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||||
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||||
|
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||||
|
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||||
|
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
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/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
22
types/dbtypes/dbtypes.go
Normal file
22
types/dbtypes/dbtypes.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package dbtypes
|
||||||
|
|
||||||
|
type DBType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
DBTypeSQL DBType = "postgres"
|
||||||
|
DBTypeMongo DBType = "mongodb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r DBType) Valid() bool {
|
||||||
|
return r == DBTypeMongo || r == DBTypeSQL
|
||||||
|
}
|
||||||
|
|
||||||
|
const DirSqlcPath = "internal/sqlc/db_query"
|
||||||
|
const PathToQueriesFile = "internal/sqlc/db_query/queries.sql"
|
||||||
|
|
||||||
|
type DB interface {
|
||||||
|
Tag() string
|
||||||
|
GenerateSettings() error
|
||||||
|
SetTemplate(methodName string) string
|
||||||
|
DBType() DBType
|
||||||
|
}
|
57
types/dbtypes/template.go
Normal file
57
types/dbtypes/template.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package dbtypes
|
||||||
|
|
||||||
|
const SqlcYamlTemplate = `version: "1"
|
||||||
|
packages:
|
||||||
|
- name: "sqlcgen"
|
||||||
|
path: ".internal/sqlc/sqlcgen"
|
||||||
|
queries: ".internal/sqlc/db_query/queries.sql"
|
||||||
|
schema:
|
||||||
|
- "todo"
|
||||||
|
engine: "postgresql"
|
||||||
|
emit_json_tags: true
|
||||||
|
emit_db_tags: true
|
||||||
|
emit_prepared_queries: false
|
||||||
|
emit_interface: false
|
||||||
|
emit_exact_table_names: false
|
||||||
|
emit_empty_slices: false
|
||||||
|
sql_package: "database/sql"
|
||||||
|
`
|
||||||
|
|
||||||
|
const PaginationMongoTemplate = `
|
||||||
|
filter := bson.M{}
|
||||||
|
|
||||||
|
total, err := r.mdb.CountDocuments(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor, err := r.mdb.Find(ctx, filter, options.Find().SetSkip(int64(request.Page * request.Limit)).SetLimit(int64(request.Limit)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close(ctx)
|
||||||
|
|
||||||
|
var records []bson.M
|
||||||
|
if err := cursor.All(ctx, &records); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const PaginationSqlRepoTemplate = `
|
||||||
|
rows, err := r.queries.%s(ctx, sqlcgen.%sParams{
|
||||||
|
Page: request.Page,
|
||||||
|
Limit: request.Size,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
// $1-PAGE, $2-LIMIT
|
||||||
|
const PaginationSqlTemplate = `-- name: %s :many
|
||||||
|
SELECT *
|
||||||
|
FROM {{ .TableName }} AS a
|
||||||
|
ORDER BY a.created_at DESC
|
||||||
|
LIMIT $2 OFFSET ($1-1)*$2;
|
||||||
|
`
|
7
utils/template.go
Normal file
7
utils/template.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func TemplateError(err string) string {
|
||||||
|
return fmt.Sprintf("//FIXME TEMPLATE ERROR: %v", err)
|
||||||
|
}
|
98
utils/text.go
Normal file
98
utils/text.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TextToLower(text string) string {
|
||||||
|
caser := cases.Lower(language.English)
|
||||||
|
return caser.String(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TextToUpper(text string) string {
|
||||||
|
caser := cases.Upper(language.English)
|
||||||
|
return caser.String(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TextToTitle(text string) string {
|
||||||
|
caser := cases.Title(language.English)
|
||||||
|
return caser.String(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TextToCamelCase(text string) string {
|
||||||
|
var result string
|
||||||
|
|
||||||
|
text = strings.ReplaceAll(text, " ", "_")
|
||||||
|
text = strings.ReplaceAll(text, "-", "_")
|
||||||
|
|
||||||
|
for _, item := range strings.Split(text, "_") {
|
||||||
|
if item == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result += textToUpperWitnInitialisms(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func TextGetLastElement(text, sep string) string {
|
||||||
|
splitRef := strings.Split(text, sep)
|
||||||
|
return splitRef[len(splitRef)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func textToUpperWitnInitialisms(text string) string {
|
||||||
|
initialismKey, ok := slices.BinarySearch(commonInitialisms, TextToUpper(text))
|
||||||
|
if ok {
|
||||||
|
text = commonInitialisms[initialismKey]
|
||||||
|
} else {
|
||||||
|
text = TextToTitle(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
var commonInitialisms = []string{
|
||||||
|
"ACL",
|
||||||
|
"API",
|
||||||
|
"ASCII",
|
||||||
|
"CPU",
|
||||||
|
"CSS",
|
||||||
|
"DNS",
|
||||||
|
"EOF",
|
||||||
|
"GUID",
|
||||||
|
"HTML",
|
||||||
|
"HTTP",
|
||||||
|
"HTTPS",
|
||||||
|
"ID",
|
||||||
|
"IP",
|
||||||
|
"JSON",
|
||||||
|
"LHS",
|
||||||
|
"QPS",
|
||||||
|
"RAM",
|
||||||
|
"RHS",
|
||||||
|
"RPC",
|
||||||
|
"SLA",
|
||||||
|
"SMTP",
|
||||||
|
"SQL",
|
||||||
|
"SSH",
|
||||||
|
"TCP",
|
||||||
|
"TLS",
|
||||||
|
"TTL",
|
||||||
|
"UDP",
|
||||||
|
"UI",
|
||||||
|
"UID",
|
||||||
|
"UUID",
|
||||||
|
"URI",
|
||||||
|
"URL",
|
||||||
|
"UTF8",
|
||||||
|
"VM",
|
||||||
|
"XML",
|
||||||
|
"XMPP",
|
||||||
|
"XSRF",
|
||||||
|
"XSS",
|
||||||
|
}
|
68
utils/text_test.go
Normal file
68
utils/text_test.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTextToLower(t *testing.T) {
|
||||||
|
text := "Some Text"
|
||||||
|
want := "some text"
|
||||||
|
|
||||||
|
assert.Equal(t, want, TextToLower(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTextToUpper(t *testing.T) {
|
||||||
|
text := "some text"
|
||||||
|
want := "SOME TEXT"
|
||||||
|
|
||||||
|
assert.Equal(t, want, TextToUpper(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTextToTitle(t *testing.T) {
|
||||||
|
text := "some text"
|
||||||
|
want := "Some Text"
|
||||||
|
|
||||||
|
assert.Equal(t, want, TextToTitle(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTextFromSnakeCaseToCamelCase(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
text string
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "_id",
|
||||||
|
args: args{
|
||||||
|
text: "_id",
|
||||||
|
},
|
||||||
|
want: "ID",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "product_id",
|
||||||
|
args: args{
|
||||||
|
text: "product_id",
|
||||||
|
},
|
||||||
|
want: "ProductID",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "product",
|
||||||
|
args: args{
|
||||||
|
text: "product",
|
||||||
|
},
|
||||||
|
want: "Product",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.want, TextToCamelCase(tt.args.text))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
5
utils/vars.go
Normal file
5
utils/vars.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
func IsPrimaryKey(s string) bool {
|
||||||
|
return s == "id" || s == "_id" || s == "uuid" || s == "guid"
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user