Add version check and cloud delete endpoints
This commit is contained in:
@@ -23,6 +23,34 @@ const TEXT_EXTENSIONS: [&str; 10] = [
|
||||
".typ", ".toml", ".bib", ".csl", ".yml", ".yaml", ".json", ".md", ".txt", ".csv",
|
||||
];
|
||||
|
||||
pub const MIN_DESKTOP_VERSION: &str = "1.0.0";
|
||||
|
||||
fn parse_version(version: &str) -> (u32, u32, u32) {
|
||||
let mut parts = version.trim().split('.').map(|part| part.parse::<u32>().unwrap_or(0));
|
||||
(
|
||||
parts.next().unwrap_or(0),
|
||||
parts.next().unwrap_or(0),
|
||||
parts.next().unwrap_or(0),
|
||||
)
|
||||
}
|
||||
|
||||
fn version_at_least(actual: &str, required: &str) -> bool {
|
||||
parse_version(actual) >= parse_version(required)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ServerVersionInfo {
|
||||
pub server_version: String,
|
||||
pub min_desktop_version: String,
|
||||
}
|
||||
|
||||
pub async fn version_info() -> Json<ServerVersionInfo> {
|
||||
Json(ServerVersionInfo {
|
||||
server_version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
min_desktop_version: MIN_DESKTOP_VERSION.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn is_text_path(path: &str) -> bool {
|
||||
let lower = path.to_lowercase();
|
||||
TEXT_EXTENSIONS.iter().any(|ext| lower.ends_with(ext))
|
||||
@@ -111,6 +139,7 @@ pub struct DeviceLoginRequest {
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
pub device_name: Option<String>,
|
||||
pub client_version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -125,6 +154,18 @@ pub async fn login(
|
||||
State(state): State<AppState>,
|
||||
Json(payload): Json<DeviceLoginRequest>,
|
||||
) -> Result<Json<DeviceLoginResponse>, (StatusCode, String)> {
|
||||
if let Some(client_version) = &payload.client_version {
|
||||
if !version_at_least(client_version, MIN_DESKTOP_VERSION) {
|
||||
return Err((
|
||||
StatusCode::UPGRADE_REQUIRED,
|
||||
format!(
|
||||
"This server requires typst-desktop v{} or newer (you have v{}). Please update the app.",
|
||||
MIN_DESKTOP_VERSION, client_version
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let user = sqlx::query_as::<_, User>(
|
||||
"SELECT id, username, email, password_hash, is_admin FROM users WHERE email = ?",
|
||||
)
|
||||
@@ -1010,6 +1051,27 @@ pub async fn create_document(
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn delete_document(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<StatusCode, (StatusCode, String)> {
|
||||
let user_id = authenticate(&state, &headers).await?;
|
||||
|
||||
let result = sqlx::query("DELETE FROM documents WHERE id = ? AND owner_id = ?")
|
||||
.bind(&id)
|
||||
.bind(&user_id)
|
||||
.execute(&state.db)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
return Err((StatusCode::NOT_FOUND, "Document not found or unauthorized".to_string()));
|
||||
}
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct CloudFile {
|
||||
pub id: String,
|
||||
@@ -1092,3 +1154,24 @@ pub async fn pull_account_file(
|
||||
content: BASE64.encode(&data),
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn delete_account_file(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<StatusCode, (StatusCode, String)> {
|
||||
let user_id = authenticate(&state, &headers).await?;
|
||||
|
||||
let result = sqlx::query("DELETE FROM files WHERE id = ? AND owner_id = ?")
|
||||
.bind(&id)
|
||||
.bind(&user_id)
|
||||
.execute(&state.db)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
return Err((StatusCode::NOT_FOUND, "File not found or unauthorized".to_string()));
|
||||
}
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
+3
-2
@@ -142,6 +142,7 @@ async fn main() {
|
||||
.route("/packages/{name}", get(packages::list_versions).delete(packages::delete_package));
|
||||
|
||||
let desktop_routes = Router::new()
|
||||
.route("/version", get(desktop::version_info))
|
||||
.route("/auth/login", post(desktop::login))
|
||||
.route("/auth/logout", post(desktop::logout))
|
||||
.route("/auth/me", get(desktop::me))
|
||||
@@ -150,10 +151,10 @@ async fn main() {
|
||||
.route("/projects/{id}/manifest", get(desktop::get_manifest))
|
||||
.route("/folders", get(desktop::list_folders))
|
||||
.route("/documents", get(desktop::list_documents).post(desktop::create_document))
|
||||
.route("/documents/{id}", get(desktop::pull_document).put(desktop::push_document))
|
||||
.route("/documents/{id}", get(desktop::pull_document).put(desktop::push_document).delete(desktop::delete_document))
|
||||
.route("/shared", get(desktop::list_shared))
|
||||
.route("/files", get(desktop::list_account_files))
|
||||
.route("/files/{id}", get(desktop::pull_account_file))
|
||||
.route("/files/{id}", get(desktop::pull_account_file).delete(desktop::delete_account_file))
|
||||
.route("/projects/{id}/file", get(desktop::pull_file).put(desktop::push_file).delete(desktop::delete_file));
|
||||
|
||||
let v1_routes = Router::new()
|
||||
|
||||
Reference in New Issue
Block a user