{
+ let mut handlebars = Handlebars::new();
+ handlebars.register_partial("base_partial", "{{> base}}")?;
+ handlebars.register_template_file("base", templates_dir.join("base.hbs"))?;
+ handlebars
+ .register_template_file("attachment", templates_dir.join("attachment.hbs"))?;
+
+ Ok(handlebars.render(template.as_ref(), data)?)
+}
+
+// TODO: Write meaningful tests
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn default_templates_dir() -> PathBuf {
+ PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("templates")
+ }
+
+ #[test]
+ fn render_template_attachment() {
+ let data = serde_json::json!({
+ "title": "Stump Attachment",
+ });
+
+ let rendered =
+ render_template(EmailTemplate::Attachment, &data, default_templates_dir())
+ .unwrap();
+
+ dbg!(&rendered);
+
+ assert!(rendered.contains("Stump Attachment"));
+ }
+}
diff --git a/crates/email/templates/attachment.hbs b/crates/email/templates/attachment.hbs
new file mode 100644
index 000000000..044ba11a1
--- /dev/null
+++ b/crates/email/templates/attachment.hbs
@@ -0,0 +1,7 @@
+{{#*inline "page"}}
+{{!-- TODO: design email --}}
+
+ You have a new attachment from Stump!
+
+{{/inline}}
+{{> base}}
\ No newline at end of file
diff --git a/crates/email/templates/base.hbs b/crates/email/templates/base.hbs
new file mode 100644
index 000000000..396cb1ad4
--- /dev/null
+++ b/crates/email/templates/base.hbs
@@ -0,0 +1,7 @@
+{{!-- TODO: design base email --}}
+
+ {{title}}
+
+ {{> page}}
+
+
\ No newline at end of file
diff --git a/crates/integrations/Cargo.toml b/crates/integrations/Cargo.toml
index 492437e7a..d49d0c5b3 100644
--- a/crates/integrations/Cargo.toml
+++ b/crates/integrations/Cargo.toml
@@ -1,10 +1,11 @@
[package]
name = "integrations"
-version = "0.0.3"
+version = "0.0.4"
edition = "2021"
[dependencies]
async-trait = { workspace = true }
+lettre = { workspace = true }
reqwest = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
diff --git a/package.json b/package.json
index 12355f74c..73d1d24b6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@stump/monorepo",
- "version": "0.0.3",
+ "version": "0.0.4",
"repository": "https://github.com/stumpapp/stump.git",
"author": "Aaron Leopold ",
"license": "MIT",
diff --git a/packages/api/package.json b/packages/api/package.json
index fa8aedfeb..b5641165e 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -1,6 +1,6 @@
{
"name": "@stump/api",
- "version": "0.0.3",
+ "version": "0.0.4",
"description": "",
"main": "src/index.ts",
"exports": {
diff --git a/packages/api/src/axios.ts b/packages/api/src/axios.ts
index dff8696ad..e9e87105f 100644
--- a/packages/api/src/axios.ts
+++ b/packages/api/src/axios.ts
@@ -25,6 +25,11 @@ export function initializeApi(baseUrl: string, version: string) {
API = axios.create({
baseURL: correctedUrl,
+ // FIXME: react-native seems to ignore this option, causing brackets to be encoded which
+ // the backend doesn't support
+ // paramsSerializer: {
+ // encode: (params) => qs.stringify(params, { arrayFormat: 'repeat' }),
+ // },
withCredentials: true,
})
}
diff --git a/packages/api/src/emailer.ts b/packages/api/src/emailer.ts
new file mode 100644
index 000000000..b89eea9b7
--- /dev/null
+++ b/packages/api/src/emailer.ts
@@ -0,0 +1,122 @@
+import {
+ CreateOrUpdateEmailDevice,
+ CreateOrUpdateEmailer,
+ EmailerSendRecord,
+ PatchEmailDevice,
+ RegisteredEmailDevice,
+ SendAttachmentEmailResponse,
+ SendAttachmentEmailsPayload,
+ SMTPEmailer,
+} from '@stump/types'
+
+import { API } from './axios'
+import { APIResult } from './types'
+import { toUrlParams } from './utils'
+
+function getEmailers(params?: Record): Promise> {
+ if (params) {
+ return API.get(`/emailers?${toUrlParams(params)}`)
+ } else {
+ return API.get('/emailers')
+ }
+}
+
+function getEmailerById(id: number): Promise> {
+ return API.get(`/emailers/${id}`)
+}
+
+function createEmailer(payload: CreateOrUpdateEmailer): Promise> {
+ return API.post('/emailers', payload)
+}
+
+function updateEmailer(
+ id: number,
+ payload: CreateOrUpdateEmailer,
+): Promise> {
+ return API.put(`/emailers/${id}`, payload)
+}
+
+function deleteEmailer(id: number): Promise> {
+ return API.delete(`/emailers/${id}`)
+}
+
+function getEmailDevices(): Promise> {
+ return API.get('/email-devices')
+}
+
+function getEmailDeviceById(id: number): Promise> {
+ return API.get(`/email-devices/${id}`)
+}
+
+function getEmailerSendHistory(
+ emailerId: number,
+ params?: Record,
+): Promise> {
+ if (params) {
+ return API.get(`/emailers/${emailerId}/send-history?${toUrlParams(params)}`)
+ } else {
+ return API.get(`/emailers/${emailerId}/send-history`)
+ }
+}
+
+function createEmailDevice(
+ payload: CreateOrUpdateEmailDevice,
+): Promise> {
+ return API.post('/email-devices', payload)
+}
+
+function updateEmailDevice(
+ id: number,
+ payload: CreateOrUpdateEmailDevice,
+): Promise> {
+ return API.put(`/email-devices/${id}`, payload)
+}
+
+function patchEmailDevice(
+ id: number,
+ payload: PatchEmailDevice,
+): Promise> {
+ return API.patch(`/email-devices/${id}`, payload)
+}
+
+function deleteEmailDevice(id: number): Promise> {
+ return API.delete(`/email-devices/${id}`)
+}
+
+function sendAttachmentEmail(
+ payload: SendAttachmentEmailsPayload,
+): Promise> {
+ return API.post('/emailers/send-attachment', payload)
+}
+
+export const emailerApi = {
+ createEmailDevice,
+ createEmailer,
+ deleteEmailDevice,
+ deleteEmailer,
+ getEmailDeviceById,
+ getEmailDevices,
+ getEmailerById,
+ getEmailerSendHistory,
+ getEmailers,
+ patchEmailDevice,
+ sendAttachmentEmail,
+ updateEmailDevice,
+ updateEmailer,
+}
+
+export const emailerQueryKeys: Record = {
+ createEmailDevice: 'emailDevice.create',
+ createEmailer: 'emailer.create',
+ deleteEmailDevice: 'emailDevice.delete',
+ deleteEmailer: 'emailer.delete',
+ getEmailDeviceById: 'emailDevice.getById',
+ getEmailDevices: 'emailDevices.get',
+ getEmailerById: 'emailer.getById',
+ getEmailerSendHistory: 'emailer.sendHistory',
+ getEmailers: 'emailer.get',
+ patchEmailDevice: 'emailDevice.patch',
+ sendAttachmentEmail: 'emailer.sendAttachment',
+ updateEmailDevice: 'emailDevice.update',
+ updateEmailer: 'emailer.update',
+}
diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts
index d97a9a4bc..f67ae63cf 100644
--- a/packages/api/src/index.ts
+++ b/packages/api/src/index.ts
@@ -1,6 +1,7 @@
export { authApi, authQueryKeys } from './auth'
export { API, apiIsInitialized, checkUrl, initializeApi, isUrl } from './axios'
export { bookClubApi, bookClubQueryKeys } from './bookClub'
+export { emailerApi, emailerQueryKeys } from './emailer'
export { epubApi, epubQueryKeys, getEpubResource, updateEpubProgress } from './epub'
export { filesystemApi, filesystemQueryKeys } from './filesystem'
export * from './job'
diff --git a/packages/api/src/library.ts b/packages/api/src/library.ts
index 1c625d238..9cc84e71b 100644
--- a/packages/api/src/library.ts
+++ b/packages/api/src/library.ts
@@ -117,6 +117,15 @@ export function updateExcludedUsers(id: string, user_ids: string[]) {
return API.post(`/libraries/${id}/excluded-users`, { user_ids })
}
+/**
+ * Start the analysis of a library by library id.
+ *
+ * @param id The id for the library to analyze
+ */
+export function startMediaAnalysis(id: string) {
+ API.post(`/libraries/${id}/analyze`)
+}
+
export const libraryApi = {
cleanLibrary,
createLibrary,
@@ -133,6 +142,7 @@ export const libraryApi = {
patchLibraryThumbnail,
regenerateThumbnails,
scanLibary,
+ startMediaAnalysis,
updateExcludedUsers,
uploadLibraryThumbnail,
visitLibrary,
@@ -154,6 +164,7 @@ export const libraryQueryKeys: Record = {
patchLibraryThumbnail: 'library.patchLibraryThumbnail',
regenerateThumbnails: 'library.regenerateThumbnails',
scanLibary: 'library.scanLibary',
+ startMediaAnalysis: 'library.startAnalysis',
updateExcludedUsers: 'library.updateExcludedUsers',
uploadLibraryThumbnail: 'library.uploadLibraryThumbnail',
visitLibrary: 'library.visitLibrary',
diff --git a/packages/api/src/log.ts b/packages/api/src/log.ts
index 1d5c0768a..9b40ecf77 100644
--- a/packages/api/src/log.ts
+++ b/packages/api/src/log.ts
@@ -13,6 +13,15 @@ export function getLogs(params?: Record): Promise): Promise