1use firebase_auth::FirebaseUser;
2use http::StatusCode;
3use qubit::{Extensions, FromRequestExtensions, Router, RpcError, handler};
4use serde::Serialize;
5use serde_json::Value;
6use tracing::debug;
7use ts_rs::TS;
8use uuid::Uuid;
9
10use super::app::{AppCtx, AppError, AppState};
11use super::auth::{NewPermissions, PermissionLevel, Permissions};
12use super::{auth, document as doc, user};
13
14pub fn router() -> Router<AppState> {
16 Router::new()
17 .handler(new_ref)
18 .handler(get_doc)
19 .handler(head_snapshot)
20 .handler(save_snapshot)
21 .handler(get_permissions)
22 .handler(set_permissions)
23 .handler(validate_session)
24 .handler(sign_up_or_sign_in)
25 .handler(user_by_username)
26 .handler(username_status)
27 .handler(get_active_user_profile)
28 .handler(set_active_user_profile)
29}
30
31#[handler(mutation)]
32async fn new_ref(ctx: AppCtx, content: Value) -> RpcResult<Uuid> {
33 doc::new_ref(ctx, content).await.into()
34}
35
36#[handler(query)]
37async fn get_doc(ctx: AppCtx, ref_id: Uuid) -> RpcResult<RefDoc> {
38 _get_doc(ctx, ref_id).await.into()
39}
40async fn _get_doc(ctx: AppCtx, ref_id: Uuid) -> Result<RefDoc, AppError> {
41 let permissions = auth::permissions(&ctx, ref_id).await?;
42 let max_level = permissions.max_level();
43 if max_level >= Some(PermissionLevel::Write) {
44 let doc_id = doc::doc_id(ctx.state, ref_id).await?;
45 Ok(RefDoc::Live {
46 doc_id,
47 permissions,
48 })
49 } else if max_level >= Some(PermissionLevel::Read) {
50 let content = doc::head_snapshot(ctx.state, ref_id).await?;
51 Ok(RefDoc::Readonly {
52 content,
53 permissions,
54 })
55 } else {
56 Err(AppError::Forbidden(ref_id))
57 }
58}
59
60#[derive(Clone, Debug, Serialize, TS)]
62#[serde(tag = "tag")]
63enum RefDoc {
64 Readonly {
66 content: Value,
67 permissions: Permissions,
68 },
69
70 Live {
72 #[serde(rename = "docId")]
73 doc_id: String,
74 permissions: Permissions,
75 },
76}
77
78#[handler(query)]
79async fn head_snapshot(ctx: AppCtx, ref_id: Uuid) -> RpcResult<Value> {
80 _head_snapshot(ctx, ref_id).await.into()
81}
82async fn _head_snapshot(ctx: AppCtx, ref_id: Uuid) -> Result<Value, AppError> {
83 auth::authorize(&ctx, ref_id, PermissionLevel::Read).await?;
84 doc::head_snapshot(ctx.state, ref_id).await
85}
86
87#[handler(mutation)]
88async fn save_snapshot(ctx: AppCtx, data: doc::RefContent) -> RpcResult<()> {
89 _save_snapshot(ctx, data).await.into()
90}
91async fn _save_snapshot(ctx: AppCtx, data: doc::RefContent) -> Result<(), AppError> {
92 auth::authorize(&ctx, data.ref_id, PermissionLevel::Write).await?;
93 doc::save_snapshot(ctx.state, data).await
94}
95
96#[handler(query)]
97async fn get_permissions(ctx: AppCtx, ref_id: Uuid) -> RpcResult<Permissions> {
98 auth::permissions(&ctx, ref_id).await.into()
99}
100
101#[handler(mutation)]
102async fn set_permissions(ctx: AppCtx, ref_id: Uuid, new: NewPermissions) -> RpcResult<()> {
103 _set_permissions(ctx, ref_id, new).await.into()
104}
105async fn _set_permissions(ctx: AppCtx, ref_id: Uuid, new: NewPermissions) -> Result<(), AppError> {
106 if ctx.user.is_none() {
107 return Err(AppError::Unauthorized);
108 }
109 auth::authorize(&ctx, ref_id, PermissionLevel::Own).await?;
110 auth::set_permissions(&ctx.state, ref_id, new).await
111}
112
113#[handler(query)]
114async fn validate_session(ctx: AppCtx) -> RpcResult<()> {
115 auth::validate_session(ctx).await.into()
116}
117
118#[handler(mutation)]
119async fn sign_up_or_sign_in(ctx: AppCtx) -> RpcResult<()> {
120 user::sign_up_or_sign_in(ctx).await.into()
121}
122
123#[handler(query)]
124async fn user_by_username(ctx: AppCtx, username: &str) -> RpcResult<Option<user::UserSummary>> {
125 user::user_by_username(ctx.state, username).await.into()
126}
127
128#[handler(query)]
129async fn username_status(ctx: AppCtx, username: &str) -> RpcResult<user::UsernameStatus> {
130 user::username_status(ctx.state, username).await.into()
131}
132
133#[handler(query)]
134async fn get_active_user_profile(ctx: AppCtx) -> RpcResult<user::UserProfile> {
135 user::get_active_user_profile(ctx).await.into()
136}
137
138#[handler(mutation)]
139async fn set_active_user_profile(ctx: AppCtx, user: user::UserProfile) -> RpcResult<()> {
140 user::set_active_user_profile(ctx, user).await.into()
141}
142
143#[derive(Debug, Clone, Serialize, TS)]
145#[serde(tag = "tag")]
146enum RpcResult<T> {
147 Ok { content: T },
148 Err { code: u16, message: String },
149}
150
151impl<T> From<AppError> for RpcResult<T> {
152 fn from(error: AppError) -> Self {
153 let code = match error {
154 AppError::Invalid(_) => StatusCode::BAD_REQUEST,
155 AppError::Unauthorized => StatusCode::UNAUTHORIZED,
156 AppError::Forbidden(_) => StatusCode::FORBIDDEN,
157 AppError::Db(sqlx::Error::RowNotFound) => StatusCode::NOT_FOUND,
158 _ => StatusCode::INTERNAL_SERVER_ERROR,
159 };
160 RpcResult::Err {
161 code: code.as_u16(),
162 message: error.to_string(),
163 }
164 }
165}
166
167impl<T> From<Result<T, AppError>> for RpcResult<T> {
168 fn from(result: Result<T, AppError>) -> Self {
169 match result {
170 Ok(content) => RpcResult::Ok { content },
171 Err(error) => error.into(),
172 }
173 }
174}
175
176impl FromRequestExtensions<AppState> for AppCtx {
178 async fn from_request_extensions(
179 state: AppState,
180 mut extensions: Extensions,
181 ) -> Result<Self, RpcError> {
182 let user: Option<FirebaseUser> = extensions.remove();
183 if let Some(some_user) = &user {
184 debug!("Handling request from user: {}", some_user.user_id);
185 }
186 Ok(AppCtx { state, user })
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use std::path::PathBuf;
193
194 #[test]
195 fn rspc_type_defs() {
196 let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("pkg").join("src");
197 super::router().write_bindings_to_dir(dir);
198 }
199}