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.
-
+
## 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`
+[2m2025-06-17T21:30:28.116518Z[0m [32m INFO[0m [2mshortcut[0m[2m:[0m 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 @@