Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions cmd/gendoc/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1161,6 +1161,32 @@ Contains a JSON object with the details of an error.
{StatusCode: http.StatusInternalServerError, Reference: "#/components/responses/InternalServerError"},
},
},
{
OperationId: "renameAppLocalBrick",
Method: http.MethodPost,
Path: "/v1/apps/{appID}/bricks/{brickID}/rename",
Parameters: (*struct {
ID string `path:"appID" description:"application identifier."`
BrickID string `path:"brickID" description:"brick identifier."`
})(nil),
Request: handlers.AppLocalBrickRenameRequest{},
CustomSuccessResponse: &CustomResponseDef{
ContentType: "application/json",
DataStructure: bricks.LocalBrickRenameResult{},
Description: "Successful response",
StatusCode: http.StatusOK,
},
Description: "Rename a local brick. Changes the brick's ID and folder name derived from the new name. Only local bricks can be renamed.",
Summary: "Rename a local brick",
Tags: []Tag{ApplicationTag},
PossibleErrors: []ErrorResponse{
{StatusCode: http.StatusBadRequest, Reference: "#/components/responses/BadRequest"},
{StatusCode: http.StatusNotFound, Reference: "#/components/responses/NotFound"},
{StatusCode: http.StatusConflict, Reference: "#/components/responses/Conflict"},
{StatusCode: http.StatusPreconditionFailed, Reference: "#/components/responses/PreconditionFailed"},
{StatusCode: http.StatusInternalServerError, Reference: "#/components/responses/InternalServerError"},
},
},
{
OperationId: "listLibraries",
Method: http.MethodGet,
Expand Down
1 change: 1 addition & 0 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ func NewHTTPRouter(
mux.Handle("PATCH /v1/apps/{appID}/bricks/{brickID}", handlers.HandleBrickUpdates(brickService, idProvider))
mux.Handle("DELETE /v1/apps/{appID}/bricks/{brickID}", handlers.HandleBrickDelete(brickService, idProvider))
mux.Handle("POST /v1/apps/{appID}/bricks", handlers.HandleAppLocalBrickCreate(idProvider))
mux.Handle("POST /v1/apps/{appID}/bricks/{brickID}/rename", handlers.HandleAppLocalBrickRename(brickService, idProvider))

mux.Handle("GET /v1/docs/", http.StripPrefix("/v1/docs/", handlers.DocsServer(docsFS)))

Expand Down
55 changes: 55 additions & 0 deletions internal/api/docs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,51 @@ paths:
summary: Upsert a brick instance for an app
tags:
- Application
/v1/apps/{appID}/bricks/{brickID}/rename:
post:
description: Rename a local brick. Changes the brick's ID and folder name derived
from the new name. Only local bricks can be renamed.
operationId: renameAppLocalBrick
parameters:
- description: application identifier.
in: path
name: appID
required: true
schema:
description: application identifier.
type: string
- description: brick identifier.
in: path
name: brickID
required: true
schema:
description: brick identifier.
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AppLocalBrickRenameRequest'
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/LocalBrickRenameResult'
description: Successful response
"400":
$ref: '#/components/responses/BadRequest'
"404":
$ref: '#/components/responses/NotFound'
"409":
$ref: '#/components/responses/Conflict'
"412":
$ref: '#/components/responses/PreconditionFailed'
"500":
$ref: '#/components/responses/InternalServerError'
summary: Rename a local brick
tags:
- Application
/v1/apps/{appID}/exposed-ports:
get:
description: Return all ports exposed by the given app.
Expand Down Expand Up @@ -1515,6 +1560,11 @@ components:
id:
type: string
type: object
AppLocalBrickRenameRequest:
properties:
name:
type: string
type: object
AppPortResponse:
properties:
ports:
Expand Down Expand Up @@ -1840,6 +1890,11 @@ components:
pagination:
$ref: '#/components/schemas/Pagination'
type: object
LocalBrickRenameResult:
properties:
id:
type: string
type: object
PackageType:
description: Package type
enum:
Expand Down
93 changes: 93 additions & 0 deletions internal/api/handlers/app_local_brick_rename.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// This file is part of arduino-app-cli.
//
// Copyright (C) Arduino s.r.l. and/or its affiliated companies
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

package handlers

import (
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"

"github.com/arduino/arduino-app-cli/internal/api/models"
"github.com/arduino/arduino-app-cli/internal/orchestrator/app"
"github.com/arduino/arduino-app-cli/internal/orchestrator/bricks"
"github.com/arduino/arduino-app-cli/internal/render"
)

type AppLocalBrickRenameRequest struct {
Name string `json:"name"`
}

func HandleAppLocalBrickRename(brickService *bricks.Service, idProvider *app.IDProvider) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
appId, err := idProvider.IDFromBase64(r.PathValue("appID"))
if err != nil {
render.EncodeResponse(w, http.StatusPreconditionFailed, models.ErrorResponse{Details: "invalid app id"})
return
}

a, err := app.Load(appId.ToPath())
if err != nil {
slog.Error("Unable to load the app", slog.String("error", err.Error()), slog.String("path", appId.String()))
render.EncodeResponse(w, http.StatusInternalServerError, models.ErrorResponse{Details: "unable to find the app"})
return
}

oldID := r.PathValue("brickID")
if oldID == "" {
render.EncodeResponse(w, http.StatusBadRequest, models.ErrorResponse{Details: "brickID must be set"})
return
}

var req AppLocalBrickRenameRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
slog.Error("Failed to decode request body", slog.String("error", err.Error()))
render.EncodeResponse(w, http.StatusBadRequest, models.ErrorResponse{Details: "invalid request body"})
return
}
if req.Name == "" {
render.EncodeResponse(w, http.StatusBadRequest, models.ErrorResponse{Details: "name is required"})
return
}

newID, err := generateBrickID(req.Name)
if err != nil {
render.EncodeResponse(w, http.StatusBadRequest, models.ErrorResponse{Details: err.Error()})
return
}

res, err := brickService.LocalBrickRename(&a, oldID, newID, req.Name)
if err != nil {
switch {
case errors.Is(err, bricks.ErrBrickNotFound):
render.EncodeResponse(w, http.StatusNotFound, models.ErrorResponse{Details: fmt.Sprintf("local brick %q not found", oldID)})
case errors.Is(err, bricks.ErrBrickNotLocal):
render.EncodeResponse(w, http.StatusBadRequest, models.ErrorResponse{Details: "only local bricks can be renamed"})
case errors.Is(err, bricks.ErrBrickIDConflict):
render.EncodeResponse(w, http.StatusConflict, models.ErrorResponse{Details: fmt.Sprintf("a brick with id %q already exists", newID)})
default:
slog.Error("Failed to rename local brick", slog.String("error", err.Error()))
render.EncodeResponse(w, http.StatusInternalServerError, models.ErrorResponse{Details: "failed to rename local brick"})
}
return
}

render.EncodeResponse(w, http.StatusOK, res)
}
}
Loading
Loading