This service handles user notifications for various events such as message creation, updates, and deletions, as well as managing user notification preferences. The service interacts with a message broker (RabbitMQ) to receive event messages and update user notifications and settings accordingly.
The service follows Clean Architecture principles with the following layers:
- API Layer (
api/): HTTP handlers and routing - Core Layer (
core/): Business logic, domain entities, and use cases - Infrastructure Layer: Database and message broker integrations
- Domain Entities:
Notification,NotificationPreference,NotificationId,UserId,ChannelId - Message Handler: Processes RabbitMQ messages for create/update/delete events
- Repository: Data access layer for notifications and preferences
- Broker Service: RabbitMQ consumer integration
- Rust toolchain
- Docker and Docker Compose
docker compose up -dThis starts:
- PostgreSQL database on port 5432
- RabbitMQ with management UI on port 15672
- Keycloak for authentication on port 8080
cargo run --bin migratorcargo runThe service will be available on http://localhost:3333.
cargo testIntegration tests use testcontainers for RabbitMQ and are automatically cleaned up. One container per test is used to ensure isolation.
Located in .env file with a cp .env.example .env to create your own.
All endpoints require a valid JWT token. Get one from Keycloak:
TOKEN=$(curl -s -X POST "http://localhost:8080/realms/beep/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=client-id" \
-d "grant_type=password" \
-d "username=testuser" \
-d "password=test123" \
| jq -r .access_token)GET /
Authorization: Bearer {token}Example:
curl -X GET "http://localhost:3333/" \
-H "Authorization: Bearer $TOKEN"Response:
{
"message": "Hello, World!",
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"username": "testuser"
}GET /users/{user_id}/notifications
Authorization: Bearer {token}Example:
curl -X GET "http://localhost:3333/users/bfb1beae-f741-4bc6-9063-b52b8452de91/notifications" \
-H "Authorization: Bearer $TOKEN"Response:
{
"notifications": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "e715c4e9-6459-4883-9d1a-01dc230bf7cf",
"title": "New Message",
"message": "You have a new message",
"notification_type": "Message",
"status": "Unread",
"created_at": "2023-12-31T10:00:00Z"
}
]
}PATCH /users/{user_id}/notifications/{notification_id}/read
Authorization: Bearer {token}Example:
curl -X PATCH "http://localhost:3333/users/bfb1beae-f741-4bc6-9063-b52b8452de91/notifications/223e4567-e89b-12d3-a456-426614174001/read" \
-H "Authorization: Bearer $TOKEN"Response: 202 Accepted (empty body)
POST /users/{user_id}/notifications/read
Authorization: Bearer {token}
Content-Type: application/jsonExample:
curl -X POST "http://localhost:3333/users/bfb1beae-f741-4bc6-9063-b52b8452de91/notifications/read" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"notification_ids": [
"550e8400-e29b-41d4-a716-446655440000",
"660e8400-e29b-41d4-a716-446655440001"
]
}'Request body:
{
"notification_ids": [
"550e8400-e29b-41d4-a716-446655440000",
"660e8400-e29b-41d4-a716-446655440001"
]
}Response: 202 Accepted (empty body)
GET /users/{user_id}/notifications/preferences
Authorization: Bearer {token}Example:
curl -X GET "http://localhost:3333/users/bfb1beae-f741-4bc6-9063-b52b8452de91/notifications/preferences" \
-H "Authorization: Bearer $TOKEN"Response:
{
"notifications_preferences": [
{
"id": "456e7890-e89b-12d3-a456-426614174002",
"user_id": "e715c4e9-6459-4883-9d1a-01dc230bf7cf",
"channel_id": "789e0123-e89b-12d3-a456-426614174003",
"enabled": true,
"muted_until": null
}
]
}PATCH /users/{user_id}/notifications/preferences
Authorization: Bearer {token}
Content-Type: application/jsonExample:
curl -X PATCH "http://localhost:3333/users/bfb1beae-f741-4bc6-9063-b52b8452de91/notifications/preferences" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "456e7890-e89b-12d3-a456-426614174002",
"user_id": "bfb1beae-f741-4bc6-9063-b52b8452de91",
"channel_id": "789e0123-e89b-12d3-a456-426614174003",
"enabled": true,
"muted_until": null
}'Request body:
{
"id": "456e7890-e89b-12d3-a456-426614174002",
"user_id": "e715c4e9-6459-4883-9d1a-01dc230bf7cf",
"channel_id": "789e0123-e89b-12d3-a456-426614174003",
"enabled": true,
"muted_until": null
}Response: 202 Accepted (empty body)
The service consumes events from RabbitMQ exchanges:
Exchange: message_created
{
"message_id": "223e4567-e89b-12d3-a456-426614174001",
"channel_id": "660e8400-e29b-41d4-a716-446655440001",
"author_id": "550e8400-e29b-41d4-a716-446655440000",
"content": "Reply with attachment and mention",
"reply_to_message_id": "123e4567-e89b-12d3-a456-426614174000",
"attachments": [
{
"id": "333e4567-e89b-12d3-a456-426614174002",
"name": "screenshot.png",
"url": "https://example.com/screenshot.png"
}
],
"notify_entries": [
{
"id": "770e8400-e29b-41d4-a716-446655440002",
"type": "mention"
}
]
}Exchange: message_updated
{
"message_id": "223e4567-e89b-12d3-a456-426614174001",
"content": "Updated message content!",
"is_pinned": true,
"notify_entries": [
{
"id": "880e8400-e29b-41d4-a716-446655440003",
"type": "mention"
}
]
}Exchange: message_deleted
{
"message_id": "223e4567-e89b-12d3-a456-426614174001"
}Main tables:
notifications: Stores user notificationsnotification_preferences: Stores user notification settings per channel
- Follow Rust coding standards and run
cargo fmt - Ensure all tests pass:
cargo test - Add tests for new functionality
- Update this README if adding new features