Skip to content

Commit 7dd9287

Browse files
committed
allow creation of user and links via args and env
1 parent 41d2ec5 commit 7dd9287

File tree

3 files changed

+111
-8
lines changed

3 files changed

+111
-8
lines changed

src/auth.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
use crate::{error::AppError, models::Claims};
12
use actix_web::{dev::Payload, FromRequest, HttpRequest};
23
use jsonwebtoken::{decode, DecodingKey, Validation};
34
use std::future::{ready, Ready};
4-
use crate::{error::AppError, models::Claims};
55

66
pub struct AuthenticatedUser {
77
pub user_id: i32,
@@ -12,19 +12,20 @@ impl FromRequest for AuthenticatedUser {
1212
type Future = Ready<Result<Self, Self::Error>>;
1313

1414
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
15-
let auth_header = req.headers()
15+
let auth_header = req
16+
.headers()
1617
.get("Authorization")
1718
.and_then(|h| h.to_str().ok());
1819

1920
if let Some(auth_header) = auth_header {
2021
if auth_header.starts_with("Bearer ") {
2122
let token = &auth_header[7..];
22-
let secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| "default_secret".to_string());
23-
23+
let secret =
24+
std::env::var("JWT_SECRET").unwrap_or_else(|_| "default_secret".to_string());
2425
match decode::<Claims>(
2526
token,
2627
&DecodingKey::from_secret(secret.as_bytes()),
27-
&Validation::default()
28+
&Validation::default(),
2829
) {
2930
Ok(token_data) => {
3031
return ready(Ok(AuthenticatedUser {
@@ -35,7 +36,7 @@ impl FromRequest for AuthenticatedUser {
3536
}
3637
}
3738
}
38-
3939
ready(Err(AppError::Unauthorized))
4040
}
41-
}
41+
}
42+

src/handlers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ fn validate_custom_code(code: &str) -> Result<(), AppError> {
131131
Ok(())
132132
}
133133

134-
fn validate_url(url: &String) -> Result<(), AppError> {
134+
fn validate_url(url: &str) -> Result<(), AppError> {
135135
if url.is_empty() {
136136
return Err(AppError::InvalidInput("URL cannot be empty".to_string()));
137137
}

src/main.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use actix_cors::Cors;
22
use actix_web::{web, App, HttpResponse, HttpServer};
33
use anyhow::Result;
4+
use clap::Parser;
45
use rust_embed::RustEmbed;
56
use simplelink::check_and_generate_admin_token;
7+
use simplelink::models::DatabasePool;
68
use simplelink::{create_db_pool, run_migrations};
79
use simplelink::{handlers, AppState};
810
use sqlx::{Postgres, Sqlite};
@@ -26,6 +28,106 @@ async fn serve_static_file(path: &str) -> HttpResponse {
2628
}
2729
}
2830

31+
async fn create_initial_links(pool: &DatabasePool) -> Result<()> {
32+
if let Ok(links) = std::env::var("INITIAL_LINKS") {
33+
for link_entry in links.split(';') {
34+
let parts: Vec<&str> = link_entry.split(',').collect();
35+
if parts.len() >= 2 {
36+
let url = parts[0];
37+
let code = parts[1];
38+
39+
match pool {
40+
DatabasePool::Postgres(pool) => {
41+
sqlx::query(
42+
"INSERT INTO links (original_url, short_code, user_id)
43+
VALUES ($1, $2, $3)
44+
ON CONFLICT (short_code)
45+
DO UPDATE SET short_code = EXCLUDED.short_code
46+
WHERE links.original_url = EXCLUDED.original_url",
47+
)
48+
.bind(url)
49+
.bind(code)
50+
.bind(1)
51+
.execute(pool)
52+
.await?;
53+
}
54+
DatabasePool::Sqlite(pool) => {
55+
// First check if the exact combination exists
56+
let exists = sqlx::query_scalar::<_, bool>(
57+
"SELECT EXISTS(
58+
SELECT 1 FROM links
59+
WHERE original_url = ?1
60+
AND short_code = ?2
61+
)",
62+
)
63+
.bind(url)
64+
.bind(code)
65+
.fetch_one(pool)
66+
.await?;
67+
68+
// Only insert if the exact combination doesn't exist
69+
if !exists {
70+
sqlx::query(
71+
"INSERT INTO links (original_url, short_code, user_id)
72+
VALUES (?1, ?2, ?3)",
73+
)
74+
.bind(url)
75+
.bind(code)
76+
.bind(1)
77+
.execute(pool)
78+
.await?;
79+
info!("Created initial link: {} -> {} for user_id: 1", code, url);
80+
} else {
81+
info!("Skipped existing link: {} -> {} for user_id: 1", code, url);
82+
}
83+
}
84+
}
85+
}
86+
}
87+
}
88+
Ok(())
89+
}
90+
91+
async fn create_admin_user(pool: &DatabasePool, email: &str, password: &str) -> Result<()> {
92+
use argon2::{
93+
password_hash::{rand_core::OsRng, SaltString},
94+
Argon2, PasswordHasher,
95+
};
96+
97+
let salt = SaltString::generate(&mut OsRng);
98+
let argon2 = Argon2::default();
99+
let password_hash = argon2
100+
.hash_password(password.as_bytes(), &salt)
101+
.map_err(|e| anyhow::anyhow!("Password hashing error: {}", e))?
102+
.to_string();
103+
104+
match pool {
105+
DatabasePool::Postgres(pool) => {
106+
sqlx::query(
107+
"INSERT INTO users (email, password_hash)
108+
VALUES ($1, $2)
109+
ON CONFLICT (email) DO NOTHING",
110+
)
111+
.bind(email)
112+
.bind(&password_hash)
113+
.execute(pool)
114+
.await?;
115+
}
116+
DatabasePool::Sqlite(pool) => {
117+
sqlx::query(
118+
"INSERT OR IGNORE INTO users (email, password_hash)
119+
VALUES (?1, ?2)",
120+
)
121+
.bind(email)
122+
.bind(&password_hash)
123+
.execute(pool)
124+
.await?;
125+
}
126+
}
127+
info!("Created admin user: {}", email);
128+
Ok(())
129+
}
130+
29131
#[actix_web::main]
30132
async fn main() -> Result<()> {
31133
// Load environment variables from .env file

0 commit comments

Comments
 (0)