// package mod_http -- http-модуль для архитектуры
package mod_http
import (
"bytes"
_ "embed"
"encoding/json"
"fmt"
"time"
svg "github.com/ajstarks/svgo"
"github.com/gofiber/fiber/v2"
"gopkg.in/yaml.v3"
"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/lev1/coord"
"gitp78su.ipnodns.ru/svi/goarch/lev1/size"
"gitp78su.ipnodns.ru/svi/goarch/lev1/view_use_case"
"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_group"
"gitp78su.ipnodns.ru/svi/kern/v3"
"gitp78su.ipnodns.ru/svi/kern/v3/kc/log_buf"
"gitp78su.ipnodns.ru/svi/kern/v3/krn/ktypes"
)
//go:embed index.html
var indexHtml []byte
// CanvasSize -- размер хоста
type CanvasSize struct {
SizeX_ int `form:"w"`
SizeY_ int `form:"h"`
}
func (sf *CanvasSize) String() string {
return fmt.Sprintf("w:%v; h:%v", sf.SizeX_, sf.SizeY_)
}
// ModHttp -- http-модуль для архитектуры
type ModHttp struct {
// fibApp *fiber.App
kCtx ktypes.IKernelCtx
log ktypes.ILogBuf
diaMode string // Режим диаграммы
oldBinArch []byte // Кеш старой архитектуры
canvSize CanvasSize // Размер холста
size types.ISize // Размер холста в его координатах
pos types.ICoord // Координаты холста
}
// NewModHttp -- возвращает новый сервис
func NewModHttp() *ModHttp {
optTerm := log_buf.OptIsTerm(true)
optPref := log_buf.OptPrefix("ModHttp")
log := log_buf.NewLogBuf(optTerm, optPref)
sf := &ModHttp{
kCtx: kern.GetKernelCtx(),
log: log,
diaMode: cons.ModeUseCase,
size: size.NewSize(800, 600),
pos: coord.NewCoord(0, 0),
}
sHttp := kern.GetKernelServerHttp()
fibApp := sHttp.Fiber()
fibApp.Static("/static", "./static", fiber.Static{
Compress: true,
Browse: true,
CacheDuration: time.Second * 10,
})
fibApp.Get("/", sf.getIndex)
fibApp.Get("/api/use_case", sf.postSetModeUseCase)
fibApp.Post("/api/arch", sf.postArch)
fibApp.Post("/api/canvas_size", sf.postSetCanvasSize)
return sf
}
// FormArch -- извлекает данные из текстового поля для обновления
type FormArch struct {
StrData string `form:"text"`
}
// Устанавливает размер холста
func (sf *ModHttp) postSetCanvasSize(ctx *fiber.Ctx) error {
err := ctx.BodyParser(&sf.canvSize)
if err != nil {
sf.log.Err("postSetCanvasSize(): on body parser, err=\n\t%v", err)
}
if sf.canvSize.SizeX_ > 0 {
sf.size.W_ = alias.SizeX(sf.canvSize.SizeX_ - 10)
}
if sf.canvSize.SizeY_ > 0 {
sf.size.H_ = alias.SizeY(sf.canvSize.SizeY_ - 10)
}
return ctx.SendString(sf.canvSize.String())
}
// Возвращает новый холст
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, sf.size, sf.pos)
sizeX, sizeY := confCanvas.Size()
canvas := svg.New(wr)
canvas.Start(int(sizeX), int(sizeY))
confCanvas.Draw(canvas)
return canvas, nil
}
// По запросу парсит, что получилось -- обновляет архитектуру
func (sf *ModHttp) postArch(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 := yaml.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.IDrawer)
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.IDrawer
)
switch strType {
case cons.TypeUseActor: // Нарисовать актора
view_actor.NewViewActor(actor, elem)
case cons.TypeUseCase: // Нарисовать вариант использования
drawer = view_use_case.NewViewUseCase(useCase, elem)
case cons.TypeUseLink: // Нарисовать связь
drawer = view_link.NewViewLink(useLink)
case cons.TypeUseGroup: // Нарисовать группу вариантов использования
drawer = view_use_group.NewViewUseGroup(useGroup, elem)
}
if drawer != nil {
mapDraw[id] = drawer
sf.log.Debug("useCase(): id=%q, elem=\n%v\n", id, elem)
}
}
for _, drawer := range mapDraw {
switch drawer.(type) {
case types.IElemLinker:
}
drawer.Draw(canvas, mapDraw)
}
return nil
}
// Возвращает картинку архитектуры
func (sf *ModHttp) postSetModeUseCase(ctx *fiber.Ctx) error {
sf.diaMode = cons.ModeUseCase
return ctx.SendString("[mode='" + sf.diaMode + "']")
}
// Возвращает главную страницу сервиса
func (sf *ModHttp) getIndex(ctx *fiber.Ctx) error {
ctx.Response().Header.Set("Content-Type", "text/html")
return ctx.Send(indexHtml)
}