A community based topic aggregation platform built on atproto
at main 350 lines 12 kB view raw
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}