readme & bugfixes

master
plazmoid 1 month ago
parent bcfdd57795
commit d434739816
  1. 41
      README.md
  2. 8
      run.py
  3. 37
      src/handlers.rs
  4. 28
      src/repo.rs

@ -0,0 +1,41 @@
# Тестовое задание для Stilsoft
Сервис для асинхронной обработки ссылок. Написан на tokio+axum+sled.
Для настройки сервиса используются переменные окружения в файле `.env`. Экспортировать их в терминал не нужно, они считаются автоматически.
Запуск осуществляется через `cargo run`.
Для отправки HTTP-запросов к сервису можно использовать дополнительную утилиту run.py:
```
./run.py list <url1> <url2>
./run.py upload <url1> <url2>
./run.py del <url1> <url2>
```
Схема:
Получение списка сохранённых ссылок:
```
Без фильтров:
GET localhost:8000/url
С фильтром:
POST localhost:8000/url
body: {"urls": [url1, url2]}
```
Загрузка ссылок:
```
POST localhost:8000/url/upload
body: {"urls": [url1, url2]}
```
Удаление сохранённых ссылок:
```
Удаление всех ссылок:
GET localhost:8000/url/delete
Удаление заданных ссылок:
POST localhost:8000/url
body: {"urls": [url1, url2]}
```

@ -1,5 +1,6 @@
#!/usr/bin/env python3
import sys
import json
import requests
BASE_URL = "http://localhost:8000"
@ -27,13 +28,16 @@ def main():
elif action == "upload":
result = requests.post(f"{BASE_URL}/urls/upload", json={"urls": urls})
elif action == "del":
result = requests.get(f"{BASE_URL}/urls/delete", json={"urls": urls})
if len(urls) == 0:
result = requests.get(f"{BASE_URL}/urls/delete")
else:
result = requests.post(f"{BASE_URL}/urls/delete", json={"urls": urls})
else:
usage()
if result.status_code == 200:
if len(result.text) > 0:
print(result.json())
print(json.dumps(result.json()))
else:
print("200 OK")
else:

@ -20,7 +20,7 @@ pub async fn get_urls<R: Repo>(
.collect::<HashSet<_>>()
});
let r_state = state.read().await;
let result = r_state.repo.list_urls(urls.as_ref())?;
let result = r_state.repo.get_urls(urls.as_ref())?;
Ok(Json(result))
}
@ -29,7 +29,31 @@ pub async fn upload_urls<R: Repo>(
State(state): State<AppStateShared<R>>,
Json(payload): Json<UrlRequest>,
) -> AppResult<impl IntoResponse> {
let urls = payload.urls;
let mut successes = vec![];
let mut failures = vec![];
let urls = {
let r_state = state.read().await;
let cached_urls = r_state.repo.get_urls(None)?;
payload
.urls
.into_iter()
.filter(|url| match cached_urls.get(&url.to_string()) {
Some(val) => {
info!("{} is cached, not fetching", url);
successes.push(UrlResult {
status: None,
url: url.clone(),
content_length: *val,
error_msg: None,
});
false
}
None => true,
})
.collect::<HashSet<_>>()
};
let tasks = {
urls.into_iter()
@ -68,9 +92,6 @@ pub async fn upload_urls<R: Repo>(
.collect::<Vec<_>>()
};
let mut successes = vec![];
let mut failures = vec![];
for task in tasks {
let result = task.await.unwrap();
let status = result.status.unwrap_or(0);
@ -92,7 +113,7 @@ pub async fn upload_urls<R: Repo>(
pub async fn delete_urls<R: Repo>(
State(state): State<AppStateShared<R>>,
req: Option<Json<UrlRequest>>,
) -> AppResult<()> {
) -> AppResult<impl IntoResponse> {
let urls = req.map(|urls| {
urls.0
.urls
@ -101,7 +122,7 @@ pub async fn delete_urls<R: Repo>(
.collect::<HashSet<_>>()
});
state.write().await.repo.delete_urls(urls.as_ref())?;
let deleted = state.write().await.repo.delete_urls(urls.as_ref())?;
Ok(())
Ok(deleted.to_string())
}

@ -10,9 +10,9 @@ use crate::{
pub trait Repo: Send + Sync + 'static {
fn insert_urls(&self, urls: &[UrlResult]) -> AppResult<()>;
fn delete_urls(&self, urls: Option<&HashSet<String>>) -> AppResult<()>;
fn delete_urls(&self, urls: Option<&HashSet<String>>) -> AppResult<usize>;
fn list_urls(&self, urls: Option<&HashSet<String>>) -> AppResult<HashMap<String, usize>>;
fn get_urls(&self, urls: Option<&HashSet<String>>) -> AppResult<HashMap<String, usize>>;
}
impl Repo for Db {
@ -27,20 +27,30 @@ impl Repo for Db {
self.apply_batch(batch).map_err(AppError::from)
}
fn delete_urls(&self, urls: Option<&HashSet<String>>) -> AppResult<()> {
fn delete_urls(&self, urls: Option<&HashSet<String>>) -> AppResult<usize> {
let mut deleted = 0;
match urls {
Some(urls) => {
for url in urls {
self.remove(url.as_bytes())?;
info!("deleting {url}");
let del = self.remove(url.as_bytes())?;
if del.is_some() {
deleted += 1
}
}
Ok(())
}
None => self.clear().map_err(AppError::from),
}
None => {
info!("deleting all urls");
deleted = self.len();
self.clear()?;
}
};
Ok(deleted)
}
fn list_urls(&self, urls: Option<&HashSet<String>>) -> AppResult<HashMap<String, usize>> {
fn get_urls(&self, urls: Option<&HashSet<String>>) -> AppResult<HashMap<String, usize>> {
match urls {
Some(urls) => {
let mut result = HashMap::new();

Loading…
Cancel
Save