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
33 changes: 17 additions & 16 deletions backend/api/inputs.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -151,6 +151,12 @@ class UpdateLocationNodeInput:
parent_id: strawberry.ID | None = None


@strawberry.enum
class ScopeVisibility(Enum):
PRIVATE = "private"
PUBLIC = "public"


@strawberry.input
class CreatePropertyDefinitionInput:
name: str
Expand All@@ -159,6 +165,7 @@ class CreatePropertyDefinitionInput:
description: str | None = None
options: list[str] | None = None
is_active: bool = True
visibility: ScopeVisibility = ScopeVisibility.PRIVATE
location_id: strawberry.ID | None = None


Expand All@@ -169,6 +176,8 @@ class UpdatePropertyDefinitionInput:
options: list[str] | None = None
is_active: bool | None = None
allowed_entities: list[PropertyEntity] | None = None
visibility: ScopeVisibility | None = None
location_id: strawberry.ID | None = None


@strawberry.input
Expand All@@ -194,12 +203,6 @@ class SavedViewEntityType(Enum):
PATIENT = "patient"


@strawberry.enum
class SavedViewVisibility(Enum):
PRIVATE = "private"
LINK_SHARED = "link_shared"


@strawberry.input
class CreateSavedViewInput:
name: str
Expand All@@ -210,7 +213,7 @@ class CreateSavedViewInput:
related_filter_definition: str = "{}"
related_sort_definition: str = "{}"
related_parameters: str = "{}"
visibility: SavedViewVisibility = SavedViewVisibility.LINK_SHARED
visibility: ScopeVisibility = ScopeVisibility.PRIVATE
location_id: strawberry.ID | None = None


Expand All@@ -223,13 +226,8 @@ class UpdateSavedViewInput:
related_filter_definition: str | None = None
related_sort_definition: str | None = None
related_parameters: str | None = None
visibility: SavedViewVisibility | None = None


@strawberry.enum
class TaskPresetScope(Enum):
PERSONAL = "PERSONAL"
GLOBAL = "GLOBAL"
visibility: ScopeVisibility | None = None
location_id: strawberry.ID | None = None


@strawberry.input
Expand All@@ -256,16 +254,19 @@ class TaskGraphInput:
@strawberry.input
class CreateTaskPresetInput:
name: str
key: str | None = None
scope: TaskPresetScope
graph: TaskGraphInput
key: str | None = None
visibility: ScopeVisibility = ScopeVisibility.PRIVATE
location_id: strawberry.ID | None = None


@strawberry.input
class UpdateTaskPresetInput:
name: str | None = None
key: str | None = None
graph: TaskGraphInput | None = None
visibility: ScopeVisibility | None = None
location_id: strawberry.ID | None = None


@strawberry.input
Expand Down
65 changes: 27 additions & 38 deletions backend/api/resolvers/property.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,10 +7,18 @@
)
from api.resolvers.base import BaseMutationResolver
from api.services.authorization import AuthorizationService
from api.services.scope import (
apply_scope_update,
can_manage_property_definition,
can_read_scoped,
normalize_root_location_ids,
resolve_scope_input,
scoped_visibility_condition,
)
from api.types.property import PropertyDefinitionType
from database import models
from graphql import GraphQLError
from sqlalchemy import or_, select
from sqlalchemy import select


def _require_user(info: Info) -> models.User:
Expand All@@ -26,19 +34,17 @@ class PropertyDefinitionQuery:
async def property_definitions(
self,
info: Info,
root_location_ids: list[strawberry.ID] | None = None,
) -> list[PropertyDefinitionType]:
user = _require_user(info)
auth_service = AuthorizationService(info.context.db)
accessible = await auth_service.get_user_accessible_location_ids(
user, info.context
scope = await auth_service.get_scope_location_ids(
user, info.context, normalize_root_location_ids(root_location_ids)
)
conditions = [models.PropertyDefinition.location_id.is_(None)]
if accessible:
conditions.append(
models.PropertyDefinition.location_id.in_(accessible)
)
result = await info.context.db.execute(
select(models.PropertyDefinition).where(or_(*conditions)),
select(models.PropertyDefinition).where(
scoped_visibility_condition(models.PropertyDefinition, user.id, scope)
),
)
return result.scalars().all()

Expand All@@ -56,22 +62,9 @@ async def create_property_definition(
data: CreatePropertyDefinitionInput,
) -> PropertyDefinitionType:
user = _require_user(info)
auth_service = AuthorizationService(info.context.db)

if data.location_id is not None:
if not await auth_service.can_access_location(
user, str(data.location_id), info.context
):
raise_forbidden()
location_id = str(data.location_id)
else:
location_id = await auth_service.default_scope_location_id(
user, info.context
)
if location_id is None:
raise_forbidden(
"You must belong to a location to create property definitions."
)
visibility, location_id = await resolve_scope_input(
info, user, data.visibility, data.location_id
)

entities_str = ",".join([e.value for e in data.allowed_entities])
options_str = ",".join(data.options) if data.options else None
Expand All@@ -83,6 +76,8 @@ async def create_property_definition(
options=options_str,
is_active=data.is_active,
allowed_entities=entities_str,
visibility=visibility,
owner_user_id=user.id,
location_id=location_id,
)
return await BaseMutationResolver.create_and_notify(
Expand DownExpand Up@@ -116,6 +111,7 @@ async def update_property_definition(
defn.allowed_entities = ",".join(
[e.value for e in data.allowed_entities],
)
await apply_scope_update(info, user, defn, data.visibility, data.location_id)

return await BaseMutationResolver.update_and_notify(
info, defn, models.PropertyDefinition, "property_definition"
Expand DownExpand Up@@ -146,15 +142,12 @@ async def _require_definition_scope(
user: models.User,
defn: models.PropertyDefinition,
) -> None:
if defn.location_id is None:
if defn.visibility != "private" and defn.location_id is None:
raise_forbidden(
"This property definition is global and cannot be modified. "
"Recreate it within a location to manage it."
)
auth_service = AuthorizationService(info.context.db)
if not await auth_service.can_access_location(
user, defn.location_id, info.context
):
if not await can_manage_property_definition(info, user, defn):
raise_forbidden()


Expand All@@ -167,21 +160,17 @@ async def user_can_use_definition(
return False
db = info.context.db
result = await db.execute(
select(models.PropertyDefinition.location_id).where(
select(models.PropertyDefinition).where(
models.PropertyDefinition.id == str(definition_id),
)
)
row = result.first()
if row is None:
defn = result.scalars().first()
if defn is None:
raise GraphQLError(
"Property definition not found.",
extensions={"code": "BAD_REQUEST"},
)
location_id = row[0]
if location_id is None:
return True
auth_service = AuthorizationService(db)
return await auth_service.can_access_location(user, location_id, info.context)
return await can_read_scoped(info, user, defn)


async def validate_property_value_inputs(info: Info, props) -> None:
Expand Down
Loading
Loading