187 lines
5.3 KiB
Plaintext
187 lines
5.3 KiB
Plaintext
# Team Activity Log — Starlark Backend
|
|
#
|
|
# Handles:
|
|
# GET /entries → list (optional ?category= filter)
|
|
# POST /entries → create
|
|
# DELETE /entries/:id → delete
|
|
#
|
|
# NOTE: The sandbox has no json module. Request body arrives as a raw
|
|
# string; response body must be a raw string. Helper functions build
|
|
# JSON manually. A json.encode/decode module would clean this up —
|
|
# tracked as a sandbox improvement.
|
|
|
|
def on_request(req):
|
|
method = req["method"]
|
|
path = req["path"]
|
|
|
|
if method == "GET" and path == "/entries":
|
|
return _list_entries(req)
|
|
elif method == "POST" and path == "/entries":
|
|
return _create_entry(req)
|
|
elif method == "DELETE" and path.startswith("/entries/"):
|
|
entry_id = path[len("/entries/"):]
|
|
return _delete_entry(req, entry_id)
|
|
|
|
return {"status": 404, "body": '{"error":"not found"}'}
|
|
|
|
|
|
def _list_entries(req):
|
|
category = ""
|
|
q = req.get("query", None)
|
|
if q != None:
|
|
cv = q.get("category", None)
|
|
if cv != None:
|
|
category = str(cv)
|
|
|
|
if category:
|
|
rows = db.query("entries", filters={"category": category}, order="-created_at", limit=200)
|
|
else:
|
|
rows = db.query("entries", order="-created_at", limit=200)
|
|
|
|
parts = []
|
|
for row in rows:
|
|
parts.append(_entry_json(row))
|
|
return {"status": 200, "body": '{"data":[' + ",".join(parts) + "]}"}
|
|
|
|
|
|
def _create_entry(req):
|
|
body = _parse_body(str(req.get("body", "")))
|
|
if not body:
|
|
return {"status": 400, "body": '{"error":"JSON body required"}'}
|
|
|
|
message = body.get("message", "")
|
|
if not message:
|
|
return {"status": 400, "body": '{"error":"message is required"}'}
|
|
|
|
category = body.get("category", "note")
|
|
username = body.get("username", "")
|
|
created_at = body.get("created_at", "")
|
|
user_id = str(req.get("user_id", ""))
|
|
|
|
# Validate category against admin settings
|
|
allowed = _allowed_categories()
|
|
if allowed and category not in allowed:
|
|
return {"status": 400, "body": '{"error":"invalid category: ' + _esc(category) + '"}'}
|
|
|
|
row_id = db.insert("entries", {
|
|
"user_id": user_id,
|
|
"username": username,
|
|
"category": category,
|
|
"message": message,
|
|
"created_at": created_at,
|
|
})
|
|
|
|
_prune()
|
|
|
|
return {"status": 201, "body": _entry_json({
|
|
"id": str(row_id), "user_id": user_id, "username": username,
|
|
"category": category, "message": message, "created_at": created_at,
|
|
})}
|
|
|
|
|
|
def _delete_entry(req, entry_id):
|
|
if not entry_id:
|
|
return {"status": 400, "body": '{"error":"entry id required"}'}
|
|
db.delete("entries", entry_id)
|
|
return {"status": 200, "body": '{"deleted":true,"id":"' + _esc(entry_id) + '"}'}
|
|
|
|
|
|
# ── Settings helpers ─────────────────────────
|
|
|
|
def _allowed_categories():
|
|
raw = settings.get("categories")
|
|
if not raw:
|
|
return []
|
|
return [p.strip() for p in raw.split(",") if p.strip()]
|
|
|
|
|
|
def _prune():
|
|
max_raw = settings.get("max_entries")
|
|
if not max_raw:
|
|
return
|
|
limit = int(max_raw)
|
|
if limit <= 0:
|
|
return
|
|
rows = db.query("entries", order="-created_at", limit=limit + 50)
|
|
if len(rows) > limit:
|
|
for row in rows[limit:]:
|
|
db.delete("entries", row["id"])
|
|
|
|
|
|
# ── JSON helpers (no json module in sandbox) ─
|
|
|
|
def _esc(s):
|
|
return str(s).replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
|
|
|
|
def _entry_json(e):
|
|
return (
|
|
'{"id":"' + _esc(e.get("id", ""))
|
|
+ '","user_id":"' + _esc(e.get("user_id", ""))
|
|
+ '","username":"' + _esc(e.get("username", ""))
|
|
+ '","category":"' + _esc(e.get("category", ""))
|
|
+ '","message":"' + _esc(e.get("message", ""))
|
|
+ '","created_at":"' + _esc(e.get("created_at", ""))
|
|
+ '"}'
|
|
)
|
|
|
|
def _parse_body(raw):
|
|
"""Minimal flat-object JSON parser. Handles {"key":"value"} payloads."""
|
|
s = raw.strip()
|
|
if not s or s[0] != "{":
|
|
return None
|
|
s = s[1:]
|
|
if s and s[-1] == "}":
|
|
s = s[:-1]
|
|
|
|
result = {}
|
|
in_str = False
|
|
escape = False
|
|
current = ""
|
|
pairs = []
|
|
|
|
for ch in s.elems():
|
|
if escape:
|
|
current += ch
|
|
escape = False
|
|
elif ch == "\\":
|
|
current += ch
|
|
escape = True
|
|
elif ch == '"':
|
|
current += ch
|
|
in_str = not in_str
|
|
elif ch == "," and not in_str:
|
|
pairs.append(current.strip())
|
|
current = ""
|
|
else:
|
|
current += ch
|
|
if current.strip():
|
|
pairs.append(current.strip())
|
|
|
|
for pair in pairs:
|
|
colon = -1
|
|
in_s = False
|
|
esc = False
|
|
for i in range(len(pair)):
|
|
c = pair[i]
|
|
if esc:
|
|
esc = False
|
|
elif c == "\\":
|
|
esc = True
|
|
elif c == '"':
|
|
in_s = not in_s
|
|
elif c == ":" and not in_s:
|
|
colon = i
|
|
break
|
|
if colon < 0:
|
|
continue
|
|
key = _unquote(pair[:colon].strip())
|
|
val = _unquote(pair[colon+1:].strip())
|
|
if key:
|
|
result[key] = val
|
|
return result
|
|
|
|
def _unquote(s):
|
|
if len(s) >= 2 and s[0] == '"' and s[-1] == '"':
|
|
return s[1:-1].replace('\\"', '"').replace("\\\\", "\\").replace("\\n", "\n")
|
|
return s
|