Skip to content

feat: add resend notification endpoint#2

Merged
joamag merged 7 commits into
masterfrom
feat/resend-endpoint
Mar 4, 2026
Merged

feat: add resend notification endpoint#2
joamag merged 7 commits into
masterfrom
feat/resend-endpoint

Conversation

@joamag
Copy link
Copy Markdown
Owner

@joamag joamag commented Mar 4, 2026

Summary

  • Adds GET /admin/resend?since=<ISO8601> endpoint to re-trigger notification emails for files modified after a given timestamp
  • Reuses existing shared link resolution, file download, and email send patterns from the scheduler
  • Filters Dropbox folder entries by server_modified >= since and sends the notification email with file attachments
  • Independent from the scheduler — does not affect its in-memory state

Test plan

  • Start the app and call /admin/resend?since=2026-03-01T00:00:00Z with valid admin credentials
  • Verify email is received with correct file attachments for files modified after the timestamp
  • Verify OperationalError is raised when since parameter is missing
  • Verify OperationalError is raised when NOTIFIER_FOLDER is not configured

🤖 Generated with Claude Code

Adds GET /admin/resend?since=<ISO8601> endpoint that lists all files
in the monitored Dropbox folder modified after the given timestamp
and re-sends the notification email with those files as attachments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 4, 2026

Warning

Rate limit exceeded

@joamag has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 49 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fbe5613c-ef91-4932-b240-acc8a0139248

📥 Commits

Reviewing files that changed from the base of the PR and between dfc08a0 and ea156a2.

📒 Files selected for processing (6)
  • AGENTS.md
  • CHANGELOG.md
  • CLAUDE.md
  • README.md
  • src/dropbox_notifier/controllers/admin.py
  • src/dropbox_notifier/scheduler.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/resend-endpoint

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@joamag joamag self-assigned this Mar 4, 2026
@joamag joamag added the enhancement New feature or request label Mar 4, 2026
@joamag joamag marked this pull request as ready for review March 4, 2026 17:58
Copilot AI review requested due to automatic review settings March 4, 2026 17:58
Isolates the Dropbox folder scanning, shared link resolution,
file filtering and download logic into a private _resend method,
keeping the route handler focused on validation and email sending.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6ed90b6c5e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

entry
for entry in entries
if entry.get(".tag") == "file"
and entry.get("server_modified", "") >= since
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Parse since as datetime before filtering entries

The filter compares entry["server_modified"] and the query since as raw strings, which is not a reliable chronological comparison for valid ISO8601 inputs that use different representations (for example timezone offsets like -05:00 or fractional seconds). In those cases this endpoint can resend files outside the requested time window or miss files that should be included, even though the input timestamp is valid.

Useful? React with 👍 / 👎.

Comment on lines +112 to +115
appier_extras.admin.Base.send_email_g(
owner,
"email/updated.html.tpl",
receivers=receivers if receivers else [email],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid sending "folder updated" emails with no changes

This call is unconditional, so requests where no files match since still send an "updated" email with an effectively empty body (added_entries=[], removed_entries=[]). That creates false notifications and can spam recipients during routine backfill/retry calls; the handler should return resent=0 without sending when there are no matching entries.

Useful? React with 👍 / 👎.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an authenticated admin-only endpoint to re-trigger Dropbox notification emails for files modified after a provided timestamp, reusing the existing email + attachment flow used by the scheduler.

Changes:

  • Add GET /admin/resend?since=<ISO8601> endpoint under the admin controller.
  • Resolve shared link + enumerate folder entries, filter by modification time, download matching files, and send “folder updated” email with attachments.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +90 to +98
if share_links:
shared = share_links[0]

# loops trying to find the best possible share link
# for the folder, keeping in mind that using extended sharing
# controls will allows deep shared folder
for share_link in share_links:
link_permissions = share_link.get("link_permissions", {})
if not link_permissions.get("can_use_extended_sharing_controls", False):
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

Filtering with entry.get("server_modified", "") >= since compares ISO8601 timestamps as raw strings. This can yield incorrect results when since uses a different timezone/format (e.g., +00:00 vs Z) or includes fractional seconds, and it also accepts invalid since values silently. Parse/validate since and server_modified as datetimes (normalize to UTC) and raise an OperationalError for invalid since formats before filtering.

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +110
shared = share_link

# creates a shared link to the folder so that it can be used
# for the URL creation in no shared link already exists
else:
shared = api.create_shared_link(folder_path)

shared_url = appier.legacy.urlparse(shared["url"])
shared_base = f"{shared_url.scheme}://{shared_url.netloc}{shared_url.path}"
shared_query = shared_url.query

Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

This endpoint can download and attach every file modified since an arbitrary timestamp. If since is far in the past (or the folder has many/large files), this may cause long request times, high Dropbox API usage, large memory consumption, and oversized emails. Consider adding a hard cap (max files and/or total bytes), returning a summary when the cap is exceeded, and/or providing a way to send links instead of attachments for large payloads.

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +86
added_entries, added_files, folder_path, shared_base, shared_query, prefix_size = (
self._resend(folder_path, since=since)
)

appier_extras.admin.Base.send_email_g(
owner,
"email/updated.html.tpl",
receivers=receivers if receivers else [email],
cc=cc,
bcc=bcc,
reply_to=reply_to,
subject=owner.to_locale(f"Dropbox folder {folder_path} updated"),
attachments=added_files,
added_entries=added_entries,
removed_entries=[],
folder_path=folder_path,
folder_url=shared_base,
folder_query=shared_query,
prefix_size=prefix_size,
)

return dict(since=since, resent=len(added_files))

def _resend(self, folder_path, since=None):
api = self.get_api()

folder_meta = api.metadata_file(folder_path)
folder_path = folder_meta["path_display"]
prefix_size = len(folder_path)

share = api.list_shared_links(folder_path)
share_links = share.get("links", [])
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

resend() duplicates a large block of folder-resolution/shared-link/email composition logic that already exists in Scheduler.scan_folder(). This increases the risk of the two flows drifting (eg. bug fixes applied to one but not the other). Consider extracting the common logic into a shared helper (eg. in a service/module used by both the scheduler and controller) and keep resend() focused on the since filtering behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +71
removed_entries=[],
folder_path=folder_path,
folder_url=shared_base,
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

Grammar in the comment: "controls will allows deep shared folder" should be "controls will allow deep shared folders".

Copilot uses AI. Check for mistakes.
joamag and others added 2 commits March 4, 2026 18:03
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Owner Author

@joamag joamag left a comment

Choose a reason for hiding this comment

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

LGTM

@joamag joamag merged commit 96bbc78 into master Mar 4, 2026
7 checks passed
@joamag joamag deleted the feat/resend-endpoint branch March 4, 2026 18:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants