1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
use std::net::SocketAddr;
use url::Url;
use axum::routing::{get, post};
use axum::Router;
use sqlx::postgres::PgPoolOptions;
use sqlx::{Pool, Postgres};
use sqids::Sqids;
use serde::Deserialize;
use info_utils::prelude::*;
pub mod get;
pub mod post;
#[derive(Clone)]
pub struct ServerState {
pub db_pool: Pool<Postgres>,
pub host: String,
pub sqids: Sqids,
pub main_page_redirect: Option<Url>,
pub behind_proxy: bool,
}
#[derive(Debug, Clone, sqlx::FromRow, PartialEq, Eq)]
pub struct UrlRow {
pub index: i64,
pub id: String,
pub url: String,
}
#[derive(Deserialize, Debug, Clone)]
pub struct CreateForm {
pub id: String,
pub url: url::Url,
}
#[tokio::main]
async fn main() -> eyre::Result<()> {
color_eyre::install()?;
let db_pool = init_db().await?;
let host = std::env::var("CHELA_HOST").unwrap_or("localhost".to_string());
let sqids = Sqids::builder()
.alphabet(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
.chars()
.collect(),
)
.blocklist(["create".to_string()].into())
.build()?;
let main_page_redirect = std::env::var("CHELA_MAIN_PAGE_REDIRECT").unwrap_or_default();
let behind_proxy = std::env::var("CHELA_BEHIND_PROXY").is_ok();
let server_state = ServerState {
db_pool,
host,
sqids,
main_page_redirect: Url::parse(&main_page_redirect).ok(),
behind_proxy,
};
let address = std::env::var("CHELA_LISTEN_ADDRESS").unwrap_or("0.0.0.0".to_string());
let port = 3000;
let router = init_routes(server_state);
let listener = tokio::net::TcpListener::bind(format!("{address}:{port}")).await?;
log!("Listening at {}:{}", address, port);
axum::serve(
listener,
router.into_make_service_with_connect_info::<SocketAddr>(),
)
.await?;
Ok(())
}
async fn init_db() -> eyre::Result<Pool<Postgres>> {
let db_pool = PgPoolOptions::new()
.max_connections(15)
.connect(
std::env::var("DATABASE_URL")
.expect("DATABASE_URL must be set")
.as_str(),
)
.await?;
log!("Successfully connected to database");
sqlx::query("CREATE SCHEMA IF NOT EXISTS chela")
.execute(&db_pool)
.await?;
log!("Created schema chela");
sqlx::query(
"
CREATE TABLE IF NOT EXISTS chela.urls (
index BIGSERIAL PRIMARY KEY,
id TEXT NOT NULL UNIQUE,
url TEXT NOT NULL,
custom_id BOOLEAN NOT NULL
)
",
)
.execute(&db_pool)
.await?;
log!("Created table chela.urls");
sqlx::query(
"
CREATE TABLE IF NOT EXISTS chela.tracking (
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
id TEXT NOT NULL,
ip TEXT,
referrer TEXT,
user_agent TEXT
)
",
)
.execute(&db_pool)
.await?;
log!("Created table chela.tracking");
Ok(db_pool)
}
fn init_routes(state: ServerState) -> Router {
Router::new()
.route("/", get(get::index))
.route("/:id", get(get::id))
.route("/create", get(get::create_id))
.route("/", post(post::create_link))
.layer(axum::Extension(state))
}
|