Compare commits
6 Commits
chore/anno
...
chore/fix-
| Author | SHA1 | Date | |
|---|---|---|---|
| 480a61b071 | |||
| 390f2c33e5 | |||
| 16485dc4a9 | |||
| 0519ae2dd0 | |||
| 342e21b9ea | |||
| c7ae883c6a |
@@ -189,12 +189,9 @@ jobs:
|
|||||||
docker image tag archicratie-web:blue "archicratie-web:blue.BAK.${TS}" || true
|
docker image tag archicratie-web:blue "archicratie-web:blue.BAK.${TS}" || true
|
||||||
docker image tag archicratie-web:green "archicratie-web:green.BAK.${TS}" || true
|
docker image tag archicratie-web:green "archicratie-web:green.BAK.${TS}" || true
|
||||||
|
|
||||||
# ✅ use cache (DO NOT --no-cache)
|
|
||||||
docker compose -p "$PROJ" -f docker-compose.yml build web_blue
|
docker compose -p "$PROJ" -f docker-compose.yml build web_blue
|
||||||
|
|
||||||
# ✅ hard fix: remove existing container if name conflicts
|
|
||||||
docker rm -f archicratie-web-blue || true
|
docker rm -f archicratie-web-blue || true
|
||||||
|
|
||||||
docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_blue
|
docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_blue
|
||||||
|
|
||||||
curl -fsS "http://127.0.0.1:8081/para-index.json" >/dev/null
|
curl -fsS "http://127.0.0.1:8081/para-index.json" >/dev/null
|
||||||
@@ -256,6 +253,7 @@ jobs:
|
|||||||
python3 - <<'PY'
|
python3 - <<'PY'
|
||||||
import os, re, json, glob, datetime
|
import os, re, json, glob, datetime
|
||||||
import yaml
|
import yaml
|
||||||
|
import datetime as dt
|
||||||
|
|
||||||
ROOT = os.getcwd()
|
ROOT = os.getcwd()
|
||||||
ANNO_ROOT = os.path.join(ROOT, "src", "annotations")
|
ANNO_ROOT = os.path.join(ROOT, "src", "annotations")
|
||||||
@@ -263,6 +261,26 @@ jobs:
|
|||||||
def is_obj(x): return isinstance(x, dict)
|
def is_obj(x): return isinstance(x, dict)
|
||||||
def is_arr(x): return isinstance(x, list)
|
def is_arr(x): return isinstance(x, list)
|
||||||
|
|
||||||
|
# --- KEY FIX: YAML timestamps -> datetime; JSON can't dump them
|
||||||
|
def iso_dt(x):
|
||||||
|
if isinstance(x, dt.datetime):
|
||||||
|
if x.tzinfo is None:
|
||||||
|
return x.isoformat()
|
||||||
|
return x.astimezone(dt.timezone.utc).isoformat().replace("+00:00","Z")
|
||||||
|
if isinstance(x, dt.date):
|
||||||
|
return x.isoformat()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def normalize(x):
|
||||||
|
s = iso_dt(x)
|
||||||
|
if s is not None:
|
||||||
|
return s
|
||||||
|
if isinstance(x, dict):
|
||||||
|
return {str(k): normalize(v) for k, v in x.items()}
|
||||||
|
if isinstance(x, list):
|
||||||
|
return [normalize(v) for v in x]
|
||||||
|
return x
|
||||||
|
|
||||||
def key_media(it):
|
def key_media(it):
|
||||||
return str((it or {}).get("src",""))
|
return str((it or {}).get("src",""))
|
||||||
|
|
||||||
@@ -282,17 +300,19 @@ jobs:
|
|||||||
seen = set()
|
seen = set()
|
||||||
out = []
|
out = []
|
||||||
for x in (dst_list or []):
|
for x in (dst_list or []):
|
||||||
|
x = normalize(x)
|
||||||
k = key_fn(x)
|
k = key_fn(x)
|
||||||
if k and k not in seen:
|
if k and k not in seen:
|
||||||
seen.add(k); out.append(x)
|
seen.add(k); out.append(x)
|
||||||
for x in (src_list or []):
|
for x in (src_list or []):
|
||||||
|
x = normalize(x)
|
||||||
k = key_fn(x)
|
k = key_fn(x)
|
||||||
if k and k not in seen:
|
if k and k not in seen:
|
||||||
seen.add(k); out.append(x)
|
seen.add(k); out.append(x)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def deep_merge(dst, src):
|
def deep_merge(dst, src):
|
||||||
# non destructif : ne supprime rien, n'écrase pas les scalaires existants
|
src = normalize(src)
|
||||||
for k, v in (src or {}).items():
|
for k, v in (src or {}).items():
|
||||||
if k in ("media", "refs", "comments_editorial") and is_arr(v):
|
if k in ("media", "refs", "comments_editorial") and is_arr(v):
|
||||||
if k == "media":
|
if k == "media":
|
||||||
@@ -305,21 +325,22 @@ jobs:
|
|||||||
|
|
||||||
if is_obj(v):
|
if is_obj(v):
|
||||||
if not is_obj(dst.get(k)):
|
if not is_obj(dst.get(k)):
|
||||||
dst[k] = dst.get(k) if is_obj(dst.get(k)) else {}
|
dst[k] = {} if not is_obj(dst.get(k)) else dst.get(k)
|
||||||
deep_merge(dst[k], v)
|
deep_merge(dst[k], v)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if is_arr(v):
|
if is_arr(v):
|
||||||
# fallback: union by json string
|
|
||||||
cur = dst.get(k, [])
|
cur = dst.get(k, [])
|
||||||
if not is_arr(cur): cur = []
|
if not is_arr(cur): cur = []
|
||||||
seen = set()
|
seen = set()
|
||||||
out = []
|
out = []
|
||||||
for x in cur:
|
for x in cur:
|
||||||
|
x = normalize(x)
|
||||||
s = json.dumps(x, sort_keys=True, ensure_ascii=False)
|
s = json.dumps(x, sort_keys=True, ensure_ascii=False)
|
||||||
if s not in seen:
|
if s not in seen:
|
||||||
seen.add(s); out.append(x)
|
seen.add(s); out.append(x)
|
||||||
for x in v:
|
for x in v:
|
||||||
|
x = normalize(x)
|
||||||
s = json.dumps(x, sort_keys=True, ensure_ascii=False)
|
s = json.dumps(x, sort_keys=True, ensure_ascii=False)
|
||||||
if s not in seen:
|
if s not in seen:
|
||||||
seen.add(s); out.append(x)
|
seen.add(s); out.append(x)
|
||||||
@@ -327,6 +348,7 @@ jobs:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# scalaires: set seulement si absent / vide
|
# scalaires: set seulement si absent / vide
|
||||||
|
v = normalize(v)
|
||||||
if k not in dst or dst.get(k) in (None, ""):
|
if k not in dst or dst.get(k) in (None, ""):
|
||||||
dst[k] = v
|
dst[k] = v
|
||||||
|
|
||||||
@@ -338,11 +360,16 @@ jobs:
|
|||||||
for k in ("media","refs","comments_editorial"):
|
for k in ("media","refs","comments_editorial"):
|
||||||
arr = entry.get(k)
|
arr = entry.get(k)
|
||||||
if not is_arr(arr): continue
|
if not is_arr(arr): continue
|
||||||
|
|
||||||
def ts(x):
|
def ts(x):
|
||||||
|
x = normalize(x)
|
||||||
try:
|
try:
|
||||||
return datetime.datetime.fromisoformat(str((x or {}).get("ts","")).replace("Z","+00:00")).timestamp()
|
s = str((x or {}).get("ts",""))
|
||||||
|
return dt.datetime.fromisoformat(s.replace("Z","+00:00")).timestamp() if s else 0
|
||||||
except Exception:
|
except Exception:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
arr = [normalize(x) for x in arr]
|
||||||
arr.sort(key=lambda x: (ts(x), json.dumps(x, sort_keys=True, ensure_ascii=False)))
|
arr.sort(key=lambda x: (ts(x), json.dumps(x, sort_keys=True, ensure_ascii=False)))
|
||||||
entry[k] = arr
|
entry[k] = arr
|
||||||
|
|
||||||
@@ -357,8 +384,11 @@ jobs:
|
|||||||
try:
|
try:
|
||||||
with open(fp, "r", encoding="utf-8") as f:
|
with open(fp, "r", encoding="utf-8") as f:
|
||||||
doc = yaml.safe_load(f) or {}
|
doc = yaml.safe_load(f) or {}
|
||||||
|
doc = normalize(doc)
|
||||||
|
|
||||||
if not isinstance(doc, dict) or doc.get("schema") != 1:
|
if not isinstance(doc, dict) or doc.get("schema") != 1:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
page = str(doc.get("page","")).strip().strip("/")
|
page = str(doc.get("page","")).strip().strip("/")
|
||||||
paras = doc.get("paras") or {}
|
paras = doc.get("paras") or {}
|
||||||
if not page or not isinstance(paras, dict):
|
if not page or not isinstance(paras, dict):
|
||||||
@@ -376,7 +406,6 @@ jobs:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
errors.append({"file": os.path.relpath(fp, ROOT), "error": str(e)})
|
errors.append({"file": os.path.relpath(fp, ROOT), "error": str(e)})
|
||||||
|
|
||||||
# tri paras
|
|
||||||
for page, obj in pages.items():
|
for page, obj in pages.items():
|
||||||
keys = list((obj.get("paras") or {}).keys())
|
keys = list((obj.get("paras") or {}).keys())
|
||||||
keys.sort(key=lambda k: (para_num(k), k))
|
keys.sort(key=lambda k: (para_num(k), k))
|
||||||
@@ -384,7 +413,7 @@ jobs:
|
|||||||
|
|
||||||
out = {
|
out = {
|
||||||
"schema": 1,
|
"schema": 1,
|
||||||
"generatedAt": datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat().replace("+00:00","Z"),
|
"generatedAt": dt.datetime.utcnow().replace(tzinfo=dt.timezone.utc).isoformat().replace("+00:00","Z"),
|
||||||
"pages": pages,
|
"pages": pages,
|
||||||
"stats": {
|
"stats": {
|
||||||
"pages": len(pages),
|
"pages": len(pages),
|
||||||
@@ -394,19 +423,19 @@ jobs:
|
|||||||
"errors": errors,
|
"errors": errors,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out = normalize(out)
|
||||||
|
|
||||||
with open("/tmp/annotations-index.json", "w", encoding="utf-8") as f:
|
with open("/tmp/annotations-index.json", "w", encoding="utf-8") as f:
|
||||||
json.dump(out, f, ensure_ascii=False)
|
json.dump(out, f, ensure_ascii=False)
|
||||||
|
|
||||||
print("OK: wrote /tmp/annotations-index.json pages=", out["stats"]["pages"], "paras=", out["stats"]["paras"], "errors=", out["stats"]["errors"])
|
print("OK: wrote /tmp/annotations-index.json pages=", out["stats"]["pages"], "paras=", out["stats"]["paras"], "errors=", out["stats"]["errors"])
|
||||||
PY
|
PY
|
||||||
|
|
||||||
# inject into running containers
|
|
||||||
for c in archicratie-web-blue archicratie-web-green; do
|
for c in archicratie-web-blue archicratie-web-green; do
|
||||||
echo "== patch $c =="
|
echo "== patch $c =="
|
||||||
docker cp /tmp/annotations-index.json "${c}:/usr/share/nginx/html/annotations-index.json"
|
docker cp /tmp/annotations-index.json "${c}:/usr/share/nginx/html/annotations-index.json"
|
||||||
done
|
done
|
||||||
|
|
||||||
# quick smoke: check new file exists and is readable
|
|
||||||
for p in 8081 8082; do
|
for p in 8081 8082; do
|
||||||
echo "== smoke annotations-index on $p =="
|
echo "== smoke annotations-index on $p =="
|
||||||
curl -fsS "http://127.0.0.1:${p}/annotations-index.json" | python3 -c 'import sys,json; j=json.load(sys.stdin); print("generatedAt:", j.get("generatedAt")); print("pages:", len(j.get("pages") or {}))'
|
curl -fsS "http://127.0.0.1:${p}/annotations-index.json" | python3 -c 'import sys,json; j=json.load(sys.stdin); print("generatedAt:", j.get("generatedAt")); print("pages:", len(j.get("pages") or {}))'
|
||||||
|
|||||||
Reference in New Issue
Block a user