Files
tesseract/internal/template/http_handlers.go
2024-11-12 00:31:10 +00:00

445 lines
9.2 KiB
Go

package template
import (
"bufio"
"database/sql"
"encoding/json"
"errors"
"fmt"
"github.com/labstack/echo/v4"
"io"
"net/http"
"strings"
"tesseract/internal/service"
)
type createTemplateRequestBody struct {
Description string `json:"description"`
Content string `json:"content"`
Documentation string `json:"documentation"`
}
type updateTemplateRequestBody struct {
Description *string `json:"description"`
Files []templateFile `json:"files"`
ImageTag *string `json:"imageTag"`
BuildArgs map[string]*string `json:"buildArgs"`
}
type templateBuildLogEvent struct {
Type string `json:"type"`
LogContent string `json:"logContent"`
}
type templateBuildFinishedEvent struct {
Type string `json:"type"`
Template *template `json:"template"`
}
func fetchAllTemplates(c echo.Context) error {
db := service.Database(c)
var templates []template
err := db.NewSelect().Model(&templates).Scan(c.Request().Context())
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return c.JSON(http.StatusOK, make([]template, 0))
}
return err
}
if len(templates) == 0 {
return c.JSON(http.StatusOK, make([]template, 0))
}
return c.JSON(http.StatusOK, templates)
}
func fetchTemplate(c echo.Context) error {
db := service.Database(c)
name := c.Param("templateName")
if strings.TrimSpace(name) == "" {
return echo.NewHTTPError(http.StatusNotFound)
}
var tmpl template
err := db.NewSelect().Model(&tmpl).
Relation("Files").
Where("name = ?", name).
Scan(c.Request().Context())
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return echo.NewHTTPError(http.StatusNotFound)
}
return err
}
if len(tmpl.Files) > 0 {
tmpl.FileMap = make(map[string]*templateFile)
}
for _, f := range tmpl.Files {
tmpl.FileMap[f.FilePath] = f
}
return c.JSON(http.StatusOK, tmpl)
}
func createOrUpdateTemplate(c echo.Context) error {
db := service.Database(c)
name := c.Param("templateName")
if strings.TrimSpace(name) == "" {
return echo.NewHTTPError(http.StatusNotFound)
}
exists, err := db.NewSelect().
Table("templates").
Where("name = ?", name).
Exists(c.Request().Context())
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return createTemplate(c)
}
return err
}
if !exists {
return createTemplate(c)
}
return updateTemplate(c)
}
func createTemplate(c echo.Context) error {
db := service.Database(c)
name := c.Param("templateName")
var body createTemplateRequestBody
err := json.NewDecoder(c.Request().Body).Decode(&body)
if err != nil {
return err
}
tx, err := db.BeginTx(c.Request().Context(), nil)
if err != nil {
return err
}
createdTemplate, err := createDockerTemplate(c.Request().Context(), tx, createTemplateOptions{
name: name,
description: body.Description,
})
if err != nil {
_ = tx.Rollback()
return err
}
if err = tx.Commit(); err != nil {
_ = tx.Rollback()
return err
}
return c.JSON(http.StatusOK, createdTemplate)
}
func updateTemplate(c echo.Context) error {
db := service.Database(c)
name := c.Param("templateName")
var body updateTemplateRequestBody
err := json.NewDecoder(c.Request().Body).Decode(&body)
if err != nil {
return err
}
if body.BuildArgs != nil && body.ImageTag == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Image tag must be specified if buildArgs is passed")
}
tx, err := db.BeginTx(c.Request().Context(), nil)
if err != nil {
return err
}
var tmpl template
err = tx.NewSelect().Model(&tmpl).
Where("name = ?", name).
Scan(c.Request().Context())
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return echo.NewHTTPError(http.StatusNotFound)
}
return err
}
if body.Description != nil {
tmpl.Description = *body.Description
_, err = tx.NewUpdate().Model(&tmpl).
Column("description").
WherePK().
Exec(c.Request().Context())
if err != nil {
_ = tx.Rollback()
return err
}
if err = tx.Commit(); err != nil {
_ = tx.Rollback()
return err
}
}
if body.ImageTag != nil {
err = tx.NewSelect().Model(&tmpl.Files).
Where("template_id = ?", tmpl.ID).
Scan(c.Request().Context())
if err != nil {
_ = tx.Rollback()
return err
}
docker := service.DockerClient(c)
log, err := buildDockerTemplate(c.Request().Context(), docker, &tmpl, templateBuildOptions{
imageTag: *body.ImageTag,
buildArgs: body.BuildArgs,
})
if err != nil {
return err
}
w := c.Response()
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
scanner := bufio.NewScanner(log)
var imageID string
for scanner.Scan() {
t := scanner.Text()
fmt.Println("DOCKER LOG: ", t)
var msg map[string]any
err = json.Unmarshal([]byte(t), &msg)
if err != nil {
return err
}
if stream, ok := msg["stream"].(string); ok {
if _, err = w.Write([]byte(stream)); err != nil {
return err
}
w.Flush()
} else if errmsg, ok := msg["error"].(string); ok {
if _, err = w.Write([]byte(errmsg + "\n")); err != nil {
return err
}
w.Flush()
} else if status, ok := msg["status"].(string); ok {
var text string
if progress, ok := msg["progress"].(string); ok {
text = fmt.Sprintf("%v: %v\n", status, progress)
} else {
text = status + "\n"
}
if _, err = w.Write([]byte(text)); err != nil {
return err
}
w.Flush()
} else if aux, ok := msg["aux"].(map[string]any); ok {
if id, ok := aux["ID"].(string); ok {
imageID = id
}
}
}
if imageID != "" {
img := TemplateImage{
TemplateID: tmpl.ID,
ImageTag: *body.ImageTag,
ImageID: imageID,
}
_, err = tx.NewInsert().Model(&img).Exec(c.Request().Context())
if err != nil {
_ = tx.Rollback()
return err
}
}
if err = tx.Commit(); err != nil {
_ = tx.Rollback()
return err
}
return nil
}
if err = tx.Commit(); err != nil {
_ = tx.Rollback()
return err
}
return c.JSON(http.StatusOK, &tmpl)
}
func deleteTemplate(c echo.Context) error {
templateName := c.Param("templateName")
if templateName == "" {
return echo.NewHTTPError(http.StatusNotFound)
}
db := service.Database(c)
tx, err := db.BeginTx(c.Request().Context(), nil)
if err != nil {
return err
}
res, err := tx.NewDelete().Table("templates").
Where("name = ?", templateName).
Exec(c.Request().Context())
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return echo.NewHTTPError(http.StatusNotFound)
}
_ = tx.Rollback()
return err
}
count, err := res.RowsAffected()
if err != nil {
_ = tx.Rollback()
return err
}
if count != 1 {
_ = tx.Rollback()
return echo.NewHTTPError(http.StatusInternalServerError)
}
if err = tx.Commit(); err != nil {
_ = tx.Rollback()
return err
}
return c.NoContent(http.StatusOK)
}
func fetchTemplateFile(c echo.Context) error {
templateName := c.Param("templateName")
if templateName == "" {
return echo.NewHTTPError(http.StatusNotFound)
}
filePath := c.Param("filePath")
if filePath == "" {
return echo.NewHTTPError(http.StatusNotFound)
}
db := service.Database(c)
var tmpl template
err := db.NewSelect().Model(&tmpl).
Column("id").
Where("name = ?", templateName).
Scan(c.Request().Context())
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return echo.NewHTTPError(http.StatusNotFound)
}
return err
}
var file templateFile
err = db.NewSelect().Model(&file).
Where("template_id = ?", tmpl.ID).
Where("file_path = ?", filePath).
Scan(c.Request().Context())
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return echo.NewHTTPError(http.StatusNotFound)
}
return err
}
return c.Blob(http.StatusOK, "application/octet-stream", file.Content)
}
func updateTemplateFile(c echo.Context) error {
templateName := c.Param("templateName")
if templateName == "" {
return echo.NewHTTPError(http.StatusNotFound)
}
filePath := c.Param("filePath")
if filePath == "" {
return echo.NewHTTPError(http.StatusNotFound)
}
db := service.Database(c)
tx, err := db.BeginTx(c.Request().Context(), nil)
if err != nil {
return err
}
var tmpl template
err = tx.NewSelect().Model(&tmpl).
Column("id").
Where("name = ?", templateName).
Scan(c.Request().Context())
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return echo.NewHTTPError(http.StatusNotFound)
}
return err
}
newContent, err := io.ReadAll(c.Request().Body)
if err != nil {
return err
}
_, err = tx.NewUpdate().Table("template_files").
Set("content = ?", newContent).
Where("template_id = ?", tmpl.ID).
Where("file_path = ?", filePath).
Exec(c.Request().Context())
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return echo.NewHTTPError(http.StatusNotFound)
}
return err
}
if err = tx.Commit(); err != nil {
_ = tx.Rollback()
return err
}
return c.NoContent(http.StatusOK)
}
func fetchAllTemplateImages(c echo.Context) error {
db := service.Database(c)
var images []TemplateImage
err := db.NewSelect().Model(&images).Scan(c.Request().Context())
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return c.JSON(http.StatusOK, make([]TemplateImage, 0))
}
return err
}
if len(images) == 0 {
return c.JSON(http.StatusOK, make([]TemplateImage, 0))
}
return c.JSON(http.StatusOK, images)
}