from datetime import datetime from typing import Literal from uuid import UUID from pydantic import BaseModel, ConfigDict, Field, model_validator BusinessRole = Literal["admin", "user"] ProjectRole = Literal["member", "viewer"] ProjectStatus = Literal["active", "inactive", "archived"] ProjectDbRole = Literal["biz_data", "iot_data"] class MetadataUserSyncRequest(BaseModel): keycloak_id: UUID username: str = Field(..., min_length=1, max_length=50) email: str = Field(..., min_length=1, max_length=100) role: BusinessRole = "user" is_active: bool = True class MetadataUsersBatchSyncRequest(BaseModel): users: list[MetadataUserSyncRequest] = Field(..., min_length=1, max_length=500) class MetadataUserUpdateRequest(BaseModel): role: BusinessRole | None = None is_active: bool | None = None class MetadataUserResponse(BaseModel): id: UUID keycloak_id: UUID username: str email: str role: str is_active: bool is_superuser: bool created_at: datetime updated_at: datetime last_login_at: datetime | None = None model_config = ConfigDict(from_attributes=True) class MetadataUserSyncResult(BaseModel): keycloak_id: UUID user: MetadataUserResponse | None = None success: bool error: str | None = None class ProjectMemberCreateRequest(BaseModel): user_id: UUID project_role: ProjectRole = "viewer" class ProjectMemberUpdateRequest(BaseModel): project_role: ProjectRole class ProjectMemberResponse(BaseModel): id: UUID user_id: UUID project_id: UUID project_role: str username: str email: str is_active: bool class AdminProjectCreateRequest(BaseModel): name: str = Field(..., min_length=1, max_length=100) code: str = Field(..., min_length=1, max_length=50) description: str | None = None gs_workspace: str = Field(..., min_length=1, max_length=100) map_extent: dict | None = None status: ProjectStatus = "active" class AdminProjectUpdateRequest(BaseModel): name: str | None = Field(default=None, min_length=1, max_length=100) code: str | None = Field(default=None, min_length=1, max_length=50) description: str | None = None gs_workspace: str | None = Field(default=None, min_length=1, max_length=100) map_extent: dict | None = None status: ProjectStatus | None = None class AdminProjectResponse(BaseModel): project_id: UUID name: str code: str description: str | None = None gs_workspace: str map_extent: dict | None = None status: str created_at: datetime updated_at: datetime class ProjectDatabaseUpsertRequest(BaseModel): db_role: ProjectDbRole dsn: str | None = Field(default=None, min_length=1) pool_min_size: int = Field(default=2, ge=1) pool_max_size: int = Field(default=10, ge=1) @model_validator(mode="after") def validate_pool_bounds(self): if self.pool_max_size < self.pool_min_size: raise ValueError("pool_max_size must be greater than or equal to pool_min_size") return self class ProjectDatabaseResponse(BaseModel): id: UUID project_id: UUID db_role: str db_type: str pool_min_size: int pool_max_size: int has_dsn: bool class ProjectDatabaseHealthRequest(BaseModel): dsn: str | None = Field(default=None, min_length=1) class ProjectDatabaseHealthResponse(BaseModel): project_id: UUID db_role: str db_type: str ok: bool detail: str