Skip to content

Commit

Permalink
Merge branch 'master' into add-only-with-attachments
Browse files Browse the repository at this point in the history
  • Loading branch information
joe-prosser authored Aug 5, 2024
2 parents 7b3a80c + 9afc496 commit e1f6a53
Show file tree
Hide file tree
Showing 12 changed files with 125 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Unreleased
- Add `only-with-attachment` filter on get comments
- Add ability to get email by id
- Add ability to upload attachment content for comments

# v0.29.0
Expand Down
23 changes: 20 additions & 3 deletions api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use resources::{
SummaryResponse,
},
documents::{Document, SyncRawEmailsRequest, SyncRawEmailsResponse},
email::{Email, GetEmailResponse},
integration::{
GetIntegrationResponse, GetIntegrationsResponse, Integration, NewIntegration,
PostIntegrationRequest, PostIntegrationResponse, PutIntegrationRequest,
Expand Down Expand Up @@ -135,7 +136,7 @@ pub use crate::{
Stream, StreamException, StreamExceptionMetadata,
},
user::{
Email, GlobalPermission, Id as UserId, Identifier as UserIdentifier,
Email as UserEmail, GlobalPermission, Id as UserId, Identifier as UserIdentifier,
ModifiedPermissions, NewUser, ProjectPermission, UpdateUser, User, Username,
},
},
Expand Down Expand Up @@ -242,6 +243,11 @@ pub struct GetCommentQuery {
pub include_markup: bool,
}

#[derive(Serialize)]
pub struct GetEmailQuery {
pub id: String,
}

impl Client {
/// Create a new API client.
pub fn new(config: Config) -> Result<Client> {
Expand Down Expand Up @@ -424,7 +430,18 @@ impl Client {
CommentsIter::new(self, source_name, page_size, timerange)
}

/// Get a page of comments from a source.
/// Get a single of email from a bucket.
pub fn get_email(&self, bucket_name: &BucketFullName, id: EmailId) -> Result<Vec<Email>> {
let query_params = GetEmailQuery { id: id.0 };
Ok(self
.get_query::<_, _, GetEmailResponse>(
self.endpoints.get_emails(bucket_name)?,
Some(&query_params),
)?
.emails)
}

/// Get a page of emails from a bucket.
pub fn get_emails_iter_page(
&self,
bucket_name: &BucketFullName,
Expand Down Expand Up @@ -1517,7 +1534,7 @@ impl<'a> EmailsIter<'a> {
}

impl<'a> Iterator for EmailsIter<'a> {
type Item = Result<Vec<NewEmail>>;
type Item = Result<Vec<Email>>;

fn next(&mut self) -> Option<Self::Item> {
if self.done {
Expand Down
12 changes: 12 additions & 0 deletions api/src/resources/attachments.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::AttachmentReference;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
Expand All @@ -7,3 +8,14 @@ pub struct ContentHash(pub String);
pub struct UploadAttachmentResponse {
pub content_hash: ContentHash,
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct AttachmentMetadata {
pub name: String,
pub size: u64,
pub content_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub attachment_reference: Option<AttachmentReference>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content_hash: Option<ContentHash>,
}
6 changes: 3 additions & 3 deletions api/src/resources/audit.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use chrono::{DateTime, Utc};

use crate::{Continuation, DatasetId, DatasetName, Email, ProjectName, UserId, Username};
use crate::{Continuation, DatasetId, DatasetName, ProjectName, UserEmail, UserId, Username};

use super::{comment::CommentTimestampFilter, project::Id as ProjectId};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -45,7 +45,7 @@ pub struct AuditEvent {

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PrintableAuditEvent {
pub actor_email: Email,
pub actor_email: UserEmail,
pub actor_tenant_name: AuditTenantName,
pub event_type: AuditEventType,
pub dataset_names: Vec<DatasetName>,
Expand Down Expand Up @@ -79,7 +79,7 @@ struct AuditTenant {
#[derive(Debug, Clone, Deserialize, Serialize)]
struct AuditUser {
display_name: Username,
email: Email,
email: UserEmail,
id: UserId,
tenant_id: AuditTenantId,
username: Username,
Expand Down
14 changes: 1 addition & 13 deletions api/src/resources/comment.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{
error::{Error, Result},
resources::attachments::AttachmentMetadata,
resources::entity_def::Name as EntityName,
resources::label_def::Name as LabelName,
resources::label_group::Name as LabelGroupName,
Expand All @@ -23,8 +24,6 @@ use std::{
str::FromStr,
};

use super::attachments::ContentHash;

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Id(pub String);

Expand Down Expand Up @@ -343,17 +342,6 @@ pub enum Sentiment {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct AttachmentReference(pub String);

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct AttachmentMetadata {
pub name: String,
pub size: u64,
pub content_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub attachment_reference: Option<AttachmentReference>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content_hash: Option<ContentHash>,
}

#[derive(Debug, Clone, PartialEq, Default, Eq)]
pub struct PropertyMap(HashMap<String, PropertyValue>);

Expand Down
6 changes: 5 additions & 1 deletion api/src/resources/documents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{CommentId, NewComment, PropertyMap, TransformTag};
use serde::{Deserialize, Serialize, Serializer};
use std::collections::{BTreeMap, HashMap};

use super::email::AttachmentMetadata;
use super::attachments::AttachmentMetadata;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Document {
Expand Down Expand Up @@ -100,11 +100,15 @@ From: [email protected]"#
name: "hello.pdf".to_string(),
size: 1000,
content_type: "pdf".to_string(),
attachment_reference: None,
content_hash: None,
},
AttachmentMetadata {
name: "world.csv".to_string(),
size: 9999,
content_type: "csv".to_string(),
attachment_reference: None,
content_hash: None,
},
],
body: RawEmailBody::Plain("Hello world".to_string()),
Expand Down
39 changes: 34 additions & 5 deletions api/src/resources/email.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
use crate::Error;
use std::str::FromStr;

use crate::{ReducibleResponse, SplittableRequest};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use crate::resources::attachments::AttachmentMetadata;

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub struct Mailbox(pub String);

Expand All @@ -11,6 +18,14 @@ pub struct MimeContent(pub String);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub struct Id(pub String);

impl FromStr for Id {
type Err = Error;

fn from_str(string: &str) -> Result<Self> {
Ok(Self(string.to_owned()))
}
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct EmailMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
Expand All @@ -26,16 +41,25 @@ pub struct EmailMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub conversation_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conversation_topic: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_read: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub folder: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub received_at: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct AttachmentMetadata {
pub name: String,
pub size: u64,
pub content_type: String,
pub struct Email {
pub id: Id,
pub mailbox: Mailbox,
pub timestamp: DateTime<Utc>,
pub mime_content: MimeContent,
pub metadata: Option<EmailMetadata>,
pub attachments: Vec<AttachmentMetadata>,
pub created_at: Option<DateTime<Utc>>,
pub updated_at: Option<DateTime<Utc>>,
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
Expand Down Expand Up @@ -80,6 +104,11 @@ pub struct Continuation(pub String);

#[derive(Debug, Clone, Deserialize)]
pub struct EmailsIterPage {
pub emails: Vec<NewEmail>,
pub emails: Vec<Email>,
pub continuation: Option<Continuation>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct GetEmailResponse {
pub emails: Vec<Email>,
}
4 changes: 2 additions & 2 deletions cli/src/commands/create/comments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use anyhow::{anyhow, ensure, Context, Result};
use colored::Colorize;
use log::{debug, info};
use reinfer_client::{
resources::comment::AttachmentMetadata, Client, CommentId, DatasetFullName, DatasetIdentifier,
NewAnnotatedComment, NewComment, Source, SourceId, SourceIdentifier,
resources::attachments::AttachmentMetadata, Client, CommentId, DatasetFullName,
DatasetIdentifier, NewAnnotatedComment, NewComment, Source, SourceId, SourceIdentifier,
};
use scoped_threadpool::Pool;
use std::{
Expand Down
4 changes: 2 additions & 2 deletions cli/src/commands/create/user.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::{Context, Result};
use reinfer_client::{
Client, Email, GlobalPermission, NewUser, ProjectName, ProjectPermission, Username,
Client, GlobalPermission, NewUser, ProjectName, ProjectPermission, UserEmail, Username,
};
use std::collections::hash_map::HashMap;
use structopt::StructOpt;
Expand All @@ -15,7 +15,7 @@ pub struct CreateUserArgs {

#[structopt(name = "email")]
/// Email address of the new user
email: Email,
email: UserEmail,

#[structopt(long = "global-permissions")]
/// Global permissions to give to the new user
Expand Down
31 changes: 29 additions & 2 deletions cli/src/commands/get/emails.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::{Context, Result};

use colored::Colorize;
use reinfer_client::{resources::bucket_statistics::Count, BucketIdentifier, Client};
use reinfer_client::{resources::bucket_statistics::Count, BucketIdentifier, Client, EmailId};
use std::{
fs::File,
io::{self, BufWriter, Write},
Expand All @@ -27,10 +27,14 @@ pub struct GetManyEmailsArgs {
#[structopt(short = "f", long = "file", parse(from_os_str))]
/// Path where to write comments as JSON. If not specified, stdout will be used.
path: Option<PathBuf>,

#[structopt(name = "id")]
/// Id of specific email to return
id: Option<EmailId>,
}

pub fn get_many(client: &Client, args: &GetManyEmailsArgs) -> Result<()> {
let GetManyEmailsArgs { bucket, path } = args;
let GetManyEmailsArgs { bucket, path, id } = args;

let file = match path {
Some(path) => Some(
Expand All @@ -41,13 +45,36 @@ pub fn get_many(client: &Client, args: &GetManyEmailsArgs) -> Result<()> {
None => None,
};

if let Some(id) = id {
if let Some(file) = file {
return download_email(client, bucket.clone(), id.clone(), file);
} else {
return download_email(client, bucket.clone(), id.clone(), io::stdout().lock());
}
}

if let Some(file) = file {
download_emails(client, bucket.clone(), file)
} else {
download_emails(client, bucket.clone(), io::stdout().lock())
}
}

fn download_email(
client: &Client,
bucket_identifier: BucketIdentifier,
id: EmailId,
mut writer: impl Write,
) -> Result<()> {
let bucket = client
.get_bucket(bucket_identifier)
.context("Operation to get bucket has failed.")?;

let response = client.get_email(&bucket.full_name(), id)?;

print_resources_as_json(response, &mut writer)
}

fn download_emails(
client: &Client,
bucket_identifier: BucketIdentifier,
Expand Down
10 changes: 9 additions & 1 deletion cli/src/commands/parse/emls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ use crate::commands::{
ensure_uip_user_consents_to_ai_unit_charge,
parse::{get_files_in_directory, get_progress_bar, Statistics},
};
use reinfer_client::{resources::email::AttachmentMetadata, BucketIdentifier, Client, NewEmail};
use reinfer_client::{
resources::attachments::AttachmentMetadata, BucketIdentifier, Client, NewEmail,
};
use structopt::StructOpt;

use super::upload_batch_of_new_emails;
Expand Down Expand Up @@ -174,6 +176,8 @@ fn read_eml_to_new_email(path: &PathBuf) -> Result<NewEmail> {
name: attachment_filename.to_owned(),
size,
content_type: format!(".{}", extension.to_string_lossy()),
attachment_reference: None,
content_hash: None,
});
}
}
Expand Down Expand Up @@ -222,11 +226,15 @@ mod tests {
name: "hello.txt".to_string(),
size: 176,
content_type: ".txt".to_string(),
attachment_reference: None,
content_hash: None,
},
AttachmentMetadata {
name: "world.pdf".to_string(),
size: 7476,
content_type: ".pdf".to_string(),
attachment_reference: None,
content_hash: None,
},
];
let expected_mime_content = include_str!("../../../tests/samples/test.eml");
Expand Down
Loading

0 comments on commit e1f6a53

Please sign in to comment.