Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions database/attrs.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type CategoriesAttrs struct {
Slug string
Name string
Description string
Sort int
}

type TagAttrs struct {
Expand Down
3 changes: 3 additions & 0 deletions database/infra/migrations/000004_categories_sort.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
DROP INDEX IF EXISTS idx_categories_sort;
ALTER TABLE categories
DROP COLUMN IF EXISTS sort;
7 changes: 7 additions & 0 deletions database/infra/migrations/000004_categories_sort.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ALTER TABLE categories
ADD COLUMN IF NOT EXISTS sort INT NOT NULL DEFAULT 0;

ALTER TABLE categories
ALTER COLUMN sort DROP DEFAULT;

CREATE INDEX IF NOT EXISTS idx_categories_sort ON categories (sort, name);
1 change: 1 addition & 0 deletions database/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ type Category struct {
Name string `gorm:"type:varchar(255);unique;not null"`
Slug string `gorm:"type:varchar(255);unique;not null"`
Description string `gorm:"type:text"`
Sort int `gorm:"type:int;not null"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`
DeletedAt gorm.DeletedAt
Expand Down
4 changes: 3 additions & 1 deletion database/repository/categories.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func (c Categories) Get() ([]database.Category, error) {
err := c.DB.Sql().
Model(&database.Category{}).
Where("categories.deleted_at is null").
Order("categories.sort asc, categories.name asc").
Find(&categories).Error

if err != nil {
Expand Down Expand Up @@ -53,7 +54,7 @@ func (c Categories) GetAll(paginate pagination.Paginate) (*pagination.Pagination
Preload("Posts", "posts.deleted_at IS NULL AND posts.published_at IS NOT NULL").
Offset(offset).
Limit(paginate.Limit).
Order("categories.name asc").
Order("categories.sort asc, categories.name asc").
Group(group).
Find(&categories).Error

Expand Down Expand Up @@ -104,6 +105,7 @@ func (c Categories) CreateOrUpdate(post database.Post, attrs database.PostsAttrs
Name: seed.Name,
Slug: seed.Slug,
Description: seed.Description,
Sort: seed.Sort,
}

if result := c.DB.Sql().Create(&category); model.HasDbIssues(result.Error) {
Expand Down
171 changes: 170 additions & 1 deletion database/repository/categories_postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package repository_test
import (
"testing"

"github.com/google/uuid"
"github.com/oullin/database"
"github.com/oullin/database/repository"
)

func TestCategoriesFindByPostgres(t *testing.T) {
conn := newPostgresConnection(t, &database.Category{})

category := seedCategory(t, conn, "news", "News")
category := seedCategory(t, conn, "news", "News", 1)

repo := repository.Categories{DB: conn}

Expand All @@ -22,3 +23,171 @@ func TestCategoriesFindByPostgres(t *testing.T) {
t.Fatalf("expected missing category lookup to return nil")
}
}

func TestCategoriesGetOrdersBySort(t *testing.T) {
conn := newPostgresConnection(t, &database.Category{})

repo := repository.Categories{DB: conn}

low := database.Category{
UUID: uuid.NewString(),
Name: "Low",
Slug: "low",
Sort: 10,
}

high := database.Category{
UUID: uuid.NewString(),
Name: "High",
Slug: "high",
Sort: 20,
}

if err := conn.Sql().Create(&high).Error; err != nil {
t.Fatalf("create high sort: %v", err)
}

if err := conn.Sql().Create(&low).Error; err != nil {
t.Fatalf("create low sort: %v", err)
}

items, err := repo.Get()
if err != nil {
t.Fatalf("get: %v", err)
}

if len(items) != 2 {
t.Fatalf("expected 2 categories, got %d", len(items))
}

if items[0].Slug != "low" || items[1].Slug != "high" {
t.Fatalf("expected sort ordering, got %+v", items)
}
}

func TestCategoriesGetOrdersBySortAndName(t *testing.T) {
conn := newPostgresConnection(t, &database.Category{})

repo := repository.Categories{DB: conn}

alpha := database.Category{
UUID: uuid.NewString(),
Name: "Alpha",
Slug: "alpha",
Sort: 10,
}

bravo := database.Category{
UUID: uuid.NewString(),
Name: "Bravo",
Slug: "bravo",
Sort: 10,
}

if err := conn.Sql().Create(&bravo).Error; err != nil {
t.Fatalf("create bravo: %v", err)
}

if err := conn.Sql().Create(&alpha).Error; err != nil {
t.Fatalf("create alpha: %v", err)
}

items, err := repo.Get()
if err != nil {
t.Fatalf("get: %v", err)
}

if len(items) != 2 {
t.Fatalf("expected 2 categories, got %d", len(items))
}

if items[0].Slug != "alpha" || items[1].Slug != "bravo" {
t.Fatalf("expected secondary name ordering, got %+v", items)
}
}

func TestCategoriesExistOrUpdatePreservesSortWhenZero(t *testing.T) {
conn := newPostgresConnection(t, &database.Category{})

repo := repository.Categories{DB: conn}

original := database.Category{
UUID: uuid.NewString(),
Name: "Original",
Slug: "original",
Sort: 25,
}

if err := conn.Sql().Create(&original).Error; err != nil {
t.Fatalf("create original: %v", err)
}

existed, err := repo.ExistOrUpdate(database.CategoriesAttrs{
Slug: "original",
Name: "Renamed",
Sort: 0,
})
if err != nil {
t.Fatalf("exist or update: %v", err)
}

if !existed {
t.Fatalf("expected category to exist")
}

var updated database.Category
if err := conn.Sql().Where("id = ?", original.ID).First(&updated).Error; err != nil {
t.Fatalf("reload category: %v", err)
}

if updated.Sort != original.Sort {
t.Fatalf("expected sort to remain %d, got %d", original.Sort, updated.Sort)
}

if updated.Name != "Renamed" {
t.Fatalf("expected name to be updated, got %s", updated.Name)
}
}

func TestCategoriesExistOrUpdateUpdatesSortWhenNonZero(t *testing.T) {
conn := newPostgresConnection(t, &database.Category{})

repo := repository.Categories{DB: conn}

original := database.Category{
UUID: uuid.NewString(),
Name: "Original",
Slug: "original",
Sort: 25,
}

if err := conn.Sql().Create(&original).Error; err != nil {
t.Fatalf("create original: %v", err)
}

existed, err := repo.ExistOrUpdate(database.CategoriesAttrs{
Slug: "original",
Name: "Renamed",
Sort: 30,
})
if err != nil {
t.Fatalf("exist or update: %v", err)
}

if !existed {
t.Fatalf("expected category to exist")
}

var updated database.Category
if err := conn.Sql().Where("id = ?", original.ID).First(&updated).Error; err != nil {
t.Fatalf("reload category: %v", err)
}

if updated.Sort != 30 {
t.Fatalf("expected sort to be updated to 30, got %d", updated.Sort)
}

if updated.Name != "Renamed" {
t.Fatalf("expected name to be updated, got %s", updated.Name)
}
}
12 changes: 6 additions & 6 deletions database/repository/posts_postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestPostsCreateLinksAssociationsPostgres(t *testing.T) {
)

user := seedUser(t, conn, "Alice", "Smith", "alice")
category := seedCategory(t, conn, "tech", "Tech")
category := seedCategory(t, conn, "tech", "Tech", 1)
tag := seedTag(t, conn, "go", "Go")

postsRepo := repository.Posts{
Expand Down Expand Up @@ -89,7 +89,7 @@ func TestPostsFindByLoadsAssociationsPostgres(t *testing.T) {
)

user := seedUser(t, conn, "Bob", "Jones", "bobj")
category := seedCategory(t, conn, "career", "Career")
category := seedCategory(t, conn, "career", "Career", 1)
tag := seedTag(t, conn, "work", "Work")
post := seedPost(t, conn, user, category, tag, "career-path", "Career Path", true)

Expand Down Expand Up @@ -138,7 +138,7 @@ func TestPostsGetAllFiltersPublishedRecordsPostgres(t *testing.T) {
authorOne := seedUser(t, conn, "Carol", "One", "carol")
authorTwo := seedUser(t, conn, "Dave", "Two", "dave")

category := seedCategory(t, conn, "engineering", "Engineering")
category := seedCategory(t, conn, "engineering", "Engineering", 1)
tag := seedTag(t, conn, "backend", "Backend")
otherTag := seedTag(t, conn, "frontend", "Frontend")

Expand Down Expand Up @@ -196,8 +196,8 @@ func TestPostsGetAllDeduplicatesResultsPostgres(t *testing.T) {

author := seedUser(t, conn, "Eve", "Duplicates", "eve")

primaryCategory := seedCategory(t, conn, "engineering", "Engineering")
secondaryCategory := seedCategory(t, conn, "engagement", "Engagement")
primaryCategory := seedCategory(t, conn, "engineering", "Engineering", 1)
secondaryCategory := seedCategory(t, conn, "engagement", "Engagement", 2)

primaryTag := seedTag(t, conn, "eng-backend", "Eng Backend")
secondaryTag := seedTag(t, conn, "eng-frontend", "Eng Frontend")
Expand Down Expand Up @@ -240,7 +240,7 @@ func TestPostsGetAllDeduplicatesResultsPostgres(t *testing.T) {
func TestPostsFindCategoryByDelegatesPostgres(t *testing.T) {
conn := newPostgresConnection(t, &database.Category{})

category := seedCategory(t, conn, "lifestyle", "Lifestyle")
category := seedCategory(t, conn, "lifestyle", "Lifestyle", 1)

postsRepo := repository.Posts{
DB: conn,
Expand Down
2 changes: 2 additions & 0 deletions database/repository/repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ func TestCategoriesFindBy(t *testing.T) {
UUID: uuid.NewString(),
Name: "News",
Slug: "news",
Sort: 1,
}

if err := conn.Sql().Create(&c).Error; err != nil {
Expand Down Expand Up @@ -192,6 +193,7 @@ func TestPostsCreateAndFind(t *testing.T) {
UUID: uuid.NewString(),
Name: "Tech",
Slug: "tech",
Sort: 1,
}

if err := conn.Sql().Create(&cat).Error; err != nil {
Expand Down
3 changes: 2 additions & 1 deletion database/repository/testhelpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,14 @@ func seedUser(t *testing.T, conn *database.Connection, first, last, username str
return user
}

func seedCategory(t *testing.T, conn *database.Connection, slug, name string) database.Category {
func seedCategory(t *testing.T, conn *database.Connection, slug, name string, sort int) database.Category {
t.Helper()

category := database.Category{
UUID: uuid.NewString(),
Slug: slug,
Name: name,
Sort: sort,
}

if err := conn.Sql().Create(&category).Error; err != nil {
Expand Down
4 changes: 2 additions & 2 deletions database/seeder/importer/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,8 +500,8 @@ func TestSeedFromFileRunsMigrations(t *testing.T) {
t.Cleanup(cleanup)

contents := strings.Join([]string{
"INSERT INTO categories (uuid, name, slug)",
"VALUES ('00000000-0000-0000-0000-00000000c001', 'Tech', 'tech');",
"INSERT INTO categories (uuid, name, slug, sort)",
"VALUES ('00000000-0000-0000-0000-00000000c001', 'Tech', 'tech', 1);",
}, "\n")

fileName := writeStorageFile(t, withSuffix(t, ".sql"), contents)
Expand Down
4 changes: 3 additions & 1 deletion database/seeder/seeds/categories.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ func (s CategoriesSeed) Create(attrs database.CategoriesAttrs) ([]database.Categ
"Cloud", "Data", "DevOps", "ML", "Startups", "Engineering",
}

for _, seed := range seeds {
for index, seed := range seeds {
sort := index + 1
categories = append(categories, database.Category{
UUID: uuid.NewString(),
Name: seed,
Slug: strings.ToLower(seed),
Description: attrs.Description,
Sort: sort,
})
}

Expand Down
1 change: 1 addition & 0 deletions handler/categories.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func (h *CategoriesHandler) Index(w http.ResponseWriter, r *http.Request) *endpo
Name: s.Name,
Slug: s.Slug,
Description: s.Description,
Sort: s.Sort,
}
},
)
Expand Down
Loading
Loading