Merge pull request 'db-visual' (#1) from db-visual into main

Reviewed-on: #1
This commit is contained in:
Александр Федорюк 2023-11-10 10:11:17 +03:00
commit e3fcf741ab
26 changed files with 586 additions and 49 deletions

14
.vscode/launch.json vendored
View File

@ -51,6 +51,20 @@
"--outdir-frontend",
"../zmap/frontend"
]
},
{
"name": "zmap docs",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go",
"args": [
"--metafile",
"/home/ymnuk/projects/zmap/zmap.yml",
"--outdir-doc",
"../zmap/docs",
"--format-markdown"
]
}
]
}

7
go.mod
View File

@ -5,8 +5,13 @@ go 1.21
require (
github.com/alexflint/go-arg v1.4.3
github.com/creasty/defaults v1.7.0
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386
github.com/satori/go.uuid v1.2.0
github.com/yuzutech/kroki-go v0.8.1
gopkg.in/yaml.v3 v3.0.1
)
require github.com/alexflint/go-scalar v1.2.0 // indirect
require (
github.com/alexflint/go-scalar v1.2.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
)

6
go.sum
View File

@ -8,12 +8,18 @@ github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdB
github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 h1:EcQR3gusLHN46TAD+G+EbaaqJArt5vHhNpXAa12PQf4=
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuzutech/kroki-go v0.8.1 h1:p6doXpgWUXgLrVWt6MA1/ODH/oMBsnNGC3f7wGUewbM=
github.com/yuzutech/kroki-go v0.8.1/go.mod h1:JyGBdT3Od/PHEDf95GirXwhf1PM31k+tevrXGVjqNpg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -16,6 +16,11 @@ var AppConfig struct {
Filename string `arg:"-m,--metafile" help:"Файл с метоописанием в формате YAML или JSON"` // Файл с метоописанием в формате YAML или JSON
OutdirBackend string `arg:"-b,--outdir-backend" help:"Директория для сохранения кода сервера"` // Директория для сохранения кода сервера
OutdirFrontend string `arg:"-f,--outdir-frontend" help:"Директория для сохранения кода клиента"` // Директория для сохранения кода клиента
OutdirDoc string `arg:"-d,--outdir-doc" help:"Директория для сохранения сгенерированной документации по проекта"`
IsMarkdown bool `arg:"--format-markdown" help:"Выходная документация в формате Markdown"`
IsHtml bool `arg:"--format-html" help:"Выходная документация в формате HTML"`
KrokiAddr string `arg:"--kroki-addr" default:"https://kroki.io" help:"Адрес сервера Kroki."`
IsSingleDoc bool `arg:"--single-doc" help:"Генерировать документацию в одном файле без разделения"`
}
var Project *structs.Project

216
lib/documentation.go Normal file
View File

@ -0,0 +1,216 @@
package lib
import (
"fmt"
"log"
"strings"
"text/template"
"git.ymnuktech.ru/ymnuk/yt-gen-app/lib/templ"
"git.ymnuktech.ru/ymnuk/yt-gen-app/structs"
uuid "github.com/satori/go.uuid"
"github.com/yuzutech/kroki-go"
)
func Documentation() {
if !AppConfig.IsHtml {
AppConfig.IsMarkdown = true
}
// Генерация общей схемы БД
fullDocDBGen()
// Генерация схем для каждой таблицы
for _, table := range Project.DB.Tables {
docDBGen(&table)
}
var err error
var tmpl *template.Template
var out []byte
if AppConfig.IsSingleDoc {
if tmpl, err = templ.ReadTmplFile("tmpl/docs/single-index.tmpl"); err != nil {
panic(err)
}
if out, err = templ.ExecuteTmplFile(tmpl, Project); err != nil {
panic(err)
}
if AppConfig.IsMarkdown {
if err = templ.WriteFile(AppConfig.OutdirDoc+"/index.md", out); err != nil {
panic(err)
}
}
if AppConfig.IsHtml {
var buff []byte
if buff, err = templ.Content.ReadFile("tmpl/docs/styles.tmpl"); err != nil {
panic(err)
}
//{{ includeTemplPart "tmpl/docs/styles.tmpl" nil }}
out = append(buff, out...)
if err = templ.WriteFile(AppConfig.OutdirDoc+"/index.html", mdToHTML(out)); err != nil {
panic(err)
}
}
} else {
if tmpl, err = templ.ReadTmplFile("tmpl/docs/index.tmpl"); err != nil {
panic(err)
}
if out, err = templ.ExecuteTmplFile(tmpl, Project); err != nil {
panic(err)
}
if AppConfig.IsMarkdown {
if err = templ.WriteFile(AppConfig.OutdirDoc+"/index.md", out); err != nil {
panic(err)
}
}
if AppConfig.IsHtml {
var buff []byte
if buff, err = templ.Content.ReadFile("tmpl/docs/styles.tmpl"); err != nil {
panic(err)
}
//{{ includeTemplPart "tmpl/docs/styles.tmpl" nil }}
out = append(buff, out...)
out = []byte(strings.ReplaceAll(string(out), ".md", ".html"))
out = mdToHTML(out)
if err = templ.WriteFile(AppConfig.OutdirDoc+"/index.html", out); err != nil {
panic(err)
}
}
// Генерация каждого отдельного файла для каждой таблицы
if tmpl, err = templ.ReadTmplFile("tmpl/docs/db/entity-table-href.tmpl"); err != nil {
panic(err)
}
for _, table := range Project.DB.Tables {
// TODO
if out, err = templ.ExecuteTmplFile(tmpl, table); err != nil {
panic(err)
}
if AppConfig.IsMarkdown {
if err = templ.WriteFile(AppConfig.OutdirDoc+"/"+templ.FieldNameLowerPrepare(table.Name)+".md", out); err != nil {
panic(err)
}
}
if AppConfig.IsHtml {
var buff1 []byte
if buff1, err = templ.Content.ReadFile("tmpl/docs/styles.tmpl"); err != nil {
panic(err)
}
//{{ includeTemplPart "tmpl/docs/styles.tmpl" nil }}
out = append(buff1, out...)
out = []byte(strings.ReplaceAll(string(out), ".md", ".html"))
out = mdToHTML(out)
if err = templ.WriteFile(AppConfig.OutdirDoc+"/"+templ.FieldNameLowerPrepare(table.Name)+".html", out); err != nil {
panic(err)
}
}
}
}
// Генерация каждой таблицы по отдельности с прилегающими ближайшими таблицами
/*for i := range Project.DB.Tables {
docDBGen(Project.DB.Tables[i])
}*/
// TODO
}
// Генерирования списка таблиц
/*func listTablesDocGen() string {
// TODO
}
// Генерирование полей таблицы
func tableDocGen(table structs.Table) string {
// TODO
}*/
// Генерация общей схемы БД
func fullDocDBGen() {
// TODO
if tmpl, err := templ.ReadTmplFile("tmpl/docs/db/full-schema.tmpl"); err != nil {
panic(err)
} else {
if buff, err := templ.ExecuteTmplFile(tmpl, Project.DB); err != nil {
panic(err)
} else {
//fmt.Println(string(buff))
//templ.WriteFile(AppConfig.OutdirDoc+"/test.txt", buff)
client := NewKrokiClient()
if result, err := client.FromString(string(buff), "dbml", kroki.SVG); err != nil {
panic(err)
} else {
//fmt.Println(result)
templ.WriteFile(AppConfig.OutdirDoc+"/full-schemadb.svg", []byte(result))
}
}
}
}
// Генерация отдельной таблицы с прилегающими ближайшими таблицами
func docDBGen(table *structs.Table) {
restStruct := structs.RestStruct{
Table: table,
Children: GetChildrenTables(table),
Parents: GetParentsTables(table),
}
if tmpl, err := templ.ReadTmplFile("tmpl/docs/db/adjacents-tables.tmpl"); err != nil {
panic(err)
} else {
if buff, err := templ.ExecuteTmplFile(tmpl, restStruct); err != nil {
fmt.Println(string(buff))
panic(err)
} else {
//fmt.Println(string(buff))
//templ.WriteFile(AppConfig.OutdirDoc+"/test.txt", buff)
client := NewKrokiClient()
if result, err := client.FromString(string(buff), "dbml", kroki.SVG); err != nil {
fmt.Println(string(buff))
panic(err)
} else {
//fmt.Println(result)
templ.WriteFile(AppConfig.OutdirDoc+"/"+templ.FieldNameLowerPrepare(restStruct.Table.Name)+".svg", []byte(result))
}
}
}
// TODO
}
func GetParentsTables(table *structs.Table) (tables []structs.Table) {
for _, fk := range table.FKs {
for _, tbl := range Project.DB.Tables {
if fk.TableID == uuid.Nil {
// Это одна из трех таблиц: user, role, user_role
switch templ.FieldNameLowerPrepare(fk.TableName) {
case "user":
tables = append(tables, structs.Table{
ID: uuid.Nil,
Name: "user",
Pk: "uuid",
})
case "role":
tables = append(tables, structs.Table{
ID: uuid.Nil,
Name: "role",
Pk: "uuid",
})
default:
log.Fatalf("Not found standart table %s", templ.FieldNameLowerPrepare(fk.TableName))
}
break
}
if fk.TableID == tbl.ID {
tables = append(tables, tbl)
break
}
}
}
return
}
func GetChildrenTables(table *structs.Table) (tables []structs.Table) {
for _, tbl := range Project.DB.Tables {
for _, fk := range tbl.FKs {
if fk.TableID == table.ID {
tables = append(tables, tbl)
}
}
}
return
}

View File

@ -21,4 +21,12 @@ func Generate() {
}
Frontend()
}
if AppConfig.OutdirDoc != "" {
// Генерация документации
if err := os.MkdirAll(AppConfig.OutdirDoc, 0755); err != nil {
log.Fatal(err)
}
Documentation()
}
}

20
lib/kroki-client.go Normal file
View File

@ -0,0 +1,20 @@
package lib
import (
"time"
"github.com/yuzutech/kroki-go"
)
var krokiClient *kroki.Client
func NewKrokiClient() *kroki.Client {
if krokiClient == nil {
tmp := kroki.New(kroki.Configuration{
URL: AppConfig.KrokiAddr,
Timeout: time.Second * 60,
})
krokiClient = &tmp
}
return krokiClient
}

21
lib/md2html.go Normal file
View File

@ -0,0 +1,21 @@
package lib
import (
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
)
func mdToHTML(md []byte) []byte {
// create markdown parser with extensions
extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
p := parser.NewWithExtensions(extensions)
doc := p.Parse(md)
// create HTML renderer with extensions
htmlFlags := html.CommonFlags | html.HrefTargetBlank
opts := html.RendererOptions{Flags: htmlFlags}
renderer := html.NewRenderer(opts)
return markdown.Render(doc, renderer)
}

View File

@ -84,6 +84,9 @@ func prepareDB(project *structs.Project) *structs.Project {
TypeParentTable: project.DB.Tables[i].FKs[j].Type, //project.DB.Tables[i].Pk,
Description: fmt.Sprintf("Foreign key for \\\"%s\\\" table", project.DB.Tables[i].FKs[j].TableName),
}
if project.DB.Tables[i].FKs[j].Description != "" {
fkTmp.Description = project.DB.Tables[i].FKs[j].Description
}
if strings.ToLower(project.DB.Tables[i].FKs[j].TableName) == "user" {
fkTmp.TypeParentTable = "uuid"
}

View File

@ -21,14 +21,20 @@ import (
var Content embed.FS
var funcMap = template.FuncMap{
//"includeTemplPart": IncludeTemplPart,
"fieldName": FieldName,
"fieldChildName": FieldChildName,
"fieldNamePrepare": FieldNamePrepare,
"fieldNameLowerPrepare": FieldNameLowerPrepare,
"fieldJsonNameStr": FieldJsonNameStr,
"fieldType": FieldType,
"fieldTypeStr": FieldTypeStr,
"fieldStringToType": FieldStringToType,
//"fieldDbTableType": FieldDbTableType,
"fieldTypeDB": FieldTypeDB,
"fieldTypeDBStr": FieldTypeDBStr,
"fieldTypeParentTable": FieldTypeParentTable,
"hasFieldType": HasFieldType,
"fieldDescript": FieldDescript,
@ -71,6 +77,22 @@ var funcMap = template.FuncMap{
"fieldInTableByName": FieldInTableByName,
}
func IncludeTemplPart(templName string, data interface{}) string {
var err error
var tmpl *template.Template
if tmpl, err = ReadTmplFile(templName); err != nil {
panic(err)
}
var out []byte
if out, err = ExecuteTmplFile(tmpl, data); err != nil {
panic(err)
}
/*arr:=strings.Split(string(out))
var newArr []string
for*/
return string(out)
}
func IsMethod(arr map[string][]string, method string) bool {
if arr == nil {
return false
@ -153,15 +175,12 @@ func FieldTypeParentTable(field *structs.Field) string {
return FieldType(&structs.Field{Type: field.TypeParentTable})
}
func FieldType(field *structs.Field) string {
if field == nil {
log.Fatal("field is empty")
func FieldTypeStr(value string) string {
value = strings.ToLower(strings.Trim(value, " "))
if value == "" {
return value
}
field.Type = strings.ToLower(strings.Trim(field.Type, " "))
if field.Type == "" {
field.Type = "text"
}
switch field.Type {
switch value {
case "text":
return "*string"
case "string":
@ -186,11 +205,24 @@ func FieldType(field *structs.Field) string {
return "[]byte"
default:
fkTmp := &structs.Field{
Name: field.Type,
Name: value,
}
return FieldName(fkTmp)
//log.Fatalf("Unknow format %s", field.Type)
}
}
func FieldType(field *structs.Field) string {
if field == nil {
log.Fatal("field is empty")
}
field.Type = strings.ToLower(strings.Trim(field.Type, " "))
value := FieldTypeStr(field.Type)
if value == "" {
field.Type = "text"
value = "text"
}
return value
//return ""
}
@ -212,15 +244,14 @@ func HasFieldType(fields []structs.Field, typeName string) (has bool) {
return false
}
func FieldTypeDB(field *structs.Field) string {
field.Type = strings.ToLower(strings.Trim(field.Type, " "))
switch field.Type {
func FieldTypeDBStr(value string, length *int) string {
switch value {
case "text":
return "TEXT"
case "string":
tmp := "VARCHAR"
if field.Length != nil && *field.Length == 0 {
tmp += fmt.Sprintf("(%d)", *field.Length)
if length != nil && *length == 0 {
tmp += fmt.Sprintf("(%d)", *length)
} else {
tmp += "(255)"
}
@ -243,11 +274,16 @@ func FieldTypeDB(field *structs.Field) string {
case "bool":
return "int"
default:
log.Fatalf("Unknow format %s", field.Type)
log.Fatalf("Unknow format %s", value)
}
return ""
}
func FieldTypeDB(field *structs.Field) string {
field.Type = strings.ToLower(strings.Trim(field.Type, " "))
return FieldTypeDBStr(field.Type, field.Length)
}
// Генерирование описания таблиц в БД
func FieldDescript(field *structs.Field, fieldId bool) (str string) {
str = "`"
@ -317,7 +353,6 @@ func ConfigParamTag(field *structs.ParamConfig) string {
tag += fmt.Sprintf(" help:\"%s\"", field.Help)
}
tag += "`"
// TODO
return tag
}
@ -333,6 +368,46 @@ func WriteTmplFile(filename string, outname string) error {
return nil
}
// Чтение шаблона и его подготовка
func ReadTmplFile(filename string) (tmpl *template.Template, err error) {
var (
buff []byte
)
if buff, err = Content.ReadFile(filename); err != nil {
return
}
var (
tmp string
//tmpl *template.Template
)
if tmp, err = RandomHex(4); err != nil {
return
}
if _, ok := funcMap["includeTemplPart"]; !ok {
funcMap["includeTemplPart"] = IncludeTemplPart
}
tmpl, err = template.New(tmp).Funcs(funcMap).Parse(string(buff))
return
}
// Запись файла на диск
func WriteFile(outname string, data []byte) (err error) {
err = os.WriteFile(outname, data, 0755)
return
}
// Выполнить шаблон
func ExecuteTmplFile(tmpl *template.Template, data interface{}) (out []byte, err error) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
if err = tmpl.Execute(w, data); err != nil {
return
}
w.Flush()
out = b.Bytes()
return
}
// Использование шаблона
func PrepareTmplFile(filename string, data interface{}, outname string) (err error) {
dir := filepath.Dir(outname)
@ -341,36 +416,25 @@ func PrepareTmplFile(filename string, data interface{}, outname string) (err err
}
fmt.Printf("Generate: %s\n", outname)
var (
buff []byte
)
if buff, err = Content.ReadFile(filename); err != nil {
return err
}
var (
tmp string
tmpl *template.Template
)
if tmp, err = RandomHex(4); err != nil {
return err
}
if tmpl, err = template.New(tmp).Funcs(funcMap).Parse(string(buff)); err != nil {
/*var b bytes.Buffer
w := bufio.NewWriter(&b)*/
var tmpl *template.Template
if tmpl, err = ReadTmplFile(filename); err != nil {
return
}
/*if err = tmpl.Execute(w, data); err != nil {
return err
}
var b bytes.Buffer
w := bufio.NewWriter(&b)
if err = tmpl.Execute(w, data); err != nil {
return err
w.Flush()*/
var out []byte
if out, err = ExecuteTmplFile(tmpl, data); err != nil {
return
}
w.Flush()
if err = os.WriteFile(outname, b.Bytes(), 0755); err != nil {
return err
}
return
return WriteFile(outname, out)
}
// Подготовка файла, если его нет на диске
func PrepareTmplIsNotExists(filename string, data interface{}, outname string) (err error) {
if err = MkdirIsNotExists(filepath.Dir(outname)); err != nil {

View File

@ -1,20 +1,22 @@
package route
import (
"{{ .Name }}/route/api"
"embed"
"mime"
"net/http"
"path/filepath"
"strings"
"zmap/route/api"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
_ "{{ .Name }}/docs"
_ "zmap/docs"
echoSwagger "github.com/swaggo/echo-swagger"
)
//go:embed static
var content embed.FS
var Content embed.FS
func Init(e *echo.Echo) {
@ -22,9 +24,25 @@ func Init(e *echo.Echo) {
api.Init(e.Group("/api"))
var contentHandler = echo.WrapHandler(http.FileServer(http.FS(content)))
var contentRewrite = middleware.Rewrite(map[string]string{"/*": "/static/$1"})
//e.GET("/", main)
e.GET("/*", contentHandler, contentRewrite)
e.GET("*", contentHandler, contentRewrite)
e.GET("/*", defaultHandler)
e.GET("*", defaultHandler)
}
func defaultHandler(c echo.Context) error {
buff, err := Content.ReadFile(strings.ReplaceAll("static/"+c.Request().URL.Path, "//", "/"))
if err != nil {
buff, err = Content.ReadFile("static/index.html")
if err != nil {
return c.String(http.StatusNotFound, "Page not found")
}
return c.Blob(http.StatusOK, http.DetectContentType(buff), buff)
}
contentType := http.DetectContentType(buff)
tmpArr := strings.Split(contentType, ";")
if len(tmpArr) > 0 && strings.Trim(tmpArr[0], " ") == "text/plain" {
ext1 := filepath.Ext(c.Request().URL.Path)
contentType = mime.TypeByExtension(ext1)
}
return c.Blob(http.StatusOK, contentType, buff)
}

View File

@ -0,0 +1,31 @@
{{ includeTemplPart "tmpl/docs/db/entity.tmpl" .Table }}
{{ range $indexi, $table := .Children }}
{{ includeTemplPart "tmpl/docs/db/entity.tmpl" $table }}
{{ end }}
{{ range $indexi, $table := .Parents }}
{{ includeTemplPart "tmpl/docs/db/entity.tmpl" $table }}
{{ end }}
{{ range $indexi, $table := .Parents }}
{{ $varNameTable := fieldNamePrepare $.Table.Name }}
{{ range $indexj, $field := $.Table.FKs }}
{{ if eq $field.TableID $table.ID }}
{{ $fieldTypeParentTable := "" }}
{{ if eq $field.Name "Parent" }}{{ $fieldTypeParentTable = fieldNamePrepare $.Name }}{{ else }}{{ $newField := index $.Table.FkFields $indexj }}{{ $fieldTypeParentTable = fieldType $newField }}{{ end }}
Ref: {{ fieldNameLowerPrepare $varNameTable }}.{{ fieldNameLowerPrepare $field.Name }}_id > {{ fieldNameLowerPrepare $fieldTypeParentTable }}.id
{{ end }}
{{ end }}
{{ end }}
{{ range $indexi, $table := .Children }}
{{ range $indexj, $field := $table.FKs }}
{{ if eq $field.TableID $.Table.ID }}
{{ $varNameTable := fieldNamePrepare $table.Name }}
{{ $fieldTypeParentTable := "" }}
{{ if eq $field.Name "Parent" }}{{ $fieldTypeParentTable = fieldNamePrepare $.Name }}{{ else }}{{ $newField := index $table.FkFields $indexj }}{{ $fieldTypeParentTable = fieldType $newField }}{{ end }}
Ref: {{ fieldNameLowerPrepare $varNameTable }}.{{ fieldNameLowerPrepare $field.Name }}_id > {{ fieldNameLowerPrepare $fieldTypeParentTable }}.id
{{ end }}
{{ end }}
{{ end }}

View File

@ -0,0 +1,22 @@
# {{ fieldNameLowerPrepare $.Name }}
![Таблица {{ fieldNameLowerPrepare $.Name }}]({{ fieldNameLowerPrepare $.Name }}.svg "Таблица {{ fieldNameLowerPrepare $.Name }}")
## Поля
|Поле|Тип|Описание|
|:---|:--|:-------|
|id|{{ fieldTypeDBStr .Pk nil }}|Первичный ключ|
{{ range $index, $field := .Fields }}|{{ fieldNameLowerPrepare $field.Name }}|{{ fieldTypeDB $field }}|{{ $field.Description }}|
{{ end }}
## Внешние ключи
|Ключ|Тип|Талица|Описание|
|:---|:--|:-----|:-------|
{{ range $index, $field := .FkFields }}|{{ fieldNameLowerPrepare $field.Name }}_id|{{ fieldNameLowerPrepare $field.TypeParentTable }}|{{ $tableNameVar := "" }}{{ if eq $field.Name "Parent" }}{{ $tableNameVar = fieldNamePrepare $.Name }}{{ else }}{{ $tableNameVar = fieldType $field }}{{ end }}{{ fieldNameLowerPrepare $tableNameVar }}|{{ $field.Description }}|
{{ end }}
[На главную](index.md)

View File

@ -0,0 +1,16 @@
![Таблица {{ fieldNameLowerPrepare $.Name }}]({{ fieldNameLowerPrepare $.Name }}.svg "Таблица {{ fieldNameLowerPrepare $.Name }}")
### Поля
|Поле|Тип|Описание|
|:---|:--|:-------|
|id|{{ fieldTypeDBStr .Pk nil }}|Первичный ключ|
{{ range $index, $field := .Fields }}|{{ fieldNameLowerPrepare $field.Name }}|{{ fieldTypeDB $field }}|{{ $field.Description }}|
{{ end }}
### Внешние ключи
|Ключ|Тип|Талица|Описание|
|:---|:--|:-----|:-------|
{{ range $index, $field := .FkFields }}|{{ fieldNameLowerPrepare $field.Name }}_id|{{ fieldNameLowerPrepare $field.TypeParentTable }}|{{ $tableNameVar := "" }}{{ if eq $field.Name "Parent" }}{{ $tableNameVar = fieldNamePrepare $.Name }}{{ else }}{{ $tableNameVar = fieldType $field }}{{ end }}{{ fieldNameLowerPrepare $tableNameVar }}|{{ $field.Description }}|
{{ end }}

View File

@ -0,0 +1,10 @@
{{ $varNameField := fieldNamePrepare .Name }}
Table {{ fieldNameLowerPrepare $varNameField }} {
id {{ fieldTypeDBStr .Pk nil }} [primary key]
{{ range $index, $field := .Fields }}
{{ fieldNameLowerPrepare $field.Name }} {{ fieldTypeDB $field }}
{{ end }}
{{ range $index, $field := .FkFields }}
{{ fieldNameLowerPrepare $field.Name }}_id {{ fieldNameLowerPrepare $field.TypeParentTable }}
{{ end }}
}

View File

@ -0,0 +1,17 @@
{{ includeTemplPart "tmpl/docs/db/user.tmpl" nil }}
{{ includeTemplPart "tmpl/docs/db/role.tmpl" nil }}
{{ includeTemplPart "tmpl/docs/db/user-role.tmpl" nil }}
{{ range $index, $table := .Tables }}
{{ includeTemplPart "tmpl/docs/db/entity.tmpl" $table }}
{{ end }}
{{ range $indexi, $table := .Tables }}
{{ $varNameTable := fieldNamePrepare $table.Name }}
{{ range $indexj, $field := $table.FkFields }}
{{ $fieldTypeParentTable := "" }}
{{ if eq $field.Name "Parent" }}{{ $fieldTypeParentTable = fieldNamePrepare $.Name }}{{ else }}{{ $fieldTypeParentTable = fieldType $field }}{{ end }}
Ref: {{ fieldNameLowerPrepare $varNameTable }}.{{ fieldNameLowerPrepare $field.Name }}_id > {{ fieldNameLowerPrepare $fieldTypeParentTable }}.id
{{ end }}
{{ end }}
{{ includeTemplPart "tmpl/docs/db/user-role-relations.tmpl" nil }}

View File

@ -0,0 +1,5 @@
Table role {
id UUID [primary key]
name VARCHAR(20)
description VARCHAR(20)
}

View File

@ -0,0 +1,4 @@
|Название|Описание|
|:-------|:-------|
{{ range $index, $table := .Tables }}|{{ $varNameField := fieldNamePrepare $table.Name }}{{ fieldNameLowerPrepare $varNameField }}|{{ $table.Description }}|
{{end}}

View File

@ -0,0 +1,4 @@
|Название|Описание|
|:-------|:-------|
{{ range $index, $table := .Tables }}|{{ $varNameField := fieldNamePrepare $table.Name }}[{{ fieldNameLowerPrepare $varNameField }}]({{ fieldNameLowerPrepare $varNameField }}.md)|{{ $table.Description }}|
{{end}}

View File

@ -0,0 +1,2 @@
Ref: user_role.user_id > user.id
Ref: user_role.role_id > role.id

View File

@ -0,0 +1,5 @@
Table user_role {
id UUID
user_id UUID
role_id UUID
}

View File

@ -0,0 +1,11 @@
Table user {
id UUID [primary key]
login VARCHAR
surname VARCHAR(100)
name VARCHAR(100)
farname VARCHAR(100)
email VARCHAR(200)
password VARCGAR
ldap BOOLEAN
attempt INT
}

View File

@ -0,0 +1,6 @@
# База данных
{{ includeTemplPart "tmpl/docs/db/table-list.tmpl" .DB }}
![Полная структура БД](full-schemadb.svg "Полная структура БД")

View File

@ -0,0 +1,13 @@
# База данных
{{ includeTemplPart "tmpl/docs/db/table-list-single.tmpl" .DB }}
![Полная структура БД](full-schemadb.svg "Полная структура БД")
{{ range $index, $table := .DB.Tables }}
{{ $varNameField := fieldNamePrepare .Name }}
## Таблица {{ fieldNameLowerPrepare $varNameField }}
{{ includeTemplPart "tmpl/docs/db/entity-table-no-href.tmpl" $table }}
{{ end }}

View File

@ -0,0 +1,6 @@
<style>
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
</style>

View File

@ -6,4 +6,9 @@ type RestStruct struct {
Project *Project
Rest *Rest
PathParams []Field
// Поля для формирования документации к БД
Table *Table
Children []Table
Parents []Table
}