feat: add groups management API
Build and Push / build (release) Successful in 50s

- GET /auth/groups - list groups with search/pagination
- POST /auth/groups/sync-from-authentik - bulk sync from Authentik
- Group model and database migration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeroen Schweitzer
2026-01-01 22:28:06 +01:00
co-authored by Claude Opus 4.5
parent 3516376d92
commit e85c9a123d
8 changed files with 376 additions and 4 deletions
+2
View File
@@ -7,6 +7,7 @@ from src.db.models.user import User
from src.db.models.role import Role, UserRole
from src.db.models.user_preferences import UserPreferences
from src.db.models.api_key import ApiKey
from src.db.models.group import Group
__all__ = [
"User",
@@ -14,4 +15,5 @@ __all__ = [
"UserRole",
"UserPreferences",
"ApiKey",
"Group",
]
+85
View File
@@ -0,0 +1,85 @@
"""
Group Model
Represents groups synced from Authentik.
Groups are used for access control and user organization.
"""
import uuid
from datetime import datetime
from typing import TYPE_CHECKING, List
from sqlalchemy import String, Boolean, DateTime, func, Table, Column, ForeignKey
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from src.db.database import Base
# Association table for User-Group many-to-many relationship
user_groups = Table(
"user_groups",
Base.metadata,
Column("user_id", UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), primary_key=True),
Column("group_id", UUID(as_uuid=True), ForeignKey("groups.id", ondelete="CASCADE"), primary_key=True),
)
class Group(Base):
"""
Group model synced from Authentik
Groups are fetched from Authentik admin API and cached locally.
They represent organizational units for access control.
"""
__tablename__ = "groups"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
primary_key=True,
default=uuid.uuid4,
)
authentik_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
unique=True,
nullable=False,
index=True,
comment="Authentik group UUID",
)
name: Mapped[str] = mapped_column(
String(255),
unique=True,
nullable=False,
index=True,
)
is_superuser: Mapped[bool] = mapped_column(
Boolean,
default=False,
nullable=False,
comment="Whether members of this group have superuser privileges",
)
parent_name: Mapped[str | None] = mapped_column(
String(255),
nullable=True,
comment="Parent group name for hierarchy",
)
member_count: Mapped[int] = mapped_column(
default=0,
nullable=False,
comment="Number of users in this group",
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
nullable=False,
)
synced_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
onupdate=func.now(),
nullable=False,
comment="Last sync from Authentik",
)
def __repr__(self) -> str:
return f"<Group {self.name}>"