Skip to content

Commit a2d5bc7

Browse files
authored
200 character limit file uploads, always return a JSON response, rest… (#385)
* 200 character limit file uploads, always return a JSON response, restructure company, add validation for file upload ids and add endpoint for getting temp files
1 parent 071a25f commit a2d5bc7

17 files changed

+331
-172
lines changed

src/constants.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub const RECOURSE_DEADLINE_SECONDS: u64 = 86400 * 2; // 2 days
1818

1919
// Validation
2020
pub const MAX_FILE_SIZE_BYTES: usize = 1_000_000; // ~1 MB
21-
pub const MAX_FILE_NAME_CHARACTERS: usize = 50;
21+
pub const MAX_FILE_NAME_CHARACTERS: usize = 200;
2222
pub const VALID_FILE_MIME_TYPES: [&str; 3] = ["image/jpeg", "image/png", "application/pdf"];
2323
pub const VALID_CURRENCIES: [&str; 2] = ["sat", "crsat"];
2424

src/service/bill_service.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ pub enum Error {
158158
#[error("Bill is in recourse and waiting for payment")]
159159
BillIsInRecourseAndWaitingForPayment,
160160

161+
/// error returned if the given file upload id is not a temp file we have
162+
#[error("No file found for file upload id")]
163+
NoFileForFileUploadId,
164+
161165
/// errors that stem from interacting with a blockchain
162166
#[error("Blockchain error: {0}")]
163167
Blockchain(#[from] blockchain::Error),
@@ -209,6 +213,7 @@ impl<'r, 'o: 'r> Responder<'r, 'o> for Error {
209213
| Error::CallerIsNotRecoursee
210214
| Error::RequestAlreadyRejected
211215
| Error::CallerIsNotHolder
216+
| Error::NoFileForFileUploadId
212217
| Error::InvalidOperation => {
213218
let body =
214219
ErrorResponse::new("bad_request", self.to_string(), 400).to_json_string();
@@ -2030,7 +2035,8 @@ impl BillServiceApi for BillService {
20302035
let files = self
20312036
.file_upload_store
20322037
.read_temp_upload_files(upload_id)
2033-
.await?;
2038+
.await
2039+
.map_err(|_| Error::NoFileForFileUploadId)?;
20342040
for (file_name, file_bytes) in files {
20352041
bill_files.push(
20362042
self.encrypt_and_save_uploaded_file(

src/service/company_service.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ impl CompanyService {
143143
let files = self
144144
.file_upload_store
145145
.read_temp_upload_files(upload_id)
146-
.await?;
146+
.await
147+
.map_err(|_| crate::service::Error::NoFileForFileUploadId)?;
147148
if !files.is_empty() {
148149
let (file_name, file_bytes) = &files[0];
149150
let file = self

src/service/contact_service.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ impl ContactService {
106106
let files = self
107107
.file_upload_store
108108
.read_temp_upload_files(upload_id)
109-
.await?;
109+
.await
110+
.map_err(|_| crate::service::Error::NoFileForFileUploadId)?;
110111
if !files.is_empty() {
111112
let (file_name, file_bytes) = &files[0];
112113
let file = self

src/service/file_upload_service.rs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ pub trait FileUploadServiceApi: Send + Sync {
1212
/// validates the given uploaded file
1313
async fn validate_attached_file(&self, file: &dyn util::file::UploadFileHandler) -> Result<()>;
1414

15-
/// uploads files for use in a bill
15+
/// uploads files
1616
async fn upload_files(
1717
&self,
1818
files: Vec<&dyn util::file::UploadFileHandler>,
1919
) -> Result<UploadFilesResponse>;
20+
21+
/// returns a temp upload file
22+
async fn get_temp_file(&self, file_upload_id: &str) -> Result<Option<(String, Vec<u8>)>>;
2023
}
2124

2225
#[derive(Clone)]
@@ -101,6 +104,20 @@ impl FileUploadServiceApi for FileUploadService {
101104
}
102105
Ok(UploadFilesResponse { file_upload_id })
103106
}
107+
108+
async fn get_temp_file(&self, file_upload_id: &str) -> Result<Option<(String, Vec<u8>)>> {
109+
let mut files = self
110+
.file_upload_store
111+
.read_temp_upload_files(file_upload_id)
112+
.await
113+
.map_err(|_| crate::service::Error::NoFileForFileUploadId)?;
114+
// return the first file in the folder
115+
if !files.is_empty() {
116+
let (file_name, file_bytes) = files.remove(0);
117+
return Ok(Some((file_name, file_bytes)));
118+
}
119+
Ok(None)
120+
}
104121
}
105122

106123
#[cfg(test)]
@@ -333,4 +350,51 @@ mod tests {
333350

334351
assert!(res.is_ok());
335352
}
353+
354+
#[tokio::test]
355+
async fn get_temp_file_baseline() {
356+
let mut storage = MockFileUploadStoreApi::new();
357+
storage.expect_read_temp_upload_files().returning(|_| {
358+
Ok(vec![(
359+
"some_file".to_string(),
360+
"hello_world".as_bytes().to_vec(),
361+
)])
362+
});
363+
let service = get_service(storage);
364+
365+
let res = service.get_temp_file("1234").await;
366+
assert!(res.is_ok());
367+
assert_eq!(
368+
res.unwrap(),
369+
Some(("some_file".to_string(), "hello_world".as_bytes().to_vec()))
370+
);
371+
}
372+
373+
#[tokio::test]
374+
async fn get_temp_file_no_file() {
375+
let mut storage = MockFileUploadStoreApi::new();
376+
storage
377+
.expect_read_temp_upload_files()
378+
.returning(|_| Ok(vec![]));
379+
let service = get_service(storage);
380+
381+
let res = service.get_temp_file("1234").await;
382+
assert!(res.is_ok());
383+
assert_eq!(res.unwrap(), None);
384+
}
385+
386+
#[tokio::test]
387+
async fn get_temp_file_err() {
388+
let mut storage = MockFileUploadStoreApi::new();
389+
storage.expect_read_temp_upload_files().returning(|_| {
390+
Err(persistence::Error::Io(std::io::Error::new(
391+
std::io::ErrorKind::Other,
392+
"test error",
393+
)))
394+
});
395+
let service = get_service(storage);
396+
397+
let res = service.get_temp_file("1234").await;
398+
assert!(res.is_err());
399+
}
336400
}

src/service/identity_service.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ impl IdentityService {
122122
let files = self
123123
.file_upload_store
124124
.read_temp_upload_files(upload_id)
125-
.await?;
125+
.await
126+
.map_err(|_| crate::service::Error::NoFileForFileUploadId)?;
126127
if !files.is_empty() {
127128
let (file_name, file_bytes) = &files[0];
128129
let file = self

src/service/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ pub enum Error {
8383
/// std io
8484
#[error("Io error: {0}")]
8585
Io(#[from] std::io::Error),
86+
87+
/// error returned if the given file upload id is not a temp file we have
88+
#[error("No file found for file upload id")]
89+
NoFileForFileUploadId,
8690
}
8791

8892
/// Map from service errors directly to rocket status codes. This allows us to
@@ -91,6 +95,15 @@ pub enum Error {
9195
impl<'r, 'o: 'r> Responder<'r, 'o> for Error {
9296
fn respond_to(self, req: &rocket::Request) -> rocket::response::Result<'o> {
9397
match self {
98+
Error::NoFileForFileUploadId => {
99+
let body =
100+
ErrorResponse::new("bad_request", self.to_string(), 400).to_json_string();
101+
Response::build()
102+
.status(Status::BadRequest)
103+
.header(ContentType::JSON)
104+
.sized_body(body.len(), Cursor::new(body))
105+
.ok()
106+
}
94107
// for now, DHT errors are InternalServerError
95108
Error::Dht(e) => {
96109
error!("{e}");

src/util/file.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@ impl UploadFileHandler for TempFile<'_> {
5050
}
5151
}
5252

53+
pub fn validate_file_upload_id(file_upload_id: &Option<String>) -> crate::service::Result<()> {
54+
if let Some(ref id) = file_upload_id {
55+
if id.is_empty() {
56+
return Err(crate::service::Error::Validation(
57+
"Empty string is not a valid file upload id".to_string(),
58+
));
59+
}
60+
}
61+
Ok(())
62+
}
63+
5364
/// Function to sanitize the filename by removing unwanted characters.
5465
pub fn sanitize_filename(filename: &str) -> String {
5566
filename
@@ -146,4 +157,10 @@ mod tests {
146157
String::from("file_name_00000000-0000-0000-0000-000000000000.tar.gz")
147158
);
148159
}
160+
161+
#[test]
162+
fn validate_file_upload_id_baseline() {
163+
assert!(validate_file_upload_id(&Some(String::from(""))).is_err(),);
164+
assert!(validate_file_upload_id(&Some(String::from("test"))).is_ok(),);
165+
}
149166
}

src/web/data.rs

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::service::contact_service::LightIdentityPublicDataWithAddress;
1+
use crate::service::contact_service::{ContactType, LightIdentityPublicDataWithAddress};
22
use crate::service::identity_service::IdentityType;
33
use crate::service::{
44
bill_service::LightBitcreditBillToReturn,
@@ -13,6 +13,18 @@ use serde::{Deserialize, Serialize};
1313
use std::fmt;
1414
use utoipa::ToSchema;
1515

16+
/// A dummy response type signaling success of a request
17+
#[derive(Debug, Serialize, ToSchema)]
18+
pub struct SuccessResponse {
19+
pub success: bool,
20+
}
21+
22+
impl SuccessResponse {
23+
pub fn new() -> Self {
24+
Self { success: true }
25+
}
26+
}
27+
1628
#[derive(Debug, Serialize, ToSchema)]
1729
pub struct EndorsementsResponse {
1830
pub endorsements: Vec<Endorsement>,
@@ -450,3 +462,68 @@ pub struct SeedPhrase {
450462
/// The seed phrase of the current private key
451463
pub seed_phrase: String,
452464
}
465+
466+
// Company
467+
#[derive(Debug, Serialize, Deserialize, Clone)]
468+
pub struct CreateCompanyPayload {
469+
pub name: String,
470+
pub country_of_registration: String,
471+
pub city_of_registration: String,
472+
#[serde(flatten)]
473+
pub postal_address: PostalAddress,
474+
pub email: String,
475+
pub registration_number: String,
476+
pub registration_date: String,
477+
pub proof_of_registration_file_upload_id: Option<String>,
478+
pub logo_file_upload_id: Option<String>,
479+
}
480+
481+
#[derive(Debug, Serialize, Deserialize, Clone)]
482+
pub struct EditCompanyPayload {
483+
pub id: String,
484+
pub name: Option<String>,
485+
pub email: Option<String>,
486+
#[serde(flatten)]
487+
pub postal_address: OptionalPostalAddress,
488+
pub logo_file_upload_id: Option<String>,
489+
}
490+
491+
#[derive(Debug, Serialize, Deserialize, Clone)]
492+
pub struct AddSignatoryPayload {
493+
pub id: String,
494+
pub signatory_node_id: String,
495+
}
496+
497+
#[derive(Debug, Serialize, Deserialize, Clone)]
498+
pub struct RemoveSignatoryPayload {
499+
pub id: String,
500+
pub signatory_node_id: String,
501+
}
502+
503+
#[derive(Debug, Serialize, Deserialize, Clone)]
504+
pub struct ListSignatoriesResponse {
505+
pub signatories: Vec<SignatoryResponse>,
506+
}
507+
508+
#[derive(Debug, Serialize, Deserialize, Clone)]
509+
pub struct SignatoryResponse {
510+
#[serde(rename = "type")]
511+
pub t: ContactType,
512+
pub node_id: String,
513+
pub name: String,
514+
#[serde(flatten)]
515+
pub postal_address: PostalAddress,
516+
pub avatar_file: Option<File>,
517+
}
518+
519+
impl From<Contact> for SignatoryResponse {
520+
fn from(value: Contact) -> Self {
521+
Self {
522+
t: value.t,
523+
node_id: value.node_id,
524+
name: value.name,
525+
postal_address: value.postal_address,
526+
avatar_file: value.avatar_file,
527+
}
528+
}
529+
}

0 commit comments

Comments
 (0)