Update migrate script with the latest database changes.
This commit is contained in:
+28
-35
@@ -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!")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user