MEGAINFRA-WEBAPP/scripts/migrations/2026-09-11-add-site-work-month-field.py
DEls 58b47eafee docs(project-calendar): add the project month/week plan; fix a stale doc reference
The migration's docstring pointed at a spec file that was never
written for this feature (the design was worked out via conversation,
not a separate spec doc) — points at the plan instead.
2026-09-11 22:35:41 +02:00

113 lines
No EOL
3.3 KiB
Python

#!/usr/bin/env python3
"""
One-shot PocketBase migration applied via:
PB_URL=https://pb.mega.infra.apphub.co.za \\
PB_ADMIN_EMAIL=admin@mega.co.za \\
PB_ADMIN_PASSWORD=... \\
python3 scripts/migrations/2026-09-11-add-site-work-month-field.py
Adds sites.work_month_start_day (see
docs/superpowers/plans/2026-09-11-project-month-week.md for why:
some sites' "work months" don't run 1st-30th, they run e.g. 21st-21st).
Strictly additive, and the field is not required — existing sites keep
whatever value they have (empty means "ordinary calendar months").
Idempotent: re-running skips the field if it already exists.
"""
import json
import os
import sys
import urllib.error
import urllib.request
NEW_FIELD = {
"name": "work_month_start_day",
"type": "number",
"required": False,
"min": 1,
"max": 31,
}
def admin_auth(base_url: str) -> str:
body = json.dumps(
{
"identity": os.environ["PB_ADMIN_EMAIL"],
"password": os.environ["PB_ADMIN_PASSWORD"],
}
).encode()
req = urllib.request.Request(
f"{base_url}/api/collections/_superusers/auth-with-password",
data=body,
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())["token"]
def get_collection(base_url: str, token: str, name: str):
req = urllib.request.Request(
f"{base_url}/api/collections/{name}", headers={"Authorization": token}
)
try:
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())
except urllib.error.HTTPError as e:
if e.code == 404:
return None
raise
def patch_collection(base_url: str, token: str, name: str, body: dict):
req = urllib.request.Request(
f"{base_url}/api/collections/{name}",
data=json.dumps(body).encode(),
headers={"Authorization": token, "Content-Type": "application/json"},
method="PATCH",
)
try:
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())
except urllib.error.HTTPError as e:
raise RuntimeError(
f"PATCH {name} failed: {e.code} {e.read().decode() if e.fp else ''}"
) from e
def main() -> int:
base_url = os.environ.get("PB_URL", "http://127.0.0.1:8090").rstrip("/")
token = admin_auth(base_url)
coll = get_collection(base_url, token, "sites")
if not coll:
print(f"ERROR: collection 'sites' not found on {base_url}")
return 1
existing = coll.get("fields", [])
have = {f["name"] for f in existing}
if NEW_FIELD["name"] in have:
print("sites: no-op, work_month_start_day already present")
else:
print("sites: adding work_month_start_day")
patch_collection(base_url, token, "sites", {"fields": existing + [NEW_FIELD]})
final = get_collection(base_url, token, "sites")
print("\nFinal sites field list:")
print(f" {sorted(f['name'] for f in final.get('fields', []))}")
print("\nMigration complete.")
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except KeyError as e:
print(
f"ERROR: missing environment variable {e}. "
f"Set PB_ADMIN_EMAIL and PB_ADMIN_PASSWORD.",
file=sys.stderr,
)
sys.exit(2)