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