Skip to content

Commit

Permalink
✨ Introduce initial get wallets endpoints (ITISFoundation#4853)
Browse files Browse the repository at this point in the history
  • Loading branch information
bisgaard-itis authored Oct 11, 2023
1 parent 63c0514 commit 0901949
Show file tree
Hide file tree
Showing 15 changed files with 443 additions and 20 deletions.
1 change: 1 addition & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
⚠️ Changes in ops configuration etc. are required before deploying.
[ Please add a link to the associated ops-issue or PR, such as in https://github.com/ITISFoundation/osparc-ops-environments or https://git.speag.com/oSparc/osparc-infra ]
🗃️ Database table changed (relevant for devops).
👽️ Public API changes (meaning: dev features are moved to being exposed in production)
or from https://gitmoji.dev/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
studies,
studies_jobs,
users,
wallets,
)


Expand All @@ -28,6 +29,7 @@ def create_router(settings: ApplicationSettings):
router.include_router(solvers_jobs.router, tags=["solvers"], prefix="/solvers")
router.include_router(studies.router, tags=["studies"], prefix="/studies")
router.include_router(studies_jobs.router, tags=["studies"], prefix="/studies")
router.include_router(wallets.router, tags=["wallets"], prefix="/wallets")

# NOTE: multiple-files upload is currently disabled
# Web form to upload files at http://localhost:8000/v0/upload-form-view
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from fastapi.responses import RedirectResponse
from fastapi_pagination.api import create_page
from models_library.api_schemas_webserver.projects import ProjectCreateNew, ProjectGet
from models_library.api_schemas_webserver.wallets import WalletGet
from models_library.clusters import ClusterID
from models_library.projects_nodes_io import BaseFileLink
from pydantic.types import PositiveInt
Expand Down Expand Up @@ -533,3 +534,28 @@ async def replace_job_custom_metadata(
f"Cannot find job={job_name} ",
status_code=status.HTTP_404_NOT_FOUND,
)


@router.get(
"/{solver_key:path}/releases/{version}/jobs/{job_id:uuid}/wallet",
response_model=WalletGet | None,
responses={**_COMMON_ERROR_RESPONSES},
include_in_schema=API_SERVER_DEV_FEATURES_ENABLED,
)
async def get_job_wallet(
solver_key: SolverKeyId,
version: VersionStr,
job_id: JobID,
webserver_api: Annotated[AuthSession, Depends(get_webserver_session)],
):
job_name = _compose_job_resource_name(solver_key, version, job_id)
_logger.debug("Getting wallet for job '%s'", job_name)

try:
return await webserver_api.get_project_wallet(project_id=job_id)

except ProjectNotFoundError:
return create_error_json_response(
f"Cannot find job={job_name}",
status_code=status.HTTP_404_NOT_FOUND,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import logging
from typing import Annotated

from fastapi import APIRouter, Depends
from models_library.api_schemas_webserver.wallets import WalletGet

from ..dependencies.webserver import AuthSession, get_webserver_session
from ._common import API_SERVER_DEV_FEATURES_ENABLED

_logger = logging.getLogger(__name__)

router = APIRouter()


@router.get(
"/{wallet_id}",
response_model=WalletGet,
include_in_schema=API_SERVER_DEV_FEATURES_ENABLED,
)
async def get_wallet(
wallet_id: int,
webserver_api: Annotated[AuthSession, Depends(get_webserver_session)],
):
return await webserver_api.get_wallet(wallet_id=wallet_id)
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ class WebServerSettings(BaseCustomSettings, MixinServiceSettings, MixinSessionSe
)
WEBSERVER_SESSION_NAME: str = "osparc.WEBAPI_SESSION"

@cached_property
def base_url(self) -> str:
# http://webserver:8080/
url_without_vtag: str = self._compose_url(
prefix="WEBSERVER",
port=URLPart.REQUIRED,
)
return url_without_vtag

@cached_property
def api_base_url(self) -> str:
# http://webserver:8080/v0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
ProjectMetadataGet,
ProjectMetadataUpdate,
)
from models_library.api_schemas_webserver.wallets import WalletGet
from models_library.generics import Envelope
from models_library.projects import ProjectID
from models_library.rest_pagination import Page
Expand Down Expand Up @@ -368,6 +369,29 @@ async def update_project_metadata(
assert data # nosec
return data

async def get_project_wallet(self, project_id: ProjectID) -> WalletGet | None:
with _handle_webserver_api_errors():
response = await self.client.get(
f"/projects/{project_id}/wallet",
cookies=self.session_cookies,
)
response.raise_for_status()
data = Envelope[WalletGet].parse_raw(response.text).data
return data

# WALLETS -------------------------------------------------

async def get_wallet(self, wallet_id: int) -> WalletGet:
with _handle_webserver_api_errors():
response = await self.client.get(
f"/wallets/{wallet_id}",
cookies=self.session_cookies,
)
response.raise_for_status()
data = Envelope[WalletGet].parse_raw(response.text).data
assert data # nosec
return data


# MODULES APP SETUP -------------------------------------------------------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
import httpx
import jsonref
from pydantic import BaseModel, Field, parse_obj_as, root_validator, validator
from simcore_service_api_server.core.settings import CatalogSettings, StorageSettings
from simcore_service_api_server.core.settings import (
CatalogSettings,
StorageSettings,
WebServerSettings,
)

service_hosts = Literal["storage", "catalog"]
service_hosts = Literal["storage", "catalog", "webserver"]


class CapturedParameterSchema(BaseModel):
Expand Down Expand Up @@ -146,7 +150,9 @@ class PathDescription(BaseModel):


def enhance_from_openapi_spec(response: httpx.Response) -> PathDescription:
assert response.url.host in get_args(service_hosts)
assert response.url.host in get_args(
service_hosts
), f"{response.url.host} is not in {service_hosts} - please add it yourself"
openapi_spec: dict[str, Any] = _get_openapi_specs(response.url.host)
return _determine_path(
openapi_spec, Path(response.request.url.raw_path.decode("utf8").split("?")[0])
Expand All @@ -161,6 +167,9 @@ def _get_openapi_specs(host: service_hosts) -> dict[str, Any]:
elif host == "catalog":
settings = CatalogSettings()
url = settings.base_url + "/api/v0/openapi.json"
elif host == "webserver":
settings = WebServerSettings()
url = settings.base_url + "/dev/doc/swagger.json"
else:
raise OpenApiSpecIssue(
f"{host=} has not been added yet to the testing system. Please do so yourself"
Expand All @@ -178,37 +187,37 @@ def _get_openapi_specs(host: service_hosts) -> dict[str, Any]:
def _determine_path(
openapi_spec: dict[str, Any], response_path: Path
) -> PathDescription:
def parts(p: str) -> tuple[str, ...]:
all_parts: list[str] = sum((elm.split("/") for elm in p.split(":")), start=[])
return tuple(part for part in all_parts if len(part) > 0)

for p in openapi_spec["paths"]:
openapi_path = Path(p)
if len(openapi_path.parts) != len(response_path.parts):
openapi_parts: tuple[str, ...] = tuple(parts(p))
response_parts: tuple[str, ...] = tuple(parts(f"{response_path}"))
if len(openapi_parts) != len(response_parts):
continue
path_params = {
param.name: param for param in _get_params(openapi_spec, p) if param.is_path
}
if (len(path_params) == 0) and (openapi_path.parts == response_path.parts):
if (len(path_params) == 0) and (openapi_parts == response_parts):
return PathDescription(
path=str(response_path), path_parameters=list(path_params.values())
)
path_param_indices: tuple[int, ...] = tuple(
openapi_path.parts.index("{" + name + "}") for name in path_params
openapi_parts.index("{" + name + "}") for name in path_params
)
if tuple(
elm
for ii, elm in enumerate(openapi_path.parts)
if ii not in path_param_indices
elm for ii, elm in enumerate(openapi_parts) if ii not in path_param_indices
) != tuple(
elm
for ii, elm in enumerate(response_path.parts)
if ii not in path_param_indices
elm for ii, elm in enumerate(response_parts) if ii not in path_param_indices
):
continue
path_param_indices_iter = iter(path_param_indices)
for key in path_params:
ii = next(path_param_indices_iter)
path_params[key].response_value = unquote(response_path.parts[ii])
return PathDescription(
path=str(openapi_path),
path=p,
path_parameters=list(path_params.values()),
)
raise PathNotInOpenApiSpecification(
Expand Down
45 changes: 45 additions & 0 deletions services/api-server/tests/mocks/get_job_wallet_found.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[
{
"name": "GET /projects/87643648-3a38-44e2-9cfe-d86ab3d50629/wallet",
"description": "<Request('GET', 'http://webserver:8080/v0/projects/87643648-3a38-44e2-9cfe-d86ab3d50629/wallet')>",
"method": "GET",
"host": "webserver",
"path": {
"path": "/v0/projects/{project_id}/wallet",
"path_parameters": [
{
"in_": "path",
"name": "project_id",
"required": true,
"schema_": {
"title": "Project Id",
"type_": "str",
"pattern": null,
"format_": "uuid",
"exclusiveMinimum": null,
"minimum": null,
"anyOf": null,
"allOf": null,
"oneOf": null
},
"response_value": "projects"
}
]
},
"query": null,
"request_payload": null,
"response_body": {
"data": {
"walletId": 1,
"name": "my_wallet",
"description": "my awesome wallet",
"owner": 3,
"thumbnail": "string",
"status": "ACTIVE",
"created": "2023-10-10T13:58:20.381826+00:00",
"modified": "2023-10-10T13:58:20.381826+00:00"
}
},
"status_code": 200
}
]
55 changes: 55 additions & 0 deletions services/api-server/tests/mocks/get_job_wallet_not_found.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
[
{
"name": "GET /projects/87643648-3a38-44e2-9cfe-d86ac3d50629/wallet",
"description": "<Request('GET', 'http://webserver:8080/v0/projects/87643648-3a38-44e2-9cfe-d86ac3d50629/wallet')>",
"method": "GET",
"host": "webserver",
"path": {
"path": "/v0/projects/{project_id}/wallet",
"path_parameters": [
{
"in_": "path",
"name": "project_id",
"required": true,
"schema_": {
"title": "Project Id",
"type_": "str",
"pattern": null,
"format_": "uuid",
"exclusiveMinimum": null,
"minimum": null,
"anyOf": null,
"allOf": null,
"oneOf": null
},
"response_value": "87643648-3a38-44e2-9cfe-d86ac3d50629"
}
]
},
"query": null,
"request_payload": null,
"response_body": {
"data": null,
"error": {
"logs": [
{
"message": "Project with uuid 87643648-3a38-44e2-9cfe-d86ac3d50629 not found.",
"level": "ERROR",
"logger": "user"
}
],
"errors": [
{
"code": "HTTPNotFound",
"message": "Project with uuid 87643648-3a38-44e2-9cfe-d86ac3d50629 not found.",
"resource": null,
"field": null
}
],
"status": 404,
"message": "Project with uuid 87643648-3a38-44e2-9cfe-d86ac3d50629 not found."
}
},
"status_code": 404
}
]
55 changes: 55 additions & 0 deletions services/api-server/tests/mocks/get_wallet_failure.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
[
{
"name": "GET /wallets/8",
"description": "<Request('GET', 'http://webserver:8080/v0/wallets/8')>",
"method": "GET",
"host": "webserver",
"path": {
"path": "/v0/wallets/{wallet_id}",
"path_parameters": [
{
"in_": "path",
"name": "wallet_id",
"required": true,
"schema_": {
"title": "Wallet Id",
"type_": "int",
"pattern": null,
"format_": null,
"exclusiveMinimum": true,
"minimum": 0,
"anyOf": null,
"allOf": null,
"oneOf": null
},
"response_value": "wallets"
}
]
},
"query": null,
"request_payload": null,
"response_body": {
"data": null,
"error": {
"logs": [
{
"message": "Wallet access forbidden. User does not have access to the wallet 8. Or wallet does not exist.",
"level": "ERROR",
"logger": "user"
}
],
"errors": [
{
"code": "HTTPForbidden",
"message": "Wallet access forbidden. User does not have access to the wallet 8. Or wallet does not exist.",
"resource": null,
"field": null
}
],
"status": 403,
"message": "Wallet access forbidden. User does not have access to the wallet 8. Or wallet does not exist."
}
},
"status_code": 403
}
]
Loading

0 comments on commit 0901949

Please sign in to comment.