A community based topic aggregation platform built on atproto
1package votes 2 3import ( 4 "context" 5 6 oauthlib "github.com/bluesky-social/indigo/atproto/auth/oauth" 7) 8 9// Service defines the business logic interface for vote operations 10// Implements write-forward pattern: validates requests, then forwards to user's PDS 11// 12// Architecture: 13// - Service validates input and checks authorization 14// - Queries user's PDS directly via com.atproto.repo.listRecords to check existing votes 15// (avoids eventual consistency issues with AppView database) 16// - Creates/deletes vote records via com.atproto.repo.createRecord/deleteRecord 17// - AppView indexes resulting records from Jetstream firehose for aggregate counts 18type Service interface { 19 // CreateVote creates a new vote or toggles off an existing vote 20 // Returns URI and CID of created vote, or empty strings if toggled off 21 // 22 // Validation: 23 // - Direction must be "up" or "down" (returns ErrInvalidDirection) 24 // - Subject URI must be valid AT-URI (returns ErrInvalidSubject) 25 // - Subject CID must be provided (returns ErrInvalidSubject) 26 // 27 // Note: Subject existence is NOT validated. Votes on non-existent or deleted 28 // subjects are allowed - the Jetstream consumer handles orphaned votes correctly 29 // by only updating counts for non-deleted subjects. 30 // 31 // Behavior: 32 // - If no vote exists: creates new vote with given direction 33 // - If vote exists with same direction: deletes vote (toggle off) 34 // - If vote exists with different direction: updates to new direction 35 CreateVote(ctx context.Context, session *oauthlib.ClientSessionData, req CreateVoteRequest) (*CreateVoteResponse, error) 36 37 // DeleteVote removes a vote on the specified subject 38 // 39 // Validation: 40 // - Subject URI must be valid AT-URI (returns ErrInvalidSubject) 41 // - Vote must exist (returns ErrVoteNotFound) 42 // 43 // Behavior: 44 // - Deletes the user's vote record from their PDS 45 // - AppView will soft-delete via Jetstream consumer 46 DeleteVote(ctx context.Context, session *oauthlib.ClientSessionData, req DeleteVoteRequest) error 47 48 // EnsureCachePopulated fetches the user's votes from their PDS if not already cached. 49 // This should be called before rendering feeds to ensure vote state is available. 50 // If cache is already populated and not expired, this is a no-op. 51 EnsureCachePopulated(ctx context.Context, session *oauthlib.ClientSessionData) error 52 53 // GetViewerVote returns the viewer's vote for a specific subject, or nil if not voted. 54 // Returns from cache if available, otherwise returns nil (caller should ensure cache is populated). 55 GetViewerVote(userDID, subjectURI string) *CachedVote 56 57 // GetViewerVotesForSubjects returns the viewer's votes for multiple subjects. 58 // Returns a map of subjectURI -> CachedVote for subjects the user has voted on. 59 // This is efficient for batch lookups when rendering feeds. 60 GetViewerVotesForSubjects(userDID string, subjectURIs []string) map[string]*CachedVote 61} 62 63// CreateVoteRequest contains the parameters for creating a vote 64type CreateVoteRequest struct { 65 // Subject is the post or comment being voted on 66 Subject StrongRef `json:"subject"` 67 68 // Direction is either "up" or "down" 69 Direction string `json:"direction"` 70} 71 72// CreateVoteResponse contains the result of creating a vote 73type CreateVoteResponse struct { 74 // URI is the AT-URI of the created vote record 75 // Empty string if vote was toggled off (deleted) 76 URI string `json:"uri"` 77 78 // CID is the content identifier of the created vote record 79 // Empty string if vote was toggled off (deleted) 80 CID string `json:"cid"` 81} 82 83// DeleteVoteRequest contains the parameters for deleting a vote 84type DeleteVoteRequest struct { 85 // Subject is the post or comment whose vote should be removed 86 Subject StrongRef `json:"subject"` 87}