Compare commits
3 Commits
2b5a3cc8e4
...
3d3b582e82
Author | SHA1 | Date | |
---|---|---|---|
3d3b582e82 | |||
f6cc0a3d93 | |||
ba33d18525 |
@ -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 (
|
||||||
|
@ -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
|
||||||
@ -23,7 +24,7 @@ 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
|
||||||
|
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