Skip to content

Commit 154ed9b

Browse files
committed
draft query
1 parent d9601ec commit 154ed9b

File tree

3 files changed

+137
-1
lines changed

3 files changed

+137
-1
lines changed

services/web/server/src/simcore_service_webserver/projects/_jobs_repository.py

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,39 @@
11
import logging
2+
from typing import cast
23

4+
import sqlalchemy as sa
5+
from models_library.products import ProductName
36
from models_library.projects import ProjectID
7+
from models_library.users import UserID
8+
from pydantic import TypeAdapter
9+
from simcore_postgres_database.models.groups import user_to_groups
10+
from simcore_postgres_database.models.project_to_groups import project_to_groups
11+
from simcore_postgres_database.models.projects import projects
412
from simcore_postgres_database.models.projects_to_jobs import projects_to_jobs
5-
from simcore_postgres_database.utils_repos import transaction_context
13+
from simcore_postgres_database.models.projects_to_products import projects_to_products
14+
from simcore_postgres_database.utils_repos import (
15+
get_columns_from_db_model,
16+
pass_or_acquire_connection,
17+
transaction_context,
18+
)
619
from sqlalchemy.dialects.postgresql import insert as pg_insert
720
from sqlalchemy.ext.asyncio import AsyncConnection
821

922
from ..db.base_repository import BaseRepository
23+
from .models import ProjectDBGet, ProjectJobDBGet
1024

1125
_logger = logging.getLogger(__name__)
1226

1327

28+
_PROJECT_JOB_DB_COLS = [
29+
*get_columns_from_db_model(
30+
projects,
31+
ProjectDBGet,
32+
),
33+
projects_to_jobs.c.job_parent_resource_name, # Add job_parent_resource_name
34+
]
35+
36+
1437
class ProjectJobsRepository(BaseRepository):
1538

1639
async def set_project_as_job(
@@ -34,3 +57,87 @@ async def set_project_as_job(
3457
)
3558

3659
await conn.execute(stmt)
60+
61+
async def list_projects_marked_as_jobs(
62+
self,
63+
connection: AsyncConnection | None = None,
64+
*,
65+
product_name: ProductName,
66+
user_id: UserID,
67+
offset: int = 0,
68+
limit: int = 10,
69+
job_parent_resource_name_filter: str | None = None,
70+
) -> tuple[int, list[ProjectJobDBGet]]:
71+
"""
72+
Lists projects marked as jobs for a specific user and product
73+
"""
74+
75+
# Step 1: Get group IDs associated with the user
76+
user_groups_query = (
77+
sa.select(user_to_groups.c.gid)
78+
.where(user_to_groups.c.uid == user_id)
79+
.subquery()
80+
)
81+
82+
# Step 2: Create access_query to filter projects based on product_name and read access
83+
access_query = (
84+
sa.select(projects_to_jobs.c.project_uuid)
85+
.select_from(
86+
projects_to_jobs.join(
87+
projects_to_products,
88+
projects_to_jobs.c.project_uuid
89+
== projects_to_products.c.project_uuid,
90+
).join(
91+
project_to_groups,
92+
projects_to_jobs.c.project_uuid == project_to_groups.c.project_uuid,
93+
)
94+
)
95+
.where(
96+
projects_to_products.c.product_name == product_name,
97+
project_to_groups.c.gid.in_(sa.select(user_groups_query.c.gid)),
98+
project_to_groups.c.read.is_(True),
99+
)
100+
)
101+
102+
# Apply job_parent_resource_name_filter if provided
103+
if job_parent_resource_name_filter:
104+
access_query = access_query.where(
105+
projects_to_jobs.c.job_parent_resource_name.like(
106+
f"%{job_parent_resource_name_filter}%"
107+
)
108+
)
109+
110+
# Convert access_query to a subquery
111+
base_query = access_query.subquery()
112+
113+
# Step 3: Query to get the total count
114+
total_query = sa.select(sa.func.count()).select_from(base_query)
115+
116+
# Step 4: Query to get the paginated list with full selection
117+
list_query = (
118+
sa.select(*_PROJECT_JOB_DB_COLS)
119+
.select_from(
120+
base_query.join(
121+
projects,
122+
projects.c.uuid == base_query.c.project_uuid,
123+
)
124+
)
125+
.order_by(
126+
projects.c.creation_date.desc(), # latests first
127+
projects.c.id.desc(),
128+
)
129+
.limit(limit)
130+
.offset(offset)
131+
)
132+
133+
# Step 5: Execute queries
134+
async with pass_or_acquire_connection(self.engine, connection) as conn:
135+
total_count = await conn.scalar(total_query)
136+
assert isinstance(total_count, int) # nosec
137+
138+
result = await conn.execute(list_query)
139+
projects_list = TypeAdapter(list[ProjectJobDBGet]).validate_python(
140+
result.fetchall()
141+
)
142+
143+
return cast(int, total_count), projects_list

services/web/server/src/simcore_service_webserver/projects/_jobs_service.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from models_library.projects import ProjectID
77
from models_library.users import UserID
88
from pydantic import AfterValidator, validate_call
9+
from simcore_service_webserver.projects.models import ProjectDBGet
910

1011
from ._access_rights_service import check_user_project_permission
1112
from ._jobs_repository import ProjectJobsRepository
@@ -45,3 +46,27 @@ async def set_project_as_job(
4546
await repo.set_project_as_job(
4647
project_uuid=project_uuid, job_parent_resource_name=job_parent_resource_name
4748
)
49+
50+
51+
@validate_call(config={"arbitrary_types_allowed": True})
52+
async def list_my_projects_marked_as_jobs(
53+
app: web.Application,
54+
*,
55+
product_name: ProductName,
56+
user_id: UserID,
57+
offset: int = 0,
58+
limit: int = 10,
59+
job_parent_resource_name_filter: str | None = None,
60+
) -> tuple[int, list[ProjectDBGet]]:
61+
"""
62+
Lists paginated projects marked as jobs for the given user and product.
63+
Optionally filters by job_parent_resource_name using SQL-like wildcard patterns.
64+
"""
65+
repo = ProjectJobsRepository.create_from_app(app)
66+
return await repo.list_projects_marked_as_jobs(
67+
user_id=user_id,
68+
product_name=product_name,
69+
offset=offset,
70+
limit=limit,
71+
job_parent_resource_name_filter=job_parent_resource_name_filter,
72+
)

services/web/server/src/simcore_service_webserver/projects/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ class ProjectDBGet(BaseModel):
7171
)
7272

7373

74+
class ProjectJobDBGet(ProjectDBGet):
75+
job_parent_resource_name: str
76+
77+
7478
class ProjectWithTrashExtra(ProjectDBGet):
7579
# This field is not part of the tables
7680
trashed_by_primary_gid: GroupID | None = None

0 commit comments

Comments
 (0)