From 3d3b582e82f3743b3b9476dec1df607eb0b39a7c Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Sat, 27 Apr 2024 07:44:41 -0700 Subject: [PATCH] Articles can be created and working on pulling over the old queries --- internal/repository/article.go | 226 ++++++++++++++++++++++++++++ internal/repository/article_test.go | 118 +++++++++++++++ 2 files changed, 344 insertions(+) create mode 100644 internal/repository/article.go create mode 100644 internal/repository/article_test.go diff --git a/internal/repository/article.go b/internal/repository/article.go new file mode 100644 index 0000000..5b0b2b8 --- /dev/null +++ b/internal/repository/article.go @@ -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 +} diff --git a/internal/repository/article_test.go b/internal/repository/article_test.go new file mode 100644 index 0000000..b4d127d --- /dev/null +++ b/internal/repository/article_test.go @@ -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 +}