diff --git a/src/db/db.go b/src/db/db.go index 7c21119..49492e8 100644 --- a/src/db/db.go +++ b/src/db/db.go @@ -231,7 +231,16 @@ func followPathThroughStructs(structPtrVal reflect.Value, path []int) (reflect.V return val, field } -func Query(ctx context.Context, conn ConnOrTx, destExample interface{}, query string, args ...interface{}) (*StructQueryIterator, error) { +func Query(ctx context.Context, conn ConnOrTx, destExample interface{}, query string, args ...interface{}) ([]interface{}, error) { + it, err := QueryIterator(ctx, conn, destExample, query, args...) + if err != nil { + return nil, err + } else { + return it.ToSlice(), nil + } +} + +func QueryIterator(ctx context.Context, conn ConnOrTx, destExample interface{}, query string, args ...interface{}) (*StructQueryIterator, error) { destType := reflect.TypeOf(destExample) columnNames, fieldPaths, err := getColumnNamesAndPaths(destType, nil, "") if err != nil { @@ -347,7 +356,7 @@ result but find nothing. var NotFound = errors.New("not found") func QueryOne(ctx context.Context, conn ConnOrTx, destExample interface{}, query string, args ...interface{}) (interface{}, error) { - rows, err := Query(ctx, conn, destExample, query, args...) + rows, err := QueryIterator(ctx, conn, destExample, query, args...) if err != nil { return nil, err } diff --git a/src/discord/gateway.go b/src/discord/gateway.go index 75075e6..8e3a4b2 100644 --- a/src/discord/gateway.go +++ b/src/discord/gateway.go @@ -408,7 +408,7 @@ func (bot *botInstance) doSender(ctx context.Context) { } defer tx.Rollback(ctx) - itMessages, err := db.Query(ctx, tx, models.DiscordOutgoingMessage{}, ` + msgs, err := db.Query(ctx, tx, models.DiscordOutgoingMessage{}, ` SELECT $columns FROM discord_outgoingmessages ORDER BY id ASC @@ -418,7 +418,6 @@ func (bot *botInstance) doSender(ctx context.Context) { return } - msgs := itMessages.ToSlice() for _, imsg := range msgs { msg := imsg.(*models.DiscordOutgoingMessage) if time.Now().After(msg.ExpiresAt) { diff --git a/src/discord/history.go b/src/discord/history.go index 343d752..4573dff 100644 --- a/src/discord/history.go +++ b/src/discord/history.go @@ -64,7 +64,7 @@ func fetchMissingContent(ctx context.Context, dbConn *pgxpool.Pool) { type query struct { Message models.DiscordMessage `db:"msg"` } - result, err := db.Query(ctx, dbConn, query{}, + imessagesWithoutContent, err := db.Query(ctx, dbConn, query{}, ` SELECT $columns FROM @@ -82,7 +82,6 @@ func fetchMissingContent(ctx context.Context, dbConn *pgxpool.Pool) { log.Error().Err(err).Msg("failed to check for messages without content") return } - imessagesWithoutContent := result.ToSlice() if len(imessagesWithoutContent) > 0 { log.Info().Msgf("There are %d Discord messages without content, fetching their content now...", len(imessagesWithoutContent)) diff --git a/src/discord/showcase.go b/src/discord/showcase.go index a145d8a..81bb596 100644 --- a/src/discord/showcase.go +++ b/src/discord/showcase.go @@ -749,7 +749,7 @@ func UpdateSnippetTagsIfAny(ctx context.Context, dbConn db.ConnOrTx, msg *Messag type tagsRow struct { Tag models.Tag `db:"tags"` } - itUserTags, err := db.Query(ctx, tx, tagsRow{}, + iUserTags, err := db.Query(ctx, tx, tagsRow{}, ` SELECT $columns FROM @@ -764,7 +764,6 @@ func UpdateSnippetTagsIfAny(ctx context.Context, dbConn db.ConnOrTx, msg *Messag if err != nil { return oops.New(err, "failed to fetch tags for user projects") } - iUserTags := itUserTags.ToSlice() var tagIDs []int for _, itag := range iUserTags { @@ -805,7 +804,7 @@ var RESnippetableUrl = regexp.MustCompile(`^https?://(youtu\.be|(www\.)?youtube\ func getSnippetAssetOrUrl(ctx context.Context, tx db.ConnOrTx, msg *models.DiscordMessage) (*uuid.UUID, *string, error) { // Check attachments - itAttachments, err := db.Query(ctx, tx, models.DiscordMessageAttachment{}, + attachments, err := db.Query(ctx, tx, models.DiscordMessageAttachment{}, ` SELECT $columns FROM handmade_discordmessageattachment @@ -816,14 +815,13 @@ func getSnippetAssetOrUrl(ctx context.Context, tx db.ConnOrTx, msg *models.Disco if err != nil { return nil, nil, oops.New(err, "failed to fetch message attachments") } - attachments := itAttachments.ToSlice() for _, iattachment := range attachments { attachment := iattachment.(*models.DiscordMessageAttachment) return &attachment.AssetID, nil, nil } // Check embeds - itEmbeds, err := db.Query(ctx, tx, models.DiscordMessageEmbed{}, + embeds, err := db.Query(ctx, tx, models.DiscordMessageEmbed{}, ` SELECT $columns FROM handmade_discordmessageembed @@ -834,7 +832,6 @@ func getSnippetAssetOrUrl(ctx context.Context, tx db.ConnOrTx, msg *models.Disco if err != nil { return nil, nil, oops.New(err, "failed to fetch discord embeds") } - embeds := itEmbeds.ToSlice() for _, iembed := range embeds { embed := iembed.(*models.DiscordMessageEmbed) if embed.VideoID != nil { diff --git a/src/hmndata/project_helper.go b/src/hmndata/project_helper.go index 8f2b867..7c85ac2 100644 --- a/src/hmndata/project_helper.go +++ b/src/hmndata/project_helper.go @@ -145,11 +145,10 @@ func FetchProjects( } // Do the query - itProjects, err := db.Query(ctx, dbConn, projectRow{}, qb.String(), qb.Args()...) + iprojects, err := db.Query(ctx, dbConn, projectRow{}, qb.String(), qb.Args()...) if err != nil { return nil, oops.New(err, "failed to fetch projects") } - iprojects := itProjects.ToSlice() // Fetch project owners to do permission checks projectIds := make([]int, len(iprojects)) @@ -340,7 +339,7 @@ func FetchMultipleProjectsOwners( UserID int `db:"user_id"` ProjectID int `db:"project_id"` } - it, err := db.Query(ctx, tx, userProject{}, + iuserprojects, err := db.Query(ctx, tx, userProject{}, ` SELECT $columns FROM handmade_user_projects @@ -351,7 +350,6 @@ func FetchMultipleProjectsOwners( if err != nil { return nil, oops.New(err, "failed to fetch project IDs") } - iuserprojects := it.ToSlice() // Get the unique user IDs from this set and fetch the users from the db var userIds []int @@ -368,7 +366,7 @@ func FetchMultipleProjectsOwners( userIds = append(userIds, userProject.UserID) } } - it, err = db.Query(ctx, tx, models.User{}, + iusers, err := db.Query(ctx, tx, models.User{}, ` SELECT $columns FROM auth_user @@ -380,7 +378,6 @@ func FetchMultipleProjectsOwners( if err != nil { return nil, oops.New(err, "failed to fetch users for projects") } - iusers := it.ToSlice() // Build the final result set with real user data res := make([]ProjectOwners, len(projectIds)) diff --git a/src/hmndata/snippet_helper.go b/src/hmndata/snippet_helper.go index 0feb709..c2b2215 100644 --- a/src/hmndata/snippet_helper.go +++ b/src/hmndata/snippet_helper.go @@ -47,7 +47,7 @@ func FetchSnippets( type snippetIDRow struct { SnippetID int `db:"snippet_id"` } - itSnippetIDs, err := db.Query(ctx, tx, snippetIDRow{}, + iSnippetIDs, err := db.Query(ctx, tx, snippetIDRow{}, ` SELECT DISTINCT snippet_id FROM @@ -61,7 +61,6 @@ func FetchSnippets( if err != nil { return nil, oops.New(err, "failed to get snippet IDs for tag") } - iSnippetIDs := itSnippetIDs.ToSlice() // special early-out: no snippets found for these tags at all if len(iSnippetIDs) == 0 { @@ -125,11 +124,10 @@ func FetchSnippets( DiscordMessage *models.DiscordMessage `db:"discord_message"` } - it, err := db.Query(ctx, tx, resultRow{}, qb.String(), qb.Args()...) + iresults, err := db.Query(ctx, tx, resultRow{}, qb.String(), qb.Args()...) if err != nil { return nil, oops.New(err, "failed to fetch threads") } - iresults := it.ToSlice() result := make([]SnippetAndStuff, len(iresults)) // allocate extra space because why not snippetIDs := make([]int, len(iresults)) @@ -151,7 +149,7 @@ func FetchSnippets( SnippetID int `db:"snippet_tags.snippet_id"` Tag *models.Tag `db:"tags"` } - itSnippetTags, err := db.Query(ctx, tx, snippetTagRow{}, + iSnippetTags, err := db.Query(ctx, tx, snippetTagRow{}, ` SELECT $columns FROM @@ -165,7 +163,6 @@ func FetchSnippets( if err != nil { return nil, oops.New(err, "failed to fetch tags for snippets") } - iSnippetTags := itSnippetTags.ToSlice() // associate tags with snippets resultBySnippetId := make(map[int]*SnippetAndStuff) diff --git a/src/hmndata/tag_helper.go b/src/hmndata/tag_helper.go index ca201b4..fab5b94 100644 --- a/src/hmndata/tag_helper.go +++ b/src/hmndata/tag_helper.go @@ -40,11 +40,10 @@ func FetchTags(ctx context.Context, dbConn db.ConnOrTx, q TagQuery) ([]*models.T qb.Add(`LIMIT $? OFFSET $?`, q.Limit, q.Offset) } - it, err := db.Query(ctx, dbConn, models.Tag{}, qb.String(), qb.Args()...) + itags, err := db.Query(ctx, dbConn, models.Tag{}, qb.String(), qb.Args()...) if err != nil { return nil, oops.New(err, "failed to fetch tags") } - itags := it.ToSlice() res := make([]*models.Tag, len(itags)) for i, itag := range itags { diff --git a/src/hmndata/threads_and_posts_helper.go b/src/hmndata/threads_and_posts_helper.go index 8011b86..f745273 100644 --- a/src/hmndata/threads_and_posts_helper.go +++ b/src/hmndata/threads_and_posts_helper.go @@ -143,11 +143,10 @@ func FetchThreads( ForumLastReadTime *time.Time `db:"slri.lastread"` } - it, err := db.Query(ctx, dbConn, resultRow{}, qb.String(), qb.Args()...) + iresults, err := db.Query(ctx, dbConn, resultRow{}, qb.String(), qb.Args()...) if err != nil { return nil, oops.New(err, "failed to fetch threads") } - iresults := it.ToSlice() result := make([]ThreadAndStuff, len(iresults)) for i, iresult := range iresults { @@ -398,11 +397,10 @@ func FetchPosts( qb.Add(`LIMIT $? OFFSET $?`, q.Limit, q.Offset) } - it, err := db.Query(ctx, dbConn, resultRow{}, qb.String(), qb.Args()...) + iresults, err := db.Query(ctx, dbConn, resultRow{}, qb.String(), qb.Args()...) if err != nil { return nil, oops.New(err, "failed to fetch posts") } - iresults := it.ToSlice() result := make([]PostAndStuff, len(iresults)) for i, iresult := range iresults { @@ -696,20 +694,28 @@ func DeletePost( tx pgx.Tx, threadId, postId int, ) (threadDeleted bool) { - isFirstPost, err := db.QueryBool(ctx, tx, + type threadInfo struct { + FirstPostID int `db:"first_id"` + Deleted bool `db:"deleted"` + } + ti, err := db.QueryOne(ctx, tx, threadInfo{}, ` - SELECT thread.first_id = $1 + SELECT $columns FROM handmade_thread AS thread WHERE - thread.id = $2 + thread.id = $1 `, - postId, threadId, ) if err != nil { - panic(oops.New(err, "failed to check if post was the first post in the thread")) + panic(oops.New(err, "failed to fetch thread info")) } + info := ti.(*threadInfo) + if info.Deleted { + return true + } + isFirstPost := info.FirstPostID == postId if isFirstPost { // Just delete the whole thread and all its posts. @@ -848,7 +854,7 @@ func CreatePostVersion(ctx context.Context, tx pgx.Tx, postId int, unparsedConte var values [][]interface{} - for _, asset := range assetResult.ToSlice() { + for _, asset := range assetResult { values = append(values, []interface{}{postId, asset.(*assetId).AssetID}) } @@ -884,7 +890,7 @@ func FixThreadPostIds(ctx context.Context, tx pgx.Tx, threadId int) error { } var firstPost, lastPost *models.Post - for _, ipost := range postsIter.ToSlice() { + for _, ipost := range postsIter { post := ipost.(*models.Post) if firstPost == nil || post.PostDate.Before(firstPost.PostDate) { diff --git a/src/hmnurl/hmnurl_test.go b/src/hmnurl/hmnurl_test.go index 77dba56..9c18d64 100644 --- a/src/hmnurl/hmnurl_test.go +++ b/src/hmnurl/hmnurl_test.go @@ -105,6 +105,8 @@ func TestUserSettings(t *testing.T) { func TestAdmin(t *testing.T) { AssertRegexMatch(t, BuildAdminAtomFeed(), RegexAdminAtomFeed, nil) AssertRegexMatch(t, BuildAdminApprovalQueue(), RegexAdminApprovalQueue, nil) + AssertRegexMatch(t, BuildAdminSetUserStatus(), RegexAdminSetUserStatus, nil) + AssertRegexMatch(t, BuildAdminNukeUser(), RegexAdminNukeUser, nil) } func TestSnippet(t *testing.T) { diff --git a/src/hmnurl/urls.go b/src/hmnurl/urls.go index 4e8e8ba..560f5db 100644 --- a/src/hmnurl/urls.go +++ b/src/hmnurl/urls.go @@ -216,6 +216,20 @@ func BuildAdminApprovalQueue() string { return Url("/admin/approvals", nil) } +var RegexAdminSetUserStatus = regexp.MustCompile(`^/admin/setuserstatus$`) + +func BuildAdminSetUserStatus() string { + defer CatchPanic() + return Url("/admin/setuserstatus", nil) +} + +var RegexAdminNukeUser = regexp.MustCompile(`^/admin/nukeuser$`) + +func BuildAdminNukeUser() string { + defer CatchPanic() + return Url("/admin/nukeuser", nil) +} + /* * Snippets */ diff --git a/src/models/subforum.go b/src/models/subforum.go index 38089f3..d65346a 100644 --- a/src/models/subforum.go +++ b/src/models/subforum.go @@ -47,7 +47,7 @@ func GetFullSubforumTree(ctx context.Context, conn *pgxpool.Pool) SubforumTree { type subforumRow struct { Subforum Subforum `db:"sf"` } - rows, err := db.Query(ctx, conn, subforumRow{}, + rowsSlice, err := db.Query(ctx, conn, subforumRow{}, ` SELECT $columns FROM @@ -59,7 +59,6 @@ func GetFullSubforumTree(ctx context.Context, conn *pgxpool.Pool) SubforumTree { panic(oops.New(err, "failed to fetch subforum tree")) } - rowsSlice := rows.ToSlice() sfTreeMap := make(map[int]*SubforumTreeNode, len(rowsSlice)) for _, row := range rowsSlice { sf := row.(*subforumRow).Subforum diff --git a/src/templates/mapping.go b/src/templates/mapping.go index f6f178c..b2848db 100644 --- a/src/templates/mapping.go +++ b/src/templates/mapping.go @@ -193,6 +193,7 @@ func UserToTemplate(u *models.User, currentTheme string) User { Username: u.Username, Email: email, IsStaff: u.IsStaff, + Status: int(u.Status), Name: u.BestName(), Bio: u.Bio, diff --git a/src/templates/src/user_profile.html b/src/templates/src/user_profile.html index 88c24af..adffc7d 100644 --- a/src/templates/src/user_profile.html +++ b/src/templates/src/user_profile.html @@ -1,5 +1,32 @@ {{ template "base.html" . }} +{{ define "extrahead" }} + +{{ end }} + {{ define "content" }}