// package mod_http -- http-модуль для архитектуры
package mod_http
import (
"bytes"
_ "embed"
"encoding/json"
"fmt"
"log"
"time"
svg "github.com/ajstarks/svgo"
"github.com/gofiber/fiber/v2"
"gitp78su.ipnodns.ru/svi/goarch/lev0/alias"
"gitp78su.ipnodns.ru/svi/goarch/lev0/cons"
"gitp78su.ipnodns.ru/svi/goarch/lev0/types"
"gitp78su.ipnodns.ru/svi/goarch/pkg/elems/canvas"
"gitp78su.ipnodns.ru/svi/goarch/pkg/views/view_actor"
"gitp78su.ipnodns.ru/svi/goarch/pkg/views/view_link"
"gitp78su.ipnodns.ru/svi/goarch/pkg/views/view_use_case"
"gitp78su.ipnodns.ru/svi/goarch/pkg/views/view_use_group"
"gitp78su.ipnodns.ru/svi/kern/v3"
"gitp78su.ipnodns.ru/svi/kern/v3/krn/ktypes"
)
//go:embed index.html
var indexHtml []byte
// ModHttp -- http-модуль для архитектуры
type ModHttp struct {
// fibApp *fiber.App
kCtx ktypes.IKernelCtx
diaMode string // Режим диаграммы
oldBinArch []byte // Кеш старой архитектуры
}
// NewModHttp -- возвращает новый сервис
func NewModHttp() *ModHttp {
sf := &ModHttp{
kCtx: kern.GetKernelCtx(),
diaMode: cons.ModeUseCase,
}
sHttp := kern.GetKernelServerHttp()
fibApp := sHttp.Fiber()
fibApp.Static("/static", "./static", fiber.Static{
Compress: true,
Browse: true,
CacheDuration: time.Second * 10,
})
fibApp.Get("/", sf.indexGet)
fibApp.Get("/use_case", sf.setModeUseCase)
fibApp.Post("/arch", sf.archPost)
return sf
}
// FormArch -- извлекает данные из текстового поля для обновления
type FormArch struct {
StrData string `form:"text"`
}
// Возвращает новый холст
func (sf *ModHttp) canvas(wr *bytes.Buffer, lstElem []map[string]interface{}) (*svg.SVG, error) {
var (
elem map[string]interface{}
isFind, isOk bool
_section interface{}
)
for _, elem = range lstElem {
_section, isOk = elem["type"]
if !isOk {
continue
}
section, isOk := _section.(string)
if !isOk {
return nil, fmt.Errorf("ServHttp.canvas(): in mapElem, type(%#+v) not string
%#+v", _section, elem)
}
if section == cons.SectionConfig {
isFind = true
break
}
}
if !isFind {
return nil, fmt.Errorf("ServHttp.canvas(): in mapElem, config not found")
}
confCanvas := canvas.NewCanvas(sf.diaMode, elem)
sizeX, sizeY := confCanvas.Size()
canvas := svg.New(wr)
canvas.Start(int(sizeX), int(sizeY))
confCanvas.Draw(canvas)
return canvas, nil
}
// По запросы парсит, что получилось -- обновляет архитектуру
func (sf *ModHttp) archPost(ctx *fiber.Ctx) error {
form := &FormArch{}
if err := ctx.BodyParser(form); err != nil {
return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): in BodyParser, err=
%v", err))
}
binData := []byte(form.StrData)
if len(binData) == len(sf.oldBinArch) {
return ctx.Send(sf.oldBinArch)
}
mapElem := []map[string]interface{}{}
err := json.Unmarshal(binData, &mapElem)
if err != nil {
return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): in Decode JSON, err=
%v", err))
}
wr := bytes.NewBuffer(nil)
canvas, err := sf.canvas(wr, mapElem)
if err != nil {
return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): in create canvas, err=
%v", err))
}
switch sf.diaMode {
case cons.ModeUseCase: // режим вариантов использования
if err := sf.useCase(canvas, mapElem); err != nil {
return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): in useCase, err=
%v", err))
}
default: // неизвестный режим
return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): unknown mode=%q", sf.diaMode))
}
canvas.End()
binData = wr.Bytes()
return ctx.Send(binData)
}
// Формирует варианты использования
func (sf *ModHttp) useCase(canvas *svg.SVG, lstElem []map[string]interface{}) error {
mapDraw := make(map[alias.Id]types.IElemDrawer)
for _, elem := range lstElem {
_id, isOk := elem["id"]
if !isOk {
return fmt.Errorf("ServHttp.useCase(): field 'id' not found
%#+v", elem)
}
id0, isOk := _id.(string)
if !isOk {
return fmt.Errorf("ServHttp.useCase(): field 'id'(%T, %+v) not alias.Id", _id, _id)
}
id := alias.Id(id0)
if id == "" {
return fmt.Errorf("ServHttp.useCase(): id=%q, elem is empty
%#+v", id, elem)
}
_, isOk = mapDraw[id]
if isOk {
return fmt.Errorf("ServHttp.useCase(): id=%q already exists", id)
}
binElem, _ := json.MarshalIndent(elem, "", " ")
_type, isOk := elem["type"]
if !isOk {
return fmt.Errorf("ServHttp.useCase(): id=%q, field 'type' not found
%+v", id, string(binElem))
}
strType, isOk := _type.(string)
if !isOk {
return fmt.Errorf("ServHttp.useCase(): id=%q, field 'type'(%+v) not string
%+v", id, _type, string(binElem))
}
var (
drawer types.IElemDrawer
err error
)
switch strType {
case cons.TypeUseActor: // Нарисовать актора
drawer, err = view_actor.NewViewActor(elem)
if err != nil {
return fmt.Errorf("ServHttp.useCase(): id=%q, in create Actor, err=
%v", id, err)
}
case cons.TypeUseCase: // Нарисовать вариант использования
drawer, err = view_use_case.NewViewUseCase(elem)
if err != nil {
return fmt.Errorf("ServHttp.useCase(): id=%q, err=%w
%+v", id, err, string(binElem))
}
case cons.TypeUseLink: // Нарисовать связь
drawer, err = view_link.NewViewLink(elem)
if err != nil {
return fmt.Errorf("ServHttp.useCase(): id=%q, in create Link, err=%w
%+v", id, err, string(binElem))
}
case cons.TypeUseGroup: // Нарисовать группу вариантов использования
drawer, err = view_use_group.NewViewUseGroup(elem)
if err != nil {
return fmt.Errorf("ServHttp.useCase(): id=%q, in create UseGroup, err=%w
%+v", id, err, string(binElem))
}
}
if drawer != nil {
mapDraw[id] = drawer
log.Printf("ServHttp.useCase(): id=%q, elem=\n%v\n\n", id, elem)
}
}
for _, drawer := range mapDraw {
switch drawer.(type) {
case types.ILinker:
}
drawer.Draw(canvas, mapDraw)
}
return nil
}
// Возвращает картинку архитектуры
func (sf *ModHttp) setModeUseCase(ctx *fiber.Ctx) error {
sf.diaMode = cons.ModeUseCase
return ctx.SendString("[mode='" + sf.diaMode + "']")
}
// Возвращает главную страницу сервиса
func (sf *ModHttp) indexGet(ctx *fiber.Ctx) error {
ctx.Response().Header.Set("Content-Type", "text/html")
return ctx.Send(indexHtml)
}