v2 foundation: shared contracts, app profile schema, wiring stubs
- projects: app profile columns + one app per org; project_external_git, deploy_targets, org_model_grants tables (migration a0f0e1d2c3b4) - ports + fakes: RepoWorkspace, DeliveryHooks, DeployTarget, Memory, ModelGrants, WorkforceBootstrap; AppProfile + AppProfileRepository - per-workstream wiring (container_org/engine/delivery), config blocks, empty routers, delivery worker, MCP/delivery service shells, web stubs - admin nav derives from ADMIN_ITEMS; plan docs updated for ext4 worktrees Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
3a68b0fa07
commit
0cd9194b64
@@ -0,0 +1,82 @@
|
||||
"""Read/write access to the app profile columns of ``projects``.
|
||||
|
||||
Foundation-owned and shared by all three workstreams (agents may call it, not edit it).
|
||||
Every lookup is scoped by ``organization_id``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.domain.projects.profile import AppProfile
|
||||
from app.infrastructure.db.models import ProjectRow
|
||||
|
||||
#: Columns a caller may change through ``update_profile``.
|
||||
EDITABLE_FIELDS = frozenset(
|
||||
{
|
||||
"app_type",
|
||||
"language",
|
||||
"framework",
|
||||
"runtime_version",
|
||||
"package_manager",
|
||||
"setup_command",
|
||||
"build_command",
|
||||
"test_command",
|
||||
"start_command",
|
||||
"app_port",
|
||||
"healthcheck_path",
|
||||
"git_source",
|
||||
"external_repo_url",
|
||||
"default_branch",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class AppProfileRepository:
|
||||
def __init__(self, session: AsyncSession) -> None:
|
||||
self._session = session
|
||||
|
||||
async def get_for_org(self, organization_id: str) -> AppProfile | None:
|
||||
row = await self._row_for_org(organization_id)
|
||||
return AppProfile.from_row(row) if row else None
|
||||
|
||||
async def get(self, *, organization_id: str, project_id: str) -> AppProfile | None:
|
||||
row = await self._session.scalar(
|
||||
select(ProjectRow).where(
|
||||
ProjectRow.id == project_id, ProjectRow.organization_id == organization_id
|
||||
)
|
||||
)
|
||||
return AppProfile.from_row(row) if row else None
|
||||
|
||||
async def update_profile(
|
||||
self,
|
||||
*,
|
||||
organization_id: str,
|
||||
project_id: str,
|
||||
values: dict[str, object],
|
||||
mark_wizard_completed: datetime | None = None,
|
||||
) -> AppProfile | None:
|
||||
unknown = set(values) - EDITABLE_FIELDS
|
||||
if unknown:
|
||||
raise ValueError(f"not editable profile fields: {sorted(unknown)}")
|
||||
row = await self._session.scalar(
|
||||
select(ProjectRow).where(
|
||||
ProjectRow.id == project_id, ProjectRow.organization_id == organization_id
|
||||
)
|
||||
)
|
||||
if row is None:
|
||||
return None
|
||||
for name, value in values.items():
|
||||
setattr(row, name, value)
|
||||
if mark_wizard_completed is not None:
|
||||
row.wizard_completed_at = mark_wizard_completed
|
||||
await self._session.flush()
|
||||
return AppProfile.from_row(row)
|
||||
|
||||
async def _row_for_org(self, organization_id: str) -> ProjectRow | None:
|
||||
return await self._session.scalar(
|
||||
select(ProjectRow).where(ProjectRow.organization_id == organization_id)
|
||||
)
|
||||
Reference in New Issue
Block a user