Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/sonarqube.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,27 @@ jobs:
sleep 15
sudo netbird status

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: clippy

- name: Cache cargo
uses: Swatinem/rust-cache@v2

- name: Run clippy (json)
run: |
cargo clippy --workspace --all-targets --all-features \
--message-format=json > clippy.json || true

- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@v6
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

- name: SonarQube Quality Gate
uses: SonarSource/sonarqube-quality-gate-action@v1
timeout-minutes: 5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ members = [
"libs/observability",
"libs/use_case/biteship",
"libs/errors"
]
, "libs/use_case/waha"]
resolver = "2"

#[build]
Expand All @@ -27,4 +27,4 @@ anyhow = "1.0"
reqwest = { version = "0.13", features = ["json"] }
async-trait = "0.1"
lapin = {version = "3.7"}
lettre = { version = "0.11", features = ["tokio1", "tokio1-native-tls"] }
lettre = { version = "0.11", features = ["tokio1", "tokio1-native-tls"] }
10 changes: 10 additions & 0 deletions libs/use_case/waha/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "waha"
version = "0.1.0"
edition = "2024"

[dependencies]
serde.workspace = true
anyhow.workspace = true
reqwest.workspace = true
thiserror.workspace = true
16 changes: 16 additions & 0 deletions libs/use_case/waha/src/dto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SendTextReq {
pub chat_id: String,
pub text: String,
pub session: String,
}

#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct WahaClientErrorResponse {
pub message: String,
pub status_code: i32,
}
13 changes: 13 additions & 0 deletions libs/use_case/waha/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use thiserror::Error;

#[derive(Error, Debug)]
pub enum WahaError {
#[error("Unauthorized")]
Unauthorized,

#[error("Client Error: {0}")]
ClientError(String),

#[error("Internal Server Waha Error")]
InternalServerError(#[from] anyhow::Error),
}
79 changes: 79 additions & 0 deletions libs/use_case/waha/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
mod dto;
mod error;

use crate::dto::{SendTextReq, WahaClientErrorResponse};
use crate::error::WahaError;
use reqwest::header;
use std::env;

pub struct WahaUseCase {
client: reqwest::Client,
base_url: String,
api_key: String,
session: String,
}

impl WahaUseCase {
pub fn new(client: reqwest::Client) -> Self {
let base_url = env::var("WAHA_API_URL").expect("BITESHIP_API_URL must be set");
let api_key = env::var("WAHA_API_KEY").expect("BITESHIP_API_KEY must be set");
let session = env::var("WAHA_SESSION").expect("WAHA_SESSION must be set");
Self {
client,
base_url,
api_key,
session,
}
}

pub async fn send_text(&self, id: String, text: String) -> Result<(), WahaError> {
let url = format!("{}/api/sendText", self.base_url);

let payload = SendTextReq {
chat_id: format!("{}@c.us", id),
text,
session: self.session.clone(),
};

let resp = self
.client
.post(url)
.header(header::CONTENT_TYPE, "application/json")
.header("X-Api-Key", self.api_key.as_str())
.header(header::ACCEPT, "application/json")
.json(&payload)
.send()
.await
.map_err(|e| WahaError::InternalServerError(e.into()))?;

self.handle_response(resp).await
}

async fn handle_response(&self, resp: reqwest::Response) -> Result<(), WahaError> {
if resp.status().is_server_error() {
let waha_err = resp
.json::<WahaClientErrorResponse>()
.await
.map_err(|e| WahaError::InternalServerError(anyhow::anyhow!(e)))?;

return Err(WahaError::InternalServerError(anyhow::anyhow!(
waha_err.message
)));
}

if resp.status().is_client_error() {
let waha_err = resp
.json::<WahaClientErrorResponse>()
.await
.map_err(|e| WahaError::InternalServerError(anyhow::anyhow!(e)))?;

if waha_err.status_code == 401 {
return Err(WahaError::Unauthorized);
}

return Err(WahaError::ClientError(waha_err.message));
}

Ok(())
}
}
15 changes: 15 additions & 0 deletions schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ VALUES ('biteship', 'confirmed', 'CREATED'),

ALTER TABLE status_mappings RENAME COLUMN courier_code TO platform;

ALTER TABLE shipments
ADD COLUMN user_id UUID REFERENCES users (id);

-- Dummy user data
INSERT INTO users (id, name, phone_number, email, created_at)
VALUES
Expand All @@ -167,3 +170,15 @@ INSERT INTO shipment_subscriptions (id, user_id, shipment_id, subscribed_statuse
VALUES
('850e8400-e29b-41d4-a716-446655440000', '550e8400-e29b-41d4-a716-446655440000', 'e975bb9f-c0b5-4fe5-a20d-e34eba31dafa', '{OUT_FOR_DELIVERY,DELIVERED,FAILED}', 'Electronics Order', '2024-01-16 09:15:00+00', '2024-01-16 09:15:00+00'),
('850e8400-e29b-41d4-a716-446655440001', '550e8400-e29b-41d4-a716-446655440001', 'e975bb9f-c0b5-4fe5-a20d-e34eba31dafa', '{DELIVERED,FAILED, RETURNED}', 'Books Order', '2024-01-22 16:15:00+00', '2024-01-22 16:15:00+00');

SELECT id,
waybill_id,
courier_code,
source,
current_status,
order_id,
external_order_ref,
created_at,
updated_at
FROM shipments
WHERE user_id = '550e8400-e29b-41d4-a716-446655440006';
1 change: 1 addition & 0 deletions services/notification-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dotenvy.workspace = true
observability = { path = "../../libs/observability" }
tracing.workspace = true
config = { path = "../../libs/config" }
waha = { path = "../../libs/use_case/waha" }
async-trait.workspace = true
uuid.workspace = true
reqwest.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion services/notification-service/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ impl NotificationHandler {
pub async fn handle(&self, event: &TrackingEventMsg) -> anyhow::Result<()> {
let template = self.resolve_template(event)?;

let (content, subject) = self.sender.render(template, &event.payload)?;
let (content, subject) = self.sender.render(template, &mut event.payload.clone())?;

self.sender.send(event, content, subject).await?;

Expand Down
2 changes: 1 addition & 1 deletion services/notification-service/src/ports/email.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ impl ChannelPort for EmailSmtpSender {
fn render(
&self,
template_id: TemplateId,
data: &TrackingMsgPayload,
data: &mut TrackingMsgPayload,
) -> anyhow::Result<(String, String)> {
let mut handlebars = Handlebars::new();

Expand Down
2 changes: 1 addition & 1 deletion services/notification-service/src/ports/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ pub trait ChannelPort: Send + Sync {
fn render(
&self,
template_id: TemplateId,
data: &TrackingMsgPayload,
data: &mut TrackingMsgPayload,
) -> anyhow::Result<(String, String)>;
}
2 changes: 1 addition & 1 deletion services/notification-service/src/ports/telegram.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl ChannelPort for TelegramSender {
fn render(
&self,
template_id: TemplateId,
data: &TrackingMsgPayload,
data: &mut TrackingMsgPayload,
) -> anyhow::Result<(String, String)> {
todo!()
}
Expand Down
42 changes: 34 additions & 8 deletions services/notification-service/src/ports/whatsapp.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
use crate::domain::{TemplateId, TrackingEventMsg, TrackingMsgPayload};
use crate::ports::ChannelPort;
use anyhow::anyhow;
use config::reqwest::get_reqwest_pool;
use reqwest::Client;
use handlebars::Handlebars;
use waha::WahaUseCase;

pub struct WhatsappSender {
client: Client,
client: WahaUseCase,
}

impl WhatsappSender {
pub fn new() -> Self {
let client = get_reqwest_pool().expect("Failed to create reqwest pool");
let client_http = get_reqwest_pool().expect("Failed to create reqwest pool");

let client = WahaUseCase::new(client_http);

Self { client }
}
Expand All @@ -21,17 +25,39 @@ impl ChannelPort for WhatsappSender {
&self,
event: &TrackingEventMsg,
content: String,
subject: String,
_subject: String,
) -> anyhow::Result<()> {
tracing::info!("sending tracking event to whatsapp");
todo!()
self.client
.send_text(event.recipient.clone(), content)
.await?;

Ok(())
}

fn render(
&self,
template_id: TemplateId,
data: &TrackingMsgPayload,
data: &mut TrackingMsgPayload,
) -> anyhow::Result<(String, String)> {
todo!()
let mut handlebars = Handlebars::new();

let template = match template_id {
TemplateId::TrackingCreatedWa => (
"templates/tracking_added/chat.stub",
"Your Shipment Is On Tracking",
),
_ => return Err(anyhow!("invalid template")),
};

let root = env!("CARGO_MANIFEST_DIR");
let path = std::path::Path::new(root).join(template.0);

handlebars.register_template_file("chat", path)?;

data.courier = data.courier.to_uppercase();
data.status = data.status.to_uppercase();
let rendered = handlebars.render("chat", data)?;

Ok((rendered, template.1.to_string()))
}
}
12 changes: 12 additions & 0 deletions services/notification-service/templates/tracking_added/chat.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
📦 *Pengiriman Berhasil Ditambahkan*

Halo,

Nomor resi *{{waybill_id}}* dari *{{courier}}* sudah berhasil ditambahkan ke sistem tracking kami.

📍 Status Saat Ini:
_*{{status}}*_

Kami akan mengirimkan notifikasi otomatis setiap ada update status pengiriman Anda.

Terima kasih telah menggunakan layanan kami.
Loading
Loading