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-message"),
54 ];
55
56 CorsLayer::new()
57 .allow_methods([Method::GET, Method::POST, Method::OPTIONS])
58 .allow_headers(AllowHeaders::list(allow_headers))
59 .expose_headers(ExposeHeaders::list(expose_headers))
60 .allow_origin(tower_http::cors::Any)
61 .allow_headers(tower_http::cors::Any)
62}
63
64impl<G: core_traits::Global> scuffle_bootstrap::Service<G> for CoreSvc<G> {
65 async fn run(self, global: Arc<G>, ctx: scuffle_context::Context) -> anyhow::Result<()> {
66 let organization_invitations_svc_tinc =
68 pb::scufflecloud::core::v1::organization_invitations_service_tinc::OrganizationInvitationsServiceTinc::new(
69 CoreSvc::<G>::default(),
70 );
71 let organizations_svc_tinc =
72 pb::scufflecloud::core::v1::organizations_service_tinc::OrganizationsServiceTinc::new(CoreSvc::<G>::default());
73 let sessions_svc_tinc =
74 pb::scufflecloud::core::v1::sessions_service_tinc::SessionsServiceTinc::new(CoreSvc::<G>::default());
75 let users_svc_tinc = pb::scufflecloud::core::v1::users_service_tinc::UsersServiceTinc::new(CoreSvc::<G>::default());
76
77 let mut openapi_schema = organization_invitations_svc_tinc.openapi_schema();
78 openapi_schema.merge(organizations_svc_tinc.openapi_schema());
79 openapi_schema.merge(sessions_svc_tinc.openapi_schema());
80 openapi_schema.merge(users_svc_tinc.openapi_schema());
81 openapi_schema.info.title = "Scuffle Cloud Core API".to_string();
82 openapi_schema.info.version = "v1".to_string();
83 openapi_schema.servers = Some(vec![Server::new("/v1")]);
84
85 let v1_rest_router = axum::Router::new()
86 .route("/openapi.json", axum::routing::get(Json(openapi_schema)))
87 .merge(organization_invitations_svc_tinc.into_router())
88 .merge(organizations_svc_tinc.into_router())
89 .merge(sessions_svc_tinc.into_router())
90 .merge(users_svc_tinc.into_router())
91 .layer(rest_cors_layer());
92
93 let organization_invitations_svc =
95 pb::scufflecloud::core::v1::organization_invitations_service_server::OrganizationInvitationsServiceServer::new(
96 CoreSvc::<G>::default(),
97 );
98 let organizations_svc = pb::scufflecloud::core::v1::organizations_service_server::OrganizationsServiceServer::new(
99 CoreSvc::<G>::default(),
100 );
101 let sessions_svc =
102 pb::scufflecloud::core::v1::sessions_service_server::SessionsServiceServer::new(CoreSvc::<G>::default());
103 let users_svc = pb::scufflecloud::core::v1::users_service_server::UsersServiceServer::new(CoreSvc::<G>::default());
104
105 let reflection_v1_svc = tonic_reflection::server::Builder::configure()
106 .register_encoded_file_descriptor_set(pb::ANNOTATIONS_PB)
107 .build_v1()?;
108 let reflection_v1alpha_svc = tonic_reflection::server::Builder::configure()
109 .register_encoded_file_descriptor_set(pb::ANNOTATIONS_PB)
110 .build_v1alpha()?;
111
112 let mut builder = tonic::service::Routes::builder();
113 builder.add_service(organization_invitations_svc);
114 builder.add_service(organizations_svc);
115 builder.add_service(sessions_svc);
116 builder.add_service(users_svc);
117 builder.add_service(reflection_v1_svc);
118 builder.add_service(reflection_v1alpha_svc);
119
120 let grpc_router = builder
121 .routes()
122 .prepare()
123 .into_axum_router()
124 .layer(tonic_web::GrpcWebLayer::new())
125 .layer(grpc_web_cors_layer());
126
127 let mut router = axum::Router::new()
128 .nest("/v1", v1_rest_router)
129 .merge(grpc_router)
130 .route_layer(axum::middleware::from_fn(crate::middleware::auth::<G>))
131 .layer(geo_ip::middleware::middleware::<G>())
132 .layer(TraceLayer::new_for_http())
133 .layer(Extension(Arc::clone(&global)))
134 .fallback(StatusCode::NOT_FOUND);
135
136 if global.swagger_ui_enabled() {
137 router = router.merge(swagger_ui_dist::generate_routes(swagger_ui_dist::ApiDefinition {
138 uri_prefix: "/v1/docs",
139 api_definition: swagger_ui_dist::OpenApiSource::Uri("/v1/openapi.json"),
140 title: Some("Scuffle Core v1 Api Docs"),
141 }));
142 }
143
144 scuffle_http::HttpServer::builder()
145 .tower_make_service_with_addr(router.into_make_service_with_connect_info::<SocketAddr>())
146 .bind(global.service_bind())
147 .ctx(ctx)
148 .build()
149 .run()
150 .await?;
151
152 Ok(())
153 }
154}