diff --git a/backend/embed/api_docs/api.swagger.json b/backend/embed/api_docs/api.swagger.json index 90ef98b..7aa7197 100644 --- a/backend/embed/api_docs/api.swagger.json +++ b/backend/embed/api_docs/api.swagger.json @@ -91,6 +91,11 @@ "$ref": "file://./paths/hosts/hostID/delete.json" } }, + "/hosts/{hostID}/nginx-config": { + "get": { + "$ref": "file://./paths/hosts/hostID/nginx-config/get.json" + } + }, "/nginx-templates": { "get": { "$ref": "file://./paths/nginx-templates/get.json" @@ -170,10 +175,18 @@ "get": { "$ref": "file://./paths/upstreams/upstreamID/get.json" }, + "put": { + "$ref": "file://./paths/upstreams/upstreamID/put.json" + }, "delete": { "$ref": "file://./paths/upstreams/upstreamID/delete.json" } }, + "/upstreams/{upstreamID}/nginx-config": { + "get": { + "$ref": "file://./paths/upstreams/upstreamID/nginx-config/get.json" + } + }, "/users": { "get": { "$ref": "file://./paths/users/get.json" diff --git a/backend/embed/api_docs/paths/hosts/hostID/nginx-config/get.json b/backend/embed/api_docs/paths/hosts/hostID/nginx-config/get.json new file mode 100644 index 0000000..909a55b --- /dev/null +++ b/backend/embed/api_docs/paths/hosts/hostID/nginx-config/get.json @@ -0,0 +1,42 @@ +{ + "operationId": "getHostNginxConfig", + "summary": "Get a Host Nginx Config object by ID", + "tags": ["Hosts"], + "parameters": [ + { + "in": "path", + "name": "hostID", + "schema": { + "type": "integer", + "minimum": 1 + }, + "required": true, + "description": "ID of the Host", + "example": 1 + } + ], + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "schema": { + "required": ["result"], + "properties": { + "result": { + "type": "string" + } + } + }, + "examples": { + "default": { + "value": { + "result": "# ------------------------------------------------------------\n# a.example.com\n# ------------------------------------------------------------\nserver {\n listen 80;\n server_name a.example.com ;\n access_log /data/logs/host-1_access.log proxy;\n error_log /data/logs/host-1_error.log warn;\n # locations ?\n # default location:\n location / {\n # Access Rules ? todo\n # Access checks must...? todo\n # Proxy!\n add_header X-Served-By $host;\n proxy_set_header Host $host;\n proxy_set_header X-Forwarded-Scheme $scheme;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_set_header X-Forwarded-For $remote_addr;\n proxy_http_version 1.1;\n # proxy a single host\n proxy_pass http://192.168.0.10:80;\n }\n # Legacy Custom Configuration\n include /data/nginx/custom/server_proxy[.]conf;\n}\n" + } + } + } + } + } + } + } +} diff --git a/backend/embed/api_docs/paths/upstreams/upstreamID/nginx-config/get.json b/backend/embed/api_docs/paths/upstreams/upstreamID/nginx-config/get.json new file mode 100644 index 0000000..694246b --- /dev/null +++ b/backend/embed/api_docs/paths/upstreams/upstreamID/nginx-config/get.json @@ -0,0 +1,42 @@ +{ + "operationId": "getUpstreamNginxConfig", + "summary": "Get a Upstream Nginx Config object by ID", + "tags": ["Upstreams"], + "parameters": [ + { + "in": "path", + "name": "upstreamID", + "schema": { + "type": "integer", + "minimum": 1 + }, + "required": true, + "description": "ID of the Upstream", + "example": 1 + } + ], + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "schema": { + "required": ["result"], + "properties": { + "result": { + "type": "string" + } + } + }, + "examples": { + "default": { + "value": { + "result": "# ------------------------------------------------------------\n# Upstream 1: API servers\n# ------------------------------------------------------------\nupstream npm_upstream_1 {\nserver 192.168.0.10:80 weight=100 ;\n server 192.168.0.11:80 weight=50 ;\n}\n" + } + } + } + } + } + } + } +} diff --git a/backend/embed/api_docs/paths/upstreams/upstreamID/put.json b/backend/embed/api_docs/paths/upstreams/upstreamID/put.json new file mode 100644 index 0000000..7c324e1 --- /dev/null +++ b/backend/embed/api_docs/paths/upstreams/upstreamID/put.json @@ -0,0 +1,93 @@ +{ + "operationId": "updateUpstream", + "summary": "Update an existing Upstream", + "tags": ["Upstreams"], + "parameters": [ + { + "in": "path", + "name": "upstreamID", + "schema": { + "type": "integer", + "minimum": 1 + }, + "required": true, + "description": "ID of the Upstream", + "example": 1 + } + ], + "requestBody": { + "description": "Upstream details to update", + "required": true, + "content": { + "application/json": { + "schema": "{{schema.UpdateUpstream}}" + } + } + }, + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "schema": { + "required": ["result"], + "properties": { + "result": { + "$ref": "#/components/schemas/UpstreamObject" + } + } + }, + "examples": { + "default": { + "value": { + "result": { + "id": 1, + "created_on": 1673234177, + "modified_on": 1673244559, + "user_id": 2, + "name": "API servers 2", + "nginx_template_id": 5, + "ip_hash": false, + "ntlm": false, + "keepalive": 0, + "keepalive_requests": 0, + "keepalive_time": "", + "keepalive_timeout": "", + "advanced_config": "", + "status": "ready", + "error_message": "", + "servers": [ + { + "id": 1, + "created_on": 1673234177, + "modified_on": 1673244559, + "upstream_id": 1, + "server": "192.168.0.10:80", + "weight": 100, + "max_conns": 0, + "max_fails": 0, + "fail_timeout": 0, + "backup": false + }, + { + "id": 2, + "created_on": 1673234177, + "modified_on": 1673244559, + "upstream_id": 1, + "server": "192.168.0.11:80", + "weight": 50, + "max_conns": 0, + "max_fails": 0, + "fail_timeout": 0, + "backup": false + } + ] + } + } + } + } + } + } + } + } +} diff --git a/backend/internal/api/handler/schema.go b/backend/internal/api/handler/schema.go index b0fdad7..9eab87f 100644 --- a/backend/internal/api/handler/schema.go +++ b/backend/internal/api/handler/schema.go @@ -105,6 +105,7 @@ func replaceIncomingSchemas(swaggerSchema []byte) []byte { str = strings.ReplaceAll(str, `"{{schema.UpdateDNSProvider}}"`, schema.UpdateDNSProvider()) str = strings.ReplaceAll(str, `"{{schema.CreateUpstream}}"`, schema.CreateUpstream()) + str = strings.ReplaceAll(str, `"{{schema.UpdateUpstream}}"`, schema.UpdateUpstream()) return []byte(str) } diff --git a/backend/internal/api/handler/upstreams.go b/backend/internal/api/handler/upstreams.go index 3d2e2a4..d5469f8 100644 --- a/backend/internal/api/handler/upstreams.go +++ b/backend/internal/api/handler/upstreams.go @@ -93,6 +93,48 @@ func CreateUpstream() func(http.ResponseWriter, *http.Request) { } } +// UpdateHost updates a host +// Route: PUT /upstreams/{upstreamID} +func UpdateUpstream() func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + var err error + var upstreamID int + if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil { + h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) + return + } + + item, err := upstream.GetByID(upstreamID) + if err != nil { + h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) + } else { + bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte) + err := json.Unmarshal(bodyBytes, &item) + if err != nil { + h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil) + return + } + + if err = validator.ValidateUpstream(item); err != nil { + h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) + return + } + + if err = item.Save(false); err != nil { + h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) + return + } + + // nolint: errcheck,gosec + // item.Expand(getExpandFromContext(r)) + + configureUpstream(item) + + h.ResultResponseJSON(w, r, http.StatusOK, item) + } + } +} + // DeleteUpstream removes a upstream // Route: DELETE /upstreams/{upstreamID} func DeleteUpstream() func(http.ResponseWriter, *http.Request) { diff --git a/backend/internal/api/router.go b/backend/internal/api/router.go index 2ba0930..494538d 100644 --- a/backend/internal/api/router.go +++ b/backend/internal/api/router.go @@ -204,6 +204,8 @@ func applyRoutes(r chi.Router) chi.Router { r.With(middleware.Enforce(user.CapabilityHostsManage)).Delete("/{upstreamID:[0-9]+}", handler.DeleteUpstream()) r.With(middleware.Enforce(user.CapabilityHostsManage)).With(middleware.EnforceRequestSchema(schema.CreateUpstream())). Post("/", handler.CreateUpstream()) + r.With(middleware.Enforce(user.CapabilityHostsManage)).With(middleware.EnforceRequestSchema(schema.UpdateUpstream())). + Put("/{upstreamID:[0-9]+}", handler.UpdateUpstream()) r.With(middleware.Enforce(user.CapabilityHostsManage)).Get("/{upstreamID:[0-9]+}/nginx-config", handler.GetUpstreamNginxConfig("json")) r.With(middleware.Enforce(user.CapabilityHostsManage)).Get("/{upstreamID:[0-9]+}/nginx-config.txt", handler.GetUpstreamNginxConfig("text")) }) diff --git a/backend/internal/api/schema/update_upstream.go b/backend/internal/api/schema/update_upstream.go new file mode 100644 index 0000000..c259f42 --- /dev/null +++ b/backend/internal/api/schema/update_upstream.go @@ -0,0 +1,69 @@ +package schema + +import "fmt" + +// UpdateUpstream is the schema for incoming data validation +func UpdateUpstream() string { + return fmt.Sprintf(` + { + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "properties": { + "name": %s, + "nginx_template_id": { + "type": "integer", + "minimum": 1 + }, + "advanced_config": %s, + "ip_hash": { + "type": "boolean" + }, + "ntlm": { + "type": "boolean" + }, + "keepalive": { + "type": "integer" + }, + "keepalive_requests": { + "type": "integer" + }, + "keepalive_time": { + "type": "string" + }, + "keepalive_timeout": { + "type": "string" + }, + "servers": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "server" + ], + "properties": { + "server": %s, + "weight": { + "type": "integer" + }, + "max_conns": { + "type": "integer" + }, + "max_fails": { + "type": "integer" + }, + "fail_timeout": { + "type": "integer" + }, + "backup": { + "type": "boolean" + } + } + } + } + } + } +`, stringMinMax(1, 100), stringMinMax(0, 1024), stringMinMax(2, 255)) +}