Skip to content

Commit

Permalink
Merge pull request #46 from goat-community/feature/sharing
Browse files Browse the repository at this point in the history
pre-release: v2
  • Loading branch information
majkshkurti authored Sep 17, 2024
2 parents 1eef8ff + 76500b9 commit d6eacb3
Show file tree
Hide file tree
Showing 162 changed files with 1,565 additions and 12,485 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ morecantile = "^2.1.4"
cppimport = "^21.3.7"
pandas = "^2.0.2"
pytest = "^7.1.1"
sentry-sdk = "^1.5.0"
alembic_utils = "^0.7.4"
rich = "^11.0.0"
sqlmodel = "^0.0.8"
Expand Down Expand Up @@ -96,6 +95,7 @@ pymgl = [
matplotlib = "^3.8.2"
cairosvg = "^2.7.1"

sentry-sdk = {extras = ["fastapi"], version = "^2.14.0"}
[tool.poetry.group.dev.dependencies]
sqlalchemy-stubs = "^0.3"
debugpy = "^1.4.1"
Expand Down
5 changes: 4 additions & 1 deletion src/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class SyncPostgresDsn(PostgresDsn):


class Settings(BaseSettings):
AUTH: Optional[bool] = False
TEST_MODE: Optional[bool] = False
ENVIRONMENT: Optional[str] = "dev"
API_V2_STR: str = "/api/v2"
Expand All @@ -23,6 +24,7 @@ class Settings(BaseSettings):
PROJECT_NAME: Optional[str] = "GOAT Core API"
USER_DATA_SCHEMA: Optional[str] = "user_data"
CUSTOMER_SCHEMA: Optional[str] = "customer"
ACCOUNTS_SCHEMA: Optional[str] = "accounts"
REGION_MAPPING_PT_TABLE: Optional[str] = "basic.region_mapping_pt"
BASE_STREET_NETWORK: Optional[UUID] = "903ecdca-b717-48db-bbce-0219e41439cf"
STREET_NETWORK_EDGE_DEFAULT_LAYER_PROJECT_ID = 36126
Expand Down Expand Up @@ -117,6 +119,8 @@ def goat_routing_authorization(
return None

SAMPLE_AUTHORIZATION = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0OG80Z1JXelh3YXBTY3NTdHdTMXZvREFJRlNOa0NtSVFpaDhzcEJTc2kwIn0.eyJleHAiOjE2OTEwMDQ1NTYsImlhdCI6MTY5MTAwNDQ5NiwiYXV0aF90aW1lIjoxNjkxMDAyNjIzLCJqdGkiOiI1MjBiN2RhNC0xYmM0LTRiM2QtODY2ZC00NDU0ODY2YThiYjIiLCJpc3MiOiJodHRwczovL2Rldi5hdXRoLnBsYW40YmV0dGVyLmRlL3JlYWxtcy9tYXN0ZXIiLCJzdWIiOiI3NDRlNGZkMS02ODVjLTQ5NWMtOGIwMi1lZmViY2U4NzUzNTkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJzZWN1cml0eS1hZG1pbi1jb25zb2xlIiwibm9uY2UiOiJjNGIzMDQ3Yi0xODVmLTQyOWEtOGZlNS1lNDliNTVhMzE3MzIiLCJzZXNzaW9uX3N0YXRlIjoiMzk5ZTc2NWMtYjM1MC00NDEwLTg4YTMtYjU5NDIyMmJkZDlhIiwiYWNyIjoiMCIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwczovL2Rldi5hdXRoLnBsYW40YmV0dGVyLmRlIl0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJzaWQiOiIzOTllNzY1Yy1iMzUwLTQ0MTAtODhhMy1iNTk0MjIyYmRkOWEiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InA0YiJ9.mjywr9Dv19egsXwM1fK6g3sZ0trk87X0tEfK7oOizuBuCdkr6PZN1Eg58FCdjIgEBXqjltOWV43UIkXde4iPVa-KU5Q34Qjv6w0STa3Aq9vFbaUfSm_690qCdr8XSKMJUWQXWYwD2cjck5UCqf7-QqsF2Ab56i40_CJLZkJOi25WKIC855qPDi8BkJgh5eWoxobdyCbwJMEeoM-3QnxY5ikib5a2_AASEN3_5MYmT6-fvpW2t-MS6u4vtcG-WfqriK8YNoGPS2a1pFjLqQLHkM__j0O_t4wXP56x9yjkUdHCXqVcSlDvZYNWrv5CLqecqjOoliNMs6RTu9gV0Gr-cA"
KEYCLOAK_SERVER_URL: Optional[str] = "http://auth-keycloak:8080"
REALM_NAME: Optional[str] = "p4b"
CELERY_TASK_TIME_LIMIT: Optional[int] = 60 # seconds
RUN_AS_BACKGROUND_TASK: Optional[bool] = True
MAX_NUMBER_PARALLEL_JOBS: Optional[int] = 6
Expand Down Expand Up @@ -177,4 +181,3 @@ class Config:


settings = Settings()
settings = Settings()
325 changes: 324 additions & 1 deletion src/core/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
from sqlmodel import SQLModel
from src.db.session import AsyncSession
from src.schemas.common import ContentIdList
from sqlalchemy import select, and_, or_
from sqlalchemy.orm import contains_eager, selectinload
from src.db.models import User


### Generic helper functions for content
Expand Down Expand Up @@ -52,7 +55,9 @@ async def read_contents_by_ids(
"""Read contents by their IDs."""
# Read contents by IDs
query = select(model).where(model.id.in_(ids.ids))
contents = await crud_content.get_multi(async_session, query=query, page_params=page_params)
contents = await crud_content.get_multi(
async_session, query=query, page_params=page_params
)

# Check if all contents were found
if len(contents.items) != len(ids.ids):
Expand Down Expand Up @@ -100,3 +105,321 @@ async def delete_content_by_id(
)
await crud_content.remove(async_session, id=id)
return


# def create_query_shared_content(
# model,
# team_link_model,
# organization_link_model,
# team_model,
# organization_model,
# role_model,
# filters,
# team_id=None,
# organization_id=None,
# ):
# """
# Creates a dynamic query for a given model (Layer or Project) and its associated team, organization, and owner user.

# :param model: The main model (Layer or Project)
# :param team_link_model: The model linking the main model with teams (LayerTeamLink or ProjectTeamLink)
# :param organization_link_model: The model linking the main model with organizations (LayerOrganizationLink or ProjectOrganizationLink)
# :param team_model: The Team model
# :param organization_model: The Organization model
# :param role_model: The Role model
# :param filters: Additional filters to apply
# :param team_id: ID of the team (optional)
# :param organization_id: ID of the organization (optional)
# :return: A SQLAlchemy query object
# """

# # Determine the link field based on the model
# link_field = f"{model.__tablename__}_id"

# # Basic query to join the User who owns the Layer or Project
# base_query = select(
# model,
# role_model.id.label("valid_role_id"),
# team_model.name,
# team_model.id,
# team_model.avatar,
# User.firstname.label("user_firstname"),
# User.lastname.label("user_lastname"),
# User.avatar.label("user_avatar"),
# ).join(
# User, model.user_id == User.id # Join on owner_id field with User model
# )

# if team_id:
# query = (
# base_query
# .join(
# team_link_model, getattr(team_link_model, link_field) == model.id
# ) # Dynamically replace `layer_id` or `project_id`
# .join(role_model, team_link_model.role_id == role_model.id)
# .join(team_model, team_link_model.team_id == team_model.id)
# .where(
# and_(
# team_link_model.team_id == team_id,
# *filters,
# )
# )
# .options(
# contains_eager(getattr(model, "team_links"))
# ) # Adjust field as needed for relationships
# )
# elif organization_id:
# query = (
# base_query
# .join(
# organization_link_model,
# getattr(organization_link_model, link_field) == model.id,
# ) # Dynamically replace `layer_id` or `project_id`
# .join(role_model, organization_link_model.role_id == role_model.id)
# .join(
# organization_model,
# organization_link_model.organization_id == organization_model.id,
# )
# .where(
# and_(
# organization_link_model.organization_id == organization_id,
# *filters,
# )
# )
# .options(
# contains_eager(getattr(model, "organization_links"))
# ) # Adjust field as needed for relationships
# )
# else:
# query = (
# base_query
# .outerjoin(
# team_link_model, getattr(team_link_model, link_field) == model.id
# ) # Dynamically replace `layer_id` or `project_id`
# .outerjoin(
# organization_link_model,
# getattr(organization_link_model, link_field) == model.id,
# ) # Dynamically replace `layer_id` or `project_id`
# .where(and_(*filters))
# .options(
# selectinload(getattr(model, "team_links")).selectinload(
# getattr(team_link_model, "team")
# ), # Adjust fields as needed
# selectinload(getattr(model, "organization_links")).selectinload(
# getattr(organization_link_model, "organization")
# ), # Adjust fields as needed
# )
# )


def create_query_shared_content(
model,
team_link_model,
organization_link_model,
team_model,
organization_model,
role_model,
filters,
team_id=None,
organization_id=None,
):
"""
Creates a dynamic query for a given model (Layer or Project) and its associated team, organization, and owner user.
:param model: The main model (Layer or Project)
:param team_link_model: The model linking the main model with teams (LayerTeamLink or ProjectTeamLink)
:param organization_link_model: The model linking the main model with organizations (LayerOrganizationLink or ProjectOrganizationLink)
:param team_model: The Team model
:param organization_model: The Organization model
:param role_model: The Role model
:param filters: Additional filters to apply
:param team_id: ID of the team (optional)
:param organization_id: ID of the organization (optional)
:return: A SQLAlchemy query object
"""

# Determine the link field based on the model
link_field = f"{model.__tablename__}_id"

# Get team or layer model
if team_id:
read_column = [
team_model.name.label("team_name"),
team_model.id,
team_model.avatar.label("team_avatar"),
]
elif organization_id:
read_column = [
organization_model.name.label("team_name"),
organization_model.id,
organization_model.avatar.label("team_avatar"),
]
else:
read_column = []

# Basic query to join the User who owns the Layer or Project
base_query = select(
model,
role_model.id.label("valid_role_id"),
User.id.label("valid_user_id"),
User.firstname.label("user_firstname"),
User.lastname.label("user_lastname"),
User.avatar.label("user_avatar"),
*read_column,
).join(
User, model.user_id == User.id # Join on owner_id field with User model
)

if team_id:
query = (
base_query.join(
team_link_model, getattr(team_link_model, link_field) == model.id
) # Dynamically replace `layer_id` or `project_id`
.join(role_model, team_link_model.role_id == role_model.id)
.join(team_model, team_link_model.team_id == team_model.id)
.where(
and_(
team_link_model.team_id == team_id,
*filters,
)
)
.options(
contains_eager(getattr(model, "team_links"))
) # Adjust field as needed for relationships
)
elif organization_id:
query = (
base_query.join(
organization_link_model,
getattr(organization_link_model, link_field) == model.id,
) # Dynamically replace `layer_id` or `project_id`
.join(role_model, organization_link_model.role_id == role_model.id)
.join(
organization_model,
organization_link_model.organization_id == organization_model.id,
)
.where(
and_(
organization_link_model.organization_id == organization_id,
*filters,
)
)
.options(
contains_eager(getattr(model, "organization_links"))
) # Adjust field as needed for relationships
)
else:
# Query for the case with no team_id or organization_id
query = (
base_query.outerjoin(
team_link_model, getattr(team_link_model, link_field) == model.id
) # Outer join for team_link_model
.outerjoin(
team_model, team_link_model.team_id == team_model.id
) # Outer join for team_model
.outerjoin(
role_model, team_link_model.role_id == role_model.id
) # Outer join for role_model
.outerjoin(
organization_link_model,
getattr(organization_link_model, link_field) == model.id,
) # Outer join for organization_link_model
.outerjoin(
organization_model,
organization_link_model.organization_id == organization_model.id,
) # Outer join for organization_model
.where(and_(*filters))
.options(
selectinload(getattr(model, "team_links")).selectinload(
team_link_model.team
), # Preload team links and corresponding teams
selectinload(getattr(model, "organization_links")).selectinload(
organization_link_model.organization
), # Preload organization links and corresponding organizations
)
)
return query

#TODO: Make a pydantic schema for shared_with and owned_by
def build_shared_with_object(
items,
role_mapping,
team_key="team_links",
org_key="organization_links",
model_name="layer",
team_id=None,
organization_id=None,
):
"""
Builds the shared_with object for both Layer and Project models.
:param items: The list of Layer or Project items
:param role_mapping: The mapping of role IDs to role names
:param team_key: The attribute name for team links (default is "team_links")
:param org_key: The attribute name for organization links (default is "organization_links")
:param model_name: The name of the model ("layer" or "project")
:param team_id: Optional ID for team-specific sharing
:param organization_id: Optional ID for organization-specific sharing
:return: A list of dictionaries containing the model and the shared_with data
"""

def get_owned_by(item):
"""Helper function to build the 'owned_by' dictionary."""
return {
"id": item[2],
"firstname": item[3],
"lastname": item[4],
"avatar": item[5],
}

def process_links(item, link_key, link_type):
"""Helper function to process either team or organization links."""
shared_with = []
links = getattr(item, link_key, None)
if not links:
return shared_with
for link in links:
shared_with.append(
{
"role": role_mapping[link.role_id], # Role based on role_mapping
"id": getattr(link, link_type).id,
"name": getattr(link, link_type).name,
"avatar": getattr(link, link_type).avatar,
}
)
return shared_with

result_arr = []

# Determine shared_with key
shared_with_key = (
"teams" if team_id else "organizations" if organization_id else None
)

for item in items:
if team_id or organization_id:
# Case where shared_with is for a specific team or organization
shared_with = {
shared_with_key: [
{
"role": role_mapping[item[1]], # Role name
"id": item[3], # Team or Organization ID
"name": item[2], # Team or Organization name
"avatar": item[4], # Team or Organization avatar
}
]
}
else:
# Case where shared_with includes both teams and organizations
shared_with = {
"teams": process_links(item[0], team_key, "team"),
"organizations": process_links(item[0], org_key, "organization"),
}

# Add owned_by information
owned_by = get_owned_by(item)
result_arr.append(
{**item[0].dict(), "shared_with": shared_with, "owned_by": owned_by}
)

return result_arr
Loading

0 comments on commit d6eacb3

Please sign in to comment.