···
···
"Coves/internal/api/handlers/oauth"
"Coves/internal/api/middleware"
"Coves/internal/api/routes"
23
+
"Coves/internal/atproto/did"
"Coves/internal/atproto/identity"
"Coves/internal/atproto/jetstream"
26
+
"Coves/internal/core/communities"
oauthCore "Coves/internal/core/oauth"
"Coves/internal/core/users"
postgresRepo "Coves/internal/db/postgres"
···
userRepo := postgresRepo.NewUserRepository(db)
userService := users.NewUserService(userRepo, identityResolver, defaultPDS)
102
+
communityRepo := postgresRepo.NewCommunityRepository(db)
104
+
// Initialize DID generator for communities
105
+
// IS_DEV_ENV=true: Generate did:plc:xxx without registering to PLC directory
106
+
// IS_DEV_ENV=false: Generate did:plc:xxx and register with PLC_DIRECTORY_URL
107
+
isDevEnv := os.Getenv("IS_DEV_ENV") == "true"
108
+
plcDirectoryURL := os.Getenv("PLC_DIRECTORY_URL")
109
+
if plcDirectoryURL == "" {
110
+
plcDirectoryURL = "https://plc.directory" // Default to Bluesky's PLC
112
+
didGenerator := did.NewGenerator(isDevEnv, plcDirectoryURL)
113
+
log.Printf("DID generator initialized (dev_mode=%v, plc_url=%s)", isDevEnv, plcDirectoryURL)
115
+
instanceDID := os.Getenv("INSTANCE_DID")
116
+
if instanceDID == "" {
117
+
instanceDID = "did:web:coves.local" // Default for development
119
+
communityService := communities.NewCommunityService(communityRepo, didGenerator, defaultPDS, instanceDID)
121
+
// Authenticate Coves instance with PDS to enable community record writes
122
+
// The instance needs a PDS account to write community records it owns
123
+
pdsHandle := os.Getenv("PDS_INSTANCE_HANDLE")
124
+
pdsPassword := os.Getenv("PDS_INSTANCE_PASSWORD")
125
+
if pdsHandle != "" && pdsPassword != "" {
126
+
log.Printf("Authenticating Coves instance (%s) with PDS...", instanceDID)
127
+
accessToken, err := authenticateWithPDS(defaultPDS, pdsHandle, pdsPassword)
129
+
log.Printf("Warning: Failed to authenticate with PDS: %v", err)
130
+
log.Println("Community creation will fail until PDS authentication is configured")
132
+
if svc, ok := communityService.(interface{ SetPDSAccessToken(string) }); ok {
133
+
svc.SetPDSAccessToken(accessToken)
134
+
log.Println("✓ Coves instance authenticated with PDS")
138
+
log.Println("Note: PDS_INSTANCE_HANDLE and PDS_INSTANCE_PASSWORD not set")
139
+
log.Println("Community creation via write-forward is disabled")
// Start Jetstream consumer for read-forward user indexing
jetstreamURL := os.Getenv("JETSTREAM_URL")
···
113
-
log.Printf("Started Jetstream consumer: %s", jetstreamURL)
158
+
log.Printf("Started Jetstream user consumer: %s", jetstreamURL)
160
+
// Note: Community indexing happens through the same Jetstream firehose
161
+
// The CommunityEventConsumer is used by handlers when processing community-related events
162
+
// For now, community records are created via write-forward to PDS, then indexed when
163
+
// they appear in the firehose. A dedicated consumer can be added later if needed.
164
+
log.Println("Community event consumer initialized (processes events from firehose)")
// Start OAuth cleanup background job
···
routes.RegisterUserRoutes(r, userService)
210
+
routes.RegisterCommunityRoutes(r, communityService)
211
+
log.Println("Community XRPC endpoints registered")
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
···
fmt.Printf("Default PDS: %s\n", defaultPDS)
log.Fatal(http.ListenAndServe(":"+port, r))
228
+
// authenticateWithPDS creates a session on the PDS and returns an access token
229
+
func authenticateWithPDS(pdsURL, handle, password string) (string, error) {
230
+
type CreateSessionRequest struct {
231
+
Identifier string `json:"identifier"`
232
+
Password string `json:"password"`
235
+
type CreateSessionResponse struct {
236
+
DID string `json:"did"`
237
+
Handle string `json:"handle"`
238
+
AccessJwt string `json:"accessJwt"`
241
+
reqBody, err := json.Marshal(CreateSessionRequest{
242
+
Identifier: handle,
243
+
Password: password,
246
+
return "", fmt.Errorf("failed to marshal request: %w", err)
249
+
resp, err := http.Post(
250
+
pdsURL+"/xrpc/com.atproto.server.createSession",
251
+
"application/json",
252
+
bytes.NewReader(reqBody),
255
+
return "", fmt.Errorf("failed to call PDS: %w", err)
257
+
defer resp.Body.Close()
259
+
if resp.StatusCode != http.StatusOK {
260
+
body, _ := io.ReadAll(resp.Body)
261
+
return "", fmt.Errorf("PDS returned status %d: %s", resp.StatusCode, string(body))
264
+
var session CreateSessionResponse
265
+
if err := json.NewDecoder(resp.Body).Decode(&session); err != nil {
266
+
return "", fmt.Errorf("failed to decode response: %w", err)
269
+
return session.AccessJwt, nil