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}