Skip to content

Comments

Add member_for_more_than_three_years account attribute#27

Open
alltheseas wants to merge 1 commit intodamus-io:masterfrom
alltheseas:elsat/three-stars-api
Open

Add member_for_more_than_three_years account attribute#27
alltheseas wants to merge 1 commit intodamus-io:masterfrom
alltheseas:elsat/three-stars-api

Conversation

@alltheseas
Copy link

@alltheseas alltheseas commented Feb 13, 2026

Summary

  • Add member_for_more_than_three_years boolean to the account attributes response
  • Uses the same 360-day-year convention as the existing one-year check (3 × 360 = 1080 days)
  • Compute total_membership_time once and reuse for both thresholds

Line count breakdown

6 lines implementation, 92 lines test

Companion PR

iOS client: damus-io/damus#3592

Test plan

  • Updated router_config.test.js to expect the new field
  • Verify accounts with < 1080 days return member_for_more_than_three_years: false
  • Verify accounts with > 1080 days return member_for_more_than_three_years: true
  • Verify inactive accounts return false for both attributes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added active membership duration tracking to account information, providing accurate tenure metrics.
  • Bug Fixes

    • Improved membership eligibility calculation to accurately reflect only active account time, ensuring correct status for inactive accounts.

@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

Reworks membership-time computation: introduces a local total_membership_time (zero for inactive accounts), uses it to set active_membership_duration in the account payload, and derives member_for_more_than_one_year from that value. No public signatures changed.

Changes

Cohort / File(s) Summary
Membership duration logic
src/user_management.js
Introduces total_membership_time and uses it to populate attributes.active_membership_duration; computes member_for_more_than_one_year from that value and adjusts conditional flow so membership time is calculated only for active accounts. No exported signatures changed.
Tests — membership-tenure assertions
test/router_config.test.js
Adds import of get_account_info_payload; replaces a single response equality check with granular assertions including member_for_more_than_one_year and active_membership_duration across multiple scenarios (new account, >1 year, >3 years, inactive). Uses nested t.test blocks with explicit t.end() in subtests.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I tally hops and seconds stored,
Active clocks that I adored,
A gentle number, soft and bright,
Durations counted, snug and right,
I munch a carrot, code restored.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title states 'Add member_for_more_than_three_years account attribute', but the actual changes show implementation of total_membership_time computation and active_membership_duration field, with member_for_more_than_one_year already present. The member_for_more_than_three_years attribute is not actually implemented in the code changes shown. Update the title to accurately reflect the actual implementation, such as 'Add active_membership_duration attribute and refactor membership time computation' or verify that member_for_more_than_three_years implementation is included in the changeset.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@alltheseas
Copy link
Author

Companion iOS PR: damus-io/damus#3592 — adds the triple star badge UI and parses the member_for_more_than_three_years attribute from this API response.

@alltheseas alltheseas force-pushed the elsat/three-stars-api branch from 6b0ae7d to 14155d4 Compare February 13, 2026 20:00
@alltheseas
Copy link
Author

@jb55 for your review, complementary PR to iOS Damus three star PR damus-io/damus#3592

Return active_membership_duration (seconds) in the attributes payload
so the client can compute badge tiers locally. Keeps
member_for_more_than_one_year for backward compat with shipped clients.

Signed-off-by: elsat <elsat@protonmail.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@alltheseas alltheseas force-pushed the elsat/three-stars-api branch from 14155d4 to 8d16a41 Compare February 20, 2026 02:44
Copy link

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

🧹 Nitpick comments (1)
test/router_config.test.js (1)

168-169: Missing assertion message strings

Lines 168–169 omit descriptive messages, unlike the equivalent assertion at line 147. Consistent messages aid in diagnosing failures.

♻️ Proposed fix
- t.ok(payload.attributes.active_membership_duration > one_year_in_seconds)
- t.ok(payload.attributes.active_membership_duration < 3 * one_year_in_seconds)
+ t.ok(payload.attributes.active_membership_duration > one_year_in_seconds, 'duration should exceed one year')
+ t.ok(payload.attributes.active_membership_duration < 3 * one_year_in_seconds, 'duration should be less than three years')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/router_config.test.js` around lines 168 - 169, Add descriptive assertion
messages to the two t.ok calls that check
payload.attributes.active_membership_duration so failures are easier to
diagnose: update the t.ok(payload.attributes.active_membership_duration >
one_year_in_seconds) and t.ok(payload.attributes.active_membership_duration < 3
* one_year_in_seconds) calls to include a third argument string (e.g.
"active_membership_duration is greater than one year" and
"active_membership_duration is less than three years") matching the style used
earlier in the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/router_config.test.js`:
- Around line 88-98: The test for the account route is missing an assertion for
the new attributes.member_for_more_than_three_years field; update the test in
router_config.test.js (the block using t.equal and res.body.attributes) to
assert that res.body.attributes.member_for_more_than_three_years is false for
this ~30-day legacy account, placing the assertion alongside the existing
member_for_more_than_one_year check so the test verifies both flags.
- Around line 109-193: Add assertions for the new
member_for_more_than_three_years attribute in each sub-test that calls
get_account_info_payload: for the "new account" test assert
payload.attributes.member_for_more_than_three_years === false; for the "> 3
years" test assert payload.attributes.member_for_more_than_three_years === true;
for the "> 1 year but < 3 years" test assert
payload.attributes.member_for_more_than_three_years === false; and for the
"inactive account" test assert
payload.attributes.member_for_more_than_three_years === false (also ensure the
inactive test still checks payload.active and active_membership_duration as
before).

---

Nitpick comments:
In `@test/router_config.test.js`:
- Around line 168-169: Add descriptive assertion messages to the two t.ok calls
that check payload.attributes.active_membership_duration so failures are easier
to diagnose: update the t.ok(payload.attributes.active_membership_duration >
one_year_in_seconds) and t.ok(payload.attributes.active_membership_duration < 3
* one_year_in_seconds) calls to include a third argument string (e.g.
"active_membership_duration is greater than one year" and
"active_membership_duration is less than three years") matching the style used
earlier in the test.

Comment on lines +88 to +98
t.equal(res.body.pubkey, account_info.pubkey)
t.equal(res.body.created_at, account_info.created_at)
t.equal(res.body.subscriber_number, 1)
t.equal(res.body.expiry, account_info.expiry)
t.equal(res.body.active, true)
t.equal(res.body.testflight_url, null)
t.equal(res.body.attributes.member_for_more_than_one_year, false)
// Legacy account with 30-day past + 30-day future expiry yields ~60 days of membership
const sixty_days = 60 * 24 * 60 * 60
t.ok(res.body.attributes.active_membership_duration > sixty_days - 10, 'duration should be approximately 60 days')
t.ok(res.body.attributes.active_membership_duration < sixty_days + 10, 'duration should be approximately 60 days')
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing member_for_more_than_three_years assertion in route test

The route test validates member_for_more_than_one_year but never asserts on the new member_for_more_than_three_years field. Since the account is only ~30 days old, the expected value is false.

🐛 Proposed fix
  t.equal(res.body.attributes.member_for_more_than_one_year, false)
+ t.equal(res.body.attributes.member_for_more_than_three_years, false)
📝 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
t.equal(res.body.pubkey, account_info.pubkey)
t.equal(res.body.created_at, account_info.created_at)
t.equal(res.body.subscriber_number, 1)
t.equal(res.body.expiry, account_info.expiry)
t.equal(res.body.active, true)
t.equal(res.body.testflight_url, null)
t.equal(res.body.attributes.member_for_more_than_one_year, false)
// Legacy account with 30-day past + 30-day future expiry yields ~60 days of membership
const sixty_days = 60 * 24 * 60 * 60
t.ok(res.body.attributes.active_membership_duration > sixty_days - 10, 'duration should be approximately 60 days')
t.ok(res.body.attributes.active_membership_duration < sixty_days + 10, 'duration should be approximately 60 days')
t.equal(res.body.pubkey, account_info.pubkey)
t.equal(res.body.created_at, account_info.created_at)
t.equal(res.body.subscriber_number, 1)
t.equal(res.body.expiry, account_info.expiry)
t.equal(res.body.active, true)
t.equal(res.body.testflight_url, null)
t.equal(res.body.attributes.member_for_more_than_one_year, false)
t.equal(res.body.attributes.member_for_more_than_three_years, false)
// Legacy account with 30-day past + 30-day future expiry yields ~60 days of membership
const sixty_days = 60 * 24 * 60 * 60
t.ok(res.body.attributes.active_membership_duration > sixty_days - 10, 'duration should be approximately 60 days')
t.ok(res.body.attributes.active_membership_duration < sixty_days + 10, 'duration should be approximately 60 days')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/router_config.test.js` around lines 88 - 98, The test for the account
route is missing an assertion for the new
attributes.member_for_more_than_three_years field; update the test in
router_config.test.js (the block using t.equal and res.body.attributes) to
assert that res.body.attributes.member_for_more_than_three_years is false for
this ~30-day legacy account, placing the assertion alongside the existing
member_for_more_than_one_year check so the test verifies both flags.

Comment on lines +109 to +193
t.test('new account returns duration and member_for_more_than_one_year false', async (t) => {
const account = {
pubkey: 'abc123',
created_at: current_time() - thirty_days_in_seconds,
expiry: current_time() + thirty_days_in_seconds,
transactions: [{
type: 'iap',
id: '1',
start_date: current_time() - thirty_days_in_seconds,
end_date: current_time() + thirty_days_in_seconds,
purchased_date: current_time() - thirty_days_in_seconds,
duration: null
}]
}
const payload = get_account_info_payload(1, account)
t.equal(payload.attributes.member_for_more_than_one_year, false)
t.ok(payload.attributes.active_membership_duration > 0, 'duration should be positive for active account')
t.ok(payload.attributes.active_membership_duration < one_year_in_seconds, 'duration should be less than one year')
t.end()
})

t.test('account with > 3 years returns correct duration', async (t) => {
const total_duration = 3 * one_year_in_seconds + 1
const account = {
pubkey: 'abc123',
created_at: current_time() - total_duration,
expiry: current_time() + thirty_days_in_seconds,
transactions: [{
type: 'iap',
id: '1',
start_date: current_time() - total_duration,
end_date: current_time(),
purchased_date: current_time() - total_duration,
duration: null
}]
}
const payload = get_account_info_payload(1, account)
t.equal(payload.attributes.member_for_more_than_one_year, true)
t.ok(payload.attributes.active_membership_duration > 3 * one_year_in_seconds, 'duration should exceed three years')
t.end()
})

t.test('account with > 1 year but < 3 years returns correct duration', async (t) => {
const total_duration = one_year_in_seconds + 1
const account = {
pubkey: 'abc123',
created_at: current_time() - total_duration,
expiry: current_time() + thirty_days_in_seconds,
transactions: [{
type: 'iap',
id: '1',
start_date: current_time() - total_duration,
end_date: current_time(),
purchased_date: current_time() - total_duration,
duration: null
}]
}
const payload = get_account_info_payload(1, account)
t.equal(payload.attributes.member_for_more_than_one_year, true)
t.ok(payload.attributes.active_membership_duration > one_year_in_seconds)
t.ok(payload.attributes.active_membership_duration < 3 * one_year_in_seconds)
t.end()
})

t.test('inactive account returns zero duration', async (t) => {
const total_duration = 3 * one_year_in_seconds + 1
const account = {
pubkey: 'abc123',
created_at: current_time() - total_duration,
expiry: current_time() - 1, // expired
transactions: [{
type: 'iap',
id: '1',
start_date: current_time() - total_duration,
end_date: current_time() - 1,
purchased_date: current_time() - total_duration,
duration: null
}]
}
const payload = get_account_info_payload(1, account)
t.equal(payload.active, false)
t.equal(payload.attributes.member_for_more_than_one_year, false)
t.equal(payload.attributes.active_membership_duration, 0)
t.end()
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

New test suite never asserts on member_for_more_than_three_years — the primary attribute added by this PR

All four sub-tests check member_for_more_than_one_year and active_membership_duration but completely omit the member_for_more_than_three_years attribute. This contradicts the stated test plan ("Verify accounts with > 1080 days return member_for_more_than_three_years: true", "Verify inactive accounts return false for both attributes").

Expected assertions per sub-test:

Test case member_for_more_than_three_years
New account (30 days) false
> 3 years true
> 1 year but < 3 years false
Inactive false
🐛 Proposed additions
  // new account test (~line 124)
  t.equal(payload.attributes.member_for_more_than_one_year, false)
+ t.equal(payload.attributes.member_for_more_than_three_years, false)

  // > 3 years test (~line 146)
  t.equal(payload.attributes.member_for_more_than_one_year, true)
+ t.equal(payload.attributes.member_for_more_than_three_years, true)

  // > 1 year but < 3 years test (~line 167)
  t.equal(payload.attributes.member_for_more_than_one_year, true)
+ t.equal(payload.attributes.member_for_more_than_three_years, false)

  // inactive account test (~line 190)
  t.equal(payload.attributes.member_for_more_than_one_year, false)
+ t.equal(payload.attributes.member_for_more_than_three_years, false)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/router_config.test.js` around lines 109 - 193, Add assertions for the
new member_for_more_than_three_years attribute in each sub-test that calls
get_account_info_payload: for the "new account" test assert
payload.attributes.member_for_more_than_three_years === false; for the "> 3
years" test assert payload.attributes.member_for_more_than_three_years === true;
for the "> 1 year but < 3 years" test assert
payload.attributes.member_for_more_than_three_years === false; and for the
"inactive account" test assert
payload.attributes.member_for_more_than_three_years === false (also ensure the
inactive test still checks payload.active and active_membership_duration as
before).

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