features/repo-updates #4
226
internal/repository/article.go
Normal file
226
internal/repository/article.go
Normal file
@ -0,0 +1,226 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
)
|
||||
|
||||
type ArticleRepository struct {
|
||||
conn *sql.DB
|
||||
defaultLimit int
|
||||
defaultOffset int
|
||||
}
|
||||
|
||||
func NewArticleRepository(conn *sql.DB) ArticleRepository {
|
||||
return ArticleRepository{
|
||||
conn: conn,
|
||||
defaultLimit: 50,
|
||||
defaultOffset: 50,
|
||||
}
|
||||
}
|
||||
|
||||
func (ar ArticleRepository) GetById(id int64) (domain.ArticleEntity, error) {
|
||||
builder := sqlbuilder.NewSelectBuilder()
|
||||
builder.Select("*")
|
||||
builder.From("articles").Where(
|
||||
builder.E("id", id),
|
||||
)
|
||||
builder.Limit(1)
|
||||
|
||||
query, args := builder.Build()
|
||||
rows, err := ar.conn.Query(query, args...)
|
||||
if err != nil {
|
||||
return domain.ArticleEntity{}, err
|
||||
}
|
||||
|
||||
data := ar.processRows(rows)
|
||||
if len(data) == 0 {
|
||||
return domain.ArticleEntity{}, errors.New(ErrUserNotFound)
|
||||
}
|
||||
|
||||
return data[0], nil
|
||||
}
|
||||
|
||||
func (ar ArticleRepository) GetByUrl(url string) (domain.ArticleEntity, error) {
|
||||
builder := sqlbuilder.NewSelectBuilder()
|
||||
builder.Select("*")
|
||||
builder.From("articles").Where(
|
||||
builder.E("url", url),
|
||||
)
|
||||
builder.Limit(1)
|
||||
|
||||
query, args := builder.Build()
|
||||
rows, err := ar.conn.Query(query, args...)
|
||||
if err != nil {
|
||||
return domain.ArticleEntity{}, err
|
||||
}
|
||||
|
||||
data := ar.processRows(rows)
|
||||
if len(data) == 0 {
|
||||
return domain.ArticleEntity{}, errors.New(ErrUserNotFound)
|
||||
}
|
||||
|
||||
return data[0], nil
|
||||
}
|
||||
|
||||
func (ar ArticleRepository) List(limit int) ([]domain.ArticleEntity, error) {
|
||||
builder := sqlbuilder.NewSelectBuilder()
|
||||
builder.Select("*")
|
||||
builder.From("articles")
|
||||
//builder.OrderBy("pubdate")
|
||||
builder.Limit(limit)
|
||||
//builder.Offset(50)
|
||||
|
||||
query, args := builder.Build()
|
||||
rows, err := ar.conn.Query(query, args...)
|
||||
if err != nil {
|
||||
return []domain.ArticleEntity{}, err
|
||||
}
|
||||
|
||||
data := ar.processRows(rows)
|
||||
if len(data) == 0 {
|
||||
return []domain.ArticleEntity{}, errors.New(ErrUserNotFound)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (ar ArticleRepository) ListByPage(page, limit int) ([]domain.ArticleEntity, error) {
|
||||
builder := sqlbuilder.NewSelectBuilder()
|
||||
builder.Select("*")
|
||||
builder.From("articles")
|
||||
builder.OrderBy("pubdate desc")
|
||||
builder.Offset(page * limit)
|
||||
builder.Limit(limit)
|
||||
|
||||
query, args := builder.Build()
|
||||
rows, err := ar.conn.Query(query, args...)
|
||||
if err != nil {
|
||||
return []domain.ArticleEntity{}, err
|
||||
}
|
||||
|
||||
data := ar.processRows(rows)
|
||||
if len(data) == 0 {
|
||||
return []domain.ArticleEntity{}, errors.New(ErrUserNotFound)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (ar ArticleRepository) ListByPublishDate(limit int) ([]domain.ArticleEntity, error) {
|
||||
builder := sqlbuilder.NewSelectBuilder()
|
||||
builder.Select("*")
|
||||
builder.From("articles")
|
||||
//builder.OrderBy("pubdate")
|
||||
builder.Limit(limit)
|
||||
//builder.Offset(50)
|
||||
|
||||
query, args := builder.Build()
|
||||
rows, err := ar.conn.Query(query, args...)
|
||||
if err != nil {
|
||||
return []domain.ArticleEntity{}, err
|
||||
}
|
||||
|
||||
data := ar.processRows(rows)
|
||||
if len(data) == 0 {
|
||||
return []domain.ArticleEntity{}, errors.New(ErrUserNotFound)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (ar ArticleRepository) ListBySource(sourceName string, limit int) ([]domain.ArticleEntity, error) {
|
||||
builder := sqlbuilder.NewSelectBuilder()
|
||||
builder.Select("*")
|
||||
builder.From("articles")
|
||||
builder.JoinWithOption("InnerJoin", "sources", "articles.sourceId=sources.Id")
|
||||
builder.OrderBy("pubdate")
|
||||
builder.Limit(limit)
|
||||
builder.Offset(50)
|
||||
|
||||
query, args := builder.Build()
|
||||
rows, err := ar.conn.Query(query, args...)
|
||||
if err != nil {
|
||||
return []domain.ArticleEntity{}, err
|
||||
}
|
||||
|
||||
data := ar.processRows(rows)
|
||||
if len(data) == 0 {
|
||||
return []domain.ArticleEntity{}, errors.New(ErrUserNotFound)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (ar ArticleRepository) Create(sourceId int64, tags, title, url, thumbnailUrl, description, authorName, authorImageUrl string, pubDate time.Time, isVideo bool) (int64, error) {
|
||||
dt := time.Now()
|
||||
queryBuilder := sqlbuilder.NewInsertBuilder()
|
||||
queryBuilder.InsertInto("articles")
|
||||
queryBuilder.Cols("UpdatedAt", "CreatedAt", "SourceId", "Tags", "Title", "Url", "PubDate", "IsVideo", "ThumbnailUrl", "Description", "AuthorName", "AuthorImageUrl")
|
||||
queryBuilder.Values(dt, dt, sourceId, tags, title, url, pubDate, isVideo, thumbnailUrl, description, authorName, authorImageUrl)
|
||||
query, args := queryBuilder.Build()
|
||||
|
||||
_, err := ar.conn.Exec(query, args...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (ur ArticleRepository) processRows(rows *sql.Rows) []domain.ArticleEntity {
|
||||
items := []domain.ArticleEntity{}
|
||||
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
var createdAt time.Time
|
||||
var updatedAt time.Time
|
||||
var deletedAt sql.NullTime
|
||||
var sourceId int64
|
||||
var tags string
|
||||
var title string
|
||||
var url string
|
||||
var pubDate time.Time
|
||||
var isVideo bool
|
||||
var thumbnail string
|
||||
var description string
|
||||
var authorName string
|
||||
var authorImageUrl string
|
||||
err := rows.Scan(
|
||||
&id, &createdAt, &updatedAt,
|
||||
&deletedAt, &sourceId, &tags,
|
||||
&title, &url, &pubDate,
|
||||
&isVideo, &thumbnail, &description,
|
||||
&authorName, &authorImageUrl)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
item := domain.ArticleEntity{
|
||||
ID: id,
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
SourceID: sourceId,
|
||||
Tags: tags,
|
||||
Title: title,
|
||||
Url: url,
|
||||
PubDate: pubDate,
|
||||
IsVideo: isVideo,
|
||||
Thumbnail: thumbnail,
|
||||
Description: description,
|
||||
AuthorName: authorName,
|
||||
AuthorImageUrl: authorImageUrl,
|
||||
}
|
||||
|
||||
if deletedAt.Valid {
|
||||
item.DeletedAt = deletedAt.Time
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
118
internal/repository/article_test.go
Normal file
118
internal/repository/article_test.go
Normal file
@ -0,0 +1,118 @@
|
||||
package repository_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
articleFakeDotCom = "www.fake.com"
|
||||
)
|
||||
|
||||
func TestCreateArticle(t *testing.T) {
|
||||
db, err := setupInMemoryDb()
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
r := repository.NewArticleRepository(db)
|
||||
created, err := r.Create(1, "", "unit test", articleFakeDotCom, "", "testing", "", "", time.Now(), false)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if created != 1 {
|
||||
t.Log("failed to create the record")
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestArticleByUrl(t *testing.T) {
|
||||
db, err := setupInMemoryDb()
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
defer db.Close()
|
||||
r := repository.NewArticleRepository(db)
|
||||
|
||||
err = insertFakeArticles(r, "u1")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
article, err := r.GetByUrl(articleFakeDotCom)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if article.Url != "www.fake.com" {
|
||||
t.Log("failed to find the requested record")
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullingMultipleArticlesWithLimit(t *testing.T) {
|
||||
db, err := setupInMemoryDb()
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
defer db.Close()
|
||||
r := repository.NewArticleRepository(db)
|
||||
insertFakeArticles(r, "u1")
|
||||
insertFakeArticles(r, "u2")
|
||||
insertFakeArticles(r, "u3")
|
||||
insertFakeArticles(r, "u4")
|
||||
|
||||
items, err := r.List(3)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if len(items) != 3 {
|
||||
t.Log("expected 3 rows back")
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullingMultipleArticlesWithPaging(t *testing.T) {
|
||||
db, err := setupInMemoryDb()
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
defer db.Close()
|
||||
r := repository.NewArticleRepository(db)
|
||||
insertFakeArticles(r, "u1")
|
||||
insertFakeArticles(r, "u2")
|
||||
insertFakeArticles(r, "u3")
|
||||
insertFakeArticles(r, "u4")
|
||||
|
||||
items, err := r.ListByPage(2, 1)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if items[0].Title != "u2" {
|
||||
t.Log("pulled the wrong record with paging")
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func insertFakeArticles(r repository.ArticleRepository, title string) error {
|
||||
_, err := r.Create(1, "", title, articleFakeDotCom, "", "testing", "", "", time.Now(), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user