Changeset 0.37.15 (#227)
This commit is contained in:
186
packages/team-activity-log/script.star
Normal file
186
packages/team-activity-log/script.star
Normal file
@@ -0,0 +1,186 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user