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