Skip to content

feat: anon login claim opportunities#3463

Merged
capJavert merged 7 commits intomainfrom
anon-login-claim-opportunity
Jan 23, 2026
Merged

feat: anon login claim opportunities#3463
capJavert merged 7 commits intomainfrom
anon-login-claim-opportunity

Conversation

@capJavert
Copy link
Copy Markdown
Contributor

@capJavert capJavert commented Jan 20, 2026

  • rename email column to identifier, to make it more generic and reusable
  • refactor anonUserId to ClaimableItem (anon flow for recruiter)
  • add option to claim anon opportunities by logged in user
  • tests

@capJavert capJavert self-assigned this Jan 20, 2026
@pulumi
Copy link
Copy Markdown

pulumi Bot commented Jan 20, 2026

🍹 The Update (preview) for dailydotdev/api/prod (at 6f82be4) was successful.

Resource Changes

    Name                                                   Type                           Operation
~   vpc-native-deployment                                  kubernetes:apps/v1:Deployment  update
~   vpc-native-hourly-notification-cron                    kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-stale-user-transactions-cron          kubernetes:batch/v1:CronJob    update
~   vpc-native-update-current-streak-cron                  kubernetes:batch/v1:CronJob    update
~   vpc-native-personalized-digest-deployment              kubernetes:apps/v1:Deployment  update
-   vpc-native-api-db-migration-006e7d95                   kubernetes:batch/v1:Job        delete
~   vpc-native-post-analytics-history-day-clickhouse-cron  kubernetes:batch/v1:CronJob    update
~   vpc-native-expire-super-agent-trial-cron               kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-gifted-plus-cron                      kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-zombie-user-companies-cron            kubernetes:batch/v1:CronJob    update
~   vpc-native-update-highlighted-views-cron               kubernetes:batch/v1:CronJob    update
~   vpc-native-user-profile-updated-sync-cron              kubernetes:batch/v1:CronJob    update
~   vpc-native-sync-subscription-with-cio-cron             kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-zombie-images-cron                    kubernetes:batch/v1:CronJob    update
-   vpc-native-api-clickhouse-migration-006e7d95           kubernetes:batch/v1:Job        delete
~   vpc-native-post-analytics-clickhouse-cron              kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-zombie-opportunities-cron             kubernetes:batch/v1:CronJob    update
~   vpc-native-check-analytics-report-cron                 kubernetes:batch/v1:CronJob    update
~   vpc-native-calculate-top-readers-cron                  kubernetes:batch/v1:CronJob    update
~   vpc-native-private-deployment                          kubernetes:apps/v1:Deployment  update
~   vpc-native-clean-zombie-users-cron                     kubernetes:batch/v1:CronJob    update
~   vpc-native-validate-active-users-cron                  kubernetes:batch/v1:CronJob    update
+   vpc-native-api-db-migration-c689979c                   kubernetes:batch/v1:Job        create
~   vpc-native-update-tag-recommendations-cron             kubernetes:batch/v1:CronJob    update
~   vpc-native-update-source-public-threshold-cron         kubernetes:batch/v1:CronJob    update
~   vpc-native-update-trending-cron                        kubernetes:batch/v1:CronJob    update
~   vpc-native-personalized-digest-cron                    kubernetes:batch/v1:CronJob    update
~   vpc-native-update-source-tag-view-cron                 kubernetes:batch/v1:CronJob    update
~   vpc-native-daily-digest-cron                           kubernetes:batch/v1:CronJob    update
~   vpc-native-generic-referral-reminder-cron              kubernetes:batch/v1:CronJob    update
~   vpc-native-update-tags-str-cron                        kubernetes:batch/v1:CronJob    update
~   vpc-native-update-views-cron                           kubernetes:batch/v1:CronJob    update
+   vpc-native-api-clickhouse-migration-c689979c           kubernetes:batch/v1:Job        create
~   vpc-native-temporal-deployment                         kubernetes:apps/v1:Deployment  update
~   vpc-native-bg-deployment                               kubernetes:apps/v1:Deployment  update
~   vpc-native-ws-deployment                               kubernetes:apps/v1:Deployment  update
~   vpc-native-generate-search-invites-cron                kubernetes:batch/v1:CronJob    update

@capJavert capJavert marked this pull request as ready for review January 20, 2026 22:28
@capJavert capJavert requested a review from a team as a code owner January 20, 2026 22:28
Comment thread src/migration/1768937354866-ClaimableItemIdentifier.ts
@capJavert
Copy link
Copy Markdown
Contributor Author

@claude review

@claude
Copy link
Copy Markdown

claude Bot commented Jan 22, 2026

Claude finished @capJavert's task in 2m 10s —— View job


Code Review

I've reviewed this PR that refactors the ClaimableItem entity from email-specific to a more generic identifier-based approach and adds the ability for logged-in users to claim anonymous opportunities. Overall, the implementation is solid with good test coverage. Here are my findings:

Strengths

1. Good Migration Design

  • The migration cleanly renames emailidentifier with proper index handling
  • @omBratteng's suggestion to use /* sql */ template literals is valid and improves readability, though the current version is functionally correct

2. Comprehensive Test Coverage

  • Excellent test suite for claimOpportunities mutation (src/schema/opportunity.ts:3037-3051)
  • Tests cover all edge cases: authentication, already claimed items, items with organizations, missing opportunityId, upsert handling
  • Cron test properly verifies cascading deletion of claimable items (src/cron/cleanZombieOpportunities.ts:32-41)

3. Transaction Safety

  • claimAnonOpportunities properly uses transaction to ensure atomicity (src/common/opportunity/user.ts:22-75)
  • Error handling returns empty array on failure rather than throwing, which is appropriate for this operation

4. Proper Security

  • claimOpportunities mutation requires authentication via @auth directive
  • Only opportunities without an organizationId can be claimed, preventing unauthorized takeover (src/common/opportunity/user.ts:40)

Issues & Recommendations

1. Potential Bug in Cron - Empty IDs Check ⚠️
src/cron/cleanZombieOpportunities.ts:36-40

The cron job filters out falsy items but this check is insufficient:

.where(`flags->>'opportunityId' IN (:...ids)`, {
  ids: raw
    .filter((item: { id: string }) => item)
    .map((item: { id: string }) => item.id),
})

Problem: If raw is empty (no opportunities deleted), the SQL IN (:...ids) clause will receive an empty array, which causes a SQL error in TypeORM.

Fix: Add early return or check for empty array:

if (raw.length === 0 || raw.every((item: { id: string }) => !item)) {
  logger.info({ count: 0, claimables: 0 }, 'zombies opportunities cleaned! 🧟');
  return;
}

const opportunityIds = raw
  .filter((item: { id: string }) => item?.id)
  .map((item: { id: string }) => item.id);

if (opportunityIds.length === 0) {
  logger.info({ count: affected, claimables: 0 }, 'zombies opportunities cleaned! 🧟');
  return;
}

const { affected: claimables } = await entityManager
  .getRepository(ClaimableItem)
  .createQueryBuilder()
  .delete()
  .where(`flags->>'opportunityId' IN (:...ids)`, { ids: opportunityIds })
  .execute();

Fix this →

2. Inconsistent Null Handling in claimAnonOpportunities
src/common/opportunity/user.ts:37

.filter((item) => item.flags.opportunityId)

This filter is good but the type system doesn't reflect that opportunityId is required. The subsequent map could technically receive undefined values if the filter fails.

Recommendation: Add TypeScript type guard or use optional chaining in map:

.filter((item): item is ClaimableItem & { flags: { opportunityId: string } } => 
  !!item.flags.opportunityId
)
.map((item) => item.flags.opportunityId)

3. Migration Feedback - Consider IF EXISTS/IF NOT EXISTS
src/migration/1768937354866-ClaimableItemIdentifier.ts:7-12

The migration uses DROP INDEX "public"."IDX_claimable_item_email" without IF EXISTS. While this should work in normal deployment flow, it's more defensive to include conditional checks as suggested in @omBratteng's review comment.

Current: Works but could fail on rollback/replay
Suggested: Use DROP INDEX IF EXISTS and CREATE INDEX IF NOT EXISTS (as shown in the review comment)

4. Missing Transaction Await ⚠️
src/cron/cleanZombieOpportunities.ts:14

con.transaction(async (entityManager) => {

The transaction is not awaited! This means the cron handler returns before the transaction completes.

Fix:

await con.transaction(async (entityManager) => {

Fix this →

5. Consider Adding Index on claimedById
src/entity/ClaimableItem.ts:48

The entity has queries filtering by claimedById: IsNull() (src/common/opportunity/user.ts:28), but there's no index on this column. Consider adding an index if this table grows large:

@Column({ nullable: true })
@Index('IDX_claimable_item_claimed_by_id')
claimedById?: string;

This is optional but could improve query performance as the table scales.

6. Minor: Hardcoded Magic Number
src/cron/cleanZombieOpportunities.ts:12

const timeThreshold = subDays(new Date(), 2);

Per CLAUDE.md guidelines, prefer using constants from src/common/constants.ts. Consider:

const timeThreshold = subDays(new Date(), 2 * ONE_DAY_IN_DAYS); // if constant exists

Though for this case, the inline 2 is acceptable since it's a business rule specific to this cron.

Summary

Must Fix (Blocking):

Should Consider:

Nice to Have:

The core functionality is well-implemented with solid test coverage. After addressing the two critical issues (#1 and #4), this PR will be good to merge.


Copy link
Copy Markdown
Contributor

@rebelchris rebelchris left a comment

Choose a reason for hiding this comment

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

Looks solid to me

@capJavert capJavert enabled auto-merge (squash) January 23, 2026 08:57
@capJavert capJavert merged commit b796cf6 into main Jan 23, 2026
10 checks passed
@capJavert capJavert deleted the anon-login-claim-opportunity branch January 23, 2026 09:04
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.

4 participants