Compare commits

...

3 Commits

4 changed files with 359 additions and 14 deletions

View File

@ -4,20 +4,20 @@ SELECT 'up SQL query';
CREATE TABLE Articles ( CREATE TABLE Articles (
ID INTEGER PRIMARY KEY AUTOINCREMENT, ID INTEGER PRIMARY KEY AUTOINCREMENT,
CreatedAt DATETIME NOT NULL, CreatedAt DATETIME NOT NULL,
LastUpdated DATETIME NOT NULL, UpdatedAt DATETIME NOT NULL,
DeletedAt DATETIME, DeletedAt DATETIME,
SourceId NUMBER NOT NULL, SourceId NUMBER NOT NULL,
Tags TEXT NOT NULL, Tags TEXT NOT NULL,
Title TEXT NOT NULL, Title TEXT NOT NULL,
Url TEXT NOT NULL, Url TEXT NOT NULL,
PubDate DATETIME NOT NULL, PubDate DATETIME NOT NULL,
Video TEXT, IsVideo TEXT NOT NULL,
VideoHeight int NOT NULL, --VideoHeight int NOT NULL,
VideoWidth int NOT NULL, --VideoWidth int NOT NULL,
Thumbnail TEXT NOT NULL, ThumbnailUrl TEXT NOT NULL,
Description TEXT NOT NULL, Description TEXT NOT NULL,
AuthorName TEXT, AuthorName TEXT NOT NULL,
AuthorImageUrl TEXT AuthorImageUrl TEXT NOT NULL
); );
CREATE Table DiscordQueue ( CREATE Table DiscordQueue (

View File

@ -7,13 +7,14 @@ import (
type ArticleEntity struct { type ArticleEntity struct {
ID int64 ID int64
CreatedAt time.Time CreatedAt time.Time
LastUpdated time.Time UpdatedAt time.Time
DeletedAt time.Time DeletedAt time.Time
SourceID int64 SourceID int64
Tags string Tags string
Title string Title string
Url string Url string
PubDate time.Time PubDate time.Time
IsVideo bool
Thumbnail string Thumbnail string
Description string Description string
AuthorName string AuthorName string
@ -21,12 +22,12 @@ type ArticleEntity struct {
} }
type DiscordQueueEntity struct { type DiscordQueueEntity struct {
ID int64 ID int64
CreatedAt time.Time CreatedAt time.Time
LastUpdated time.Time UpdatedAt time.Time
DeletedAt time.Time DeletedAt time.Time
ArticleId int64 ArticleId int64
SourceId int64 SourceId int64
} }
type DiscordWebHookEntity struct { type DiscordWebHookEntity struct {

View 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
}

View 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
}