CommunityPro / CommunityPro/community-pro-api

Mentorship: request lifecycle + capacity enforcement (Phase 6.3)

Open
#10 0 comments 0 reactions 0 assignees View on GitHub
mentorship
Dominant language
C#
Stars
0
Forks
0
PR merge metrics
No merged PRs in 30d

Description

## Scope
Mentee-initiated requests through to active mentorships.

## Lifecycle
`Pending → Accepted ⇒ ActiveMentorship | Declined(reason) | Withdrawn` — then `Active → Concluded` (either side).

## Data model
```csharp
public sealed class MentorshipRequest
{
public Guid Id { get; set; }
public Guid MentorId { get; set; }
public Guid MenteeId { get; set; }
public string Message { get; set; }
public RequestStatus Status { get; set; } // Pending | Accepted | Declined | Withdrawn
public string? DeclineReason { get; set; }
}

public sealed class Mentorship
{
public Guid Id { get; set; }
public Guid MentorId { get; set; }
public Guid MenteeId { get; set; }
public MentorshipStatus Status { get; set; } // Active | Concluded
}
```

## Endpoints (active-member policy)
| Method | Path | Notes |
|---|---|---|
| POST | `/mentorship/requests` | mentee → mentor; message + goals |
| GET | `/mentorship/requests/incoming` / `outgoing` | paginated |
| POST | `/mentorship/requests/{id}/accept` | mentor only; creates `Mentorship` |
| POST | `/mentorship/requests/{id}/decline` | mentor only; reason optional but stored |
| POST | `/mentorship/requests/{id}/withdraw` | mentee only |
| GET | `/mentorship/mine` | active + concluded mentorships for caller |
| POST | `/mentorship/{id}/conclude` | either participant |

## Rules
- **Capacity on accept:** active mentorships < `Capacity`, else `409 mentorship.at_capacity`. Hitting capacity auto-sets `IsAcceptingMentees = false` (manual re-enable when a mentorship concludes — do not auto-flip back).
- No duplicate open request to the same mentor; no self-mentorship; mentor must be accepting.
- Brevo (existing `IEmailSender`, best-effort): request received → mentor; accepted/declined → mentee.

## Error codes
`mentorship.not_found`, `mentorship.at_capacity`, `mentorship.not_accepting`, `mentorship.duplicate_request`, `mentorship.invalid_state`, `mentorship.forbidden`

## Dependencies
Blocked by #9.

## Acceptance criteria
- [ ] Full transition matrix tested incl. wrong-party actions (mentee accepting, etc.)
- [ ] Capacity race: two concurrent accepts at capacity-1 → exactly one succeeds (constraint or transaction, tested)
- [ ] Emails logged-not-thrown on failure

### Conventions (project-wide, non-negotiable)
- .NET 9, records for immutable shapes, file-scoped namespaces, primary constructors where they read well. Minimal-API endpoints grouped per module via `IEndpointModule.MapEndpoints`.
- `Result` (SharedKernel) instead of exception-driven control flow. Endpoint results map failures to ProblemDetails with the stable error codes listed above — the frontend keys off them.
- Module owns its EF Core `DbContext` mapped to its own Postgres schema. Modules never reference each other's internals — cross-module needs go through a public contract interface or an in-process domain event (`IEventPublisher`).
- All external calls (GitHub, Stripe, Brevo, Cloudinary, Meilisearch) behind interfaces owned by the consuming module.
- Every list endpoint paginated (offset is fine). xUnit tests in `tests/CommunityPro.Tests//` following the existing harness patterns (see `Members/MembersTestHarness.cs`).

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.