CommunityPro / CommunityPro/community-pro-api
Mentorship: messaging thread + unread tracking (Phase 6.5)
- Dominant language
- C#
- Stars
- 0
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
## Scope
Per-mentorship message thread. **Polling v1 — no websockets** (frontend polls with `refetchInterval: 5000` while the thread is open).
## Data model
```csharp
public sealed class ThreadMessage
{
public Guid Id { get; set; }
public Guid MentorshipId { get; set; }
public Guid SenderId { get; set; }
public string Body { get; set; }
public DateTimeOffset SentAt { get; set; }
}
// plus LastReadAt per participant (e.g. MentorshipParticipantState) for unread counts
```
## Endpoints (participants only)
| Method | Path | Notes |
|---|---|---|
| GET | `/mentorship/{id}/messages?after=` | incremental fetch for polling; `after` = cursor (message id or timestamp); capped page size |
| POST | `/mentorship/{id}/messages` | body length-capped (~2000 chars), trimmed, non-empty |
| POST | `/mentorship/{id}/read` | stamp caller's `LastReadAt` |
Unread counts (per mentorship, for the dashboard) derived from `LastReadAt` — expose on the `GET /mentorship/mine` payload from #10 rather than a separate endpoint.
## Rules
- Only `Active` mentorship participants can post; reading history stays allowed after conclusion.
- Plain text only in v1 — no HTML rendering path at all.
## Error codes
`mentorship.not_found`, `mentorship.forbidden`, `mentorship.invalid_message`, `mentorship.invalid_state`
## Dependencies
Blocked by #10.
## Acceptance criteria
- [ ] `after` cursor returns strictly newer messages, stable ordering
- [ ] Non-participant gets not_found-shaped forbidden (no existence leak)
- [ ] Unread counts correct across both participants
- [ ] Posting to a concluded mentorship rejected
### 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.