Skip to content

refactor: extract shared event helpers, eliminate duplication#51

Merged
KalebCole merged 3 commits intomainfrom
sasha/code-review-cleanup
Mar 27, 2026
Merged

refactor: extract shared event helpers, eliminate duplication#51
KalebCole merged 3 commits intomainfrom
sasha/code-review-cleanup

Conversation

@KalebCole
Copy link
Copy Markdown
Owner

@KalebCole KalebCole commented Mar 27, 2026

Summary

Full code review and cleanup pass on the partiful-cli codebase. Main focus: eliminating duplication across the largest files.

Changes Made

New file: src/lib/events.js (shared helpers)

Extracted repeated patterns into a single shared module:

  • buildBaseEvent() — event payload construction (was duplicated in events.js, bulk.js, and helpers/clone.js)
  • confirm() — readline yes/no prompt (was duplicated in events.js and blasts.js)
  • toFirestoreMap() — recursive Firestore field conversion (moved from events.js to shared lib)
  • resolvePosterImage() — poster catalog lookup (was copy-pasted across create/update/clone)
  • resolveUploadImage() — image file/URL upload handling (was copy-pasted across create/update/clone)
  • validateImageOptions() — mutual exclusivity check for --poster/--poster-search/--image
  • buildLinks() — link array construction from CLI options
  • DEFAULT_GUEST_STATUS_COUNTS — was literally copy-pasted as a 16-key object 3 times

src/commands/events.js (824 → 558 lines, -32%)

  • Replaced all duplicated logic with calls to shared helpers
  • Added local makePayload() and handleError() to reduce repeated boilerplate
  • Removed unused readline and path imports

src/commands/bulk.js (315 → 275 lines)

  • Now uses shared buildBaseEvent() instead of local buildEventPayload() duplicate
  • Fixed inconsistent error handling: was dynamically importing PartifulError inside catch block
  • Now imports PartifulError at top level like every other file

src/commands/blasts.js (118 → 106 lines)

  • Removed duplicate confirm() function, now imports from shared lib
  • Removed unused MAX_BLASTS_PER_EVENT constant (was defined but never used)

Bug fixes

  • Errors from poster/image resolution now throw typed errors (NotFoundError, ValidationError) instead of generic Error, preserving correct exit codes in JSON output

Issues Found But Not Fixed

  • helpers/clone.js duplicates ~80% of events clone — these are two separate commands (+clone vs events clone) with slightly different option sets. Merging them would change the public CLI interface.
  • generateSeries() in bulk.js is defined but unused (dead code for a feature that lives in events create --repeat). Kept it since it may be intended for future bulk series support.
  • Doctor command writes JSON directly to stdout instead of using jsonOutput() — intentional since it also writes a human-readable table to stderr.

Metrics

  • Lines before: 3885
  • Lines after: 3729
  • Net reduction: 156 lines (-4%)
  • Files modified: 3 changed + 1 new
  • All 106 tests pass ✅

Summary by CodeRabbit

  • Bug Fixes

    • More consistent and clearer error reporting across bulk and event commands; non-specific errors now surface readable messages.
  • Refactor

    • Centralized event construction and payload wrapping for consistent create/clone/update/cancel behavior.
    • Consolidated image/poster selection, upload, and validation flows.
    • Unified confirmation prompts and standardized dry-run vs real-run behavior.
  • Documentation

    • Clarified timezone handling in date parsing.

Changes:
- Extract shared lib/events.js with:
  - buildBaseEvent(): event payload construction (was duplicated in events.js, bulk.js, clone.js)
  - confirm(): readline prompt (was duplicated in events.js and blasts.js)
  - toFirestoreMap(): Firestore field conversion (moved from events.js)
  - resolvePosterImage(): poster lookup logic (was duplicated in create/update/clone)
  - resolveUploadImage(): image upload handling (was duplicated in create/update/clone)
  - validateImageOptions(): mutual exclusivity check
  - buildLinks(): link array construction
  - DEFAULT_GUEST_STATUS_COUNTS: was copy-pasted 3x

- events.js: 824 → 558 lines (-32%), extracted makePayload() and handleError() helpers
- bulk.js: 315 → 275 lines, now uses shared buildBaseEvent instead of local duplicate
- blasts.js: 118 → 106 lines, removed duplicate confirm() and unused MAX_BLASTS_PER_EVENT
- Fixed: bulk.js catch block was dynamically importing PartifulError (inconsistent)
- Fixed: errors from poster/image helpers now throw typed errors (NotFoundError, ValidationError)
  instead of generic Error, preserving correct exit codes

Total: 3885 → 3729 lines (-156, -4%)
Files modified: 4 (3 changed + 1 new)
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1ac6b1ad-68f2-4828-971f-60f8523597d5

📥 Commits

Reviewing files that changed from the base of the PR and between 46e8087 and 43de7d9.

📒 Files selected for processing (1)
  • src/commands/bulk.js

📝 Walkthrough

Walkthrough

Extracted event/image/upload/confirmation and Firestore-mapping helpers into a new shared module (src/lib/events.js) and updated src/commands/events.js, src/commands/bulk.js, and src/commands/blasts.js to consume those helpers; also clarified timezone docs in src/lib/dates.js.

Changes

Cohort / File(s) Summary
New Shared Library
src/lib/events.js
Added centralized utilities and constants: confirm(), buildBaseEvent(), buildLinks(), resolveUploadImage(), resolvePosterImage(), validateImageOptions(), toFirestoreMap(), isUrl(), ALLOWED_IMAGE_EXTENSIONS, DEFAULT_GUEST_STATUS_COUNTS, plus temp-file/download/upload orchestration and Firestore mapping helpers.
Events Command Refactor
src/commands/events.js
Replaced local implementations with imports from ../lib/events.js; added local makePayload() and handleError() wrappers; unified create/clone/update/cancel flows to use buildBaseEvent, buildLinks, image/poster resolution, and shared payload wrapping; removed duplicated validation and Firestore conversion code.
Bulk Command Refactor
src/commands/bulk.js
Introduced makePayload() and handleError(); switched bulk create to use buildBaseEvent(...).event and makePayload(config, ...) for /createEvent payloads; unified error reporting to handle PartifulError and normalized error messages.
Blast Confirmation Change
src/commands/blasts.js
Removed local readline-based confirm() and MAX_BLASTS_PER_EVENT; now imports confirm from ../lib/events.js (confirmation control flow and --yes bypass unchanged).
Dates Documentation
src/lib/dates.js
Expanded parseDateTime() header comment to clarify timezone handling limitation (documentation-only change).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hopped through files with nimble paws,
Pulled out copies, smoothed the laws.
One library now holds the keys,
Posters, uploads, confirms with ease.
A tidy burrow — code at peace. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor: extract shared event helpers, eliminate duplication' accurately summarizes the main change: refactoring to extract shared utilities into a new module and removing duplicated code across multiple command files.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sasha/code-review-cleanup

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

Copy link
Copy Markdown

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
src/commands/bulk.js (1)

105-120: ⚠️ Potential issue | 🟠 Major

Bulk poster columns are silently ignored now.

normalizeRow() still accepts poster and posterSearch, but both the dry-run path and the live create loop now only use buildBaseEvent(n).event. Since buildBaseEvent() never resolves media, rows with those columns will succeed and create events without the requested poster. Either resolve them here with the shared poster helpers or reject those columns explicitly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/bulk.js` around lines 105 - 120, The dry-run and live create
paths are currently using only buildBaseEvent(n).event which ignores
poster/posterSearch resolved by normalizeRow; update both paths to resolve
poster media before sending: in the dry-run branch call the shared
poster-resolution helper (the same logic used elsewhere to turn
poster/posterSearch into resolved media) and include that resolved poster in the
object returned from buildBaseEvent for jsonOutput, and in the live-create loop
replace payload = { data: wrapPayload(config, { event }) } with a payload that
includes the resolved poster (e.g., resolvePoster/attachPoster result merged
into event) so poster/posterSearch are honored; alternatively, if
poster/posterSearch are unsupported here, explicitly validate normalizeRow
output and throw/reject when poster or posterSearch are present so callers get a
clear error instead of silent omission.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/events.js`:
- Around line 18-29: The bulk create code currently builds the payload as {
data: wrapPayload(config, { event }) } which sends payload.data.event instead of
payload.data.params.event; import makePayload from events.js and replace that
inline payload with makePayload(config, { event }) so the wrapper produces
payload.data.params.event; update the bulk create call site that constructs the
payload (the code that uses wrapPayload for events) to call makePayload(config,
{ event }) and remove the direct wrapPayload usage for this case.

In `@src/lib/events.js`:
- Around line 59-66: buildBaseEvent serializes startDate/endDate with the
machine timezone because parseDateTime ignores the timezone parameter; update
the implementation so the timezone parameter is honored before producing the ISO
string. Specifically, modify parseDateTime (in src/lib/dates.js) to
construct/convert the Date in the provided timezone (or return a timezone-aware
ISO) and then have buildBaseEvent continue to call parseDateTime(opts.date,
opts.timezone) and use the returned value.toISOString(); ensure parseDateTime
uses the timezone argument to calculate the correct instant so event.startDate /
event.endDate match event.timezone.
- Around line 42-47: The exported function validateImageExtension is broken:
replace the incorrect await_extname_sync call and wrong variable usage by
importing/using Node's path.extname and computing const ext =
path.extname(filePath).toLowerCase(); then check if
ALLOWED_IMAGE_EXTENSIONS.includes(ext) and throw the Error with the existing
message if not; alternatively, if the function is unused across the codebase you
can remove the exported validateImageExtension symbol instead. Ensure the
implementation references the function name validateImageExtension and the
variable ext so callers get a working check.

---

Outside diff comments:
In `@src/commands/bulk.js`:
- Around line 105-120: The dry-run and live create paths are currently using
only buildBaseEvent(n).event which ignores poster/posterSearch resolved by
normalizeRow; update both paths to resolve poster media before sending: in the
dry-run branch call the shared poster-resolution helper (the same logic used
elsewhere to turn poster/posterSearch into resolved media) and include that
resolved poster in the object returned from buildBaseEvent for jsonOutput, and
in the live-create loop replace payload = { data: wrapPayload(config, { event })
} with a payload that includes the resolved poster (e.g.,
resolvePoster/attachPoster result merged into event) so poster/posterSearch are
honored; alternatively, if poster/posterSearch are unsupported here, explicitly
validate normalizeRow output and throw/reject when poster or posterSearch are
present so callers get a clear error instead of silent omission.
🪄 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: d2f7a3e6-8d7e-45ad-8e3e-1e2b4cd48325

📥 Commits

Reviewing files that changed from the base of the PR and between efce329 and 00aae66.

📒 Files selected for processing (4)
  • src/commands/blasts.js
  • src/commands/bulk.js
  • src/commands/events.js
  • src/lib/events.js

Comment on lines +59 to +66
export function buildBaseEvent(opts) {
const startDate = parseDateTime(opts.date, opts.timezone);
const endDate = opts.endDate ? parseDateTime(opts.endDate, opts.timezone) : null;

const event = {
title: opts.title,
startDate: startDate.toISOString(),
timezone: opts.timezone || 'America/Los_Angeles',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

buildBaseEvent() still serializes dates in the process timezone.

buildBaseEvent() passes opts.timezone into parseDateTime(), but src/lib/dates.js Lines 6-60 never use that parameter when constructing the Date. So these lines can emit startDate/endDate in the machine timezone while also setting event.timezone to the requested zone. Now that create, clone, and bulk all share this helper, --timezone can shift events by hours.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/events.js` around lines 59 - 66, buildBaseEvent serializes
startDate/endDate with the machine timezone because parseDateTime ignores the
timezone parameter; update the implementation so the timezone parameter is
honored before producing the ISO string. Specifically, modify parseDateTime (in
src/lib/dates.js) to construct/convert the Date in the provided timezone (or
return a timezone-aware ISO) and then have buildBaseEvent continue to call
parseDateTime(opts.date, opts.timezone) and use the returned
value.toISOString(); ensure parseDateTime uses the timezone argument to
calculate the correct instant so event.startDate / event.endDate match
event.timezone.

…mezone docs

- Fix bulk create payload: use makePayload() wrapper so /createEvent gets
  data.params.event (not data.event)
- Remove broken validateImageExtension() — called nonexistent function,
  was never used anywhere
- Document timezone limitation in dates.js
Copy link
Copy Markdown

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/bulk.js`:
- Around line 150-151: Create a small helper named handleError(thrown) that
normalizes any thrown value before calling jsonError: if thrown is a
PartifulError call jsonError(thrown.message, thrown.exitCode, thrown.type,
thrown.details); else if thrown is an Error call jsonError(thrown.message ||
String(thrown), /*exitCode*/1); else if typeof thrown === 'string' call
jsonError(thrown, 1); otherwise call jsonError(JSON.stringify(thrown, null, 2)
|| String(thrown), 1). Replace the two existing branches in src/commands/bulk.js
(the spots currently doing `if (e instanceof PartifulError) ... else
jsonError(e.message)`) to just call handleError(e) so non-Error throw values are
safely handled.
🪄 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: abd0ebe3-16a4-4401-8b9c-0064e003756b

📥 Commits

Reviewing files that changed from the base of the PR and between 00aae66 and 46e8087.

📒 Files selected for processing (3)
  • src/commands/bulk.js
  • src/lib/dates.js
  • src/lib/events.js
✅ Files skipped from review due to trivial changes (2)
  • src/lib/dates.js
  • src/lib/events.js

@KalebCole KalebCole merged commit 46ad212 into main Mar 27, 2026
1 check was pending
@KalebCole KalebCole deleted the sasha/code-review-cleanup branch March 27, 2026 03:31
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