fraiseql_server/storage/
local.rs1use std::{path::PathBuf, time::Duration};
4
5use async_trait::async_trait;
6use fraiseql_error::FileError;
7
8use super::{StorageBackend, StorageResult, validate_key};
9
10pub struct LocalStorageBackend {
12 root: PathBuf,
13}
14
15impl LocalStorageBackend {
16 pub fn new(root: &str) -> Self {
18 Self {
19 root: PathBuf::from(root),
20 }
21 }
22
23 fn key_path(&self, key: &str) -> StorageResult<PathBuf> {
24 validate_key(key)?;
25 Ok(self.root.join(key))
26 }
27}
28
29#[async_trait]
30impl StorageBackend for LocalStorageBackend {
31 async fn upload(&self, key: &str, data: &[u8], _content_type: &str) -> StorageResult<String> {
32 let path = self.key_path(key)?;
33 if let Some(parent) = path.parent() {
34 tokio::fs::create_dir_all(parent).await.map_err(|e| FileError::Storage {
35 message: format!("Failed to create directory: {e}"),
36 source: Some(Box::new(e)),
37 })?;
38 }
39 tokio::fs::write(&path, data).await.map_err(|e| FileError::Storage {
40 message: format!("Failed to write file: {e}"),
41 source: Some(Box::new(e)),
42 })?;
43 Ok(key.to_string())
44 }
45
46 async fn download(&self, key: &str) -> StorageResult<Vec<u8>> {
47 let path = self.key_path(key)?;
48 tokio::fs::read(&path).await.map_err(|e| {
49 if e.kind() == std::io::ErrorKind::NotFound {
50 FileError::NotFound {
51 id: key.to_string(),
52 }
53 } else {
54 FileError::Storage {
55 message: format!("Failed to read file: {e}"),
56 source: Some(Box::new(e)),
57 }
58 }
59 })
60 }
61
62 async fn delete(&self, key: &str) -> StorageResult<()> {
63 let path = self.key_path(key)?;
64 tokio::fs::remove_file(&path).await.map_err(|e| {
65 if e.kind() == std::io::ErrorKind::NotFound {
66 FileError::NotFound {
67 id: key.to_string(),
68 }
69 } else {
70 FileError::Storage {
71 message: format!("Failed to delete file: {e}"),
72 source: Some(Box::new(e)),
73 }
74 }
75 })
76 }
77
78 async fn exists(&self, key: &str) -> StorageResult<bool> {
79 let path = self.key_path(key)?;
80 match tokio::fs::metadata(&path).await {
81 Ok(_) => Ok(true),
82 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
83 Err(e) => Err(FileError::Storage {
84 message: format!("Failed to check file existence: {e}"),
85 source: Some(Box::new(e)),
86 }),
87 }
88 }
89
90 async fn presigned_url(&self, _key: &str, _expiry: Duration) -> StorageResult<String> {
91 Err(FileError::Storage {
92 message: "Presigned URLs are not supported for local storage".to_string(),
93 source: None,
94 })
95 }
96}