-
Notifications
You must be signed in to change notification settings - Fork 106
Add /api-portals CRUD resource to platform-api #3219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dushaniw
wants to merge
20
commits into
wso2:main
Choose a base branch
from
dushaniw:feat/api-portals-crud
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
43fc09d
add api portal schema for postgres.
dushaniw d9aff4b
database schema.
dushaniw feb757a
add dao models for devportal.
dushaniw ef9ce1d
remove data_version. add dao code.
dushaniw ebe5325
add service and rest api implementation.
dushaniw 88df843
add integration tests at handler level.
dushaniw f6c666e
Validate portal URL scheme on create and update
dushaniw 0d7ca1d
Align API Portal description maxLength with database column
dushaniw 48e92af
Accept workflowStatus on create and enforce url/status consistency
dushaniw 11211d8
Split API Portal config into authConfig and metadata; encrypt secrets
dushaniw 6435f86
Add outbound AuthProvider surface for API Portal callers
dushaniw 53273f7
Clear stored authConfig when switching authType to local
dushaniw b1338ad
fix duplicate tag.
dushaniw 86f1d2e
remove devportals tag.
dushaniw 949ce62
Remove workflowStatus from the /api-portals wire surface
dushaniw f87bfc4
Rename workflow_status → status on the api_portals column and Go code
dushaniw aa1cdf8
Tighten the outbound token-endpoint call: shape check + no redirects
dushaniw 78e2dc6
Enforce redirect refusal on caller-supplied *http.Client too
dushaniw cd6eac3
Merge branch 'main' of github.com:wso2/api-platform into feat/api-por…
dushaniw 320f532
feat(api-portals): expose APIPortals capability on pdk.Deps
dushaniw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| /* | ||
| * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| * | ||
| */ | ||
|
|
||
| package handler | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "fmt" | ||
| "log/slog" | ||
| "net/http" | ||
| "strings" | ||
|
|
||
| "github.com/wso2/api-platform/platform-api/api" | ||
| "github.com/wso2/api-platform/platform-api/internal/apperror" | ||
| "github.com/wso2/api-platform/platform-api/internal/constants" | ||
| "github.com/wso2/api-platform/platform-api/internal/middleware" | ||
| "github.com/wso2/api-platform/platform-api/internal/router" | ||
| "github.com/wso2/api-platform/platform-api/internal/service" | ||
|
|
||
| "github.com/wso2/api-platform/httpkit/httputil" | ||
| ) | ||
|
|
||
| // APIPortalHandler exposes /api-portals CRUD. The generated OpenAPI types | ||
| // (api.CreateApiPortalRequest / api.ApiPortalResponse / …) are the wire contract | ||
| // AND the service-layer contract — the service speaks in these directly so its | ||
| // methods also satisfy pdk.APIPortals for plugins. | ||
| type APIPortalHandler struct { | ||
| svc *service.APIPortalService | ||
| identity *service.IdentityService | ||
| slogger *slog.Logger | ||
| } | ||
|
|
||
| // NewAPIPortalHandler constructs an APIPortalHandler. | ||
| func NewAPIPortalHandler(svc *service.APIPortalService, identity *service.IdentityService, slogger *slog.Logger) *APIPortalHandler { | ||
| return &APIPortalHandler{svc: svc, identity: identity, slogger: slogger} | ||
| } | ||
|
|
||
| // CreateAPIPortal — POST /api-portals | ||
| func (h *APIPortalHandler) CreateAPIPortal(w http.ResponseWriter, r *http.Request) error { | ||
| orgID, ok := middleware.GetOrganizationFromRequest(r) | ||
| if !ok { | ||
| return apperror.Unauthorized.New().WithLogMessage("organization claim not found in token") | ||
| } | ||
|
|
||
| var req api.CreateApiPortalRequest | ||
| if err := json.NewDecoder(r.Body).Decode(&req); err != nil { | ||
| return apperror.NewValidation(err) | ||
| } | ||
|
|
||
| createdBy, err := resolveActorErr(r, h.identity, "create api portal") | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| resp, err := h.svc.CreateAPIPortal(&req, orgID, createdBy) | ||
| if err != nil { | ||
| return serviceError(err, fmt.Sprintf("failed to create api portal %q for org %s by user %s", req.Handle, orgID, createdBy)) | ||
| } | ||
|
|
||
| setLocation(w, "api-portals", derefStr(resp.Handle)) | ||
| httputil.WriteJSON(w, http.StatusCreated, resp) | ||
| return nil | ||
| } | ||
|
|
||
| // GetAPIPortal — GET /api-portals/{apiPortalId} | ||
| func (h *APIPortalHandler) GetAPIPortal(w http.ResponseWriter, r *http.Request) error { | ||
| orgID, ok := middleware.GetOrganizationFromRequest(r) | ||
| if !ok { | ||
| return apperror.Unauthorized.New().WithLogMessage("organization claim not found in token") | ||
| } | ||
|
|
||
| handle := strings.TrimSpace(r.PathValue("apiPortalId")) | ||
| if handle == "" { | ||
| return apperror.ValidationFailed.New("API Portal ID is required") | ||
| } | ||
|
|
||
| resp, err := h.svc.GetAPIPortal(handle, orgID) | ||
| if err != nil { | ||
| return serviceError(err, fmt.Sprintf("failed to get api portal %q in org %s", handle, orgID)) | ||
| } | ||
| httputil.WriteJSON(w, http.StatusOK, resp) | ||
| return nil | ||
| } | ||
|
|
||
| // ListAPIPortals — GET /api-portals | ||
| func (h *APIPortalHandler) ListAPIPortals(w http.ResponseWriter, r *http.Request) error { | ||
| orgID, ok := middleware.GetOrganizationFromRequest(r) | ||
| if !ok { | ||
| return apperror.Unauthorized.New().WithLogMessage("organization claim not found in token") | ||
| } | ||
|
|
||
| opts := parseListOptions(r) | ||
|
|
||
| resp, err := h.svc.ListAPIPortals(orgID, opts.Limit, opts.Offset, opts.SortBy, opts.SortOrder, opts.Search) | ||
| if err != nil { | ||
| return serviceError(err, fmt.Sprintf("failed to list api portals for org %s", orgID)) | ||
| } | ||
| httputil.WriteJSON(w, http.StatusOK, resp) | ||
| return nil | ||
| } | ||
|
|
||
| // UpdateAPIPortal — PUT /api-portals/{apiPortalId} | ||
| func (h *APIPortalHandler) UpdateAPIPortal(w http.ResponseWriter, r *http.Request) error { | ||
| orgID, ok := middleware.GetOrganizationFromRequest(r) | ||
| if !ok { | ||
| return apperror.Unauthorized.New().WithLogMessage("organization claim not found in token") | ||
| } | ||
|
|
||
| handle := strings.TrimSpace(r.PathValue("apiPortalId")) | ||
| if handle == "" { | ||
| return apperror.ValidationFailed.New("API Portal ID is required") | ||
| } | ||
|
|
||
| var req api.UpdateApiPortalRequest | ||
| if err := json.NewDecoder(r.Body).Decode(&req); err != nil { | ||
| return apperror.NewValidation(err) | ||
| } | ||
|
|
||
| updatedBy, err := resolveActorErr(r, h.identity, "update api portal") | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| resp, err := h.svc.UpdateAPIPortal(handle, &req, orgID, updatedBy) | ||
| if err != nil { | ||
| return serviceError(err, fmt.Sprintf("failed to update api portal %q in org %s by user %s", handle, orgID, updatedBy)) | ||
| } | ||
| httputil.WriteJSON(w, http.StatusOK, resp) | ||
| return nil | ||
| } | ||
|
|
||
| // DeleteAPIPortal — DELETE /api-portals/{apiPortalId} | ||
| func (h *APIPortalHandler) DeleteAPIPortal(w http.ResponseWriter, r *http.Request) error { | ||
| orgID, ok := middleware.GetOrganizationFromRequest(r) | ||
| if !ok { | ||
| return apperror.Unauthorized.New().WithLogMessage("organization claim not found in token") | ||
| } | ||
|
|
||
| handle := strings.TrimSpace(r.PathValue("apiPortalId")) | ||
| if handle == "" { | ||
| return apperror.ValidationFailed.New("API Portal ID is required") | ||
| } | ||
|
|
||
| actor, err := resolveActorErr(r, h.identity, "delete api portal") | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if err := h.svc.DeleteAPIPortal(handle, orgID, actor); err != nil { | ||
| return serviceError(err, fmt.Sprintf("failed to delete api portal %q in org %s by user %s", handle, orgID, actor)) | ||
| } | ||
| w.WriteHeader(http.StatusNoContent) | ||
| return nil | ||
| } | ||
|
|
||
| // RegisterRoutes wires all /api-portals routes onto the shared mux. | ||
| func (h *APIPortalHandler) RegisterRoutes(mux router.Router) { | ||
| base := constants.APIBasePath + "/api-portals" | ||
| mux.HandleFunc("POST "+base, middleware.MapErrors(h.slogger, h.CreateAPIPortal)) | ||
| mux.HandleFunc("GET "+base, middleware.MapErrors(h.slogger, h.ListAPIPortals)) | ||
| mux.HandleFunc("GET "+base+"/{apiPortalId}", middleware.MapErrors(h.slogger, h.GetAPIPortal)) | ||
| mux.HandleFunc("PUT "+base+"/{apiPortalId}", middleware.MapErrors(h.slogger, h.UpdateAPIPortal)) | ||
| mux.HandleFunc("DELETE "+base+"/{apiPortalId}", middleware.MapErrors(h.slogger, h.DeleteAPIPortal)) | ||
| } | ||
|
|
||
| // derefStr returns the pointed-to string or "" when nil. Local helper used by | ||
| // setLocation to source the Location header from the api-generated response. | ||
| func derefStr(p *string) string { | ||
| if p == nil { | ||
| return "" | ||
| } | ||
| return *p | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.