diff --git a/src/migration/migrations/2021-07-30T034432Z_AddUserLastMarkedRead.go b/src/migration/migrations/2021-07-30T034432Z_AddUserLastMarkedRead.go new file mode 100644 index 0000000..ef1df6c --- /dev/null +++ b/src/migration/migrations/2021-07-30T034432Z_AddUserLastMarkedRead.go @@ -0,0 +1,44 @@ +package migrations + +import ( + "context" + "time" + + "git.handmade.network/hmn/hmn/src/migration/types" + "git.handmade.network/hmn/hmn/src/oops" + "github.com/jackc/pgx/v4" +) + +func init() { + registerMigration(AddUserLastMarkedRead{}) +} + +type AddUserLastMarkedRead struct{} + +func (m AddUserLastMarkedRead) Version() types.MigrationVersion { + return types.MigrationVersion(time.Date(2021, 7, 30, 3, 44, 32, 0, time.UTC)) +} + +func (m AddUserLastMarkedRead) Name() string { + return "AddUserLastMarkedRead" +} + +func (m AddUserLastMarkedRead) Description() string { + return "Add a field to users that tracks when they last read EVERYTHING." +} + +func (m AddUserLastMarkedRead) Up(ctx context.Context, tx pgx.Tx) error { + _, err := tx.Exec(ctx, ` + ALTER TABLE auth_user + ADD marked_all_read_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT 'epoch'; + `) + if err != nil { + return oops.New(err, "failed to insert new column") + } + + return nil +} + +func (m AddUserLastMarkedRead) Down(ctx context.Context, tx pgx.Tx) error { + panic("Implement me") +} diff --git a/src/models/user.go b/src/models/user.go index eebce74..bdb6d58 100644 --- a/src/models/user.go +++ b/src/models/user.go @@ -36,4 +36,6 @@ type User struct { DiscordSaveShowcase bool `db:"discord_save_showcase"` DiscordDeleteSnippetOnMessageDelete bool `db:"discord_delete_snippet_on_message_delete"` + + MarkedAllReadAt time.Time `db:"marked_all_read_at"` } diff --git a/src/website/feed.go b/src/website/feed.go index b27e38a..2c2db92 100644 --- a/src/website/feed.go +++ b/src/website/feed.go @@ -354,7 +354,9 @@ func fetchAllPosts(c *RequestContext, lineageBuilder *models.SubforumLineageBuil postResult := iPostResult.(*feedPostQuery) hasRead := false - if postResult.ThreadLastReadTime != nil && postResult.ThreadLastReadTime.After(postResult.Post.PostDate) { + if c.CurrentUser != nil && c.CurrentUser.MarkedAllReadAt.After(postResult.Post.PostDate) { + hasRead = true + } else if postResult.ThreadLastReadTime != nil && postResult.ThreadLastReadTime.After(postResult.Post.PostDate) { hasRead = true } else if postResult.SubforumLastReadTime != nil && postResult.SubforumLastReadTime.After(postResult.Post.PostDate) { hasRead = true diff --git a/src/website/forums.go b/src/website/forums.go index 46156da..1a10f9e 100644 --- a/src/website/forums.go +++ b/src/website/forums.go @@ -306,54 +306,67 @@ func ForumMarkRead(c *RequestContext) ResponseData { } defer tx.Rollback(c.Context()) - // TODO(ben): Rework this logic when we rework blogs, threads, etc. sfIds := []int{sfId} if sfId == 0 { - // Select all categories - type sfIdResult struct { - SubforumID int `db:"id"` - } - cats, err := db.Query(c.Context(), tx, sfIdResult{}, + // Mark literally everything as read + _, err := tx.Exec(c.Context(), ` - SELECT $columns - FROM handmade_subforum - WHERE kind = ANY ($1) + UPDATE auth_user + SET marked_all_read_at = NOW() + WHERE id = $1 `, - []models.ThreadType{models.ThreadTypeProjectArticle, models.ThreadTypeForumPost}, + c.CurrentUser.ID, ) if err != nil { - return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch category IDs for CLRI")) + return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to mark all posts as read")) } - catIdResults := cats.ToSlice() - sfIds = make([]int, len(catIdResults)) - for i, res := range catIdResults { - sfIds[i] = res.(*sfIdResult).SubforumID + // Delete thread unread info + _, err = tx.Exec(c.Context(), + ` + DELETE FROM handmade_threadlastreadinfo + WHERE user_id = $1; + `, + c.CurrentUser.ID, + ) + if err != nil { + return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to delete thread unread info")) } - } - c.Perf.StartBlock("SQL", "Update CLRIs") - _, err = tx.Exec(c.Context(), - ` - INSERT INTO handmade_categorylastreadinfo (category_id, user_id, lastread) + // Delete subforum unread info + _, err = tx.Exec(c.Context(), + ` + DELETE FROM handmade_subforumlastreadinfo + WHERE user_id = $1; + `, + c.CurrentUser.ID, + ) + if err != nil { + return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to delete subforum unread info")) + } + } else { + c.Perf.StartBlock("SQL", "Update SLRIs") + _, err = tx.Exec(c.Context(), + ` + INSERT INTO handmade_subforumlastreadinfo (subforum_id, user_id, lastread) SELECT id, $2, $3 - FROM handmade_category + FROM handmade_subforum WHERE id = ANY ($1) - ON CONFLICT (category_id, user_id) DO UPDATE + ON CONFLICT (subforum_id, user_id) DO UPDATE SET lastread = EXCLUDED.lastread `, - sfIds, - c.CurrentUser.ID, - time.Now(), - ) - c.Perf.EndBlock() - if err != nil { - return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to update forum clris")) - } + sfIds, + c.CurrentUser.ID, + time.Now(), + ) + c.Perf.EndBlock() + if err != nil { + return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to update forum slris")) + } - c.Perf.StartBlock("SQL", "Delete TLRIs") - _, err = tx.Exec(c.Context(), - ` + c.Perf.StartBlock("SQL", "Delete TLRIs") + _, err = tx.Exec(c.Context(), + ` DELETE FROM handmade_threadlastreadinfo WHERE user_id = $2 @@ -361,20 +374,21 @@ func ForumMarkRead(c *RequestContext) ResponseData { SELECT id FROM handmade_thread WHERE - category_id = ANY ($1) + subforum_id = ANY ($1) ) `, - sfIds, - c.CurrentUser.ID, - ) - c.Perf.EndBlock() - if err != nil { - return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to delete unnecessary tlris")) + sfIds, + c.CurrentUser.ID, + ) + c.Perf.EndBlock() + if err != nil { + return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to delete unnecessary tlris")) + } } err = tx.Commit(c.Context()) if err != nil { - return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to commit CLRI/TLRI updates")) + return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to commit SLRI/TLRI updates")) } var redirUrl string diff --git a/src/website/landing.go b/src/website/landing.go index 88e38b3..0ba76df 100644 --- a/src/website/landing.go +++ b/src/website/landing.go @@ -146,7 +146,9 @@ func Index(c *RequestContext) ResponseData { projectPost := projectPostRow.(*projectPostQuery) hasRead := false - if projectPost.ThreadLastReadTime != nil && projectPost.ThreadLastReadTime.After(projectPost.Post.PostDate) { + if c.CurrentUser != nil && c.CurrentUser.MarkedAllReadAt.After(projectPost.Post.PostDate) { + hasRead = true + } else if projectPost.ThreadLastReadTime != nil && projectPost.ThreadLastReadTime.After(projectPost.Post.PostDate) { hasRead = true } else if projectPost.SubforumLastReadTime != nil && projectPost.SubforumLastReadTime.After(projectPost.Post.PostDate) { hasRead = true