Skip to content

Commit

Permalink
feat(): add Category repository
Browse files Browse the repository at this point in the history
  • Loading branch information
ncarlier committed Mar 15, 2019
1 parent 0afc1cd commit 4af960b
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 22 deletions.
11 changes: 11 additions & 0 deletions pkg/db/category.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package db

import "github.com/ncarlier/reader/pkg/model"

// CategoryRepository is the repository interface to manage categories
type CategoryRepository interface {
GetCategoryByUserIDAndTitle(userID uint32, title string) (*model.Category, error)
GetCategoriesByUserID(userID uint32) ([]model.Category, error)
CreateOrUpdateCategory(category model.Category) (*model.Category, error)
DeleteCategory(category model.Category) error
}
1 change: 1 addition & 0 deletions pkg/db/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
type DB interface {
Close() error
UserRepository
CategoryRepository
ArticleRepository
}

Expand Down
164 changes: 164 additions & 0 deletions pkg/db/postgres/category.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package postgres

import (
"database/sql"
"errors"

"github.com/ncarlier/reader/pkg/model"
)

func (pg *DB) createCategory(category model.Category) (*model.Category, error) {
row := pg.db.QueryRow(`
INSERT INTO categories
(user_id, title)
VALUES
($1, $2)
RETURNING id, user_id, title, created_at
`,
category.UserID, category.Title,
)
result := model.Category{}

err := row.Scan(
&result.ID,
&result.UserID,
&result.Title,
&result.CreatedAt,
)
if err != nil {
return nil, err
}
return &result, nil
}

func (pg *DB) updateCategory(category model.Category) (*model.Category, error) {
row := pg.db.QueryRow(`
UPDATE categories SET
title=$3,
updated_at=NOW()
WHERE id=$1 AND user_id=$2
RETURNING id, user_id, title, created_at, updated_at
`,
category.ID, category.UserID, category.Title,
)

result := model.Category{}

err := row.Scan(
&result.ID,
&result.UserID,
&result.Title,
&result.CreatedAt,
&result.UpdatedAt,
)
if err != nil {
return nil, err
}
return &result, nil
}

// CreateOrUpdateCategory creates or updates a category into the DB
func (pg *DB) CreateOrUpdateCategory(category model.Category) (*model.Category, error) {
if category.ID != nil {
return pg.updateCategory(category)
}
return pg.createCategory(category)
}

// GetCategoryByUserIDAndTitle returns a category of an user form the DB
func (pg *DB) GetCategoryByUserIDAndTitle(userID uint32, title string) (*model.Category, error) {
row := pg.db.QueryRow(`
SELECT
id,
user_id,
title,
created_at,
updated_at
FROM categories
WHERE user_id = $1 AND title = $2`,
userID, title,
)

result := model.Category{}

err := row.Scan(
&result.ID,
&result.UserID,
&result.Title,
&result.CreatedAt,
&result.UpdatedAt,
)

if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
}
return &result, nil
}

// GetCategoriesByUserID returns categories of an user from DB
func (pg *DB) GetCategoriesByUserID(userID uint32) ([]model.Category, error) {
rows, err := pg.db.Query(`
SELECT
id,
user_id,
title,
created_at,
updated_at
FROM categories
WHERE user_id=$1
ORDER BY title ASC`,
userID,
)
if err != nil {
return nil, err
}
defer rows.Close()

var result []model.Category

for rows.Next() {
category := model.Category{}
err = rows.Scan(
&category.ID,
&category.UserID,
&category.Title,
&category.CreatedAt,
&category.UpdatedAt,
)
if err != nil {
return nil, err
}
result = append(result, category)
}
err = rows.Err()
if err != nil {
return nil, err
}
return result, nil
}

// DeleteCategory removes an category from the DB
func (pg *DB) DeleteCategory(category model.Category) error {
result, err := pg.db.Exec(`
DELETE FROM categories
WHERE ID=$1
`,
category.ID,
)
if err != nil {
return err
}

count, err := result.RowsAffected()
if err != nil {
return err
}

if count == 0 {
return errors.New("no category has been removed")
}

return nil
}
59 changes: 59 additions & 0 deletions pkg/db/test/category_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package dbtest

import (
"testing"

"github.com/ncarlier/reader/pkg/assert"
"github.com/ncarlier/reader/pkg/model"
)

func assertCategoryExists(t *testing.T, userID *uint32, title string) *model.Category {
category, err := testDB.GetCategoryByUserIDAndTitle(*userID, title)
assert.Nil(t, err, "error on getting category by user and title should be nil")
if category != nil {
return category
}

category = &model.Category{
UserID: userID,
Title: title,
}

category, err = testDB.CreateOrUpdateCategory(*category)
assert.Nil(t, err, "error on create/update category should be nil")
assert.NotNil(t, category, "category shouldn't be nil")
assert.NotNil(t, category.ID, "category ID shouldn't be nil")
assert.Equal(t, title, category.Title, "")
return category
}
func TestCreateOrUpdateCategory(t *testing.T) {
teardownTestCase := setupTestCase(t)
defer teardownTestCase(t)

title := "My test category"

// Assert user exists
user := assertUserExists(t, "test-002")

assertCategoryExists(t, user.ID, title)
}

func TestDeleteCategory(t *testing.T) {
teardownTestCase := setupTestCase(t)
defer teardownTestCase(t)

title := "My test category"

// Assert user exists
user := assertUserExists(t, "test-002")

// Assert category exists
category := assertCategoryExists(t, user.ID, title)

err := testDB.DeleteCategory(*category)
assert.Nil(t, err, "error on delete should be nil")

categories, err := testDB.GetCategoriesByUserID(*user.ID)
assert.Nil(t, err, "error should be nil")
assert.True(t, len(categories) == 0, "categories should be empty")
}
41 changes: 19 additions & 22 deletions pkg/db/test/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,29 @@ import (
"github.com/ncarlier/reader/pkg/model"
)

func TestCreateOrUpdateUser(t *testing.T) {
teardownTestCase := setupTestCase(t)
defer teardownTestCase(t)

username := "test-001"
func assertUserExists(t *testing.T, username string) *model.User {
user, err := testDB.GetUserByUsername(username)
assert.Nil(t, err, "error getting user by username should be nil")
if user != nil {
return user
}

user := &model.User{
user = &model.User{
Username: username,
}
user, err := testDB.CreateOrUpdateUser(*user)
assert.Nil(t, err, "error should be nil")
user, err = testDB.CreateOrUpdateUser(*user)
assert.Nil(t, err, "error on create/update user should be nil")
assert.NotNil(t, user, "user shouldn't be nil")
assert.NotNil(t, user.ID, "user ID shouldn't be nil")
assert.Equal(t, username, user.Username, "")
return user
}

func TestCreateOrUpdateUser(t *testing.T) {
teardownTestCase := setupTestCase(t)
defer teardownTestCase(t)

user := assertUserExists(t, "test-001")
assert.True(t, *user.ID > 0, "user ID should be a valid integer")
assert.True(t, !user.Enabled, "user should be disabled")
}
Expand All @@ -30,21 +39,9 @@ func TestDeleteUser(t *testing.T) {
defer teardownTestCase(t)

username := "test-001"
user := assertUserExists(t, username)

user, err := testDB.GetUserByUsername(username)
assert.Nil(t, err, "error should be nil")
if user == nil {
user = &model.User{
Username: username,
}
_, err = testDB.CreateOrUpdateUser(*user)
assert.Nil(t, err, "error should be nil")
}

user = &model.User{
Username: username,
}
err = testDB.DeleteUser(*user)
err := testDB.DeleteUser(*user)
assert.Nil(t, err, "error should be nil")

user, err = testDB.GetUserByUsername(username)
Expand Down
12 changes: 12 additions & 0 deletions pkg/model/category.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package model

import "time"

// Category structure definition
type Category struct {
ID *uint32 `json:"id,omitempty"`
UserID *uint32 `json:"user_id,omitempty"`
Title string `json:"title,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}

0 comments on commit 4af960b

Please sign in to comment.