diff --git a/.env b/.env index 0ba965c..3cc194b 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ -DATABASE_FILENAME=shortcuts.db +DATABASE_FILENAME=shortcut.db DATABASE_URL=sqlite://${DATABASE_FILENAME}?mode=rwc -PORT=3000 +PORT=8035 UI_URL=http://localhost:${PORT} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31cc8d9..42b6afc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,7 +85,7 @@ jobs: - name: Set-up Database env: - DATABASE_URL: "sqlite://shortcuts.db?mode=rwc" + DATABASE_URL: "sqlite://shortcut.db?mode=rwc" run: | sqlx db create sqlx migrate run --source ./migrations @@ -105,5 +105,5 @@ jobs: nohup cargo run > server.log 2>&1 & echo "Waiting for server to start..." # Wait up to 30 seconds for healthcheck - timeout 30 bash -c 'until curl -fs http://localhost:3000/healthcheck; do echo "Waiting for server..."; sleep 2; done' + timeout 30 bash -c 'until curl -fs http://localhost:8035/healthcheck; do echo "Waiting for server..."; sleep 2; done' echo "Server is up and running." diff --git a/.gitignore b/.gitignore index 2e011e3..111abe9 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,5 @@ SQLite-trunk* *.db *.db-shm *.db-wal -ui/assets/main.css \ No newline at end of file +ui/assets/main.css +build/*_installer.iss \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 045346f..cd27a70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,20 @@ [package] -name = "shortcuts" +name = "shortcut" version = "1.0.3" edition = "2021" +include = ["ui/", "migrations/"] +build = "build/build.rs" [dependencies] -axum = { version = "0.8.1", features = ["macros"] } +axum = { version = "0.8.4", features = ["macros"] } dotenv = "0.15" serde = { version = "1.0.219", features = ["derive"] } sqlx = { version= "0.8.5", features=["sqlite"] } state = "0.6.0" tera = { version = "1.20", default-features = false} tera-hot-reload = { version = "0.2.4" } -tokio = { version = "1.39.2", features = ["full"] } -tower-http = { version = "0.6.1", features = ["full"] } +tokio = { version = "1.45.0", features = ["full"] } +tower-http = { version = "0.6.4", features = ["full"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing = "0.1.40" thiserror = "2.0.12" @@ -21,3 +23,4 @@ sqlx-cli = "0.8.5" rust-fuzzy-search = "0.1.1" lazy_static = "1.5.0" mockall = "0.13.1" +auto-launch = "0.5.0" diff --git a/Makefile b/Makefile index 4035e6b..8458e1f 100644 --- a/Makefile +++ b/Makefile @@ -14,3 +14,26 @@ tailwind-watch: server-watch: RUST_LOG=info cargo watch -x run + +build-windows: + echo "🦀 Building Rust project..." + rustup target add x86_64-pc-windows-gnu + cargo build --release --target x86_64-pc-windows-gnu + + # Linux commands (without inno): + VERSION=$(grep '^version' Cargo.toml | head -n1 | cut -d '"' -f2) + sed "s/{#AppVersion}/$VERSION/" build/windows_template.iss > build/windows_installer.iss + + # Window commands: + # $versionLine = Get-Content Cargo.toml | Where-Object { $_ -match '^version\s*=' } | Select-Object -First 1 + # $version = $versionLine -replace '^version\s*=\s*"(.*)"', '$1' + # (Get-Content build\windows_template.iss) -replace '\{#AppVersion\}', $version | Set-Content build\windows_installer.iss + # & "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" ".\build\windows_installer.iss" + + echo "✅ Done!" + +build-mac: + sudo apt install -y libzstd-dev pkg-config build-essential + + cargo build --release --target x86_64-apple-darwin + # cargo build --release --target x86_64-unknown-linux-gnu diff --git a/README.md b/README.md index 8b308dd..205e4cb 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,16 @@ [![CI][s0]][l0] [![UNSAFE][s3]][l3] [![dep_status][s7]][l7] -[s0]: https://github.com/AlexTrebs/Shortcuts/workflows/CI/badge.svg -[l0]: https://github.com/AlexTrebs/Shortcuts/actions +[s0]: https://github.com/AlexTrebs/Shortcut/workflows/CI/badge.svg +[l0]: https://github.com/AlexTrebs/Shortcut/actions [s3]: https://img.shields.io/badge/unsafe-forbidden-success.svg [l3]: https://github.com/rust-secure-code/safety-dance/ -[s7]: https://deps.rs/repo/github/AlexTrebs/Shortcuts/status.svg -[l7]: https://deps.rs/repo/github/AlexTrebs/Shortcuts +[s7]: https://deps.rs/repo/github/AlexTrebs/Shortcut/status.svg +[l7]: https://deps.rs/repo/github/AlexTrebs/Shortcut
A fast, simple, browser shortcut tool.
-![](docs/images/shortcuts_demo.gif) +![](docs/images/shortcut_demo.gif) ## Table of Contents @@ -104,34 +104,34 @@ Following this you will need to set up a shortcut for your search bar. Given you 1. Go to chrome://settings/search. 2. Click "Manage search engines and site search". 3. Click "Add" next to "Site search". -4. Fill the url to be `http://localhost:3000/api/get?keyword=%s` +4. Fill the url to be `http://localhost:8035/api/get?keyword=%s` 5. Fill out the rest of the details and save. #### Mozilla Firefox: 1. Go to about:preferences#search. -2. Click "Search Shortcuts". +2. Click "Search Shortcut". 3. Click "Add". -4. Fill the url to be `http://localhost:3000/api/get?keyword=%s` +4. Fill the url to be `http://localhost:8035/api/get?keyword=%s` 5. Fill out the rest of the details and save. #### Microsoft Edge: 1. Go to edge://settings/searchEngines?search=address. 2. Click "Add" next to "Site search". -3. Fill the url to be `http://localhost:3000/api/get?keyword=%s` +3. Fill the url to be `http://localhost:8035/api/get?keyword=%s` 4. Fill out the rest of the details and save. #### Brave: 1. Go to brave://settings/search. 2. Click "Manage search engines and site search". 3. Click "Add" next to "Site search". -4. Fill the url to be `http://localhost:3000/api/get?keyword=%s` +4. Fill the url to be `http://localhost:8035/api/get?keyword=%s` 5. Fill out the rest of the details and save. #### Vivaldi: 1. Go to vivaldi://settings/search. 2. Click "Search Engines". 3. Click "Add" next to "Site search". -4. Fill the url to be `http://localhost:3000/api/get?keyword=%s` +4. Fill the url to be `http://localhost:8035/api/get?keyword=%s` 5. Fill out the rest of the details and save. ## 5. Architecture [Top â–²](#table-of-contents) @@ -201,7 +201,7 @@ All the config is kept within the `.env` file. The default is: ```sh -DATABASE_FILENAME=shortcuts.db +DATABASE_FILENAME=shortcut.db DATABASE_URL=sqlite://${DATABASE_FILENAME}?mode=rwc PORT=3000 UI_URL=http://localhost:${PORT} diff --git a/app_icon.rc b/app_icon.rc new file mode 100644 index 0000000..600dde5 --- /dev/null +++ b/app_icon.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON "docs/images/icon.ico" \ No newline at end of file diff --git a/app_icon.res b/app_icon.res new file mode 100644 index 0000000..dbb5810 Binary files /dev/null and b/app_icon.res differ diff --git a/build/ShortcutInstaller.exe b/build/ShortcutInstaller.exe new file mode 100644 index 0000000..8d98742 Binary files /dev/null and b/build/ShortcutInstaller.exe differ diff --git a/build/build.rs b/build/build.rs new file mode 100644 index 0000000..99eac01 --- /dev/null +++ b/build/build.rs @@ -0,0 +1,80 @@ +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; + +fn main() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let profile = env::var("PROFILE").unwrap(); + let target = env::var("TARGET").unwrap(); + + let ui_dest = PathBuf::from(manifest_dir) + .join("target") + .join(target) + .join(profile) + .join("ui"); + let ui_src = Path::new("ui"); + println!("us_dest: {:?}", ui_dest.to_str()); + + println!("cargo:info=Copying UI folder to build output..."); + + if !ui_src.exists() { + panic!("cargo:error=UI directory does not exist! Make sure it is in the project root."); + } + + fs::create_dir_all(&ui_dest).expect("cargo:error=Failed to create UI directory."); + + copy_dir(ui_src, &ui_dest).expect("cargo:error=Failed to copy UI files."); + + println!("cargo:info=UI folder copied successfully!"); + + // Apply OS-specific setup after copying files + apply_os_specific_setup(&ui_dest); +} + +/// Recursively copies all files and subdirectories +fn copy_dir(src: &Path, dest: &Path) -> std::io::Result<()> { + for entry in fs::read_dir(src)? { + let entry = entry?; + let path = entry.path(); + let dest_path = dest.join(entry.file_name()); + + if path.is_dir() { + fs::create_dir_all(&dest_path)?; + copy_dir(&path, &dest_path)?; + } else { + fs::copy(&path, &dest_path)?; + } + } + Ok(()) +} + +/// OS-specific setup function +fn apply_os_specific_setup(ui_dest: &Path) { + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + + match target_os.as_str() { + "windows" => { + println!("cargo:rerun-if-changed=app_icon.rc"); + println!("cargo:rustc-link-arg=app_icon.res"); + println!("cargo:info=Applying Windows-specific setup..."); + let _ = std::process::Command::new("attrib") + .args(["+R", ui_dest.to_str().unwrap()]) + .status(); + } + "linux" => { + println!("cargo:info=Applying Linux-specific setup..."); + let _ = std::process::Command::new("chmod") + .args(["-R", "755", ui_dest.to_str().unwrap()]) + .status(); + } + "macos" => { + println!("cargo:info=Applying macOS-specific setup..."); + let _ = std::process::Command::new("chmod") + .args(["-R", "755", ui_dest.to_str().unwrap()]) + .status(); + } + _ => { + println!("cargo:error=Unknown target OS: {}", target_os); + } + } +} diff --git a/build/windows_template.iss b/build/windows_template.iss new file mode 100644 index 0000000..565dd3f --- /dev/null +++ b/build/windows_template.iss @@ -0,0 +1,17 @@ +[Setup] +AppName=Shortcut +AppVersion={#AppVersion} +DefaultDirName={pf}\Shortcut +OutputDir=. +OutputBaseFilename=ShortcutInstaller +Compression=lzma +SolidCompression=yes +SetupIconFile=docs\icon.ico + +[Files] +Source: "target/x86_64-pc-windows-gnu/release/shortcut.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "../target/x86_64-pc-windows-gnu/release/ui/*"; DestDir: "{app}/ui/"; Flags: ignoreversion recursesubdirs +Source: "../.env"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs + +[Icons] +Name: "{group}\Shortcut"; Filename: "{app}\shortcut.exe" \ No newline at end of file diff --git a/docs/images/icon.ico b/docs/images/icon.ico new file mode 100644 index 0000000..d96bfd1 Binary files /dev/null and b/docs/images/icon.ico differ diff --git a/docs/images/small_icon.png b/docs/images/small_icon.png new file mode 100644 index 0000000..376d578 Binary files /dev/null and b/docs/images/small_icon.png differ diff --git a/migrations/001_create_shortcuts_table_up.sql b/migrations/001_create_shortcut_table_up.sql similarity index 89% rename from migrations/001_create_shortcuts_table_up.sql rename to migrations/001_create_shortcut_table_up.sql index d48b71d..71e85d2 100644 --- a/migrations/001_create_shortcuts_table_up.sql +++ b/migrations/001_create_shortcut_table_up.sql @@ -4,7 +4,7 @@ -- Load the spellfix1 extension -- SELECT load_extension('extensions/spellfix1'); -CREATE TABLE IF NOT EXISTS shortcuts ( +CREATE TABLE IF NOT EXISTS shortcut ( id INTEGER UNIQUE, created INTEGER NOT NULL, updated INTEGER NOT NULL, diff --git a/package.json b/package.json index 9887a0b..f767c4b 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "shortcuts", + "name": "shortcut", "version": "1.0.3", "description": "", "main": "index.js", diff --git a/server.log b/server.log new file mode 100644 index 0000000..d742c88 --- /dev/null +++ b/server.log @@ -0,0 +1,9 @@ +nohup: ignoring input + Compiling shortcut v1.0.3 (/root/workplace/shortcut) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 5.93s + Running `target/debug/shortcut` +2025-06-17T21:30:28.116518Z  INFO shortcut: listening on 0.0.0.0:8035 + +thread 'main' panicked at /root/workplace/shortcut/src/lib.rs:96:50: +called `Result::unwrap()` on an `Err` value: Os { code: 98, kind: AddrInUse, message: "Address already in use" } +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/error.rs b/src/error.rs index d64877c..690234b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,7 +4,7 @@ use thiserror::Error; pub enum ShortcutError { #[error("shortcut not found.")] NotFound, - #[error("Failed to get shortcuts.")] + #[error("Failed to get shortcut.")] FailedToGet, #[error("Failed to create shortcut. Please try again.")] FailedToCreate, @@ -14,8 +14,8 @@ pub enum ShortcutError { FailedToUpdate, #[error("Failed to find match for keyword, would you like to create one?.")] NoMatches, - #[error("Failed to search shortcuts.")] + #[error("Failed to search shortcut.")] FailedToSearch, - #[error("Failed to delete shortcuts.")] + #[error("Failed to delete shortcut.")] FailedToDelete } \ No newline at end of file diff --git a/src/macros/renderable.rs b/src/macros/renderable.rs index d6bb787..53b4da0 100644 --- a/src/macros/renderable.rs +++ b/src/macros/renderable.rs @@ -12,7 +12,7 @@ macro_rules! impl_renderable { ($($t:ty),*) => { $(impl Renderable for $t { fn get_html(&self, tera: Tera) -> Html { - Html(self.render(tera)) + Html(self.render(&tera)) } })* }; diff --git a/src/main.rs b/src/main.rs index 4d1c462..90c8942 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,35 @@ -use shortcuts::run; +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +use shortcut::run; +use auto_launch::AutoLaunch; +use std::env; #[tokio::main] async fn main() { + let binding = env::current_exe().unwrap(); + let app_path = binding.to_str().unwrap(); + let app_name = "shortcut"; + let args = &["--minimized"]; + + #[cfg(target_os = "windows")] { + let auto = AutoLaunch::new(app_name, app_path, args); + setup_auto_launch(auto); + } + + #[cfg(target_os = "macos")] { + let auto = AutoLaunch::new(app_name, app_path, false, args); + setup_auto_launch(auto); + } + + #[cfg(target_os = "linux")] { + let auto = AutoLaunch::new(app_name, app_path, args); + setup_auto_launch(auto); + } + run().await; +} + +fn setup_auto_launch(auto: AutoLaunch) { + // enable the auto launch + let _ =auto.enable().is_ok(); + auto.is_enabled().unwrap(); } \ No newline at end of file diff --git a/src/repository/shortcut.rs b/src/repository/shortcut.rs index a538dc5..be10039 100644 --- a/src/repository/shortcut.rs +++ b/src/repository/shortcut.rs @@ -97,7 +97,7 @@ impl ShortcutRepositoryTrait for ShortcutRepository { } async fn fuzzy_search(&self, search: &str) -> Result, ShortcutError> { - let result = sqlx::query_as!(Shortcut, r#"SELECT * FROM shortcuts;"#) + let result = sqlx::query_as!(Shortcut, r#"SELECT * FROM shortcut;"#) .fetch_all(&self.database) .await; @@ -133,7 +133,7 @@ impl ShortcutRepositoryTrait for ShortcutRepository { } async fn get(&self, keyword: &str) -> Result { - let result = sqlx::query_as!(Shortcut, r#"SELECT * FROM shortcuts WHERE keyword = ?1;"#, keyword) + let result = sqlx::query_as!(Shortcut, r#"SELECT * FROM shortcut WHERE keyword = ?1;"#, keyword) .fetch_one(&self.database).await; match result { @@ -148,7 +148,7 @@ impl ShortcutRepositoryTrait for ShortcutRepository { async fn create(&self, shortcut: &Shortcut) -> Result { let result = sqlx::query!( - r#"INSERT INTO shortcuts (created, updated, keyword, url) VALUES (?1, ?2, ?3, ?4)"#, + r#"INSERT INTO shortcut (created, updated, keyword, url) VALUES (?1, ?2, ?3, ?4)"#, shortcut.created, shortcut.updated, shortcut.keyword, @@ -169,7 +169,7 @@ impl ShortcutRepositoryTrait for ShortcutRepository { async fn update(&self, shortcut: &Shortcut) -> Result { let result = sqlx::query!( - r#"UPDATE shortcuts SET url = ?1, updated = ?2 WHERE keyword = ?3"#, + r#"UPDATE shortcut SET url = ?1, updated = ?2 WHERE keyword = ?3"#, shortcut.url, shortcut.updated, shortcut.keyword @@ -194,7 +194,7 @@ impl ShortcutRepositoryTrait for ShortcutRepository { } async fn delete(&self, keyword: &str) -> Result { - let result = sqlx::query!(r#"DELETE from shortcuts WHERE keyword = ?1"#, keyword) + let result = sqlx::query!(r#"DELETE from shortcut WHERE keyword = ?1"#, keyword) .execute(&self.database) .await; @@ -275,7 +275,7 @@ mod shortcut_repository_tests { async fn bulk_insert(pool: Pool) -> Pool { // Create temporary table within memory - pool.execute("CREATE TABLE IF NOT EXISTS shortcuts ( + pool.execute("CREATE TABLE IF NOT EXISTS shortcut ( id INTEGER UNIQUE, created INTEGER NOT NULL, updated INTEGER NOT NULL, @@ -286,7 +286,7 @@ mod shortcut_repository_tests { // Batch insert test data let query = " - INSERT INTO shortcuts (created, updated, keyword, url) VALUES + INSERT INTO shortcut (created, updated, keyword, url) VALUES (1, 2, 'google', 'https://google.co.uk'), (11, 22, 'g', 'https://google.com'), (111, 222, 'test', 'https://test.co.uk'), diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 2307312..2e33329 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -8,7 +8,7 @@ use self::{ middleware::get_htmx_header, shortcut::post_shortcut, shortcut::redirect_shortcut, - shortcut::search_shortcuts, + shortcut::search_shortcut, shortcut::update_shortcut, shortcut::delete_shortcut, }; @@ -18,7 +18,7 @@ pub mod shortcut; pub fn create_api_routes() -> Router { Router::new() - .route("/search", get(search_shortcuts)) + .route("/search", get(search_shortcut)) .route("/post", post(post_shortcut)) .route("/get", get(redirect_shortcut)) .route("/update", post(update_shortcut)) diff --git a/src/routes/shortcut.rs b/src/routes/shortcut.rs index ab69df1..fdbc2c0 100644 --- a/src/routes/shortcut.rs +++ b/src/routes/shortcut.rs @@ -26,7 +26,7 @@ pub async fn get_empty_html() -> Html { /// It will call the `Shortcut_Service` function `find_similar` function and return the result. /// /// If the keyword inputted is empty, it will instead return an empty `Html` `String`. -pub async fn search_shortcuts( +pub async fn search_shortcut( Extension(app): Extension, Query(params): Query, ) -> Result, ()> { diff --git a/src/schema.rs b/src/schema.rs index ebfa465..be3c82a 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -17,7 +17,7 @@ pub async fn connect_db() -> Pool { // let google: Shortcut = Shortcut::new("google".to_owned(), "https://google.co.uk".to_owned()); - // sqlx::query("INSERT OR IGNORE INTO shortcuts (created, updated, keyword, url) VALUES (?1, ?2, ?3, ?4)") + // sqlx::query("INSERT OR IGNORE INTO shortcut (created, updated, keyword, url) VALUES (?1, ?2, ?3, ?4)") // .bind(google.created) // .bind(google.updated) // .bind(google.keyword) diff --git a/ui/templates/components/common/header.html b/ui/templates/components/common/header.html index ef48706..a5c4628 100644 --- a/ui/templates/components/common/header.html +++ b/ui/templates/components/common/header.html @@ -1,7 +1,7 @@
-

Shortcuts

+

Shortcut