about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2022-10-07 19:53:04 +0300
committerVika <vika@fireburn.ru>2022-10-07 19:53:04 +0300
commit9f7b903901acb0cd6ec9cb2146406a92ebf79cab (patch)
treec7a45f69d2d59621365494dc2d3657848390b61d
parent6cb479acc61ab19f655cedd878283b214e352a3d (diff)
downloadkittybox-9f7b903901acb0cd6ec9cb2146406a92ebf79cab.tar.zst
templates: move static assets to the templates crate
It makes more sense to keep CSS near the templates, and the
client-side JavaScript code too, since it depends on the DOM structure
to work. Additionally, the overhead of `include_dir!()` is almost
completely mitigated by the fact that this is a separate crate that
isn't recompiled often.

The linking stage, however, is still expected to take a little bit
long. But I doubt it'd be longer than what it was before, since it's
the same exact files that get linked into the app.
-rw-r--r--.gitignore2
-rw-r--r--kittybox-rs/Cargo.lock63
-rw-r--r--kittybox-rs/Cargo.toml2
-rw-r--r--kittybox-rs/build.rs14
-rw-r--r--kittybox-rs/src/frontend/login.rs2
-rw-r--r--kittybox-rs/src/frontend/mod.rs47
-rw-r--r--kittybox-rs/src/frontend/onboarding.rs2
-rw-r--r--kittybox-rs/src/indieauth/mod.rs4
-rw-r--r--kittybox-rs/templates/Cargo.toml12
-rw-r--r--kittybox-rs/templates/assets/jslicense.html (renamed from kittybox-rs/javascript/jslicense.html)0
-rw-r--r--kittybox-rs/templates/assets/onboarding.css (renamed from kittybox-rs/src/frontend/onboarding.css)0
-rw-r--r--kittybox-rs/templates/assets/style.css (renamed from kittybox-rs/src/frontend/style.css)0
-rw-r--r--kittybox-rs/templates/build.rs26
-rw-r--r--kittybox-rs/templates/javascript/dist/indieauth.js118
-rw-r--r--kittybox-rs/templates/javascript/dist/webauthn/register.js1
-rw-r--r--kittybox-rs/templates/javascript/src/indieauth.ts (renamed from kittybox-rs/javascript/src/indieauth.ts)0
-rw-r--r--kittybox-rs/templates/javascript/src/lib.ts (renamed from kittybox-rs/javascript/src/lib.ts)0
-rw-r--r--kittybox-rs/templates/javascript/src/onboarding.ts (renamed from kittybox-rs/javascript/src/onboarding.ts)0
-rw-r--r--kittybox-rs/templates/javascript/src/webauthn/register.ts0
-rw-r--r--kittybox-rs/templates/javascript/tsconfig.json (renamed from kittybox-rs/javascript/tsconfig.json)0
-rw-r--r--kittybox-rs/templates/src/lib.rs31
21 files changed, 239 insertions, 85 deletions
diff --git a/.gitignore b/.gitignore
index 6744025..ceff1aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,8 +8,10 @@ dump.rdb
 .\#*
 .*~
 *~
+/.log
 /kittybox-rs/test-dir
 /kittybox-rs/media-store
 /kittybox-rs/auth-store
 /kittybox-rs/fonts/*
+/kittybox-rs/companion-lite/dist
 /token.txt
diff --git a/kittybox-rs/Cargo.lock b/kittybox-rs/Cargo.lock
index 8815b41..e8e0bf3 100644
--- a/kittybox-rs/Cargo.lock
+++ b/kittybox-rs/Cargo.lock
@@ -171,9 +171,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
 name = "axum"
-version = "0.5.15"
+version = "0.5.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9de18bc5f2e9df8f52da03856bf40e29b747de5a84e43aefff90e3dc4a21529b"
+checksum = "c9e3356844c4d6a6d6467b8da2cffb4a2820be256f50a3a386c9d152bab31043"
 dependencies = [
  "async-trait",
  "axum-core",
@@ -204,9 +204,9 @@ dependencies = [
 
 [[package]]
 name = "axum-core"
-version = "0.2.7"
+version = "0.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4f44a0e6200e9d11a1cdc989e4b358f6e3d354fbf48478f345a17f4e43f8635"
+checksum = "d9f0c0a60006f2a293d82d571f635042a72edf927539b7685bd62d361963839b"
 dependencies = [
  "async-trait",
  "bytes",
@@ -214,6 +214,8 @@ dependencies = [
  "http",
  "http-body",
  "mime",
+ "tower-layer",
+ "tower-service",
 ]
 
 [[package]]
@@ -1158,6 +1160,25 @@ dependencies = [
 ]
 
 [[package]]
+name = "include_dir"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "482a2e29200b7eed25d7fdbd14423326760b7f6658d21a4cf12d55a50713c69f"
+dependencies = [
+ "include_dir_macros",
+]
+
+[[package]]
+name = "include_dir_macros"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e074c19deab2501407c91ba1860fa3d6820bfde307db6d8cb851b55a10be89b"
+dependencies = [
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+]
+
+[[package]]
 name = "indexmap"
 version = "1.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1229,8 +1250,8 @@ dependencies = [
  "futures-util",
  "hex",
  "hyper",
+ "kittybox-frontend-renderer",
  "kittybox-indieauth",
- "kittybox-templates",
  "kittybox-util",
  "lazy_static",
  "listenfd",
@@ -1265,34 +1286,36 @@ dependencies = [
 ]
 
 [[package]]
-name = "kittybox-indieauth"
+name = "kittybox-frontend-renderer"
 version = "0.1.0"
 dependencies = [
- "axum-core",
- "data-encoding",
+ "axum",
+ "chrono",
+ "ellipse",
+ "faker_rand",
  "http",
+ "include_dir",
+ "kittybox-indieauth",
+ "kittybox-util",
+ "markup",
+ "microformats",
  "rand 0.8.5",
- "serde",
  "serde_json",
- "serde_urlencoded",
- "sha2",
- "url",
 ]
 
 [[package]]
-name = "kittybox-templates"
+name = "kittybox-indieauth"
 version = "0.1.0"
 dependencies = [
- "chrono",
- "ellipse",
- "faker_rand",
+ "axum-core",
+ "data-encoding",
  "http",
- "kittybox-indieauth",
- "kittybox-util",
- "markup",
- "microformats",
  "rand 0.8.5",
+ "serde",
  "serde_json",
+ "serde_urlencoded",
+ "sha2",
+ "url",
 ]
 
 [[package]]
diff --git a/kittybox-rs/Cargo.toml b/kittybox-rs/Cargo.toml
index 6b0057f..cf16896 100644
--- a/kittybox-rs/Cargo.toml
+++ b/kittybox-rs/Cargo.toml
@@ -42,7 +42,7 @@ default-members = [".", "./util", "./templates", "./indieauth"]
 version = "0.1.0"
 path = "./util"
 features = ["fs"]
-[dependencies.kittybox-templates]
+[dependencies.kittybox-frontend-renderer]
 version = "0.1.0"
 path = "./templates"
 [dependencies.kittybox-indieauth]
diff --git a/kittybox-rs/build.rs b/kittybox-rs/build.rs
index 3d4c62b..c639cf8 100644
--- a/kittybox-rs/build.rs
+++ b/kittybox-rs/build.rs
@@ -1,20 +1,6 @@
 fn main() {
     use std::env;
     let out_dir = env::var("OUT_DIR").unwrap();
-    println!("cargo:rerun-if-changed=javascript/");
-
-    if let Ok(exit) = std::process::Command::new("tsc")
-        .arg("--outDir")
-        .arg(std::path::Path::new(&out_dir).join("kittybox_js"))
-        .current_dir("javascript")
-        .spawn()
-        .unwrap()
-        .wait()
-    {
-        if !exit.success() {
-            std::process::exit(exit.code().unwrap_or(1))
-        }
-    }
 
     println!("cargo:rerun-if-changed=companion-lite/");
     let companion_out = std::path::Path::new(&out_dir).join("companion");
diff --git a/kittybox-rs/src/frontend/login.rs b/kittybox-rs/src/frontend/login.rs
index 9665ce7..c693899 100644
--- a/kittybox-rs/src/frontend/login.rs
+++ b/kittybox-rs/src/frontend/login.rs
@@ -9,7 +9,7 @@ use std::str::FromStr;
 use crate::frontend::templates::Template;
 use crate::frontend::{FrontendError, IndiewebEndpoints};
 use crate::{database::Storage, ApplicationState};
-use kittybox_templates::LoginPage;
+use kittybox_frontend_renderer::LoginPage;
 
 pub async fn form<S: Storage>(req: Request<ApplicationState<S>>) -> Result {
     let owner = req.url().origin().ascii_serialization() + "/";
diff --git a/kittybox-rs/src/frontend/mod.rs b/kittybox-rs/src/frontend/mod.rs
index 58de39d..f0f4e5a 100644
--- a/kittybox-rs/src/frontend/mod.rs
+++ b/kittybox-rs/src/frontend/mod.rs
@@ -12,7 +12,12 @@ use tracing::{debug, error};
 //pub mod login;
 pub mod onboarding;
 
-use kittybox_templates::{Entry, ErrorPage, Feed, MainPage, Template, VCard, POSTS_PER_PAGE};
+use kittybox_frontend_renderer::{
+    Entry, Feed, VCard,
+    ErrorPage, Template, MainPage,
+    POSTS_PER_PAGE
+};
+pub use kittybox_frontend_renderer::assets::statics;
 
 #[derive(Debug, Deserialize)]
 pub struct QueryParams {
@@ -266,43 +271,3 @@ pub async fn catchall<D: Storage>(
         }
     }
 }
-
-const STYLE_CSS: &[u8] = include_bytes!("./style.css");
-// XXX const path handling is ugly, and concat!() doesn't take
-// constants, only literals... how annoying!
-//
-// This might break compiling on inferior operating systems that use
-// backslashes as their path separator
-const ONBOARDING_JS: &[u8] = include_bytes!(concat!(
-    env!("OUT_DIR"), "/", "kittybox_js", "/", "onboarding.js"
-));
-const ONBOARDING_CSS: &[u8] = include_bytes!("./onboarding.css");
-const INDIEAUTH_JS: &[u8] = include_bytes!(concat!(
-    env!("OUT_DIR"), "/", "kittybox_js", "/", "indieauth.js"
-));
-const LIB_JS: &[u8] = include_bytes!(concat!(
-    env!("OUT_DIR"), "/", "kittybox_js", "/", "lib.js"
-));
-const JSLABELS_HTML: &[u8] = include_bytes!("../../javascript/jslicense.html");
-const MIME_JS: &str = "application/javascript";
-const MIME_CSS: &str = "text/css";
-const MIME_PLAIN: &str = "text/plain";
-const MIME_HTML: &str = "text/html; charset=utf-8";
-
-pub async fn statics(Path(name): Path<String>) -> impl IntoResponse {
-    use axum::http::header::CONTENT_TYPE;
-
-    match name.as_str() {
-        "style.css" => (StatusCode::OK, [(CONTENT_TYPE, MIME_CSS)], STYLE_CSS),
-        "onboarding.js" => (StatusCode::OK, [(CONTENT_TYPE, MIME_JS)], ONBOARDING_JS),
-        "onboarding.css" => (StatusCode::OK, [(CONTENT_TYPE, MIME_CSS)], ONBOARDING_CSS),
-        "indieauth.js" => (StatusCode::OK, [(CONTENT_TYPE, MIME_JS)], INDIEAUTH_JS),
-        "lib.js" => (StatusCode::OK, [(CONTENT_TYPE, MIME_JS)], LIB_JS),
-        "jslicense.html" => (StatusCode::OK, [(CONTENT_TYPE, MIME_HTML)], JSLABELS_HTML),
-        _ => (
-            StatusCode::NOT_FOUND,
-            [(CONTENT_TYPE, MIME_PLAIN)],
-            "not found".as_bytes(),
-        ),
-    }
-}
diff --git a/kittybox-rs/src/frontend/onboarding.rs b/kittybox-rs/src/frontend/onboarding.rs
index b460e6a..b4bae8e 100644
--- a/kittybox-rs/src/frontend/onboarding.rs
+++ b/kittybox-rs/src/frontend/onboarding.rs
@@ -5,7 +5,7 @@ use axum::{
     response::{Html, IntoResponse},
     Json,
 };
-use kittybox_templates::{ErrorPage, OnboardingPage, Template};
+use kittybox_frontend_renderer::{ErrorPage, OnboardingPage, Template};
 use serde::Deserialize;
 use tracing::{debug, error};
 
diff --git a/kittybox-rs/src/indieauth/mod.rs b/kittybox-rs/src/indieauth/mod.rs
index 6dc9ec6..aaa3301 100644
--- a/kittybox-rs/src/indieauth/mod.rs
+++ b/kittybox-rs/src/indieauth/mod.rs
@@ -150,12 +150,12 @@ async fn authorization_endpoint_get<A: AuthBackend, D: Storage + 'static>(
     let me = format!("https://{}/", host).parse().unwrap();
     // TODO fetch h-app from client_id
     // TODO verify redirect_uri registration
-    Html(kittybox_templates::Template {
+    Html(kittybox_frontend_renderer::Template {
         title: "Confirm sign-in via IndieAuth",
         blog_name: "Kittybox",
         feeds: vec![],
         user: None,
-        content: kittybox_templates::AuthorizationRequestPage {
+        content: kittybox_frontend_renderer::AuthorizationRequestPage {
             request,
             credentials: auth.list_user_credential_types(&me).await.unwrap(),
             user: db.get_post(me.as_str()).await.unwrap().unwrap(),
diff --git a/kittybox-rs/templates/Cargo.toml b/kittybox-rs/templates/Cargo.toml
index a32a3a2..ffdfc25 100644
--- a/kittybox-rs/templates/Cargo.toml
+++ b/kittybox-rs/templates/Cargo.toml
@@ -1,5 +1,5 @@
 [package]
-name = "kittybox-templates"
+name = "kittybox-frontend-renderer"
 version = "0.1.0"
 edition = "2021"
 
@@ -12,10 +12,12 @@ rand = "^0.8.5"
 version="^0.2.0"
 
 [dependencies]
-ellipse = "^0.2.0"           # Truncate and ellipsize strings in a human-friendly way
-http = "^0.2.7"              # Hyper's strong HTTP types
-markup = "^0.12.0"           # HTML templating engine
-serde_json = "^1.0.64"       # A JSON serialization file format
+ellipse = "^0.2.0"
+http = "^0.2.7"
+markup = "^0.12.0"
+serde_json = "^1.0.64"
+include_dir = "^0.7.2"
+axum = "^0.5.16"
 [dependencies.chrono]
 version = "^0.4.19"
 features = ["serde"]
diff --git a/kittybox-rs/javascript/jslicense.html b/kittybox-rs/templates/assets/jslicense.html
index 90c681c..90c681c 100644
--- a/kittybox-rs/javascript/jslicense.html
+++ b/kittybox-rs/templates/assets/jslicense.html
diff --git a/kittybox-rs/src/frontend/onboarding.css b/kittybox-rs/templates/assets/onboarding.css
index 6f191b9..6f191b9 100644
--- a/kittybox-rs/src/frontend/onboarding.css
+++ b/kittybox-rs/templates/assets/onboarding.css
diff --git a/kittybox-rs/src/frontend/style.css b/kittybox-rs/templates/assets/style.css
index a8ef6e4..a8ef6e4 100644
--- a/kittybox-rs/src/frontend/style.css
+++ b/kittybox-rs/templates/assets/style.css
diff --git a/kittybox-rs/templates/build.rs b/kittybox-rs/templates/build.rs
new file mode 100644
index 0000000..1140060
--- /dev/null
+++ b/kittybox-rs/templates/build.rs
@@ -0,0 +1,26 @@
+fn main() {
+    use std::env;
+    let out_dir = std::path::PathBuf::from(env::var("OUT_DIR").unwrap());
+    println!("cargo:rerun-if-changed=assets/");
+    let assets = std::fs::read_dir("assets").unwrap();
+    for file in assets.map(|a| a.unwrap()) {
+        std::fs::copy(
+            file.path(),
+            out_dir.join(file.file_name())
+        )
+            .unwrap();
+    }
+    println!("cargo::rerun-if-changed=javascript/");
+    if let Ok(exit) = std::process::Command::new("tsc")
+        .arg("--outDir")
+        .arg(&out_dir)
+        .current_dir("javascript")
+        .spawn()
+        .unwrap()
+        .wait()
+    {
+        if !exit.success() {
+            std::process::exit(exit.code().unwrap_or(1))
+        }
+    }
+}
diff --git a/kittybox-rs/templates/javascript/dist/indieauth.js b/kittybox-rs/templates/javascript/dist/indieauth.js
new file mode 100644
index 0000000..297b4b5
--- /dev/null
+++ b/kittybox-rs/templates/javascript/dist/indieauth.js
@@ -0,0 +1,118 @@
+"use strict";
+const WEBAUTHN_TIMEOUT = 60 * 1000;
+async function webauthn_create_credential() {
+    const response = await fetch("/.kittybox/webauthn/pre_register");
+    const { challenge, rp, user } = await response.json();
+    return await navigator.credentials.create({
+        publicKey: {
+            challenge: Uint8Array.from(challenge, (c) => c.charCodeAt(0)),
+            rp: rp,
+            user: {
+                id: Uint8Array.from(user.cred_id, (c) => c.charCodeAt(0)),
+                name: user.name,
+                displayName: user.displayName
+            },
+            pubKeyCredParams: [{ alg: -7, type: "public-key" }],
+            authenticatorSelection: {},
+            timeout: WEBAUTHN_TIMEOUT,
+            attestation: "none"
+        }
+    });
+}
+async function webauthn_authenticate() {
+    const response = await fetch("/.kittybox/webauthn/pre_auth");
+    const { challenge, credentials } = await response.json();
+    try {
+        return await navigator.credentials.get({
+            publicKey: {
+                challenge: Uint8Array.from(challenge, (c) => c.charCodeAt(0)),
+                allowCredentials: credentials.map(cred => ({
+                    id: Uint8Array.from(cred.id, c => c.charCodeAt(0)),
+                    type: cred.type
+                })),
+                timeout: WEBAUTHN_TIMEOUT
+            }
+        });
+    }
+    catch (e) {
+        console.error("WebAuthn authentication failed:", e);
+        alert("Using your authenticator failed. (Check the DevTools for details)");
+        throw e;
+    }
+}
+async function submit_handler(e) {
+    e.preventDefault();
+    if (e.target != null && e.target instanceof HTMLFormElement) {
+        const form = e.target;
+        let scopes;
+        if (form.elements.namedItem("scope") === undefined) {
+            scopes = [];
+        }
+        else if (form.elements.namedItem("scope") instanceof Node) {
+            scopes = [form.elements.namedItem("scope")]
+                .filter((e) => e.checked)
+                .map((e) => e.value);
+        }
+        else {
+            scopes = Array.from(form.elements.namedItem("scope"))
+                .filter((e) => e.checked)
+                .map((e) => e.value);
+        }
+        const authorization_request = {
+            response_type: form.elements.namedItem("response_type").value,
+            client_id: form.elements.namedItem("client_id").value,
+            redirect_uri: form.elements.namedItem("redirect_uri").value,
+            state: form.elements.namedItem("state").value,
+            code_challenge: form.elements.namedItem("code_challenge").value,
+            code_challenge_method: form.elements.namedItem("code_challenge_method").value,
+            // I would love to leave that as a list, but such is the form of
+            // IndieAuth.  application/x-www-form-urlencoded doesn't have
+            // lists, so scopes are space-separated instead. It is annoying.
+            scope: scopes.length > 0 ? scopes.join(" ") : undefined,
+        };
+        let credential = null;
+        switch (form.elements.namedItem("auth_method").value) {
+            case "password":
+                credential = form.elements.namedItem("user_password").value;
+                if (credential.length == 0) {
+                    alert("Please enter a password.");
+                    return;
+                }
+                break;
+            case "webauthn":
+                // credential = await webauthn_authenticate();
+                alert("WebAuthn isn't implemented yet!");
+                return;
+                break;
+            default:
+                alert("Please choose an authentication method.");
+                return;
+        }
+        console.log("Authorization request:", authorization_request);
+        console.log("Authentication method:", credential);
+        const body = JSON.stringify({
+            request: authorization_request,
+            authorization_method: credential
+        });
+        console.log(body);
+        const response = await fetch(form.action, {
+            method: form.method,
+            body: body,
+            headers: {
+                "Content-Type": "application/json"
+            }
+        });
+        if (response.ok) {
+            let location = response.headers.get("Location");
+            if (location != null) {
+                window.location.href = location;
+            }
+            else {
+                throw "Error: didn't return a location";
+            }
+        }
+    }
+    else {
+        return;
+    }
+}
diff --git a/kittybox-rs/templates/javascript/dist/webauthn/register.js b/kittybox-rs/templates/javascript/dist/webauthn/register.js
new file mode 100644
index 0000000..3918c74
--- /dev/null
+++ b/kittybox-rs/templates/javascript/dist/webauthn/register.js
@@ -0,0 +1 @@
+"use strict";
diff --git a/kittybox-rs/javascript/src/indieauth.ts b/kittybox-rs/templates/javascript/src/indieauth.ts
index 01732b7..01732b7 100644
--- a/kittybox-rs/javascript/src/indieauth.ts
+++ b/kittybox-rs/templates/javascript/src/indieauth.ts
diff --git a/kittybox-rs/javascript/src/lib.ts b/kittybox-rs/templates/javascript/src/lib.ts
index 38ba65b..38ba65b 100644
--- a/kittybox-rs/javascript/src/lib.ts
+++ b/kittybox-rs/templates/javascript/src/lib.ts
diff --git a/kittybox-rs/javascript/src/onboarding.ts b/kittybox-rs/templates/javascript/src/onboarding.ts
index 0b455eb..0b455eb 100644
--- a/kittybox-rs/javascript/src/onboarding.ts
+++ b/kittybox-rs/templates/javascript/src/onboarding.ts
diff --git a/kittybox-rs/templates/javascript/src/webauthn/register.ts b/kittybox-rs/templates/javascript/src/webauthn/register.ts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/kittybox-rs/templates/javascript/src/webauthn/register.ts
diff --git a/kittybox-rs/javascript/tsconfig.json b/kittybox-rs/templates/javascript/tsconfig.json
index 18b94c7..18b94c7 100644
--- a/kittybox-rs/javascript/tsconfig.json
+++ b/kittybox-rs/templates/javascript/tsconfig.json
diff --git a/kittybox-rs/templates/src/lib.rs b/kittybox-rs/templates/src/lib.rs
index d58e831..5b3a8df 100644
--- a/kittybox-rs/templates/src/lib.rs
+++ b/kittybox-rs/templates/src/lib.rs
@@ -9,6 +9,37 @@ pub use login::LoginPage;
 mod mf2;
 pub use mf2::{Entry, VCard, Feed, Food, POSTS_PER_PAGE};
 
+pub mod assets {
+    use axum::response::{IntoResponse, Response};
+    use axum::extract::Path;
+    use axum::http::StatusCode;
+    use axum::http::header::{CONTENT_TYPE, CACHE_CONTROL};
+
+    const ASSETS: include_dir::Dir<'static> = include_dir::include_dir!("$OUT_DIR");
+    const CACHE_FOR_A_DAY: &str = "max-age=86400";
+
+    pub async fn statics(Path(path): Path<String>) -> Response {
+
+        let content_type: &'static str = if path.ends_with(".js") {
+            "application/javascript"
+        } else if path.ends_with(".css") {
+            "text/css"
+        } else if path.ends_with(".html") {
+            "text/html; charset=\"utf-8\""
+        } else {
+            "application/octet-stream"
+        };
+
+        match ASSETS.get_file(path) {
+            Some(file) => (StatusCode::OK,
+                           [(CONTENT_TYPE, content_type),
+                            (CACHE_CONTROL, CACHE_FOR_A_DAY)],
+                           file.contents()).into_response(),
+            None => StatusCode::NOT_FOUND.into_response()
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use faker_rand::en_us::internet::Domain;