Skip to content

Commit eb7a87b

Browse files
authored
Merge pull request #73 from thivindu/platform-api
Implement list gateways that a API is deployed and artifacts that are deployed in a Gateway
2 parents 2a1c7e0 + 75de591 commit eb7a87b

File tree

14 files changed

+483
-11
lines changed

14 files changed

+483
-11
lines changed

platform-api/spec/impls/api-lifecycle-management.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
## Entry Points
44

5-
- `platform-api/src/internal/handler/api.go` – implements `/api/v1/apis` CRUD, `/api/v1/projects/:projectId/apis` listing routes and `/api/v1/apis/:apiId/deploy-revision` for API deployment
6-
- `platform-api/src/internal/service/api.go` – validates names, contexts, versions, orchestrates default values and generates deployment YAML and deploys APIs in the Gateway plus repository calls.
5+
- `platform-api/src/internal/handler/api.go` – implements `/api/v1/apis` CRUD, `/api/v1/projects/:projectId/apis` listing routes, `/api/v1/apis/:apiId/deploy-revision` for API deployment, and `/api/v1/apis/:apiId/gateways` for retrieving deployment status
6+
- `platform-api/src/internal/service/api.go` – validates names, contexts, versions, orchestrates default values and generates deployment YAML and deploys APIs in the Gateway plus repository calls. Also handles gateway deployment queries.
77
- `platform-api/src/internal/repository/api.go` – persists APIs, security, CORS, backend services, rate limiting, and operations using transactions.
8-
- `platform-api/src/internal/database/schema.sql` – contains tables for APIs, security configs, backend services, rate limits, and operations.
8+
- `platform-api/src/internal/repository/gateway.go` – handles gateway operations including querying which gateways have specific APIs deployed.
9+
- `platform-api/src/internal/database/schema.sql` – contains tables for APIs, security configs, backend services, rate limits, operations, and API deployments tracking.
910
- `platform-api/src/resources/openapi.yaml` – provides the published API lifecycle contract for client integrations.
1011

1112
## Behaviour
@@ -14,10 +15,15 @@
1415
2. Repository layer writes the main API record and related configuration tables within a single transaction.
1516
3. GET routes return fully hydrated API structures, including nested security and backend definitions.
1617
4. Update requests replace mutable fields and rebuild related configuration sets; deletes cascade via foreign keys.
17-
5. Generates comprehensive deployment API YAML including:
18+
5. Gateway deployment tracking uses the `api_deployments` table to maintain relationships between APIs and gateways.
19+
6. The gateways endpoint queries deployed APIs by joining API deployments with gateway records, filtered by organization for security.
20+
7. Generates comprehensive deployment API YAML including:
1821
- API metadata and configuration
1922
- Security policies (mTLS, OAuth2, API Key)
23+
24+
## Verification
2025
- Create: `curl -k -X POST https://localhost:8443/api/v1/apis -H 'Content-Type: application/json' -d '{"name":"inventory","context":"/inventory","version":"v1","projectId":"<projectId>"}'`.
2126
- Fetch: `curl -k https://localhost:8443/api/v1/apis/<apiId>`; confirm nested structures.
2227
- List: `curl -k https://localhost:8443/api/v1/projects/<projectId>/apis` to verify pagination metadata and entries.
2328
- Deploy API: `curl -k -X POST https://localhost:8443/api/v1/apis/<apiId>/deploy-revision -H 'Content-Type: application/json' -d '[{"name": "production-deployment","gatewayId": "987e6543-e21b-45d3-a789-426614174999", "displayOnDevportal": true}]'` to trigger API deployment.
29+
- Get API Gateways: `curl -k https://localhost:8443/api/v1/apis/<apiId>/gateways` to retrieve all gateways where the API is deployed; expect JSON array with gateway details (id, name, displayName, vhost, isActive, etc.).

platform-api/spec/impls/gateway-management/gateway-management.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Gateway Management provides APIs for registering, managing, and deleting API gat
2929
| PUT | `/api/v1/gateways/{id}` | Update gateway | ✅ Implemented |
3030
| DELETE | `/api/v1/gateways/{id}` | Delete gateway | ✅ Implemented |
3131
| POST | `/api/v1/gateways/{id}/tokens` | Rotate gateway token | ✅ Implemented |
32+
| GET | `/api/v1/gateways/{id}/live-proxy-artifacts` | Get deployed artifacts | ✅ Implemented |
3233
| DELETE | `/api/v1/gateways/{id}/tokens/{tokenId}` | Revoke token | ⏳ Planned |
3334

3435
## Authentication & Authorization

platform-api/src/internal/constants/constants.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,39 @@ var ValidGatewayFunctionalityType = map[string]bool{
6464

6565
// DefaultGatewayFunctionalityType Default gateway functionality type for new gateways
6666
const DefaultGatewayFunctionalityType = GatewayFunctionalityTypeRegular
67+
68+
// API Type Constants
69+
const (
70+
APITypeHTTP = "HTTP"
71+
APITypeWS = "WS"
72+
APITypeSOAPToREST = "SOAPTOREST"
73+
APITypeSOAP = "SOAP"
74+
APITypeGraphQL = "GRAPHQL"
75+
APITypeWebSub = "WEBSUB"
76+
APITypeSSE = "SSE"
77+
APITypeWebhook = "WEBHOOK"
78+
APITypeAsync = "ASYNC"
79+
)
80+
81+
// API SubType Constants
82+
const (
83+
APISubTypeHTTP = "REST"
84+
APISubTypeGraphQL = "GQL"
85+
APISubTypeAsync = "ASYNC"
86+
APISubTypeWebSocket = "WEBSOCKET"
87+
APISubTypeSOAP = "SOAP"
88+
)
89+
90+
// Artifact Type Constants
91+
const (
92+
ArtifactTypeAPI = "API"
93+
ArtifactTypeMCP = "MCP"
94+
ArtifactTypeAPIProduct = "API_PRODUCT"
95+
)
96+
97+
// ValidArtifactTypes Valid artifact types deployed to gateways
98+
var ValidArtifactTypes = map[string]bool{
99+
ArtifactTypeAPI: true,
100+
ArtifactTypeMCP: true,
101+
ArtifactTypeAPIProduct: true,
102+
}

platform-api/src/internal/dto/gateway.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,21 @@ type GatewayStatusListResponse struct {
9090
List []GatewayStatusResponse `json:"list"`
9191
Pagination Pagination `json:"pagination"`
9292
}
93+
94+
// GatewayArtifact represents an artifact (API, MCP, API Product) deployed to a gateway
95+
type GatewayArtifact struct {
96+
ID string `json:"id"`
97+
Name string `json:"name"`
98+
DisplayName string `json:"displayName,omitempty"`
99+
Type string `json:"type"` // "API", "MCP", "API_PRODUCT"
100+
SubType string `json:"subType,omitempty"` // For APIs: "REST", "ASYNC", "GQL"
101+
CreatedAt time.Time `json:"createdAt"`
102+
UpdatedAt time.Time `json:"updatedAt"`
103+
}
104+
105+
// GatewayArtifactListResponse represents a paginated list of artifacts deployed to a gateway
106+
type GatewayArtifactListResponse struct {
107+
Count int `json:"count"`
108+
List []GatewayArtifact `json:"list"`
109+
Pagination Pagination `json:"pagination"`
110+
}

platform-api/src/internal/handler/api.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@ package handler
1919

2020
import (
2121
"errors"
22+
"github.com/gin-gonic/gin"
2223
"log"
2324
"net/http"
2425
"platform-api/src/internal/constants"
2526
"platform-api/src/internal/dto"
2627
"platform-api/src/internal/middleware"
2728
"platform-api/src/internal/service"
2829
"platform-api/src/internal/utils"
29-
30-
"github.com/gin-gonic/gin"
3130
)
3231

3332
type APIHandler struct {
@@ -340,6 +339,36 @@ func (h *APIHandler) DeployAPIRevision(c *gin.Context) {
340339
c.JSON(http.StatusOK, deployments)
341340
}
342341

342+
// GetAPIDeployedGateways handles GET /api/v1/apis/{apiId}/gateways
343+
func (h *APIHandler) GetAPIDeployedGateways(c *gin.Context) {
344+
orgId, exists := middleware.GetOrganizationFromContext(c)
345+
if !exists {
346+
c.JSON(http.StatusUnauthorized, utils.NewErrorResponse(401, "Unauthorized",
347+
"Organization claim not found in token"))
348+
return
349+
}
350+
351+
apiId := c.Param("apiId")
352+
if apiId == "" {
353+
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request", "API ID is required"))
354+
return
355+
}
356+
357+
// Get paginated gateways for the API
358+
gatewayListResponse, err := h.apiService.GetGatewaysForAPI(apiId, orgId)
359+
if err != nil {
360+
if errors.Is(err, constants.ErrAPINotFound) {
361+
c.JSON(http.StatusNotFound, utils.NewErrorResponse(404, "Not Found", "API not found"))
362+
return
363+
}
364+
c.JSON(http.StatusInternalServerError, utils.NewErrorResponse(500, "Internal Server Error", "Failed to get API gateways"))
365+
return
366+
}
367+
368+
// Return paginated gateway list
369+
c.JSON(http.StatusOK, gatewayListResponse)
370+
}
371+
343372
// RegisterRoutes registers all API routes
344373
func (h *APIHandler) RegisterRoutes(r *gin.Engine) {
345374
// API routes
@@ -351,5 +380,6 @@ func (h *APIHandler) RegisterRoutes(r *gin.Engine) {
351380
apiGroup.PUT("/:apiId", h.UpdateAPI)
352381
apiGroup.DELETE("/:apiId", h.DeleteAPI)
353382
apiGroup.POST("/:apiId/deploy-revision", h.DeployAPIRevision)
383+
apiGroup.GET("/:apiId/gateways", h.GetAPIDeployedGateways)
354384
}
355385
}

platform-api/src/internal/handler/gateway.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,51 @@ func (h *GatewayHandler) RotateToken(c *gin.Context) {
321321
c.JSON(http.StatusCreated, response)
322322
}
323323

324+
// GetGatewayArtifacts handles GET /api/v1/gateways/{gatewayId}/live-proxy-artifacts
325+
func (h *GatewayHandler) GetGatewayArtifacts(c *gin.Context) {
326+
orgId, exists := middleware.GetOrganizationFromContext(c)
327+
if !exists {
328+
c.JSON(http.StatusUnauthorized, utils.NewErrorResponse(401, "Unauthorized",
329+
"Organization claim not found in token"))
330+
return
331+
}
332+
333+
gatewayId := c.Param("gatewayId")
334+
if gatewayId == "" {
335+
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
336+
"Gateway ID is required"))
337+
return
338+
}
339+
340+
// Parse artifact type filter parameter
341+
artifactType := c.Query("artifactType")
342+
// Validate artifactType if provided
343+
if artifactType != "" {
344+
if !constants.ValidArtifactTypes[artifactType] {
345+
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
346+
"Invalid artifact type. Valid values are: "+constants.ArtifactTypeAPI+", "+constants.ArtifactTypeMCP+
347+
", "+constants.ArtifactTypeAPIProduct))
348+
return
349+
}
350+
}
351+
352+
// Get paginated artifacts for the gateway
353+
artifactListResponse, err := h.gatewayService.GetGatewayArtifacts(gatewayId, orgId, artifactType)
354+
if err != nil {
355+
if errors.Is(err, constants.ErrGatewayNotFound) {
356+
c.JSON(http.StatusNotFound, utils.NewErrorResponse(404, "Not Found",
357+
"Gateway not found"))
358+
return
359+
}
360+
c.JSON(http.StatusInternalServerError, utils.NewErrorResponse(500, "Internal Server Error",
361+
"Failed to get gateway artifacts"))
362+
return
363+
}
364+
365+
// Return paginated artifact list
366+
c.JSON(http.StatusOK, artifactListResponse)
367+
}
368+
324369
// RegisterRoutes registers gateway routes with the router
325370
func (h *GatewayHandler) RegisterRoutes(r *gin.Engine) {
326371
gatewayGroup := r.Group("/api/v1/gateways")
@@ -331,6 +376,7 @@ func (h *GatewayHandler) RegisterRoutes(r *gin.Engine) {
331376
gatewayGroup.PUT("/:gatewayId", h.UpdateGateway)
332377
gatewayGroup.DELETE("/:gatewayId", h.DeleteGateway)
333378
gatewayGroup.POST("/:gatewayId/tokens", h.RotateToken)
379+
gatewayGroup.GET("/:gatewayId/live-proxy-artifacts", h.GetGatewayArtifacts)
334380
}
335381

336382
gatewayStatusGroup := r.Group("/api/v1/status")

platform-api/src/internal/repository/api.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,37 @@ func (r *APIRepo) GetAPIsByOrganizationID(orgID string, projectID *string) ([]*m
266266
return apis, rows.Err()
267267
}
268268

269+
// GetAPIsByGatewayID retrieves all APIs deployed to a specific gateway
270+
func (r *APIRepo) GetAPIsByGatewayID(gatewayID, organizationID string) ([]*model.API, error) {
271+
query := `
272+
SELECT a.uuid, a.name, a.display_name, a.type, a.created_at, a.updated_at
273+
FROM apis a
274+
INNER JOIN api_deployments ad ON a.uuid = ad.api_uuid
275+
WHERE ad.gateway_uuid = ? AND a.organization_uuid = ?
276+
ORDER BY a.created_at DESC
277+
`
278+
279+
rows, err := r.db.Query(query, gatewayID, organizationID)
280+
if err != nil {
281+
return nil, err
282+
}
283+
defer rows.Close()
284+
285+
var apis []*model.API
286+
for rows.Next() {
287+
api := &model.API{}
288+
err := rows.Scan(
289+
&api.ID, &api.Name, &api.DisplayName, &api.Type, &api.CreatedAt, &api.UpdatedAt,
290+
)
291+
if err != nil {
292+
return nil, err
293+
}
294+
apis = append(apis, api)
295+
}
296+
297+
return apis, nil
298+
}
299+
269300
// UpdateAPI modifies an existing API
270301
func (r *APIRepo) UpdateAPI(api *model.API) error {
271302
tx, err := r.db.Begin()

platform-api/src/internal/repository/gateway.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,36 @@ func (r *GatewayRepo) CountActiveTokens(gatewayId string) (int, error) {
287287
err := r.db.QueryRow(query, gatewayId).Scan(&count)
288288
return count, err
289289
}
290+
291+
// GetGatewaysByAPIID retrieves all gateways where the specified API is deployed
292+
func (r *GatewayRepo) GetGatewaysByAPIID(apiID, organizationID string) ([]*model.Gateway, error) {
293+
query := `
294+
SELECT g.uuid, g.organization_uuid, g.name, g.display_name, g.description,
295+
g.vhost, g.is_critical, g.gateway_functionality_type, g.is_active,
296+
g.created_at, g.updated_at
297+
FROM gateways g
298+
INNER JOIN api_deployments ad ON g.uuid = ad.gateway_uuid
299+
WHERE ad.api_uuid = ? AND g.organization_uuid = ?
300+
ORDER BY g.created_at DESC
301+
`
302+
rows, err := r.db.Query(query, apiID, organizationID)
303+
if err != nil {
304+
return nil, err
305+
}
306+
defer rows.Close()
307+
308+
var gateways []*model.Gateway
309+
for rows.Next() {
310+
gateway := &model.Gateway{}
311+
err := rows.Scan(
312+
&gateway.ID, &gateway.OrganizationID, &gateway.Name, &gateway.DisplayName,
313+
&gateway.Description, &gateway.Vhost, &gateway.IsCritical,
314+
&gateway.FunctionalityType, &gateway.IsActive, &gateway.CreatedAt, &gateway.UpdatedAt,
315+
)
316+
if err != nil {
317+
return nil, err
318+
}
319+
gateways = append(gateways, gateway)
320+
}
321+
return gateways, nil
322+
}

platform-api/src/internal/repository/interfaces.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type APIRepository interface {
4848
GetAPIByUUID(apiId string) (*model.API, error)
4949
GetAPIsByProjectID(projectID string) ([]*model.API, error)
5050
GetAPIsByOrganizationID(orgID string, projectID *string) ([]*model.API, error)
51+
GetAPIsByGatewayID(gatewayID, organizationID string) ([]*model.API, error)
5152
UpdateAPI(api *model.API) error
5253
DeleteAPI(apiId string) error
5354
CreateDeployment(deployment *model.APIDeployment) error
@@ -61,6 +62,7 @@ type GatewayRepository interface {
6162
GetByUUID(gatewayId string) (*model.Gateway, error)
6263
GetByOrganizationID(orgID string) ([]*model.Gateway, error)
6364
GetByNameAndOrgID(name, orgID string) (*model.Gateway, error)
65+
GetGatewaysByAPIID(apiID, organizationID string) ([]*model.Gateway, error)
6466
List() ([]*model.Gateway, error)
6567
Delete(gatewayID, organizationID string) error
6668
UpdateGateway(gateway *model.Gateway) error

platform-api/src/internal/server/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func StartPlatformAPIServer(cfg *config.Server) (*Server, error) {
8484
projectService := service.NewProjectService(projectRepo, orgRepo, apiRepo)
8585
gatewayEventsService := service.NewGatewayEventsService(wsManager)
8686
apiService := service.NewAPIService(apiRepo, projectRepo, gatewayRepo, gatewayEventsService)
87-
gatewayService := service.NewGatewayService(gatewayRepo, orgRepo)
87+
gatewayService := service.NewGatewayService(gatewayRepo, orgRepo, apiRepo)
8888
internalGatewayService := service.NewGatewayInternalAPIService(apiRepo, gatewayRepo, orgRepo)
8989

9090
// Initialize handlers

0 commit comments

Comments
 (0)