Skip to content

Phase 7#12

Merged
shubhamxdd merged 3 commits into
mainfrom
phase-7
May 19, 2026
Merged

Phase 7#12
shubhamxdd merged 3 commits into
mainfrom
phase-7

Conversation

@shubhamxdd
Copy link
Copy Markdown
Owner

@shubhamxdd shubhamxdd commented May 19, 2026

  • Add UpgradeModal and PublicRoute components; integrate into Generator, Resources, Solver, and Dashboard for enhanced user experience
  • resolve worker status deadlock and ensure storage/db transaction integrity
    • Implement after_job_end hook in ARQ worker to force-fail jobs on unhandled crashes, preventing infinite "processing" states.
    • Refactor resource upload logic to automatically delete storage files (DigitalOcean Spaces) if the database transaction fails,
      preventing "orphan" files.
    • Use explicit _job_id in ARQ to ensure reliable correlation between background tasks and database job records.
    • Add comprehensive logging to worker hooks for better production observability.

Summary by CodeRabbit

  • New Features

    • Welcome banner with quick-start guide now displayed on dashboard.
    • Upgrade modal shown when users reach usage limits for paper generation, file uploads, and solver features.
    • Login and register routes now protected; authenticated users are redirected to dashboard.
  • Bug Fixes

    • Resource uploads now properly clean up stored files when the transaction fails.

Review Change Stack

…erator, Resources, Solver, and Dashboard for enhanced user experience
… integrity

   - Implement `after_job_end` hook in ARQ worker to force-fail jobs on unhandled crashes, preventing infinite "processing" states.
   - Refactor resource upload logic to automatically delete storage files (DigitalOcean Spaces) if the database transaction fails,
   preventing "orphan" files.
   - Use explicit `_job_id` in ARQ to ensure reliable correlation between background tasks and database job records.
   - Add comprehensive logging to worker hooks for better production observability.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

Warning

Rate limit exceeded

@shubhamxdd has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 34 minutes and 1 second before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ 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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7bd0b787-89eb-41ea-a64f-45e3d6bce038

📥 Commits

Reviewing files that changed from the base of the PR and between bf0165e and df41d69.

📒 Files selected for processing (1)
  • backend/app/routers/resources.py
📝 Walkthrough

Walkthrough

This PR extends job reliability through explicit job ID tracking and post-execution state reconciliation, adds comprehensive upload error handling with file cleanup, and introduces a premium monetization tier with upgrade prompts shown when users hit feature limits. Auth-gated public routes prevent logged-in users from accessing login/register pages, and a new welcome banner guides onboarding.

Changes

Job Reliability, Premium Tiers, and Auth

Layer / File(s) Summary
Job ID Enqueue Pattern Initiation
backend/app/routers/papers.py
Paper creation now passes _job_id=str(new_job.id) to the Redis enqueue call, establishing explicit job ID tracking for ARQ.
Upload Transaction Safety and Job Enqueue
backend/app/routers/resources.py
Resource upload wraps the entire transaction in try/except/finally: on any error it rolls back the DB, deletes the uploaded file from storage, logs the failure, and returns a 500 error; rethrows HTTPException as-is. The enqueue call also passes _job_id for consistency.
ARQ Worker Lifecycle and Job State Reconciliation
backend/app/workers/arq_worker.py
Worker lifecycle is enhanced with startup/shutdown logging and a new on_job_start hook; a new after_job_end hook reads job status from ARQ context, updates the Job row with final status/completion timestamp, and cascades failed status to related Resource/Paper entities. WorkerSettings registers both new hooks.
Auth Route Protection
frontend/src/App.tsx, frontend/src/components/PublicRoute.tsx
Login and register routes are wrapped with a new PublicRoute component that checks useAuthStore for tokens and redirects authenticated users to /; unauthenticated users see the login/register forms.
Premium Upgrade Modal Component
frontend/src/components/UpgradeModal.tsx
New reusable modal component displays a premium upgrade dialog with benefits list, $9/mo pricing, and two actions (Upgrade Now / Maybe Later). Dialog open/closed state and optional message text are controlled via props.
Multi-Page Upgrade Integration
frontend/src/pages/Generator.tsx, frontend/src/pages/Resources.tsx, frontend/src/pages/Solver.tsx
Generator, Resources, and Solver pages each add local state for isUpgradeModalOpen and upgradeMessage. When their respective APIs return HTTP 403 (paper creation, file upload, question submission), they set a limit-specific message, open the UpgradeModal, and close any in-progress dialogs; other errors still show toast notifications.
Welcome Onboarding Banner
frontend/src/components/WelcomeBanner.tsx, frontend/src/App.tsx
New WelcomeBanner component renders a three-step Quick Start guide on the dashboard with a dismiss button that persists visibility state to localStorage; the component is integrated into the Dashboard in App.tsx.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • shubhamxdd/pyq-appl#3: The backend job-tracking and resource-upload changes in this PR directly extend the OCR/document-processing workflow and Redis job infrastructure introduced in that PR.

Poem

🐰 A rabbit hops through jobs with pride,
With IDs tracked and errors tied,
Premium gates keep limits in check,
While welcome banners deck the deck,
Auth guards the routes, all safe and sound!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Phase 7' is vague and generic, using a non-descriptive term that does not convey meaningful information about the changeset. Replace with a specific, descriptive title that reflects the main changes, such as 'Add upgrade modal, fix worker job tracking, and implement transaction rollback for uploads' or a more concise variant.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch phase-7

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/app/routers/papers.py (1)

154-167: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Mark new_job as failed when enqueue fails (don’t commit it as queued).

At Line 163-166, enqueue failures still end in await db.commit(), which can persist new_job in queued even though no worker job exists.

Suggested fix
+from datetime import datetime
...
     except Exception as e:
         logger.error(f"Redis enqueue error: {e}")
+        new_job.status = "failed"
+        new_job.completed_at = datetime.utcnow()
         new_paper.status = "failed"
         await db.commit()
         raise HTTPException(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/routers/papers.py` around lines 154 - 167, The enqueue exception
block needs to mark the database job as failed instead of leaving it as queued:
in the except handling for redis.enqueue_job failure, set new_job.status =
"failed" (and optionally new_job.error or similar) before calling await
db.commit(), ensure new_paper.status is also set to "failed" as already done,
and then raise the HTTPException; this ensures the Job model (new_job) is
persisted as failed rather than incorrectly remaining queued when
redis.enqueue_job (generate_paper_task) fails.
🧹 Nitpick comments (1)
backend/app/workers/tasks.py (1)

21-22: ⚡ Quick win

Remove commented-out debug code.

The commented RuntimeError was likely used to test the worker crash handling introduced in this PR. Debug artifacts like this should not be committed to the main branch, as they add noise and can confuse future developers.

🧹 Proposed fix
     print(f"\n🚀 [TASK START] Resource ID: {resource_id}")
-
-    # raise RuntimeError("Simulated Worker Crash")
     
     async with SessionLocal() as db:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/workers/tasks.py` around lines 21 - 22, Remove the commented-out
debug line "# raise RuntimeError("Simulated Worker Crash")" from
backend/app/workers/tasks.py so no test/debug artifacts remain; locate the
commented statement in the tasks module (the commented RuntimeError used to
simulate a worker crash) and delete it, leaving the surrounding function/class
logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/app/routers/resources.py`:
- Around line 123-127: The cleanup currently runs regardless of whether the DB
commit+refresh succeeded, which can delete the uploaded storage file while the
DB record exists; modify the handler around db.commit() and
db.refresh(new_resource) (the try/except/finally block that also calls the
storage deletion code) to track success: set a local flag (e.g.,
commit_and_refresh_ok = False), set it to True only after await db.commit() and
await db.refresh(new_resource) complete without error, and then in the
cleanup/finally section only delete the uploaded file(s) if
commit_and_refresh_ok is False (i.e., rollback or error path); keep existing
rollback and re-raise behavior intact so errors still propagate.
- Around line 139-142: The handler currently raises
HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed
to complete upload transaction: {str(e)}") which leaks internal exception text
(variable e) to clients; instead, log the full exception internally (e.g.,
logger.exception or logging.exception with the context in the same function
where the raise appears) and change the HTTPException detail to a generic
message like "Failed to complete upload transaction" or "Internal server error
while completing upload transaction" while preserving the 500 status; ensure you
reference the same raise HTTPException and status.HTTP_500_INTERNAL_SERVER_ERROR
locations and remove direct inclusion of str(e) from the client-facing detail.
- Around line 132-135: The except block uses an undefined name `logger`, causing
a NameError during cleanup; define a module logger and use it instead. Add an
import logging and create a logger via logger = logging.getLogger(__name__) at
module scope (or ensure an existing logger variable is initialized before the
except block), then keep the existing logger.error(...) and
storage_service.delete_file(object_name) calls in the except handler. Ensure the
symbol `logger` is available in the same module as the exception handler
(resources.py) so the cleanup path can log errors reliably.

In `@frontend/src/components/UpgradeModal.tsx`:
- Around line 67-76: The Button in UpgradeModal.tsx currently only logs and
calls onClose in its onClick handler; replace the console.log with a real
upgrade flow by invoking a dedicated upgrade function (e.g., a prop like
onUpgrade or a local helper such as initiateCheckout/initiateRazorpay) from the
Button's onClick, handle async errors and loading state (disable the Button
while pending), and only call onClose after a successful redirect/checkout
initiation; update the UpgradeModal component signature to accept an onUpgrade
prop if needed and use Button/onClick/onClose to trigger that flow.

In `@frontend/src/components/WelcomeBanner.tsx`:
- Around line 45-52: The dismiss control in WelcomeBanner.tsx is an icon-only
Button (component Button with onClick={handleDismiss} and child <X />) and needs
an accessible name; update the Button to provide an accessible label (e.g., add
aria-label="Dismiss" or aria-label="Close" or include a visually-hidden text
child) so screen readers can announce the action, ensuring the existing
handleDismiss and X icon usage remain unchanged.

---

Outside diff comments:
In `@backend/app/routers/papers.py`:
- Around line 154-167: The enqueue exception block needs to mark the database
job as failed instead of leaving it as queued: in the except handling for
redis.enqueue_job failure, set new_job.status = "failed" (and optionally
new_job.error or similar) before calling await db.commit(), ensure
new_paper.status is also set to "failed" as already done, and then raise the
HTTPException; this ensures the Job model (new_job) is persisted as failed
rather than incorrectly remaining queued when redis.enqueue_job
(generate_paper_task) fails.

---

Nitpick comments:
In `@backend/app/workers/tasks.py`:
- Around line 21-22: Remove the commented-out debug line "# raise
RuntimeError("Simulated Worker Crash")" from backend/app/workers/tasks.py so no
test/debug artifacts remain; locate the commented statement in the tasks module
(the commented RuntimeError used to simulate a worker crash) and delete it,
leaving the surrounding function/class logic unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 667c7351-0c8e-474a-b1a4-7a4533543fc9

📥 Commits

Reviewing files that changed from the base of the PR and between 87775ab and bf0165e.

📒 Files selected for processing (11)
  • backend/app/routers/papers.py
  • backend/app/routers/resources.py
  • backend/app/workers/arq_worker.py
  • backend/app/workers/tasks.py
  • frontend/src/App.tsx
  • frontend/src/components/PublicRoute.tsx
  • frontend/src/components/UpgradeModal.tsx
  • frontend/src/components/WelcomeBanner.tsx
  • frontend/src/pages/Generator.tsx
  • frontend/src/pages/Resources.tsx
  • frontend/src/pages/Solver.tsx

Comment on lines +123 to +127
await db.commit()
await db.refresh(new_resource)
except Exception as e:
await db.rollback()
raise e # Caught by outer try/except
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t delete storage files after a successful DB commit.

If Line 123 commit succeeds but Line 124 refresh fails, current logic still hits cleanup at Line 135 and deletes the uploaded file, leaving a committed DB record pointing to missing storage.

Suggested fix
-    try:
+    committed = False
+    try:
...
-            await db.commit()
+            await db.commit()
+            committed = True
             await db.refresh(new_resource)
...
     except Exception as e:
         # CLEANUP: Delete from storage if DB transaction failed
         logger.error(f"Upload transaction failed, cleaning up storage: {e}")
-        storage_service.delete_file(object_name)
+        if not committed:
+            storage_service.delete_file(object_name)

Also applies to: 133-136

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/routers/resources.py` around lines 123 - 127, The cleanup
currently runs regardless of whether the DB commit+refresh succeeded, which can
delete the uploaded storage file while the DB record exists; modify the handler
around db.commit() and db.refresh(new_resource) (the try/except/finally block
that also calls the storage deletion code) to track success: set a local flag
(e.g., commit_and_refresh_ok = False), set it to True only after await
db.commit() and await db.refresh(new_resource) complete without error, and then
in the cleanup/finally section only delete the uploaded file(s) if
commit_and_refresh_ok is False (i.e., rollback or error path); keep existing
rollback and re-raise behavior intact so errors still propagate.

Comment thread backend/app/routers/resources.py
Comment on lines 139 to 142
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to queue background task: {str(e)}"
detail=f"Failed to complete upload transaction: {str(e)}"
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid returning raw internal exception text to clients.

Line 141 exposes internal error details ({str(e)}) in a 500 response.

Suggested fix
         raise HTTPException(
             status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
-            detail=f"Failed to complete upload transaction: {str(e)}"
+            detail="Failed to complete upload transaction."
         )
🧰 Tools
🪛 Ruff (0.15.13)

[warning] 139-142: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 141-141: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/routers/resources.py` around lines 139 - 142, The handler
currently raises
HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed
to complete upload transaction: {str(e)}") which leaks internal exception text
(variable e) to clients; instead, log the full exception internally (e.g.,
logger.exception or logging.exception with the context in the same function
where the raise appears) and change the HTTPException detail to a generic
message like "Failed to complete upload transaction" or "Internal server error
while completing upload transaction" while preserving the 500 status; ensure you
reference the same raise HTTPException and status.HTTP_500_INTERNAL_SERVER_ERROR
locations and remove direct inclusion of str(e) from the client-facing detail.

Comment on lines +67 to +76
<Button
className="w-full h-12 text-base font-bold shadow-lg shadow-primary/20"
onClick={() => {
// Future: Redirect to checkout or open Razorpay
console.log("Redirecting to payment...");
onClose();
}}
>
Upgrade Now — $9/mo
</Button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wire the primary CTA to an actual upgrade action.

Line 71 only logs and closes the dialog, so “Upgrade Now” does not upgrade or redirect users.

💡 Suggested fix
 interface UpgradeModalProps {
   isOpen: boolean;
   onClose: () => void;
+  onUpgrade: () => void;
   message?: string;
 }
 
-export function UpgradeModal({ isOpen, onClose, message }: UpgradeModalProps) {
+export function UpgradeModal({ isOpen, onClose, onUpgrade, message }: UpgradeModalProps) {
@@
           <Button 
             className="w-full h-12 text-base font-bold shadow-lg shadow-primary/20"
             onClick={() => {
-              // Future: Redirect to checkout or open Razorpay
-              console.log("Redirecting to payment...");
-              onClose();
+              onUpgrade();
             }}
           >
             Upgrade Now — $9/mo
           </Button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/UpgradeModal.tsx` around lines 67 - 76, The Button in
UpgradeModal.tsx currently only logs and calls onClose in its onClick handler;
replace the console.log with a real upgrade flow by invoking a dedicated upgrade
function (e.g., a prop like onUpgrade or a local helper such as
initiateCheckout/initiateRazorpay) from the Button's onClick, handle async
errors and loading state (disable the Button while pending), and only call
onClose after a successful redirect/checkout initiation; update the UpgradeModal
component signature to accept an onUpgrade prop if needed and use
Button/onClick/onClose to trigger that flow.

Comment on lines +45 to +52
<Button
variant="ghost"
size="icon"
onClick={handleDismiss}
className="size-8 text-muted-foreground hover:text-foreground"
>
<X className="size-4" />
</Button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an accessible label to the dismiss icon button.

The close control is icon-only, so assistive tech won’t get a clear action name.

♿ Suggested fix
         <Button 
           variant="ghost" 
           size="icon" 
           onClick={handleDismiss}
+          aria-label="Dismiss welcome banner"
           className="size-8 text-muted-foreground hover:text-foreground"
         >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
variant="ghost"
size="icon"
onClick={handleDismiss}
className="size-8 text-muted-foreground hover:text-foreground"
>
<X className="size-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={handleDismiss}
aria-label="Dismiss welcome banner"
className="size-8 text-muted-foreground hover:text-foreground"
>
<X className="size-4" />
</Button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/WelcomeBanner.tsx` around lines 45 - 52, The dismiss
control in WelcomeBanner.tsx is an icon-only Button (component Button with
onClick={handleDismiss} and child <X />) and needs an accessible name; update
the Button to provide an accessible label (e.g., add aria-label="Dismiss" or
aria-label="Close" or include a visually-hidden text child) so screen readers
can announce the action, ensuring the existing handleDismiss and X icon usage
remain unchanged.

@shubhamxdd shubhamxdd merged commit 4f13ba2 into main May 19, 2026
1 check was pending
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