chore(task): archive 07-03-invite-link-limits

This commit is contained in:
2026-07-03 05:00:01 -07:00
parent 9fd2088a2a
commit 0a77e87d55
6 changed files with 2 additions and 2 deletions

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,82 @@
# Invite Link Limits Design
## Architecture
This change spans the persisted invitation model, the invitation create API, the registration consumption transaction, and the organization settings UI.
The invitation row remains the source of truth. The existing `expires_at` column continues to represent validity. New persisted counters will represent usage limits:
- `max_uses`: maximum successful invitation registrations allowed.
- `used_count`: number of successful registrations that have consumed the invitation.
## Data Model
Add non-null integer columns to `organization_invitations`:
- `max_uses integer not null default 1`
- `used_count integer not null default 0`
Existing invitation rows remain compatible: previous one-time invites become `max_uses = 1`, `used_count = 0` unless already accepted. The existing `status = accepted` already prevents use of accepted legacy rows.
## Invitation Creation Contract
`POST /api/organizations/[id]/invitations` accepts:
- `email?: string`
- `roleId: string`
- `validityDays?: number`
- `maxUses?: number`
Defaults:
- `validityDays = 7`
- `maxUses = 1`
Validation:
- `roleId` is still required.
- `validityDays` must be a positive integer in an operationally reasonable range.
- `maxUses` must be a positive integer in an operationally reasonable range.
- Invalid input returns `jsonFailure(...)` with the existing structured API shape.
## Invitation Consumption
Registration only counts a use after account creation and membership insertion succeed inside the existing transaction.
An invitation is valid only if:
- `status === "active"`
- `expiresAt >= now`
- `usedCount < maxUses`
On successful invitation registration:
- Increment `usedCount`.
- If the increment reaches `maxUses`, set `status = "accepted"` and fill `acceptedByAccountId` / `acceptedAt` with the consuming account details.
- If remaining uses exist, keep `status = "active"` and leave accepted metadata empty.
The update should guard against concurrent over-consumption by updating with a `used_count < max_uses` predicate and checking that a row was returned.
## UI
`OrganizationInviteDialog` adds controls for:
- Validity days, default 7.
- Maximum uses, default 1.
`OrganizationDetailClient` displays:
- Expiry time.
- Usage as `usedCount / maxUses`.
Copy behavior remains unchanged.
## Compatibility
The existing `/register?invite=...` URL format remains unchanged.
Existing rows without the new columns are migrated through default values. Existing one-time semantics are preserved by default.
## Rollback
If needed, UI inputs can be hidden and backend defaults can keep the old behavior. Dropping the columns would require a reverse migration only if deployed migrations must be rolled back.

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,42 @@
# Invite Link Limits Implementation Plan
## Checklist
1. Update Trellis planning artifacts and get approval to start implementation.
2. Load pre-development specs with `trellis-before-dev`.
3. Update `modules/core/server/schema.ts`:
- Add `maxUses` and `usedCount` integer columns to `organizationInvitations`.
4. Generate or add the Drizzle migration for the new columns.
5. Update shared invitation types and row mappers:
- `modules/core/types.ts`
- `modules/core/server/settings.ts`
- `modules/core/server/store.ts`
6. Update invitation creation API:
- Parse `validityDays` and `maxUses`.
- Validate positive integer ranges.
- Persist `expiresAt`, `maxUses`, and `usedCount`.
7. Update invitation registration flow:
- Reject exhausted links.
- Increment usage inside the transaction.
- Mark status accepted only when `usedCount` reaches `maxUses`.
- Guard concurrent consumption with an update predicate.
8. Update frontend:
- Add validity and maximum-use controls to `OrganizationInviteDialog`.
- Display usage count in `OrganizationDetailClient`.
- Update invitation explanatory text where it still says invitations are one-time only.
9. Run validation:
- `pnpm lint`
- `pnpm type-check`
- `pnpm test` if tests are relevant or fast enough after type changes.
- `pnpm build` if lint/type-check pass.
## Risk Points
- Concurrent registrations could over-consume an invite unless the update predicate checks `used_count < max_uses`.
- Existing accepted invitations should remain unusable regardless of migrated counter defaults.
- Frontend numeric inputs submit strings, so backend parsing must not trust the client.
## Rollback Points
- Before migration: schema/API/UI changes can be reverted together.
- After migration: reverting code while keeping columns is safe because defaults preserve one-use invite behavior.

View File

@@ -0,0 +1,48 @@
# Invite link limits
## Goal
Allow organization admins to configure invitation link validity and usage limits when creating invite links.
This should make invite links safer to distribute by letting admins choose how long a link remains valid and how many times it may be used.
## Confirmed Facts
- Invitation links are created from `modules/settings/components/OrganizationInviteDialog.tsx`.
- The invitation create API is `app/api/organizations/[id]/invitations/route.ts`.
- Invitation persistence is backed by the `organization_invitations` table in `modules/core/server/schema.ts`.
- Invitations already have an `expiresAt` column and are currently created with a fixed 7-day validity window.
- Registration consumes invitation tokens through `modules/core/server/auth.ts`.
- The current registration flow marks an invitation as `accepted` after one successful invitation registration, so existing invite links behave as single-use links.
- Invitation rows are shown in `modules/settings/components/OrganizationDetailClient.tsx`, currently with target, role, status, expiry, and copy action.
## Requirements
- Admins can configure an invitation link's validity period when creating an organization invitation.
- Admins can configure the maximum usage count for an invitation link when creating an organization invitation.
- Existing behavior remains compatible by default: if the admin does not change the controls, a new invite should expire after 7 days and be usable once.
- The backend validates create-invitation inputs and rejects invalid limit values with structured API failures.
- The registration path rejects expired or exhausted invitation links.
- Invitation list data exposes enough information to show configured limits and current usage.
- The UI makes the configured validity and usage limit visible when creating and reviewing invitations.
- Maximum access count means maximum successful invitation registrations, not page loads or token preview requests.
## Out of Scope
- Invitation revocation management beyond existing status behavior.
- Editing limits after an invitation has been created.
- Tracking anonymous page views unless product intent explicitly requires page-load based access counting.
## Acceptance Criteria
- [x] Creating an invitation with default form values produces a 7-day, one-use invitation.
- [x] Creating an invitation with a custom validity period stores and displays the corresponding expiry.
- [x] Creating an invitation with a custom maximum usage count stores and displays the configured usage limit.
- [x] Registration with an expired invitation fails with the existing invalid/expired invitation behavior.
- [x] Registration after the invitation has reached its maximum allowed uses fails.
- [x] Successful invitation registration increments usage accounting and only exhausts the link when the configured maximum is reached.
- [x] Type-check, lint, and relevant tests/build checks pass.
## Notes
- Recommended interpretation: count successful invitation registrations as usage. Counting page loads would make links vulnerable to browser refreshes, previews, bots, and accidental visits, and it would require adding a separate token validation/view endpoint.

View File

@@ -0,0 +1,26 @@
{
"id": "invite-link-limits",
"name": "invite-link-limits",
"title": "Invite link limits",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul",
"createdAt": "2026-07-03",
"completedAt": "2026-07-03",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}