Compare commits
13 Commits
ade60bdbaa
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
405000ded5 | ||
|
|
94cec746cb | ||
|
|
c032020f59 | ||
|
|
60b34a0817 | ||
|
|
d2b6957013 | ||
|
|
7b9915e81c | ||
|
|
dc51af8c39 | ||
|
|
60608fe199 | ||
|
|
7c4364b52f | ||
|
|
065f1d52f0 | ||
|
|
4ae7e75a96 | ||
|
|
d430b69888 | ||
|
|
ed7a0344e0 |
19
.env
19
.env
@@ -1,19 +0,0 @@
|
|||||||
# memberapi.ose.tw backend env (development)
|
|
||||||
APP_ENV=development
|
|
||||||
PORT=8000
|
|
||||||
|
|
||||||
DB_HOST=127.0.0.1
|
|
||||||
DB_PORT=54321
|
|
||||||
DB_NAME=member_center
|
|
||||||
DB_USER=member_ose
|
|
||||||
DB_PASSWORD=CHANGE_ME
|
|
||||||
|
|
||||||
KEYCLOAK_BASE_URL=
|
|
||||||
KEYCLOAK_REALM=
|
|
||||||
KEYCLOAK_VERIFY_TLS=true
|
|
||||||
KEYCLOAK_ISSUER=
|
|
||||||
KEYCLOAK_JWKS_URL=
|
|
||||||
KEYCLOAK_AUDIENCE=
|
|
||||||
|
|
||||||
PUBLIC_FRONTEND_ORIGINS=https://member.ose.tw,https://mkt.ose.tw,https://admin.ose.tw
|
|
||||||
INTERNAL_SHARED_SECRET=CHANGE_ME
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# memberapi.ose.tw backend env (local development)
|
# memberapi.ose.tw backend env (development)
|
||||||
APP_ENV=development
|
APP_ENV=development
|
||||||
PORT=8000
|
PORT=8000
|
||||||
|
|
||||||
@@ -8,20 +8,33 @@ DB_NAME=member.ose.tw
|
|||||||
DB_USER=member_ose
|
DB_USER=member_ose
|
||||||
DB_PASSWORD=Dmrax5bKDf
|
DB_PASSWORD=Dmrax5bKDf
|
||||||
|
|
||||||
KEYCLOAK_BASE_URL=https://auth.ose.tw
|
# Keycloak 參數說明:
|
||||||
|
# - KEYCLOAK_ISSUER 必須與 token 的 iss 完全一致(建議填公開網址)。
|
||||||
|
# - KEYCLOAK_BASE_URL 是後端對 Keycloak 的基底網址(development 統一走公開入口)。
|
||||||
|
# - KEYCLOAK_JWKS_URL / KEYCLOAK_TOKEN_ENDPOINT / KEYCLOAK_USERINFO_ENDPOINT 可明確覆寫端點。
|
||||||
|
# - KEYCLOAK_AUDIENCE 可選,但建議設定以啟用 aud 驗證。
|
||||||
|
# - KEYCLOAK_CLIENT_* 給 /auth/oidc/exchange 與 /auth/refresh 使用。
|
||||||
|
# - KEYCLOAK_ADMIN_CLIENT_* 給 Keycloak Admin API 同步流程使用。
|
||||||
|
KEYCLOAK_BASE_URL=https://auth.ose.tw/
|
||||||
KEYCLOAK_REALM=master
|
KEYCLOAK_REALM=master
|
||||||
KEYCLOAK_VERIFY_TLS=true
|
KEYCLOAK_VERIFY_TLS=true
|
||||||
|
KEYCLOAK_ISSUER=https://auth.ose.tw/realms/master
|
||||||
|
KEYCLOAK_JWKS_URL=https://auth.ose.tw/realms/master/protocol/openid-connect/certs
|
||||||
|
KEYCLOAK_AUDIENCE=
|
||||||
KEYCLOAK_CLIENT_ID=member-frontend
|
KEYCLOAK_CLIENT_ID=member-frontend
|
||||||
KEYCLOAK_CLIENT_SECRET=bp2I0HWyz5cjcu5RGnBPXNC2vjCdckkv
|
KEYCLOAK_CLIENT_SECRET=bp2I0HWyz5cjcu5RGnBPXNC2vjCdckkv
|
||||||
|
KEYCLOAK_TOKEN_ENDPOINT=https://auth.ose.tw/realms/master/protocol/openid-connect/token
|
||||||
|
KEYCLOAK_USERINFO_ENDPOINT=https://auth.ose.tw/realms/master/protocol/openid-connect/userinfo
|
||||||
KEYCLOAK_ADMIN_CLIENT_ID=member-backend
|
KEYCLOAK_ADMIN_CLIENT_ID=member-backend
|
||||||
KEYCLOAK_ADMIN_CLIENT_SECRET=hat8BmxlP0eZ7CXuKbV4HwQ3abLHzAJ9
|
KEYCLOAK_ADMIN_CLIENT_SECRET=hat8BmxlP0eZ7CXuKbV4HwQ3abLHzAJ9
|
||||||
KEYCLOAK_ADMIN_REALM=master
|
KEYCLOAK_ADMIN_REALM=master
|
||||||
|
|
||||||
PUBLIC_FRONTEND_ORIGINS=http://127.0.0.1:5173,http://localhost:5173
|
PUBLIC_FRONTEND_ORIGINS=http://127.0.0.1:5173,http://localhost:5173,https://member.ose.tw,https://mkt.ose.tw,https://admin.ose.tw
|
||||||
INTERNAL_SHARED_SECRET=CHANGE_ME
|
INTERNAL_SHARED_SECRET=CHANGE_ME
|
||||||
MEMBER_REQUIRED_REALM_ROLES=admin,manager
|
MEMBER_REQUIRED_REALM_ROLES=admin,manager
|
||||||
ADMIN_REQUIRED_REALM_ROLES=admin,manager
|
ADMIN_REQUIRED_REALM_ROLES=admin,manager
|
||||||
|
|
||||||
|
# Cache backend: memory | redis
|
||||||
CACHE_BACKEND=memory
|
CACHE_BACKEND=memory
|
||||||
CACHE_REDIS_URL=redis://127.0.0.1:6379/0
|
CACHE_REDIS_URL=redis://127.0.0.1:6379/0
|
||||||
CACHE_PREFIX=memberapi
|
CACHE_PREFIX=memberapi
|
||||||
|
|||||||
35
.env.example
35
.env.example
@@ -1,35 +0,0 @@
|
|||||||
# memberapi.ose.tw backend env (development)
|
|
||||||
APP_ENV=development
|
|
||||||
PORT=8000
|
|
||||||
|
|
||||||
DB_HOST=127.0.0.1
|
|
||||||
DB_PORT=54321
|
|
||||||
DB_NAME=member_center
|
|
||||||
DB_USER=member_ose
|
|
||||||
DB_PASSWORD=CHANGE_ME
|
|
||||||
|
|
||||||
# Keycloak (preferred when KEYCLOAK_BASE_URL + KEYCLOAK_REALM are set)
|
|
||||||
KEYCLOAK_BASE_URL=
|
|
||||||
KEYCLOAK_REALM=
|
|
||||||
KEYCLOAK_VERIFY_TLS=true
|
|
||||||
KEYCLOAK_ISSUER=
|
|
||||||
KEYCLOAK_JWKS_URL=
|
|
||||||
KEYCLOAK_AUDIENCE=
|
|
||||||
KEYCLOAK_CLIENT_ID=
|
|
||||||
KEYCLOAK_CLIENT_SECRET=
|
|
||||||
KEYCLOAK_TOKEN_ENDPOINT=
|
|
||||||
KEYCLOAK_USERINFO_ENDPOINT=
|
|
||||||
KEYCLOAK_ADMIN_CLIENT_ID=
|
|
||||||
KEYCLOAK_ADMIN_CLIENT_SECRET=
|
|
||||||
KEYCLOAK_ADMIN_REALM=
|
|
||||||
|
|
||||||
PUBLIC_FRONTEND_ORIGINS=https://member.ose.tw,https://mkt.ose.tw,https://admin.ose.tw
|
|
||||||
INTERNAL_SHARED_SECRET=CHANGE_ME
|
|
||||||
MEMBER_REQUIRED_REALM_ROLES=admin,manager
|
|
||||||
ADMIN_REQUIRED_REALM_ROLES=admin,manager
|
|
||||||
|
|
||||||
# Cache backend: memory | redis
|
|
||||||
CACHE_BACKEND=memory
|
|
||||||
CACHE_REDIS_URL=redis://127.0.0.1:6379/0
|
|
||||||
CACHE_PREFIX=memberapi
|
|
||||||
CACHE_DEFAULT_TTL_SECONDS=30
|
|
||||||
41
.env.production
Normal file
41
.env.production
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# memberapi.ose.tw backend env (development)
|
||||||
|
APP_ENV=development
|
||||||
|
PORT=8000
|
||||||
|
|
||||||
|
DB_HOST=postgresql
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_NAME=member.ose.tw
|
||||||
|
DB_USER=member_ose
|
||||||
|
DB_PASSWORD=Dmrax5bKDf
|
||||||
|
|
||||||
|
# Keycloak 參數說明:
|
||||||
|
# - KEYCLOAK_ISSUER 必須與 token 的 iss 完全一致(建議填公開網址)。
|
||||||
|
# - KEYCLOAK_BASE_URL 是後端對 Keycloak 的基底網址(development 統一走公開入口)。
|
||||||
|
# - KEYCLOAK_JWKS_URL / KEYCLOAK_TOKEN_ENDPOINT / KEYCLOAK_USERINFO_ENDPOINT 可明確覆寫端點。
|
||||||
|
# - KEYCLOAK_AUDIENCE 可選,但建議設定以啟用 aud 驗證。
|
||||||
|
# - KEYCLOAK_CLIENT_* 給 /auth/oidc/exchange 與 /auth/refresh 使用。
|
||||||
|
# - KEYCLOAK_ADMIN_CLIENT_* 給 Keycloak Admin API 同步流程使用。
|
||||||
|
KEYCLOAK_BASE_URL=http://auth_ose_tw:8080
|
||||||
|
KEYCLOAK_REALM=master
|
||||||
|
KEYCLOAK_VERIFY_TLS=true
|
||||||
|
KEYCLOAK_ISSUER=https://auth.ose.tw/realms/master
|
||||||
|
KEYCLOAK_JWKS_URL=http://auth_ose_tw:8080/realms/master/protocol/openid-connect/certs
|
||||||
|
KEYCLOAK_AUDIENCE=
|
||||||
|
KEYCLOAK_CLIENT_ID=member-frontend
|
||||||
|
KEYCLOAK_CLIENT_SECRET=bp2I0HWyz5cjcu5RGnBPXNC2vjCdckkv
|
||||||
|
KEYCLOAK_TOKEN_ENDPOINT=http://auth_ose_tw:8080/realms/master/protocol/openid-connect/token
|
||||||
|
KEYCLOAK_USERINFO_ENDPOINT=http://auth_ose_tw:8080/realms/master/protocol/openid-connect/userinfo
|
||||||
|
KEYCLOAK_ADMIN_CLIENT_ID=member-backend
|
||||||
|
KEYCLOAK_ADMIN_CLIENT_SECRET=hat8BmxlP0eZ7CXuKbV4HwQ3abLHzAJ9
|
||||||
|
KEYCLOAK_ADMIN_REALM=master
|
||||||
|
|
||||||
|
PUBLIC_FRONTEND_ORIGINS=https://member.ose.tw,https://mkt.ose.tw,https://admin.ose.tw
|
||||||
|
INTERNAL_SHARED_SECRET=CHANGE_ME
|
||||||
|
MEMBER_REQUIRED_REALM_ROLES=admin,manager
|
||||||
|
ADMIN_REQUIRED_REALM_ROLES=admin,manager
|
||||||
|
|
||||||
|
# Cache backend: memory | redis
|
||||||
|
CACHE_BACKEND=redis
|
||||||
|
CACHE_REDIS_URL=redis://cache/0
|
||||||
|
CACHE_PREFIX=memberapi
|
||||||
|
CACHE_DEFAULT_TTL_SECONDS=30
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# memberapi.ose.tw backend env (production)
|
|
||||||
APP_ENV=production
|
|
||||||
PORT=8000
|
|
||||||
|
|
||||||
DB_HOST=postgresql
|
|
||||||
DB_PORT=5432
|
|
||||||
DB_NAME=member_center
|
|
||||||
DB_USER=member_ose
|
|
||||||
DB_PASSWORD=CHANGE_ME
|
|
||||||
|
|
||||||
# Keycloak (preferred when KEYCLOAK_BASE_URL + KEYCLOAK_REALM are set)
|
|
||||||
KEYCLOAK_BASE_URL=
|
|
||||||
KEYCLOAK_REALM=
|
|
||||||
KEYCLOAK_VERIFY_TLS=true
|
|
||||||
KEYCLOAK_ISSUER=
|
|
||||||
KEYCLOAK_JWKS_URL=
|
|
||||||
KEYCLOAK_AUDIENCE=
|
|
||||||
KEYCLOAK_CLIENT_ID=
|
|
||||||
KEYCLOAK_CLIENT_SECRET=
|
|
||||||
KEYCLOAK_TOKEN_ENDPOINT=
|
|
||||||
KEYCLOAK_USERINFO_ENDPOINT=
|
|
||||||
KEYCLOAK_ADMIN_CLIENT_ID=
|
|
||||||
KEYCLOAK_ADMIN_CLIENT_SECRET=
|
|
||||||
KEYCLOAK_ADMIN_REALM=
|
|
||||||
|
|
||||||
PUBLIC_FRONTEND_ORIGINS=https://member.ose.tw,https://mkt.ose.tw,https://admin.ose.tw
|
|
||||||
INTERNAL_SHARED_SECRET=CHANGE_ME
|
|
||||||
MEMBER_REQUIRED_REALM_ROLES=admin,manager
|
|
||||||
ADMIN_REQUIRED_REALM_ROLES=admin,manager
|
|
||||||
|
|
||||||
# Cache backend: memory | redis
|
|
||||||
CACHE_BACKEND=redis
|
|
||||||
CACHE_REDIS_URL=redis://redis:6379/0
|
|
||||||
CACHE_PREFIX=memberapi
|
|
||||||
CACHE_DEFAULT_TTL_SECONDS=30
|
|
||||||
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Python cache
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.pytest_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# Virtualenv
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
|
||||||
|
# Build metadata
|
||||||
|
*.egg-info/
|
||||||
|
|
||||||
|
# Local env and logs
|
||||||
|
.env
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
13
README.md
13
README.md
@@ -7,7 +7,7 @@ cd backend
|
|||||||
python -m venv .venv
|
python -m venv .venv
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
pip install -e .
|
pip install -e .
|
||||||
cp .env.example .env
|
# local development uses .env.development directly
|
||||||
psql "$DATABASE_URL" -f scripts/init_schema.sql
|
psql "$DATABASE_URL" -f scripts/init_schema.sql
|
||||||
./scripts/start_dev.sh
|
./scripts/start_dev.sh
|
||||||
```
|
```
|
||||||
@@ -75,6 +75,8 @@ curl http://127.0.0.1:8000/healthz
|
|||||||
- `GET /admin/members/{user_sub}/roles`
|
- `GET /admin/members/{user_sub}/roles`
|
||||||
- `GET/POST/PATCH/DELETE /admin/api-clients`
|
- `GET/POST/PATCH/DELETE /admin/api-clients`
|
||||||
|
|
||||||
|
> `roles` 現在包含 `role_code` 欄位(建議用於跨系統權限語意解析);`role_key` 保留為唯一識別鍵。
|
||||||
|
|
||||||
### Internal APIs (`X-Client-Key` + `X-API-Key`)
|
### Internal APIs (`X-Client-Key` + `X-API-Key`)
|
||||||
- `GET /internal/companies`
|
- `GET /internal/companies`
|
||||||
- `GET /internal/sites`
|
- `GET /internal/sites`
|
||||||
@@ -83,4 +85,11 @@ curl http://127.0.0.1:8000/healthz
|
|||||||
- `GET /internal/members`
|
- `GET /internal/members`
|
||||||
- `POST /internal/users/upsert-by-sub`
|
- `POST /internal/users/upsert-by-sub`
|
||||||
- `GET /internal/users/{user_sub}/roles`
|
- `GET /internal/users/{user_sub}/roles`
|
||||||
- `POST /internal/idp/users/ensure`
|
- `POST /internal/provider/users/ensure`
|
||||||
|
|
||||||
|
## DB Migration
|
||||||
|
|
||||||
|
- 既有 DB 升級(新增 `roles.role_code`):
|
||||||
|
```bash
|
||||||
|
psql "$DATABASE_URL" -f scripts/migrate_add_role_code.sql
|
||||||
|
```
|
||||||
|
|||||||
@@ -71,6 +71,13 @@ def _generate_unique_key(prefix: str, exists_check) -> str:
|
|||||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"failed_generate_{prefix.lower()}_key")
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"failed_generate_{prefix.lower()}_key")
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_role_code(value: str | None, fallback_name: str) -> str:
|
||||||
|
candidate = (value or "").strip()
|
||||||
|
if candidate:
|
||||||
|
return candidate
|
||||||
|
return fallback_name.strip()
|
||||||
|
|
||||||
|
|
||||||
def _company_item(company) -> CompanyItem:
|
def _company_item(company) -> CompanyItem:
|
||||||
return CompanyItem(
|
return CompanyItem(
|
||||||
id=company.id,
|
id=company.id,
|
||||||
@@ -451,6 +458,7 @@ def list_roles(
|
|||||||
RoleItem(
|
RoleItem(
|
||||||
id=row.id,
|
id=row.id,
|
||||||
role_key=row.role_key,
|
role_key=row.role_key,
|
||||||
|
role_code=row.role_code,
|
||||||
system_key=system_map[row.system_id].system_key,
|
system_key=system_map[row.system_id].system_key,
|
||||||
system_name=system_map[row.system_id].name,
|
system_name=system_map[row.system_id].name,
|
||||||
name=row.name,
|
name=row.name,
|
||||||
@@ -481,9 +489,11 @@ def create_role(payload: RoleCreateRequest, db: Session = Depends(get_db)) -> Ro
|
|||||||
)
|
)
|
||||||
|
|
||||||
role_key = _generate_unique_key("RL", lambda key: roles_repo.get_by_key(key) is not None)
|
role_key = _generate_unique_key("RL", lambda key: roles_repo.get_by_key(key) is not None)
|
||||||
|
role_code = _resolve_role_code(payload.role_code, payload.name)
|
||||||
try:
|
try:
|
||||||
row = roles_repo.create(
|
row = roles_repo.create(
|
||||||
role_key=role_key,
|
role_key=role_key,
|
||||||
|
role_code=role_code,
|
||||||
system_id=system.id,
|
system_id=system.id,
|
||||||
name=payload.name,
|
name=payload.name,
|
||||||
description=payload.description,
|
description=payload.description,
|
||||||
@@ -496,6 +506,7 @@ def create_role(payload: RoleCreateRequest, db: Session = Depends(get_db)) -> Ro
|
|||||||
return RoleItem(
|
return RoleItem(
|
||||||
id=row.id,
|
id=row.id,
|
||||||
role_key=row.role_key,
|
role_key=row.role_key,
|
||||||
|
role_code=row.role_code,
|
||||||
system_key=system.system_key,
|
system_key=system.system_key,
|
||||||
system_name=system.name,
|
system_name=system.name,
|
||||||
name=row.name,
|
name=row.name,
|
||||||
@@ -527,6 +538,7 @@ def update_role(role_key: str, payload: RoleUpdateRequest, db: Session = Depends
|
|||||||
system_id = system.id
|
system_id = system.id
|
||||||
target_system = system
|
target_system = system
|
||||||
next_provider_role_name = payload.name if payload.name is not None else role.name
|
next_provider_role_name = payload.name if payload.name is not None else role.name
|
||||||
|
next_role_code = _resolve_role_code(payload.role_code, next_provider_role_name)
|
||||||
next_description = payload.description if payload.description is not None else role.description
|
next_description = payload.description if payload.description is not None else role.description
|
||||||
|
|
||||||
if target_system.id != old_system.id:
|
if target_system.id != old_system.id:
|
||||||
@@ -551,6 +563,7 @@ def update_role(role_key: str, payload: RoleUpdateRequest, db: Session = Depends
|
|||||||
role = roles_repo.update(
|
role = roles_repo.update(
|
||||||
role,
|
role,
|
||||||
system_id=system_id,
|
system_id=system_id,
|
||||||
|
role_code=next_role_code,
|
||||||
name=payload.name,
|
name=payload.name,
|
||||||
description=payload.description,
|
description=payload.description,
|
||||||
status=payload.status,
|
status=payload.status,
|
||||||
@@ -566,6 +579,7 @@ def update_role(role_key: str, payload: RoleUpdateRequest, db: Session = Depends
|
|||||||
return RoleItem(
|
return RoleItem(
|
||||||
id=role.id,
|
id=role.id,
|
||||||
role_key=role.role_key,
|
role_key=role.role_key,
|
||||||
|
role_code=role.role_code,
|
||||||
system_key=system.system_key,
|
system_key=system.system_key,
|
||||||
system_name=system.name,
|
system_name=system.name,
|
||||||
name=role.name,
|
name=role.name,
|
||||||
@@ -610,6 +624,7 @@ def list_system_roles(system_key: str, db: Session = Depends(get_db)) -> SystemR
|
|||||||
RoleItem(
|
RoleItem(
|
||||||
id=row.id,
|
id=row.id,
|
||||||
role_key=row.role_key,
|
role_key=row.role_key,
|
||||||
|
role_code=row.role_code,
|
||||||
system_key=system.system_key,
|
system_key=system.system_key,
|
||||||
system_name=system.name,
|
system_name=system.name,
|
||||||
name=row.name,
|
name=row.name,
|
||||||
@@ -637,6 +652,7 @@ def list_site_roles(site_key: str, db: Session = Depends(get_db)) -> SiteRolesRe
|
|||||||
SiteRoleItem(
|
SiteRoleItem(
|
||||||
id=site_role.id,
|
id=site_role.id,
|
||||||
role_key=role.role_key,
|
role_key=role.role_key,
|
||||||
|
role_code=role.role_code,
|
||||||
role_name=role.name,
|
role_name=role.name,
|
||||||
system_key=system.system_key,
|
system_key=system.system_key,
|
||||||
system_name=system.name,
|
system_name=system.name,
|
||||||
@@ -972,6 +988,7 @@ def list_member_effective_roles(user_sub: str, db: Session = Depends(get_db)) ->
|
|||||||
system_key=system.system_key,
|
system_key=system.system_key,
|
||||||
system_name=system.name,
|
system_name=system.name,
|
||||||
role_key=role.role_key,
|
role_key=role.role_key,
|
||||||
|
role_code=role.role_code,
|
||||||
role_name=role.name,
|
role_name=role.name,
|
||||||
)
|
)
|
||||||
for site, company, role, system in rows
|
for site, company, role, system in rows
|
||||||
@@ -1005,7 +1022,6 @@ def list_api_clients(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/sync/from-provider")
|
@router.post("/sync/from-provider")
|
||||||
@router.post("/sync/from-keycloak", include_in_schema=False)
|
|
||||||
def sync_catalog_from_provider(db: Session = Depends(get_db), force: bool = Query(default=True)) -> dict[str, int]:
|
def sync_catalog_from_provider(db: Session = Depends(get_db), force: bool = Query(default=True)) -> dict[str, int]:
|
||||||
return sync_from_provider(db, force=force)
|
return sync_from_provider(db, force=force)
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ def upsert_user_by_sub(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _build_user_role_rows(db: Session, user_sub: str) -> list[tuple[str, str, str, str, str, str, str, str]]:
|
def _build_user_role_rows(db: Session, user_sub: str) -> list[tuple[str, str, str, str, str, str, str, str, str]]:
|
||||||
users_repo = UsersRepository(db)
|
users_repo = UsersRepository(db)
|
||||||
user_sites_repo = UserSitesRepository(db)
|
user_sites_repo = UserSitesRepository(db)
|
||||||
|
|
||||||
@@ -59,6 +59,7 @@ def _build_user_role_rows(db: Session, user_sub: str) -> list[tuple[str, str, st
|
|||||||
system.system_key,
|
system.system_key,
|
||||||
system.name,
|
system.name,
|
||||||
role.role_key,
|
role.role_key,
|
||||||
|
role.role_code,
|
||||||
role.name,
|
role.name,
|
||||||
)
|
)
|
||||||
for site, company, role, system in rows
|
for site, company, role, system in rows
|
||||||
@@ -84,6 +85,7 @@ def get_user_roles(user_sub: str, db: Session = Depends(get_db)) -> InternalUser
|
|||||||
system_key=system_key,
|
system_key=system_key,
|
||||||
system_name=system_name,
|
system_name=system_name,
|
||||||
role_key=role_key,
|
role_key=role_key,
|
||||||
|
role_code=role_code,
|
||||||
role_name=role_name,
|
role_name=role_name,
|
||||||
)
|
)
|
||||||
for (
|
for (
|
||||||
@@ -94,6 +96,7 @@ def get_user_roles(user_sub: str, db: Session = Depends(get_db)) -> InternalUser
|
|||||||
system_key,
|
system_key,
|
||||||
system_name,
|
system_name,
|
||||||
role_key,
|
role_key,
|
||||||
|
role_code,
|
||||||
role_name,
|
role_name,
|
||||||
) in rows
|
) in rows
|
||||||
],
|
],
|
||||||
@@ -103,8 +106,6 @@ def get_user_roles(user_sub: str, db: Session = Depends(get_db)) -> InternalUser
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/provider/users/ensure", response_model=ProviderEnsureUserResponse)
|
@router.post("/provider/users/ensure", response_model=ProviderEnsureUserResponse)
|
||||||
@router.post("/idp/users/ensure", response_model=ProviderEnsureUserResponse, include_in_schema=False)
|
|
||||||
@router.post("/keycloak/users/ensure", response_model=ProviderEnsureUserResponse, include_in_schema=False)
|
|
||||||
def ensure_idp_user(
|
def ensure_idp_user(
|
||||||
payload: ProviderEnsureUserRequest,
|
payload: ProviderEnsureUserRequest,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ def internal_list_roles(
|
|||||||
InternalRoleItem(
|
InternalRoleItem(
|
||||||
id=i.id,
|
id=i.id,
|
||||||
role_key=i.role_key,
|
role_key=i.role_key,
|
||||||
|
role_code=i.role_code,
|
||||||
system_key=system_map[i.system_id].system_key,
|
system_key=system_map[i.system_id].system_key,
|
||||||
system_name=system_map[i.system_id].name,
|
system_name=system_map[i.system_id].name,
|
||||||
name=i.name,
|
name=i.name,
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ def get_my_permission_snapshot(
|
|||||||
system.system_key,
|
system.system_key,
|
||||||
system.name,
|
system.name,
|
||||||
role.role_key,
|
role.role_key,
|
||||||
|
role.role_code,
|
||||||
role.name,
|
role.name,
|
||||||
)
|
)
|
||||||
for site, company, role, system in rows
|
for site, company, role, system in rows
|
||||||
|
|||||||
@@ -10,10 +10,14 @@ from app.db.base import Base
|
|||||||
|
|
||||||
class Role(Base):
|
class Role(Base):
|
||||||
__tablename__ = "roles"
|
__tablename__ = "roles"
|
||||||
__table_args__ = (UniqueConstraint("system_id", "name", name="uq_roles_system_name"),)
|
__table_args__ = (
|
||||||
|
UniqueConstraint("system_id", "name", name="uq_roles_system_name"),
|
||||||
|
UniqueConstraint("system_id", "role_code", name="uq_roles_system_role_code"),
|
||||||
|
)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(UUID(as_uuid=False), primary_key=True, default=lambda: str(uuid4()))
|
id: Mapped[str] = mapped_column(UUID(as_uuid=False), primary_key=True, default=lambda: str(uuid4()))
|
||||||
role_key: Mapped[str] = mapped_column(String(128), unique=True, nullable=False, index=True)
|
role_key: Mapped[str] = mapped_column(String(128), unique=True, nullable=False, index=True)
|
||||||
|
role_code: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
||||||
system_id: Mapped[str] = mapped_column(UUID(as_uuid=False), ForeignKey("systems.id", ondelete="CASCADE"), nullable=False)
|
system_id: Mapped[str] = mapped_column(UUID(as_uuid=False), ForeignKey("systems.id", ondelete="CASCADE"), nullable=False)
|
||||||
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||||
description: Mapped[str | None] = mapped_column(String(1024))
|
description: Mapped[str | None] = mapped_column(String(1024))
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class RolesRepository:
|
|||||||
pattern = f"%{keyword}%"
|
pattern = f"%{keyword}%"
|
||||||
cond = or_(
|
cond = or_(
|
||||||
Role.role_key.ilike(pattern),
|
Role.role_key.ilike(pattern),
|
||||||
|
Role.role_code.ilike(pattern),
|
||||||
Role.name.ilike(pattern),
|
Role.name.ilike(pattern),
|
||||||
Role.description.ilike(pattern),
|
Role.description.ilike(pattern),
|
||||||
)
|
)
|
||||||
@@ -48,6 +49,7 @@ class RolesRepository:
|
|||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
role_key: str,
|
role_key: str,
|
||||||
|
role_code: str,
|
||||||
system_id: str,
|
system_id: str,
|
||||||
name: str,
|
name: str,
|
||||||
description: str | None,
|
description: str | None,
|
||||||
@@ -55,6 +57,7 @@ class RolesRepository:
|
|||||||
) -> Role:
|
) -> Role:
|
||||||
item = Role(
|
item = Role(
|
||||||
role_key=role_key,
|
role_key=role_key,
|
||||||
|
role_code=role_code,
|
||||||
system_id=system_id,
|
system_id=system_id,
|
||||||
name=name,
|
name=name,
|
||||||
description=description,
|
description=description,
|
||||||
@@ -70,12 +73,15 @@ class RolesRepository:
|
|||||||
item: Role,
|
item: Role,
|
||||||
*,
|
*,
|
||||||
system_id: str | None = None,
|
system_id: str | None = None,
|
||||||
|
role_code: str | None = None,
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
description: str | None = None,
|
description: str | None = None,
|
||||||
status: str | None = None,
|
status: str | None = None,
|
||||||
) -> Role:
|
) -> Role:
|
||||||
if system_id is not None:
|
if system_id is not None:
|
||||||
item.system_id = system_id
|
item.system_id = system_id
|
||||||
|
if role_code is not None:
|
||||||
|
item.role_code = role_code
|
||||||
if name is not None:
|
if name is not None:
|
||||||
item.name = name
|
item.name = name
|
||||||
if description is not None:
|
if description is not None:
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ class SystemItem(BaseModel):
|
|||||||
|
|
||||||
class RoleCreateRequest(BaseModel):
|
class RoleCreateRequest(BaseModel):
|
||||||
system_key: str
|
system_key: str
|
||||||
|
role_code: str | None = None
|
||||||
name: str
|
name: str
|
||||||
description: str | None = None
|
description: str | None = None
|
||||||
status: str = "active"
|
status: str = "active"
|
||||||
@@ -81,6 +82,7 @@ class RoleCreateRequest(BaseModel):
|
|||||||
|
|
||||||
class RoleUpdateRequest(BaseModel):
|
class RoleUpdateRequest(BaseModel):
|
||||||
system_key: str | None = None
|
system_key: str | None = None
|
||||||
|
role_code: str | None = None
|
||||||
name: str | None = None
|
name: str | None = None
|
||||||
description: str | None = None
|
description: str | None = None
|
||||||
status: str | None = None
|
status: str | None = None
|
||||||
@@ -89,6 +91,7 @@ class RoleUpdateRequest(BaseModel):
|
|||||||
class RoleItem(BaseModel):
|
class RoleItem(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
role_key: str
|
role_key: str
|
||||||
|
role_code: str
|
||||||
system_key: str
|
system_key: str
|
||||||
system_name: str
|
system_name: str
|
||||||
name: str
|
name: str
|
||||||
@@ -138,6 +141,7 @@ class SiteRoleAssignRequest(BaseModel):
|
|||||||
class SiteRoleItem(BaseModel):
|
class SiteRoleItem(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
role_key: str
|
role_key: str
|
||||||
|
role_code: str
|
||||||
role_name: str
|
role_name: str
|
||||||
system_key: str
|
system_key: str
|
||||||
system_name: str
|
system_name: str
|
||||||
@@ -163,6 +167,7 @@ class UserEffectiveRoleItem(BaseModel):
|
|||||||
system_key: str
|
system_key: str
|
||||||
system_name: str
|
system_name: str
|
||||||
role_key: str
|
role_key: str
|
||||||
|
role_code: str
|
||||||
role_name: str
|
role_name: str
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class InternalSystemListResponse(BaseModel):
|
|||||||
class InternalRoleItem(BaseModel):
|
class InternalRoleItem(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
role_key: str
|
role_key: str
|
||||||
|
role_code: str
|
||||||
system_key: str
|
system_key: str
|
||||||
system_name: str
|
system_name: str
|
||||||
name: str
|
name: str
|
||||||
@@ -99,6 +100,7 @@ class InternalUserRoleItem(BaseModel):
|
|||||||
system_key: str
|
system_key: str
|
||||||
system_name: str
|
system_name: str
|
||||||
role_key: str
|
role_key: str
|
||||||
|
role_code: str
|
||||||
role_name: str
|
role_name: str
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class RoleSnapshotItem(BaseModel):
|
|||||||
system_key: str
|
system_key: str
|
||||||
system_name: str
|
system_name: str
|
||||||
role_key: str
|
role_key: str
|
||||||
|
role_code: str
|
||||||
role_name: str
|
role_name: str
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ def sync_from_provider(db: Session, *, force: bool = False) -> dict[str, int]:
|
|||||||
role_key = _generate_unique_key("RL", lambda key: roles_repo.get_by_key(key) is not None)
|
role_key = _generate_unique_key("RL", lambda key: roles_repo.get_by_key(key) is not None)
|
||||||
roles_repo.create(
|
roles_repo.create(
|
||||||
role_key=role_key,
|
role_key=role_key,
|
||||||
|
role_code=role_name,
|
||||||
system_id=system.id,
|
system_id=system.id,
|
||||||
name=role_name,
|
name=role_name,
|
||||||
description=role_desc,
|
description=role_desc,
|
||||||
@@ -259,6 +260,7 @@ def sync_from_provider(db: Session, *, force: bool = False) -> dict[str, int]:
|
|||||||
else:
|
else:
|
||||||
roles_repo.update(
|
roles_repo.update(
|
||||||
role,
|
role,
|
||||||
|
role_code=role.role_code or role_name,
|
||||||
name=role_name,
|
name=role_name,
|
||||||
description=role_desc,
|
description=role_desc,
|
||||||
status=role_status,
|
status=role_status,
|
||||||
@@ -373,6 +375,7 @@ def sync_systems_from_provider(db: Session, *, force: bool = False) -> dict[str,
|
|||||||
role_key = _generate_unique_key("RL", lambda key: roles_repo.get_by_key(key) is not None)
|
role_key = _generate_unique_key("RL", lambda key: roles_repo.get_by_key(key) is not None)
|
||||||
roles_repo.create(
|
roles_repo.create(
|
||||||
role_key=role_key,
|
role_key=role_key,
|
||||||
|
role_code=role_name,
|
||||||
system_id=system.id,
|
system_id=system.id,
|
||||||
name=role_name,
|
name=role_name,
|
||||||
description=role_desc,
|
description=role_desc,
|
||||||
@@ -382,6 +385,7 @@ def sync_systems_from_provider(db: Session, *, force: bool = False) -> dict[str,
|
|||||||
else:
|
else:
|
||||||
roles_repo.update(
|
roles_repo.update(
|
||||||
role,
|
role,
|
||||||
|
role_code=role.role_code or role_name,
|
||||||
name=role_name,
|
name=role_name,
|
||||||
description=role_desc,
|
description=role_desc,
|
||||||
status=role_status,
|
status=role_status,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from app.schemas.permissions import RoleSnapshotItem, RoleSnapshotResponse
|
|||||||
|
|
||||||
class PermissionService:
|
class PermissionService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def build_role_snapshot(user_sub: str, rows: list[tuple[str, str, str, str, str, str, str, str]]) -> RoleSnapshotResponse:
|
def build_role_snapshot(user_sub: str, rows: list[tuple[str, str, str, str, str, str, str, str, str]]) -> RoleSnapshotResponse:
|
||||||
return RoleSnapshotResponse(
|
return RoleSnapshotResponse(
|
||||||
user_sub=user_sub,
|
user_sub=user_sub,
|
||||||
roles=[
|
roles=[
|
||||||
@@ -15,6 +15,7 @@ class PermissionService:
|
|||||||
system_key=system_key,
|
system_key=system_key,
|
||||||
system_name=system_name,
|
system_name=system_name,
|
||||||
role_key=role_key,
|
role_key=role_key,
|
||||||
|
role_code=role_code,
|
||||||
role_name=role_name,
|
role_name=role_name,
|
||||||
)
|
)
|
||||||
for (
|
for (
|
||||||
@@ -25,6 +26,7 @@ class PermissionService:
|
|||||||
system_key,
|
system_key,
|
||||||
system_name,
|
system_name,
|
||||||
role_key,
|
role_key,
|
||||||
|
role_code,
|
||||||
role_name,
|
role_name,
|
||||||
) in rows
|
) in rows
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -68,13 +68,15 @@ CREATE TABLE systems (
|
|||||||
CREATE TABLE roles (
|
CREATE TABLE roles (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
role_key TEXT NOT NULL UNIQUE,
|
role_key TEXT NOT NULL UNIQUE,
|
||||||
|
role_code TEXT NOT NULL,
|
||||||
system_id UUID NOT NULL REFERENCES systems(id) ON DELETE CASCADE,
|
system_id UUID NOT NULL REFERENCES systems(id) ON DELETE CASCADE,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'active',
|
status VARCHAR(16) NOT NULL DEFAULT 'active',
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
CONSTRAINT uq_roles_system_name UNIQUE (system_id, name)
|
CONSTRAINT uq_roles_system_name UNIQUE (system_id, name),
|
||||||
|
CONSTRAINT uq_roles_system_role_code UNIQUE (system_id, role_code)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE site_roles (
|
CREATE TABLE site_roles (
|
||||||
@@ -126,6 +128,7 @@ CREATE INDEX idx_users_username ON users(username);
|
|||||||
CREATE INDEX idx_users_email ON users(email);
|
CREATE INDEX idx_users_email ON users(email);
|
||||||
CREATE INDEX idx_sites_company_id ON sites(company_id);
|
CREATE INDEX idx_sites_company_id ON sites(company_id);
|
||||||
CREATE INDEX idx_roles_system_id ON roles(system_id);
|
CREATE INDEX idx_roles_system_id ON roles(system_id);
|
||||||
|
CREATE INDEX idx_roles_role_code ON roles(role_code);
|
||||||
CREATE INDEX idx_site_roles_site_id ON site_roles(site_id);
|
CREATE INDEX idx_site_roles_site_id ON site_roles(site_id);
|
||||||
CREATE INDEX idx_site_roles_role_id ON site_roles(role_id);
|
CREATE INDEX idx_site_roles_role_id ON site_roles(role_id);
|
||||||
CREATE INDEX idx_user_sites_user_id ON user_sites(user_id);
|
CREATE INDEX idx_user_sites_user_id ON user_sites(user_id);
|
||||||
|
|||||||
27
scripts/migrate_add_role_code.sql
Normal file
27
scripts/migrate_add_role_code.sql
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE roles
|
||||||
|
ADD COLUMN IF NOT EXISTS role_code TEXT;
|
||||||
|
|
||||||
|
UPDATE roles
|
||||||
|
SET role_code = name
|
||||||
|
WHERE role_code IS NULL OR btrim(role_code) = '';
|
||||||
|
|
||||||
|
ALTER TABLE roles
|
||||||
|
ALTER COLUMN role_code SET NOT NULL;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'uq_roles_system_role_code'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE roles
|
||||||
|
ADD CONSTRAINT uq_roles_system_role_code UNIQUE (system_id, role_code);
|
||||||
|
END IF;
|
||||||
|
END$$;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_roles_role_code ON roles(role_code);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
-- Rename legacy IdP column names to provider_* naming.
|
|
||||||
-- Safe to run multiple times.
|
|
||||||
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'public' AND table_name = 'companies' AND column_name = 'idp_group_id'
|
|
||||||
) AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'public' AND table_name = 'companies' AND column_name = 'provider_group_id'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE public.companies RENAME COLUMN idp_group_id TO provider_group_id;
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
-- companies.display_name -> companies.name
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'public' AND table_name = 'companies' AND column_name = 'display_name'
|
|
||||||
) AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'public' AND table_name = 'companies' AND column_name = 'name'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE public.companies RENAME COLUMN display_name TO name;
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'public' AND table_name = 'companies' AND column_name = 'legal_name'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE public.companies DROP COLUMN legal_name;
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'public' AND table_name = 'systems' AND column_name = 'provider_client_id'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE public.systems DROP COLUMN provider_client_id;
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'public' AND table_name = 'roles' AND column_name = 'provider_role_name'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE public.roles DROP COLUMN provider_role_name;
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.table_constraints
|
|
||||||
WHERE table_schema='public' AND table_name='roles' AND constraint_name='uq_roles_system_provider_role_name'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE public.roles DROP CONSTRAINT uq_roles_system_provider_role_name;
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF NOT EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.table_constraints
|
|
||||||
WHERE table_schema='public' AND table_name='roles' AND constraint_name='uq_roles_system_name'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE public.roles ADD CONSTRAINT uq_roles_system_name UNIQUE (system_id, name);
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'public' AND table_name = 'sites' AND column_name = 'idp_group_id'
|
|
||||||
) AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'public' AND table_name = 'sites' AND column_name = 'provider_group_id'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE public.sites RENAME COLUMN idp_group_id TO provider_group_id;
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'public' AND table_name = 'systems' AND column_name = 'idp_client_id'
|
|
||||||
) AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'public' AND table_name = 'systems' AND column_name = 'provider_client_id'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE public.systems RENAME COLUMN idp_client_id TO provider_client_id;
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'public' AND table_name = 'roles' AND column_name = 'idp_role_name'
|
|
||||||
) AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'public' AND table_name = 'roles' AND column_name = 'provider_role_name'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE public.roles RENAME COLUMN idp_role_name TO provider_role_name;
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'public' AND table_name = 'users' AND column_name = 'idp_user_id'
|
|
||||||
) AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'public' AND table_name = 'users' AND column_name = 'provider_user_id'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE public.users RENAME COLUMN idp_user_id TO provider_user_id;
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
@@ -3,4 +3,11 @@ set -euo pipefail
|
|||||||
|
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
exec uvicorn app.main:app --env-file .env.development --host 127.0.0.1 --port 8000 --reload
|
ENV_FILE=".env.development"
|
||||||
|
if [ ! -f "$ENV_FILE" ]; then
|
||||||
|
echo "missing $ENV_FILE."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Loading environment from '$ENV_FILE'"
|
||||||
|
exec uvicorn app.main:app --env-file "$ENV_FILE" --host 127.0.0.1 --port 8000 --reload
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ def test_internal_idp_ensure_requires_config() -> None:
|
|||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
try:
|
try:
|
||||||
resp = client.post(
|
resp = client.post(
|
||||||
"/internal/idp/users/ensure",
|
"/internal/provider/users/ensure",
|
||||||
json={
|
json={
|
||||||
"sub": "idp-sub-1",
|
"sub": "idp-sub-1",
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
|
|||||||
Reference in New Issue
Block a user