Compare commits

..

5 Commits

Author SHA1 Message Date
ce28426075 chore: rename project to bakit
All checks were successful
Publish Docker Images / build-and-publish (push) Successful in 13m40s
2025-11-20 11:01:55 +01:00
c46ab8397c style: fix some clippy errors
Some checks failed
Publish Docker Images / build-and-publish (push) Has been cancelled
2025-11-20 10:51:48 +01:00
daa92328c5 docs(README): open links in new tabs 2025-11-20 10:51:48 +01:00
2f0ebc8144 chore: add Sonar analysis 2025-11-16 02:12:21 +01:00
797ab461ab feat: send confirmation email to sender
When users submit a contact form, they now receive a confirmation
email acknowlledging receipt of their message. The backend also
continues to send a notification email to the configured recipient.

If the backend fails to send the acknowledgement email to the sender,
it will assume the email is not valid and will therefore not transmit
the contact request to the configured recipient.

Changes:
- Refactor `send_email()` to `send_emails()` that sends two emails:
  - Confirmation email from the submitter
  - Notification email to the configured recipient
- Add `From<T>` implementations of various errors for new error type
  `ContactError`.
- Errors now return a translation identifier for the frontend.
2025-11-16 02:12:21 +01:00
10 changed files with 50 additions and 46 deletions

View File

@@ -84,7 +84,7 @@ Cachix is a Nix binary cache that dramatically speeds up builds by caching build
Configure these in the workflow's `env` section or as repository variables: Configure these in the workflow's `env` section or as repository variables:
| Variable | Description | Default Value | Example | | Variable | Description | Default Value | Example |
|--------------------+------------------------------------------------+---------------+--------------------| |--------------------|------------------------------------------------|---------------|--------------------|
| `CACHIX_NAME` | Name of the Cachix cache to use | `devenv` | `phundrak-dot-com` | | `CACHIX_NAME` | Name of the Cachix cache to use | `devenv` | `phundrak-dot-com` |
| `CACHIX_SKIP_PUSH` | Whether to skip pushing artifacts to the cache | `true` | `false` | | `CACHIX_SKIP_PUSH` | Whether to skip pushing artifacts to the cache | `true` | `false` |

40
Cargo.lock generated
View File

@@ -134,6 +134,26 @@ dependencies = [
"fs_extra", "fs_extra",
] ]
[[package]]
name = "bakit"
version = "0.1.0"
dependencies = [
"chrono",
"config",
"dotenvy",
"governor",
"lettre",
"poem",
"poem-openapi",
"serde",
"serde_json",
"thiserror",
"tokio",
"tracing",
"tracing-subscriber",
"validator",
]
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.7" version = "0.21.7"
@@ -1573,26 +1593,6 @@ dependencies = [
"sha2", "sha2",
] ]
[[package]]
name = "phundrak-dot-com-backend"
version = "0.1.0"
dependencies = [
"chrono",
"config",
"dotenvy",
"governor",
"lettre",
"poem",
"poem-openapi",
"serde",
"serde_json",
"thiserror",
"tokio",
"tracing",
"tracing-subscriber",
"validator",
]
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "0.4.30" version = "0.4.30"

View File

@@ -1,5 +1,5 @@
[package] [package]
name = "phundrak-dot-com-backend" name = "bakit"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
publish = false publish = false
@@ -11,7 +11,7 @@ path = "src/lib.rs"
[[bin]] [[bin]]
path = "src/main.rs" path = "src/main.rs"
name = "phundrak-dot-com-backend" name = "bakit"
[dependencies] [dependencies]
chrono = { version = "0.4.42", features = ["serde"] } chrono = { version = "0.4.42", features = ["serde"] }

View File

@@ -3,14 +3,20 @@ include_toc: true
gitea: none gitea: none
--- ---
# phundrak.com Backend <h1 align="center">Bakit</h1>
<div align="center">
<strong>
A backend for my personal website
</strong>
</div>
<br/>
<div align="center"> <div align="center">
<a href="https://sonar.phundrak.com/dashboard?id=phundrak-backend" target="_blank"> <a href="https://sonar.phundrak.com/dashboard?id=bakit" target="_blank">
<img src="https://sonar.phundrak.com/api/project_badges/measure?project=phundrak-backend&metric=coverage&token=sqb_bda24bf36825576d6c6b76048044e103339c3c5f" alt="Sonar Coverage" /> <img src="https://sonar.phundrak.com/api/project_badges/measure?project=bakit&metric=coverage&token=sqb_bda24bf36825576d6c6b76048044e103339c3c5f" alt="Sonar Coverage" />
</a> </a>
<a href="https://sonar.phundrak.com/dashboard?id=phundrak-backend" target="_blank"> <a href="https://sonar.phundrak.com/dashboard?id=bakit" target="_blank">
<img src="https://sonar.phundrak.com/api/project_badges/measure?project=phundrak-backend&metric=alert_status&token=sqb_bda24bf36825576d6c6b76048044e103339c3c5f" alt="Sonar Quality Gate Status" /> <img src="https://sonar.phundrak.com/api/project_badges/measure?project=bakit&metric=alert_status&token=sqb_bda24bf36825576d6c6b76048044e103339c3c5f" alt="Sonar Quality Gate Status" />
</a> </a>
<a href="#license"> <a href="#license">
<img src="https://img.shields.io/badge/License-AGPL--3.0--only-blue" alt="License" /> <img src="https://img.shields.io/badge/License-AGPL--3.0--only-blue" alt="License" />
@@ -20,8 +26,6 @@ gitea: none
</a> </a>
</div> </div>
The backend for [phundrak.com](https://phundrak.com), built with Rust and the [Poem](https://github.com/poem-web/poem) web framework.
## Features ## Features
- **RESTful API** with automatic OpenAPI/Swagger documentation - **RESTful API** with automatic OpenAPI/Swagger documentation
@@ -137,14 +141,14 @@ For optimized production builds:
cargo build --release cargo build --release
``` ```
The compiled binary will be at `target/release/backend`. The compiled binary will be at `target/release/bakit`.
**With Nix:** **With Nix:**
Build the backend binary: Build the backend binary:
```bash ```bash
nix build .#backend nix build .#backend
# Binary available at: ./result/bin/backend # Binary available at: ./result/bin/bakit
``` ```
Build Docker images: Build Docker images:
@@ -157,7 +161,7 @@ nix build .#backendDockerLatest
# Load into Docker # Load into Docker
docker load < result docker load < result
# Image will be available as: localhost/phundrak/backend-rust:latest # Image will be available as: localhost/phundrak/bakit:latest
``` ```
The Nix build ensures reproducible builds with all dependencies pinned. The Nix build ensures reproducible builds with all dependencies pinned.
@@ -332,7 +336,7 @@ Docker images are automatically built and published via GitHub Actions to the co
Pull and run the latest image: Pull and run the latest image:
```bash ```bash
# Pull from Phundrak Labs (labs.phundrak.com) # Pull from Phundrak Labs (labs.phundrak.com)
docker pull labs.phundrak.com/phundrak/phundrak-dot-com-backend:latest docker pull labs.phundrak.com/phundrak/bakit:latest
# Run the container # Run the container
docker run -d \ docker run -d \
@@ -345,7 +349,7 @@ docker run -d \
-e APP__EMAIL__PASSWORD=your_password \ -e APP__EMAIL__PASSWORD=your_password \
-e APP__EMAIL__FROM="Contact Form <noreply@example.com>" \ -e APP__EMAIL__FROM="Contact Form <noreply@example.com>" \
-e APP__EMAIL__RECIPIENT="Admin <admin@example.com>" \ -e APP__EMAIL__RECIPIENT="Admin <admin@example.com>" \
labs.phundrak.com/phundrak/phundrak-dot-com-backend:latest labs.phundrak.com/phundrak/bakit:latest
``` ```
### Available Image Tags ### Available Image Tags
@@ -363,7 +367,7 @@ Build with Nix (recommended for reproducibility):
```bash ```bash
nix build .#backendDockerLatest nix build .#backendDockerLatest
docker load < result docker load < result
docker run -p 3100:3100 localhost/phundrak/backend-rust:latest docker run -p 3100:3100 localhost/phundrak/bakit:latest
``` ```
Build with Docker directly: Build with Docker directly:
@@ -379,7 +383,7 @@ version: '3.8'
services: services:
backend: backend:
image: labs.phundrak.com/phundrak/phundrak-dot-com-backend:latest image: labs.phundrak.com/phundrak/bakit:latest
ports: ports:
- "3100:3100" - "3100:3100"
environment: environment:
@@ -431,7 +435,7 @@ To use the published images, authenticate with the registry:
echo $GITHUB_TOKEN | docker login labs.phundrak.com -u USERNAME --password-stdin echo $GITHUB_TOKEN | docker login labs.phundrak.com -u USERNAME --password-stdin
# Pull the image # Pull the image
docker pull labs.phundrak.com/phundrak/phundrak-dot-com-backend:latest docker pull labs.phundrak.com/phundrak/bakit:latest
``` ```
### Required Secrets ### Required Secrets

View File

@@ -5,7 +5,7 @@ application:
protocol: http protocol: http
host: 127.0.0.1 host: 127.0.0.1
base_url: http://127.0.0.1:3100 base_url: http://127.0.0.1:3100
name: "com.phundrak.backend.dev" name: "bakit-dev"
email: email:
host: localhost host: localhost

View File

@@ -2,7 +2,7 @@ debug: false
frontend_url: "" frontend_url: ""
application: application:
name: "com.phundrak.backend.prod" name: "bakit-prod"
protocol: https protocol: https
host: 0.0.0.0 host: 0.0.0.0
base_url: "" base_url: ""

View File

@@ -1 +1 @@
sonar.projectKey=phundrak-backend sonar.projectKey=bakit

View File

@@ -3,5 +3,5 @@
#[cfg(not(tarpaulin_include))] #[cfg(not(tarpaulin_include))]
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), std::io::Error> { async fn main() -> Result<(), std::io::Error> {
phundrak_dot_com_backend::run(None).await bakit::run(None).await
} }

View File

@@ -268,7 +268,7 @@ mod tests {
#[test] #[test]
fn from_validation_errors_with_name_error() { fn from_validation_errors_with_name_error() {
use validator::{Validate, ValidationError}; use validator::Validate;
#[derive(Validate)] #[derive(Validate)]
struct TestStruct { struct TestStruct {

View File

@@ -933,7 +933,7 @@ mod tests {
assert!(result.is_err()); assert!(result.is_err());
match result.unwrap_err() { match result.unwrap_err() {
ContactError::CouldNotParseSettingsEmail(_) => (), ContactError::CouldNotParseSettingsEmail(_) => (),
e => panic!("Expected CouldNotParseSettingsEmail, got {:?}", e), e => panic!("Expected CouldNotParseSettingsEmail, got {e:?}"),
} }
} }
@@ -964,7 +964,7 @@ mod tests {
assert!(result.is_err()); assert!(result.is_err());
match result.unwrap_err() { match result.unwrap_err() {
ContactError::CouldNotParseRequestEmailAddress(_) => (), ContactError::CouldNotParseRequestEmailAddress(_) => (),
e => panic!("Expected CouldNotParseRequestEmailAddress, got {:?}", e), e => panic!("Expected CouldNotParseRequestEmailAddress, got {e:?}"),
} }
} }
@@ -996,7 +996,7 @@ mod tests {
assert!(result.is_err()); assert!(result.is_err());
match result.unwrap_err() { match result.unwrap_err() {
ContactError::CouldNotSendEmail(_) => (), ContactError::CouldNotSendEmail(_) => (),
e => panic!("Expected CouldNotSendEmail, got {:?}", e), e => panic!("Expected CouldNotSendEmail, got {e:?}"),
} }
} }
} }