diff --git a/internal/template/http_handlers.go b/internal/template/http_handlers.go index d0c5c4e..9bdc3bf 100644 --- a/internal/template/http_handlers.go +++ b/internal/template/http_handlers.go @@ -56,36 +56,21 @@ func fetchTemplate(c echo.Context) error { return c.JSON(http.StatusOK, template) } -func createOrUpdateTemplate(c echo.Context) error { - mgr := templateManagerFrom(c) - exists, err := mgr.hasTemplate(c.Request().Context(), c.Param("templateName")) - if err != nil { - return err - } - if !exists { - return createTemplate(c) - } - - var body postTemplateRequestBody - err = json.NewDecoder(c.Request().Body).Decode(&body) - if err != nil { - return err - } - - if body.ImageTag != nil || body.BuildArgs != nil { - return buildTemplate(c, body) - } - - return updateTemplate(c, body) -} - func createTemplate(c echo.Context) error { - mgr := templateManagerFrom(c) name := c.Param("templateName") + mgr := templateManagerFrom(c) + + exists, err := mgr.hasTemplate(c.Request().Context(), name) + if err != nil { + return err + } + + if exists { + return echo.NewHTTPError(http.StatusConflict) + } var body createTemplateRequestBody - err := json.NewDecoder(c.Request().Body).Decode(&body) - if err != nil { + if err = json.NewDecoder(c.Request().Body).Decode(&body); err != nil { return err } @@ -101,6 +86,29 @@ func createTemplate(c echo.Context) error { return c.JSON(http.StatusOK, createdTemplate) } +func updateOrBuildTemplate(c echo.Context) error { + mgr := templateManagerFrom(c) + exists, err := mgr.hasTemplate(c.Request().Context(), c.Param("templateName")) + if err != nil { + return err + } + if !exists { + return echo.NewHTTPError(http.StatusNotFound) + } + + var body postTemplateRequestBody + err = json.NewDecoder(c.Request().Body).Decode(&body) + if err != nil { + return err + } + + if body.ImageTag != nil || body.BuildArgs != nil { + return buildTemplate(c, body) + } + + return updateTemplate(c, body) +} + func updateTemplate(c echo.Context, body postTemplateRequestBody) error { name := c.Param("templateName") mgr := templateManagerFrom(c) diff --git a/internal/template/routes.go b/internal/template/routes.go index 636dc49..c135e10 100644 --- a/internal/template/routes.go +++ b/internal/template/routes.go @@ -9,7 +9,8 @@ func DefineRoutes(g *echo.Group, services service.Services) { g.Use(newTemplateManagerMiddleware(services)) g.GET("/templates", fetchAllTemplates) g.GET("/templates/:templateName", fetchTemplate, validateTemplateName) - g.POST("/templates/:templateName", createOrUpdateTemplate, validateTemplateName) + g.PUT("/templates/:templateName", createTemplate, validateTemplateName) + g.POST("/templates/:templateName", updateOrBuildTemplate, validateTemplateName) g.DELETE("/templates/:templateName", deleteTemplate, validateTemplateName) g.GET("/templates/:templateName/:filePath", fetchTemplateFile, validateTemplateName, validateTemplateFilePath) g.POST("/templates/:templateName/:filePath", updateTemplateFile, validateTemplateName, validateTemplateFilePath) diff --git a/internal/template/template_manager.go b/internal/template/template_manager.go index b00252c..ebec825 100644 --- a/internal/template/template_manager.go +++ b/internal/template/template_manager.go @@ -41,6 +41,7 @@ type buildTemplateOptions struct { } var errTemplateNotFound = errors.New("template not found") +var errTemplateExists = errors.New("template already exists") var errBaseTemplateNotFound = errors.New("base template not found") var errTemplateFileNotFound = errors.New("template file not found") diff --git a/web/src/api.ts b/web/src/api.ts index 12ea5b9..c8d9ecb 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -1,6 +1,6 @@ import { promiseOrThrow } from "./lib/errors"; -interface ApiErrorResponse { +interface ApiErrorDetails { code: string; error: string; } @@ -8,12 +8,13 @@ interface ApiErrorResponse { const API_ERROR_BAD_TEMPLATE = "BAD_TEMPLATE"; const API_ERROR_WORKSPACE_EXISTS = "WORKSPACE_EXISTS"; -enum ApiError { - NotFound = "NOT_FOUND", - BadRequest = "BAD_REQUEST", - Internal = "INTERNAL", - Network = "NETWORK", -} +type ApiError = + | { type: "NOT_FOUND" } + | { type: "NETWORK" } + | { type: "BAD_REQUEST" } + | { type: "CONFLICT" } + | { type: "INTERNAL" } + | { type: "BAD_REQUEST"; details: ApiErrorDetails }; async function fetchApi( url: URL | RequestInfo, @@ -21,22 +22,27 @@ async function fetchApi( ): Promise { const res = await promiseOrThrow( fetch(`${import.meta.env.VITE_API_URL}/api${url}`, init), - () => ApiError.Network, + () => ({ type: "NETWORK" }), ); if (res.status !== 200) { switch (res.status) { case 400: - throw await res.json(); + throw { + type: "BAD_REQUEST", + details: await res.json(), + }; case 404: - throw ApiError.NotFound; + throw { type: "NOT_FOUND" }; + case 409: + throw { type: "CONFLICT" }; default: - throw ApiError.Internal; + throw { type: "INTERNAL" }; } } return res; } -function isApiErrorResponse(error: unknown): error is ApiErrorResponse { +function isApiErrorResponse(error: unknown): error is ApiErrorDetails { return ( error !== null && error !== undefined && @@ -49,8 +55,7 @@ function isApiErrorResponse(error: unknown): error is ApiErrorResponse { export { API_ERROR_BAD_TEMPLATE, API_ERROR_WORKSPACE_EXISTS, - ApiError, fetchApi, isApiErrorResponse, }; -export type { ApiErrorResponse }; +export type { ApiError, ApiErrorDetails }; diff --git a/web/src/templates/api.ts b/web/src/templates/api.ts index 1ffed05..e18fb93 100644 --- a/web/src/templates/api.ts +++ b/web/src/templates/api.ts @@ -6,7 +6,8 @@ import type { TemplateImage, BaseTemplate, } from "./types"; -import { fetchApi } from "@/api"; +import { ApiError, fetchApi } from "@/api"; +import { promiseOrThrow } from "@/lib/errors"; function useTemplates() { return useSWR( @@ -17,7 +18,7 @@ function useTemplates() { } function useTemplate(name: string) { - return useSWR( + return useSWR( ["/templates", name], async (): Promise