|
|
@@ -3131,6 +3131,169 @@ static JSValue js_storage_list_collections(JSContext* ctx, JSValue this_val, int
|
|
|
return response;
|
|
|
}
|
|
|
|
|
|
+// Shared shape for the file bindings: {success, error?} plus record fields.
|
|
|
+static JSValue fileInfoToJs(JSContext* ctx, const ScriptFileInfo& info) {
|
|
|
+ JSValue obj = JS_NewObject(ctx);
|
|
|
+ JS_SetPropertyStr(ctx, obj, "success", JS_TRUE);
|
|
|
+ JS_SetPropertyStr(ctx, obj, "id", JS_NewString(ctx, info.id.c_str()));
|
|
|
+ JS_SetPropertyStr(ctx, obj, "name", JS_NewString(ctx, info.name.c_str()));
|
|
|
+ JS_SetPropertyStr(ctx, obj, "mimeType", JS_NewString(ctx, info.mime_type.c_str()));
|
|
|
+ JS_SetPropertyStr(ctx, obj, "size", JS_NewInt64(ctx, info.size));
|
|
|
+ JS_SetPropertyStr(ctx, obj, "fileType", JS_NewString(ctx, info.file_type.c_str()));
|
|
|
+ JS_SetPropertyStr(ctx, obj, "relatedId", JS_NewString(ctx, info.related_id.c_str()));
|
|
|
+ JS_SetPropertyStr(ctx, obj, "checksum", JS_NewString(ctx, info.checksum.c_str()));
|
|
|
+ JS_SetPropertyStr(ctx, obj, "isPublic", JS_NewBool(ctx, info.is_public));
|
|
|
+ JS_SetPropertyStr(ctx, obj, "refCount", JS_NewInt64(ctx, info.ref_count));
|
|
|
+ JS_SetPropertyStr(ctx, obj, "createdAt", JS_NewInt64(ctx, info.created_at));
|
|
|
+ JS_SetPropertyStr(ctx, obj, "deduplicated", JS_NewBool(ctx, info.deduplicated));
|
|
|
+ return obj;
|
|
|
+}
|
|
|
+
|
|
|
+// storage.uploadFile(base64, meta) - Store binary content in the database file store
|
|
|
+static JSValue js_storage_upload_file(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
|
+ const ScriptContext* script_ctx = getScriptContext(ctx);
|
|
|
+ if (!script_ctx || !script_ctx->storage_upload_file) {
|
|
|
+ return createJsError(ctx, "Storage file API not available");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (argc < 1) {
|
|
|
+ return JS_ThrowTypeError(ctx, "storage.uploadFile requires base64 data");
|
|
|
+ }
|
|
|
+
|
|
|
+ const char* data_cstr = JS_ToCString(ctx, argv[0]);
|
|
|
+ if (!data_cstr) {
|
|
|
+ return JS_ThrowTypeError(ctx, "data must be a base64 string");
|
|
|
+ }
|
|
|
+ std::string decoded = base64Decode(data_cstr);
|
|
|
+ JS_FreeCString(ctx, data_cstr);
|
|
|
+
|
|
|
+ if (decoded.empty()) {
|
|
|
+ return createJsError(ctx, "uploadFile received empty or invalid base64 data");
|
|
|
+ }
|
|
|
+
|
|
|
+ ScriptFileMeta meta;
|
|
|
+ if (argc > 1 && JS_IsObject(argv[1])) {
|
|
|
+ auto readStr = [&](const char* key, std::string& out) {
|
|
|
+ JSValue v = JS_GetPropertyStr(ctx, argv[1], key);
|
|
|
+ if (!JS_IsUndefined(v) && !JS_IsNull(v)) {
|
|
|
+ const char* s = JS_ToCString(ctx, v);
|
|
|
+ if (s) {
|
|
|
+ out = s;
|
|
|
+ JS_FreeCString(ctx, s);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ JS_FreeValue(ctx, v);
|
|
|
+ };
|
|
|
+ readStr("name", meta.name);
|
|
|
+ readStr("mimeType", meta.mime_type);
|
|
|
+ readStr("fileType", meta.file_type);
|
|
|
+ readStr("relatedId", meta.related_id);
|
|
|
+
|
|
|
+ JSValue pub = JS_GetPropertyStr(ctx, argv[1], "isPublic");
|
|
|
+ if (JS_IsBool(pub)) {
|
|
|
+ meta.is_public = JS_ToBool(ctx, pub) != 0;
|
|
|
+ }
|
|
|
+ JS_FreeValue(ctx, pub);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (meta.file_type.empty()) {
|
|
|
+ meta.file_type = "generated";
|
|
|
+ }
|
|
|
+
|
|
|
+ std::vector<uint8_t> bytes(decoded.begin(), decoded.end());
|
|
|
+ auto result = script_ctx->storage_upload_file(bytes, meta);
|
|
|
+ if (result.failed()) {
|
|
|
+ return createJsError(ctx, result.error().message());
|
|
|
+ }
|
|
|
+ return fileInfoToJs(ctx, result.value());
|
|
|
+}
|
|
|
+
|
|
|
+// storage.downloadFile(id) - Fetch binary content, returned base64-encoded
|
|
|
+static JSValue js_storage_download_file(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
|
+ const ScriptContext* script_ctx = getScriptContext(ctx);
|
|
|
+ if (!script_ctx || !script_ctx->storage_download_file) {
|
|
|
+ return createJsError(ctx, "Storage file API not available");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (argc < 1) {
|
|
|
+ return JS_ThrowTypeError(ctx, "storage.downloadFile requires a file id");
|
|
|
+ }
|
|
|
+
|
|
|
+ const char* id = JS_ToCString(ctx, argv[0]);
|
|
|
+ if (!id) {
|
|
|
+ return JS_ThrowTypeError(ctx, "file id must be a string");
|
|
|
+ }
|
|
|
+ std::string id_str(id);
|
|
|
+ JS_FreeCString(ctx, id);
|
|
|
+
|
|
|
+ auto result = script_ctx->storage_download_file(id_str);
|
|
|
+ if (result.failed()) {
|
|
|
+ return createJsError(ctx, result.error().message());
|
|
|
+ }
|
|
|
+
|
|
|
+ const auto& bytes = result.value();
|
|
|
+ std::string raw(bytes.begin(), bytes.end());
|
|
|
+
|
|
|
+ JSValue obj = JS_NewObject(ctx);
|
|
|
+ JS_SetPropertyStr(ctx, obj, "success", JS_TRUE);
|
|
|
+ JS_SetPropertyStr(ctx, obj, "data", JS_NewString(ctx, base64Encode(raw).c_str()));
|
|
|
+ JS_SetPropertyStr(ctx, obj, "size", JS_NewInt64(ctx, static_cast<int64_t>(bytes.size())));
|
|
|
+ return obj;
|
|
|
+}
|
|
|
+
|
|
|
+// storage.getFileInfo(id) - Metadata for a stored file
|
|
|
+static JSValue js_storage_get_file_info(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
|
+ const ScriptContext* script_ctx = getScriptContext(ctx);
|
|
|
+ if (!script_ctx || !script_ctx->storage_get_file_info) {
|
|
|
+ return createJsError(ctx, "Storage file API not available");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (argc < 1) {
|
|
|
+ return JS_ThrowTypeError(ctx, "storage.getFileInfo requires a file id");
|
|
|
+ }
|
|
|
+
|
|
|
+ const char* id = JS_ToCString(ctx, argv[0]);
|
|
|
+ if (!id) {
|
|
|
+ return JS_ThrowTypeError(ctx, "file id must be a string");
|
|
|
+ }
|
|
|
+ std::string id_str(id);
|
|
|
+ JS_FreeCString(ctx, id);
|
|
|
+
|
|
|
+ auto result = script_ctx->storage_get_file_info(id_str);
|
|
|
+ if (result.failed()) {
|
|
|
+ return createJsError(ctx, result.error().message());
|
|
|
+ }
|
|
|
+ return fileInfoToJs(ctx, result.value());
|
|
|
+}
|
|
|
+
|
|
|
+// storage.deleteFile(id) - Drop a stored file
|
|
|
+static JSValue js_storage_delete_file(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
|
+ const ScriptContext* script_ctx = getScriptContext(ctx);
|
|
|
+ if (!script_ctx || !script_ctx->storage_delete_file) {
|
|
|
+ return createJsError(ctx, "Storage file API not available");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (argc < 1) {
|
|
|
+ return JS_ThrowTypeError(ctx, "storage.deleteFile requires a file id");
|
|
|
+ }
|
|
|
+
|
|
|
+ const char* id = JS_ToCString(ctx, argv[0]);
|
|
|
+ if (!id) {
|
|
|
+ return JS_ThrowTypeError(ctx, "file id must be a string");
|
|
|
+ }
|
|
|
+ std::string id_str(id);
|
|
|
+ JS_FreeCString(ctx, id);
|
|
|
+
|
|
|
+ auto result = script_ctx->storage_delete_file(id_str);
|
|
|
+ if (result.failed()) {
|
|
|
+ return createJsError(ctx, result.error().message());
|
|
|
+ }
|
|
|
+
|
|
|
+ JSValue obj = JS_NewObject(ctx);
|
|
|
+ JS_SetPropertyStr(ctx, obj, "success", JS_TRUE);
|
|
|
+ return obj;
|
|
|
+}
|
|
|
+
|
|
|
// Setup storage API on smartbotic namespace
|
|
|
static void setupStorageAPI(JSContext* ctx, JSValue smartbotic) {
|
|
|
JSValue storage = JS_NewObject(ctx);
|
|
|
@@ -3141,6 +3304,10 @@ static void setupStorageAPI(JSContext* ctx, JSValue smartbotic) {
|
|
|
JS_SetPropertyStr(ctx, storage, "delete", JS_NewCFunction(ctx, js_storage_delete, "delete", 3));
|
|
|
JS_SetPropertyStr(ctx, storage, "query", JS_NewCFunction(ctx, js_storage_query, "query", 2));
|
|
|
JS_SetPropertyStr(ctx, storage, "listCollections", JS_NewCFunction(ctx, js_storage_list_collections, "listCollections", 1));
|
|
|
+ JS_SetPropertyStr(ctx, storage, "uploadFile", JS_NewCFunction(ctx, js_storage_upload_file, "uploadFile", 2));
|
|
|
+ JS_SetPropertyStr(ctx, storage, "downloadFile", JS_NewCFunction(ctx, js_storage_download_file, "downloadFile", 1));
|
|
|
+ JS_SetPropertyStr(ctx, storage, "getFileInfo", JS_NewCFunction(ctx, js_storage_get_file_info, "getFileInfo", 1));
|
|
|
+ JS_SetPropertyStr(ctx, storage, "deleteFile", JS_NewCFunction(ctx, js_storage_delete_file, "deleteFile", 1));
|
|
|
|
|
|
JS_SetPropertyStr(ctx, smartbotic, "storage", storage);
|
|
|
}
|