Problem: the hexagonal refactor churns the backend tree for nine more phases; the UI delivery stack (web/ SPA, cmd/desktop Wails wrapper, compose/web image) must move to its own repo first so doc/layout rewrites land once on a backend-only tree. Change: - New repo git.hubris.network/dtoro/oikos-web (v0.33.0): web/, desktop/ (updateURL repointed to oikos-web releases), compose/, own CI (web + desktop jobs), own deploy script (CI-green gate, TOCTOU guard, version-tagged images, prune-to-3), own webhook receiver on :9798 + launchd unit, own compose project publishing the same 8091:80. - Cutover executed on mac-mini in order: oikos stack's web service stopped+removed, oikos-web project brought up on 8091; outer Caddy untouched (targets the published port) — serving + Authentik flow + /wails 404 quirk verified post-cutover. - Stripped from oikos: web/, cmd/desktop/, compose/web/, desktop CI workflow, ci.yml web job, Makefile ui/desktop/desktop-package/install targets, the compose web service, oikos-web from deploy.sh's fallback prune list; wails + go-keyring dropped from go.mod, vendor synced. - README / CONTRIBUTING / AGENTS.md / .agents dev+operations docs now point at the new repo; mbse + mascot design docs carry a path note. Risk: production SPA serving depends on the new pipeline now; rollback is versioned-image re-up of the old web service from a pre-split checkout (port 8091). Desktop builds installed before the split still check dtoro/oikos releases — one manual reinstall, noted in the oikos-web release notes. Verification: go vet, make test (race), make generate-check, golangci (no new findings; baseline down 400→365); post-cutover curls — localhost:8091 200, /wails/runtime.js 404, outer Caddy 302 Authentik.
145 lines
3.7 KiB
Python
145 lines
3.7 KiB
Python
# ISC License
|
|
#
|
|
# Copyright (c) 2018-2025, Andrea Giammarchi, @WebReflection
|
|
#
|
|
# Permission to use, copy, modify, and/or distribute this software for any
|
|
# purpose with or without fee is hereby granted, provided that the above
|
|
# copyright notice and this permission notice appear in all copies.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
# PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
import json as _json
|
|
|
|
class _Known:
|
|
def __init__(self):
|
|
self.key = []
|
|
self.value = []
|
|
|
|
class _String:
|
|
def __init__(self, value):
|
|
self.value = value
|
|
|
|
def _array_keys(value):
|
|
for i in range(len(value)):
|
|
yield i
|
|
|
|
def _object_keys(value):
|
|
for key in value:
|
|
yield key
|
|
|
|
def _is_array(value):
|
|
return isinstance(value, (list, tuple))
|
|
|
|
def _is_object(value):
|
|
return isinstance(value, dict)
|
|
|
|
def _is_string(value):
|
|
return isinstance(value, str)
|
|
|
|
def _index(known, input, value):
|
|
input.append(value)
|
|
index = str(len(input) - 1)
|
|
known.key.append(value)
|
|
known.value.append(index)
|
|
return index
|
|
|
|
def _relate(known, input, value):
|
|
if _is_string(value) or _is_array(value) or _is_object(value):
|
|
try:
|
|
return known.value[known.key.index(value)]
|
|
except:
|
|
return _index(known, input, value)
|
|
|
|
return value
|
|
|
|
def _resolver(input, lazy, parsed):
|
|
def resolver(output):
|
|
keys = _array_keys(output) if _is_array(output) else _object_keys(output) if _is_object(output) else []
|
|
for key in keys:
|
|
value = output[key]
|
|
if isinstance(value, _String):
|
|
tmp = input[int(value.value)]
|
|
output[key] = tmp
|
|
if (_is_array(tmp) or _is_object(tmp)) and tmp not in parsed:
|
|
parsed.append(tmp)
|
|
lazy.append([output, key])
|
|
|
|
return output
|
|
|
|
return resolver
|
|
|
|
def _transform(known, input, value):
|
|
if _is_array(value):
|
|
output = []
|
|
for val in value:
|
|
output.append(_relate(known, input, val))
|
|
return output
|
|
|
|
if _is_object(value):
|
|
obj = {}
|
|
for key in value:
|
|
obj[key] = _relate(known, input, value[key])
|
|
return obj
|
|
|
|
return value
|
|
|
|
def _wrap(value):
|
|
if _is_string(value):
|
|
return _String(value)
|
|
|
|
if _is_array(value):
|
|
i = 0
|
|
for val in value:
|
|
value[i] = _wrap(val)
|
|
i += 1
|
|
|
|
elif _is_object(value):
|
|
for key in value:
|
|
value[key] = _wrap(value[key])
|
|
|
|
return value
|
|
|
|
def parse(value, *args, **kwargs):
|
|
json = _json.loads(value, *args, **kwargs)
|
|
wrapped = []
|
|
for value in json:
|
|
wrapped.append(_wrap(value))
|
|
|
|
input = []
|
|
for value in wrapped:
|
|
if isinstance(value, _String):
|
|
input.append(value.value)
|
|
else:
|
|
input.append(value)
|
|
|
|
value = input[0]
|
|
lazy = []
|
|
revive = _resolver(input, lazy, [value])
|
|
|
|
value = revive(value)
|
|
|
|
i = 0
|
|
while i < len(lazy):
|
|
o, k = lazy[i]
|
|
i += 1
|
|
o[k] = revive(o[k])
|
|
|
|
return value
|
|
|
|
|
|
def stringify(value, *args, **kwargs):
|
|
known = _Known()
|
|
input = []
|
|
output = []
|
|
i = int(_index(known, input, value))
|
|
while i < len(input):
|
|
output.append(_transform(known, input, input[i]))
|
|
i += 1
|
|
return _json.dumps(output, *args, **kwargs)
|