Skip to content
Merged
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
9 changes: 9 additions & 0 deletions database/queries/registry.sql
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ SELECT id,
FROM registry
WHERE id = sqlc.arg(id);

-- name: GetRegistryByName :one
SELECT id,
name,
reg_type,
created_at,
updated_at
FROM registry
WHERE name = sqlc.arg(name);

-- name: InsertRegistry :one
INSERT INTO registry (
name,
Expand Down
6 changes: 6 additions & 0 deletions database/queries/servers.sql
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,9 @@ INSERT INTO mcp_server_icon (
) ON CONFLICT (server_id, source_uri, mime_type, theme)
DO UPDATE SET
theme = sqlc.arg(theme);

-- name: DeleteServerVersion :exec
DELETE FROM mcp_server
WHERE reg_id = sqlc.arg(reg_id)
AND name = sqlc.arg(name)
AND version = sqlc.arg(version);
2 changes: 1 addition & 1 deletion docs/thv-registry-api/docs.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/thv-registry-api/swagger.json

Large diffs are not rendered by default.

112 changes: 66 additions & 46 deletions docs/thv-registry-api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,72 @@ info:
version: "0.1"
openapi: 3.1.0
paths:
/{registryName}/v0.1/servers/{serverName}/versions/{version}:
delete:
description: Delete a server version from a specific managed registry
parameters:
- description: Registry name
in: path
name: registryName
required: true
schema:
type: string
- description: Server name (URL-encoded)
in: path
name: serverName
required: true
schema:
type: string
- description: Version (URL-encoded)
in: path
name: version
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
type: object
responses:
"204":
description: No content
"400":
content:
application/json:
schema:
additionalProperties:
type: string
type: object
description: Bad request
"403":
content:
application/json:
schema:
additionalProperties:
type: string
type: object
description: Not a managed registry
"404":
content:
application/json:
schema:
additionalProperties:
type: string
type: object
description: Server version not found
"500":
content:
application/json:
schema:
additionalProperties:
type: string
type: object
description: Internal server error
summary: Delete server version from specific registry
tags:
- registry
- official
/api/v0/registry/info:
get:
deprecated: true
Expand Down Expand Up @@ -532,52 +598,6 @@ paths:
tags:
- extension
/extension/v0/registries/{registryName}/servers/{serverName}/versions/{version}:
delete:
description: Delete a server from the registry
parameters:
- description: Registry Name
in: path
name: registryName
required: true
schema:
type: string
- description: URL-encoded server name (e.g., \
in: path
name: serverName
required: true
schema:
type: string
- description: URL-encoded version to retrieve (e.g., \
in: path
name: version
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
type: object
responses:
"400":
content:
application/json:
schema:
additionalProperties:
type: string
type: object
description: Bad request
"501":
content:
application/json:
schema:
additionalProperties:
type: string
type: object
description: Not implemented
summary: Delete server
tags:
- extension
put:
description: Create or update a server in the registry
parameters:
Expand Down
35 changes: 0 additions & 35 deletions internal/api/extension/v0/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ func Router(svc service.RegistryService) http.Handler {

r.Route("/registries/{registryName}/servers/{serverName}", func(r chi.Router) {
r.Put("/versions/{version}", routes.upsertVersion)
r.Delete("/versions/{version}", routes.deleteVersion)
})

return r
Expand Down Expand Up @@ -152,37 +151,3 @@ func (*Routes) upsertVersion(w http.ResponseWriter, r *http.Request) {

common.WriteErrorResponse(w, "Creating or updating servers is not supported", http.StatusNotImplemented)
}

// deleteVersion handles DELETE /extension/v0/registries/{registryName}/servers/{serverName}/versions/{version}
//
// @Summary Delete server
// @Description Delete a server from the registry
// @Tags extension
// @Accept json
// @Produce json
// @Param registryName path string true "Registry Name"
// @Param serverName path string true "URL-encoded server name (e.g., \"com.example%2Fmy-server\")"
// @Param version path string true "URL-encoded version to retrieve (e.g., \"1.0.0\")"
// @Failure 400 {object} map[string]string "Bad request"
// @Failure 501 {object} map[string]string "Not implemented"
// @Router /extension/v0/registries/{registryName}/servers/{serverName}/versions/{version} [delete]
func (*Routes) deleteVersion(w http.ResponseWriter, r *http.Request) {
registryName := chi.URLParam(r, "registryName")
serverName := chi.URLParam(r, "serverName")
version := chi.URLParam(r, "version")

if strings.TrimSpace(registryName) == "" {
common.WriteErrorResponse(w, "Registry name is required", http.StatusBadRequest)
return
}
if strings.TrimSpace(serverName) == "" {
common.WriteErrorResponse(w, "Server ID is required", http.StatusBadRequest)
return
}
if strings.TrimSpace(version) == "" {
common.WriteErrorResponse(w, "Version is required", http.StatusBadRequest)
return
}

common.WriteErrorResponse(w, "Deleting servers is not supported", http.StatusNotImplemented)
}
86 changes: 0 additions & 86 deletions internal/api/extension/v0/routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,92 +99,6 @@ func TestUpsertVersion(t *testing.T) {
}
}

func TestDeleteVersion(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish)

mockSvc := mocks.NewMockRegistryService(ctrl)
router := Router(mockSvc)

tests := []struct {
name string
path string
method string
wantStatus int
wantError string
}{
{
name: "delete version - valid ID",
path: "/registries/foo/servers/test-server-123/versions/1.0.0",
method: "DELETE",
wantStatus: http.StatusNotImplemented,
},
{
name: "delete version - empty registry name",
path: "/registries/%20/servers/test-server-123/versions/1.0.0",
method: "DELETE",
wantStatus: http.StatusBadRequest,
wantError: "Registry name is required",
},
{
name: "delete version - empty server name",
path: "/registries/foo/servers/%20/versions/1.0.0",
method: "DELETE",
wantStatus: http.StatusBadRequest,
wantError: "Server ID is required",
},
{
name: "delete version - empty version",
path: "/registries/foo/servers/test-server-123/versions/%20",
method: "DELETE",
wantStatus: http.StatusBadRequest,
wantError: "Version is required",
},
{
name: "delete version - with special characters",
path: "/registries/foo/servers/test-server-123/versions/1.0%2F0.0",
method: "DELETE",
wantStatus: http.StatusNotImplemented,
},
{
name: "delete version - no id",
path: "/registries/foo/servers/test-server-123/versions/",
method: "DELETE",
wantStatus: http.StatusNotFound,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
req, err := http.NewRequest(tt.method, tt.path, nil)
require.NoError(t, err)

rr := httptest.NewRecorder()
router.ServeHTTP(rr, req)

assert.Equal(t, tt.wantStatus, rr.Code)

if tt.wantStatus == http.StatusNotImplemented {
var response map[string]string
err = json.Unmarshal(rr.Body.Bytes(), &response)
require.NoError(t, err)
assert.Contains(t, response, "error")
assert.Equal(t, "Deleting servers is not supported", response["error"])
}

if tt.wantStatus == http.StatusBadRequest && tt.wantError != "" {
var response map[string]string
err = json.Unmarshal(rr.Body.Bytes(), &response)
require.NoError(t, err)
assert.Contains(t, response, "error")
assert.Equal(t, tt.wantError, response["error"])
}
})
}
}

func TestListRegistries(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
Expand Down
66 changes: 66 additions & 0 deletions internal/api/registry/v01/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package v01

import (
"errors"
"net/http"
"strconv"
"strings"
Expand Down Expand Up @@ -43,6 +44,7 @@ func Router(svc service.RegistryService) http.Handler {
r.Route("/{registryName}/v0.1/servers/{serverName}", func(r chi.Router) {
r.Get("/versions", routes.listVersionsWithRegistryName)
r.Get("/versions/{version}", routes.getVersionWithRegistryName)
r.Delete("/versions/{version}", routes.deleteVersionWithRegistryName)
})
r.Post("/{registryName}/v0.1/publish", routes.publishWithRegistryName)

Expand Down Expand Up @@ -328,6 +330,70 @@ func (routes *Routes) getVersionWithRegistryName(w http.ResponseWriter, r *http.
routes.handleGetVersion(w, r, registryName)
}

// deleteVersionWithRegistryName handles DELETE /{registryName}/v0.1/servers/{serverName}/versions/{version}
//
// @Summary Delete server version from specific registry
// @Description Delete a server version from a specific managed registry
// @Tags registry,official
// @Accept json
// @Produce json
// @Param registryName path string true "Registry name"
// @Param serverName path string true "Server name (URL-encoded)"
// @Param version path string true "Version (URL-encoded)"
// @Success 204 "No content"
// @Failure 400 {object} map[string]string "Bad request"
// @Failure 403 {object} map[string]string "Not a managed registry"
// @Failure 404 {object} map[string]string "Server version not found"
// @Failure 500 {object} map[string]string "Internal server error"
// @Router /{registryName}/v0.1/servers/{serverName}/versions/{version} [delete]
func (routes *Routes) deleteVersionWithRegistryName(w http.ResponseWriter, r *http.Request) {
// Extract URL parameters
registryName := chi.URLParam(r, "registryName")
serverName := chi.URLParam(r, "serverName")
version := chi.URLParam(r, "version")

if strings.TrimSpace(registryName) == "" {
common.WriteErrorResponse(w, "Registry name is required", http.StatusBadRequest)
return
}

if strings.TrimSpace(serverName) == "" {
common.WriteErrorResponse(w, "Server name is required", http.StatusBadRequest)
return
}

if strings.TrimSpace(version) == "" {
common.WriteErrorResponse(w, "Version is required", http.StatusBadRequest)
return
}

// Call service layer
err := routes.service.DeleteServerVersion(
r.Context(),
service.WithRegistryName[service.DeleteServerVersionOptions](registryName),
service.WithName[service.DeleteServerVersionOptions](serverName),
service.WithVersion[service.DeleteServerVersionOptions](version),
)

if err != nil {
// Check for specific error types
if errors.Is(err, service.ErrRegistryNotFound) || errors.Is(err, service.ErrServerNotFound) {
common.WriteErrorResponse(w, err.Error(), http.StatusNotFound)
return
}
if errors.Is(err, service.ErrNotManagedRegistry) {
common.WriteErrorResponse(w, err.Error(), http.StatusForbidden)
return
}
// All other errors
common.WriteErrorResponse(w, "Failed to delete server version", http.StatusInternalServerError)
return
}

// Success - return 204 No Content
w.WriteHeader(http.StatusNoContent)
}

// handlePublish is a shared helper that handles publishing with an optional registry name.
func (*Routes) handlePublish(w http.ResponseWriter, r *http.Request, registryName string) {
// TODO: Use registryName in the actual implementation
Expand Down
2 changes: 2 additions & 0 deletions internal/db/sqlc/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading