A community based topic aggregation platform built on atproto
1//go:build ignore
2
3package main
4
5import (
6 "database/sql"
7 "fmt"
8 "log"
9 "math/rand"
10 "time"
11
12 _ "github.com/lib/pq"
13)
14
15// Post URI: at://did:plc:hcuo3qx2lr7h7dquusbeobht/social.coves.community.post/3m4yohkzbkc2b
16// Community DID: did:plc:hcuo3qx2lr7h7dquusbeobht
17// Community Handle: test-usnews.community.coves.social
18
19const (
20 postURI = "at://did:plc:hcuo3qx2lr7h7dquusbeobht/social.coves.community.post/3m4yohkzbkc2b"
21 postCID = "bafyzohran123"
22 communityDID = "did:plc:hcuo3qx2lr7h7dquusbeobht"
23)
24
25type User struct {
26 DID string
27 Handle string
28 Name string
29}
30
31type Comment struct {
32 URI string
33 CID string
34 RKey string
35 DID string
36 RootURI string
37 RootCID string
38 ParentURI string
39 ParentCID string
40 Content string
41 CreatedAt time.Time
42}
43
44var userNames = []string{
45 "sarah_jenkins", "michael_chen", "jessica_rodriguez", "david_nguyen",
46 "emily_williams", "james_patel", "ashley_garcia", "robert_kim",
47 "jennifer_lee", "william_martinez", "amanda_johnson", "daniel_brown",
48 "melissa_davis", "christopher_wilson", "rebecca_anderson", "matthew_taylor",
49 "laura_thomas", "anthony_moore", "stephanie_jackson", "joshua_white",
50 "nicole_harris", "ryan_martin", "rachel_thompson", "kevin_garcia",
51 "michelle_robinson", "brandon_clark", "samantha_lewis", "justin_walker",
52 "kimberly_hall", "tyler_allen", "brittany_young", "andrew_king",
53}
54
55var positiveComments = []string{
56 "This is such fantastic news! Zohran represents real progressive values and I couldn't be happier with this outcome!",
57 "Finally! A mayor who actually understands the needs of working families. This is a historic moment for NYC!",
58 "What an incredible victory! Zohran's grassroots campaign shows that people power still matters in politics.",
59 "I'm so proud of our city today. This win gives me hope for the future of progressive politics!",
60 "This is exactly what NYC needed. Zohran's policies on housing and healthcare are going to transform our city!",
61 "Congratulations to Zohran! His commitment to affordable housing is going to make such a difference.",
62 "I've been following his campaign since day one and I'm thrilled to see him win. He truly deserves this!",
63 "This victory is proof that authentic progressive candidates can win. So excited for what's ahead!",
64 "Zohran's dedication to public transit and climate action is exactly what we need. Great day for NYC!",
65 "What a momentous occasion! His policies on education are going to help so many families.",
66 "I'm emotional reading this! Zohran gives me so much hope for the direction of our city.",
67 "This is the change we've been waiting for! Can't wait to see his vision become reality.",
68 "His campaign was inspiring from start to finish. This win is well-deserved!",
69 "Finally, a mayor who will prioritize working people over corporate interests!",
70 "The grassroots organizing that made this happen was incredible to witness. Democracy in action!",
71 "Zohran's focus on social justice is refreshing. This is a win for all New Yorkers!",
72 "I volunteered for his campaign and this victory means everything. So proud!",
73 "This gives me faith in our democratic process. People-powered campaigns can still win!",
74 "His policies on criminal justice reform are exactly what NYC needs right now.",
75 "What an amazing day for progressive politics! Zohran is going to do great things.",
76}
77
78var replyComments = []string{
79 "Absolutely agree! This is going to be transformative.",
80 "Couldn't have said it better myself!",
81 "Yes! This is exactly right.",
82 "100% this! So well said.",
83 "This perfectly captures how I feel too!",
84 "Exactly my thoughts! Great perspective.",
85 "So true! I'm equally excited.",
86 "Well put! I share your optimism.",
87 "This! Absolutely this!",
88 "I feel the same way! Great comment.",
89 "You took the words right out of my mouth!",
90 "Perfectly stated! I agree completely.",
91 "Yes yes yes! This is it exactly.",
92 "This is spot on! Thank you for sharing.",
93 "I couldn't agree more with this take!",
94 "Exactly what I was thinking! Well said.",
95 "This captures it perfectly!",
96 "So much this! Great comment.",
97 "You nailed it! I feel exactly the same.",
98 "This is the best take I've seen! Agreed!",
99}
100
101var deepReplyComments = []string{
102 "And it's not just about the policies, it's about the movement he's building!",
103 "This thread is giving me life! So glad to see so many people excited.",
104 "I love seeing all this positive energy! We're going to change NYC together.",
105 "Reading these comments makes me even more hopeful. We did this!",
106 "The solidarity in this thread is beautiful. This is what democracy looks like!",
107 "I'm so grateful to be part of this community right now. Historic moment!",
108 "This conversation shows how ready people are for real change.",
109 "Seeing this support gives me so much hope for what's possible.",
110 "This is the kind of energy we need to keep up! Let's go!",
111 "I'm saving this thread to look back on this incredible moment.",
112}
113
114func generateTID() string {
115 // Simple TID generator for testing (timestamp in microseconds + random)
116 now := time.Now().UnixMicro()
117 return fmt.Sprintf("%d%04d", now, rand.Intn(10000))
118}
119
120func createUser(db *sql.DB, handle, name string, idx int) (*User, error) {
121 did := fmt.Sprintf("did:plc:testuser%d%d", time.Now().Unix(), idx)
122 user := &User{
123 DID: did,
124 Handle: handle,
125 Name: name,
126 }
127
128 query := `
129 INSERT INTO users (did, handle, pds_url, created_at, updated_at)
130 VALUES ($1, $2, $3, NOW(), NOW())
131 ON CONFLICT (did) DO NOTHING
132 `
133
134 _, err := db.Exec(query, user.DID, user.Handle, "http://localhost:3001")
135 if err != nil {
136 return nil, fmt.Errorf("failed to create user: %w", err)
137 }
138
139 log.Printf("Created user: %s (%s)", user.Handle, user.DID)
140 return user, nil
141}
142
143func createComment(db *sql.DB, user *User, content, parentURI, parentCID string, createdAt time.Time) (*Comment, error) {
144 rkey := generateTID()
145 uri := fmt.Sprintf("at://%s/social.coves.community.comment/%s", user.DID, rkey)
146 cid := fmt.Sprintf("bafy%s", rkey)
147
148 comment := &Comment{
149 URI: uri,
150 CID: cid,
151 RKey: rkey,
152 DID: user.DID,
153 RootURI: postURI,
154 RootCID: postCID,
155 ParentURI: parentURI,
156 ParentCID: parentCID,
157 Content: content,
158 CreatedAt: createdAt,
159 }
160
161 query := `
162 INSERT INTO comments (
163 uri, cid, rkey, commenter_did, root_uri, root_cid,
164 parent_uri, parent_cid, content, created_at, indexed_at
165 ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NOW())
166 ON CONFLICT (uri) DO NOTHING
167 RETURNING id
168 `
169
170 var id int64
171 err := db.QueryRow(query,
172 comment.URI, comment.CID, comment.RKey, comment.DID,
173 comment.RootURI, comment.RootCID, comment.ParentURI, comment.ParentCID,
174 comment.Content, comment.CreatedAt,
175 ).Scan(&id)
176 if err != nil {
177 return nil, fmt.Errorf("failed to create comment: %w", err)
178 }
179
180 log.Printf("Created comment by %s: %.50s...", user.Handle, content)
181 return comment, nil
182}
183
184func updateCommentCount(db *sql.DB, parentURI string, isPost bool) error {
185 if isPost {
186 _, err := db.Exec(`
187 UPDATE posts
188 SET comment_count = comment_count + 1
189 WHERE uri = $1
190 `, parentURI)
191 return err
192 }
193
194 _, err := db.Exec(`
195 UPDATE comments
196 SET reply_count = reply_count + 1
197 WHERE uri = $1
198 `, parentURI)
199 return err
200}
201
202func main() {
203 // Connect to dev database
204 dbURL := "postgres://dev_user:dev_password@localhost:5435/coves_dev?sslmode=disable"
205 db, err := sql.Open("postgres", dbURL)
206 if err != nil {
207 log.Fatalf("Failed to connect to database: %v", err)
208 }
209 defer db.Close()
210
211 if err := db.Ping(); err != nil {
212 log.Fatalf("Failed to ping database: %v", err)
213 }
214
215 log.Println("Connected to database successfully!")
216 log.Printf("Post URI: %s", postURI)
217 log.Println("Starting to generate test data...")
218
219 rand.Seed(time.Now().UnixNano())
220
221 // Create users
222 log.Println("\n=== Creating Users ===")
223 users := make([]*User, 0, len(userNames))
224 for i, name := range userNames {
225 handle := fmt.Sprintf("%s.bsky.social", name)
226 user, err := createUser(db, handle, name, i)
227 if err != nil {
228 log.Printf("Warning: Failed to create user %s: %v", name, err)
229 continue
230 }
231 users = append(users, user)
232 }
233
234 log.Printf("\nCreated %d users", len(users))
235
236 // Generate comments with varied timing
237 log.Println("\n=== Creating Top-Level Comments ===")
238 baseTime := time.Now().Add(-2 * time.Hour) // Comments from 2 hours ago
239 topLevelComments := make([]*Comment, 0)
240
241 // Create 15-20 top-level comments
242 numTopLevel := 15 + rand.Intn(6)
243 for i := 0; i < numTopLevel && i < len(users); i++ {
244 user := users[i]
245 content := positiveComments[i%len(positiveComments)]
246 createdAt := baseTime.Add(time.Duration(i*5+rand.Intn(3)) * time.Minute)
247
248 comment, err := createComment(db, user, content, postURI, postCID, createdAt)
249 if err != nil {
250 log.Printf("Warning: Failed to create top-level comment: %v", err)
251 continue
252 }
253
254 topLevelComments = append(topLevelComments, comment)
255
256 // Update post comment count
257 if err := updateCommentCount(db, postURI, true); err != nil {
258 log.Printf("Warning: Failed to update post comment count: %v", err)
259 }
260
261 // Small delay to avoid timestamp collisions
262 time.Sleep(10 * time.Millisecond)
263 }
264
265 log.Printf("Created %d top-level comments", len(topLevelComments))
266
267 // Create first-level replies (replies to top-level comments)
268 log.Println("\n=== Creating First-Level Replies ===")
269 firstLevelReplies := make([]*Comment, 0)
270
271 for i, parentComment := range topLevelComments {
272 // 60% chance of having replies
273 if rand.Float64() > 0.6 {
274 continue
275 }
276
277 // 1-3 replies per comment
278 numReplies := 1 + rand.Intn(3)
279 for j := 0; j < numReplies; j++ {
280 userIdx := (i*3 + j + len(topLevelComments)) % len(users)
281 user := users[userIdx]
282 content := replyComments[rand.Intn(len(replyComments))]
283 createdAt := parentComment.CreatedAt.Add(time.Duration(5+rand.Intn(10)) * time.Minute)
284
285 comment, err := createComment(db, user, content, parentComment.URI, parentComment.CID, createdAt)
286 if err != nil {
287 log.Printf("Warning: Failed to create first-level reply: %v", err)
288 continue
289 }
290
291 firstLevelReplies = append(firstLevelReplies, comment)
292
293 // Update parent comment reply count
294 if err := updateCommentCount(db, parentComment.URI, false); err != nil {
295 log.Printf("Warning: Failed to update comment reply count: %v", err)
296 }
297
298 time.Sleep(10 * time.Millisecond)
299 }
300 }
301
302 log.Printf("Created %d first-level replies", len(firstLevelReplies))
303
304 // Create second-level replies (replies to replies) - testing nested threading
305 log.Println("\n=== Creating Second-Level Replies ===")
306 secondLevelCount := 0
307
308 for i, parentComment := range firstLevelReplies {
309 // 40% chance of having deep replies
310 if rand.Float64() > 0.4 {
311 continue
312 }
313
314 // 1-2 deep replies
315 numReplies := 1 + rand.Intn(2)
316 for j := 0; j < numReplies; j++ {
317 userIdx := (i*2 + j + len(topLevelComments) + len(firstLevelReplies)) % len(users)
318 user := users[userIdx]
319 content := deepReplyComments[rand.Intn(len(deepReplyComments))]
320 createdAt := parentComment.CreatedAt.Add(time.Duration(3+rand.Intn(7)) * time.Minute)
321
322 _, err := createComment(db, user, content, parentComment.URI, parentComment.CID, createdAt)
323 if err != nil {
324 log.Printf("Warning: Failed to create second-level reply: %v", err)
325 continue
326 }
327
328 secondLevelCount++
329
330 // Update parent comment reply count
331 if err := updateCommentCount(db, parentComment.URI, false); err != nil {
332 log.Printf("Warning: Failed to update comment reply count: %v", err)
333 }
334
335 time.Sleep(10 * time.Millisecond)
336 }
337 }
338
339 log.Printf("Created %d second-level replies", secondLevelCount)
340
341 // Print summary
342 totalComments := len(topLevelComments) + len(firstLevelReplies) + secondLevelCount
343 log.Println("\n=== Summary ===")
344 log.Printf("Total users created: %d", len(users))
345 log.Printf("Total comments created: %d", totalComments)
346 log.Printf(" - Top-level comments: %d", len(topLevelComments))
347 log.Printf(" - First-level replies: %d", len(firstLevelReplies))
348 log.Printf(" - Second-level replies: %d", secondLevelCount)
349 log.Println("\nDone! Check the post at !test-usnews for the comments.")
350}