Skip to content

fix: address PR 414 review feedback#415

Merged
is0692vs merged 1 commit into
stagingfrom
pr414-review
Apr 13, 2026
Merged

fix: address PR 414 review feedback#415
is0692vs merged 1 commit into
stagingfrom
pr414-review

Conversation

@is0692vs

@is0692vs is0692vs commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Resolve the open review feedback on PR #414 by restoring concurrent cleanup/deletion in apps/api/src/routes/papers.ts and updating the Playwright JSON assertions to use toEqual with full payloads.

Greptile Summary

PR #414 のレビュー指摘に対応し、papers.ts のアップロードエラー時クリーンアップを並列R2バッチ削除方式に戻すとともに、PlaywrightのAPIアサーションを toEqual による完全ペイロード検証に更新しています。

  • P1: アップロードのcatchブロック(line 707)で db.delete(papers) がエラーを投げると throw error が実行されず、元のアップロードエラーがDBエラーに上書きされます。また、既にインサート済みの papers レコードが孤立してDBに残ります。.catch() で吸収してから元のエラーを再スローしてください。

Confidence Score: 4/5

P1バグ(アップロードキャッチブロックでの db.delete エラーハンドリング欠如)が残っているためマージ前に修正が必要。

E2Eテストの修正は適切で問題なし。papers.ts のR2並列削除の復元も意図通り。しかしアップロードエラーのcatchブロックで db.delete が失敗した場合に孤立レコードが残り元エラーが失われるP1バグが存在し、スコアは4。

apps/api/src/routes/papers.ts(特にL707のクリーンアップ処理)

Important Files Changed

Filename Overview
apps/api/src/routes/papers.ts アップロードエラーのキャッチブロックで db.delete(papers) が例外を投げると元のエラーが上書きされ孤立レコードが残るP1バグがある。DELETEエンドポイントの並列R2削除は復元済み。
apps/e2e/tests/scenarios/auth-flow.spec.ts toEqual による完全ペイロードアサーション(expect.any(String) で動的フィールドを吸収)に更新され、より強固なテストになっている。
apps/e2e/tests/scenarios/backend-auth-e2e.spec.ts 全テストケースで toEqual 完全ペイロードアサーションが適用されており、PR#414 のレビュー指摘に対応した修正として適切。

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[POST /api/papers - アップロード開始] --> B[pMap: R2ファイル並列アップロード]
    B -->|成功| C[db.insert papers]
    B -->|失敗| E[catch block]
    C --> D[db.insert paperAuthors / paperOrgs / paperFiles]
    D -->|成功| F[201 OK]
    D -->|失敗| E
    E --> G[Promise.all: R2クリーンアップ - エラー無視]
    G --> H[db.delete papers ⚠️ エラーハンドリングなし]
    H -->|成功| I[throw 元エラー]
    H -->|失敗⚠️| J[DBエラー伝播 - 孤立レコード残存]

    K[DELETE /api/papers/:id] --> L[権限確認]
    L --> M[R2ファイルキー取得]
    M --> N[Promise.all: R2バッチ削除 並列実行]
    N -->|成功| O[db.delete papers]
    N -->|部分失敗⚠️| P[500 - 一部R2ファイルが消える可能性]
    O --> Q[200 OK]
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/api/src/routes/papers.ts
Line: 707-708

Comment:
**クリーンアップの `db.delete` エラーが元のエラーを上書きする**

`db.delete(papers)` が例外を投げると、`throw error`(元のアップロードエラー)は実行されず、DBエラーがそのまま伝播します。その結果、ファイルのない孤立した `papers` レコードがDBに残り(ステップ 3〜5 でパーパーが既にインサートされていた場合)、ユーザーへ返るエラーも元の原因とは無関係のDB例外になります。`.catch()` でエラーを飲み込んだ上で元のエラーを再スローしてください。

```suggestion
        await db.delete(papers).where(eq(papers.id, paperId)).catch((dbErr) => {
            console.error("Failed to clean up paper record during rollback:", dbErr instanceof Error ? dbErr.message : String(dbErr));
        });
        throw error;
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/api/src/routes/papers.ts
Line: 1298-1303

Comment:
**R2 バッチ削除が失敗した際にファイルが部分的に消える可能性**

`Promise.all(deletePromises)` がリジェクトすると `db.delete(papers)` はスキップされ、ペーパーレコードはDBに残ります(ユーザーに 500 が返る)。ただし Promise.all は既にスタート済みの他バッチをキャンセルしないため、複数バッチがある場合に一部の R2 ファイルだけが削除されてしまいます。R2 エラーをキャッチして DB 削除まで進めるか、DBから先に削除することを検討してください。

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: address PR 414 review feedback" | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

Context used:

  • Context used - 日本語で!!! (source)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 55ba2706-15ee-43c3-8657-399713b6b670

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pr414-review

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.

@vercel

vercel Bot commented Apr 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
open-shelf Ignored Ignored Apr 13, 2026 2:45am

@codspeed-hq

codspeed-hq Bot commented Apr 13, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 2 untouched benchmarks
⏩ 15 skipped benchmarks1


Comparing pr414-review (121365b) with staging (a6721a1)

Open in CodSpeed

Footnotes

  1. 15 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@coderabbitai coderabbitai Bot added the javascript Pull requests that update javascript code label Apr 13, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request parallelizes Cloudflare R2 object deletions using Promise.all and updates E2E tests to use stricter equality assertions with complete user object schemas. Review feedback indicates that the Cloudflare R2 delete method does not support bulk deletion via arrays as implemented; it is recommended to use pMap for concurrent single-key deletions while avoiding side effects within the mapping function to adhere to repository standards.

Comment on lines +697 to +706
const cleanupPromises: Promise<void>[] = [];
for (let i = 0; i < uploadedKeys.length; i += 1000) {
try {
await c.env.BUCKET.delete(uploadedKeys.slice(i, i + 1000));
} /* v8 ignore start */ catch (e) {
// Ignore cleanup errors
console.error("Cleanup failed intentionally:", e instanceof Error ? e.message : String(e));
} /* v8 ignore stop */
cleanupPromises.push(
c.env.BUCKET.delete(uploadedKeys.slice(i, i + 1000)).catch((e) => {
// Ignore cleanup errors
console.error("Cleanup failed intentionally:", e instanceof Error ? e.message : String(e));
}),
);
}
await Promise.all(cleanupPromises);

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.

high

The Cloudflare R2 delete method expects a single string key, not an array of keys. Passing an array (via slice) will likely fail to delete the intended objects. Use pMap for concurrent deletion. To adhere to repository rules, ensure the mapper function returns a result object instead of performing side effects like logging, which should be handled after the mapping is complete.

        const cleanupResults = await pMap(
            uploadedKeys,
            (key) =>
                c.env.BUCKET.delete(key)
                    .then(() => ({ success: true }))
                    .catch((e) => ({ success: false, error: e })),
            { concurrency: MAX_CONCURRENT_UPLOADS },
        );

        cleanupResults.forEach((res) => {
            if (!res.success) {
                console.error("Cleanup failed intentionally:", res.error instanceof Error ? res.error.message : String(res.error));
            }
        });
References
  1. Avoid side effects in mapper functions (like in pMap). Instead of modifying external state, the mapper should return a result object, and the collection of results should be processed afterwards.

Comment on lines +1298 to +1302
const deletePromises: Promise<void>[] = [];
for (let i = 0; i < keys.length; i += 1000) {
await c.env.BUCKET.delete(keys.slice(i, i + 1000));
deletePromises.push(c.env.BUCKET.delete(keys.slice(i, i + 1000)));
}
await Promise.all(deletePromises);

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.

high

The R2 delete method does not support bulk deletion with an array of keys. Use pMap for concurrent deletion. Additionally, to maintain consistency with repository patterns, the mapper function should return a result object rather than performing side effects (like logging) directly inside the mapper.

Suggested change
const deletePromises: Promise<void>[] = [];
for (let i = 0; i < keys.length; i += 1000) {
await c.env.BUCKET.delete(keys.slice(i, i + 1000));
deletePromises.push(c.env.BUCKET.delete(keys.slice(i, i + 1000)));
}
await Promise.all(deletePromises);
const deletionResults = await pMap(
keys,
(key) =>
c.env.BUCKET.delete(key)
.then(() => ({ success: true }))
.catch((e) => ({ success: false, error: e })),
{ concurrency: MAX_CONCURRENT_UPLOADS },
);
deletionResults.forEach((res) => {
if (!res.success) {
console.error("File deletion failed:", res.error instanceof Error ? res.error.message : String(res.error));
}
});
References
  1. Avoid side effects in mapper functions (like in pMap). Instead of modifying external state, the mapper should return a result object, and the collection of results should be processed afterwards.

Comment on lines 707 to 708
await db.delete(papers).where(eq(papers.id, paperId));
throw error;

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.

P1 クリーンアップの db.delete エラーが元のエラーを上書きする

db.delete(papers) が例外を投げると、throw error(元のアップロードエラー)は実行されず、DBエラーがそのまま伝播します。その結果、ファイルのない孤立した papers レコードがDBに残り(ステップ 3〜5 でパーパーが既にインサートされていた場合)、ユーザーへ返るエラーも元の原因とは無関係のDB例外になります。.catch() でエラーを飲み込んだ上で元のエラーを再スローしてください。

Suggested change
await db.delete(papers).where(eq(papers.id, paperId));
throw error;
await db.delete(papers).where(eq(papers.id, paperId)).catch((dbErr) => {
console.error("Failed to clean up paper record during rollback:", dbErr instanceof Error ? dbErr.message : String(dbErr));
});
throw error;
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/api/src/routes/papers.ts
Line: 707-708

Comment:
**クリーンアップの `db.delete` エラーが元のエラーを上書きする**

`db.delete(papers)` が例外を投げると、`throw error`(元のアップロードエラー)は実行されず、DBエラーがそのまま伝播します。その結果、ファイルのない孤立した `papers` レコードがDBに残り(ステップ 3〜5 でパーパーが既にインサートされていた場合)、ユーザーへ返るエラーも元の原因とは無関係のDB例外になります。`.catch()` でエラーを飲み込んだ上で元のエラーを再スローしてください。

```suggestion
        await db.delete(papers).where(eq(papers.id, paperId)).catch((dbErr) => {
            console.error("Failed to clean up paper record during rollback:", dbErr instanceof Error ? dbErr.message : String(dbErr));
        });
        throw error;
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +1298 to 1303
const deletePromises: Promise<void>[] = [];
for (let i = 0; i < keys.length; i += 1000) {
await c.env.BUCKET.delete(keys.slice(i, i + 1000));
deletePromises.push(c.env.BUCKET.delete(keys.slice(i, i + 1000)));
}
await Promise.all(deletePromises);
await db.delete(papers).where(eq(papers.id, paperId));

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.

P2 R2 バッチ削除が失敗した際にファイルが部分的に消える可能性

Promise.all(deletePromises) がリジェクトすると db.delete(papers) はスキップされ、ペーパーレコードはDBに残ります(ユーザーに 500 が返る)。ただし Promise.all は既にスタート済みの他バッチをキャンセルしないため、複数バッチがある場合に一部の R2 ファイルだけが削除されてしまいます。R2 エラーをキャッチして DB 削除まで進めるか、DBから先に削除することを検討してください。

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/api/src/routes/papers.ts
Line: 1298-1303

Comment:
**R2 バッチ削除が失敗した際にファイルが部分的に消える可能性**

`Promise.all(deletePromises)` がリジェクトすると `db.delete(papers)` はスキップされ、ペーパーレコードはDBに残ります(ユーザーに 500 が返る)。ただし Promise.all は既にスタート済みの他バッチをキャンセルしないため、複数バッチがある場合に一部の R2 ファイルだけが削除されてしまいます。R2 エラーをキャッチして DB 削除まで進めるか、DBから先に削除することを検討してください。

How can I resolve this? If you propose a fix, please make it concise.

@is0692vs
is0692vs merged commit f85d39e into staging Apr 13, 2026
15 checks passed
@codecov

codecov Bot commented Apr 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.00000% with 1 line in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
apps/api/src/routes/papers.ts 90.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@is0692vs
is0692vs deleted the pr414-review branch April 16, 2026 04:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

javascript Pull requests that update javascript code size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant