package dag
import (
"context"
"fmt"
"net/http"
"os"
"strings"
"github.com/gofiber/fiber/v2"
"github.com/oarkflow/form"
"github.com/oarkflow/json/jsonparser"
"github.com/oarkflow/mq"
"github.com/oarkflow/mq/consts"
)
// RenderNotFound handles 404 errors.
func renderFiberNotFound(c *fiber.Ctx) error {
html := `
`
c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
return c.Status(fiber.StatusNotFound).SendString(html)
}
// Render handles process and request routes.
func (tm *DAG) RenderFiber(c *fiber.Ctx) error {
ctx, data, err := form.ParseBodyAsJSON(c.UserContext(), c.Get("Content-Type"), c.Body(), c.Queries())
if err != nil {
return c.Status(fiber.StatusNotFound).SendString(err.Error())
}
accept := c.Get("Accept")
userCtx := form.UserContext(ctx)
ctx = context.WithValue(ctx, "method", c.Method())
if c.Method() == fiber.MethodGet && userCtx.Get("task_id") != "" {
manager, ok := tm.taskManager.Get(userCtx.Get("task_id"))
if !ok || manager == nil {
if strings.Contains(accept, fiber.MIMETextHTML) || accept == "" {
return renderFiberNotFound(c)
}
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": "task not found"})
}
}
result := tm.Process(ctx, data)
if result.Error != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": result.Error.Error()})
}
if result.Ctx == nil {
result.Ctx = ctx
}
contentType := consts.TypeJson
if ct, ok := result.Ctx.Value(consts.ContentType).(string); ok {
contentType = ct
}
switch contentType {
case consts.TypeHtml:
htmlContent, err := jsonparser.GetString(result.Payload, "html_content")
if err != nil {
return err
}
if strings.Contains(htmlContent, "{{current_uri}}") {
htmlContent = strings.ReplaceAll(htmlContent, "{{current_uri}}", c.Path())
}
c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
return c.SendString(htmlContent)
default:
if c.Method() != fiber.MethodPost {
return c.Status(fiber.StatusMethodNotAllowed).JSON(fiber.Map{"message": "not allowed"})
}
c.Set(fiber.HeaderContentType, fiber.MIMEApplicationJSON)
return c.JSON(result.Payload)
}
}
// TaskStatusHandler retrieves task statuses.
func (tm *DAG) fiberTaskStatusHandler(c *fiber.Ctx) error {
taskID := c.Query("taskID")
if taskID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"message": "taskID is missing"})
}
manager, ok := tm.taskManager.Get(taskID)
if !ok {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"message": "Invalid TaskID"})
}
result := make(map[string]TaskState)
manager.taskStates.ForEach(func(key string, value *TaskState) bool {
key = strings.Split(key, Delimiter)[0]
nodeID := strings.Split(value.NodeID, Delimiter)[0]
rs := jsonparser.Delete(value.Result.Payload, "html_content")
status := value.Status
if status == mq.Processing {
status = mq.Completed
}
state := TaskState{
NodeID: nodeID,
Status: status,
UpdatedAt: value.UpdatedAt,
Result: mq.Result{
Payload: rs,
Error: value.Result.Error,
Status: status,
},
}
result[key] = state
return true
})
return c.Type(fiber.MIMEApplicationJSON).JSON(result)
}
func (tm *DAG) BaseURI() string {
return tm.httpPrefix
}
// processSVGContent processes SVG to ensure proper scaling using viewBox
func (tm *DAG) processSVGContent(svgContent string) string {
// Extract width and height from SVG
var widthVal, heightVal float64
var hasWidth, hasHeight bool
// Find width attribute
if strings.Contains(svgContent, `width="`) {
start := strings.Index(svgContent, `width="`) + 7
end := strings.Index(svgContent[start:], `"`)
if end > 0 {
width := svgContent[start : start+end]
// Remove pt, px, or other units and convert to float
cleanWidth := strings.TrimSuffix(strings.TrimSuffix(strings.TrimSuffix(width, "pt"), "px"), "in")
if _, err := fmt.Sscanf(cleanWidth, "%f", &widthVal); err == nil {
hasWidth = true
// Convert pt to pixels (1pt = 1.33px approximately)
if strings.HasSuffix(width, "pt") {
widthVal *= 1.33
}
}
}
}
// Find height attribute
if strings.Contains(svgContent, `height="`) {
start := strings.Index(svgContent, `height="`) + 8
end := strings.Index(svgContent[start:], `"`)
if end > 0 {
height := svgContent[start : start+end]
// Remove pt, px, or other units and convert to float
cleanHeight := strings.TrimSuffix(strings.TrimSuffix(strings.TrimSuffix(height, "pt"), "px"), "in")
if _, err := fmt.Sscanf(cleanHeight, "%f", &heightVal); err == nil {
hasHeight = true
// Convert pt to pixels (1pt = 1.33px approximately)
if strings.HasSuffix(height, "pt") {
heightVal *= 1.33
}
}
}
}
// If we don't have both dimensions, use fallback values
if !hasWidth || !hasHeight || widthVal <= 0 || heightVal <= 0 {
// Try to extract from viewBox first
if strings.Contains(svgContent, `viewBox="`) {
start := strings.Index(svgContent, `viewBox="`) + 9
end := strings.Index(svgContent[start:], `"`)
if end > 0 {
viewBox := svgContent[start : start+end]
parts := strings.Fields(viewBox)
if len(parts) >= 4 {
if w, err := fmt.Sscanf(parts[2], "%f", &widthVal); err == nil && w == 1 && widthVal > 0 {
hasWidth = true
}
if h, err := fmt.Sscanf(parts[3], "%f", &heightVal); err == nil && h == 1 && heightVal > 0 {
hasHeight = true
}
}
}
}
// Final fallback
if !hasWidth || widthVal <= 0 {
widthVal = 800
}
if !hasHeight || heightVal <= 0 {
heightVal = 600
}
}
// Create viewBox
viewBox := fmt.Sprintf("0 0 %.0f %.0f", widthVal, heightVal)
// Process the SVG content
processedSVG := svgContent
// Add or update viewBox
if strings.Contains(processedSVG, `viewBox="`) {
// Replace existing viewBox
start := strings.Index(processedSVG, `viewBox="`)
end := strings.Index(processedSVG[start+9:], `"`) + start + 9
processedSVG = processedSVG[:start] + fmt.Sprintf(`viewBox="%s"`, viewBox) + processedSVG[end+1:]
} else {
// Add viewBox to opening svg tag
svgStart := strings.Index(processedSVG, "