diff --git a/crates/app/src/assets.rs b/crates/app/src/assets.rs index aba7de9..50b256b 100644 --- a/crates/app/src/assets.rs +++ b/crates/app/src/assets.rs @@ -41,6 +41,7 @@ pub const ME_JS: &str = include_str!("./public/js/me.js"); pub const STREAMS_JS: &str = include_str!("./public/js/streams.js"); pub const CARP_JS: &str = include_str!("./public/js/carp.js"); pub const PROTO_LINKS_JS: &str = include_str!("./public/js/proto_links.js"); +pub const APP_SDK_JS: &str = include_str!("./public/js/app_sdk.js"); // html pub const BODY: &str = include_str!("./public/html/body.lisp"); diff --git a/crates/app/src/langs/en-US.toml b/crates/app/src/langs/en-US.toml index 243b70f..c77bfd5 100644 --- a/crates/app/src/langs/en-US.toml +++ b/crates/app/src/langs/en-US.toml @@ -234,6 +234,7 @@ version = "1.0.0" "stacks:label.block_all" = "Block all" "stacks:label.unblock_all" = "Unblock all" +"forge:label.forges" = "Forges" "forge:label.my_forges" = "My forges" "forge:label.create_new" = "Create new forge" "forge:tab.info" = "Info" @@ -242,6 +243,7 @@ version = "1.0.0" "forge:action.close" = "Close" "developer:label.for_developers" = "for Developers" +"developer:label.apps" = "Apps" "developer:label.my_apps" = "My apps" "developer:label.create_new" = "Create new app" "developer:label.homepage" = "Homepage" diff --git a/crates/app/src/public/html/components.lisp b/crates/app/src/public/html/components.lisp index a83ad44..516fe2a 100644 --- a/crates/app/src/public/html/components.lisp +++ b/crates/app/src/public/html/components.lisp @@ -1142,16 +1142,6 @@ (text "{{ icon \"circle-user-round\" }}") (span (text "{{ text \"auth:link.my_profile\" }}"))) - (a - ("href" "/journals/0/0") - (icon (text "notebook")) - (str (text "general:link.journals"))) - (text "{% if config.lw_host -%}") - (button - ("onclick" "document.getElementById('littleweb').showModal()") - (icon (text "globe")) - (str (text "general:link.little_web"))) - (text "{%- endif %}") (text "{% if not user.settings.disable_achievements -%}") (a ("href" "/achievements") diff --git a/crates/app/src/public/html/macros.lisp b/crates/app/src/public/html/macros.lisp index 969439b..fff3188 100644 --- a/crates/app/src/public/html/macros.lisp +++ b/crates/app/src/public/html/macros.lisp @@ -39,12 +39,6 @@ ("title" "Create post") (icon (text "square-pen"))) - (a - ("href" "/chats/0/0") - ("class" "button {% if selected == 'chats' -%}active{%- endif %}") - ("title" "Chats") - (icon (text "message-circle"))) - (a ("href" "/requests") ("class" "button {% if selected == 'requests' -%}active{%- endif %}") @@ -65,6 +59,43 @@ ("id" "notifications_span") (text "{{ user.notification_count }}"))) + (text "{% if user -%}") + (div + ("class" "dropdown") + (button + ("class" "flex-row {% if selected == 'chats' or selected == 'journals' -%}active{%- endif %}") + ("onclick" "trigger('atto::hooks::dropdown', [event])") + ("exclude" "dropdown") + ("title" "More services") + (icon (text "grip"))) + + (div + ("class" "inner") + (a + ("href" "/chats/0/0") + ("title" "Chats") + (icon (text "message-circle")) + (str (text "communities:label.chats"))) + (a + ("href" "/journals/0/0") + (icon (text "notebook")) + (str (text "general:link.journals"))) + (a + ("href" "/forges") + (icon (text "anvil")) + (str (text "forge:label.forges"))) + (a + ("href" "/developer") + (icon (text "code")) + (str (text "developer:label.apps"))) + (text "{% if config.lw_host -%}") + (button + ("onclick" "document.getElementById('littleweb').showModal()") + (icon (text "globe")) + (str (text "general:link.little_web"))) + (text "{%- endif %}"))) + (text "{%- endif %}") + (text "{% if not hide_user_menu -%}") (div ("class" "dropdown") diff --git a/crates/app/src/public/js/app_sdk.js b/crates/app/src/public/js/app_sdk.js new file mode 100644 index 0000000..10de985 --- /dev/null +++ b/crates/app/src/public/js/app_sdk.js @@ -0,0 +1,108 @@ +import { + JSONParse as json_parse, + JSONStringify as json_stringify, +} from "https://unpkg.com/json-with-bigint@3.4.4/json-with-bigint.js"; + +export default function tetratto(tetratto_host, api_key) { + function api_promise(res) { + return new Promise((resolve, reject) => { + if (res.ok) { + resolve(res.payload); + } else { + reject(res.message); + } + }); + } + + async function app() { + return api_promise( + json_parse( + await ( + await fetch(`${tetratto_host}/api/v1/app_data/app`, { + method: "GET", + headers: { + "Atto-Secret-Key": api_key, + }, + }) + ).text(), + ), + ); + } + + async function query(body) { + return api_promise( + json_parse( + await ( + await fetch(`${tetratto_host}/api/v1/app_data/query`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Atto-Secret-Key": api_key, + }, + body: json_stringify(body), + }) + ).text(), + ), + ); + } + + async function insert(key, value) { + return api_promise( + json_parse( + await ( + await fetch(`${tetratto_host}/api/v1/app_data`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Atto-Secret-Key": api_key, + }, + body: json_stringify({ + key, + value, + }), + }) + ).text(), + ), + ); + } + + async function remove(id) { + return api_promise( + json_parse( + await ( + await fetch(`${tetratto_host}/api/v1/app_data/${id}`, { + method: "DELETE", + headers: { + "Atto-Secret-Key": api_key, + }, + }) + ).text(), + ), + ); + } + + async function remove_query(body) { + return api_promise( + json_parse( + await ( + await fetch(`${tetratto_host}/api/v1/app_data/query`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + "Atto-Secret-Key": api_key, + }, + body: json_stringify(body), + }) + ).text(), + ), + ); + } + + return { + app, + query, + insert, + remove, + remove_query, + }; +} diff --git a/crates/app/src/public/js/atto.js b/crates/app/src/public/js/atto.js index 157d6d3..d8ffb86 100644 --- a/crates/app/src/public/js/atto.js +++ b/crates/app/src/public/js/atto.js @@ -415,33 +415,35 @@ media_theme_pref(); }); self.define("hooks::long_text.init", (_) => { - for (const element of Array.from( - document.querySelectorAll("[hook=long]") || [], - )) { - const is_long = element.innerText.length >= 64 * 8; + setTimeout(() => { + for (const element of Array.from( + document.querySelectorAll("[hook=long]") || [], + )) { + const is_long = element.innerText.length >= 64 * 8; - if (!is_long) { - continue; + if (!is_long) { + continue; + } + + element.classList.add("hook:long.hidden_text"); + + if (element.getAttribute("hook-arg") === "lowered") { + element.classList.add("hook:long.hidden_text+lowered"); + } + + const html = element.innerHTML; + const short = html.slice(0, 64 * 8); + element.innerHTML = `${short}...`; + + // event + const listener = () => { + self["hooks::long"](element, html); + element.removeEventListener("click", listener); + }; + + element.addEventListener("click", listener); } - - element.classList.add("hook:long.hidden_text"); - - if (element.getAttribute("hook-arg") === "lowered") { - element.classList.add("hook:long.hidden_text+lowered"); - } - - const html = element.innerHTML; - const short = html.slice(0, 64 * 8); - element.innerHTML = `${short}...`; - - // event - const listener = () => { - self["hooks::long"](element, html); - element.removeEventListener("click", listener); - }; - - element.addEventListener("click", listener); - } + }, 150); }); self.define("hooks::alt", (_) => { diff --git a/crates/app/src/routes/api/v1/app_data.rs b/crates/app/src/routes/api/v1/app_data.rs index c2983f1..f9da1c8 100644 --- a/crates/app/src/routes/api/v1/app_data.rs +++ b/crates/app/src/routes/api/v1/app_data.rs @@ -9,6 +9,23 @@ use tetratto_core::model::{ ApiReturn, Error, }; +pub async fn get_app_request( + headers: HeaderMap, + Extension(data): Extension, +) -> impl IntoResponse { + let data = &(data.read().await).0; + let app = match get_app_from_key!(data, headers) { + Some(x) => x, + None => return Json(Error::NotAllowed.into()), + }; + + Json(ApiReturn { + ok: true, + message: "Success".to_string(), + payload: Some(app), + }) +} + pub async fn query_request( headers: HeaderMap, Extension(data): Extension, diff --git a/crates/app/src/routes/api/v1/mod.rs b/crates/app/src/routes/api/v1/mod.rs index 9e48f8d..3b6e61e 100644 --- a/crates/app/src/routes/api/v1/mod.rs +++ b/crates/app/src/routes/api/v1/mod.rs @@ -433,6 +433,7 @@ pub fn routes() -> Router { .route("/apps/{id}/roll", post(apps::roll_api_key_request)) // app data .route("/app_data", post(app_data::create_request)) + .route("/app_data/app", get(app_data::get_app_request)) .route("/app_data/{id}", delete(app_data::delete_request)) .route("/app_data/{id}/value", post(app_data::update_value_request)) .route("/app_data/query", post(app_data::query_request)) diff --git a/crates/app/src/routes/assets.rs b/crates/app/src/routes/assets.rs index d7843bd..f18ede0 100644 --- a/crates/app/src/routes/assets.rs +++ b/crates/app/src/routes/assets.rs @@ -20,3 +20,4 @@ serve_asset!(me_js_request: ME_JS("text/javascript")); serve_asset!(streams_js_request: STREAMS_JS("text/javascript")); serve_asset!(carp_js_request: CARP_JS("text/javascript")); serve_asset!(proto_links_request: PROTO_LINKS_JS("text/javascript")); +serve_asset!(app_sdk_request: APP_SDK_JS("text/javascript")); diff --git a/crates/app/src/routes/mod.rs b/crates/app/src/routes/mod.rs index e0fa067..cde54f5 100644 --- a/crates/app/src/routes/mod.rs +++ b/crates/app/src/routes/mod.rs @@ -21,6 +21,7 @@ pub fn routes(config: &Config) -> Router { .route("/js/streams.js", get(assets::streams_js_request)) .route("/js/carp.js", get(assets::carp_js_request)) .route("/js/proto_links.js", get(assets::proto_links_request)) + .route("/js/app_sdk.js", get(assets::app_sdk_request)) .nest_service( "/public", get_service(tower_http::services::ServeDir::new(&config.dirs.assets)), diff --git a/example/app_sdk_test.js b/example/app_sdk_test.js new file mode 100644 index 0000000..000eb8d --- /dev/null +++ b/example/app_sdk_test.js @@ -0,0 +1,28 @@ +// @ts-nocheck +// APP_API_KEY=... deno run --allow-net --allow-import --allow-env -r app_sdk_test.js +const deno = Deno; +const sdk = (await import("http://localhost:4118/js/app_sdk.js")).default( + "http://localhost:4118", + deno.env.get("APP_API_KEY"), +); + +// check data used +console.log("data used:", (await sdk.app()).data_used); + +// record insert +await sdk.insert("deno_test", "Hello, Deno!"); +console.log("record created"); +console.log("data used:", (await sdk.app()).data_used); + +// testing record query then delete +const record = ( + await sdk.query({ + query: { KeyIs: "deno_test" }, + mode: { One: 0 }, + }) +).One; + +console.log(record); +await sdk.remove(record.id); +console.log("record deleted"); +console.log("data used:", (await sdk.app()).data_used);