scufflecloud_core/services/
mod.rs

1use std::net::SocketAddr;
2use std::sync::Arc;
3
4use axum::http::StatusCode;
5use axum::{Extension, Json};
6use scuffle_http::http::Method;
7use tinc::TincService;
8use tinc::openapi::Server;
9use tower_http::cors::CorsLayer;
10use tower_http::trace::TraceLayer;
11
12use crate::CoreConfig;
13
14mod organization_invitations;
15mod organizations;
16mod sessions;
17mod users;
18
19#[derive(Debug)]
20pub struct CoreSvc<G> {
21    _phantom: std::marker::PhantomData<G>,
22}
23
24impl<G> Default for CoreSvc<G> {
25    fn default() -> Self {
26        Self {
27            _phantom: std::marker::PhantomData,
28        }
29    }
30}
31
32impl<G: CoreConfig> scuffle_bootstrap::Service<G> for CoreSvc<G> {
33    async fn run(self, global: Arc<G>, ctx: scuffle_context::Context) -> anyhow::Result<()> {
34        // REST
35        let organization_invitations_svc_tinc =
36            pb::scufflecloud::core::v1::organization_invitations_service_tinc::OrganizationInvitationsServiceTinc::new(
37                CoreSvc::<G>::default(),
38            );
39        let organizations_svc_tinc =
40            pb::scufflecloud::core::v1::organizations_service_tinc::OrganizationsServiceTinc::new(CoreSvc::<G>::default());
41        let sessions_svc_tinc =
42            pb::scufflecloud::core::v1::sessions_service_tinc::SessionsServiceTinc::new(CoreSvc::<G>::default());
43        let users_svc_tinc = pb::scufflecloud::core::v1::users_service_tinc::UsersServiceTinc::new(CoreSvc::<G>::default());
44
45        let cors = CorsLayer::new()
46            .allow_methods([Method::GET, Method::POST, Method::OPTIONS])
47            .allow_origin(tower_http::cors::Any)
48            .allow_headers(tower_http::cors::Any);
49
50        let mut openapi_schema = organization_invitations_svc_tinc.openapi_schema();
51        openapi_schema.merge(organizations_svc_tinc.openapi_schema());
52        openapi_schema.merge(sessions_svc_tinc.openapi_schema());
53        openapi_schema.merge(users_svc_tinc.openapi_schema());
54        openapi_schema.info.title = "Scuffle Cloud Core API".to_string();
55        openapi_schema.info.version = "v1".to_string();
56        openapi_schema.servers = Some(vec![Server::new("/v1")]);
57
58        let v1_rest_router = axum::Router::new()
59            .route("/openapi.json", axum::routing::get(Json(openapi_schema)))
60            .merge(organization_invitations_svc_tinc.into_router())
61            .merge(organizations_svc_tinc.into_router())
62            .merge(sessions_svc_tinc.into_router())
63            .merge(users_svc_tinc.into_router())
64            .layer(cors);
65
66        // gRPC
67        let organization_invitations_svc =
68            pb::scufflecloud::core::v1::organization_invitations_service_server::OrganizationInvitationsServiceServer::new(
69                CoreSvc::<G>::default(),
70            );
71        let organizations_svc = pb::scufflecloud::core::v1::organizations_service_server::OrganizationsServiceServer::new(
72            CoreSvc::<G>::default(),
73        );
74        let sessions_svc =
75            pb::scufflecloud::core::v1::sessions_service_server::SessionsServiceServer::new(CoreSvc::<G>::default());
76        let users_svc = pb::scufflecloud::core::v1::users_service_server::UsersServiceServer::new(CoreSvc::<G>::default());
77
78        let reflection_v1_svc = tonic_reflection::server::Builder::configure()
79            .register_encoded_file_descriptor_set(pb::ANNOTATIONS_PB)
80            .build_v1()?;
81        let reflection_v1alpha_svc = tonic_reflection::server::Builder::configure()
82            .register_encoded_file_descriptor_set(pb::ANNOTATIONS_PB)
83            .build_v1alpha()?;
84
85        let mut builder = tonic::service::Routes::builder();
86        builder.add_service(organization_invitations_svc);
87        builder.add_service(organizations_svc);
88        builder.add_service(sessions_svc);
89        builder.add_service(users_svc);
90        builder.add_service(reflection_v1_svc);
91        builder.add_service(reflection_v1alpha_svc);
92        let grpc_router = builder.routes().prepare().into_axum_router();
93
94        let mut router = axum::Router::new()
95            .nest("/v1", v1_rest_router)
96            .merge(grpc_router)
97            .route_layer(axum::middleware::from_fn(crate::middleware::auth::<G>))
98            .layer(axum::middleware::from_fn(crate::middleware::ip_address::<G>))
99            .layer(TraceLayer::new_for_http())
100            .layer(Extension(Arc::clone(&global)))
101            .fallback(StatusCode::NOT_FOUND);
102
103        if global.swagger_ui_enabled() {
104            router = router.merge(swagger_ui_dist::generate_routes(swagger_ui_dist::ApiDefinition {
105                uri_prefix: "/v1/docs",
106                api_definition: swagger_ui_dist::OpenApiSource::Uri("/v1/openapi.json"),
107                title: Some("V1 Api Docs"),
108            }));
109        }
110
111        scuffle_http::HttpServer::builder()
112            .tower_make_service_with_addr(router.into_make_service_with_connect_info::<SocketAddr>())
113            .bind(global.bind())
114            .ctx(ctx)
115            .build()
116            .run()
117            .await?;
118
119        Ok(())
120    }
121}