Update migrate script with the latest database changes.

This commit is contained in:
2026-01-23 19:56:44 +00:00
parent d4ebc1bf99
commit c2933d60c2
+28 -35
View File
@@ -13,8 +13,7 @@ Or via the CLI entry point (if installed):
import asyncio import asyncio
from datetime import datetime, timezone from datetime import datetime, timezone
from uuid import UUID
import base64url
from paskia.authsession import EXPIRES from paskia.authsession import EXPIRES
@@ -36,13 +35,6 @@ SQL_DB_DEFAULT = "sqlite+aiosqlite:///paskia.sqlite"
JSON_DB_DEFAULT = "paskia.jsonl" JSON_DB_DEFAULT = "paskia.jsonl"
def _bytes_to_str(b: bytes | None) -> str | None:
"""Convert bytes to base64url string."""
if b is None:
return None
return base64url.enc(b)
async def migrate_from_sql( async def migrate_from_sql(
sql_db_path: str = SQL_DB_DEFAULT, sql_db_path: str = SQL_DB_DEFAULT,
json_db_path: str = JSON_DB_DEFAULT, json_db_path: str = JSON_DB_DEFAULT,
@@ -76,12 +68,11 @@ async def migrate_from_sql(
# Initialize destination JSON database # Initialize destination JSON database
json_db = JSONDB(json_db_path) json_db = JSONDB(json_db_path)
json_db.load() await json_db.load()
print(f"Migrating from {sql_db_path} to {json_db_path}...") print(f"Migrating from {sql_db_path} to {json_db_path}...")
# Build all data directly without saving (we'll save once at the end) # Build all data directly without saving (we'll save once at the end)
with json_db._lock:
# Track old permission ID -> new scope mapping for migration # Track old permission ID -> new scope mapping for migration
# Also track org-specific admin permissions to consolidate # Also track org-specific admin permissions to consolidate
old_org_admin_pattern = re.compile(r"^auth:org:([0-9a-f-]+)$", re.IGNORECASE) old_org_admin_pattern = re.compile(r"^auth:org:([0-9a-f-]+)$", re.IGNORECASE)
@@ -96,7 +87,7 @@ async def migrate_from_sql(
# Migrate permissions with UUID keys and scope field # Migrate permissions with UUID keys and scope field
# Always create exactly one common auth:org:admin permission for all org admin needs # Always create exactly one common auth:org:admin permission for all org admin needs
org_admin_perm_uuid = str(uuid7.create()) org_admin_perm_uuid: UUID = uuid7.create()
json_db._data.permissions[org_admin_perm_uuid] = _PermissionData( json_db._data.permissions[org_admin_perm_uuid] = _PermissionData(
scope="auth:org:admin", scope="auth:org:admin",
display_name="Org Admin", display_name="Org Admin",
@@ -119,7 +110,7 @@ async def migrate_from_sql(
continue continue
# Regular permission - create with UUID key # Regular permission - create with UUID key
perm_uuid = str(uuid7.create()) perm_uuid: UUID = uuid7.create()
json_db._data.permissions[perm_uuid] = _PermissionData( json_db._data.permissions[perm_uuid] = _PermissionData(
scope=perm.id, # Old ID becomes the scope scope=perm.id, # Old ID becomes the scope
display_name=perm.display_name, display_name=perm.display_name,
@@ -133,8 +124,8 @@ async def migrate_from_sql(
# Migrate organizations # Migrate organizations
orgs = await sql_db.list_organizations() orgs = await sql_db.list_organizations()
for org in orgs: for org in orgs:
key = str(org.uuid) org_key: UUID = org.uuid
json_db._data.orgs[key] = _OrgData( json_db._data.orgs[org_key] = _OrgData(
display_name=org.display_name, display_name=org.display_name,
) )
# Update permissions to allow this org to grant them (by scope) # Update permissions to allow this org to grant them (by scope)
@@ -143,24 +134,24 @@ async def migrate_from_sql(
# Find permission with this scope and add org # Find permission with this scope and add org
for pid, p in json_db._data.permissions.items(): for pid, p in json_db._data.permissions.items():
if p.scope == new_scope: if p.scope == new_scope:
p.orgs[key] = True p.orgs[org_key] = True
break break
# Ensure every org can grant auth:org:admin # Ensure every org can grant auth:org:admin
json_db._data.permissions[org_admin_perm_uuid].orgs[key] = True json_db._data.permissions[org_admin_perm_uuid].orgs[org_key] = True
print(f" Migrated {len(orgs)} organizations") print(f" Migrated {len(orgs)} organizations")
# Migrate roles - convert old permission IDs to scopes # Migrate roles - convert old permission IDs to scopes
role_count = 0 role_count = 0
for org in orgs: for org in orgs:
for role in org.roles: for role in org.roles:
key = str(role.uuid) role_key: UUID = role.uuid
# Convert old permission IDs to scopes # Convert old permission IDs to scopes
new_permissions = {} new_permissions = {}
for old_perm_id in role.permissions or []: for old_perm_id in role.permissions or []:
new_scope = perm_id_to_scope.get(old_perm_id, old_perm_id) new_scope = perm_id_to_scope.get(old_perm_id, old_perm_id)
new_permissions[new_scope] = True new_permissions[new_scope] = True
json_db._data.roles[key] = _RoleData( json_db._data.roles[role_key] = _RoleData(
org=str(role.org_uuid), org=role.org_uuid,
display_name=role.display_name, display_name=role.display_name,
permissions=new_permissions, permissions=new_permissions,
) )
@@ -173,10 +164,10 @@ async def migrate_from_sql(
user_models = result.scalars().all() user_models = result.scalars().all()
for um in user_models: for um in user_models:
user = um.as_dataclass() user = um.as_dataclass()
key = str(user.uuid) user_key: UUID = user.uuid
json_db._data.users[key] = _UserData( json_db._data.users[user_key] = _UserData(
display_name=user.display_name, display_name=user.display_name,
role=str(user.role_uuid), role=user.role_uuid,
created_at=user.created_at or datetime.now(timezone.utc), created_at=user.created_at or datetime.now(timezone.utc),
last_seen=user.last_seen, last_seen=user.last_seen,
visits=user.visits, visits=user.visits,
@@ -189,11 +180,11 @@ async def migrate_from_sql(
cred_models = result.scalars().all() cred_models = result.scalars().all()
for cm in cred_models: for cm in cred_models:
cred = cm.as_dataclass() cred = cm.as_dataclass()
key = str(cred.uuid) cred_key: UUID = cred.uuid
json_db._data.credentials[key] = _CredentialData( json_db._data.credentials[cred_key] = _CredentialData(
credential_id=cred.credential_id, credential_id=cred.credential_id,
user=str(cred.user_uuid), user=cred.user_uuid,
aaguid=str(cred.aaguid), aaguid=cred.aaguid,
public_key=cred.public_key, public_key=cred.public_key,
sign_count=cred.sign_count, sign_count=cred.sign_count,
created_at=cred.created_at, created_at=cred.created_at,
@@ -208,10 +199,10 @@ async def migrate_from_sql(
session_models = result.scalars().all() session_models = result.scalars().all()
for sm in session_models: for sm in session_models:
sess = sm.as_dataclass() sess = sm.as_dataclass()
key_b64 = _bytes_to_str(sess.key) session_key: bytes = sess.key
json_db._data.sessions[key_b64] = _SessionData( json_db._data.sessions[session_key] = _SessionData(
user=str(sess.user_uuid), user=sess.user_uuid,
credential=str(sess.credential_uuid), credential=sess.credential_uuid,
host=sess.host, host=sess.host,
ip=sess.ip, ip=sess.ip,
user_agent=sess.user_agent, user_agent=sess.user_agent,
@@ -225,9 +216,9 @@ async def migrate_from_sql(
token_models = result.scalars().all() token_models = result.scalars().all()
for tm in token_models: for tm in token_models:
token = tm.as_dataclass() token = tm.as_dataclass()
key_b64 = _bytes_to_str(token.key) token_key: bytes = token.key
json_db._data.reset_tokens[key_b64] = _ResetTokenData( json_db._data.reset_tokens[token_key] = _ResetTokenData(
user=str(token.user_uuid), user=token.user_uuid,
expiry=token.expiry, expiry=token.expiry,
token_type=token.token_type, token_type=token.token_type,
) )
@@ -236,7 +227,9 @@ async def migrate_from_sql(
# Queue and flush all changes with actor "migrate" # Queue and flush all changes with actor "migrate"
json_db._current_actor = "migrate" json_db._current_actor = "migrate"
json_db._queue_change() json_db._queue_change()
json_db.flush() from paskia.db.jsonl import flush_changes
await flush_changes(json_db.db_path, json_db._pending_changes)
print("Migration complete!") print("Migration complete!")