Skip to content

Commit 22a7d87

Browse files
enhancement in saved filters API (#833)
- filter id generated by server - s3 calls optimized - added method for update filters
1 parent c2164eb commit 22a7d87

File tree

6 files changed

+196
-144
lines changed

6 files changed

+196
-144
lines changed

server/src/handlers/http/modal/server.rs

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -190,29 +190,33 @@ impl Server {
190190

191191
// get the filters web scope
192192
pub fn get_filters_webscope() -> Scope {
193-
web::scope("/filters").service(
194-
web::scope("/{user_id}")
195-
.service(
196-
web::resource("")
197-
.route(web::get().to(filters::list).authorize(Action::ListFilter)),
198-
)
199-
.service(
200-
web::scope("/{filter_id}").service(
201-
web::resource("")
202-
.route(web::get().to(filters::get).authorize(Action::GetFilter))
203-
.route(
204-
web::post()
205-
.to(filters::post)
206-
.authorize(Action::CreateFilter),
207-
)
208-
.route(
209-
web::delete()
210-
.to(filters::delete)
211-
.authorize(Action::DeleteFilter),
212-
),
213-
),
193+
web::scope("/filters")
194+
.service(
195+
web::resource("").route(
196+
web::post()
197+
.to(filters::post)
198+
.authorize(Action::CreateFilter),
214199
),
215-
)
200+
)
201+
.service(
202+
web::scope("/filter").service(
203+
web::resource("/{filter_id}")
204+
.route(web::get().to(filters::get).authorize(Action::GetFilter))
205+
.route(
206+
web::delete()
207+
.to(filters::delete)
208+
.authorize(Action::DeleteFilter),
209+
)
210+
.route(
211+
web::put()
212+
.to(filters::update)
213+
.authorize(Action::CreateFilter),
214+
),
215+
),
216+
)
217+
.service(web::scope("/{user_id}").service(
218+
web::resource("").route(web::get().to(filters::list).authorize(Action::ListFilter)),
219+
))
216220
}
217221

218222
// get the query factory

server/src/handlers/http/users/filters.rs

Lines changed: 49 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,19 @@ use crate::{
2020
handlers::{http::ingest::PostError, STREAM_NAME_HEADER_KEY},
2121
option::CONFIG,
2222
storage::{object_storage::filter_path, ObjectStorageError},
23-
users::filters::{Filter, FILTERS},
23+
users::filters::{Filter, CURRENT_FILTER_VERSION, FILTERS},
2424
};
2525
use actix_web::{http::header::ContentType, web, HttpRequest, HttpResponse, Responder};
2626
use bytes::Bytes;
2727
use http::StatusCode;
28-
use serde_json::{Error as SerdeError, Value as JsonValue};
28+
use rand::distributions::DistString;
29+
use serde_json::Error as SerdeError;
2930

3031
pub async fn list(req: HttpRequest) -> Result<impl Responder, FiltersError> {
3132
let user_id = req
3233
.match_info()
3334
.get("user_id")
3435
.ok_or(FiltersError::Metadata("No User Id Provided"))?;
35-
3636
let stream_name = req
3737
.headers()
3838
.iter()
@@ -41,117 +41,85 @@ pub async fn list(req: HttpRequest) -> Result<impl Responder, FiltersError> {
4141
.1
4242
.to_str()
4343
.map_err(|_| FiltersError::Metadata("Non ASCII Stream Name Provided"))?;
44+
let filters = FILTERS.list_filters_by_user_and_stream(user_id, stream_name);
4445

45-
// .users/user_id/filters/stream_name/filter_id
46-
let path = filter_path(user_id, stream_name, "");
47-
48-
let store = CONFIG.storage().get_object_store();
49-
let filters = store
50-
.get_objects(
51-
Some(&path),
52-
Box::new(|file_name: String| file_name.ends_with("json")),
53-
)
54-
.await?;
55-
56-
let mut filt = vec![];
57-
for filter in filters {
58-
filt.push(serde_json::from_slice::<JsonValue>(&filter)?)
59-
}
60-
61-
Ok((web::Json(filt), StatusCode::OK))
46+
Ok((web::Json(filters), StatusCode::OK))
6247
}
6348

6449
pub async fn get(req: HttpRequest) -> Result<impl Responder, FiltersError> {
65-
let user_id = req
66-
.match_info()
67-
.get("user_id")
68-
.ok_or(FiltersError::Metadata("No User Id Provided"))?;
69-
70-
let filt_id = req
50+
let filter_id = req
7151
.match_info()
7252
.get("filter_id")
7353
.ok_or(FiltersError::Metadata("No Filter Id Provided"))?;
7454

75-
let stream_name = req
76-
.headers()
77-
.iter()
78-
.find(|&(key, _)| key == STREAM_NAME_HEADER_KEY)
79-
.ok_or_else(|| FiltersError::Metadata("Stream Name Not Provided"))?
80-
.1
81-
.to_str()
82-
.map_err(|_| FiltersError::Metadata("Non ASCII Stream Name Provided"))?;
83-
84-
if let Some(filter) = FILTERS.find(filt_id) {
55+
if let Some(filter) = FILTERS.get_filter(filter_id) {
8556
return Ok((web::Json(filter), StatusCode::OK));
8657
}
8758

88-
// if it is not in memory go to s3
89-
let path = filter_path(user_id, stream_name, &format!("{}.json", filt_id));
90-
let resource = CONFIG
91-
.storage()
92-
.get_object_store()
93-
.get_object(&path)
94-
.await?;
59+
Err(FiltersError::Metadata("Filter Not Found"))
60+
}
9561

96-
let resource = serde_json::from_slice::<Filter>(&resource)?;
62+
pub async fn post(body: Bytes) -> Result<HttpResponse, PostError> {
63+
let filter: Filter = serde_json::from_slice(&body)?;
64+
let filter_id = rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 10);
65+
let user_id = &filter.user_id;
66+
let stream_name = &filter.stream_name;
67+
let mut cloned_filter = filter.clone();
68+
cloned_filter.filter_id = Some(filter_id.clone());
69+
cloned_filter.version = Some(CURRENT_FILTER_VERSION.to_string());
70+
FILTERS.update(&cloned_filter);
9771

98-
Ok((web::Json(resource), StatusCode::OK))
99-
}
72+
let path = filter_path(user_id, stream_name, &format!("{}.json", filter_id));
10073

101-
pub async fn post(req: HttpRequest, body: Bytes) -> Result<HttpResponse, PostError> {
102-
let user_id = req
103-
.match_info()
104-
.get("user_id")
105-
.ok_or(FiltersError::Metadata("No User Id Provided"))?;
74+
let store = CONFIG.storage().get_object_store();
75+
let filter_bytes = serde_json::to_vec(&cloned_filter)?;
76+
store.put_object(&path, Bytes::from(filter_bytes)).await?;
10677

107-
let filt_id = req
78+
Ok(HttpResponse::Ok().finish())
79+
}
80+
81+
pub async fn update(req: HttpRequest, body: Bytes) -> Result<HttpResponse, PostError> {
82+
let filter_id = req
10883
.match_info()
10984
.get("filter_id")
11085
.ok_or(FiltersError::Metadata("No Filter Id Provided"))?;
86+
let filter = FILTERS
87+
.get_filter(filter_id)
88+
.ok_or(FiltersError::Metadata("Filter Not Found"))?;
89+
let user_id = &filter.user_id;
90+
let stream_name = &filter.stream_name;
11191

112-
let stream_name = req
113-
.headers()
114-
.iter()
115-
.find(|&(key, _)| key == STREAM_NAME_HEADER_KEY)
116-
.ok_or_else(|| FiltersError::Metadata("Stream Name Not Provided"))?
117-
.1
118-
.to_str()
119-
.map_err(|_| FiltersError::Metadata("Non ASCII Stream Name Provided"))?;
92+
let mut cloned_filter: Filter = serde_json::from_slice(&body)?;
93+
cloned_filter.filter_id = Some(filter_id.to_string());
94+
cloned_filter.version = Some(CURRENT_FILTER_VERSION.to_string());
95+
FILTERS.update(&cloned_filter);
12096

121-
let path = filter_path(user_id, stream_name, &format!("{}.json", filt_id));
122-
let filter: Filter = serde_json::from_slice(&body)?;
123-
FILTERS.update(filter);
97+
let path = filter_path(user_id, stream_name, &format!("{}.json", filter_id));
12498

12599
let store = CONFIG.storage().get_object_store();
126-
store.put_object(&path, body).await?;
100+
let filter_bytes = serde_json::to_vec(&cloned_filter)?;
101+
store.put_object(&path, Bytes::from(filter_bytes)).await?;
127102

128103
Ok(HttpResponse::Ok().finish())
129104
}
130105

131106
pub async fn delete(req: HttpRequest) -> Result<HttpResponse, PostError> {
132-
let user_id = req
133-
.match_info()
134-
.get("user_id")
135-
.ok_or(FiltersError::Metadata("No User Id Provided"))?;
136-
137-
let filt_id = req
107+
let filter_id = req
138108
.match_info()
139109
.get("filter_id")
140110
.ok_or(FiltersError::Metadata("No Filter Id Provided"))?;
111+
let filter = FILTERS
112+
.get_filter(filter_id)
113+
.ok_or(FiltersError::Metadata("Filter Not Found"))?;
114+
let stream_name = &filter.stream_name;
115+
let user_id = &filter.user_id;
141116

142-
let stream_name = req
143-
.headers()
144-
.iter()
145-
.find(|&(key, _)| key == STREAM_NAME_HEADER_KEY)
146-
.ok_or_else(|| FiltersError::Metadata("Stream Name Not Provided"))?
147-
.1
148-
.to_str()
149-
.map_err(|_| FiltersError::Metadata("Non ASCII Stream Name Provided"))?;
150-
151-
let path = filter_path(user_id, stream_name, &format!("{}.json", filt_id));
117+
let path = filter_path(user_id, stream_name, &format!("{}.json", filter_id));
152118
let store = CONFIG.storage().get_object_store();
153119
store.delete_object(&path).await?;
154120

121+
FILTERS.delete_filter(filter_id);
122+
155123
Ok(HttpResponse::Ok().finish())
156124
}
157125

@@ -161,7 +129,7 @@ pub enum FiltersError {
161129
ObjectStorage(#[from] ObjectStorageError),
162130
#[error("Serde Error: {0}")]
163131
Serde(#[from] SerdeError),
164-
#[error("Cannot perform this operation: {0}")]
132+
#[error("Operation cannot be performed: {0}")]
165133
Metadata(&'static str),
166134
}
167135

server/src/storage/localfs.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,37 @@ impl ObjectStorage for LocalFS {
350350
Ok(dirs)
351351
}
352352

353+
async fn get_all_saved_filters(&self) -> Result<Vec<Bytes>, ObjectStorageError> {
354+
let mut filters = vec![];
355+
let users_root_path = self.root.join(USERS_ROOT_DIR);
356+
let directories = ReadDirStream::new(fs::read_dir(&users_root_path).await?);
357+
let users: Vec<DirEntry> = directories.try_collect().await?;
358+
for user in users {
359+
if !user.path().is_dir() {
360+
continue;
361+
}
362+
let stream_root_path = users_root_path.join(user.path()).join("filters");
363+
let directories = ReadDirStream::new(fs::read_dir(&stream_root_path).await?);
364+
let streams: Vec<DirEntry> = directories.try_collect().await?;
365+
for stream in streams {
366+
if !stream.path().is_dir() {
367+
continue;
368+
}
369+
let filters_path = users_root_path
370+
.join(user.path())
371+
.join("filters")
372+
.join(stream.path());
373+
let directories = ReadDirStream::new(fs::read_dir(&filters_path).await?);
374+
let filters_files: Vec<DirEntry> = directories.try_collect().await?;
375+
for filter in filters_files {
376+
let file = fs::read(filter.path()).await?;
377+
filters.push(file.into());
378+
}
379+
}
380+
}
381+
Ok(filters)
382+
}
383+
353384
async fn list_dates(&self, stream_name: &str) -> Result<Vec<String>, ObjectStorageError> {
354385
let path = self.root.join(stream_name);
355386
let directories = ReadDirStream::new(fs::read_dir(&path).await?);

server/src/storage/object_storage.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub trait ObjectStorage: Sync + 'static {
8484
async fn list_streams(&self) -> Result<Vec<LogStream>, ObjectStorageError>;
8585
async fn list_old_streams(&self) -> Result<Vec<LogStream>, ObjectStorageError>;
8686
async fn list_dirs(&self) -> Result<Vec<String>, ObjectStorageError>;
87+
async fn get_all_saved_filters(&self) -> Result<Vec<Bytes>, ObjectStorageError>;
8788
async fn list_dates(&self, stream_name: &str) -> Result<Vec<String>, ObjectStorageError>;
8889
async fn upload_file(&self, key: &str, path: &Path) -> Result<(), ObjectStorageError>;
8990
async fn delete_object(&self, path: &RelativePath) -> Result<(), ObjectStorageError>;

server/src/storage/s3.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,50 @@ impl ObjectStorage for S3 {
640640
.collect::<Vec<_>>())
641641
}
642642

643+
async fn get_all_saved_filters(&self) -> Result<Vec<Bytes>, ObjectStorageError> {
644+
let mut filters = vec![];
645+
let users_root_path = object_store::path::Path::from(USERS_ROOT_DIR);
646+
let resp = self
647+
.client
648+
.list_with_delimiter(Some(&users_root_path))
649+
.await?;
650+
651+
let users = resp
652+
.common_prefixes
653+
.iter()
654+
.flat_map(|path| path.parts())
655+
.filter(|name| name.as_ref() != USERS_ROOT_DIR)
656+
.map(|name| name.as_ref().to_string())
657+
.collect::<Vec<_>>();
658+
for user in users {
659+
let user_filters_path = object_store::path::Path::from(format!(
660+
"{}/{}/{}",
661+
USERS_ROOT_DIR, user, "filters"
662+
));
663+
let resp = self
664+
.client
665+
.list_with_delimiter(Some(&user_filters_path))
666+
.await?;
667+
let streams = resp
668+
.common_prefixes
669+
.iter()
670+
.filter(|name| name.as_ref() != USERS_ROOT_DIR)
671+
.map(|name| name.as_ref().to_string())
672+
.collect::<Vec<_>>();
673+
for stream in streams {
674+
let filters_path = RelativePathBuf::from(&stream);
675+
let filter_bytes = self
676+
.get_objects(
677+
Some(&filters_path),
678+
Box::new(|file_name| file_name.ends_with(".json")),
679+
)
680+
.await?;
681+
filters.extend(filter_bytes);
682+
}
683+
}
684+
Ok(filters)
685+
}
686+
643687
fn get_bucket_name(&self) -> String {
644688
self.bucket.clone()
645689
}

0 commit comments

Comments
 (0)