Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
14 changes: 12 additions & 2 deletions api/specs/web-server/_products.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
from models_library.api_schemas_webserver.product import (
GenerateInvitation,
GetCreditPrice,
GetProduct,
InvitationGenerated,
ProductGet,
ProductUIGet,
UpdateProductTemplate,
)
from models_library.generics import Envelope
Expand Down Expand Up @@ -40,7 +41,8 @@ async def get_current_product_price():

@router.get(
"/products/{product_name}",
response_model=Envelope[GetProduct],
response_model=Envelope[ProductGet],
description="NOTE: `/products/current` is used to define current project w/o naming it",
tags=[
"po",
],
Expand All @@ -49,6 +51,14 @@ async def get_product(_params: Annotated[_ProductsRequestParams, Depends()]):
...


@router.get(
"/products/current/ui",
response_model=Envelope[ProductUIGet],
)
async def get_current_product_ui():
...


@router.put(
"/products/{product_name}/templates/{template_id}",
status_code=status.HTTP_204_NO_CONTENT,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from datetime import datetime
from typing import Annotated, TypeAlias
from typing import Annotated, Any, TypeAlias

from common_library.basic_types import DEFAULT_FACTORY
from pydantic import (
ConfigDict,
Field,
Expand All @@ -10,6 +11,7 @@
PlainSerializer,
PositiveInt,
)
from pydantic.config import JsonDict

from ..basic_types import IDStr, NonNegativeDecimal
from ..emails import LowerCaseEmailStr
Expand All @@ -27,64 +29,83 @@ class GetCreditPrice(OutputSchema):
description="Price of a credit in USD. "
"If None, then this product's price is UNDEFINED",
)
min_payment_amount_usd: NonNegativeInt | None = Field(
...,
description="Minimum amount (included) in USD that can be paid for this product"
"Can be None if this product's price is UNDEFINED",
)
min_payment_amount_usd: Annotated[
NonNegativeInt | None,
Field(
description="Minimum amount (included) in USD that can be paid for this product"
"Can be None if this product's price is UNDEFINED",
),
]

@staticmethod
def _update_json_schema_extra(schema: JsonDict) -> None:
schema.update(
{
"examples": [
{
"productName": "osparc",
"usdPerCredit": None,
"minPaymentAmountUsd": None,
},
{
"productName": "osparc",
"usdPerCredit": "10",
"minPaymentAmountUsd": "10",
},
]
}
)

model_config = ConfigDict(
json_schema_extra={
"examples": [
{
"productName": "osparc",
"usdPerCredit": None,
"minPaymentAmountUsd": None,
},
{
"productName": "osparc",
"usdPerCredit": "10",
"minPaymentAmountUsd": "10",
},
]
}
json_schema_extra=_update_json_schema_extra,
)


class GetProductTemplate(OutputSchema):
id_: IDStr = Field(..., alias="id")
id_: Annotated[IDStr, Field(alias="id")]
content: str


class UpdateProductTemplate(InputSchema):
content: str


class GetProduct(OutputSchema):
class ProductGet(OutputSchema):
name: ProductName
display_name: str
short_name: str | None = Field(
default=None, description="Short display name for SMS"
)

vendor: dict | None = Field(default=None, description="vendor attributes")
issues: list[dict] | None = Field(
default=None, description="Reference to issues tracker"
)
manuals: list[dict] | None = Field(default=None, description="List of manuals")
support: list[dict] | None = Field(
default=None, description="List of support resources"
)
short_name: Annotated[
str | None, Field(description="Short display name for SMS")
] = None

vendor: Annotated[dict | None, Field(description="vendor attributes")] = None
issues: Annotated[
list[dict] | None, Field(description="Reference to issues tracker")
] = None
manuals: Annotated[list[dict] | None, Field(description="List of manuals")] = None
support: Annotated[
list[dict] | None, Field(description="List of support resources")
] = None

login_settings: dict
max_open_studies_per_user: PositiveInt | None
is_payment_enabled: bool
credits_per_usd: NonNegativeDecimal | None

templates: list[GetProductTemplate] = Field(
default_factory=list,
description="List of templates available to this product for communications (e.g. emails, sms, etc)",
)
templates: Annotated[
list[GetProductTemplate],
Field(
description="List of templates available to this product for communications (e.g. emails, sms, etc)",
default_factory=list,
),
] = DEFAULT_FACTORY


class ProductUIGet(OutputSchema):
product_name: ProductName
ui: Annotated[
dict[str, Any],
Field(description="Front-end owned ui product configuration"),
]


ExtraCreditsUsdRangeInt: TypeAlias = Annotated[int, Field(ge=0, lt=500)]
Expand All @@ -105,26 +126,32 @@ class InvitationGenerated(OutputSchema):
created: datetime
invitation_link: HttpUrl

@staticmethod
def _update_json_schema_extra(schema: JsonDict) -> None:
schema.update(
{
"examples": [
{
"productName": "osparc",
"issuer": "john.doe",
"guest": "[email protected]",
"trialAccountDays": 7,
"extraCreditsInUsd": 30,
"created": "2023-09-27T15:30:00",
"invitationLink": "https://example.com/invitation#1234",
},
# w/o optional
{
"productName": "osparc",
"issuer": "[email protected]",
"guest": "[email protected]",
"created": "2023-09-27T15:30:00",
"invitationLink": "https://example.com/invitation#1234",
},
]
}
)

model_config = ConfigDict(
json_schema_extra={
"examples": [
{
"productName": "osparc",
"issuer": "john.doe",
"guest": "[email protected]",
"trialAccountDays": 7,
"extraCreditsInUsd": 30,
"created": "2023-09-27T15:30:00",
"invitationLink": "https://example.com/invitation#1234",
},
# w/o optional
{
"productName": "osparc",
"issuer": "[email protected]",
"guest": "[email protected]",
"created": "2023-09-27T15:30:00",
"invitationLink": "https://example.com/invitation#1234",
},
]
}
json_schema_extra=_update_json_schema_extra,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""new products ui column

Revision ID: 78f24aaf3f78
Revises: 68777fdf9539
Create Date: 2025-02-12 16:06:09.815111+00:00

"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = "78f24aaf3f78"
down_revision = "68777fdf9539"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"products",
sa.Column(
"ui",
postgresql.JSONB(astext_type=sa.Text()),
server_default=sa.text("'{}'::jsonb"),
nullable=False,
),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("products", "ui")
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ class ProductLoginSettingsDict(TypedDict, total=False):
nullable=False,
doc="Regular expression that matches product hostname from an url string",
),
# EMAILS --------------------
sa.Column(
"support_email",
sa.String,
Expand Down Expand Up @@ -200,6 +201,13 @@ class ProductLoginSettingsDict(TypedDict, total=False):
doc="Overrides simcore_service_webserver.login.settings.LoginSettings."
"SEE LoginSettingsForProduct",
),
sa.Column(
"ui",
JSONB,
nullable=False,
server_default=sa.text("'{}'::jsonb"),
doc="Front-end owned UI configuration",
),
sa.Column(
"registration_email_template",
sa.String,
Expand All @@ -212,6 +220,7 @@ class ProductLoginSettingsDict(TypedDict, total=False):
nullable=True,
doc="Custom jinja2 template for registration email",
),
# lifecycle
sa.Column(
"created",
sa.DateTime(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,24 @@ def random_product(
"group_id": group_id,
}

if ui := fake.random_element(
[
None,
# Examples from https://github.com/itisfoundation/osparc-simcore/blob/1dcd369717959348099cc6241822a1f0aff0382c/services/static-webserver/client/source/resource/osparc/new_studies.json
{
"categories": [
{"id": "precomputed", "title": "Precomputed"},
{
"id": "personalized",
"title": "Personalized",
"description": fake.sentence(),
},
]
},
]
):
data.update(ui=ui)

assert set(data.keys()).issubset({c.name for c in products.columns})
data.update(overrides)
return data
Expand Down
2 changes: 1 addition & 1 deletion services/web/server/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.56.0
0.57.0
2 changes: 1 addition & 1 deletion services/web/server/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.56.0
current_version = 0.57.0
commit = True
message = services/webserver api version: {current_version} → {new_version}
tag = False
Expand Down
Loading
Loading