diff --git a/src/db/db.go b/src/db/db.go index 5d70d99a..c96ea90e 100644 --- a/src/db/db.go +++ b/src/db/db.go @@ -33,6 +33,7 @@ type ConnOrTx interface { Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) QueryRow(ctx context.Context, sql string, args ...any) pgx.Row Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) + CopyFrom(ctx context.Context, tableName pgx.Identifier, columnNames []string, rowSrc pgx.CopyFromSource) (int64, error) // Both raw database connections and transactions in pgx can begin/commit // transactions. For database connections it does the obvious thing; for diff --git a/src/hmndata/threads_and_posts_helper.go b/src/hmndata/threads_and_posts_helper.go index a51fde3e..7d7e66a5 100644 --- a/src/hmndata/threads_and_posts_helper.go +++ b/src/hmndata/threads_and_posts_helper.go @@ -628,7 +628,7 @@ func UserCanEditPost(ctx context.Context, connOrTx db.ConnOrTx, user models.User func CreateNewPost( ctx context.Context, - tx pgx.Tx, + conn db.ConnOrTx, projectId int, threadId int, threadType models.ThreadType, userId int, @@ -637,7 +637,7 @@ func CreateNewPost( ipString string, ) (postId, versionId int) { // Create post - err := tx.QueryRow(ctx, + err := conn.QueryRow(ctx, ` INSERT INTO post (postdate, thread_id, thread_type, current_id, author_id, project_id, reply_id, preview) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) @@ -657,10 +657,10 @@ func CreateNewPost( } // Create and associate version - versionId = CreatePostVersion(ctx, tx, postId, unparsedContent, ipString, "", nil) + versionId = CreatePostVersion(ctx, conn, postId, unparsedContent, ipString, "", nil) // Fix up thread - err = FixThreadPostIds(ctx, tx, threadId) + err = FixThreadPostIds(ctx, conn, threadId) if err != nil { panic(oops.New(err, "failed to fix up thread post IDs")) } @@ -678,7 +678,7 @@ func CreateNewPost( } updates := strings.Join(updateEntries, ", ") - _, err = tx.Exec(ctx, + _, err = conn.Exec(ctx, ` UPDATE project SET `+updates+` @@ -768,7 +768,7 @@ func DeletePost( const maxPostContentLength = 200000 -func CreatePostVersion(ctx context.Context, tx pgx.Tx, postId int, unparsedContent string, ipString string, editReason string, editorId *int) (versionId int) { +func CreatePostVersion(ctx context.Context, conn db.ConnOrTx, postId int, unparsedContent string, ipString string, editReason string, editorId *int) (versionId int) { if len(unparsedContent) > maxPostContentLength { logging.ExtractLogger(ctx).Warn(). Str("preview", unparsedContent[:400]). @@ -787,7 +787,7 @@ func CreatePostVersion(ctx context.Context, tx pgx.Tx, postId int, unparsedConte } // Create post version - err := tx.QueryRow(ctx, + err := conn.QueryRow(ctx, ` INSERT INTO post_version (post_id, text_raw, text_parsed, ip, date, edit_reason, editor_id) VALUES ($1, $2, $3, $4, $5, $6, $7) @@ -806,7 +806,7 @@ func CreatePostVersion(ctx context.Context, tx pgx.Tx, postId int, unparsedConte } // Update post with version id and preview - _, err = tx.Exec(ctx, + _, err = conn.Exec(ctx, ` UPDATE post SET current_id = $1, preview = $2 @@ -822,7 +822,7 @@ func CreatePostVersion(ctx context.Context, tx pgx.Tx, postId int, unparsedConte // Update asset usage - _, err = tx.Exec(ctx, + _, err = conn.Exec(ctx, ` DELETE FROM post_asset_usage WHERE post_id = $1 @@ -839,7 +839,7 @@ func CreatePostVersion(ctx context.Context, tx pgx.Tx, postId int, unparsedConte keys = append(keys, key) } - assetIDs, err := db.QueryScalar[uuid.UUID](ctx, tx, + assetIDs, err := db.QueryScalar[uuid.UUID](ctx, conn, ` SELECT id FROM asset @@ -857,7 +857,7 @@ func CreatePostVersion(ctx context.Context, tx pgx.Tx, postId int, unparsedConte values = append(values, []interface{}{postId, assetID}) } - _, err = tx.CopyFrom(ctx, pgx.Identifier{"post_asset_usage"}, []string{"post_id", "asset_id"}, pgx.CopyFromRows(values)) + _, err = conn.CopyFrom(ctx, pgx.Identifier{"post_asset_usage"}, []string{"post_id", "asset_id"}, pgx.CopyFromRows(values)) if err != nil { panic(oops.New(err, "failed to insert post asset usage")) } @@ -873,8 +873,8 @@ Ensures that the first_id and last_id on the thread are still good. Returns errThreadEmpty if the thread contains no visible posts any more. You should probably mark the thread as deleted in this case. */ -func FixThreadPostIds(ctx context.Context, tx pgx.Tx, threadId int) error { - posts, err := db.Query[models.Post](ctx, tx, +func FixThreadPostIds(ctx context.Context, conn db.ConnOrTx, threadId int) error { + posts, err := db.Query[models.Post](ctx, conn, ` SELECT $columns FROM post @@ -902,7 +902,7 @@ func FixThreadPostIds(ctx context.Context, tx pgx.Tx, threadId int) error { return errThreadEmpty } - _, err = tx.Exec(ctx, + _, err = conn.Exec(ctx, ` UPDATE thread SET first_id = $1, last_id = $2 diff --git a/src/migration/seed.go b/src/migration/seed.go index 081c9167..25a38fb4 100644 --- a/src/migration/seed.go +++ b/src/migration/seed.go @@ -10,7 +10,9 @@ import ( "git.handmade.network/hmn/hmn/src/auth" "git.handmade.network/hmn/hmn/src/config" "git.handmade.network/hmn/hmn/src/db" + "git.handmade.network/hmn/hmn/src/hmndata" "git.handmade.network/hmn/hmn/src/models" + "git.handmade.network/hmn/hmn/src/oops" "git.handmade.network/hmn/hmn/src/utils" lorem "github.com/HandmadeNetwork/golorem" "github.com/jackc/pgx/v4" @@ -117,9 +119,26 @@ func SampleSeed() { charlie := seedUser(ctx, conn, models.User{Username: "charlie", Name: "Charlie"}) fmt.Println("Creating a spammer...") - seedUser(ctx, conn, models.User{Username: "spam", Name: "Hot singletons in your local area", Status: models.UserStatusConfirmed}) + spammer := seedUser(ctx, conn, models.User{ + Username: "spam", + Status: models.UserStatusConfirmed, + Name: "Hot singletons in your local area", + Bio: "Howdy, everybody I go by Jarva seesharpe from Bangalore. In this way, assuming you need to partake in a shared global instance with me then, at that poi", + }) - _ = []*models.User{alice, bob, charlie} + users := []*models.User{alice, bob, charlie, spammer} + + fmt.Println("Creating some threads...") + for i := 0; i < 5; i++ { + thread := seedThread(ctx, conn, models.Thread{}) + populateThread(ctx, conn, thread, users, rand.Intn(5)+1) + } + + // spam-only thread + { + thread := seedThread(ctx, conn, models.Thread{}) + populateThread(ctx, conn, thread, []*models.User{spammer}, 1) + } // admin := CreateAdminUser("admin", "12345678") // user := CreateUser("regular_user", "12345678") @@ -178,6 +197,59 @@ func seedUser(ctx context.Context, conn db.ConnOrTx, input models.User) *models. return user } +func seedThread(ctx context.Context, conn db.ConnOrTx, input models.Thread) *models.Thread { + input.Type = utils.OrDefault(input.Type, models.ThreadTypeForumPost) + + var defaultSubforum *int + if input.Type == models.ThreadTypeForumPost { + id := 2 + defaultSubforum = &id + } + + thread, err := db.QueryOne[models.Thread](ctx, conn, + ` + INSERT INTO thread ( + title, + type, sticky, + project_id, subforum_id, + first_id, last_id + ) + VALUES ( + $1, + $2, $3, + $4, $5, + $6, $7 + ) + RETURNING $columns + `, + utils.OrDefault(input.Title, lorem.Sentence(3, 8)), + utils.OrDefault(input.Type, models.ThreadTypeForumPost), false, + utils.OrDefault(input.ProjectID, models.HMNProjectID), utils.OrDefault(input.SubforumID, defaultSubforum), + -1, -1, + ) + if err != nil { + panic(oops.New(err, "failed to create thread")) + } + + return thread +} + +func populateThread(ctx context.Context, conn db.ConnOrTx, thread *models.Thread, users []*models.User, numPosts int) { + var lastPostId int + for i := 0; i < numPosts; i++ { + user := users[i%len(users)] + + var replyId *int + if lastPostId != 0 { + if rand.Intn(10) < 3 { + replyId = &lastPostId + } + } + + hmndata.CreateNewPost(ctx, conn, thread.ProjectID, thread.ID, thread.Type, user.ID, replyId, lorem.Paragraph(1, 10), "192.168.2.1") + } +} + func randomName() string { return "John Doe" // chosen by fair dice roll. guaranteed to be random. }