+3
-3
aggregators/kagi-news/src/coves_client.py
+3
-3
aggregators/kagi-news/src/coves_client.py
·········
·········
+2
-2
cmd/server/main.go
+2
-2
cmd/server/main.go
······
···+postJetstreamURL = "ws://localhost:6008/subscribe?wantedCollections=social.coves.community.post"···
+17
-10
cmd/validate-lexicon/main.go
+17
-10
cmd/validate-lexicon/main.go
···validationErrors = append(validationErrors, fmt.Sprintf("Failed to resolve schema %s (from %s): %v", schemaID, schemaFiles[i], err))···
···validationErrors = append(validationErrors, fmt.Sprintf("Failed to resolve schema %s (from %s): %v", schemaID, schemaFiles[i], err))···
+5
-5
docs/COMMUNITY_FEEDS.md
+5
-5
docs/COMMUNITY_FEEDS.md
···············
···············
+1
-1
docs/FEED_SYSTEM_IMPLEMENTATION.md
+1
-1
docs/FEED_SYSTEM_IMPLEMENTATION.md
+1
-1
docs/PRD_GOVERNANCE.md
+1
-1
docs/PRD_GOVERNANCE.md
···
···
+17
-17
docs/PRD_POSTS.md
+17
-17
docs/PRD_POSTS.md
·········-- [x] **Handler:** `POST /xrpc/social.coves.post.create` ✅ (Alpha - see IMPLEMENTATION_POST_CREATION.md)···- [x] **E2E Test:** Create text post → Write to **community's PDS** → Index via Jetstream → Verify in AppView ✅·········- ⚠️ **Derive post characteristics:** DEFERRED (embed_type, text_length, has_title, has_embed for content rules filtering)···············
·········+- [x] **Handler:** `POST /xrpc/social.coves.community.post.create` ✅ (Alpha - see IMPLEMENTATION_POST_CREATION.md)···- [x] **E2E Test:** Create text post → Write to **community's PDS** → Index via Jetstream → Verify in AppView ✅·········- ⚠️ **Derive post characteristics:** DEFERRED (embed_type, text_length, has_title, has_embed for content rules filtering)···············
+4
-4
docs/aggregators/PRD_AGGREGATORS.md
+4
-4
docs/aggregators/PRD_AGGREGATORS.md
············
············
+3
-3
docs/aggregators/PRD_KAGI_NEWS_RSS.md
+3
-3
docs/aggregators/PRD_KAGI_NEWS_RSS.md
······
······
+1
-1
internal/api/handlers/post/create.go
+1
-1
internal/api/handlers/post/create.go
+7
-7
internal/api/routes/post.go
+7
-7
internal/api/routes/post.go
···func RegisterPostRoutes(r chi.Router, service posts.Service, authMiddleware *middleware.AtProtoAuthMiddleware) {-r.With(authMiddleware.RequireAuth).Post("/xrpc/social.coves.post.create", createHandler.HandleCreate)-// r.With(authMiddleware.RequireAuth).Post("/xrpc/social.coves.post.update", updateHandler.HandleUpdate)-// r.With(authMiddleware.RequireAuth).Post("/xrpc/social.coves.post.delete", deleteHandler.HandleDelete)
···func RegisterPostRoutes(r chi.Router, service posts.Service, authMiddleware *middleware.AtProtoAuthMiddleware) {+r.With(authMiddleware.RequireAuth).Post("/xrpc/social.coves.community.post.create", createHandler.HandleCreate)+// r.With(authMiddleware.RequireAuth).Post("/xrpc/social.coves.community.post.update", updateHandler.HandleUpdate)+// r.With(authMiddleware.RequireAuth).Post("/xrpc/social.coves.community.post.delete", deleteHandler.HandleDelete)
+11
-11
internal/atproto/jetstream/post_consumer.go
+11
-11
internal/atproto/jetstream/post_consumer.go
···············// This prevents users from creating posts that appear to be from communities they don't control······
···············// This prevents users from creating posts that appear to be from communities they don't control······
-86
internal/atproto/lexicon/social/coves/interaction/comment.json
-86
internal/atproto/lexicon/social/coves/interaction/comment.json
···
···
-75
internal/atproto/lexicon/social/coves/interaction/createComment.json
-75
internal/atproto/lexicon/social/coves/interaction/createComment.json
···
···
-41
internal/atproto/lexicon/social/coves/interaction/deleteComment.json
-41
internal/atproto/lexicon/social/coves/interaction/deleteComment.json
···
···
-39
internal/atproto/lexicon/social/coves/post/crosspost.json
-39
internal/atproto/lexicon/social/coves/post/crosspost.json
···
···
-99
internal/atproto/lexicon/social/coves/post/getCrosspostChain.json
-99
internal/atproto/lexicon/social/coves/post/getCrosspostChain.json
···
···
-129
internal/atproto/lexicon/social/coves/post/record.json
-129
internal/atproto/lexicon/social/coves/post/record.json
···-"description": "DID of the user who created this post. Server-populated from authenticated session; clients MUST NOT provide this field. Required for attribution, moderation, and accountability."-"description": "For microblog posts - information about the original author from federated platform"
···
+20
-7
internal/core/posts/post.go
+20
-7
internal/core/posts/post.go
···············
···············
+19
-17
internal/core/posts/service.go
+19
-17
internal/core/posts/service.go
············
············
+47
internal/db/migrations/015_alter_content_labels_to_jsonb.sql
+47
internal/db/migrations/015_alter_content_labels_to_jsonb.sql
···
···+-- Change content_labels from TEXT[] to JSONB to preserve full com.atproto.label.defs#selfLabels structure+COMMENT ON COLUMN posts.content_labels IS 'Self-applied labels per com.atproto.label.defs#selfLabels (JSONB: {"values":[{"val":"nsfw","neg":false}]})';
+11
-8
internal/db/postgres/feed_repo.go
+11
-8
internal/db/postgres/feed_repo.go
············// TODO(feed-generator): Implement viewer state (saved, voted, blocked) in feed generator skeleton···
············// TODO(feed-generator): Implement viewer state (saved, voted, blocked) in feed generator skeleton···
+10
-7
internal/db/postgres/feed_repo_base.go
+10
-7
internal/db/postgres/feed_repo_base.go
···············
···············
+12
-20
internal/db/postgres/post_repo.go
+12
-20
internal/db/postgres/post_repo.go
···············
······+// post.ContentLabels contains com.atproto.label.defs#selfLabels JSON: {"values":[{"val":"nsfw","neg":false}]}·········
+11
-11
internal/db/postgres/vote_repo_test.go
+11
-11
internal/db/postgres/vote_repo_test.go
··················-_, err := repo.GetByVoterAndSubject(ctx, "did:plc:nobody", "at://did:plc:community/social.coves.post.record/nopost")···············
··················+_, err := repo.GetByVoterAndSubject(ctx, "did:plc:nobody", "at://did:plc:community/social.coves.community.post/nopost")···············
+3
-3
internal/validation/lexicon.go
+3
-3
internal/validation/lexicon.go
······
······+// ResolveReference resolves a schema reference (e.g., "social.coves.community.post.get#postView")
+7
-3
internal/validation/lexicon_test.go
+7
-3
internal/validation/lexicon_test.go
·········
·········
+8
-8
tests/integration/aggregator_e2e_test.go
+8
-8
tests/integration/aggregator_e2e_test.go
···-req := httptest.NewRequest("POST", "/xrpc/social.coves.post.create", bytes.NewReader(reqJSON))······-req := httptest.NewRequest("POST", "/xrpc/social.coves.post.create", bytes.NewReader(reqJSON))···-req := httptest.NewRequest("POST", "/xrpc/social.coves.post.create", bytes.NewReader(reqJSON))······-req := httptest.NewRequest("POST", "/xrpc/social.coves.post.create", bytes.NewReader(reqJSON))···-req := httptest.NewRequest("POST", "/xrpc/social.coves.post.create", bytes.NewReader(reqJSON))
···+req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON))······+req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON))···+req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON))···+req = httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON))···+req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON))···+req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON))
+4
-4
tests/integration/aggregator_test.go
+4
-4
tests/integration/aggregator_test.go
······if err := aggRepo.RecordAggregatorPost(ctx, aggregatorDID, communityDID, postURI, "bafy123"); err != nil {···if err := aggRepo.RecordAggregatorPost(ctx, aggregatorDID, communityDID, postURI, "bafy123"); err != nil {···if err := aggRepo.RecordAggregatorPost(ctx, aggregatorDID, communityDID, postURI, "bafy123"); err != nil {
······if err := aggRepo.RecordAggregatorPost(ctx, aggregatorDID, communityDID, postURI, "bafy123"); err != nil {···if err := aggRepo.RecordAggregatorPost(ctx, aggregatorDID, communityDID, postURI, "bafy123"); err != nil {···if err := aggRepo.RecordAggregatorPost(ctx, aggregatorDID, communityDID, postURI, "bafy123"); err != nil {
+1
-1
tests/integration/feed_test.go
+1
-1
tests/integration/feed_test.go
···-assert.Equal(t, "social.coves.post.record", record["$type"], "Record should have correct $type")
···+assert.Equal(t, "social.coves.community.post", record["$type"], "Record should have correct $type")
+1
-1
tests/integration/helpers.go
+1
-1
tests/integration/helpers.go
+41
-21
tests/integration/post_creation_test.go
+41
-21
tests/integration/post_creation_test.go
··········································
··········································
+15
-15
tests/integration/post_e2e_test.go
+15
-15
tests/integration/post_e2e_test.go
···························-req := httptest.NewRequest("POST", "/xrpc/social.coves.post.create", bytes.NewReader(reqJSON))···-jetstreamURL := fmt.Sprintf("ws://%s:6008/subscribe?wantedCollections=social.coves.post.record",···
···························+req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON))···+jetstreamURL := fmt.Sprintf("ws://%s:6008/subscribe?wantedCollections=social.coves.community.post",···
+13
-13
tests/integration/post_handler_test.go
+13
-13
tests/integration/post_handler_test.go
···-req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.post.create", bytes.NewReader(body))···-req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.post.create", bytes.NewReader(body))···-req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.post.create", bytes.NewReader(body))···-req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.post.create", bytes.NewReader(invalidJSON))···-req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.post.create", bytes.NewReader(body))···-req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.post.create", bytes.NewReader(body))···-req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.post.create", bytes.NewReader(body))···-req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.post.create", bytes.NewReader(body))···-req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.post.create", bytes.NewReader(body))···-req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.post.create", bytes.NewReader(body))······-req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.post.create", bytes.NewReader(body))···-req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.post.create", bytes.NewReader(body))
···+req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.community.post.create", bytes.NewReader(body))···+req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.community.post.create", bytes.NewReader(body))···+req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.community.post.create", bytes.NewReader(body))···+req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.community.post.create", bytes.NewReader(invalidJSON))···+req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.community.post.create", bytes.NewReader(body))···+req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.community.post.create", bytes.NewReader(body))···+req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.community.post.create", bytes.NewReader(body))···+req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.community.post.create", bytes.NewReader(body))···+req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.community.post.create", bytes.NewReader(body))···+req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.community.post.create", bytes.NewReader(body))······+req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.community.post.create", bytes.NewReader(body))···+req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.community.post.create", bytes.NewReader(body))
+1
-1
tests/integration/timeline_test.go
+1
-1
tests/integration/timeline_test.go
···-assert.Equal(t, "social.coves.post.record", record["$type"], "Record should have correct $type")
···+assert.Equal(t, "social.coves.community.post", record["$type"], "Record should have correct $type")
+1
-1
tests/lexicon-test-data/actor/saved-invalid-type.json
+1
-1
tests/lexicon-test-data/actor/saved-invalid-type.json
+1
-1
tests/lexicon-test-data/actor/saved-valid.json
+1
-1
tests/lexicon-test-data/actor/saved-valid.json
+12
-3
tests/lexicon-test-data/interaction/comment-invalid-content.json
+12
-3
tests/lexicon-test-data/interaction/comment-invalid-content.json
···
+12
-7
tests/lexicon-test-data/interaction/comment-valid-sticker.json
+12
-7
tests/lexicon-test-data/interaction/comment-valid-sticker.json
···
···
+26
-20
tests/lexicon-test-data/interaction/comment-valid-text.json
+26
-20
tests/lexicon-test-data/interaction/comment-valid-text.json
···-"text": "Great post! I especially liked the part about @alice.example.com's contribution to the project.",
···+"content": "Great post! I especially liked the part about @alice.example.com's contribution to the project.",
+1
-1
tests/lexicon-test-data/moderation/tribunal-vote-invalid-decision.json
+1
-1
tests/lexicon-test-data/moderation/tribunal-vote-invalid-decision.json
+1
-1
tests/lexicon-test-data/moderation/tribunal-vote-valid.json
+1
-1
tests/lexicon-test-data/moderation/tribunal-vote-valid.json
···"reasoning": "The moderator's action was justified based on clear violation of Rule 2 (No Spam). The user posted the same promotional content across multiple communities within a short timeframe.",
···"reasoning": "The moderator's action was justified based on clear violation of Rule 2 (No Spam). The user posted the same promotional content across multiple communities within a short timeframe.",
+5
-5
tests/lexicon-test-data/post/post-invalid-enum-type.json
+5
-5
tests/lexicon-test-data/post/post-invalid-enum-type.json
···
···
+5
-6
tests/lexicon-test-data/post/post-invalid-missing-community.json
+5
-6
tests/lexicon-test-data/post/post-invalid-missing-community.json
+6
-7
tests/lexicon-test-data/post/post-valid-text.json
+6
-7
tests/lexicon-test-data/post/post-valid-text.json
···-"text": "I've been working with Go for a while now and wanted to share some thoughts on error handling patterns...",···
···+"content": "I've been working with Go for a while now and wanted to share some thoughts on error handling patterns...",···
+10
-4
tests/lexicon_validation_test.go
+10
-4
tests/lexicon_validation_test.go
·········
·········