Skip to content

Users search improvments #270

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

Merged
merged 11 commits into from
Nov 21, 2024
Merged
39 changes: 39 additions & 0 deletions alembic/versions/7aa933ec5de9_kratos_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Kratos data

Revision ID: 7aa933ec5de9
Revises: 05bbef1eec3f
Create Date: 2024-11-19 16:04:00.539685

"""

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = "7aa933ec5de9"
down_revision = "05bbef1eec3f"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("user", sa.Column("email", sa.String(), nullable=True))
op.add_column("user", sa.Column("verified", sa.Boolean(), nullable=True))
op.add_column("user", sa.Column("created_at", sa.DateTime(), nullable=True))
op.add_column("user", sa.Column("metadata_public", sa.JSON(), nullable=True))
op.add_column("user", sa.Column("sso_provider", sa.String(), nullable=True))
op.create_unique_constraint(None, "user", ["email"])
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, "user", type_="unique")
op.drop_column("user", "sso_provider")
op.drop_column("user", "metadata_public")
op.drop_column("user", "created_at")
op.drop_column("user", "verified")
op.drop_column("user", "email")
# ### end Alembic commands ###
2 changes: 2 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
from starlette.routing import Route, Mount

from controller.project.manager import check_in_deletion_projects
from controller.user.manager import migrate_kratos_users
from route_prefix import (
PREFIX_ORGANIZATION,
PREFIX_PROJECT,
Expand Down Expand Up @@ -70,6 +71,7 @@
logger = logging.getLogger(__name__)

init_config()
migrate_kratos_users()
fastapi_app = FastAPI()


Expand Down
6 changes: 3 additions & 3 deletions controller/auth/kratos.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
KRATOS_IDENTITY_CACHE_TIMEOUT = timedelta(minutes=30)


def __get_cached_values() -> Dict[str, Dict[str, Any]]:
def get_cached_values() -> Dict[str, Dict[str, Any]]:
global KRATOS_IDENTITY_CACHE
if not KRATOS_IDENTITY_CACHE or len(KRATOS_IDENTITY_CACHE) == 0:
__refresh_identity_cache()
Expand Down Expand Up @@ -71,7 +71,7 @@ def __get_link_from_kratos_request(request: requests.Response) -> str:
def __get_identity(user_id: str, only_simple: bool = True) -> Dict[str, Any]:
if not isinstance(user_id, str):
user_id = str(user_id)
cache = __get_cached_values()
cache = get_cached_values()
if user_id in cache:
if only_simple:
return cache[user_id]["simple"]
Expand Down Expand Up @@ -117,7 +117,7 @@ def __parse_identity_to_simple(identity: Dict[str, Any]) -> Dict[str, str]:


def get_userid_from_mail(user_mail: str) -> str:
values = __get_cached_values()
values = get_cached_values()
for key in values:
if key == "collected":
continue
Expand Down
92 changes: 45 additions & 47 deletions controller/user/manager.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Any, Dict, List
from submodules.model import User, enums
from submodules.model import User, daemon, enums
from submodules.model.business_objects import user, user_activity, general
from controller.auth import kratos
from submodules.model.exceptions import EntityNotFoundException
Expand All @@ -20,6 +20,7 @@ def get_or_create_user(user_id: str) -> User:
if not user_item:
user_item = user.create(user_id, with_commit=True)
kratos.__refresh_identity_cache()
migrate_kratos_users()
update_last_interaction(user_item.id)
return user_item

Expand Down Expand Up @@ -104,53 +105,50 @@ def update_last_interaction(user_id: str) -> None:
user_activity.update_last_interaction(user_id)


def get_mapped_sorted_paginated_users(
active_users: Dict[str, Any],
sort_key: str,
sort_direction: int,
offset: int,
limit: int,
) -> List[Dict[str, Any]]:

final_users = []
save_len_final_users = 0

# mapping users with the users in kratos
active_users_ids = list(active_users.keys())

for user_id in active_users_ids:
get_user = kratos.__get_identity(user_id, False)["identity"]
if get_user and get_user["traits"]["email"] is not None:
get_user["email"] = get_user["traits"]["email"]
get_user["verified"] = get_user["verifiable_addresses"][0]["verified"]
active_user_by_id = active_users[user_id]
get_user["last_interaction"] = active_user_by_id["last_interaction"]
get_user["role"] = active_user_by_id["role"]
get_user["organization"] = active_user_by_id["organizationName"]

public_meta = get_user["metadata_public"]
get_user["sso_provider"] = (
public_meta.get("registration_scope", {}).get("provider_id", None)
if public_meta
else None
)

final_users.append(get_user)
save_len_final_users += 1

final_users = sorted(
final_users,
key=lambda x: (x[sort_key] is None, x.get(sort_key, "")),
reverse=sort_direction == -1,
)

# paginating users
final_users = final_users[offset : offset + limit]

return final_users, save_len_final_users


def delete_user(user_id: str) -> None:
user.delete(user_id, with_commit=True)
user_activity.delete_user_activity(user_id, with_commit=True)
kratos.__refresh_identity_cache()


def migrate_kratos_users() -> None:
# this is only supposed to be called during startup of the application
daemon.run_with_db_token(__migrate_kratos_users)


def __migrate_kratos_users():
users = kratos.get_cached_values()
filtered_users = {k: v for k, v in users.items() if k != "collected"}
users_database = user.get_by_id_list(list(filtered_users.keys()))
for user_database in users_database:
user_id = str(user_database.id)
user_identity = filtered_users[user_id]["identity"]
if (
user_database.email != user_identity["traits"]["email"]
or user_database.verified
!= user_identity["verifiable_addresses"][0]["verified"]
or user_database.created_at
!= user_identity["verifiable_addresses"][0]["created_at"]
or user_database.metadata_public != user_identity["metadata_public"]
or (
user_identity["metadata_public"]
.get("registration_scope", {})
.get("provider_id", None)
!= user_database.sso_provider
)
):
user.update_user(
user=user_database,
email=user_identity["traits"]["email"],
verified=user_identity["verifiable_addresses"][0]["verified"],
created_at=user_identity["verifiable_addresses"][0]["created_at"],
metadata_public=user_identity["metadata_public"],
sso_provider=(
user_identity["metadata_public"]
.get("registration_scope", {})
.get("provider_id", None)
if user_identity["metadata_public"]
else None
),
with_commit=True,
)
4 changes: 4 additions & 0 deletions fast_api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,3 +440,7 @@ class UpdateCustomerButton(BaseModel):
location: Optional[CustomerButtonLocation] = None
visible: Optional[StrictBool] = None
config: Optional[Dict[StrictStr, Any]] = None


class MissingUsersBody(BaseModel):
user_ids: List[StrictStr]
42 changes: 36 additions & 6 deletions fast_api/routes/organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
DeleteOrganizationBody,
DeleteUserBody,
MappedSortedPaginatedUsers,
MissingUsersBody,
RemoveUserToOrganizationBody,
UserLanguageDisplay,
)
Expand All @@ -24,7 +25,8 @@
from controller.user import manager as user_manager

from fast_api.routes.client_response import get_silent_success, pack_json_result
from submodules.model.business_objects import organization
from submodules.model import events
from submodules.model.business_objects import organization, user
from submodules.model.util import sql_alchemy_to_dict
from util import notification

Expand Down Expand Up @@ -281,23 +283,37 @@ def get_mapped_sorted_paginated_users(
user.last_interaction.isoformat() if user.last_interaction else None
),
"role": user.role,
"organizationName": (
"organization": (
organization_manager.get_organization_by_id(str(user.organization_id))[
"name"
]
if user.organization_id
else ""
),
"email": user.email,
"verified": user.verified,
"created_at": user.created_at.isoformat(),
"metadata_public": user.metadata_public,
"sso_provider": user.sso_provider,
}
for user in active_users
]
active_users = {user["id"]: user for user in active_users}

data, final_len = user_manager.get_mapped_sorted_paginated_users(
active_users, body.sort_key, body.sort_direction, body.offset, body.limit
final_users = []
final_users = sorted(
active_users,
key=lambda x: (x[body.sort_key] is None, x.get(body.sort_key, "")),
reverse=body.sort_direction == -1,
)

# paginating users
final_users = final_users[body.offset : body.offset + body.limit]

return pack_json_result(
{"mappedSortedPaginatedUsers": data, "fullCountUsers": final_len},
{
"mappedSortedPaginatedUsers": final_users,
"fullCountUsers": len(active_users),
},
wrap_for_frontend=False, # needed because it's used like this on the frontend (kratos values)
)

Expand All @@ -307,3 +323,17 @@ def delete_user(request: Request, body: DeleteUserBody = Body(...)):
auth_manager.check_admin_access(request.state.info)
user_manager.delete_user(body.user_id)
return get_silent_success()


@router.post("/missing-users-interaction")
def get_missing_users_interaction(request: Request, body: MissingUsersBody = Body(...)):
auth_manager.check_admin_access(request.state.info)
data = user.get_missing_users(body.user_ids)
return pack_json_result(data, wrap_for_frontend=False)


@router.get("/user-to-organization")
def get_user_to_organization(request: Request):
auth_manager.check_admin_access(request.state.info)
data = user.get_user_to_organization()
return pack_json_result(data, wrap_for_frontend=False)
2 changes: 1 addition & 1 deletion submodules/model
Submodule model updated 2 files
+54 −0 business_objects/user.py
+5 −0 models.py