A community based topic aggregation platform built on atproto
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 for "Your son don't wanna be here..." NBACentral post
14// at://did:plc:hcuo3qx2lr7h7dquusbeobht/social.coves.community.post/3m56mowhbuk22
15
16const (
17 postURI = "at://did:plc:hcuo3qx2lr7h7dquusbeobht/social.coves.community.post/3m56mowhbuk22"
18 postCID = "bafyreibml4midgt7ojq7dnabnku5ikzro4erfvdux6mmiqeat7pci2gy4u"
19 communityDID = "did:plc:hcuo3qx2lr7h7dquusbeobht"
20)
21
22type User struct {
23 DID string
24 Handle string
25 Name string
26}
27
28type Comment struct {
29 URI string
30 CID string
31 RKey string
32 DID string
33 RootURI string
34 RootCID string
35 ParentURI string
36 ParentCID string
37 Content string
38 CreatedAt time.Time
39}
40
41var userNames = []string{
42 "lakers_fan_23", "pistons_nation", "nba_historian", "hoops_enthusiast",
43 "detroit_pride", "basketball_iq", "courtside_view", "rim_protector",
44 "three_point_specialist", "paint_beast", "fast_break_fan", "clutch_time",
45 "triple_double_king", "defense_wins", "small_ball_era", "old_school_hoops",
46 "draft_expert", "salary_cap_guru", "trade_machine", "basketball_analytics",
47 "box_score_reader", "eye_test_guy", "film_room_analyst", "player_development",
48 "hometown_hero", "bandwagon_fan", "loyal_since_day_one", "casual_viewer",
49 "die_hard_supporter", "armchair_coach", "nbatv_addict", "league_pass_subscriber",
50}
51
52var topLevelComments = []string{
53 "Imagine having to explain to your mom at Thanksgiving that you got ejected for fighting your brother 💀",
54 "Mrs. Duren watching this at home like 'I didn't raise y'all like this'",
55 "Their mom is somewhere absolutely LIVID right now. Both of them getting the belt when they get home",
56 "This is the most expensive sibling rivalry in history lmao",
57 "Jalen really said 'I've been whooping your ass since we were kids, what makes you think tonight's different' 😂",
58 "Ausar thought the NBA would protect him from his older brother. He thought wrong.",
59 "The trash talk must have been PERSONAL. That's years of sibling beef coming out",
60 "Family group chat is gonna be awkward after this one",
61 "Their parents spent 18 years breaking up fights just for it to happen on national TV",
62 "This is what happens when little bro thinks he's tough now that he's in the league",
63 "Jalen's been dunking on Ausar in the driveway for years, this was just another Tuesday for him",
64 "The fact that they're both in the league and THIS is how they settle it 💀💀💀",
65 "Ausar: 'I'm in the NBA now, I'm not scared of you anymore' - Jalen: 'BET'",
66 "Mom definitely called both of them after the game. Neither one answered lol",
67 "This is the content I pay League Pass for. Brothers getting into it on the court is peak entertainment",
68 "Thanksgiving dinner is about to be TENSE in the Duren/Thompson household",
69 "Little brother energy vs Big brother authority. Tale as old as time",
70 "The refs trying to break them up like 'Sir that's your BROTHER'",
71 "Jalen been waiting for this moment since Ausar got drafted",
72 "Both of them getting fined and their mom making them split the cost 😂",
73 "This brings me back to fighting my brother over the last piece of pizza. Just at a much higher tax bracket",
74 "The Pistons and Rockets staff trying to separate them: 'Guys we have practice tomorrow!'",
75 "Ausar finally tall enough to talk back and chose violence",
76 "Their dad watching like 'At least wait til you're both All-Stars before embarrassing the family'",
77 "This is what decades of 'Mom said it's my turn on the Xbox' leads to",
78}
79
80var replyComments = []string{
81 "LMAOOO facts, mom's not playing",
82 "Bro I'm crying at this visual 😂😂😂",
83 "This is the one right here 💀",
84 "Thanksgiving about to be SILENT",
85 "You know their dad had flashbacks to breaking up driveway fights",
86 "The family group chat IS ON FIRE right now I guarantee it",
87 "Little bro syndrome is real and Ausar has it BAD",
88 "Big facts. Jalen been the big brother his whole life, NBA don't change that",
89 "Mom's gonna make them hug it out before Christmas I'm calling it now",
90 "This comment wins 😂😂😂",
91 "I need the full footage of what was said because it had to be PERSONAL",
92 "Years of sibling rivalry just exploded on NBA hardwood",
93 "The refs were so confused trying to separate family members 💀",
94 "Both of them getting the 'I'm disappointed' text from mom",
95 "Ausar thought NBA money meant he was safe. Nope.",
96 "Jalen's been waiting to humble him since draft night",
97 "This is exactly what their parents warned them about lmao",
98 "The fine money coming out of their allowance fr fr",
99 "Peak sibling behavior. I respect it.",
100 "Someone check on Mrs. Duren she's probably stress eating rn",
101}
102
103var deepReplyComments = []string{
104 "And you KNOW mom's taking both their sides AND neither side at the same time",
105 "Family dynamics don't stop just cause you're making millions. Big brother gonna big brother",
106 "This thread has me in TEARS. Y'all are hilarious 😭",
107 "The fact that NBA refs had to break up a family dispute is sending me",
108 "Both of them are gonna act like nothing happened next family reunion",
109 "I guarantee their teammates are ROASTING them in the group chats right now",
110 "This is the most relatable NBA drama I've ever seen. We all fought our siblings",
111 "Mom's calling BOTH coaches after this I just know it",
112 "The league office trying to figure out how to fine siblings for fighting each other",
113 "This is gonna be an amazing 30 for 30 one day: 'What if I told you family and basketball don't always mix'",
114}
115
116func generateTID() string {
117 // Simple TID generator for testing (timestamp in microseconds + random)
118 now := time.Now().UnixMicro()
119 return fmt.Sprintf("%d%04d", now, rand.Intn(10000))
120}
121
122func createUser(db *sql.DB, handle, name string, idx int) (*User, error) {
123 did := fmt.Sprintf("did:plc:testuser%d%d", time.Now().Unix(), idx)
124 user := &User{
125 DID: did,
126 Handle: handle,
127 Name: name,
128 }
129
130 query := `
131 INSERT INTO users (did, handle, pds_url, created_at, updated_at)
132 VALUES ($1, $2, $3, NOW(), NOW())
133 ON CONFLICT (did) DO NOTHING
134 `
135
136 _, err := db.Exec(query, user.DID, user.Handle, "http://localhost:3001")
137 if err != nil {
138 return nil, fmt.Errorf("failed to create user: %w", err)
139 }
140
141 log.Printf("Created user: %s (%s)", user.Handle, user.DID)
142 return user, nil
143}
144
145func createComment(db *sql.DB, user *User, content, parentURI, parentCID string, createdAt time.Time) (*Comment, error) {
146 rkey := generateTID()
147 uri := fmt.Sprintf("at://%s/social.coves.community.comment/%s", user.DID, rkey)
148 cid := fmt.Sprintf("bafy%s", rkey)
149
150 comment := &Comment{
151 URI: uri,
152 CID: cid,
153 RKey: rkey,
154 DID: user.DID,
155 RootURI: postURI,
156 RootCID: postCID,
157 ParentURI: parentURI,
158 ParentCID: parentCID,
159 Content: content,
160 CreatedAt: createdAt,
161 }
162
163 query := `
164 INSERT INTO comments (
165 uri, cid, rkey, commenter_did, root_uri, root_cid,
166 parent_uri, parent_cid, content, created_at, indexed_at
167 ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NOW())
168 ON CONFLICT (uri) DO NOTHING
169 RETURNING id
170 `
171
172 var id int64
173 err := db.QueryRow(query,
174 comment.URI, comment.CID, comment.RKey, comment.DID,
175 comment.RootURI, comment.RootCID, comment.ParentURI, comment.ParentCID,
176 comment.Content, comment.CreatedAt,
177 ).Scan(&id)
178 if err != nil {
179 return nil, fmt.Errorf("failed to create comment: %w", err)
180 }
181
182 log.Printf("Created comment by %s: %.50s...", user.Handle, content)
183 return comment, nil
184}
185
186func updateCommentCount(db *sql.DB, parentURI string, isPost bool) error {
187 if isPost {
188 _, err := db.Exec(`
189 UPDATE posts
190 SET comment_count = comment_count + 1
191 WHERE uri = $1
192 `, parentURI)
193 return err
194 }
195
196 _, err := db.Exec(`
197 UPDATE comments
198 SET reply_count = reply_count + 1
199 WHERE uri = $1
200 `, parentURI)
201 return err
202}
203
204func main() {
205 // Connect to dev database
206 dbURL := "postgres://dev_user:dev_password@localhost:5435/coves_dev?sslmode=disable"
207 db, err := sql.Open("postgres", dbURL)
208 if err != nil {
209 log.Fatalf("Failed to connect to database: %v", err)
210 }
211 defer db.Close()
212
213 if err := db.Ping(); err != nil {
214 log.Fatalf("Failed to ping database: %v", err)
215 }
216
217 log.Println("Connected to database successfully!")
218 log.Printf("Post URI: %s", postURI)
219 log.Println("Starting to generate NBA test comments...")
220
221 rand.Seed(time.Now().UnixNano())
222
223 // Create users
224 log.Println("\n=== Creating Users ===")
225 users := make([]*User, 0, len(userNames))
226 for i, name := range userNames {
227 handle := fmt.Sprintf("%s.bsky.social", name)
228 user, err := createUser(db, handle, name, i)
229 if err != nil {
230 log.Printf("Warning: Failed to create user %s: %v", name, err)
231 continue
232 }
233 users = append(users, user)
234 }
235
236 log.Printf("\nCreated %d users", len(users))
237
238 // Generate comments with varied timing
239 log.Println("\n=== Creating Top-Level Comments ===")
240 baseTime := time.Now().Add(-3 * time.Hour) // Comments from 3 hours ago
241 topLevelCommentsCreated := make([]*Comment, 0)
242
243 // Create 18-22 top-level comments
244 numTopLevel := 18 + rand.Intn(5)
245 for i := 0; i < numTopLevel && i < len(users) && i < len(topLevelComments); i++ {
246 user := users[i]
247 content := topLevelComments[i]
248 createdAt := baseTime.Add(time.Duration(i*4+rand.Intn(3)) * time.Minute)
249
250 comment, err := createComment(db, user, content, postURI, postCID, createdAt)
251 if err != nil {
252 log.Printf("Warning: Failed to create top-level comment: %v", err)
253 continue
254 }
255
256 topLevelCommentsCreated = append(topLevelCommentsCreated, comment)
257
258 // Update post comment count
259 if err := updateCommentCount(db, postURI, true); err != nil {
260 log.Printf("Warning: Failed to update post comment count: %v", err)
261 }
262
263 // Small delay to avoid timestamp collisions
264 time.Sleep(10 * time.Millisecond)
265 }
266
267 log.Printf("Created %d top-level comments", len(topLevelCommentsCreated))
268
269 // Create first-level replies (replies to top-level comments)
270 log.Println("\n=== Creating First-Level Replies ===")
271 firstLevelReplies := make([]*Comment, 0)
272
273 for i, parentComment := range topLevelCommentsCreated {
274 // 70% chance of having replies (NBA threads get lots of engagement)
275 if rand.Float64() > 0.7 {
276 continue
277 }
278
279 // 1-4 replies per comment
280 numReplies := 1 + rand.Intn(4)
281 for j := 0; j < numReplies && len(replyComments) > 0; j++ {
282 userIdx := (i*3 + j + len(topLevelCommentsCreated)) % len(users)
283 user := users[userIdx]
284 content := replyComments[rand.Intn(len(replyComments))]
285 createdAt := parentComment.CreatedAt.Add(time.Duration(3+rand.Intn(8)) * time.Minute)
286
287 comment, err := createComment(db, user, content, parentComment.URI, parentComment.CID, createdAt)
288 if err != nil {
289 log.Printf("Warning: Failed to create first-level reply: %v", err)
290 continue
291 }
292
293 firstLevelReplies = append(firstLevelReplies, comment)
294
295 // Update parent comment reply count
296 if err := updateCommentCount(db, parentComment.URI, false); err != nil {
297 log.Printf("Warning: Failed to update comment reply count: %v", err)
298 }
299
300 time.Sleep(10 * time.Millisecond)
301 }
302 }
303
304 log.Printf("Created %d first-level replies", len(firstLevelReplies))
305
306 // Create second-level replies (replies to replies) - deep threads
307 log.Println("\n=== Creating Second-Level Replies ===")
308 secondLevelCount := 0
309
310 for i, parentComment := range firstLevelReplies {
311 // 50% chance of having deep replies (NBA drama threads go DEEP)
312 if rand.Float64() > 0.5 {
313 continue
314 }
315
316 // 1-2 deep replies
317 numReplies := 1 + rand.Intn(2)
318 for j := 0; j < numReplies && len(deepReplyComments) > 0; j++ {
319 userIdx := (i*2 + j + len(topLevelCommentsCreated) + len(firstLevelReplies)) % len(users)
320 user := users[userIdx]
321 content := deepReplyComments[rand.Intn(len(deepReplyComments))]
322 createdAt := parentComment.CreatedAt.Add(time.Duration(2+rand.Intn(5)) * time.Minute)
323
324 _, err := createComment(db, user, content, parentComment.URI, parentComment.CID, createdAt)
325 if err != nil {
326 log.Printf("Warning: Failed to create second-level reply: %v", err)
327 continue
328 }
329
330 secondLevelCount++
331
332 // Update parent comment reply count
333 if err := updateCommentCount(db, parentComment.URI, false); err != nil {
334 log.Printf("Warning: Failed to update comment reply count: %v", err)
335 }
336
337 time.Sleep(10 * time.Millisecond)
338 }
339 }
340
341 log.Printf("Created %d second-level replies", secondLevelCount)
342
343 // Print summary
344 totalComments := len(topLevelCommentsCreated) + len(firstLevelReplies) + secondLevelCount
345 log.Println("\n=== Summary ===")
346 log.Printf("Total users created: %d", len(users))
347 log.Printf("Total comments created: %d", totalComments)
348 log.Printf(" - Top-level comments: %d", len(topLevelCommentsCreated))
349 log.Printf(" - First-level replies: %d", len(firstLevelReplies))
350 log.Printf(" - Second-level replies: %d", secondLevelCount)
351 log.Println("\nDone! Check the NBACentral post for the brothers drama comments.")
352}