Files
Magent/scripts/reconcile_verified_accounts.py
T
Assclaw de25255ea8
Magent CI/CD / verify (push) Successful in 1m50s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Skipped
Reconcile verified account IDs and make language repairs observable
2026-09-11 19:56:32 +12:00

66 lines
3.1 KiB
Python

"""Audited operator repair. Preview by default; --apply requires explicit authorization."""
import argparse
import asyncio
from contextlib import closing
import json
import os
from pathlib import Path
import sqlite3
from app import db
from app.services import duplicate_accounts as duplicates
from app.services import identity_review as review
async def reconcile(apply, output):
os.umask(0o077)
output.mkdir(parents=True, exist_ok=False, mode=0o700)
if apply:
with closing(db._connect()) as source, closing(sqlite3.connect(output / 'before.sqlite')) as target:
source.backup(target)
changes, blocked, seen = [], [], set()
actor = {'username': 'maintenance:authorized-identity-reconciliation'}
report, local, runtime = await review.review_identities()
initial = report['counts']
while True:
target = next((row for row in report['rows'] if row['candidate_jellyfin_id']
and row['candidate_jellyfin_id'] not in seen
and len(duplicates.identity_group(report, row)) > 1), None)
if not target:
break
identity = target['candidate_jellyfin_id']
seen.add(identity)
ids = [row['user']['id'] for row in duplicates.identity_group(report, target)]
with closing(db._connect()) as conn:
state = duplicates.account_state(conn, ids)
preview = duplicates.build_preview(report, local, runtime, state, target['user']['id'])
if preview['can_confirm']:
result = duplicates.consolidate(preview, report, local, runtime, state, actor) if apply else {
'kept_user_id': preview['keep_id'], 'consolidated': len(ids) - 1}
changes.append(result)
if apply:
report, local, runtime = await review.review_identities()
else:
blocked.append({'ids': ids, 'jellyfin_id': identity, 'issues': preview['issues']})
(output / 'progress.json').write_text(json.dumps({'changes': changes, 'blocked': blocked}))
if len(seen) % 20 == 0:
print('Reviewed groups:', len(seen), 'consolidated rows:', sum(r['consolidated'] for r in changes), flush=True)
if apply:
ready = [row['user']['id'] for row in report['rows'] if row['can_confirm']
and row['basis'] in {'confirmed_id', 'stored_jellyfin_id', 'stored_seerr_id'}]
if ready:
review.save_confirmations(report, local, runtime, ready, actor)
report, _, _ = await review.review_identities()
summary = {'applied': apply, 'before': initial, 'after': report['counts'],
'consolidated_rows': sum(r['consolidated'] for r in changes), 'groups': len(changes), 'blocked': blocked}
(output / 'result.json').write_text(json.dumps(summary, indent=2))
print(json.dumps(summary), flush=True)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--apply', action='store_true')
parser.add_argument('--output', type=Path, required=True, help='New private backup/report directory')
args = parser.parse_args()
asyncio.run(reconcile(args.apply, args.output))