Skip to content
25 changes: 17 additions & 8 deletions backend/app/api/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,38 @@
from sqlalchemy.ext.asyncio import AsyncSession

from app.auth import CurrentUser, get_current_user
from app.db import get_session
from app.db import get_read_session, get_session
from app.models import DbConnection
from app.permissions import require_project_member
from app.schemas import ConnectionCreateIn, ConnectionOut
from app.security import encrypt_text
from app.sanitize import sanitize_for_storage


router = APIRouter(prefix="/api/connections", tags=["connections"])


@router.get("/by-project/{project_space_uuid}", response_model=list[ConnectionOut])
@router.get(
"/by-project/{project_space_uuid}", response_model=list[ConnectionOut]
)
async def list_connections(
project_space_uuid: uuid.UUID,
user: CurrentUser = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
session: AsyncSession = Depends(get_read_session),
) -> list[ConnectionOut]:
"""List DB connections for a project."""
await require_project_member(session, project_space_uuid, user.user_account_uuid)
await require_project_member(
session, project_space_uuid, user.user_account_uuid
)
rows = await session.execute(
select(DbConnection)
.where(DbConnection.project_space_uuid == project_space_uuid)
.order_by(DbConnection.created_at.desc())
)
cons = rows.scalars().all()
return [
ConnectionOut(db_connection_uuid=c.db_connection_uuid, conn_name=c.conn_name)
ConnectionOut(
db_connection_uuid=c.db_connection_uuid, conn_name=c.conn_name
)
for c in cons
]

Expand All @@ -47,7 +52,9 @@ async def create_connection(
session: AsyncSession = Depends(get_session),
) -> ConnectionOut:
"""Create a DB connection for a project (encrypt DSN at rest)."""
await require_project_member(session, project_space_uuid, user.user_account_uuid)
await require_project_member(
session, project_space_uuid, user.user_account_uuid
)
encrypted = encrypt_text(str(sanitize_for_storage(body.dsn)))
c = DbConnection(
db_connection_uuid=uuid.uuid4(),
Expand All @@ -60,4 +67,6 @@ async def create_connection(
)
session.add(c)
await session.commit()
return ConnectionOut(db_connection_uuid=c.db_connection_uuid, conn_name=c.conn_name)
return ConnectionOut(
db_connection_uuid=c.db_connection_uuid, conn_name=c.conn_name
)
18 changes: 11 additions & 7 deletions backend/app/api/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from sqlalchemy.ext.asyncio import AsyncSession

from app.auth import CurrentUser, get_current_user
from app.db import get_session
from app.db import get_read_session, get_session
from app.models import ProjectMember, ProjectSpace, UserAccount
from app.schemas import (
ProjectCreateIn,
Expand All @@ -19,14 +19,13 @@
)
from app.sanitize import sanitize_for_storage


router = APIRouter(prefix="/api/projects", tags=["projects"])


@router.get("", response_model=list[ProjectOut])
async def list_projects(
user: CurrentUser = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
session: AsyncSession = Depends(get_read_session),
) -> list[ProjectOut]:
"""List projects that the current user is a member of."""
rows = await session.execute(
Expand All @@ -40,7 +39,9 @@ async def list_projects(
)
projects = rows.scalars().all()
return [
ProjectOut(project_space_uuid=p.project_space_uuid, project_name=p.project_name)
ProjectOut(
project_space_uuid=p.project_space_uuid, project_name=p.project_name
)
for p in projects
]

Expand Down Expand Up @@ -74,11 +75,13 @@ async def create_project(
)


@router.get("/{project_space_uuid}/members", response_model=list[ProjectMemberOut])
@router.get(
"/{project_space_uuid}/members", response_model=list[ProjectMemberOut]
)
async def list_project_members(
project_space_uuid: uuid.UUID,
user: CurrentUser = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
session: AsyncSession = Depends(get_read_session),
) -> list[ProjectMemberOut]:
"""List members of a project (MVP: any member can view)."""
# owner/editor/viewer 모두 멤버 조회 가능(MVP)
Expand Down Expand Up @@ -164,7 +167,8 @@ async def add_project_member(
if existing_role == "owner":
# Avoid leaving project without an owner via this endpoint.
raise HTTPException(
status_code=400, detail="cannot change owner role via invite endpoint"
status_code=400,
detail="cannot change owner role via invite endpoint",
)

# Race-safe upsert on composite PK.
Expand Down
16 changes: 10 additions & 6 deletions backend/app/api/share.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@
from sqlalchemy.ext.asyncio import AsyncSession

from app.auth import CurrentUser, get_current_user
from app.db import get_session
from app.db import get_read_session, get_session
from app.ddl.export import snapshot_json_to_sql
from app.models import ProjectMember, SchemaSnapshot, SchemaSnapshotData, ShareLink

from app.models import (
ProjectMember,
SchemaSnapshot,
SchemaSnapshotData,
ShareLink,
)

router = APIRouter(prefix="/api", tags=["share"])

Expand Down Expand Up @@ -54,7 +58,7 @@ async def create_share_link(
@router.get("/share/{share_link_uuid}")
async def get_share_link_info(
share_link_uuid: uuid.UUID,
session: AsyncSession = Depends(get_session),
session: AsyncSession = Depends(get_read_session),
) -> dict:
"""Return share link metadata and recent snapshots."""
link = await session.get(ShareLink, share_link_uuid)
Expand Down Expand Up @@ -91,7 +95,7 @@ async def get_share_link_info(
async def get_shared_snapshot(
share_link_uuid: uuid.UUID,
schema_snapshot_uuid: uuid.UUID,
session: AsyncSession = Depends(get_session),
session: AsyncSession = Depends(get_read_session),
) -> dict:
"""Return a snapshot via a share link (no auth)."""
link = await session.get(ShareLink, share_link_uuid)
Expand Down Expand Up @@ -123,7 +127,7 @@ async def get_shared_snapshot(
async def export_shared_snapshot_sql(
share_link_uuid: uuid.UUID,
schema_snapshot_uuid: uuid.UUID,
session: AsyncSession = Depends(get_session),
session: AsyncSession = Depends(get_read_session),
) -> str:
"""Export a shared snapshot as SQL via a share link."""
link = await session.get(ShareLink, share_link_uuid)
Expand Down
32 changes: 22 additions & 10 deletions backend/app/api/snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@
from sqlalchemy.ext.asyncio import AsyncSession

from app.auth import CurrentUser, get_current_user
from app.db import get_session
from app.models import DbConnection, JobQueue, SchemaSnapshot, SchemaSnapshotData
from app.db import get_read_session, get_session
from app.models import (
DbConnection,
JobQueue,
SchemaSnapshot,
SchemaSnapshotData,
)
from app.permissions import require_project_member
from app.schemas import SnapshotCreateIn, SnapshotDetailOut, SnapshotOut
from app.ddl.export import snapshot_json_to_sql


router = APIRouter(prefix="/api/snapshots", tags=["snapshots"])


Expand All @@ -27,7 +31,9 @@ async def create_snapshot(
session: AsyncSession = Depends(get_session),
) -> SnapshotOut:
"""Create a schema snapshot job for a project connection."""
await require_project_member(session, project_space_uuid, user.user_account_uuid)
await require_project_member(
session, project_space_uuid, user.user_account_uuid
)

# Ensure connection belongs to this project
conn = await session.get(DbConnection, body.db_connection_uuid)
Expand Down Expand Up @@ -77,7 +83,7 @@ async def create_snapshot(
async def get_snapshot(
schema_snapshot_uuid: uuid.UUID,
user: CurrentUser = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
session: AsyncSession = Depends(get_read_session),
) -> SnapshotDetailOut:
"""Get a snapshot's status and (if present) captured JSON."""
snap = await session.get(SchemaSnapshot, schema_snapshot_uuid)
Expand All @@ -102,11 +108,13 @@ async def get_snapshot(
)


@router.get("/{schema_snapshot_uuid}/export.sql", response_class=PlainTextResponse)
@router.get(
"/{schema_snapshot_uuid}/export.sql", response_class=PlainTextResponse
)
async def export_snapshot_sql(
schema_snapshot_uuid: uuid.UUID,
user: CurrentUser = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
session: AsyncSession = Depends(get_read_session),
) -> str:
"""Export a snapshot as PostgreSQL DDL (best-effort)."""
snap = await session.get(SchemaSnapshot, schema_snapshot_uuid)
Expand All @@ -121,14 +129,18 @@ async def export_snapshot_sql(
return snapshot_json_to_sql(data.snapshot_json)


@router.get("/by-project/{project_space_uuid}", response_model=list[SnapshotOut])
@router.get(
"/by-project/{project_space_uuid}", response_model=list[SnapshotOut]
)
async def list_snapshots(
project_space_uuid: uuid.UUID,
user: CurrentUser = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
session: AsyncSession = Depends(get_read_session),
) -> list[SnapshotOut]:
"""List snapshots for a project."""
await require_project_member(session, project_space_uuid, user.user_account_uuid)
await require_project_member(
session, project_space_uuid, user.user_account_uuid
)
rows = await session.execute(
select(SchemaSnapshot)
.where(SchemaSnapshot.project_space_uuid == project_space_uuid)
Expand Down
Loading
Loading