+35
.sqlx/query-2adc9fa303079a3e9c28bbf0565c1ac60eea3a5c37e34fc6a0cb6e151c325382.json
+35
.sqlx/query-2adc9fa303079a3e9c28bbf0565c1ac60eea3a5c37e34fc6a0cb6e151c325382.json
···
1
+
{
2
+
"db_name": "PostgreSQL",
3
+
"query": "INSERT INTO users (email, password_hash) VALUES ($1, $2) RETURNING *",
4
+
"describe": {
5
+
"columns": [
6
+
{
7
+
"ordinal": 0,
8
+
"name": "id",
9
+
"type_info": "Int4"
10
+
},
11
+
{
12
+
"ordinal": 1,
13
+
"name": "email",
14
+
"type_info": "Varchar"
15
+
},
16
+
{
17
+
"ordinal": 2,
18
+
"name": "password_hash",
19
+
"type_info": "Text"
20
+
}
21
+
],
22
+
"parameters": {
23
+
"Left": [
24
+
"Varchar",
25
+
"Text"
26
+
]
27
+
},
28
+
"nullable": [
29
+
false,
30
+
false,
31
+
false
32
+
]
33
+
},
34
+
"hash": "2adc9fa303079a3e9c28bbf0565c1ac60eea3a5c37e34fc6a0cb6e151c325382"
35
+
}
+22
.sqlx/query-4560c237741ce9d4166aecd669770b3360a3ac71e649b293efb88d92c3254068.json
+22
.sqlx/query-4560c237741ce9d4166aecd669770b3360a3ac71e649b293efb88d92c3254068.json
···
1
+
{
2
+
"db_name": "PostgreSQL",
3
+
"query": "SELECT id FROM users WHERE email = $1",
4
+
"describe": {
5
+
"columns": [
6
+
{
7
+
"ordinal": 0,
8
+
"name": "id",
9
+
"type_info": "Int4"
10
+
}
11
+
],
12
+
"parameters": {
13
+
"Left": [
14
+
"Text"
15
+
]
16
+
},
17
+
"nullable": [
18
+
false
19
+
]
20
+
},
21
+
"hash": "4560c237741ce9d4166aecd669770b3360a3ac71e649b293efb88d92c3254068"
22
+
}
+34
.sqlx/query-f3f58600e971f1be6cbe206bba24f77769f54c6230e28f5b3dc719b869d9cb3f.json
+34
.sqlx/query-f3f58600e971f1be6cbe206bba24f77769f54c6230e28f5b3dc719b869d9cb3f.json
···
1
+
{
2
+
"db_name": "PostgreSQL",
3
+
"query": "SELECT * FROM users WHERE email = $1",
4
+
"describe": {
5
+
"columns": [
6
+
{
7
+
"ordinal": 0,
8
+
"name": "id",
9
+
"type_info": "Int4"
10
+
},
11
+
{
12
+
"ordinal": 1,
13
+
"name": "email",
14
+
"type_info": "Varchar"
15
+
},
16
+
{
17
+
"ordinal": 2,
18
+
"name": "password_hash",
19
+
"type_info": "Text"
20
+
}
21
+
],
22
+
"parameters": {
23
+
"Left": [
24
+
"Text"
25
+
]
26
+
},
27
+
"nullable": [
28
+
false,
29
+
false,
30
+
false
31
+
]
32
+
},
33
+
"hash": "f3f58600e971f1be6cbe206bba24f77769f54c6230e28f5b3dc719b869d9cb3f"
34
+
}
+4
Cargo.toml
+4
Cargo.toml
+2
-2
src/handlers.rs
+2
-2
src/handlers.rs
···
1
1
use actix_web::{web, HttpResponse, Responder, HttpRequest};
2
-
use jsonwebtoken::{encode, decode, Header, EncodingKey, DecodingKey, Validation, errors::Error as JwtError};use crate::{error::AppError, models::{AuthResponse, Claims, CreateLink, Link, LoginRequest, RegisterRequest, User, UserResponse}, AppState};
2
+
use jsonwebtoken::{encode, Header, EncodingKey};use crate::{error::AppError, models::{AuthResponse, Claims, CreateLink, Link, LoginRequest, RegisterRequest, User, UserResponse}, AppState};
3
3
use regex::Regex;
4
4
use argon2::{password_hash::{rand_core::OsRng, SaltString}, PasswordVerifier};
5
5
use lazy_static::lazy_static;
6
6
use argon2::{Argon2, PasswordHash, PasswordHasher};
7
-
use crate::auth::{AuthenticatedUser};
7
+
use crate::auth::AuthenticatedUser;
8
8
9
9
lazy_static! {
10
10
static ref VALID_CODE_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9_-]{1,32}$").unwrap();
+11
src/lib.rs
+11
src/lib.rs
+6
-19
src/main.rs
+6
-19
src/main.rs
···
1
1
use actix_web::{web, App, HttpServer};
2
2
use actix_cors::Cors;
3
3
use anyhow::Result;
4
-
use sqlx::PgPool;
4
+
use sqlx::postgres::PgPoolOptions;
5
+
use simple_link::{AppState, handlers};
5
6
use tracing::info;
6
-
7
-
mod error;
8
-
mod handlers;
9
-
mod models;
10
-
mod auth;
11
-
12
-
#[derive(Clone)]
13
-
pub struct AppState {
14
-
db: PgPool,
15
-
}
16
7
17
8
#[actix_web::main]
18
9
async fn main() -> Result<()> {
···
26
17
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
27
18
28
19
// Create database connection pool
29
-
use sqlx::postgres::PgPoolOptions;
30
-
31
-
// In main(), replace the PgPool::connect with:
32
20
let pool = PgPoolOptions::new()
33
21
.max_connections(5)
34
22
.acquire_timeout(std::time::Duration::from_secs(3))
···
36
24
.await?;
37
25
38
26
// Run database migrations
39
-
//sqlx::migrate!("./migrations").run(&pool).await?;
27
+
sqlx::migrate!("./migrations").run(&pool).await?;
40
28
41
29
let state = AppState { db: pool };
42
30
···
58
46
.route("/shorten", web::post().to(handlers::create_short_url))
59
47
.route("/links", web::get().to(handlers::get_all_links))
60
48
.route("/auth/register", web::post().to(handlers::register))
61
-
.route("/auth/login", web::post().to(handlers::login)),
62
-
49
+
.route("/auth/login", web::post().to(handlers::login))
50
+
.route("/health", web::get().to(handlers::health_check)),
63
51
)
64
52
.service(
65
53
web::resource("/{short_code}")
66
54
.route(web::get().to(handlers::redirect_to_url))
67
55
)
68
-
.service(web::resource("/{short_code}").route(web::get().to(handlers::redirect_to_url)))
69
56
})
70
-
.workers(2) // Limit worker threads
57
+
.workers(2)
71
58
.backlog(10_000)
72
59
.bind("127.0.0.1:8080")?
73
60
.run()
+9
-2
src/migrations/2025125_initial.sql
migrations/20250125000000_init.sql
+9
-2
src/migrations/2025125_initial.sql
migrations/20250125000000_init.sql
···
1
+
-- Add Migration Version
2
+
CREATE TABLE IF NOT EXISTS _sqlx_migrations (
3
+
version BIGINT PRIMARY KEY,
4
+
description TEXT NOT NULL,
5
+
installed_on TIMESTAMPTZ NOT NULL DEFAULT NOW()
6
+
);
7
+
1
8
-- Create users table
2
9
CREATE TABLE users (
3
10
id SERIAL PRIMARY KEY,
···
5
12
password_hash TEXT NOT NULL
6
13
);
7
14
8
-
-- Create links table with user_id from the start
15
+
-- Create links table
9
16
CREATE TABLE links (
10
17
id SERIAL PRIMARY KEY,
11
18
original_url TEXT NOT NULL,
···
15
22
user_id INTEGER REFERENCES users(id)
16
23
);
17
24
18
-
-- Create clicks table for tracking
25
+
-- Create clicks table
19
26
CREATE TABLE clicks (
20
27
id SERIAL PRIMARY KEY,
21
28
link_id INTEGER REFERENCES links(id),