mirror of
https://github.com/SinTan1729/chhoto-url
synced 2025-04-30 12:56:52 -05:00
Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
bbf5811e13 | |||
efc2b03415 | |||
3aa8884676 | |||
084334cd11 | |||
4eb88f3beb | |||
c23fcdc9bd | |||
21f76f2962 |
6 changed files with 44 additions and 40 deletions
19
README.md
19
README.md
|
@ -38,9 +38,9 @@ for small. URL means, well... URL. So the name simply means Small URL.
|
||||||
# Features
|
# Features
|
||||||
- Shortens URLs of any length to a randomly generated link.
|
- Shortens URLs of any length to a randomly generated link.
|
||||||
- (Optional) Allows you to specify the shortened URL instead of the generated
|
- (Optional) Allows you to specify the shortened URL instead of the generated
|
||||||
one. (It's surprisingly missing in a surprising number of alternatives.)
|
one. (It's missing in a surprising number of alternatives.)
|
||||||
- Opening the shortened URL in your browser will instantly redirect you
|
- Opening the shortened URL in your browser will instantly redirect you
|
||||||
to the correct long URL. (So no stupid redirecting pages.)
|
to the correct long URL. (So no stupid redirection pages.)
|
||||||
- Super lightweight and snappy. (The docker image is only ~6MB and RAM uasge
|
- Super lightweight and snappy. (The docker image is only ~6MB and RAM uasge
|
||||||
stays under 5MB under normal use.)
|
stays under 5MB under normal use.)
|
||||||
- Counts number of hits for each short link in a privacy respecting way
|
- Counts number of hits for each short link in a privacy respecting way
|
||||||
|
@ -52,8 +52,8 @@ for small. URL means, well... URL. So the name simply means Small URL.
|
||||||
- Allows setting the URL of your website, in case you want to conveniently
|
- Allows setting the URL of your website, in case you want to conveniently
|
||||||
generate short links locally.
|
generate short links locally.
|
||||||
- Links are stored in an SQLite database.
|
- Links are stored in an SQLite database.
|
||||||
- Available as a Docker container.
|
- Available as a Docker container with a provided compose file.
|
||||||
- Backend written in Rust using [Actix](https://actix.rs/), frontend
|
- Backend written in Rust using [Actix](https://actix.rs/), and frontend
|
||||||
written in plain HTML and vanilla JS, using [Pure CSS](https://purecss.io/)
|
written in plain HTML and vanilla JS, using [Pure CSS](https://purecss.io/)
|
||||||
for styling.
|
for styling.
|
||||||
- Uses very basic authentication using a provided password. It's not encrypted in transport.
|
- Uses very basic authentication using a provided password. It's not encrypted in transport.
|
||||||
|
@ -61,17 +61,16 @@ for small. URL means, well... URL. So the name simply means Small URL.
|
||||||
encrypt the connection by SSL.
|
encrypt the connection by SSL.
|
||||||
|
|
||||||
# Bloat that will not be implemented
|
# Bloat that will not be implemented
|
||||||
- Tracking or spying of any kind. The only logs that still exist are
|
- **Tracking or spying of any kind.** The only logs that still exist are
|
||||||
errors printed to stderr and the basic logging (only warnings) provided by the
|
errors printed to stderr and the basic logging (only warnings) provided by the
|
||||||
[`env_logger`](https://crates.io/crates/env_logger) crate.
|
[`env_logger`](https://crates.io/crates/env_logger) crate.
|
||||||
- User management. If you need a shortener for your whole organization, either
|
- **User management.** If you need a shortener for your whole organization, either
|
||||||
run separate containers for everyone or use something else.
|
run separate containers for everyone or use something else.
|
||||||
- Cookies, newsletters, "we value your privacy" popups or any of the multiple
|
- **Cookies, newsletters**, "we value your privacy" popups or any of the multiple
|
||||||
other ways modern web shows how anti-user it is. We all hate those, and they're
|
other ways modern web shows how anti-user it is. We all hate those, and they're
|
||||||
not needed here.
|
not needed here.
|
||||||
- Paywalls or messages begging for donations. If you want to support me (for
|
- **Paywalls** or messages begging for donations. If you want to buy me a coffee,
|
||||||
whatever reason), you can message me through GitHub issues.
|
you can message me through GitHub discussions or mail me.
|
||||||
|
|
||||||
# Screenshots
|
# Screenshots
|
||||||
<p align="middle">
|
<p align="middle">
|
||||||
<img src="screenshot-desktop.webp" height="250" alt="desktop screenshot" />
|
<img src="screenshot-desktop.webp" height="250" alt="desktop screenshot" />
|
||||||
|
|
26
actix/Cargo.lock
generated
26
actix/Cargo.lock
generated
|
@ -31,7 +31,7 @@ dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"bytes",
|
"bytes",
|
||||||
"derive_more 0.99.19",
|
"derive_more 0.99.20",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"http-range",
|
"http-range",
|
||||||
"log",
|
"log",
|
||||||
|
@ -458,7 +458,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chhoto-url"
|
name = "chhoto-url"
|
||||||
version = "5.7.0"
|
version = "5.7.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-session",
|
"actix-session",
|
||||||
|
@ -562,9 +562,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "0.99.19"
|
version = "0.99.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f"
|
checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case",
|
"convert_case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
@ -760,9 +760,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.15"
|
version = "0.2.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -1059,9 +1059,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jiff"
|
name = "jiff"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "59ec30f7142be6fe14e1b021f50b85db8df2d4324ea6e91ec3e5dcde092021d0"
|
checksum = "5a064218214dc6a10fbae5ec5fa888d80c45d611aba169222fc272072bf7aef6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jiff-static",
|
"jiff-static",
|
||||||
"log",
|
"log",
|
||||||
|
@ -1072,9 +1072,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jiff-static"
|
name = "jiff-static"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "526b834d727fd59d37b076b0c3236d9adde1b1729a4361e20b2026f738cc1dbe"
|
checksum = "199b7932d97e325aff3a7030e141eafe7f2c6268e1d1b24859b753a627f45254"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1407,7 +1407,7 @@ version = "0.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.2.15",
|
"getrandom 0.2.16",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1741,9 +1741,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.14"
|
version = "0.7.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034"
|
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "chhoto-url"
|
name = "chhoto-url"
|
||||||
version = "5.7.0"
|
version = "5.7.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Sayantan Santra <sayantan[dot]santra689[at]gmail[dot]com"]
|
authors = ["Sayantan Santra <sayantan[dot]santra689[at]gmail[dot]com"]
|
||||||
license = "mit"
|
license = "mit"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra689@gmail.com>
|
// SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra689@gmail.com>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
use rusqlite::Connection;
|
use rusqlite::{Connection, Error};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
// Struct for encoding a DB row
|
// Struct for encoding a DB row
|
||||||
|
@ -67,12 +67,11 @@ pub fn add_hit(shortlink: &str, db: &Connection) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert a new link
|
// Insert a new link
|
||||||
pub fn add_link(shortlink: String, longlink: String, db: &Connection) -> bool {
|
pub fn add_link(shortlink: String, longlink: String, db: &Connection) -> Result<usize, Error> {
|
||||||
db.execute(
|
db.execute(
|
||||||
"INSERT INTO urls (long_url, short_url, hits) VALUES (?1, ?2, ?3)",
|
"INSERT INTO urls (long_url, short_url, hits) VALUES (?1, ?2, ?3)",
|
||||||
(longlink, shortlink, 0),
|
(longlink, shortlink, 0),
|
||||||
)
|
)
|
||||||
.is_ok()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete and existing link
|
// Delete and existing link
|
||||||
|
|
|
@ -63,15 +63,9 @@ async fn main() -> Result<()> {
|
||||||
// Set the site_url without the quotes
|
// Set the site_url without the quotes
|
||||||
env::set_var("site_url", url);
|
env::set_var("site_url", url);
|
||||||
eprintln!("WARN: The site_url environment variable is encapsulated by quotes. Automatically adjusting to {}", url);
|
eprintln!("WARN: The site_url environment variable is encapsulated by quotes. Automatically adjusting to {}", url);
|
||||||
|
|
||||||
// Tell the user what URI the server will respond with
|
|
||||||
eprintln!("INFO: Public URI is: {url}:{port}.")
|
|
||||||
} else {
|
} else {
|
||||||
// No issues
|
// No issues
|
||||||
eprintln!("INFO: Configured Site URL is: {site_url}.");
|
eprintln!("INFO: Configured Site URL is: {site_url}.");
|
||||||
|
|
||||||
// Tell the user what URI the server will respond with
|
|
||||||
eprintln!("INFO: Public URI is: {site_url}:{port}.")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Site URL is not configured
|
// Site URL is not configured
|
||||||
|
|
|
@ -6,7 +6,7 @@ use actix_web::HttpRequest;
|
||||||
use nanoid::nanoid;
|
use nanoid::nanoid;
|
||||||
use rand::seq::IndexedRandom;
|
use rand::seq::IndexedRandom;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rusqlite::Connection;
|
use rusqlite::{ffi::SQLITE_CONSTRAINT_UNIQUE, Connection};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
|
@ -123,15 +123,27 @@ pub fn add_link(req: String, db: &Connection) -> (bool, String) {
|
||||||
len = 4;
|
len = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
if chunks.shortlink.is_empty() {
|
let shortlink_provided = if chunks.shortlink.is_empty() {
|
||||||
chunks.shortlink = gen_link(style, len);
|
chunks.shortlink = gen_link(style, len);
|
||||||
}
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
if validate_link(chunks.shortlink.as_str()) {
|
if validate_link(chunks.shortlink.as_str()) {
|
||||||
if database::add_link(chunks.shortlink.clone(), chunks.longlink, db) {
|
match database::add_link(chunks.shortlink.clone(), chunks.longlink, db) {
|
||||||
(true, chunks.shortlink)
|
Ok(_) => (true, chunks.shortlink),
|
||||||
} else {
|
Err(error) => {
|
||||||
|
if error.sqlite_error().map(|err| err.extended_code)
|
||||||
|
== Some(SQLITE_CONSTRAINT_UNIQUE)
|
||||||
|
&& shortlink_provided
|
||||||
|
{
|
||||||
(false, String::from("Short URL is already in use!"))
|
(false, String::from("Short URL is already in use!"))
|
||||||
|
} else {
|
||||||
|
// This should be super rare
|
||||||
|
(false, String::from("Something went wrong!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(false, String::from("Short URL is not valid!"))
|
(false, String::from("Short URL is not valid!"))
|
||||||
|
|
Loading…
Reference in a new issue