Skip to content
Open
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
34 changes: 34 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Tests

on:
push:
pull_request:

jobs:
backend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: backend
- run: cargo test

frontend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: frontend/.node-version
cache: npm
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- run: npm test
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ frontend/dist/
.DS_Store

tmp/
.claude
.claude

# Local screenshot mocks — never commit
/*.png
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ cd backend && cargo clippy
# Frontend
cd frontend && npm install
cd frontend && npm run build
cd frontend && npm run test
cd frontend && npm run dev

# Docker
Expand Down
16 changes: 16 additions & 0 deletions README.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,20 @@ npm run dev

Le serveur de développement Vite démarre sur `http://localhost:5173` et proxifie `/api/*` vers le backend.

## Tests

Les tests backend sont dans le module Rust qu'ils couvrent (`#[cfg(test)]`). Les tests frontend sont des fichiers Vitest à côté du TypeScript concerné (`*.test.ts` / `*.test.tsx`).

```bash
# Backend — tests unitaires, sans base de données
cd backend && cargo test

# Frontend
cd frontend && npm test
```

La CI lance les deux à chaque push et pull request (`.github/workflows/test.yml`).

## Variables d'environnement

### Principales
Expand Down Expand Up @@ -262,11 +276,13 @@ rclone-replication-ui/
```bash
# Backend
cd backend && cargo build # Compiler
cd backend && cargo test # Tests
cd backend && cargo clippy # Lint
cd backend && cargo fmt # Formater

# Frontend
cd frontend && npm run build # Build production
cd frontend && npm run test # Tests
cd frontend && npm run dev # Serveur de développement
cd frontend && npm run lint # Lint

Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,20 @@ npm run dev

The Vite dev server starts on `http://localhost:5173` and proxies `/api/*` to the backend.

## Tests

Backend tests live next to the Rust they cover (`#[cfg(test)]` modules). Frontend tests are Vitest files next to the TypeScript they cover (`*.test.ts` / `*.test.tsx`).

```bash
# Backend — unit tests, no database
cd backend && cargo test

# Frontend
cd frontend && npm test
```

CI runs both on every push and pull request (`.github/workflows/test.yml`).

## Environment variables

### Core
Expand Down Expand Up @@ -262,11 +276,13 @@ rclone-replication-ui/
```bash
# Backend
cd backend && cargo build # Build
cd backend && cargo test # Tests
cd backend && cargo clippy # Lint
cd backend && cargo fmt # Format

# Frontend
cd frontend && npm run build # Production build
cd frontend && npm run test # Tests
cd frontend && npm run dev # Dev server
cd frontend && npm run lint # Lint

Expand Down
1 change: 1 addition & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ RUN mkdir src && echo "fn main() {}" > src/main.rs \

# Compilation de l'application
COPY src ./src
COPY locales ./locales
RUN touch src/main.rs && cargo build --release

# ─── Rclone stage ────────────────────────────────────────────────────────────
Expand Down
19 changes: 19 additions & 0 deletions backend/locales/en/notifications.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"reason": "The previous run is still in progress",
"error": {
"subject": "Replication task failed",
"body": "**Task**: `{task_id}`\n**Run**: `{run_id}`\n**Exit code**: `{exit_code}`\n\n**Logs**:\n```\n{logs}\n```"
},
"success": {
"subject": "Replication task completed successfully",
"body": "**Task**: `{task_id}`\n**Run**: `{run_id}`\n\nThe synchronization completed successfully."
},
"skipped": {
"subject": "Replication task skipped",
"body": "**Task**: `{task_id}`\n**Reason**: {reason}\n\nThe cron schedule tried to start this task, but the previous synchronization has not finished. The run was _skipped_."
},
"test": {
"subject": "Test — rclone-ui",
"body": "This message confirms that the notification channel is **working correctly**.\n\n_Sent from rclone-ui._"
}
}
19 changes: 19 additions & 0 deletions backend/locales/fr/notifications.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"reason": "L'exécution précédente est encore en cours",
"error": {
"subject": "Échec de la tâche de réplication",
"body": "**Tâche** : `{task_id}`\n**Run** : `{run_id}`\n**Code de sortie** : `{exit_code}`\n\n**Logs** :\n```\n{logs}\n```"
},
"success": {
"subject": "Tâche de réplication terminée avec succès",
"body": "**Tâche** : `{task_id}`\n**Run** : `{run_id}`\n\nLa synchronisation s'est terminée avec succès."
},
"skipped": {
"subject": "Tâche de réplication ignorée",
"body": "**Tâche** : `{task_id}`\n**Raison** : L'exécution précédente est encore en cours\n\nLa planification cron a tenté de lancer cette tâche, mais la synchronisation précédente n'est pas terminée. L'exécution a été _ignorée_."
},
"test": {
"subject": "Test — rclone-ui",
"body": "Ce message confirme que le canal de notification **fonctionne correctement**.\n\n_Envoyé depuis rclone-ui._"
}
}
19 changes: 19 additions & 0 deletions backend/locales/pt/notifications.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"reason": "A execução anterior ainda está em curso",
"error": {
"subject": "Falha da tarefa de replicação",
"body": "**Tarefa** : `{task_id}`\n**Execução** : `{run_id}`\n**Código de saída** : `{exit_code}`\n\n**Registos** :\n```\n{logs}\n```"
},
"success": {
"subject": "Tarefa de replicação concluída com sucesso",
"body": "**Tarefa** : `{task_id}`\n**Execução** : `{run_id}`\n\nA sincronização terminou com sucesso."
},
"skipped": {
"subject": "Tarefa de replicação ignorada",
"body": "**Tarefa** : `{task_id}`\n**Motivo** : {reason}\n\nO agendamento cron tentou iniciar esta tarefa, mas a sincronização anterior ainda não terminou. A execução foi _ignorada_."
},
"test": {
"subject": "Teste — rclone-ui",
"body": "Esta mensagem confirma que o canal de notificação **funciona corretamente**.\n\n_Enviado a partir do rclone-ui._"
}
}
13 changes: 13 additions & 0 deletions backend/src/entities/notification_channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,23 @@ pub struct Model {
pub name: String,
pub apprise_url: String,
pub enabled: bool,
#[serde(default = "default_language")]
pub language: String,
#[sea_orm(column_type = "JsonBinary")]
#[serde(default = "default_templates")]
pub templates: serde_json::Value,
pub created_at: DateTimeWithTimeZone,
pub updated_at: DateTimeWithTimeZone,
}

fn default_language() -> String {
"fr".to_string()
}

fn default_templates() -> serde_json::Value {
serde_json::json!({})
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::task::Entity")]
Expand Down
33 changes: 33 additions & 0 deletions backend/src/migration/m011_add_notification_channel_language.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use sea_orm_migration::prelude::*;

#[derive(DeriveMigrationName)]
pub struct Migration;

#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();
db.execute_unprepared(
"ALTER TABLE notification_channels \
ADD COLUMN IF NOT EXISTS language TEXT NOT NULL DEFAULT 'fr'",
)
.await?;
db.execute_unprepared(
"ALTER TABLE notification_channels \
ADD COLUMN IF NOT EXISTS templates JSONB NOT NULL DEFAULT '{}'::jsonb",
)
.await?;
Ok(())
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();
db.execute_unprepared(
"ALTER TABLE notification_channels DROP COLUMN IF EXISTS templates",
)
.await?;
db.execute_unprepared("ALTER TABLE notification_channels DROP COLUMN IF EXISTS language")
.await?;
Ok(())
}
}
2 changes: 2 additions & 0 deletions backend/src/migration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod m007_add_retry_to_tasks;
mod m008_add_skipped_status;
mod m009_add_encryption_to_tasks;
mod m010_create_encrypted_file_state;
mod m011_add_notification_channel_language;

pub struct Migrator;

Expand All @@ -27,6 +28,7 @@ impl MigratorTrait for Migrator {
Box::new(m008_add_skipped_status::Migration),
Box::new(m009_add_encryption_to_tasks::Migration),
Box::new(m010_create_encrypted_file_state::Migration),
Box::new(m011_add_notification_channel_language::Migration),
]
}
}
Loading