From 54d32833a668842cb2f3e1077e1bd38dee6eeeae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 06:18:12 +0000 Subject: [PATCH 1/7] Initial plan From 4272d8a3ed73541845c848243a97ce6d7ad88482 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 06:25:18 +0000 Subject: [PATCH 2/7] Add /holes/_sfw endpoint to filter out NSFW tagged holes Co-authored-by: KYLN24 <54014142+KYLN24@users.noreply.github.com> --- apis/hole/apis.go | 42 +++++++++++++++++++++++++++++++++++++++++ apis/hole/routes.go | 1 + tests/hole_test.go | 46 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/apis/hole/apis.go b/apis/hole/apis.go index f044f7a..122755f 100644 --- a/apis/hole/apis.go +++ b/apis/hole/apis.go @@ -161,6 +161,48 @@ func ListGoodHoles(c *fiber.Ctx) error { return Serialize(c, &holes) } +// ListSfwHoles +// +// @Summary List safe for work holes +// @Tags Hole +// @Produce json +// @Router /holes/_sfw [get] +// @Param object query QueryTime false "query" +// @Success 200 {array} Hole +func ListSfwHoles(c *fiber.Ctx) error { + var query QueryTime + err := common.ValidateQuery(c, &query) + if err != nil { + return err + } + _, err = common.GetUserID(c) + if err != nil { + return err + } + + // get holes + var holes Holes + querySet, err := holes.MakeQuerySet(query.Offset, query.Size, query.Order, c) + if err != nil { + return err + } + + // Exclude holes that have any NSFW tags + // Use a subquery to find holes with NSFW tags + querySet = querySet.Where("hole.id NOT IN (?)", + DB.Table("hole_tags"). + Select("hole_tags.hole_id"). + Joins("JOIN tag ON tag.id = hole_tags.tag_id"). + Where("tag.nsfw = ?", true)) + + err = querySet.Find(&holes).Error + if err != nil { + return err + } + + return Serialize(c, &holes) +} + // ListHoles // // @Summary API for Listing Holes diff --git a/apis/hole/routes.go b/apis/hole/routes.go index cafc83d..a06ec45 100644 --- a/apis/hole/routes.go +++ b/apis/hole/routes.go @@ -13,6 +13,7 @@ func RegisterRoutes(app fiber.Router) { app.Get("/holes/:id", GetHole) app.Get("/holes", ListHoles) app.Get("/holes/_good", ListGoodHoles) + app.Get("/holes/_sfw", ListSfwHoles) app.Post("/divisions/:id/holes", utils.MiddlewareHasAnsweredQuestions, CreateHole) app.Post("/holes", utils.MiddlewareHasAnsweredQuestions, CreateHoleOld) app.Patch("/holes/:id/_webvpn", ModifyHole) diff --git a/tests/hole_test.go b/tests/hole_test.go index 5713e11..6e4bc2f 100644 --- a/tests/hole_test.go +++ b/tests/hole_test.go @@ -140,3 +140,49 @@ func TestHoleStats(t *testing.T) { } } + +func TestListSfwHoles(t *testing.T) { + // Create test holes with and without NSFW tags + nsfwTag := Tag{Name: "*nsfw_test", Nsfw: true} + safeTag := Tag{Name: "safe_test", Nsfw: false} + + holeWithNsfw := Hole{ + DivisionID: 1, + Tags: Tags{&nsfwTag}, + Floors: Floors{{Content: "test nsfw hole"}}, + } + + holeWithoutNsfw := Hole{ + DivisionID: 1, + Tags: Tags{&safeTag}, + Floors: Floors{{Content: "test safe hole"}}, + } + + err := DB.Create(&holeWithNsfw).Error + assert.Nil(t, err) + + err = DB.Create(&holeWithoutNsfw).Error + assert.Nil(t, err) + + // Get SFW holes + var sfwHoles Holes + testAPIModel(t, "get", "/api/holes/_sfw", 200, &sfwHoles) + + // Verify that holes with NSFW tags are excluded + for _, hole := range sfwHoles { + var tags Tags + err := DB.Model(hole).Association("Tags").Find(&tags) + assert.Nil(t, err) + + // Check that none of the tags are NSFW + for _, tag := range tags { + assert.False(t, tag.Nsfw, "SFW holes should not have NSFW tags") + } + } + + // Clean up + DB.Unscoped().Delete(&holeWithNsfw) + DB.Unscoped().Delete(&holeWithoutNsfw) + DB.Unscoped().Delete(&nsfwTag) + DB.Unscoped().Delete(&safeTag) +} From 602f41714c9a4e1391e7ff8494efd3591fb2c288 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 06:27:04 +0000 Subject: [PATCH 3/7] Improve query performance and test cleanup for SFW endpoint Co-authored-by: KYLN24 <54014142+KYLN24@users.noreply.github.com> --- apis/hole/apis.go | 12 ++++++------ tests/hole_test.go | 11 +++++------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/apis/hole/apis.go b/apis/hole/apis.go index 122755f..4f2f69d 100644 --- a/apis/hole/apis.go +++ b/apis/hole/apis.go @@ -188,12 +188,12 @@ func ListSfwHoles(c *fiber.Ctx) error { } // Exclude holes that have any NSFW tags - // Use a subquery to find holes with NSFW tags - querySet = querySet.Where("hole.id NOT IN (?)", - DB.Table("hole_tags"). - Select("hole_tags.hole_id"). - Joins("JOIN tag ON tag.id = hole_tags.tag_id"). - Where("tag.nsfw = ?", true)) + // Use LEFT JOIN to find holes without NSFW tags for better performance + querySet = querySet. + Joins("LEFT JOIN hole_tags ON hole.id = hole_tags.hole_id AND hole_tags.tag_id IN (?)", + DB.Table("tag").Select("id").Where("nsfw = ?", true)). + Where("hole_tags.hole_id IS NULL"). + Group("hole.id") err = querySet.Find(&holes).Error if err != nil { diff --git a/tests/hole_test.go b/tests/hole_test.go index 6e4bc2f..042183e 100644 --- a/tests/hole_test.go +++ b/tests/hole_test.go @@ -160,9 +160,14 @@ func TestListSfwHoles(t *testing.T) { err := DB.Create(&holeWithNsfw).Error assert.Nil(t, err) + defer DB.Unscoped().Delete(&holeWithNsfw) err = DB.Create(&holeWithoutNsfw).Error assert.Nil(t, err) + defer DB.Unscoped().Delete(&holeWithoutNsfw) + + defer DB.Unscoped().Delete(&nsfwTag) + defer DB.Unscoped().Delete(&safeTag) // Get SFW holes var sfwHoles Holes @@ -179,10 +184,4 @@ func TestListSfwHoles(t *testing.T) { assert.False(t, tag.Nsfw, "SFW holes should not have NSFW tags") } } - - // Clean up - DB.Unscoped().Delete(&holeWithNsfw) - DB.Unscoped().Delete(&holeWithoutNsfw) - DB.Unscoped().Delete(&nsfwTag) - DB.Unscoped().Delete(&safeTag) } From a6e727e031b3116a5336c6d42bbb4b9d734e2439 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 06:28:36 +0000 Subject: [PATCH 4/7] Fix defer cleanup order in test Co-authored-by: KYLN24 <54014142+KYLN24@users.noreply.github.com> --- tests/hole_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/hole_test.go b/tests/hole_test.go index 042183e..0c689c5 100644 --- a/tests/hole_test.go +++ b/tests/hole_test.go @@ -158,6 +158,10 @@ func TestListSfwHoles(t *testing.T) { Floors: Floors{{Content: "test safe hole"}}, } + // Set up cleanup in reverse order (tags cleanup last due to LIFO defer execution) + defer DB.Unscoped().Delete(&nsfwTag) + defer DB.Unscoped().Delete(&safeTag) + err := DB.Create(&holeWithNsfw).Error assert.Nil(t, err) defer DB.Unscoped().Delete(&holeWithNsfw) @@ -166,9 +170,6 @@ func TestListSfwHoles(t *testing.T) { assert.Nil(t, err) defer DB.Unscoped().Delete(&holeWithoutNsfw) - defer DB.Unscoped().Delete(&nsfwTag) - defer DB.Unscoped().Delete(&safeTag) - // Get SFW holes var sfwHoles Holes testAPIModel(t, "get", "/api/holes/_sfw", 200, &sfwHoles) From 414c2e80c19542e414518266cb85613b206721c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 06:30:35 +0000 Subject: [PATCH 5/7] Improve test: remove redundant Nsfw field and clarify defer order Co-authored-by: KYLN24 <54014142+KYLN24@users.noreply.github.com> --- tests/hole_test.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/hole_test.go b/tests/hole_test.go index 0c689c5..fc07644 100644 --- a/tests/hole_test.go +++ b/tests/hole_test.go @@ -143,8 +143,9 @@ func TestHoleStats(t *testing.T) { func TestListSfwHoles(t *testing.T) { // Create test holes with and without NSFW tags - nsfwTag := Tag{Name: "*nsfw_test", Nsfw: true} - safeTag := Tag{Name: "safe_test", Nsfw: false} + // Note: Tag names starting with '*' automatically get Nsfw=true via BeforeCreate hook + nsfwTag := Tag{Name: "*nsfw_test"} + safeTag := Tag{Name: "safe_test"} holeWithNsfw := Hole{ DivisionID: 1, @@ -158,18 +159,19 @@ func TestListSfwHoles(t *testing.T) { Floors: Floors{{Content: "test safe hole"}}, } - // Set up cleanup in reverse order (tags cleanup last due to LIFO defer execution) - defer DB.Unscoped().Delete(&nsfwTag) - defer DB.Unscoped().Delete(&safeTag) - err := DB.Create(&holeWithNsfw).Error assert.Nil(t, err) + // Cleanup in reverse order: delete holes before tags due to foreign key references defer DB.Unscoped().Delete(&holeWithNsfw) err = DB.Create(&holeWithoutNsfw).Error assert.Nil(t, err) defer DB.Unscoped().Delete(&holeWithoutNsfw) + // These will execute after holes are deleted (LIFO order) + defer DB.Unscoped().Delete(&nsfwTag) + defer DB.Unscoped().Delete(&safeTag) + // Get SFW holes var sfwHoles Holes testAPIModel(t, "get", "/api/holes/_sfw", 200, &sfwHoles) From f579e590044dfaeb2f2f1842641106264d0e31fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 06:32:11 +0000 Subject: [PATCH 6/7] Simplify test cleanup with single defer function Co-authored-by: KYLN24 <54014142+KYLN24@users.noreply.github.com> --- tests/hole_test.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/hole_test.go b/tests/hole_test.go index fc07644..7fe4757 100644 --- a/tests/hole_test.go +++ b/tests/hole_test.go @@ -161,16 +161,18 @@ func TestListSfwHoles(t *testing.T) { err := DB.Create(&holeWithNsfw).Error assert.Nil(t, err) - // Cleanup in reverse order: delete holes before tags due to foreign key references - defer DB.Unscoped().Delete(&holeWithNsfw) err = DB.Create(&holeWithoutNsfw).Error assert.Nil(t, err) - defer DB.Unscoped().Delete(&holeWithoutNsfw) - // These will execute after holes are deleted (LIFO order) - defer DB.Unscoped().Delete(&nsfwTag) - defer DB.Unscoped().Delete(&safeTag) + // Cleanup: delete in order that respects foreign keys + // With CASCADE constraints, deleting holes will clean up hole_tags associations + defer func() { + DB.Unscoped().Delete(&holeWithNsfw) + DB.Unscoped().Delete(&holeWithoutNsfw) + DB.Unscoped().Delete(&nsfwTag) + DB.Unscoped().Delete(&safeTag) + }() // Get SFW holes var sfwHoles Holes From c4464ca7f7301a6820e90b8d4403d7ae466fda15 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 06:33:33 +0000 Subject: [PATCH 7/7] Improve code readability with intermediate variable Co-authored-by: KYLN24 <54014142+KYLN24@users.noreply.github.com> --- apis/hole/apis.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apis/hole/apis.go b/apis/hole/apis.go index 4f2f69d..623578c 100644 --- a/apis/hole/apis.go +++ b/apis/hole/apis.go @@ -189,9 +189,9 @@ func ListSfwHoles(c *fiber.Ctx) error { // Exclude holes that have any NSFW tags // Use LEFT JOIN to find holes without NSFW tags for better performance + nsfwTagIDs := DB.Table("tag").Select("id").Where("nsfw = ?", true) querySet = querySet. - Joins("LEFT JOIN hole_tags ON hole.id = hole_tags.hole_id AND hole_tags.tag_id IN (?)", - DB.Table("tag").Select("id").Where("nsfw = ?", true)). + Joins("LEFT JOIN hole_tags ON hole.id = hole_tags.hole_id AND hole_tags.tag_id IN (?)", nsfwTagIDs). Where("hole_tags.hole_id IS NULL"). Group("hole.id")