Skip to content

feat(scheduler): background auto-publish task (PR F2)#71

Merged
thierryvm merged 1 commit into
mainfrom
feature/scheduler-background-task
May 12, 2026
Merged

feat(scheduler): background auto-publish task (PR F2)#71
thierryvm merged 1 commit into
mainfrom
feature/scheduler-background-task

Conversation

@thierryvm

@thierryvm thierryvm commented May 11, 2026

Copy link
Copy Markdown
Owner

Self-review

Suit le pattern Foundation-then-Consumer validé sur PR #56/#57 et confirmé
sur #69/#70. Le data layer ferme depuis #70 ; cette PR consomme.

Motivation

PR #69+#70 ont ajouté toute la SQL pour le scheduler (try_lock_for_publish,
mark_publish_attempt_failed, backoff policy en Rust) mais aucune Tauri
command ne les appelait — #[allow(dead_code)] partout. Sans la background
task de cette PR, l'auto-publish v0.4.0 n'existe pas du point de vue utilisateur.

Design

Trois couches volontairement minces dans src-tauri/src/scheduler.rs :

  1. tick(pool, dispatcher, notifier) — un tour de polling. Lit
    db::scheduler::list_due_for_publish et route chaque post via
    process_post. Aucun AppHandle — testable contre une SQLite in-memory.
  2. process_posttry_lock_for_publish, hand-off dispatcher,
    mark_publish_attempt_failed sur Err. Le statut 'published' est mis à
    jour par publish_post / publish_linkedin_post existants — pas de
    duplication.
  3. spawn(handle) — seul point qui voit le AppHandle réel. Détache
    un tokio task qui poll toutes les 60s. Lives for the process lifetime.

Pourquoi des traits (PublishDispatcher / Notifier) ?

Le runtime Tauri ne se monte pas à bas coût dans un #[tokio::test]. Les
traits donnent une seam pour mocker dispatcher + notifier dans les tests sans
toucher au code de production (les impls Tauri sont des shells minces).

Pourquoi pas de timer in-Rust pour les retries ?

Tout le retry timing vit dans BACKOFF_MINUTES, re-lu par list_due_for_publish
à chaque tick. Le scheduler ne tient pas d'état entre les ticks et survit aux
redémarrages sans rien perdre — la source de vérité est sur disque.

Pre-check token expiry

accounts.token_expires_at (migration 014) est consulté avant chaque dispatch.
Token expiré → erreur claire au lieu d'un 401 générique de Meta/LinkedIn. La
tentative compte comme un échec dans le budget retry, donc l'utilisateur finit
par recevoir la notification GaveUp même sur un token définitivement mort.

Evidence

cargo check                              # OK
cargo clippy --all-targets -- -D warnings # OK
cargo test --lib                         # 249 passed (240 baseline + 9 scheduler)
npm run typecheck                        # OK

Tests scheduler (src-tauri/src/scheduler.rs, 9 nouveaux) :

  • tick_with_no_due_posts_is_noop — pas de dispatch sans posts due
  • tick_publishes_overdue_draft_and_flips_status — E2E F2 headline
  • tick_records_failure_and_returns_to_draft_until_threshold
  • tick_skips_already_locked_post — protection race multi-instance
  • repeated_failures_eventually_notify_user — GaveUp → notification 1×
  • tick_respects_backoff_window_between_failures — pas de re-tick immédiat
  • pre_check_token_expiry_blocks_expired_account
  • pre_check_token_expiry_allows_account_with_future_expiry
  • pre_check_token_expiry_falls_open_for_legacy_post_with_no_account

Out of scope

Test plan

  • CI verte
  • Sourcery review consultée
  • Pas de breakage frontend (npm run typecheck OK localement)

🤖 Generated with Claude Code

Summary by Sourcery

Ajouter un planificateur d’arrière‑plan qui publie automatiquement, à intervalles réguliers, les brouillons arrivés à échéance via les commandes de publication existantes et remonte les erreurs critiques à l’utilisateur.

New Features:

  • Introduire un planificateur d’auto‑publication en arrière‑plan qui interroge SQLite pour trouver les brouillons arrivés à échéance et les envoie vers les flux de publication Instagram/LinkedIn existants.
  • Ajouter des notifications desktop pour informer les utilisateurs lorsqu’une publication planifiée a épuisé son budget de tentatives et est marquée comme échouée.
  • Vérifier à l’avance l’expiration du jeton OAuth avant de lancer les publications planifiées afin de fournir des erreurs plus claires et d’éviter des tentatives inutiles sur des comptes expirés.

Enhancements:

  • Raccorder le planificateur au démarrage de l’application Tauri afin qu’il s’exécute comme une tâche de durée de vie du processus, détachée, sans bloquer l’initialisation.

Build:

  • Déclarer des dépendances explicites sur tauri-plugin-notification et async-trait pour le planificateur et l’intégration des notifications.

Tests:

  • Ajouter des tests de type intégration pour la boucle de tick du planificateur, couvrant les publications réussies, le comportement de retry/backoff, les sémantiques de verrouillage et le déclenchement des notifications.
  • Ajouter des tests pour la vérification préalable de l’expiration du jeton afin de garantir que les jetons expirés bloquent l’envoi, tandis que les comptes valides ou hérités sont autorisés.
Original summary in English

Summary by Sourcery

Add a background scheduler that periodically auto-publishes due drafts via existing publish commands and surfaces hard failures to the user.

New Features:

  • Introduce a background auto-publish scheduler that polls SQLite for due drafts and dispatches them to existing Instagram/LinkedIn publish flows.
  • Add desktop notifications to inform users when a scheduled post exhausts its retry budget and is marked as failed.
  • Pre-check OAuth token expiry before dispatching scheduled publishes to provide clearer errors and avoid futile retries on expired accounts.

Enhancements:

  • Wire the scheduler into the Tauri app startup so it runs as a detached process-lifetime task without blocking initialization.

Build:

  • Declare explicit dependencies on tauri-plugin-notification and async-trait for the scheduler and notification integration.

Tests:

  • Add integration-style tests for the scheduler tick loop covering successful publishes, retry/backoff behavior, locking semantics, and notification triggering.
  • Add tests for the token expiry pre-check to ensure expired tokens block dispatch while valid or legacy accounts are allowed.

Wires the data layer mergé in PR #69+#70 to a real tokio task. Polls
SQLite every 60s for due drafts, atomically locks each row via
try_lock_for_publish, dispatches through the existing publish_post /
publish_linkedin_post commands, and surfaces a desktop notification
when a post exhausts MAX_FAILED_ATTEMPTS.

The scheduler stays a dumb pump: zero in-Rust retry timers, all
backoff lives in db::scheduler::BACKOFF_MINUTES re-read every tick.
That survives app restarts without losing track of pending posts.

Architecture:
- `tick` / `process_post` take traits (PublishDispatcher + Notifier)
  so the loop is testable without an AppHandle (real Tauri runtime
  is too heavy to stand up in #[tokio::test]). Production impls are
  thin shells: TauriDispatcher routes by network, TauriNotifier
  wraps tauri-plugin-notification.
- Pre-check token expiry against accounts.token_expires_at (added in
  migration 014). Expired token short-circuits with a clear error
  rather than waiting for Meta/LinkedIn to 401 — the dispatch still
  counts as a failed attempt so the retry budget burns down even on
  doomed tokens.

New deps:
- tauri-plugin-notification = "2" + notification:default capability
- async-trait = "0.1" for the dispatcher seam (already transitive)

Tests: +9 in src/scheduler.rs (240 -> 249 lib tests). Cover the
headline F2 E2E (overdue draft -> published after one tick), retry
budget exhaustion -> notification, lock-contention skip, backoff
window respect between failures, and the three token-expiry branches.

Out of scope (deferred to follow-ups):
- Scheduler UI badges in Composer/Dashboard (PR F3)
- Sourcery #70 complexity follow-ups (task #94) — DateTime<Utc>
  parsing in PostRecord touches frontend serde, warrants a dedicated
  PR rather than smuggling it through F2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented May 11, 2026

Copy link
Copy Markdown

Guide du relecteur

Implémente un planificateur en arrière-plan dans Tauri qui interroge périodiquement SQLite pour les publications arrivées à échéance et les envoie via les commandes de publication existantes, avec reprise/exponentiel de re-tentative, pré‑vérifications d’expiration de jeton, et notifications utilisateur lorsque les tentatives sont épuisées, le tout intégré au démarrage de l’application et couvert par des tests de type intégration.

Diagramme de séquence pour un tick du planificateur avec échec de publication et notification

sequenceDiagram
    participant SchedulerTask as SchedulerTask
    participant DbScheduler as db_scheduler
    participant Dispatcher as PublishDispatcher
    participant Notifier as Notifier

    SchedulerTask->>DbScheduler: list_due_for_publish(pool)
    DbScheduler-->>SchedulerTask: Vec<PostRecord>
    loop for each PostRecord
        SchedulerTask->>DbScheduler: try_lock_for_publish(pool, post.id)
        alt lock acquired
            SchedulerTask->>Dispatcher: publish(post)
            alt publish Ok
                Dispatcher-->>SchedulerTask: Ok(())
                Note over SchedulerTask,Dispatcher: publish_post / publish_linkedin_post flip status to published
            else publish Err
                Dispatcher-->>SchedulerTask: Err(String)
                SchedulerTask->>DbScheduler: mark_publish_attempt_failed(pool, post.id)
                alt PublishFailureOutcome::GaveUp
                    DbScheduler-->>SchedulerTask: GaveUp { failed_attempts }
                    SchedulerTask->>Notifier: notify_gave_up(post, failed_attempts)
                else PublishFailureOutcome::WillRetry / Skipped
                    DbScheduler-->>SchedulerTask: WillRetry | Skipped
                end
            end
        else lock not acquired / error
            DbScheduler-->>SchedulerTask: false | Err
        end
    end
Loading

Modifications au niveau des fichiers

Change Details Files
Add a background scheduler module that polls the database and dispatches due posts via an abstracted dispatcher/notifier layer.
  • Introduce src-tauri/src/scheduler.rs implementing tick(), process_post(), and spawn() functions to drive scheduled publishing from SQLite.
  • Define PublishDispatcher and Notifier traits plus TauriDispatcher/TauriNotifier concrete implementations to decouple scheduler logic from Tauri runtime and enable testing.
  • Use try_lock_for_publish and mark_publish_attempt_failed from db::scheduler to enforce single-consumer locking, retry counting, and terminal failure handling.
  • Implement desktop notification on terminal failure via Notifier::notify_gave_up, with a production implementation using the Tauri notification plugin.
src-tauri/src/scheduler.rs
Wire the scheduler and notification plugin into the Tauri application startup and dependency graph.
  • Declare tauri-plugin-notification and async-trait as explicit dependencies to support notifications and async traits used by the scheduler.
  • Register the new scheduler module in src-tauri/src/lib.rs and call scheduler::spawn(app.handle().clone()) during setup to start the detached background task.
  • Enable the notification plugin in the Tauri builder chain so desktop notifications can be shown from the scheduler.
src-tauri/Cargo.toml
src-tauri/src/lib.rs
src-tauri/Cargo.lock
src-tauri/capabilities/default.json
Add a pre-dispatch token-expiry check to avoid futile publishes and surface clearer errors.
  • Implement pre_check_token_expiry(pool, post) to look up the associated account, parse token_expires_at, and reject dispatches whose tokens are expired or about to expire within a grace window.
  • Call pre_check_token_expiry from TauriDispatcher::publish before routing to publish_post/publish_linkedin_post, treating expiry as a counted failure that contributes to the retry budget.
src-tauri/src/scheduler.rs
Add integration-style tests for scheduler behavior, including retries, backoff, locking, and token-expiry handling.
  • Create in-memory SQLite test harness with migrations to exercise scheduler::tick and related logic without a full Tauri runtime.
  • Add MockDispatcher/MockNotifier test implementations to simulate success/failure and notification behavior while asserting DB status transitions and call counts.
  • Cover scenarios including no-due-posts, successful publish, failure with retry, lock contention, respecting backoff windows, terminal failure notification, and token expiry edge cases.
src-tauri/src/scheduler.rs

Conseils et commandes

Interagir avec Sourcery

  • Déclencher une nouvelle revue : Commentez @sourcery-ai review sur la pull request.
  • Poursuivre les discussions : Répondez directement aux commentaires de revue de Sourcery.
  • Générer une issue GitHub à partir d’un commentaire de revue : Demandez à Sourcery de créer une issue à partir d’un commentaire de revue en y répondant. Vous pouvez aussi répondre à un commentaire de revue avec @sourcery-ai issue pour créer une issue à partir de celui‑ci.
  • Générer un titre de pull request : Écrivez @sourcery-ai n’importe où dans le titre de la pull request pour générer un titre à tout moment. Vous pouvez également commenter @sourcery-ai title sur la pull request pour (re)générer le titre à tout moment.
  • Générer un résumé de pull request : Écrivez @sourcery-ai summary n’importe où dans le corps de la pull request pour générer un résumé de PR à tout moment exactement à l’endroit souhaité. Vous pouvez également commenter @sourcery-ai summary sur la pull request pour (re)générer le résumé à tout moment.
  • Générer un guide du relecteur : Commentez @sourcery-ai guide sur la pull request pour (re)générer le guide du relecteur à tout moment.
  • Résoudre tous les commentaires Sourcery : Commentez @sourcery-ai resolve sur la pull request pour résoudre tous les commentaires Sourcery. Utile si vous avez déjà traité tous les commentaires et ne voulez plus les voir.
  • Ignorer toutes les revues Sourcery : Commentez @sourcery-ai dismiss sur la pull request pour ignorer toutes les revues Sourcery existantes. Particulièrement utile si vous voulez repartir de zéro avec une nouvelle revue – n’oubliez pas de commenter @sourcery-ai review pour déclencher une nouvelle revue !

Personnaliser votre expérience

Accédez à votre dashboard pour :

  • Activer ou désactiver des fonctionnalités de revue telles que le résumé de pull request généré par Sourcery, le guide du relecteur, et d’autres.
  • Changer la langue de la revue.
  • Ajouter, supprimer ou modifier des instructions de revue personnalisées.
  • Ajuster d’autres paramètres de revue.

Obtenir de l’aide

Original review guide in English

Reviewer's Guide

Implements a background scheduler in Tauri that periodically polls SQLite for due posts and dispatches them through existing publish commands, with retry/backoff, token-expiry prechecks, and user notifications when retries are exhausted, all wired into app startup and covered by integration-style tests.

Sequence diagram for scheduler tick with publish failure and notification

sequenceDiagram
    participant SchedulerTask as SchedulerTask
    participant DbScheduler as db_scheduler
    participant Dispatcher as PublishDispatcher
    participant Notifier as Notifier

    SchedulerTask->>DbScheduler: list_due_for_publish(pool)
    DbScheduler-->>SchedulerTask: Vec<PostRecord>
    loop for each PostRecord
        SchedulerTask->>DbScheduler: try_lock_for_publish(pool, post.id)
        alt lock acquired
            SchedulerTask->>Dispatcher: publish(post)
            alt publish Ok
                Dispatcher-->>SchedulerTask: Ok(())
                Note over SchedulerTask,Dispatcher: publish_post / publish_linkedin_post flip status to published
            else publish Err
                Dispatcher-->>SchedulerTask: Err(String)
                SchedulerTask->>DbScheduler: mark_publish_attempt_failed(pool, post.id)
                alt PublishFailureOutcome::GaveUp
                    DbScheduler-->>SchedulerTask: GaveUp { failed_attempts }
                    SchedulerTask->>Notifier: notify_gave_up(post, failed_attempts)
                else PublishFailureOutcome::WillRetry / Skipped
                    DbScheduler-->>SchedulerTask: WillRetry | Skipped
                end
            end
        else lock not acquired / error
            DbScheduler-->>SchedulerTask: false | Err
        end
    end
Loading

File-Level Changes

Change Details Files
Add a background scheduler module that polls the database and dispatches due posts via an abstracted dispatcher/notifier layer.
  • Introduce src-tauri/src/scheduler.rs implementing tick(), process_post(), and spawn() functions to drive scheduled publishing from SQLite.
  • Define PublishDispatcher and Notifier traits plus TauriDispatcher/TauriNotifier concrete implementations to decouple scheduler logic from Tauri runtime and enable testing.
  • Use try_lock_for_publish and mark_publish_attempt_failed from db::scheduler to enforce single-consumer locking, retry counting, and terminal failure handling.
  • Implement desktop notification on terminal failure via Notifier::notify_gave_up, with a production implementation using the Tauri notification plugin.
src-tauri/src/scheduler.rs
Wire the scheduler and notification plugin into the Tauri application startup and dependency graph.
  • Declare tauri-plugin-notification and async-trait as explicit dependencies to support notifications and async traits used by the scheduler.
  • Register the new scheduler module in src-tauri/src/lib.rs and call scheduler::spawn(app.handle().clone()) during setup to start the detached background task.
  • Enable the notification plugin in the Tauri builder chain so desktop notifications can be shown from the scheduler.
src-tauri/Cargo.toml
src-tauri/src/lib.rs
src-tauri/Cargo.lock
src-tauri/capabilities/default.json
Add a pre-dispatch token-expiry check to avoid futile publishes and surface clearer errors.
  • Implement pre_check_token_expiry(pool, post) to look up the associated account, parse token_expires_at, and reject dispatches whose tokens are expired or about to expire within a grace window.
  • Call pre_check_token_expiry from TauriDispatcher::publish before routing to publish_post/publish_linkedin_post, treating expiry as a counted failure that contributes to the retry budget.
src-tauri/src/scheduler.rs
Add integration-style tests for scheduler behavior, including retries, backoff, locking, and token-expiry handling.
  • Create in-memory SQLite test harness with migrations to exercise scheduler::tick and related logic without a full Tauri runtime.
  • Add MockDispatcher/MockNotifier test implementations to simulate success/failure and notification behavior while asserting DB status transitions and call counts.
  • Cover scenarios including no-due-posts, successful publish, failure with retry, lock contention, respecting backoff windows, terminal failure notification, and token expiry edge cases.
src-tauri/src/scheduler.rs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - j'ai trouvé 1 problème

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src-tauri/src/scheduler.rs" line_range="124-133" />
<code_context>
+/// file (legacy rows from before migration 014) or the post has no
+/// `account_id`; the publish call itself will surface any real auth
+/// problem in those cases.
+async fn pre_check_token_expiry(pool: &sqlx::SqlitePool, post: &PostRecord) -> Result<(), String> {
+    let Some(account_id) = post.account_id else {
+        return Ok(());
+    };
+    let account = match crate::db::accounts::get_by_id(pool, account_id).await {
+        Ok(a) => a,
+        Err(_) => return Ok(()), // let publish surface the real error
+    };
+    let Some(expires_at) = account.token_expires_at.as_deref() else {
+        return Ok(());
+    };
+    let Ok(parsed) = chrono::DateTime::parse_from_rfc3339(expires_at) else {
+        return Ok(());
+    };
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Ignorer les erreurs de recherche de compte / d'analyse de timestamp peut rendre le diagnostic des problèmes d'authentification plus difficile.

Ici, à la fois un `get_by_id` en échec et un format `token_expires_at` invalide aboutissent à `Ok(())` sans aucun logging. Cela signifie que des lignes corrompues ou des changements de schéma qui affectent le comportement de publish/auth ne seront pas visibles dans les diagnostics. Merci d'ajouter au minimum des logs de niveau debug/warn lorsque la recherche de compte échoue ou que l'analyse du timestamp échoue, afin que ces problèmes de données soient observables.

Implémentation suggérée :

```rust
async fn pre_check_token_expiry(pool: &sqlx::SqlitePool, post: &PostRecord) -> Result<(), String> {
    let Some(account_id) = post.account_id else {
        return Ok(());
    };

    let account = match crate::db::accounts::get_by_id(pool, account_id).await {
        Ok(a) => a,
        Err(err) => {
            tracing::warn!(
                ?err,
                account_id,
                "pre_check_token_expiry: failed to look up account for post; falling back to letting publish surface auth error"
            );
            return Ok(()); // let publish surface the real error
        }
    };

    let Some(expires_at) = account.token_expires_at.as_deref() else {
        return Ok(());
    };

    let parsed = match chrono::DateTime::parse_from_rfc3339(expires_at) {
        Ok(dt) => dt,
        Err(err) => {
            tracing::warn!(
                ?err,
                account_id,
                expires_at,
                "pre_check_token_expiry: invalid token_expires_at format; falling back to letting publish surface auth error"
            );
            return Ok(());
        }
    };

```

Si `tracing` (ou une autre bibliothèque de logging) n'est pas déjà utilisée dans ce fichier, ajoutez un import approprié comme `use tracing::warn;` / `use tracing::instrument;` ou assurez-vous que `tracing::warn!` est disponible conformément à vos conventions de logging existantes. Si votre projet préfère la bibliothèque `log`, remplacez `tracing::warn!` par `log::warn!` et adaptez les imports en conséquence.
</issue_to_address>

Sourcery est gratuit pour l'open source - si vous trouvez nos revues utiles, pensez à les partager ✨
Aidez-moi à être plus utile ! Merci de cliquer sur 👍 ou 👎 sur chaque commentaire ; j'utiliserai vos retours pour améliorer vos revues.
Original comment in English

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src-tauri/src/scheduler.rs" line_range="124-133" />
<code_context>
+/// file (legacy rows from before migration 014) or the post has no
+/// `account_id`; the publish call itself will surface any real auth
+/// problem in those cases.
+async fn pre_check_token_expiry(pool: &sqlx::SqlitePool, post: &PostRecord) -> Result<(), String> {
+    let Some(account_id) = post.account_id else {
+        return Ok(());
+    };
+    let account = match crate::db::accounts::get_by_id(pool, account_id).await {
+        Ok(a) => a,
+        Err(_) => return Ok(()), // let publish surface the real error
+    };
+    let Some(expires_at) = account.token_expires_at.as_deref() else {
+        return Ok(());
+    };
+    let Ok(parsed) = chrono::DateTime::parse_from_rfc3339(expires_at) else {
+        return Ok(());
+    };
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Swallowing account lookup / timestamp parse errors may make diagnosing auth issues harder.

Here both a failed `get_by_id` and an invalid `token_expires_at` format resolve to `Ok(())` with no logging. That means corrupt rows or schema changes that affect publish/auth behaviour won’t be visible in diagnostics. Please add at least debug/warn logs when the account lookup fails or the timestamp parse fails so these data issues are observable.

Suggested implementation:

```rust
async fn pre_check_token_expiry(pool: &sqlx::SqlitePool, post: &PostRecord) -> Result<(), String> {
    let Some(account_id) = post.account_id else {
        return Ok(());
    };

    let account = match crate::db::accounts::get_by_id(pool, account_id).await {
        Ok(a) => a,
        Err(err) => {
            tracing::warn!(
                ?err,
                account_id,
                "pre_check_token_expiry: failed to look up account for post; falling back to letting publish surface auth error"
            );
            return Ok(()); // let publish surface the real error
        }
    };

    let Some(expires_at) = account.token_expires_at.as_deref() else {
        return Ok(());
    };

    let parsed = match chrono::DateTime::parse_from_rfc3339(expires_at) {
        Ok(dt) => dt,
        Err(err) => {
            tracing::warn!(
                ?err,
                account_id,
                expires_at,
                "pre_check_token_expiry: invalid token_expires_at format; falling back to letting publish surface auth error"
            );
            return Ok(());
        }
    };

```

If `tracing` (or another logging crate) is not already used in this file, add an appropriate import such as `use tracing::warn;` / `use tracing::instrument;` or ensure `tracing::warn!` is available according to your existing logging conventions. If your project prefers the `log` crate, replace `tracing::warn!` with `log::warn!` and adjust imports accordingly.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +124 to +133
async fn pre_check_token_expiry(pool: &sqlx::SqlitePool, post: &PostRecord) -> Result<(), String> {
let Some(account_id) = post.account_id else {
return Ok(());
};
let account = match crate::db::accounts::get_by_id(pool, account_id).await {
Ok(a) => a,
Err(_) => return Ok(()), // let publish surface the real error
};
let Some(expires_at) = account.token_expires_at.as_deref() else {
return Ok(());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Ignorer les erreurs de recherche de compte / d'analyse de timestamp peut rendre le diagnostic des problèmes d'authentification plus difficile.

Ici, à la fois un get_by_id en échec et un format token_expires_at invalide aboutissent à Ok(()) sans aucun logging. Cela signifie que des lignes corrompues ou des changements de schéma qui affectent le comportement de publish/auth ne seront pas visibles dans les diagnostics. Merci d'ajouter au minimum des logs de niveau debug/warn lorsque la recherche de compte échoue ou que l'analyse du timestamp échoue, afin que ces problèmes de données soient observables.

Implémentation suggérée :

async fn pre_check_token_expiry(pool: &sqlx::SqlitePool, post: &PostRecord) -> Result<(), String> {
    let Some(account_id) = post.account_id else {
        return Ok(());
    };

    let account = match crate::db::accounts::get_by_id(pool, account_id).await {
        Ok(a) => a,
        Err(err) => {
            tracing::warn!(
                ?err,
                account_id,
                "pre_check_token_expiry: failed to look up account for post; falling back to letting publish surface auth error"
            );
            return Ok(()); // let publish surface the real error
        }
    };

    let Some(expires_at) = account.token_expires_at.as_deref() else {
        return Ok(());
    };

    let parsed = match chrono::DateTime::parse_from_rfc3339(expires_at) {
        Ok(dt) => dt,
        Err(err) => {
            tracing::warn!(
                ?err,
                account_id,
                expires_at,
                "pre_check_token_expiry: invalid token_expires_at format; falling back to letting publish surface auth error"
            );
            return Ok(());
        }
    };

Si tracing (ou une autre bibliothèque de logging) n'est pas déjà utilisée dans ce fichier, ajoutez un import approprié comme use tracing::warn; / use tracing::instrument; ou assurez-vous que tracing::warn! est disponible conformément à vos conventions de logging existantes. Si votre projet préfère la bibliothèque log, remplacez tracing::warn! par log::warn! et adaptez les imports en conséquence.

Original comment in English

suggestion (bug_risk): Swallowing account lookup / timestamp parse errors may make diagnosing auth issues harder.

Here both a failed get_by_id and an invalid token_expires_at format resolve to Ok(()) with no logging. That means corrupt rows or schema changes that affect publish/auth behaviour won’t be visible in diagnostics. Please add at least debug/warn logs when the account lookup fails or the timestamp parse fails so these data issues are observable.

Suggested implementation:

async fn pre_check_token_expiry(pool: &sqlx::SqlitePool, post: &PostRecord) -> Result<(), String> {
    let Some(account_id) = post.account_id else {
        return Ok(());
    };

    let account = match crate::db::accounts::get_by_id(pool, account_id).await {
        Ok(a) => a,
        Err(err) => {
            tracing::warn!(
                ?err,
                account_id,
                "pre_check_token_expiry: failed to look up account for post; falling back to letting publish surface auth error"
            );
            return Ok(()); // let publish surface the real error
        }
    };

    let Some(expires_at) = account.token_expires_at.as_deref() else {
        return Ok(());
    };

    let parsed = match chrono::DateTime::parse_from_rfc3339(expires_at) {
        Ok(dt) => dt,
        Err(err) => {
            tracing::warn!(
                ?err,
                account_id,
                expires_at,
                "pre_check_token_expiry: invalid token_expires_at format; falling back to letting publish surface auth error"
            );
            return Ok(());
        }
    };

If tracing (or another logging crate) is not already used in this file, add an appropriate import such as use tracing::warn; / use tracing::instrument; or ensure tracing::warn! is available according to your existing logging conventions. If your project prefers the log crate, replace tracing::warn! with log::warn! and adjust imports accordingly.

@thierryvm thierryvm merged commit 49d5433 into main May 12, 2026
5 checks passed
@thierryvm thierryvm deleted the feature/scheduler-background-task branch May 12, 2026 08:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant