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