From f3692e1145f4413ac2e0d00ed07830babddf7d77 Mon Sep 17 00:00:00 2001 From: Cheng Zhou Date: Tue, 16 Dec 2025 16:42:42 +0800 Subject: [PATCH] =?UTF-8?q?Step=203=EF=BC=9AStudy=20/=20Site=20/=20StudyMe?= =?UTF-8?q?mber=20+=20=E9=A1=B9=E7=9B=AE=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/v1/members.py | 90 ++++++++++++++++++++++++ backend/app/api/v1/router.py | 5 +- backend/app/api/v1/sites.py | 69 ++++++++++++++++++ backend/app/api/v1/studies.py | 65 +++++++++++++++++ backend/app/core/deps.py | 51 ++++++++++++++ backend/app/crud/member.py | 64 +++++++++++++++++ backend/app/crud/site.py | 48 +++++++++++++ backend/app/crud/study.py | 71 +++++++++++++++++++ backend/app/db/base.py | 10 +-- backend/app/db/base_class.py | 5 ++ backend/app/models/site.py | 21 ++++++ backend/app/models/study.py | 22 ++++++ backend/app/models/study_member.py | 20 ++++++ backend/app/models/user.py | 2 +- backend/app/schemas/member.py | 29 ++++++++ backend/app/schemas/site.py | 34 +++++++++ backend/app/schemas/study.py | 39 ++++++++++ pg_data/base/16384/1247 | Bin 122880 -> 122880 bytes pg_data/base/16384/1249 | Bin 466944 -> 475136 bytes pg_data/base/16384/1259 | Bin 114688 -> 114688 bytes pg_data/base/16384/24576 | Bin 8192 -> 8192 bytes pg_data/base/16384/24581 | Bin 16384 -> 16384 bytes pg_data/base/16384/24583 | Bin 16384 -> 16384 bytes pg_data/base/16384/24584 | 0 pg_data/base/16384/24588 | 0 pg_data/base/16384/24589 | Bin 0 -> 8192 bytes pg_data/base/16384/24590 | Bin 0 -> 8192 bytes pg_data/base/16384/24597 | Bin 0 -> 8192 bytes pg_data/base/16384/24598 | 0 pg_data/base/16384/24603 | 0 pg_data/base/16384/24604 | Bin 0 -> 8192 bytes pg_data/base/16384/24605 | Bin 0 -> 8192 bytes pg_data/base/16384/24612 | Bin 0 -> 8192 bytes pg_data/base/16384/24613 | 0 pg_data/base/16384/24618 | Bin 0 -> 8192 bytes pg_data/base/16384/24620 | Bin 0 -> 8192 bytes pg_data/base/16384/24632 | Bin 0 -> 8192 bytes pg_data/base/16384/2579 | Bin 16384 -> 16384 bytes pg_data/base/16384/2604 | Bin 8192 -> 8192 bytes pg_data/base/16384/2606 | Bin 24576 -> 24576 bytes pg_data/base/16384/2608 | Bin 114688 -> 114688 bytes pg_data/base/16384/2610 | Bin 32768 -> 32768 bytes pg_data/base/16384/2620 | Bin 0 -> 8192 bytes pg_data/base/16384/2656 | Bin 16384 -> 16384 bytes pg_data/base/16384/2657 | Bin 16384 -> 16384 bytes pg_data/base/16384/2658 | Bin 122880 -> 131072 bytes pg_data/base/16384/2659 | Bin 90112 -> 90112 bytes pg_data/base/16384/2662 | Bin 32768 -> 32768 bytes pg_data/base/16384/2663 | Bin 40960 -> 40960 bytes pg_data/base/16384/2664 | Bin 16384 -> 16384 bytes pg_data/base/16384/2665 | Bin 16384 -> 16384 bytes pg_data/base/16384/2666 | Bin 16384 -> 16384 bytes pg_data/base/16384/2667 | Bin 16384 -> 16384 bytes pg_data/base/16384/2673 | Bin 81920 -> 81920 bytes pg_data/base/16384/2674 | Bin 65536 -> 65536 bytes pg_data/base/16384/2678 | Bin 16384 -> 16384 bytes pg_data/base/16384/2679 | Bin 16384 -> 16384 bytes pg_data/base/16384/2699 | Bin 8192 -> 16384 bytes pg_data/base/16384/2701 | Bin 8192 -> 16384 bytes pg_data/base/16384/2702 | Bin 8192 -> 16384 bytes pg_data/base/16384/2703 | Bin 32768 -> 32768 bytes pg_data/base/16384/2704 | Bin 40960 -> 40960 bytes pg_data/base/16384/3455 | Bin 16384 -> 16384 bytes pg_data/global/pg_control | Bin 8192 -> 8192 bytes pg_data/pg_wal/000000010000000000000001 | Bin 16777216 -> 16777216 bytes pg_data/pg_xact/0000 | Bin 8192 -> 8192 bytes 66 files changed, 639 insertions(+), 6 deletions(-) create mode 100644 backend/app/api/v1/members.py create mode 100644 backend/app/api/v1/sites.py create mode 100644 backend/app/api/v1/studies.py create mode 100644 backend/app/crud/member.py create mode 100644 backend/app/crud/site.py create mode 100644 backend/app/crud/study.py create mode 100644 backend/app/db/base_class.py create mode 100644 backend/app/models/site.py create mode 100644 backend/app/models/study.py create mode 100644 backend/app/models/study_member.py create mode 100644 backend/app/schemas/member.py create mode 100644 backend/app/schemas/site.py create mode 100644 backend/app/schemas/study.py create mode 100644 pg_data/base/16384/24584 create mode 100644 pg_data/base/16384/24588 create mode 100644 pg_data/base/16384/24589 create mode 100644 pg_data/base/16384/24590 create mode 100644 pg_data/base/16384/24597 create mode 100644 pg_data/base/16384/24598 create mode 100644 pg_data/base/16384/24603 create mode 100644 pg_data/base/16384/24604 create mode 100644 pg_data/base/16384/24605 create mode 100644 pg_data/base/16384/24612 create mode 100644 pg_data/base/16384/24613 create mode 100644 pg_data/base/16384/24618 create mode 100644 pg_data/base/16384/24620 create mode 100644 pg_data/base/16384/24632 diff --git a/backend/app/api/v1/members.py b/backend/app/api/v1/members.py new file mode 100644 index 00000000..31e1e3f1 --- /dev/null +++ b/backend/app/api/v1/members.py @@ -0,0 +1,90 @@ +import uuid + +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.deps import get_db_session, require_study_member, require_study_roles +from app.crud import member as member_crud +from app.crud import study as study_crud +from app.schemas.member import StudyMemberCreate, StudyMemberRead, StudyMemberUpdate + +router = APIRouter() + + +async def _ensure_study_exists(db: AsyncSession, study_id: uuid.UUID): + study = await study_crud.get(db, study_id) + if not study: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Study not found") + return study + + +@router.post( + "/", + response_model=StudyMemberRead, + status_code=status.HTTP_201_CREATED, + dependencies=[Depends(require_study_roles(["PM"]))], +) +async def add_member( + study_id: uuid.UUID, + member_in: StudyMemberCreate, + db: AsyncSession = Depends(get_db_session), +) -> StudyMemberRead: + await _ensure_study_exists(db, study_id) + existing = await member_crud.get_member(db, study_id, member_in.user_id) + if existing: + raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Member already exists") + member = await member_crud.add_member(db, study_id, member_in) + return member + + +@router.get( + "/", + response_model=list[StudyMemberRead], + dependencies=[Depends(require_study_member())], +) +async def list_members( + study_id: uuid.UUID, + skip: int = 0, + limit: int = 100, + db: AsyncSession = Depends(get_db_session), +) -> list[StudyMemberRead]: + await _ensure_study_exists(db, study_id) + members = await member_crud.list_members(db, study_id, skip=skip, limit=limit) + return list(members) + + +@router.patch( + "/{member_id}", + response_model=StudyMemberRead, + dependencies=[Depends(require_study_roles(["PM"]))], +) +async def update_member( + study_id: uuid.UUID, + member_id: uuid.UUID, + member_in: StudyMemberUpdate, + db: AsyncSession = Depends(get_db_session), +) -> StudyMemberRead: + await _ensure_study_exists(db, study_id) + member = await member_crud.get_member_by_id(db, member_id) + if not member or member.study_id != study_id: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Member not found") + updated = await member_crud.update_member(db, member, member_in) + return updated + + +@router.delete( + "/{member_id}", + response_model=StudyMemberRead, + dependencies=[Depends(require_study_roles(["PM"]))], +) +async def remove_member( + study_id: uuid.UUID, + member_id: uuid.UUID, + db: AsyncSession = Depends(get_db_session), +) -> StudyMemberRead: + await _ensure_study_exists(db, study_id) + member = await member_crud.get_member_by_id(db, member_id) + if not member or member.study_id != study_id: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Member not found") + removed = await member_crud.remove_member(db, member) + return removed diff --git a/backend/app/api/v1/router.py b/backend/app/api/v1/router.py index a30bc091..7b77b492 100644 --- a/backend/app/api/v1/router.py +++ b/backend/app/api/v1/router.py @@ -1,7 +1,10 @@ from fastapi import APIRouter -from app.api.v1 import auth, users +from app.api.v1 import auth, users, studies, sites, members api_router = APIRouter() api_router.include_router(auth.router, prefix="/auth", tags=["auth"]) api_router.include_router(users.router, prefix="/users", tags=["users"]) +api_router.include_router(studies.router, prefix="/studies", tags=["studies"]) +api_router.include_router(sites.router, prefix="/studies/{study_id}/sites", tags=["sites"]) +api_router.include_router(members.router, prefix="/studies/{study_id}/members", tags=["study-members"]) diff --git a/backend/app/api/v1/sites.py b/backend/app/api/v1/sites.py new file mode 100644 index 00000000..c6bdb4e8 --- /dev/null +++ b/backend/app/api/v1/sites.py @@ -0,0 +1,69 @@ +import uuid + +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.deps import get_db_session, require_study_member, require_study_roles +from app.crud import site as site_crud +from app.crud import study as study_crud +from app.schemas.site import SiteCreate, SiteRead, SiteUpdate + +router = APIRouter() + + +async def _ensure_study_exists(db: AsyncSession, study_id: uuid.UUID): + study = await study_crud.get(db, study_id) + if not study: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Study not found") + return study + + +@router.post( + "/", + response_model=SiteRead, + status_code=status.HTTP_201_CREATED, + dependencies=[Depends(require_study_roles(["PM"]))], +) +async def create_site( + study_id: uuid.UUID, + site_in: SiteCreate, + db: AsyncSession = Depends(get_db_session), +) -> SiteRead: + await _ensure_study_exists(db, study_id) + site = await site_crud.create_site(db, study_id, site_in) + return site + + +@router.get( + "/", + response_model=list[SiteRead], + dependencies=[Depends(require_study_member())], +) +async def list_sites( + study_id: uuid.UUID, + skip: int = 0, + limit: int = 100, + db: AsyncSession = Depends(get_db_session), +) -> list[SiteRead]: + await _ensure_study_exists(db, study_id) + sites = await site_crud.list_by_study(db, study_id, skip=skip, limit=limit) + return list(sites) + + +@router.patch( + "/{site_id}", + response_model=SiteRead, + dependencies=[Depends(require_study_roles(["PM"]))], +) +async def update_site( + study_id: uuid.UUID, + site_id: uuid.UUID, + site_in: SiteUpdate, + db: AsyncSession = Depends(get_db_session), +) -> SiteRead: + await _ensure_study_exists(db, study_id) + site = await site_crud.get_site(db, site_id) + if not site or site.study_id != study_id: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Site not found") + updated = await site_crud.update_site(db, site, site_in) + return updated diff --git a/backend/app/api/v1/studies.py b/backend/app/api/v1/studies.py new file mode 100644 index 00000000..029246a0 --- /dev/null +++ b/backend/app/api/v1/studies.py @@ -0,0 +1,65 @@ +import uuid + +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.deps import get_current_user, get_db_session, require_roles, require_study_member, require_study_roles +from app.crud import study as study_crud +from app.schemas.study import StudyCreate, StudyRead, StudyUpdate + +router = APIRouter() + + +@router.post("/", response_model=StudyRead, status_code=status.HTTP_201_CREATED, dependencies=[Depends(require_roles(["ADMIN"]))]) +async def create_study( + study_in: StudyCreate, + db: AsyncSession = Depends(get_db_session), + current_user=Depends(get_current_user), +) -> StudyRead: + existing = await study_crud.get_by_code(db, study_in.code) + if existing: + raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Study code already exists") + study = await study_crud.create(db, study_in, created_by=current_user.id) + return study + + +@router.get("/", response_model=list[StudyRead]) +async def list_studies( + skip: int = 0, + limit: int = 100, + db: AsyncSession = Depends(get_db_session), + current_user=Depends(get_current_user), +) -> list[StudyRead]: + if current_user.role == "ADMIN": + studies = await study_crud.list_studies(db, skip=skip, limit=limit) + else: + studies = await study_crud.list_studies_for_user(db, current_user.id, skip=skip, limit=limit) + return list(studies) + + +@router.get("/{study_id}", response_model=StudyRead, dependencies=[Depends(require_study_member())]) +async def get_study( + study_id: uuid.UUID, + db: AsyncSession = Depends(get_db_session), +) -> StudyRead: + study = await study_crud.get(db, study_id) + if not study: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Study not found") + return study + + +@router.patch( + "/{study_id}", + response_model=StudyRead, + dependencies=[Depends(require_study_roles(["PM"]))], +) +async def update_study( + study_id: uuid.UUID, + study_in: StudyUpdate, + db: AsyncSession = Depends(get_db_session), +) -> StudyRead: + study = await study_crud.get(db, study_id) + if not study: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Study not found") + updated = await study_crud.update(db, study, study_in) + return updated diff --git a/backend/app/core/deps.py b/backend/app/core/deps.py index d2ceca90..69c64aa6 100644 --- a/backend/app/core/deps.py +++ b/backend/app/core/deps.py @@ -7,6 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.core.security import decode_token, oauth2_scheme from app.crud import user as user_crud +from app.crud import member as member_crud from app.db.session import SessionLocal from app.schemas.user import TokenPayload @@ -52,3 +53,53 @@ def require_roles(roles: Iterable[str]) -> Callable: return current_user return dependency + + +async def get_study_member( + study_id: uuid.UUID, + current_user=Depends(get_current_user), + db: AsyncSession = Depends(get_db_session), +): + if current_user.role == "ADMIN": + return None + return await member_crud.get_member(db, study_id, current_user.id) + + +def require_study_member(): + async def dependency( + study_id: uuid.UUID, + current_user=Depends(get_current_user), + db: AsyncSession = Depends(get_db_session), + ): + if current_user.role == "ADMIN": + return current_user + membership = await member_crud.get_member(db, study_id, current_user.id) + if not membership or not membership.is_active: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Not a member of this study", + ) + return current_user + + return dependency + + +def require_study_roles(roles: Iterable[str]): + roles_set = set(roles) + + async def dependency( + study_id: uuid.UUID, + current_user=Depends(get_current_user), + db: AsyncSession = Depends(get_db_session), + ): + if current_user.role == "ADMIN": + return current_user + membership = await member_crud.get_member(db, study_id, current_user.id) + if not membership or not membership.is_active or membership.role_in_study not in roles_set: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Insufficient study permissions", + ) + return current_user + + return dependency diff --git a/backend/app/crud/member.py b/backend/app/crud/member.py new file mode 100644 index 00000000..24db02a6 --- /dev/null +++ b/backend/app/crud/member.py @@ -0,0 +1,64 @@ +import uuid +from typing import Sequence + +from sqlalchemy import select, update +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.study_member import StudyMember +from app.schemas.member import StudyMemberCreate, StudyMemberUpdate + + +async def get_member(db: AsyncSession, study_id: uuid.UUID, user_id: uuid.UUID) -> StudyMember | None: + result = await db.execute( + select(StudyMember).where(StudyMember.study_id == study_id, StudyMember.user_id == user_id) + ) + return result.scalar_one_or_none() + + +async def get_member_by_id(db: AsyncSession, member_id: uuid.UUID) -> StudyMember | None: + result = await db.execute(select(StudyMember).where(StudyMember.id == member_id)) + return result.scalar_one_or_none() + + +async def add_member(db: AsyncSession, study_id: uuid.UUID, member_in: StudyMemberCreate) -> StudyMember: + member = StudyMember( + study_id=study_id, + user_id=member_in.user_id, + role_in_study=member_in.role_in_study, + is_active=member_in.is_active, + ) + db.add(member) + await db.commit() + await db.refresh(member) + return member + + +async def list_members(db: AsyncSession, study_id: uuid.UUID, skip: int = 0, limit: int = 100) -> Sequence[StudyMember]: + result = await db.execute( + select(StudyMember).where(StudyMember.study_id == study_id).offset(skip).limit(limit) + ) + return result.scalars().all() + + +async def update_member(db: AsyncSession, member: StudyMember, member_in: StudyMemberUpdate) -> StudyMember: + update_data = {k: v for k, v in member_in.model_dump(exclude_unset=True).items() if v is not None} + if update_data: + await db.execute( + update(StudyMember) + .where(StudyMember.id == member.id) + .values(**update_data) + ) + await db.commit() + await db.refresh(member) + return member + + +async def remove_member(db: AsyncSession, member: StudyMember) -> StudyMember: + await db.execute( + update(StudyMember) + .where(StudyMember.id == member.id) + .values(is_active=False) + ) + await db.commit() + await db.refresh(member) + return member diff --git a/backend/app/crud/site.py b/backend/app/crud/site.py new file mode 100644 index 00000000..69b79ed4 --- /dev/null +++ b/backend/app/crud/site.py @@ -0,0 +1,48 @@ +import uuid +from typing import Sequence + +from sqlalchemy import select, update +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.site import Site +from app.schemas.site import SiteCreate, SiteUpdate + + +async def create_site(db: AsyncSession, study_id: uuid.UUID, site_in: SiteCreate) -> Site: + site = Site( + study_id=study_id, + name=site_in.name, + city=site_in.city, + pi_name=site_in.pi_name, + contact=site_in.contact, + is_active=site_in.is_active, + ) + db.add(site) + await db.commit() + await db.refresh(site) + return site + + +async def get_site(db: AsyncSession, site_id: uuid.UUID) -> Site | None: + result = await db.execute(select(Site).where(Site.id == site_id)) + return result.scalar_one_or_none() + + +async def update_site(db: AsyncSession, site: Site, site_in: SiteUpdate) -> Site: + update_data = site_in.model_dump(exclude_unset=True) + if update_data: + await db.execute( + update(Site) + .where(Site.id == site.id) + .values(**update_data) + ) + await db.commit() + await db.refresh(site) + return site + + +async def list_by_study(db: AsyncSession, study_id: uuid.UUID, skip: int = 0, limit: int = 100) -> Sequence[Site]: + result = await db.execute( + select(Site).where(Site.study_id == study_id).offset(skip).limit(limit) + ) + return result.scalars().all() diff --git a/backend/app/crud/study.py b/backend/app/crud/study.py new file mode 100644 index 00000000..f3e4a130 --- /dev/null +++ b/backend/app/crud/study.py @@ -0,0 +1,71 @@ +import uuid +from typing import Sequence + +from sqlalchemy import select, update +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.study import Study +from app.schemas.study import StudyCreate, StudyUpdate + + +async def create(db: AsyncSession, study_in: StudyCreate, *, created_by: uuid.UUID | None = None) -> Study: + study = Study( + code=study_in.code, + name=study_in.name, + sponsor=study_in.sponsor, + protocol_no=study_in.protocol_no, + phase=study_in.phase, + status=study_in.status, + created_by=created_by, + ) + db.add(study) + await db.commit() + await db.refresh(study) + return study + + +async def get(db: AsyncSession, study_id: uuid.UUID) -> Study | None: + result = await db.execute(select(Study).where(Study.id == study_id)) + return result.scalar_one_or_none() + + +async def get_by_code(db: AsyncSession, code: str) -> Study | None: + result = await db.execute(select(Study).where(Study.code == code)) + return result.scalar_one_or_none() + + +async def update(db: AsyncSession, study: Study, study_in: StudyUpdate) -> Study: + update_data = study_in.model_dump(exclude_unset=True) + if update_data: + await db.execute( + update(Study) + .where(Study.id == study.id) + .values(**update_data) + ) + await db.commit() + await db.refresh(study) + return study + + +async def list_studies(db: AsyncSession, skip: int = 0, limit: int = 100) -> Sequence[Study]: + result = await db.execute(select(Study).offset(skip).limit(limit)) + return result.scalars().all() + + +async def list_studies_for_user( + db: AsyncSession, + user_id: uuid.UUID, + skip: int = 0, + limit: int = 100, +) -> Sequence[Study]: + from app.models.study_member import StudyMember # local import to avoid cycles + + stmt = ( + select(Study) + .join(StudyMember, StudyMember.study_id == Study.id) + .where(StudyMember.user_id == user_id, StudyMember.is_active.is_(True)) + .offset(skip) + .limit(limit) + ) + result = await db.execute(stmt) + return result.scalars().all() diff --git a/backend/app/db/base.py b/backend/app/db/base.py index fa2b68a5..0b1a63b7 100644 --- a/backend/app/db/base.py +++ b/backend/app/db/base.py @@ -1,5 +1,7 @@ -from sqlalchemy.orm import DeclarativeBase +from app.db.base_class import Base - -class Base(DeclarativeBase): - pass +# Import models to ensure metadata is loaded for create_all +from app.models.user import User # noqa: F401 +from app.models.study import Study # noqa: F401 +from app.models.site import Site # noqa: F401 +from app.models.study_member import StudyMember # noqa: F401 diff --git a/backend/app/db/base_class.py b/backend/app/db/base_class.py new file mode 100644 index 00000000..fa2b68a5 --- /dev/null +++ b/backend/app/db/base_class.py @@ -0,0 +1,5 @@ +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + pass diff --git a/backend/app/models/site.py b/backend/app/models/site.py new file mode 100644 index 00000000..c92035cd --- /dev/null +++ b/backend/app/models/site.py @@ -0,0 +1,21 @@ +import uuid +from datetime import datetime + +from sqlalchemy import Boolean, DateTime, ForeignKey, String, func +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import Mapped, mapped_column + +from app.db.base_class import Base + + +class Site(Base): + __tablename__ = "sites" + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False) + name: Mapped[str] = mapped_column(String(200), nullable=False) + city: Mapped[str | None] = mapped_column(String(100), nullable=True) + pi_name: Mapped[str | None] = mapped_column(String(100), nullable=True) + contact: Mapped[str | None] = mapped_column(String(100), nullable=True) + is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="true") + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now()) diff --git a/backend/app/models/study.py b/backend/app/models/study.py new file mode 100644 index 00000000..4748004d --- /dev/null +++ b/backend/app/models/study.py @@ -0,0 +1,22 @@ +import uuid +from datetime import datetime + +from sqlalchemy import DateTime, ForeignKey, String, func +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import Mapped, mapped_column + +from app.db.base_class import Base + + +class Study(Base): + __tablename__ = "studies" + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + code: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True) + name: Mapped[str] = mapped_column(String(200), nullable=False) + sponsor: Mapped[str | None] = mapped_column(String(200), nullable=True) + protocol_no: Mapped[str | None] = mapped_column(String(100), nullable=True) + phase: Mapped[str | None] = mapped_column(String(50), nullable=True) + status: Mapped[str] = mapped_column(String(20), nullable=False, default="DRAFT") + created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True) + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now()) diff --git a/backend/app/models/study_member.py b/backend/app/models/study_member.py new file mode 100644 index 00000000..5ed2f2fd --- /dev/null +++ b/backend/app/models/study_member.py @@ -0,0 +1,20 @@ +import uuid +from datetime import datetime + +from sqlalchemy import Boolean, DateTime, ForeignKey, String, UniqueConstraint, func +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import Mapped, mapped_column + +from app.db.base_class import Base + + +class StudyMember(Base): + __tablename__ = "study_members" + __table_args__ = (UniqueConstraint("study_id", "user_id", name="uq_study_member"),) + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), nullable=False, index=True) + user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False) + role_in_study: Mapped[str] = mapped_column(String(20), nullable=False) + is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="true") + added_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now()) diff --git a/backend/app/models/user.py b/backend/app/models/user.py index 50e5e1b5..96851aa2 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -5,7 +5,7 @@ from sqlalchemy import Boolean, DateTime, String, func from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column -from app.db.base import Base +from app.db.base_class import Base class User(Base): diff --git a/backend/app/schemas/member.py b/backend/app/schemas/member.py new file mode 100644 index 00000000..0fa7935c --- /dev/null +++ b/backend/app/schemas/member.py @@ -0,0 +1,29 @@ +import uuid +from datetime import datetime +from typing import Literal, Optional + +from pydantic import BaseModel, ConfigDict, Field + +StudyRole = Literal["PM", "CRA", "PV", "IMP"] + + +class StudyMemberCreate(BaseModel): + user_id: uuid.UUID + role_in_study: StudyRole + is_active: bool = True + + +class StudyMemberUpdate(BaseModel): + role_in_study: Optional[StudyRole] = None + is_active: Optional[bool] = None + + +class StudyMemberRead(BaseModel): + id: uuid.UUID + study_id: uuid.UUID + user_id: uuid.UUID + role_in_study: StudyRole + is_active: bool + added_at: datetime + + model_config = ConfigDict(from_attributes=True) diff --git a/backend/app/schemas/site.py b/backend/app/schemas/site.py new file mode 100644 index 00000000..d66a7c2d --- /dev/null +++ b/backend/app/schemas/site.py @@ -0,0 +1,34 @@ +import uuid +from datetime import datetime +from typing import Optional + +from pydantic import BaseModel, ConfigDict, Field + + +class SiteCreate(BaseModel): + name: str = Field(min_length=1) + city: Optional[str] = None + pi_name: Optional[str] = None + contact: Optional[str] = None + is_active: bool = True + + +class SiteUpdate(BaseModel): + name: Optional[str] = None + city: Optional[str] = None + pi_name: Optional[str] = None + contact: Optional[str] = None + is_active: Optional[bool] = None + + +class SiteRead(BaseModel): + id: uuid.UUID + study_id: uuid.UUID + name: str + city: Optional[str] + pi_name: Optional[str] + contact: Optional[str] + is_active: bool + created_at: datetime + + model_config = ConfigDict(from_attributes=True) diff --git a/backend/app/schemas/study.py b/backend/app/schemas/study.py new file mode 100644 index 00000000..1bda9bbf --- /dev/null +++ b/backend/app/schemas/study.py @@ -0,0 +1,39 @@ +import uuid +from datetime import datetime +from typing import Literal, Optional + +from pydantic import BaseModel, ConfigDict, Field + +StudyStatus = Literal["DRAFT", "ACTIVE", "CLOSED"] + + +class StudyCreate(BaseModel): + code: str = Field(min_length=1) + name: str = Field(min_length=1) + sponsor: Optional[str] = None + protocol_no: Optional[str] = None + phase: Optional[str] = None + status: StudyStatus = "DRAFT" + + +class StudyUpdate(BaseModel): + code: Optional[str] = None + name: Optional[str] = None + sponsor: Optional[str] = None + protocol_no: Optional[str] = None + phase: Optional[str] = None + status: Optional[StudyStatus] = None + + +class StudyRead(BaseModel): + id: uuid.UUID + code: str + name: str + sponsor: Optional[str] + protocol_no: Optional[str] + phase: Optional[str] + status: StudyStatus + created_by: Optional[uuid.UUID] + created_at: datetime + + model_config = ConfigDict(from_attributes=True) diff --git a/pg_data/base/16384/1247 b/pg_data/base/16384/1247 index 37c318b11ced17991c77fdded16978a44a0ca653..ada60ecd274d6836ced6b6426178add5d95514d4 100644 GIT binary patch delta 723 zcmZoTz}^5vTNqgm@SG@{&d2}+F$^b!CkpsX4hj*Q_`qTmfkQw{{{|B%a2Ocm!6YAp zAcFz}BZtEO|NkK(Y6%Ps@x>*jDV6cLskuqh3->dcPOrJaD8ZzjFxfB3N)O!t0W<^D zfd-)JHv{SCo^F4Wk(E~!C<_B>(*tiZnrpxe6ab6!G4P`qD4qavQD(_>#TG{E>D4zF zC7C3^PEkkK$%m#>0;m(NvFZjRFT@REKw%gV2fINFW)ei0kAWA>B+dkAsAPg%VFd{g sF0d;!(e?76>E#0I1qBFLt2I(sZ~!G>fD`PF=@)J=3T)T7!FYom0D&I)zyJUM delta 364 ycmZoTz}^5vTNqgm@Ob!5W@G>Y7X}H5i2^2*gF?h6KCl=?6o-J^c8ME|FW3RpO~1$h diff --git a/pg_data/base/16384/1249 b/pg_data/base/16384/1249 index 2d790ada3434440965c4b23a860827c8e65c732e..2dc327b50b2d2e10828894202a27abd3ec46b845 100644 GIT binary patch delta 4542 zcmeI0&1)M+7{+I%{g5o%vZX{xZOM{sE3q9}w(GQ&X~8MZEhK3Qg_a&9)e_iA(Av@( zl2a5ACC9p3I=9k4pcg|+DfAFS4?Xl$ddM*%4!!i4yE`+x?`mgeH@L?@1u|>%d!Ctn z=lvMmdz$U_a=pINkMyH`wXgN{Zwz(x_ZL_}lshAwZLEY_FoJp0YTMZ3f@T88`8* zel)eFumfX17%ghoCGCJ{?aEeqHAFe#uKKnR+zj5IF-&iBfUQZGk`KUECG0Q=TamD} zfi5pvk+9<+Y+1tgU+Cr%#0XwtNyKRGnr*AGZ6CL278xI>RfJC2M`z)ok2~~f_W}DI zk=mf2Ci#l?ivjj~ySQ;fquz--LvM|F{1@+q0Pk)5RaBoR*3bv+nb9fEQL~ zwfBjYhW{c~TYu0El2>+KHvE+@1z2^hgXY7USD5iH6HZ7c?6E|?M2*G3l|&bO^inr3 z12#@IsEXzV?0lUbU(??IjK+5aTUn#*rLA*(RWv7v#_@61FumRsb-iZ=Y>axdwiec# zKv@h`dLh8HH+egA8fq041mP%OcYx(3jH^4qW+aTOJHT=h#?>8QSqbCn4zOtnqjl$X zVZf$D%nNSl%mC?Iq0+E(0p-O|WyrZWSzbjKgiacBx^U$OHM|S6EFvkI5f+W{XA0PP z3FFQbuyYc|ohe{x3FFQbu#|*xXA0P)gmGsISW?95q+mL&hSh4;zOWyELQ7Ijc(spW z`1WQrw@l?w*I+dsVAZu6)`5MLqBgO_uyY#t_K99x5G}?6EcSL9UAH&HeGWQ)lr@Sg zqQhvAL&vntZS#JuZQ36y>z^Vt+{oEmIJ{;eV1@3U+3uRS__;z~)NuMZG+#$3H(xLu z2{7dQ_$F96CgE1`04slMZNAip4Gh+gi6Yjv@%IBg_VdN8aWjHT13ihY8RId1n#6kE zc!YnVPv7Q^yI&X7)brn*i|7+{Z2}e4!n4R>rbA;*qCqFy|KB$+24miEt*}hlKx~@C zhWFZP9U6kLpqnx3*Se7{m1p+>VwI$^`TSnBwnIc)@gmosL)ML^BwjbDw) z&3$+k&by1S(c;@ni61S1vC+a|+-L!ejTR2$MhjqUv~UV&liZD{^`r^M0$g z+pf_pp~DPso#@M6dV-Qo9YM*br*8m8?PMayr6<_1(IeOh>G_&VG`O(w;&)-)*sH~y zn?`JC_^^5Mw!&hp7QHmhxI#7}e8L=G3uzZ)L&1k-iT~>_(x5cxI0;mST-cm{No

_(=W2I=K}y~tl(e( diff --git a/pg_data/base/16384/1259 b/pg_data/base/16384/1259 index 804ed80769af8e0e85b11839b8559c33c2c6dd38..8d8b67a2cff057b3953f0588e0678b25b6bc7980 100644 GIT binary patch delta 2058 zcmb_cO-~bH5T3!-^0kEUQBgj!t3WAeDOgQ0QR{&aJsD4X>w;}eLkN|YL{4m?;`rkEF{J2)eleACJ{+n6GZraPHihb-? zpm*MuQK9{hS1M|6${3}8{U!OnP*J9peXip6GZkBDX-_HM6BU8h&BrR19;vvyLNQiU zKNcRUxc+ndv+wdw9Tx}?5l;!CKnWV;y}iAwLKD{jGWU|X{9*R*hj7%2DsObJL0LbU$X|4$*0`|8nudK zD~Vm?kOTRY&`L1u8BkCo$P~#`a(Lc|HZ1dix=6|542Yd7qHpoOTjLRB7s%b3RgKXF z*@s3K_8{ritmL^Q_gOd@gZAjyM&ic7So}gOaBM3Bm1h4R92~p`j&?hF$b+Lt1IXS< z=I5t#`Q&))!bCh-ycHQMK8SP#*hXSgaNLfq>hVx638C-EtHn;LB?*~DKYDFWdn&DIisf^i9lXO^MlC62m8;+IeLvL z9%DPD@AANqv}$(7V@Wf&ijx60kk~9Aa|YF0n1KA4GBN*kNVh+3yrZ9qN8HNUV1_c%c5W8VM1LH=<6#4q=Odu`-Ffy<)FmXySh{-ED z>$&>)?kIb;A!ETIL*B%c+|0ac6{92-LnD>^{GyV8aF>b_Gc$d|T>aeeyn@`y(vpZ$ zCzC9{V*d<}!YFUQBEvjivn*3b{Q$Et_ei%wUPl*SPd~6zfR4KD#mV!MiID*SbD=59 delta 11 ScmZp0XmHr5kRU(NU;zLbF$6LI diff --git a/pg_data/base/16384/24581 b/pg_data/base/16384/24581 index 2bf92b71b8a960d3a3f29617bac57ac18d775bf5..8d1c65e9242411bd52a352412d0458ffc58f81ae 100644 GIT binary patch delta 123 zcmZo@U~Fh$)MNkxkNZ;?!8F4M`493e3LtipAu9t5kOoVD=!uG=8|{wCi%)!A8g3@)Hf%R2Uf~7{ufio%LLOe0P+++K{o}kRixC7+?l602bO9?f?J) delta 50 zcmZo@U~Fh$lw|+{j$Kn2L9_?M2l)^3ED9iYk|8St%S1=TjdsW6H+pQ5pBUf)02V_G ASpWb4 diff --git a/pg_data/base/16384/24583 b/pg_data/base/16384/24583 index e95ae8035238417b4665554c4affd426fa86f065..6de5b9c647abae8802e3443aaf055404b2020dec 100644 GIT binary patch delta 115 zcmZo@U~Fh$)MNmH5BH}qf@lwh5Aq-6SrkC*BtupP79b6l0MQc_MK{_VlNa}R08uQ% k@Bps(!F+{{j=SY28t}<5G6*>ECZ^0A?T)09S?;H2?qr delta 50 zcmZo@U~Fh$lw|;d1=puAf@lwh5Aq-6SrkC*BtupPmWhsv8|{wCZ}iwAKQX`q05Q1@ A4gdfE diff --git a/pg_data/base/16384/24584 b/pg_data/base/16384/24584 new file mode 100644 index 00000000..e69de29b diff --git a/pg_data/base/16384/24588 b/pg_data/base/16384/24588 new file mode 100644 index 00000000..e69de29b diff --git a/pg_data/base/16384/24589 b/pg_data/base/16384/24589 new file mode 100644 index 0000000000000000000000000000000000000000..6faf682803f5ab76f7dc2875ac59d9636273c8f8 GIT binary patch literal 8192 zcmeIuO$~rB3@5%+mbt;HT$vzE2z zW1FM&FsyvO!{Lr00RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAn>Qa(w@I% Cbq1gS literal 0 HcmV?d00001 diff --git a/pg_data/base/16384/24590 b/pg_data/base/16384/24590 new file mode 100644 index 0000000000000000000000000000000000000000..4546d1454b97af028dea43ed181278ca293078e3 GIT binary patch literal 8192 zcmeIuO$~rB3@5$AJFt;HT$vzE2z zW1FM&Fs!`4!{Lr00RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAn>Qa(w@Jh C=?1a@ literal 0 HcmV?d00001 diff --git a/pg_data/base/16384/24597 b/pg_data/base/16384/24597 new file mode 100644 index 0000000000000000000000000000000000000000..1cac0492f28c738c02eb4ed363d46794d3886e6c GIT binary patch literal 8192 zcmeIuOAUZ95CyD}bhm|}62?L~~J2!rkKgC5voX0)27JFpPTGpPg zZI05zu=4p1hdYJ@2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+z@Gw3d;S8Z Ca0W5} literal 0 HcmV?d00001 diff --git a/pg_data/base/16384/24612 b/pg_data/base/16384/24612 new file mode 100644 index 0000000000000000000000000000000000000000..be5a10615489d940a878cda7b4240c475f5a71e6 GIT binary patch literal 8192 zcmeIuO$~rB3CfRsL|o@RwHAA1&05x; zk8O_9!?5!C4u?C21PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfWV&uOMCtT Dh2{on literal 0 HcmV?d00001 diff --git a/pg_data/base/16384/24613 b/pg_data/base/16384/24613 new file mode 100644 index 00000000..e69de29b diff --git a/pg_data/base/16384/24618 b/pg_data/base/16384/24618 new file mode 100644 index 0000000000000000000000000000000000000000..314573e5a07b6c6c8ca4de18ccd371bd5ebb4efa GIT binary patch literal 8192 zcmeIuyA6Oa3B literal 0 HcmV?d00001 diff --git a/pg_data/base/16384/24632 b/pg_data/base/16384/24632 new file mode 100644 index 0000000000000000000000000000000000000000..ef32a783b623257d164224004582744b4eba803d GIT binary patch literal 8192 zcmeIuO$~rB3CfRsM4ZnxwHAA1&05x; zk8O_9!?5!H4u?C21PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfWV&uOMCtT D#K{K3 literal 0 HcmV?d00001 diff --git a/pg_data/base/16384/2579 b/pg_data/base/16384/2579 index 1f883b0e94e0e2d295d5bb68ca89a2f98eb30a98..23bc6c36633029caf39e219e8135522d43a0a210 100644 GIT binary patch delta 195 zcmZo@U~Fh$+`yv1t+8b~BLfipU@X|ouW*u4{=p0dh6_M^0EjmL@d6;80K^SITrfjn tqhqyLJrjcjg8)bu6fiM}qw&Si_@Zch5j4Iq8ea&FFNn5L%SgyDb`g93{Ji0v@Hg0W#<1>=Fa6^sl(>@Ww&pAF>Cs$krx z*drx-g9)UJfk6RGGBB_+urM%jNHAz7Ffgc2o+~#;#mKxCB!{k$4XRKBtT0x-ipk7u z@=bYlMVLZCkS-u#g({RvU|KB4JMHF3=CRel8HfrL5hKyL*f7bdawY4aRLKF zaY<=PWqfXGZc=JdaeQfUYEgVpo0(oD<@lM`9Q>y5#F1xiZ+F^CPqbGR88NS6R=1Ok}rRlz(a25~gk>m?w&UJ&Gb zh-cvLSH*BYA5aena3nz7&n+xVTgNMdtz%*kgE^iZEWn@(bUNw0UJsTfcR2+%!D#lY4wF$lxl3=LDc1elv4o;QPg9_Cb8u;;B|o(G0C z5HK+a!3=_WUI1heFkfT>y`Nl^npl#W5}#BF%@msO001Q-5WtoxIB1+H*uXY3F$lt( f4h;kT1ccKIvY9}+*aRL7FbDI2gJJW900VviqT|}a delta 41 wcmZoTz}RqraRZA3&x(R6j0`{!!EixfvVeof=0=C*oSP>&Tx8pPA;5qi03Lk~BLDyZ diff --git a/pg_data/base/16384/2608 b/pg_data/base/16384/2608 index 581c0e0c1619db5efb6a3115cb838baa650e896a..915ae595f64db074cf0747a0dbdb4c0163d204e4 100644 GIT binary patch delta 3415 zcma);*-{lj42E-11i?mJPy`onML^ue3$MI&-*;`?ao=~H;3c0^a!m zzJ&SH$sx{}!P7-yCg$ru-AQJsR$Hq4^Sv_tQ+xb5Q6XL%n!bLU-QP5^^K+$|{c3`X zXhePo`ams2t=;rzI??thPO(?9jnJ{=BIDM{bD;Oi?Pwz!EkL65(ENNgUNqXk4{~Yi5q1 zUj}9+AWAWfxqKM_H~t3%vPQWVj@!@xw_3U9 z)WZ7QY-pPG4S=f@eCBS(PK8pm@*YK{Fz#s`$NB&}R6Jh_1E17^i7T6m6`JT{<)AUF z8t9*OYLCnPbFht*fsdS^1!I%4+8g(93a)5;vwqF=fy;EzWjbV;O59|k#@%;<7Hf&3 z6k|EAPo4waOI$|7d>w0JiZXBpKbC=aRS?`jj%zO9mdVzE?zjWu7O_U#`2Ih(riXm( z4ZN-3Z@&K3F~!1OqqZL>{I7Ah9A~k1K*R~h`nB3GftTm^rpt6?6sI)l0=A24xL860 z-B1qGw1Q)?&}_@1n02q#yv z$>B=N=H9p~C0vKS|MRss@Nx-QR*D6h=q2Tl+!A-drE1rDCPP7a8Fll)({XA#BLfI9MleklNI0XiqCJ6;q1}fuqRoeKMJtfq3S_tVFm7n} zVPt4dV2o(;VchsIf>G@T6IdUE9++ZguwoEpVCGQx|DPQqZjr#ipqen5&n9VdN2UT? zxiVC_C7N;_i1K-XX{>?j3=DyjH?qpZH7PYtqnj=aRc?x=Tn4CII$?61W|t}~Z1|u`P0*Bz0+n)1uFYtM`;ZN)(ilx8 dA4p}wWOO$$LzNq$Ddz<$=K;H6b411paR4NNR+|6- delta 55 zcmZo@U}|V!+Q5>)v*PL$Mg}0b!w|tSSs>xeW<|&8jGGM{JCKNtAAA`%M`WxJ2LJ#B B6Bhsg diff --git a/pg_data/base/16384/2620 b/pg_data/base/16384/2620 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c7494c5cee7fff040b8ea674131c5a9304e644af 100644 GIT binary patch literal 8192 zcmeI1&raJw5XQ#@L&Cp?(jtK>;TBPZC?)@#5}%NI>ZwxI1Gg-y5JIA<6cD}UDG0Q* zdHc}q_sAnieZ%}U=ApqA=7*SvZZ9#P0dYVa5C_BoaX=gp2gCt! zKpYSU!~t_w_kc^=l#Qz z-ucJ=$?@?~zx%1%XtkU5WQG0sd5UO}-g~5oe5eU=C@B5G_$i;zQNoz=z0&6%8GLP( zi?60Hw+^S^^9%AVFkj`LeO=T`6dm%Cs}-4e__X||8c?jP!-}(P*(q&<%Nin!MCI`{7d-4 z_~ML1+W(61O)%fc_wQry#Se&K?B|!if5VPb|Koab^{;~ab9e^dqQXfQ!oug&|6KU~ E0;GZ^=l}o! literal 0 HcmV?d00001 diff --git a/pg_data/base/16384/2656 b/pg_data/base/16384/2656 index 6f6218b631bc390a636dcc5bedd0d064ae736cb1..804b9c528b60fe9367e0327c490234d49c3ac097 100644 GIT binary patch delta 139 zcmZo@U~Fh$+`yusU(r3CkpT!y7#ie1$g?Pb*bn9_FkArQ13 diff --git a/pg_data/base/16384/2658 b/pg_data/base/16384/2658 index 2e1ddb6e77eed2048255752ba45da2c01e96dc5e..0379b3f187dccf997b48e8ed38be0f092185df7c 100644 GIT binary patch delta 3311 zcmZ8jU2GIp6h61zg#}7kpu*B0m_o&hf1{}~8!#D+4~@QTVq+SE>8#x*Tdm#N4rWOV z+e9NPEYPxryHHw6Z3mO~KhkYQC7$0iFgWs8-tvi!^_niBk zbI+V}@64I$y}7!#H!$im*7g1>1>uTNKD)tqyEH$nTstthJ+OfU1(hn}>%WiO`1^?1 zbRgw=nx$Q7|ylU~HLN_n!l)D(ABxEz70YS|3!x6d(j12443p7JlfdH^L6rnL- zWJsmnA)Vrojga*~5c={3mF6$#GzR2=T0-1)4Lq;GqKmiEz{23$xi~%D+8i=1k zqJRoL2$>&<&`fFoPC$CVpk_b?sSGIeG;7eUY=lO$s3ME4gsy=L0E?%w1E-N7Wc;*2 zI%Eh~?nm;#Xg`jiU!iorN(OWtTs`yvOcJ*iV4Wb&j+&CpfoLCE}x2;Dk?BY{i;5kSRUIiXPCgi22{SPzV55F3bR zuw$638NHDLAQq3KqT@PUIgV<9BwzrIKt1@-aXgpM^T)8Qz!)$Bq=7ELIHt&byF^Xt zmG6}7o$te0-*`po%uz-DQ7UR`LPxRsC{}-q)xa1q0;GYi8^S04E$NR0QX0KVA$^NM zl>EHC6R&}DX`~kTXwqegGuX=d{=kEOtdCCS~?Tfjn}(;JleBzY8tI|c>7^Yt=u4h)8q== z)7r<+Ap+sFfx6#7_UKn6H%^XT>f#H;a7Ddl>5O zVd>?2qEA-b7eW7S_>|Z_4RbJG=i%8L3R~hKHV5y_eWY&Q$Mh;|b@xR?`X0dV33IoZ zTYA7656^hWJm3}3{F_nSKl0L`*d{+(5S22sz?KDA-momSKfrSOq4-ob@tUTG7#ggB zrxmQ355;y_xgc~|IWKm~XUy%N$MDrWk~_%S<#~~`9*4hVbU|!J<%<57>|el^`X1q4 zuUPYI3u2#acntpz9-1C=rmX$*2*cG!9PJTmo8xH(8>)YUi;stf-!L6#?kX=^g60n) z>Z1JOG4665KK`xNyr1VLyEpMLfZ;~j?_#NBFTc0;ADR7{t%ulPtKjWx{uldSE<=qq z&L3K~s&`vfQ-bg3hw)_{#@`y`6`&7D0tOHPuR(@}bt1?-{=jB{F@XQj`i2xrLK;Bh zkb3RFPt~cg)p9k1sQB9zChnjwgFn>M)?N6dQJA>hG4J)?hGww`yxlCq#O?2S%!|LR zVXOVwJl>~5vq4~PPi*kskozL@#ceTcwK?X!iIn3AnD0(>w6(N%wpey@uQ0aTj@RDP z5>25Md};T>F#2iGTIg25?T!Pjv#}U(_kW#9hkf-A-Zqa~otn8jBUa&m0GR zH>KCX;H_W{5~%_2CK5n^YRrqjqG4KH#M|B+m|IpA{JmT6EAVcD9}uoEt#SU|= delta 1456 zcmXYxPe@}|6vofB35vCChLIs{hnY96ZNqfY$6zs(wmgdq8C<+YDOm{3v=*Gf#p@pi z7lWpB6EtcxajrUX#)6BiN(Kh(B9u@DYSn1e_&*C54!D>_3bP0zrN5hL;QPLFzI*Pu z=iVpAVok9^W6l?S)%*9B5`H~WOT7{O?C}@BUHRo~Xz6T7HFpj#l+*Ra<%URe^{US@ zg{4Q1WFNT_NBcoHu*hog;n{y=svY<*H$9JjkH}zn7mV@wRj(zCq ztS!|U1_BEpGQ&V<7_DhXf~Y;q;IqTFco+d--v_8`K)jFUv@1z8%K;uu<7foUvhLj{ zM{DRZI5s-X;nNI3EFePc01H10JmNSSO}lcOwxu=<<l&N8{r>J8@}@Z$0LSgI7a?Xg8h(gnVJl zlG6mY9b*tsNYE1uCb&Jmg9a0Bw^T=&WR!0>!gBy;)Rq_;LgV;_QA;AkGk6}FL$l~H z8bG}f)<^8eovB8Bb47*pH>)amH7q^}a+1jHAHTl!?N7JwekbM^mRF3gul=nq`Wx!u z zmj0kh>6Rj8?M3D3%9^ruTTumc>$>XJYei-0#UgV)H`KeX!v3IGj-@w?>TR7~S0Vo; z36cL(RPXBY21SVtHu^T`*tNm#%f{<7bW(!O&1#~gBD$+Y)Uv6D8audV$a!5~Sfj_2 zP3*2M^_4!`R5x{EQ@yX7x77{(bc=2S+osVL^yxN}rngnn9}?SX&=W1?zRwN14%DFT z-B+LMKTX4P)2m_*Y2D+H>xX=>`~ltzLuIqA<bduNugAv?A{LT=zb0kYJDY%NY;B&2Lx z*|FmYK7v^S0aN9_9;D@x{JnpAgDNX(8Gc_%)qb=1dQvic&nns4Y1%I}%IhZ`OQ8O)=rKSm5Xf--0XjgRy9XWOrf4>$r% zp&4XYN})c~g*wm#@i8=lGN{OL2AV=M$Pi2O@1I($6VMnML0R7U{OWBw?Aey376xXE z8m(t7@Bz4N{Cajn?CaOT`&f701n+`>&g1?K@h|bZ_*}du{!Jd@fvXDq(S|GHxi}Rs z@qX-I71zWo;;MLAToE_LUeIMR3mEdkO9<=?{#Vohw=Zx4-U9mtb+LC~3EaZEcc3Qr z4phb7fr=O%!23v`tbli*B#s>j_EH=x3id)A6AJcR91{xm6ukK_pCpEM@|JB#sFMdnopV4#WwMWB&tjUA!-jBMWw497h)Hp18Vr9~bN?;1%8#dxg7V zuW$#vG4PQEy8~Xiz}w(uu%Evz_VciWHL*U7GYfW00dLVwahzGOw%9vF*YE=G5M4v| z4$(DaFNm%odqUSD4`riZv{8Z$o)B$B_Jn97vL{3vkv$>0i0lc`MPyHiE+RLKUog5T z8KD0@L)wTNe1^0U*=I-_k$r}=5jol}EL=qPis>SQ#d0Z2LKQd42A#z diff --git a/pg_data/base/16384/2662 b/pg_data/base/16384/2662 index 3b68991b5dc9343b428e1796b9beb406e2de90c7..e2e5499ae91778a0a8babffabb74b6b7b741efc7 100644 GIT binary patch delta 258 zcmZo@U}|V!+Q8z#GvV-bMg|~IVLBkbS-{~Al?V8Ec^ zkXaF5TvD1+8K0Y)o0M8q4CQ5}0QJt`0GYtcpv|BFR+m|lid9|(Di1U#6R09NKP45Y zce9etBVitknGkpAFn!>gEZ`u*FEGJ@;YEJ{!-ReZhK&Az&B_j)jOrWu4HyD|c)=tG zhKfl63<;AA7(9U3V1fa|37|p)AQtF%n7rFjmgz&^=4*~0c
    ^nmu37NWQx>Sl<$ zb%FAzo-4>stwi`s2VEZSXNZ1H1__5^u%}V=sRP5UpgujmBtNmZB;Lrx)Y2k8GcP5z z0;DY&M1TP=gBs9+0u*(q@~SWkfa)QE4bsTVpa69kC^R8fqsmJF9T0?Uu!RYB2Z*9M Vz`_L80RkWgK%xy4V@?74`~mGXk`Mp@ delta 118 zcmZoTz|?SnX#&LElw?p&rFF=%TBFiV3@%HGM0%! z977#M7fc~cofw0HLup|=vXM|TAm)ey<>8X`U<(USED`}J$Sg@MhMIwHfiQyvR325I r5Y&O0K%L1&sfi`2De*~_5MMwX2XTNP)BzxMTo5BbzTB*(drb%c*vMXq delta 41 wcmZo@U~Fh$+`yv1bD(buBLfiJU^L*}ETC|Jaq|a7Bd(1B28^4Hbk7L^00krrqyPW_ diff --git a/pg_data/base/16384/2665 b/pg_data/base/16384/2665 index 6c7039dd13552878de43a9ac2466d2b9fd8256d5..3c6c216d9e7d9fed82d2aa512d8ccf78263b77c2 100644 GIT binary patch delta 377 zcmZo@U~Fh$+`yv1^I*$#Mg}1G!RW!YSwP_gqr63T07F8z1A_z*f9MKeI03{JT?q^x zK)j;MVWVR(M?DjR1cQNtY63_n14D61X-Z{$Zfb5)YEf}~X>n>%d}c~~T6StBM4o|x zi9sBvdWddR4NMGT3<_}bOAF(XtzclN2O22KpaEBaEL$93fMT%-P@Px;#399*C8@iqG)^(G`=tzUkHsah|J&o(Sn~H09Z{Iq5uE@ delta 39 ucmZo@U~Fh$+`yv1-LY;8BLfiJVEnL|U*RC*<`0TZViN-vZ2n}y#|{7mu?^_} diff --git a/pg_data/base/16384/2667 b/pg_data/base/16384/2667 index 5ff452d1eaa2f4d143bdbe0a0ad4f321e6256f63..5b3e5eb1324a0591577248bba62e706aca3adaf4 100644 GIT binary patch delta 195 zcmZo@U~Fh$+`yv1ov?K}BLfipU@X|ouW*u4{=p0dh6_M^0EjmL@d6;80K^SITrfjn zqhqyLJrjcjg8+kZ0!SkWFfoV&`FaUp4wx?nQ_(DLw S01{si$mfUgH-EI?X9obtB^&?% delta 35 qcmZo@U~Fh$+`yv1y<+%0i{|jLN diff --git a/pg_data/base/16384/2673 b/pg_data/base/16384/2673 index bd48d8b5ca7b217562f92e46b16b8074f2e05430..4939f76e697cb1e71dcfc72ee2155310f7a2cce9 100644 GIT binary patch delta 1778 zcmZ|OIc(EV7zgmrA(2WGQ*)GrJLL>*DdsLxh0KDGC}8ezo1+l!aJJ>BO-a(QF+m1~ zjvXU;NCyUx7$VW36Y56CN(jW5k^duKiTX*l^p~aI_ulvXo^8*IcwVF&wqjFXK4^sN zQ>Ya74Xdc_qk>5@d6OoQaby%JB3UGjSV#&nkXp{9X{3sjky6fozyE8<)hSO2>S-q0 zNe_0qzUb}) z+q4Pw0~D~YpV(Hn*zaq27rxIO!o2`@1N=FAm*_L7T>SvDFc2`IAFdv>Q#5+U@)`_LT4}FR(%QJ@)ktSL^GPK|vk$nnvND-wf~@ z0p7<8tP*{$^Z3=muaR3n(iNg`l^d-TekH&ixH_TbV*F+HWx_9UpQWxSoaaKT@Y8U0 zLWb~D0e&*TPjLSQ+=t)4PRDsdi&)?oPlyS(*>&MZ1N;bFoltb{_$VFb2~AGrdrE7M F{{;##%|!qJ delta 125 zcmZo@U~On%-N53&<8djAkpT#5m?YRX3pjKzZoc7oh-qWM0mjWzZm%r4FJw&y%d4fEAl3wHM9cIdN?YSoQ|f1CH$i3XEqMg&aB!7&L%5V2%NU1rQ5# UZf{d!JOeaRgJt_84MsU}0N^br)c^nh diff --git a/pg_data/base/16384/2674 b/pg_data/base/16384/2674 index 37648bc4342bdcc86e8bc03a7847a95205ffddc6..cd8532fe5689b36debbc991b6a7e445b17b1985c 100644 GIT binary patch delta 1726 zcmZ{jIcyU_6oy}H#~8~4D}lfg61V^n2@$Q70tyP3mVjVxb1w;pxy^0PU9QA{iy#-c zK*)3{C@gK03b~9#lhO^sP(|9tr7Ofg-tq49$~)4Z*?jNK|7PBdEh}bOv1Y81%9cNm z5yqEDD`jhq=2)^@)u|1&s`ig>z2rVYcPLH2V?;X*nK>AO&MBTY229R^4k(^9?0?_m zQ>pGxw?;LZkK0;jd&F#f&}n&2r^SytB~Yu2c?qlowt(+6L^AeXr_#Jm6Ns%q23QW# zp$uXb#58aV-$iIIv`~Q$=4Ge})uA$28d^nuV_r8Yk3uW>r-wO*fDW}_x(c%jRDudn z7Rtk*i+Ee+K#LGTOITlkvd{#iK~4D8Aqy%(862(*-yS>+tjX3o(e@{M`fny`3YtTs z95i+Z8W{0}$Xm=q05zCK~*>}eu zK@VAg-^BxEv@2kApXKw7-eWvIu!sL$#>WTTM4*Jg9$a=kF`#l5bek>kBZ6)zzR4JV zG&jNJquB=T4?#CV_;qk|)Pidv3yR>QuI8%Z!K=BVcyObamH$@5?>{yy=n`ANTRO`) zKiQ1pTa4jOTx6X47|h3QcK`kbU0{JVUIjtt!AD)qS(fjm;{azAmyegfq0`{9(;B!x z1f9x-1PUSiWC%Y|`{islk0Uzj$9zn2xh?;Z2|B9W)`R;)&=Ho;`*)aeo}YL5ETxR$ eb01O*B diff --git a/pg_data/base/16384/2678 b/pg_data/base/16384/2678 index b884e88df1e95b2a9504d8bc57d4efe280a906a6..542e419cfd5f7bbc96eb681017280ee805fde02d 100644 GIT binary patch delta 209 zcmZo@U~Fh$+`yv1ed5$~Mg}1G!n+a delta 35 pcmZo@U~Fh$+`yv1t#NG%BLfhuVA`;mUtuQGMkg-e&6`a9xB$6x3Qhn3 diff --git a/pg_data/base/16384/2699 b/pg_data/base/16384/2699 index b7ed4da93fdc9c0b752be2374c9deba7eb8bd7ad..10bcbc6732a526edc127b473bc8733da29f8269f 100644 GIT binary patch literal 16384 zcmeI%v2DUY7{&3=u^kK{#1mkbR8bHMP*PH+L`=X486j1mf{c(2@CXc$A|u=gJX^7B zeLYnFlb-ILPp6ZA?U|X)e!n73wz5!!A}QNiR%WbjgE_rc%;`+1eWA)eG`6t&$t`A+$)QjHvf$NX1r>-aJdGCDe O`h)9{>+PS%T3i85mMA{} delta 42 rcmZo@U~F)ZWdMQ;|I!&jvW`PA0Ipx8)~xBXQBq7za0s#t;@Xl?(6^oOJQ;61)Q$9lZbt zFQKEOU(tqhhH^Hhx5<|t=$rQBfvqAUA7A$+J;|Y@^|W4TWb5;wDiu9XFBs?S+U!W* z^Xs$kSRsG_0tg_000IagfB*srAbt|o*RoB-#-`c$CG<4EO?Ku!Y009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_Kd1qjr{@9xDSCr7=5tI^Fk=?_Qa(_}ap zTqeDXUfAk119KbO?%p^1E$yY~{{8I!VPy83?tb6wH=O$kJ7&M`?%QU+ro9&z`26j^ zW%jG?zG?QJ_B%!QpZW)u|BAb}{Fj~k@h$(VySMx+^ZIQ6?a1;kyL-!jN&C6~pLS&V Om)yPOpZ^wRUHk$F{?kYR delta 42 rcmZo@U~F)ZWdMQ+|I-;kvP3+->8SKuhj$B4RzmjUEOnirS7_}sh?d} i)laS~>W=HOI&mGTA6%Ez_pXcTmg|DL>Dtn(r1KBP04Qky delta 42 rcmZo@U~F)ZWdMQ yAcFvdS^`Kt2=D=U0u1U2U=Eni59Es@@j)sikodenJ|_~N2gv72*jyFRrVjuAMg}1G!*qgkvw(vNll+TL1BM+v4h#i-4h$1|0vIZK3>XrC zn4!yn;Y6pyW_PD@b`L%V0R{z!;*!#o%J|&W+@#c^VxS>2I6y{$03Q%aI231=q@s)S zfW(3NGJ!HPI5rD;eKO^VD2F&Ahj|D4WPyeWOgGvm&ub84|IiL}O1s16%?-cVeE1jy ifmX(&*@V&l=Rz+eHy8bB-o#2i5Uqf3F|1rXl= z;uAo;1Bh1u@eCmD0OAS|dqM)!M#oF6^}GxQ3<4lC87vY&Brk&=l&=Hj>q7ZjP`(b7 zuL|XBL-{IDz9y6}3+1ar`7%(x8k8>$;>Q9lRD}wNK^3S#`Jzz10+i1OMyimR< Tl+Od@3qbiC37f;TSBU@s00k%? delta 41 wcmZo@U~Fh$+`yv1v*7X+Mg|}#V2R+~ET9m>!ZKq*0@FsvS8SW3w3mqh0Q(*cX8-^I diff --git a/pg_data/global/pg_control b/pg_data/global/pg_control index 037eaa80c953bf6f939b702202870c6a538f4f6c..82c5ae15aa6774d4398c5fad4d35af776688109f 100644 GIT binary patch delta 96 zcmZp0XmFSyVU#B3n8^SH7Eh)#f@lUP4dTEsNcbibh-6^kXqf0FCzJ%$1rY*m)z2sPC2s>s@oG3D}VF63r R7PYL+hK%RsCOa_l0s!BA5`6#w diff --git a/pg_data/pg_wal/000000010000000000000001 b/pg_data/pg_wal/000000010000000000000001 index 674239a580d39ae894a6b169ae9b45fc1e9a5fb9..b505c9a25ddd5782606dc5eb87f4697ece2dc7b8 100644 GIT binary patch delta 809262 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 z2}wvo5|WUFBqSjTNk~Exl8}TXBq0e&NJ0{lkc1>8Aqh!HLK2dYgd`*(2}wvo5|WUF zBqSjTNk~Exl8}TXBq0e&NJ0{lkc1>8Aqh!HLK2dYgd`*(2}wvo5|WUFBqSjTNk~Ex zl8}TXBq0e&NJ0{lkc1>8Aqh!HLK2dYgd`*(2}wvo5|WUFBqSjTNk~Exl8}TXBq0e& zNJ0{lkc1>8Aqh!HLK2dYgd`*(2}wvo5|WUFBqSjTNk~Exl8}TXBq0e&NJ0{lkc1>8 zAqh!HLK2dYgd`*(2}wvo5|WUFBqSjTNk~Exl8}TXBq0e&NJ0{lkc1>8Aqh!HLK2dY zgd`*(2}wvo5|WUFBqSjTNk~Exl8}TXBq0e&NJ0{lkc1>8Aqh!HLK2dYgd`*(2}wvo z5|WUFBqSjTNk~Exl8}TXBq0e&NJ0{lkc1>8Aqh!HLK2dYgd`*(2}wvo5|WUFBqSjT zNk~Exl8}TXBq0e&NJ0{lkc1>8Aqh!HLK2dYgd`*(2}wvo5|WUFBqSjTNk~Exl8}TX zBq0e&NJ0{lkc1>8Aqh!HLK2dYgd`*(2}wvo5|WUFBqSjTNk~Exl8}TXBq0e&NJ0{l zkc1>8Aqh!HLK2dYgd`*(2}wvo5|WUFBqSjTNk~Exl8}TXBq0e&NJ0{lkc1>8Aqh!H zLK2dYgd`*(2}wvo5|WUFBqSjTNk~Exl8}TXBq0e&NJ0{lkc1>8Aqh!HLK2dYgd`*( z2}wvo5|WUFBqSjTNk~Exl8}TXBq0e&NJ0{lkc1>8Aqh!HLK2dYgd`*(2}wvo5|WUF zBqSjTNk~Exl8}TXBq0e&NJ0{lkc1>8Aqh!HLK2dYgd`*(2}wvo5|WUFBpkjEtS-`O diff --git a/pg_data/pg_xact/0000 b/pg_data/pg_xact/0000 index b48986325b22e01778eab7ab2305f184b2af5ac6..3616a9bda7648b9c03a6ffdd116c6d0a1a925bc2 100644 GIT binary patch delta 16 XcmZp0XmFUYl_`{Q<3W4*i5Iv4HX#OC delta 15 WcmZp0XmFUYl~Hu#L3{a$7q|g1^adCJ