scufflecloud_core/
services.rs1use std::net::SocketAddr;
2use std::sync::Arc;
3
4use axum::http::{HeaderName, StatusCode};
5use axum::{Extension, Json};
6use reqwest::header::CONTENT_TYPE;
7use scuffle_http::http::Method;
8use tinc::TincService;
9use tinc::openapi::Server;
10use tower_http::cors::{AllowHeaders, CorsLayer, ExposeHeaders};
11use tower_http::trace::TraceLayer;
12
13use crate::middleware;
14
15mod organization_invitations;
16mod organizations;
17mod sessions;
18mod users;
19
20#[derive(Debug)]
21pub struct CoreSvc<G> {
22 _phantom: std::marker::PhantomData<G>,
23}
24
25impl<G> Default for CoreSvc<G> {
26 fn default() -> Self {
27 Self {
28 _phantom: std::marker::PhantomData,
29 }
30 }
31}
32
33fn rest_cors_layer() -> CorsLayer {
34 CorsLayer::new()
35 .allow_methods([Method::GET, Method::POST, Method::OPTIONS])
36 .allow_origin(tower_http::cors::Any)
37 .allow_headers(tower_http::cors::Any)
38}
39
40fn grpc_web_cors_layer() -> CorsLayer {
41 let allow_headers = [
43 CONTENT_TYPE,
44 HeaderName::from_static("x-grpc-web"),
45 HeaderName::from_static("grpc-timeout"),
46 ]
47 .into_iter()
48 .chain(middleware::auth_headers());
49
50 let expose_headers = [
51 HeaderName::from_static("grpc-encoding"),
52 HeaderName::from_static("grpc-status"),
53 HeaderName::from_static("grpc-status-details-bin"),
54 HeaderName::from_static("grpc-message"),
55 ];
56
57 CorsLayer::new()
58 .allow_methods([Method::GET, Method::POST, Method::OPTIONS])
59 .allow_headers(AllowHeaders::list(allow_headers))
60 .expose_headers(ExposeHeaders::list(expose_headers))
61 .allow_origin(tower_http::cors::Any)
62 .allow_headers(tower_http::cors::Any)
63}
64
65impl<G: core_traits::Global> scuffle_bootstrap::Service<G> for CoreSvc<G> {
66 async fn run(self, global: Arc<G>, ctx: scuffle_context::Context) -> anyhow::Result<()> {
67 let organization_invitations_svc_tinc =
69 pb::scufflecloud::core::v1::organization_invitations_service_tinc::OrganizationInvitationsServiceTinc::new(
70 CoreSvc::<G>::default(),
71 );
72 let organizations_svc_tinc =
73 pb::scufflecloud::core::v1::organizations_service_tinc::OrganizationsServiceTinc::new(CoreSvc::<G>::default());
74 let sessions_svc_tinc =
75 pb::scufflecloud::core::v1::sessions_service_tinc::SessionsServiceTinc::new(CoreSvc::<G>::default());
76 let users_svc_tinc = pb::scufflecloud::core::v1::users_service_tinc::UsersServiceTinc::new(CoreSvc::<G>::default());
77
78 let mut openapi_schema = organization_invitations_svc_tinc.openapi_schema();
79 openapi_schema.merge(organizations_svc_tinc.openapi_schema());
80 openapi_schema.merge(sessions_svc_tinc.openapi_schema());
81 openapi_schema.merge(users_svc_tinc.openapi_schema());
82 openapi_schema.info.title = "Scuffle Cloud Core API".to_string();
83 openapi_schema.info.version = "v1".to_string();
84 openapi_schema.servers = Some(vec![Server::new("/v1")]);
85
86 let v1_rest_router = axum::Router::new()
87 .route("/openapi.json", axum::routing::get(Json(openapi_schema)))
88 .merge(organization_invitations_svc_tinc.into_router())
89 .merge(organizations_svc_tinc.into_router())
90 .merge(sessions_svc_tinc.into_router())
91 .merge(users_svc_tinc.into_router())
92 .layer(rest_cors_layer());
93
94 let organization_invitations_svc =
96 pb::scufflecloud::core::v1::organization_invitations_service_server::OrganizationInvitationsServiceServer::new(
97 CoreSvc::<G>::default(),
98 );
99 let organizations_svc = pb::scufflecloud::core::v1::organizations_service_server::OrganizationsServiceServer::new(
100 CoreSvc::<G>::default(),
101 );
102 let sessions_svc =
103 pb::scufflecloud::core::v1::sessions_service_server::SessionsServiceServer::new(CoreSvc::<G>::default());
104 let users_svc = pb::scufflecloud::core::v1::users_service_server::UsersServiceServer::new(CoreSvc::<G>::default());
105
106 let reflection_v1_svc = tonic_reflection::server::Builder::configure()
107 .register_encoded_file_descriptor_set(pb::ANNOTATIONS_PB)
108 .build_v1()?;
109 let reflection_v1alpha_svc = tonic_reflection::server::Builder::configure()
110 .register_encoded_file_descriptor_set(pb::ANNOTATIONS_PB)
111 .build_v1alpha()?;
112
113 let mut builder = tonic::service::Routes::builder();
114 builder.add_service(organization_invitations_svc);
115 builder.add_service(organizations_svc);
116 builder.add_service(sessions_svc);
117 builder.add_service(users_svc);
118 builder.add_service(reflection_v1_svc);
119 builder.add_service(reflection_v1alpha_svc);
120
121 let grpc_router = builder
122 .routes()
123 .prepare()
124 .into_axum_router()
125 .layer(tonic_web::GrpcWebLayer::new())
126 .layer(grpc_web_cors_layer());
127
128 let mut router = axum::Router::new()
129 .nest("/v1", v1_rest_router)
130 .merge(grpc_router)
131 .route_layer(axum::middleware::from_fn(crate::middleware::auth::<G>))
132 .layer(geo_ip::middleware::middleware::<G>())
133 .layer(TraceLayer::new_for_http())
134 .layer(Extension(Arc::clone(&global)))
135 .fallback(StatusCode::NOT_FOUND);
136
137 if global.swagger_ui_enabled() {
138 router = router.merge(swagger_ui_dist::generate_routes(swagger_ui_dist::ApiDefinition {
139 uri_prefix: "/v1/docs",
140 api_definition: swagger_ui_dist::OpenApiSource::Uri("/v1/openapi.json"),
141 title: Some("Scuffle Core v1 Api Docs"),
142 }));
143 }
144
145 scuffle_http::HttpServer::builder()
146 .tower_make_service_with_addr(router.into_make_service_with_connect_info::<SocketAddr>())
147 .bind(global.service_bind())
148 .ctx(ctx)
149 .build()
150 .run()
151 .await?;
152
153 Ok(())
154 }
155}