feat: add organization and member management APIs for admin and internal use

This commit is contained in:
Chris
2026-03-30 01:23:02 +08:00
parent f00b8cefaa
commit 0f0b197b32
14 changed files with 701 additions and 2 deletions

View File

@@ -0,0 +1,43 @@
from sqlalchemy import delete, select
from sqlalchemy.orm import Session
from app.models.member_organization import MemberOrganization
from app.models.organization import Organization
class MemberOrganizationsRepository:
def __init__(self, db: Session) -> None:
self.db = db
def list_organizations_by_member_id(self, member_id: str) -> list[Organization]:
stmt = (
select(Organization)
.join(MemberOrganization, MemberOrganization.organization_id == Organization.id)
.where(MemberOrganization.member_id == member_id)
.order_by(Organization.name.asc())
)
return list(self.db.scalars(stmt).all())
def add_if_not_exists(self, member_id: str, organization_id: str) -> MemberOrganization:
stmt = select(MemberOrganization).where(
MemberOrganization.member_id == member_id,
MemberOrganization.organization_id == organization_id,
)
existing = self.db.scalar(stmt)
if existing:
return existing
relation = MemberOrganization(member_id=member_id, organization_id=organization_id)
self.db.add(relation)
self.db.commit()
self.db.refresh(relation)
return relation
def remove(self, member_id: str, organization_id: str) -> int:
stmt = delete(MemberOrganization).where(
MemberOrganization.member_id == member_id,
MemberOrganization.organization_id == organization_id,
)
result = self.db.execute(stmt)
self.db.commit()
return int(result.rowcount or 0)

View File

@@ -0,0 +1,67 @@
from sqlalchemy import func, or_, select
from sqlalchemy.orm import Session
from app.models.organization import Organization
class OrganizationsRepository:
def __init__(self, db: Session) -> None:
self.db = db
def list(
self,
keyword: str | None = None,
status: str | None = None,
limit: int = 50,
offset: int = 0,
) -> tuple[list[Organization], int]:
stmt = select(Organization)
count_stmt = select(func.count()).select_from(Organization)
if keyword:
pattern = f"%{keyword}%"
cond = or_(Organization.org_code.ilike(pattern), Organization.name.ilike(pattern))
stmt = stmt.where(cond)
count_stmt = count_stmt.where(cond)
if status:
stmt = stmt.where(Organization.status == status)
count_stmt = count_stmt.where(Organization.status == status)
stmt = stmt.order_by(Organization.created_at.desc()).limit(limit).offset(offset)
items = list(self.db.scalars(stmt).all())
total = int(self.db.scalar(count_stmt) or 0)
return items, total
def get_by_id(self, org_id: str) -> Organization | None:
stmt = select(Organization).where(Organization.id == org_id)
return self.db.scalar(stmt)
def get_by_code(self, org_code: str) -> Organization | None:
stmt = select(Organization).where(Organization.org_code == org_code)
return self.db.scalar(stmt)
def create(self, org_code: str, name: str, tax_id: str | None, status: str = "active") -> Organization:
org = Organization(org_code=org_code, name=name, tax_id=tax_id, status=status)
self.db.add(org)
self.db.commit()
self.db.refresh(org)
return org
def update(
self,
org: Organization,
*,
name: str | None = None,
tax_id: str | None = None,
status: str | None = None,
) -> Organization:
if name is not None:
org.name = name
if tax_id is not None:
org.tax_id = tax_id
if status is not None:
org.status = status
self.db.commit()
self.db.refresh(org)
return org

View File

@@ -1,4 +1,4 @@
from sqlalchemy import select
from sqlalchemy import func, or_, select
from sqlalchemy.orm import Session
from app.models.user import User
@@ -12,6 +12,35 @@ class UsersRepository:
stmt = select(User).where(User.authentik_sub == authentik_sub)
return self.db.scalar(stmt)
def get_by_id(self, user_id: str) -> User | None:
stmt = select(User).where(User.id == user_id)
return self.db.scalar(stmt)
def list(
self,
keyword: str | None = None,
is_active: bool | None = None,
limit: int = 50,
offset: int = 0,
) -> tuple[list[User], int]:
stmt = select(User)
count_stmt = select(func.count()).select_from(User)
if keyword:
pattern = f"%{keyword}%"
cond = or_(User.authentik_sub.ilike(pattern), User.email.ilike(pattern), User.display_name.ilike(pattern))
stmt = stmt.where(cond)
count_stmt = count_stmt.where(cond)
if is_active is not None:
stmt = stmt.where(User.is_active == is_active)
count_stmt = count_stmt.where(User.is_active == is_active)
stmt = stmt.order_by(User.created_at.desc()).limit(limit).offset(offset)
items = list(self.db.scalars(stmt).all())
total = int(self.db.scalar(count_stmt) or 0)
return items, total
def upsert_by_sub(
self,
authentik_sub: str,
@@ -40,3 +69,21 @@ class UsersRepository:
self.db.commit()
self.db.refresh(user)
return user
def update_member(
self,
user: User,
*,
email: str | None = None,
display_name: str | None = None,
is_active: bool | None = None,
) -> User:
if email is not None:
user.email = email
if display_name is not None:
user.display_name = display_name
if is_active is not None:
user.is_active = is_active
self.db.commit()
self.db.refresh(user)
return user