make
This commit is contained in:
@@ -705,8 +705,8 @@ async def extensions(account_id: AccountId = Depends(check_account_id_exists)):
|
|||||||
),
|
),
|
||||||
"kvSchema": _load_kv_schema_from_config(ext.id),
|
"kvSchema": _load_kv_schema_from_config(ext.id),
|
||||||
"grantedPermissions": (
|
"grantedPermissions": (
|
||||||
user_exts_map.get(ext.id).extra.granted_permissions
|
user_ext.extra.granted_permissions
|
||||||
if user_exts_map.get(ext.id) and user_exts_map.get(ext.id).extra
|
if (user_ext := user_exts_map.get(ext.id)) and user_ext.extra
|
||||||
else []
|
else []
|
||||||
),
|
),
|
||||||
"latestRelease": (
|
"latestRelease": (
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||||
@@ -37,7 +39,7 @@ def _ext_static_dir(ext_id: str, upgrade_hash: str | None = None) -> Path:
|
|||||||
|
|
||||||
|
|
||||||
def _ensure_kv_table(db: Database, ext_id: str) -> str:
|
def _ensure_kv_table(db: Database, ext_id: str) -> str:
|
||||||
table = f"{ext_id}.kv" if db.schema else "kv"
|
table = _kv_table_name(db, ext_id)
|
||||||
query = f"""
|
query = f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table} (
|
CREATE TABLE IF NOT EXISTS {table} (
|
||||||
key TEXT PRIMARY KEY,
|
key TEXT PRIMARY KEY,
|
||||||
@@ -50,6 +52,16 @@ def _ensure_kv_table(db: Database, ext_id: str) -> str:
|
|||||||
_kv_schema_cache: dict[str, dict] = {}
|
_kv_schema_cache: dict[str, dict] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def _kv_table_name(db: Database, ext_id: str) -> str:
|
||||||
|
table = f"{ext_id}.kv" if db.schema else "kv"
|
||||||
|
if (
|
||||||
|
re.fullmatch(r"[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)?", table)
|
||||||
|
is None
|
||||||
|
):
|
||||||
|
raise ValueError("Invalid KV table name")
|
||||||
|
return table
|
||||||
|
|
||||||
|
|
||||||
def _load_kv_schema(ext_id: str) -> dict:
|
def _load_kv_schema(ext_id: str) -> dict:
|
||||||
if ext_id in _kv_schema_cache:
|
if ext_id in _kv_schema_cache:
|
||||||
return _kv_schema_cache[ext_id]
|
return _kv_schema_cache[ext_id]
|
||||||
@@ -102,9 +114,9 @@ def _coerce_schema_value(schema_entry: dict, value):
|
|||||||
|
|
||||||
async def _kv_get(db: Database, ext_id: str, key: str) -> str | None:
|
async def _kv_get(db: Database, ext_id: str, key: str) -> str | None:
|
||||||
await db.execute(_ensure_kv_table(db, ext_id))
|
await db.execute(_ensure_kv_table(db, ext_id))
|
||||||
table = f"{ext_id}.kv" if db.schema else "kv"
|
table = _kv_table_name(db, ext_id)
|
||||||
row = await db.fetchone(
|
row: dict[str, Any] | None = await db.fetchone(
|
||||||
f"SELECT value FROM {table} WHERE key = :key",
|
f"SELECT value FROM {table} WHERE key = :key", # noqa: S608
|
||||||
{"key": key},
|
{"key": key},
|
||||||
)
|
)
|
||||||
if not row:
|
if not row:
|
||||||
@@ -125,19 +137,19 @@ async def _kv_get(db: Database, ext_id: str, key: str) -> str | None:
|
|||||||
|
|
||||||
async def _kv_set(db: Database, ext_id: str, key: str, value: str) -> None:
|
async def _kv_set(db: Database, ext_id: str, key: str, value: str) -> None:
|
||||||
await db.execute(_ensure_kv_table(db, ext_id))
|
await db.execute(_ensure_kv_table(db, ext_id))
|
||||||
table = f"{ext_id}.kv" if db.schema else "kv"
|
table = _kv_table_name(db, ext_id)
|
||||||
existing = await db.fetchone(
|
existing: dict[str, Any] | None = await db.fetchone(
|
||||||
f"SELECT key FROM {table} WHERE key = :key",
|
f"SELECT key FROM {table} WHERE key = :key", # noqa: S608
|
||||||
{"key": key},
|
{"key": key},
|
||||||
)
|
)
|
||||||
if existing:
|
if existing:
|
||||||
await db.execute(
|
await db.execute(
|
||||||
f"UPDATE {table} SET value = :value WHERE key = :key",
|
f"UPDATE {table} SET value = :value WHERE key = :key", # noqa: S608
|
||||||
{"key": key, "value": value},
|
{"key": key, "value": value},
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await db.execute(
|
await db.execute(
|
||||||
f"INSERT INTO {table} (key, value) VALUES (:key, :value)",
|
f"INSERT INTO {table} (key, value) VALUES (:key, :value)", # noqa: S608
|
||||||
{"key": key, "value": value},
|
{"key": key, "value": value},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -174,6 +186,12 @@ def _register_pages_routes(router: APIRouter, ext_id: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def _register_kv_routes(router: APIRouter, ext_id: str, db: Database, ext) -> None:
|
def _register_kv_routes(router: APIRouter, ext_id: str, db: Database, ext) -> None:
|
||||||
|
_register_kv_read_routes(router, ext_id, db, ext)
|
||||||
|
_register_kv_write_routes(router, ext_id, db)
|
||||||
|
_register_kv_increment_route(router, ext_id, db, ext)
|
||||||
|
|
||||||
|
|
||||||
|
def _register_kv_read_routes(router: APIRouter, ext_id: str, db: Database, ext) -> None:
|
||||||
@router.get("/api/v1/kv/{key}")
|
@router.get("/api/v1/kv/{key}")
|
||||||
async def api_kv_get(key: str, user: User = Depends(check_user_exists)):
|
async def api_kv_get(key: str, user: User = Depends(check_user_exists)):
|
||||||
await _require_permission(user.id, ext_id, "ext.db.read_write")
|
await _require_permission(user.id, ext_id, "ext.db.read_write")
|
||||||
@@ -190,6 +208,8 @@ def _register_kv_routes(router: APIRouter, ext_id: str, db: Database, ext) -> No
|
|||||||
value = await _kv_get(db, ext_id, key)
|
value = await _kv_get(db, ext_id, key)
|
||||||
return {"key": key, "value": value}
|
return {"key": key, "value": value}
|
||||||
|
|
||||||
|
|
||||||
|
def _register_kv_write_routes(router: APIRouter, ext_id: str, db: Database) -> None:
|
||||||
@router.post("/api/v1/kv/{key}")
|
@router.post("/api/v1/kv/{key}")
|
||||||
async def api_kv_set(
|
async def api_kv_set(
|
||||||
key: str, payload: dict, user: User = Depends(check_user_exists)
|
key: str, payload: dict, user: User = Depends(check_user_exists)
|
||||||
@@ -213,6 +233,10 @@ def _register_kv_routes(router: APIRouter, ext_id: str, db: Database, ext) -> No
|
|||||||
await websocket_updater(f"{ext_id}:{key}", str(value))
|
await websocket_updater(f"{ext_id}:{key}", str(value))
|
||||||
return {"key": key, "value": value}
|
return {"key": key, "value": value}
|
||||||
|
|
||||||
|
|
||||||
|
def _register_kv_increment_route(
|
||||||
|
router: APIRouter, ext_id: str, db: Database, ext
|
||||||
|
) -> None:
|
||||||
@router.post("/api/v1/kv/{key}/increment")
|
@router.post("/api/v1/kv/{key}/increment")
|
||||||
async def api_kv_increment(key: str, user: User = Depends(check_user_exists)):
|
async def api_kv_increment(key: str, user: User = Depends(check_user_exists)):
|
||||||
await _require_permission(user.id, ext_id, "ext.db.read_write")
|
await _require_permission(user.id, ext_id, "ext.db.read_write")
|
||||||
|
|||||||
+41
-13
@@ -1,7 +1,9 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
from wasmtime import (
|
from wasmtime import (
|
||||||
Caller,
|
Caller,
|
||||||
@@ -72,7 +74,7 @@ def _run(coro):
|
|||||||
|
|
||||||
|
|
||||||
def _ensure_kv_table(db: Database, ext_id: str) -> str:
|
def _ensure_kv_table(db: Database, ext_id: str) -> str:
|
||||||
table = f"{ext_id}.kv" if db.schema else "kv"
|
table = _kv_table_name(db, ext_id)
|
||||||
return f"""
|
return f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table} (
|
CREATE TABLE IF NOT EXISTS {table} (
|
||||||
key TEXT PRIMARY KEY,
|
key TEXT PRIMARY KEY,
|
||||||
@@ -81,8 +83,18 @@ def _ensure_kv_table(db: Database, ext_id: str) -> str:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _kv_table_name(db: Database, ext_id: str) -> str:
|
||||||
|
table = f"{ext_id}.kv" if db.schema else "kv"
|
||||||
|
if (
|
||||||
|
re.fullmatch(r"[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)?", table)
|
||||||
|
is None
|
||||||
|
):
|
||||||
|
raise RuntimeError("Invalid KV table name")
|
||||||
|
return table
|
||||||
|
|
||||||
|
|
||||||
def _get_memory(caller: Caller):
|
def _get_memory(caller: Caller):
|
||||||
memory = caller.get_export("memory")
|
memory = caller.get_export("memory") # type: ignore[attr-defined]
|
||||||
if memory is None:
|
if memory is None:
|
||||||
raise RuntimeError("WASM module does not export memory")
|
raise RuntimeError("WASM module does not export memory")
|
||||||
return memory
|
return memory
|
||||||
@@ -109,10 +121,11 @@ def _db_get(
|
|||||||
) -> int:
|
) -> int:
|
||||||
key = _read_bytes(caller, key_ptr, key_len).decode(errors="ignore")
|
key = _read_bytes(caller, key_ptr, key_len).decode(errors="ignore")
|
||||||
_run(db.execute(_ensure_kv_table(db, ext_id)))
|
_run(db.execute(_ensure_kv_table(db, ext_id)))
|
||||||
table = f"{ext_id}.kv" if db.schema else "kv"
|
table = _kv_table_name(db, ext_id)
|
||||||
row = _run(
|
row = _run(
|
||||||
db.fetchone(
|
db.fetchone(
|
||||||
f"SELECT value FROM {table} WHERE key = :key", {"key": key}
|
f"SELECT value FROM {table} WHERE key = :key", # noqa: S608
|
||||||
|
{"key": key},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if not row:
|
if not row:
|
||||||
@@ -145,23 +158,24 @@ def _db_set(
|
|||||||
raise RuntimeError("Invalid value for schema") from exc
|
raise RuntimeError("Invalid value for schema") from exc
|
||||||
value = json.dumps(coerced) if entry.get("type") == "json" else str(coerced)
|
value = json.dumps(coerced) if entry.get("type") == "json" else str(coerced)
|
||||||
_run(db.execute(_ensure_kv_table(db, ext_id)))
|
_run(db.execute(_ensure_kv_table(db, ext_id)))
|
||||||
table = f"{ext_id}.kv" if db.schema else "kv"
|
table = _kv_table_name(db, ext_id)
|
||||||
row = _run(
|
row = _run(
|
||||||
db.fetchone(
|
db.fetchone(
|
||||||
f"SELECT key FROM {table} WHERE key = :key", {"key": key}
|
f"SELECT key FROM {table} WHERE key = :key", # noqa: S608
|
||||||
|
{"key": key},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if row:
|
if row:
|
||||||
_run(
|
_run(
|
||||||
db.execute(
|
db.execute(
|
||||||
f"UPDATE {table} SET value = :value WHERE key = :key",
|
f"UPDATE {table} SET value = :value WHERE key = :key", # noqa: S608
|
||||||
{"key": key, "value": value},
|
{"key": key, "value": value},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
_run(
|
_run(
|
||||||
db.execute(
|
db.execute(
|
||||||
f"INSERT INTO {table} (key, value) VALUES (:key, :value)",
|
f"INSERT INTO {table} (key, value) VALUES (:key, :value)", # noqa: S608
|
||||||
{"key": key, "value": value},
|
{"key": key, "value": value},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -174,7 +188,7 @@ def _load_module(module_path: Path, ext_id: str):
|
|||||||
engine = Engine(config)
|
engine = Engine(config)
|
||||||
store = Store(engine)
|
store = Store(engine)
|
||||||
if hasattr(store, "add_fuel"):
|
if hasattr(store, "add_fuel"):
|
||||||
store.add_fuel(settings.lnbits_wasm_fuel)
|
store.add_fuel(settings.lnbits_wasm_fuel) # type: ignore[attr-defined]
|
||||||
module = Module.from_file(engine, str(module_path))
|
module = Module.from_file(engine, str(module_path))
|
||||||
db = Database(f"ext_{ext_id}")
|
db = Database(f"ext_{ext_id}")
|
||||||
|
|
||||||
@@ -188,8 +202,15 @@ def _load_module(module_path: Path, ext_id: str):
|
|||||||
) -> int:
|
) -> int:
|
||||||
return _db_set(db, ext_id, caller, key_ptr, key_len, val_ptr, val_len)
|
return _db_set(db, ext_id, caller, key_ptr, key_len, val_ptr, val_len)
|
||||||
|
|
||||||
|
def linker_define(linker: Linker, module: str, name: str, func: Func) -> None:
|
||||||
|
try:
|
||||||
|
linker.define(store, module, name, func)
|
||||||
|
except TypeError:
|
||||||
|
linker.define(module, name, func) # type: ignore[call-arg, arg-type]
|
||||||
|
|
||||||
linker = Linker(engine)
|
linker = Linker(engine)
|
||||||
linker.define(
|
linker_define(
|
||||||
|
linker,
|
||||||
"host",
|
"host",
|
||||||
"db_get",
|
"db_get",
|
||||||
Func(
|
Func(
|
||||||
@@ -201,7 +222,8 @@ def _load_module(module_path: Path, ext_id: str):
|
|||||||
db_get,
|
db_get,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
linker.define(
|
linker_define(
|
||||||
|
linker,
|
||||||
"host",
|
"host",
|
||||||
"db_set",
|
"db_set",
|
||||||
Func(
|
Func(
|
||||||
@@ -236,9 +258,15 @@ def main() -> int:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
store, instance = _load_module(module_path, ext_id)
|
store, instance = _load_module(module_path, ext_id)
|
||||||
func = instance.exports(store)[function_name]
|
export = instance.exports(store)[function_name]
|
||||||
|
if not isinstance(export, Func):
|
||||||
|
raise RuntimeError(f"Export '{function_name}' is not callable")
|
||||||
|
func = cast(Func, export)
|
||||||
int_args = [int(a) for a in args]
|
int_args = [int(a) for a in args]
|
||||||
result = func(store, *int_args)
|
if hasattr(func, "call"):
|
||||||
|
result = func.call(store, *int_args) # type: ignore[attr-defined]
|
||||||
|
else:
|
||||||
|
result = func(store, *int_args) # type: ignore[operator]
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
payload = {"ok": False, "error": str(exc)}
|
payload = {"ok": False, "error": str(exc)}
|
||||||
sys.stdout.write(json.dumps(payload))
|
sys.stdout.write(json.dumps(payload))
|
||||||
|
|||||||
Reference in New Issue
Block a user