feat: implement repo fetching, add sentry

This commit is contained in:
2026-01-15 20:12:54 -08:00
parent 6f2f64fd73
commit 68d9a0a626
12 changed files with 822 additions and 51 deletions

View File

@@ -4,13 +4,15 @@ use serde::{Deserialize, Serialize};
use crate::{AppState, account::AccountRepository, error::Result};
#[derive(Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
struct Repository {
id: u64,
name: String,
full_name: String,
description: String,
language: String,
stars: usize,
description: Option<String>,
language: Option<String>,
#[serde(alias = "stargazers_count")]
stars: Option<usize>,
updated_at: DateTime<Utc>,
private: bool,
}
@@ -25,11 +27,13 @@ pub async fn get_repos(
let response = app_state
.reqwest_client
.get("https://api.github.com/user/repos")
.get("https://api.github.com/user/repos?affiliation=owner")
.bearer_auth(token)
.send()
.await?;
response.error_for_status_ref()?;
let data = response.json::<Vec<Repository>>().await?;
tracing::debug!(github_response = ?data.iter().filter(|r| r.private == true).collect::<Vec<&Repository>>(), "received repos");
Ok(HttpResponse::Ok().json(response.json::<Vec<Repository>>().await?))
Ok(HttpResponse::Ok().json(data))
}

View File

@@ -28,7 +28,7 @@ struct ErrorResponse {
impl ResponseError for Error {
fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
match self {
Error::AccessToken => HttpResponse::Unauthorized().finish(),
Error::AccessToken => HttpResponse::BadRequest().finish(),
Error::Unauthorized => HttpResponse::Unauthorized().finish(),
Error::TokenExpired => HttpResponse::Unauthorized().json(ErrorResponse {
error: "token expired".to_string(),

View File

@@ -6,8 +6,12 @@ mod middleware;
use std::env;
use actix_web::{App, HttpServer, middleware::from_fn, web};
use actix_web::{App, HttpServer, middleware::from_fn, rt::System, web};
use sqlx::PgPool;
use tracing::level_filters::LevelFilter;
use tracing_subscriber::{
EnvFilter, fmt::format::FmtSpan, layer::SubscriberExt, util::SubscriberInitExt,
};
use crate::auth::{Auth, JWT};
@@ -17,9 +21,15 @@ struct AppState {
auth: Auth,
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let reqwest_client = reqwest::Client::new();
async fn run() -> std::io::Result<()> {
let reqwest_client = reqwest::Client::builder()
.user_agent(concat!(
env!("CARGO_PKG_NAME"),
"/",
env!("CARGO_PKG_VERSION")
))
.build()
.expect("failed to create reqwest client");
let app_data = web::Data::new(AppState {
reqwest_client: reqwest_client.clone(),
pool: PgPool::connect(
@@ -33,21 +43,74 @@ async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
.app_data(app_data.clone())
.wrap(
sentry::integrations::actix::Sentry::builder()
.capture_server_errors(true)
.start_transaction(true)
.finish(),
)
.wrap(tracing_actix_web::TracingLogger::default())
.route(
"/",
web::get()
.to(async || concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"))),
)
.service(
web::scope("/api")
.route(
"/{user_id}/repos",
web::get().to(endpoints::get_repos::get_repos),
)
.wrap(from_fn(middleware::protected)),
web::scope("/api").service(
web::scope("/v0").service(
web::scope("/user")
.route(
"/{user_id}/repos",
web::get().to(endpoints::get_repos::get_repos),
)
.wrap(from_fn(middleware::protected)),
),
),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
// cant use #[actix_web::main] because of Sentry
// Note: "Macros like #[tokio::main] and #[actix_web::main] are not supported. The Sentry client must be initialized before the async runtime is started."
// https://docs.sentry.io/platforms/rust/guides/actix-web/
fn main() -> std::io::Result<()> {
let tracing_env_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy()
.add_directive("reqwest=info".parse().unwrap())
.add_directive("hyper=info".parse().unwrap())
.add_directive("h2=info".parse().unwrap())
.add_directive("rustls=info".parse().unwrap());
tracing_subscriber::registry()
.with(tracing_env_filter)
.with(
tracing_subscriber::fmt::layer()
.compact()
.with_span_events(FmtSpan::CLOSE),
)
.with(sentry::integrations::tracing::layer())
.init();
let guard = sentry::init((
env::var("SENTRY_DSN").ok(),
sentry::ClientOptions {
release: sentry::release_name!(),
traces_sample_rate: 1.0,
session_mode: sentry::SessionMode::Request,
debug: true,
..Default::default()
},
));
if guard.is_enabled() {
tracing::info!("sentry initialized");
} else {
tracing::info!("sentry **NOT** initialized")
};
System::new().block_on(run())
}