CommunityPro / CommunityPro/community-pro-api

Content: Post entity, tags, and approval workflow (Phase 4.1)

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

Description

## Scope
New `CommunityPro.Content` module: `content` Postgres schema, `ContentDbContext`, migrations, and the `Post` aggregate with its status workflow. Domain logic only — endpoints come in follow-up tickets.

## Workflow
`Draft → Submitted → (Approved ⇒ Published) | Rejected(reason) → Draft`

## Data model (content schema)
```csharp
public sealed class Post
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; }
public string Title { get; set; }
public string Slug { get; set; } // unique, generated from title, immutable after publish
public string Excerpt { get; set; }
public string ContentJson { get; set; } // TipTap JSON (stored for re-editing)
public string ContentHtml { get; set; } // sanitized render, produced server-side
public string? CoverImageUrl { get; set; } // Cloudinary
public List Tags { get; set; } // jsonb; e.g. Engineering | Onboarding | Community Insights
public PostStatus Status { get; set; } // Draft | Submitted | Approved | Rejected | Published
public bool IsFeatured { get; set; } // single feature slot
public string? RejectionReason { get; set; }
public DateTimeOffset? PublishedAt { get; set; }
}
```

## Rules
- Slug: generated, unique (DB constraint), immutable once published.
- Status transitions validated in a domain service returning `Result` — illegal transitions yield `content.invalid_state`.
- `IsFeatured` is a single slot: setting it clears the previous featured post (same pattern as members featured flag).
- Rejection requires a reason; rejected posts return to `Draft` keeping the reason for the author to see.

## Error codes
`content.not_found`, `content.invalid_state`, `content.invalid_post`

## Acceptance criteria
- [ ] Module scaffold matches existing modules (Members is the reference): DbContext, schema, `IEndpointModule` stub, DI extension
- [ ] EF migration creates the table with slug unique constraint
- [ ] Transition table fully tested (every legal + illegal transition)
- [ ] Featured single-slot behavior tested

### 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

Research direction

Read the existing Members module as the reference, including tests/CommunityPro.Tests/Members/MembersTestHarness.cs, and trace the IEndpointModule.MapEndpoints and DI extension conventions. Then map the ContentDbContext, content schema migration, Post status transition service, and tests for every transition and featured-slot behavior; done means all listed acceptance criteria and error codes are covered.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, postgresql
Domain
backend, databases
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.