A community based topic aggregation platform built on atproto
1// setup_dev_aggregator.go - Creates a local test aggregator on the local PDS
2//
3// This script creates an aggregator account on the local PDS for development testing.
4// After running, you'll need to:
5// 1. Register the aggregator via OAuth UI
6// 2. Generate an API key via the createApiKey endpoint
7//
8// Usage: go run scripts/setup_dev_aggregator.go
9package main
10
11import (
12 "bytes"
13 "context"
14 "database/sql"
15 "encoding/json"
16 "fmt"
17 "io"
18 "log"
19 "net/http"
20
21 _ "github.com/lib/pq"
22)
23
24const (
25 PDSURL = "http://localhost:3001"
26 DatabaseURL = "postgres://dev_user:dev_password@localhost:5435/coves_dev?sslmode=disable"
27)
28
29type CreateAccountRequest struct {
30 Email string `json:"email"`
31 Handle string `json:"handle"`
32 Password string `json:"password"`
33}
34
35type CreateAccountResponse struct {
36 DID string `json:"did"`
37 Handle string `json:"handle"`
38 AccessJWT string `json:"accessJwt"`
39}
40
41type CreateSessionRequest struct {
42 Identifier string `json:"identifier"`
43 Password string `json:"password"`
44}
45
46type CreateSessionResponse struct {
47 DID string `json:"did"`
48 Handle string `json:"handle"`
49 AccessJWT string `json:"accessJwt"`
50}
51
52func main() {
53 ctx := context.Background()
54
55 // Configuration
56 handle := "test-aggregator.local.coves.dev"
57 email := "test-aggregator@example.com"
58 password := "test-password-12345"
59 displayName := "Test Aggregator (Dev)"
60
61 log.Printf("Setting up dev aggregator: %s", handle)
62
63 // Connect to database
64 db, err := sql.Open("postgres", DatabaseURL)
65 if err != nil {
66 log.Fatalf("Failed to connect to database: %v", err)
67 }
68 defer db.Close()
69
70 // Step 1: Try to create account on PDS (or get existing session)
71 log.Printf("Creating account on PDS: %s", PDSURL)
72
73 var did string
74
75 // First try to create account
76 createResp, err := createAccount(handle, email, password)
77 if err != nil {
78 log.Printf("Account creation failed (may already exist): %v", err)
79 log.Printf("Trying to create session with existing account...")
80
81 // Try to login instead
82 sessionResp, err := createSession(handle, password)
83 if err != nil {
84 log.Fatalf("Failed to create session: %v", err)
85 }
86 did = sessionResp.DID
87 log.Printf("Logged in as existing account: %s", did)
88 } else {
89 did = createResp.DID
90 log.Printf("Created new account: %s", did)
91 }
92
93 // Step 2: Check if already in users table
94 var existingHandle string
95 err = db.QueryRowContext(ctx, "SELECT handle FROM users WHERE did = $1", did).Scan(&existingHandle)
96 if err == nil {
97 log.Printf("User already exists in users table: %s", existingHandle)
98 } else {
99 // Insert into users table
100 log.Printf("Inserting user into users table...")
101 _, err = db.ExecContext(ctx, `
102 INSERT INTO users (did, handle, pds_url)
103 VALUES ($1, $2, $3)
104 ON CONFLICT (did) DO UPDATE SET handle = $2
105 `, did, handle, PDSURL)
106 if err != nil {
107 log.Fatalf("Failed to insert user: %v", err)
108 }
109 }
110
111 // Step 3: Check if already in aggregators table
112 var existingAggDID string
113 err = db.QueryRowContext(ctx, "SELECT did FROM aggregators WHERE did = $1", did).Scan(&existingAggDID)
114 if err == nil {
115 log.Printf("Aggregator already exists in aggregators table")
116 } else {
117 // Insert into aggregators table
118 log.Printf("Inserting aggregator into aggregators table...")
119 recordURI := fmt.Sprintf("at://%s/social.coves.aggregator.declaration/self", did)
120 recordCID := "dev-placeholder-cid"
121
122 _, err = db.ExecContext(ctx, `
123 INSERT INTO aggregators (did, display_name, description, record_uri, record_cid, created_at, indexed_at)
124 VALUES ($1, $2, $3, $4, $5, NOW(), NOW())
125 `, did, displayName, "Development test aggregator", recordURI, recordCID)
126 if err != nil {
127 log.Fatalf("Failed to insert aggregator: %v", err)
128 }
129 }
130
131 fmt.Println()
132 fmt.Println("========================================")
133 fmt.Println(" DEV AGGREGATOR ACCOUNT CREATED")
134 fmt.Println("========================================")
135 fmt.Println()
136 fmt.Printf(" DID: %s\n", did)
137 fmt.Printf(" Handle: %s\n", handle)
138 fmt.Printf(" Password: %s\n", password)
139 fmt.Println()
140 fmt.Println(" Next steps:")
141 fmt.Println(" 1. Start Coves server: make run")
142 fmt.Println(" 2. Authenticate as this account via OAuth UI")
143 fmt.Println(" 3. Call POST /xrpc/social.coves.aggregator.createApiKey")
144 fmt.Println(" 4. Save the API key and add to aggregators/kagi-news/.env")
145 fmt.Println()
146 fmt.Println("========================================")
147}
148
149func createAccount(handle, email, password string) (*CreateAccountResponse, error) {
150 reqBody := CreateAccountRequest{
151 Email: email,
152 Handle: handle,
153 Password: password,
154 }
155
156 body, _ := json.Marshal(reqBody)
157 resp, err := http.Post(PDSURL+"/xrpc/com.atproto.server.createAccount", "application/json", bytes.NewReader(body))
158 if err != nil {
159 return nil, fmt.Errorf("request failed: %w", err)
160 }
161 defer resp.Body.Close()
162
163 respBody, _ := io.ReadAll(resp.Body)
164 if resp.StatusCode != http.StatusOK {
165 return nil, fmt.Errorf("status %d: %s", resp.StatusCode, string(respBody))
166 }
167
168 var result CreateAccountResponse
169 if err := json.Unmarshal(respBody, &result); err != nil {
170 return nil, fmt.Errorf("failed to parse response: %w", err)
171 }
172
173 return &result, nil
174}
175
176func createSession(identifier, password string) (*CreateSessionResponse, error) {
177 reqBody := CreateSessionRequest{
178 Identifier: identifier,
179 Password: password,
180 }
181
182 body, _ := json.Marshal(reqBody)
183 resp, err := http.Post(PDSURL+"/xrpc/com.atproto.server.createSession", "application/json", bytes.NewReader(body))
184 if err != nil {
185 return nil, fmt.Errorf("request failed: %w", err)
186 }
187 defer resp.Body.Close()
188
189 respBody, _ := io.ReadAll(resp.Body)
190 if resp.StatusCode != http.StatusOK {
191 return nil, fmt.Errorf("status %d: %s", resp.StatusCode, string(respBody))
192 }
193
194 var result CreateSessionResponse
195 if err := json.Unmarshal(respBody, &result); err != nil {
196 return nil, fmt.Errorf("failed to parse response: %w", err)
197 }
198
199 return &result, nil
200}