Dawarich: the third new app through the checklist — template, record, fixture, first ladder step 1.15.2 -> 1.15.3
gates / gates (push) Successful in 3s

Location history with PostGIS 17 + Redis + Sidekiq. The seeded known login replaced by after_install behind the install
hold; geocoding off; SECRET_KEY_BASE a data_key; smtp_mapping with mail-off boot measured. onboarding/dawarich.md complete.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-01 18:37:05 +02:00
parent 882ac14309
commit 72247a387e
8 changed files with 512 additions and 0 deletions
+24
View File
@@ -224,6 +224,29 @@
"description": "Minecraft szerver kezelő webes felülettel",
"initial_credentials.note": "Kezdeti admin jelszó (a telepítéskor beállított). Az első belépés után változtasd meg a Crafty felületén — ez a kártya továbbra is a kezdeti jelszót mutatja."
},
"dawarich": {
"app_info.add_people": "A családtagod fiókját te hozod létre a Beállítások, Felhasználók oldalon; mindenki a saját telefonjáról küldi a helyét.",
"app_info.default_creds": "demo@dawarich.app / safepassword",
"app_info.first_steps[0]": "Nyisd meg a timeline.DOMAIN címet, és jelentkezz be: demo@dawarich.app és a Beállítások oldalon látható jelszó",
"app_info.first_steps[1]": "A fiókod beállításaiban cseréld le az e-mail-címet a sajátodra - a demo@dawarich.app név nyilvános",
"app_info.first_steps[2]": "A beállításokban másold ki az API-kulcsodat",
"app_info.first_steps[3]": "Telepítsd a Dawarich alkalmazást a telefonodra, és add meg: https://timeline.DOMAIN és az API-kulcsot",
"app_info.first_steps[4]": "A térkép csempéit a böngésződ a Dawarich térképszerveréről tölti le; a helyadataid a te szervereden maradnak",
"app_info.tagline": "Saját helyelőzmények - a telefonod a te szerveredre küldi, hol jártál",
"app_info.use_cases[0]": "A Google Idővonal helyett: a helyelőzményeid a saját szervereden",
"app_info.use_cases[1]": "Térkép, statisztika és utazások az összes helyről, ahol jártál",
"app_info.use_cases[2]": "Google Takeout, GPX és GeoJSON import a régi adataidhoz",
"app_info.use_cases[3]": "A Dawarich, az Overland vagy az OwnTracks alkalmazás küldi a telefonod helyét",
"deploy_fields[ADMIN_PASSWORD].description": "Az első bejelentkezéshez: demo@dawarich.app és ez a jelszó. A Beállításokban cseréld le az e-mail-címet a sajátodra.",
"deploy_fields[ADMIN_PASSWORD].label": "Bejelentkezési jelszó (demo@dawarich.app)",
"deploy_fields[DB_PASSWORD].label": "Adatbázis jelszó",
"deploy_fields[DOMAIN].description": "A szerver domain neve",
"deploy_fields[DOMAIN].label": "Domain",
"deploy_fields[SECRET_KEY_BASE].label": "Titkosítási kulcs",
"deploy_fields[SUBDOMAIN].description": "Az alkalmazás aldomainje",
"deploy_fields[SUBDOMAIN].label": "Aldomain",
"description": "Saját helyelőzmények térképen - a Google Idővonal helyett"
},
"docmost": {
"app_info.first_steps[0]": "Nyisd meg a docs.DOMAIN címet a böngészőben",
"app_info.first_steps[1]": "Az első regisztrált felhasználó automatikusan admin lesz",
@@ -1205,6 +1228,7 @@
}
},
"reasons": {
"dawarich": "new app 2026-10-01 through NEW-APP-CHECKLIST.md: te-form, no kérjük; reviewed by CC against the operator rules",
"karakeep": "new app 2026-10-01 through NEW-APP-CHECKLIST.md: te-form, no kérjük; reviewed by CC against the operator rules",
"radicale": "new app 2026-10-01 through NEW-APP-CHECKLIST.md: te-form, no kérjük; reviewed by CC against the operator rules"
}
+64
View File
@@ -2003,6 +2003,69 @@ class Karakeep:
return ok
# =============================================================================================
class Dawarich:
"""Dawarich (2026-10-01, new app through NEW-APP-CHECKLIST.md). THE FRONT DOOR is the route the phone apps use:
`POST /api/v1/overland/batches?api_key=<key>` (Overland's GeoJSON — one point with a unique place and time), read back
with `GET /api/v1/points?api_key=…&start_at=…&end_at=…`. The API key is the account's own (the household copies it
from its settings page); the fixture reads it with the app's own CLI (`bin/rails runner` in the app's container —
R-156's allowed route, nothing planted). Negative controls on every readback: no key 401, a wrong key 401, a range
with nothing in it answers []."""
sub = "timeline"
@staticmethod
def _key(w):
out = w.guest("docker exec dawarich bin/rails runner 'puts User.order(:id).first.api_key' 2>/dev/null | tail -1")
k = (out or "").strip()
return k if re.fullmatch(r"[A-Za-z0-9_-]{20,}", k) else None
def seed(self, w, sub, say):
if not w.wait_app(sub, "/api/v1/health", want=("200",), tries=90):
self.tried = "/api/v1/health never answered 200"
return None
key = self._key(w)
if not key:
self.tried = "no API key from the app's own CLI"
return None
lat = round(47.40 + secrets.randbelow(2000) / 10000.0, 4)
lon = round(19.00 + secrets.randbelow(2000) / 10000.0, 4)
ts = "2026-09-%02dT%02d:%02d:00Z" % (1 + secrets.randbelow(28), secrets.randbelow(24), secrets.randbelow(60))
body = {"locations": [{"type": "Feature", "geometry": {"type": "Point", "coordinates": [lon, lat]},
"properties": {"timestamp": ts, "altitude": 110, "speed": 0, "horizontal_accuracy": 5,
"device_id": "felhom-fixture"}}]}
rc, code, out = w.app_curl(sub, f"/api/v1/overland/batches?api_key={key}", "-H", "Content-Type: application/json",
data=json.dumps(body), method="POST")
say(f" dawarich: POST /api/v1/overland/batches http={code} ({lat},{lon} at {ts})")
if code not in ("200", "201"):
self.tried = f"overland batch -> {code} {(out or '')[:120]}"
return None
return {"lat": lat, "lon": lon, "ts": ts}
def verify(self, w, sub, t, say):
if not w.wait_app(sub, "/api/v1/health", want=("200",), tries=90):
say(" dawarich: /api/v1/health never answered")
return False
key = self._key(w)
day = t["ts"][:10]
rng = f"start_at={day}T00:00:00Z&end_at={day}T23:59:59Z"
rc, c_none, _ = w.app_curl(sub, f"/api/v1/points?{rng}")
rc, c_wrong, _ = w.app_curl(sub, f"/api/v1/points?api_key=felhom-wrong-key&{rng}")
rc, c_empty, o_empty = w.app_curl(sub, f"/api/v1/points?api_key={key}&start_at=2001-01-01T00:00:00Z&end_at=2001-01-02T00:00:00Z")
if c_none != "401" or c_wrong != "401" or (o_empty or "").strip() != "[]":
say(f" dawarich: READBACK UNUSABLE — no key {c_none}, wrong key {c_wrong}, empty range {(o_empty or '')[:40]!r}")
return False
found = False
for _ in range(12): # the batch is stored by a background job
rc, code, out = w.app_curl(sub, f"/api/v1/points?api_key={key}&{rng}")
# the point's fields are read as text: the unique coordinates must both appear in the answer
found = code == "200" and str(t["lat"]) in (out or "") and str(t["lon"]) in (out or "")
if found:
break
time.sleep(5)
say(f" dawarich: readback http={code} found={found} (controls: no key {c_none}, wrong {c_wrong}, empty range [])")
return found
FIXTURES = {
"uptime-kuma": UptimeKuma(),
"crafty-controller": Crafty(),
@@ -2041,4 +2104,5 @@ FIXTURES = {
"calcom": Calcom(),
"radicale": Radicale(),
"karakeep": Karakeep(),
"dawarich": Dawarich(),
}