feat(presentation): routes and main — Plan 1 complete

This commit is contained in:
2026-05-14 04:06:17 +02:00
parent 38106ecdb6
commit c5d262c68f
4 changed files with 126 additions and 1 deletions

View File

@@ -14,6 +14,7 @@ api-types = { workspace = true }
postgres = { workspace = true }
auth = { workspace = true }
axum = { workspace = true }
sqlx = { workspace = true }
tower-http = { workspace = true }
tokio = { workspace = true, features = ["full"] }
serde = { workspace = true }

View File

@@ -1,4 +1,41 @@
pub mod errors;
pub mod extractors;
pub mod handlers;
pub mod routes;
pub mod state;
use std::sync::Arc;
use sqlx::PgPool;
use state::AppState;
use async_trait::async_trait;
use domain::{errors::DomainError, events::DomainEvent, ports::EventPublisher};
struct NoOpEventPublisher;
#[async_trait]
impl EventPublisher for NoOpEventPublisher {
async fn publish(&self, _e: &DomainEvent) -> Result<(), DomainError> {
Ok(())
}
}
pub fn build_state(pool: PgPool, jwt_secret: String) -> AppState {
AppState {
users: Arc::new(postgres::user::PgUserRepository::new(pool.clone())),
thoughts: Arc::new(postgres::thought::PgThoughtRepository::new(pool.clone())),
likes: Arc::new(postgres::like::PgLikeRepository::new(pool.clone())),
boosts: Arc::new(postgres::boost::PgBoostRepository::new(pool.clone())),
follows: Arc::new(postgres::follow::PgFollowRepository::new(pool.clone())),
blocks: Arc::new(postgres::block::PgBlockRepository::new(pool.clone())),
tags: Arc::new(postgres::tag::PgTagRepository::new(pool.clone())),
api_keys: Arc::new(postgres::api_key::PgApiKeyRepository::new(pool.clone())),
top_friends: Arc::new(postgres::top_friend::PgTopFriendRepository::new(pool.clone())),
notifications: Arc::new(postgres::notification::PgNotificationRepository::new(pool.clone())),
remote_actors: Arc::new(postgres::remote_actor::PgRemoteActorRepository::new(pool.clone())),
feed: Arc::new(postgres::feed::PgFeedRepository::new(pool.clone())),
auth: Arc::new(auth::JwtAuthService::new(jwt_secret, 86400 * 30)),
hasher: Arc::new(auth::Argon2PasswordHasher),
events: Arc::new(NoOpEventPublisher),
}
}

View File

@@ -1 +1,28 @@
fn main() {}
use sqlx::PgPool;
use tower_http::cors::CorsLayer;
use tracing_subscriber::EnvFilter;
#[tokio::main]
async fn main() {
dotenvy::dotenv().ok();
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.init();
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL required");
let jwt_secret = std::env::var("JWT_SECRET").expect("JWT_SECRET required");
let port = std::env::var("PORT").unwrap_or_else(|_| "3000".into());
let pool = PgPool::connect(&database_url).await.expect("DB connect failed");
sqlx::migrate!("../adapters/postgres/migrations").run(&pool).await.expect("Migrations failed");
let state = presentation::build_state(pool, jwt_secret);
let app = presentation::routes::router()
.with_state(state)
.layer(CorsLayer::permissive());
let addr = format!("0.0.0.0:{port}");
tracing::info!("Listening on {addr}");
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
}

View File

@@ -0,0 +1,60 @@
use axum::{
routing::{delete, get, patch, post, put},
Router,
};
use crate::{handlers::*, state::AppState};
pub fn router() -> Router<AppState> {
Router::new()
// auth
.route("/auth/register", post(auth::post_register))
.route("/auth/login", post(auth::post_login))
// users — static paths before parameterised
.route("/users/me", patch(users::patch_profile))
.route("/users/me/top-friends", put(social::put_top_friends))
.route("/users/{username}", get(users::get_user))
.route("/users/{username}/following", get(feed::get_following_handler))
.route("/users/{username}/followers", get(feed::get_followers_handler))
.route("/users/{username}/top-friends", get(social::get_top_friends_handler))
// follows & blocks (use {id} param)
.route(
"/users/{id}/follow",
post(social::post_follow).delete(social::delete_follow),
)
.route(
"/users/{id}/block",
post(social::post_block).delete(social::delete_block),
)
// thoughts
.route("/thoughts", post(thoughts::post_thought))
.route(
"/thoughts/{id}",
get(thoughts::get_thought_handler)
.patch(thoughts::patch_thought)
.delete(thoughts::delete_thought_handler),
)
.route("/thoughts/{id}/thread", get(thoughts::get_thread_handler))
// likes & boosts
.route(
"/thoughts/{id}/like",
post(social::post_like).delete(social::delete_like),
)
.route(
"/thoughts/{id}/boost",
post(social::post_boost).delete(social::delete_boost),
)
// feeds
.route("/feed", get(feed::home_feed))
.route("/feed/public", get(feed::public_feed))
.route("/search", get(feed::search_handler))
// notifications
.route("/notifications", get(notifications::list_notifications))
.route("/notifications/read-all", post(notifications::mark_all_read))
.route("/notifications/{id}/read", post(notifications::mark_notification_read))
// api keys
.route(
"/api-keys",
get(api_keys::get_api_keys).post(api_keys::post_api_key),
)
.route("/api-keys/{id}", delete(api_keys::delete_api_key_handler))
}