features/repo-updates #4

Merged
jtom38 merged 13 commits from features/repo-updates into main 2024-04-28 11:42:58 -07:00
15 changed files with 650 additions and 116 deletions
Showing only changes of commit 7227744621 - Show all commits

4
.gitignore vendored
View File

@ -4,6 +4,9 @@ __debug_bin
server server
.vscode .vscode
# hide the swagger files in the repo
docs/
# Binaries for programs and plugins # Binaries for programs and plugins
*.exe *.exe
*.exe~ *.exe~
@ -15,6 +18,7 @@ collector
# Test binary, built with `go test -c` # Test binary, built with `go test -c`
*.test *.test
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out

View File

@ -5,7 +5,7 @@ CREATE TABLE Articles (
ID INTEGER PRIMARY KEY AUTOINCREMENT, ID INTEGER PRIMARY KEY AUTOINCREMENT,
CreatedAt DATETIME NOT NULL, CreatedAt DATETIME NOT NULL,
UpdatedAt DATETIME NOT NULL, UpdatedAt DATETIME NOT NULL,
DeletedAt DATETIME, DeletedAt DATETIME NOT NULL,
SourceId NUMBER NOT NULL, SourceId NUMBER NOT NULL,
Tags TEXT NOT NULL, Tags TEXT NOT NULL,
Title TEXT NOT NULL, Title TEXT NOT NULL,
@ -33,12 +33,12 @@ CREATE Table DiscordWebHooks (
ID INTEGER PRIMARY KEY AUTOINCREMENT, ID INTEGER PRIMARY KEY AUTOINCREMENT,
CreatedAt DATETIME NOT NULL, CreatedAt DATETIME NOT NULL,
UpdatedAt DATETIME NOT NULL, UpdatedAt DATETIME NOT NULL,
DeletedAt DATETIME, DeletedAt DATETIME NOT NULL,
--Name TEXT NOT NULL, -- Defines webhook purpose --Name TEXT NOT NULL, -- Defines webhook purpose
--Key TEXT, --Key TEXT,
Url TEXT NOT NULL, -- Webhook Url Url TEXT NOT NULL, -- Webhook Url
Server TEXT NOT NULL, -- Defines the server its bound it. Used for refrence Server TEXT NOT NULL, -- Defines the server its bound it. Used for reference
Channel TEXT NOT NULL, -- Defines the channel its bound to. Used for refrence Channel TEXT NOT NULL, -- Defines the channel its bound to. Used for reference
Enabled BOOLEAN NOT NULL Enabled BOOLEAN NOT NULL
); );
@ -65,12 +65,9 @@ CREATE Table Sources (
ID INTEGER PRIMARY KEY AUTOINCREMENT, ID INTEGER PRIMARY KEY AUTOINCREMENT,
CreatedAt DATETIME NOT NULL, CreatedAt DATETIME NOT NULL,
UpdatedAt DATETIME NOT NULL, UpdatedAt DATETIME NOT NULL,
DeletedAt DATETIME, DeletedAt DATETIME NOT NULL,
Site TEXT NOT NULL, -- Vanity name DisplayName TEXT NOT NULL, -- Vanity name
Name TEXT NOT NULL, -- Defines the name of the source. IE: dadjokes Source TEXT NOT NULL, -- Defines the service that will use this record. IE reddit or youtube
Source TEXT NOT NULL, -- Defines the service that will use this reocrd. IE reddit or youtube
Type TEXT NOT NULL, -- Defines what kind of feed this is. feed, user, tag
Value TEXT,
Enabled BOOLEAN NOT NULL, Enabled BOOLEAN NOT NULL,
Url TEXT NOT NULL, Url TEXT NOT NULL,
Tags TEXT NOT NULL Tags TEXT NOT NULL

View File

@ -6,34 +6,34 @@ SELECT 'up SQL query';
--CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; --CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Final Fantasy XIV Entries -- Final Fantasy XIV Entries
INSERT INTO sources (CreatedAt, UpdatedAt, Site, Name, Source, Type, Value, Enabled, Url, Tags) VALUES INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", 'ffxiv', 'Final Fantasy XIV - NA', 'ffxiv', 'scrape', 'a', TRUE, 'https://na.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, na, lodestone'); ("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'ffxiv', 'Final Fantasy XIV - NA', TRUE, 'https://na.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, na, lodestone');
INSERT INTO sources (CreatedAt, UpdatedAt, Site, Name, Source, Type, Value, Enabled, Url, Tags) VALUES INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", 'ffxiv', 'Final Fantasy XIV - JP', 'ffxiv', 'scrape', 'a', FALSE, 'https://jp.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, jp, lodestone'); ("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'ffxiv', 'Final Fantasy XIV - JP', FALSE, 'https://jp.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, jp, lodestone');
INSERT INTO sources (CreatedAt, UpdatedAt, Site, Name, Source, Type, Value, Enabled, Url, Tags) VALUES INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", 'ffxiv', 'Final Fantasy XIV - EU', 'ffxiv', 'scrape', 'a', FALSE, 'https://eu.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, eu, lodestone'); ("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'ffxiv', 'Final Fantasy XIV - EU', FALSE, 'https://eu.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, eu, lodestone');
INSERT INTO sources (CreatedAt, UpdatedAt, Site, Name, Source, Type, Value, Enabled, Url, Tags) VALUES INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", 'ffxiv', 'Final Fantasy XIV - FR', 'ffxiv', 'scrape', 'a', FALSE, 'https://fr.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, fr, lodestone'); ("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'ffxiv', 'Final Fantasy XIV - FR', FALSE, 'https://fr.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, fr, lodestone');
INSERT INTO sources (CreatedAt, UpdatedAt, Site, Name, Source, Type, Value, Enabled, Url, Tags) VALUES INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", 'ffxiv', 'Final Fantasy XIV - DE', 'ffxiv', 'scrape', 'a', FALSE, 'https://de.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, de, lodestone'); ("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'ffxiv', 'Final Fantasy XIV - DE', FALSE, 'https://de.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, de, lodestone');
-- Reddit Entries -- Reddit Entries
INSERT INTO sources (CreatedAt, UpdatedAt, Site, Name, Source, Type, Value, Enabled, Url, Tags) VALUES INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", 'reddit', 'dadjokes', 'reddit', 'feed', 'a', TRUE, 'https://reddit.com/r/dadjokes', 'reddit, dadjokes'); ("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'reddit', 'dadjokes', TRUE, 'https://reddit.com/r/dadjokes', 'reddit, dadjokes');
INSERT INTO sources (CreatedAt, UpdatedAt, Site, Name, Source, Type, Value, Enabled, Url, Tags) VALUES INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", 'reddit', 'steamdeck', 'reddit', 'feed', 'a', TRUE, 'https://reddit.com/r/steamdeck', 'reddit, steam deck, steam, deck'); ("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'reddit', 'steamdeck', TRUE, 'https://reddit.com/r/steamdeck', 'reddit, steam deck, steam, deck');
-- Youtube Entries -- Youtube Entries
INSERT INTO sources (CreatedAt, UpdatedAt, Site, Name, Source, Type, Value, Enabled, Url, Tags) VALUES INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", 'youtube', 'Game Grumps', 'youtube', 'feed', 'a', TRUE, 'https://www.youtube.com/user/GameGrumps', 'youtube, game grumps, game, grumps'); ("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'youtube', 'Game Grumps', TRUE, 'https://www.youtube.com/user/GameGrumps', 'youtube, game grumps, game, grumps');
-- RSS Entries -- RSS Entries
INSERT INTO sources (CreatedAt, UpdatedAt, Site, Name, Source, Type, Value, Enabled, Url, Tags) VALUES INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", 'steampowered', 'steam deck', 'rss', 'feed', 'a', TRUE, 'https://store.steampowered.com/feeds/news/app/1675200/?cc=US&l=english&snr=1_2108_9__2107', 'rss, steampowered, steam, deck, steam deck'); ("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'steampowered', 'steam deck', TRUE, 'https://store.steampowered.com/feeds/news/app/1675200/?cc=US&l=english&snr=1_2108_9__2107', 'rss, steampowered, steam, deck, steam deck');
-- Twitch Entries -- Twitch Entries
INSERT INTO sources (CreatedAt, UpdatedAt, Site, Name, Source, Type, Value, Enabled, Url, Tags) VALUES INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", 'twitch', 'Nintendo', 'twitch', 'api', 'a', TRUE, 'https://twitch.tv/nintendo', 'twitch, nintendo'); ("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'twitch', 'Nintendo', TRUE, 'https://twitch.tv/nintendo', 'twitch, nintendo');
-- +goose StatementEnd -- +goose StatementEnd

9
internal/domain/const.go Normal file
View File

@ -0,0 +1,9 @@
package domain
const (
SourceCollectorRss = "rss"
SourceCollectorFfxiv = "ffxiv"
SourceCollectorTwitch = "twitch"
SourceCollectorYoutube = "youtube"
SourceCollectorReddit = "reddit"
)

View File

@ -67,14 +67,22 @@ type SourceEntity struct {
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
DeletedAt time.Time DeletedAt time.Time
Site string
Name string // Who will collect from it. Used
// domain.SourceCollector...
Source string Source string
Type string
Value string // Human Readable value to state what is getting collected
Enabled bool DisplayName string
// Tells the parser where to look for data
Url string Url string
// Static tags for this defined record
Tags string Tags string
// If the record is disabled, then it will be skipped on processing
Enabled bool
} }
type SubscriptionEntity struct { type SubscriptionEntity struct {

View File

@ -166,8 +166,8 @@ func (ar ArticleRepository) Create(sourceId int64, tags, title, url, thumbnailUr
dt := time.Now() dt := time.Now()
queryBuilder := sqlbuilder.NewInsertBuilder() queryBuilder := sqlbuilder.NewInsertBuilder()
queryBuilder.InsertInto("articles") queryBuilder.InsertInto("articles")
queryBuilder.Cols("UpdatedAt", "CreatedAt", "SourceId", "Tags", "Title", "Url", "PubDate", "IsVideo", "ThumbnailUrl", "Description", "AuthorName", "AuthorImageUrl") queryBuilder.Cols("UpdatedAt", "CreatedAt", "DeletedAt", "SourceId", "Tags", "Title", "Url", "PubDate", "IsVideo", "ThumbnailUrl", "Description", "AuthorName", "AuthorImageUrl")
queryBuilder.Values(dt, dt, sourceId, tags, title, url, pubDate, isVideo, thumbnailUrl, description, authorName, authorImageUrl) queryBuilder.Values(dt, dt, timeZero, sourceId, tags, title, url, pubDate, isVideo, thumbnailUrl, description, authorName, authorImageUrl)
query, args := queryBuilder.Build() query, args := queryBuilder.Build()
_, err := ar.conn.Exec(query, args...) _, err := ar.conn.Exec(query, args...)
@ -185,7 +185,7 @@ func (ur ArticleRepository) processRows(rows *sql.Rows) []domain.ArticleEntity {
var id int64 var id int64
var createdAt time.Time var createdAt time.Time
var updatedAt time.Time var updatedAt time.Time
var deletedAt sql.NullTime var deletedAt time.Time
var sourceId int64 var sourceId int64
var tags string var tags string
var title string var title string
@ -210,6 +210,7 @@ func (ur ArticleRepository) processRows(rows *sql.Rows) []domain.ArticleEntity {
ID: id, ID: id,
CreatedAt: createdAt, CreatedAt: createdAt,
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
DeletedAt: deletedAt,
SourceID: sourceId, SourceID: sourceId,
Tags: tags, Tags: tags,
Title: title, Title: title,
@ -222,10 +223,6 @@ func (ur ArticleRepository) processRows(rows *sql.Rows) []domain.ArticleEntity {
AuthorImageUrl: authorImageUrl, AuthorImageUrl: authorImageUrl,
} }
if deletedAt.Valid {
item.DeletedAt = deletedAt.Time
}
items = append(items, item) items = append(items, item)
} }

View File

@ -12,6 +12,7 @@ const (
) )
func TestCreateArticle(t *testing.T) { func TestCreateArticle(t *testing.T) {
t.Log(time.Time{})
db, err := setupInMemoryDb() db, err := setupInMemoryDb()
if err != nil { if err != nil {
t.Log(err) t.Log(err)
@ -134,9 +135,10 @@ func TestPullingByPublishDate(t *testing.T) {
t.Log("expected two items back") t.Log("expected two items back")
t.FailNow() t.FailNow()
} }
pubDate := items[1].PubDate.Day()
if items[0].PubDate.Day() != (today.Day() - 2) { expectedDay := today.Day() - 1
t.Log("expected the record that was 2 days old") if pubDate != expectedDay {
t.Log("expected a record that was 2 days old")
t.FailNow() t.FailNow()
} }
} }
@ -144,7 +146,6 @@ func TestPullingByPublishDate(t *testing.T) {
//func TestArticleBySource //func TestArticleBySource
func insertFakeArticles(r repository.ArticleRepository, title string, daysOld int) error { func insertFakeArticles(r repository.ArticleRepository, title string, daysOld int) error {
pubDate := time.Now().AddDate(0,0, daysOld) pubDate := time.Now().AddDate(0,0, daysOld)
_, err := r.Create(1, "", title, articleFakeDotCom, "", "testing", "", "", pubDate, false) _, err := r.Create(1, "", title, articleFakeDotCom, "", "testing", "", "", pubDate, false)
if err != nil { if err != nil {

View File

@ -0,0 +1,71 @@
package repository
import (
"context"
"database/sql"
"time"
"github.com/huandu/go-sqlbuilder"
)
var (
timeZero = time.Time{}
)
func deleteFromTable(ctx context.Context, conn *sql.DB, tableName string, id int64) (int64, error) {
b := sqlbuilder.NewDeleteBuilder()
b.DeleteFrom(tableName)
b.Where(
b.Equal("Id", id),
)
query, args := b.Build()
_, err := conn.ExecContext(ctx, query, args...)
if err != nil {
return 0, err
}
return 1, nil
}
func restoreRow(ctx context.Context, conn *sql.DB, tableName string, id int64) (int64, error) {
timeZero := time.Time{}
b := sqlbuilder.NewUpdateBuilder()
b.Update(tableName)
b.Set(
b.Assign("UpdatedAt", time.Now()),
b.Assign("DeletedAt", timeZero),
)
b.Where(
b.Equal("Id", id),
)
query, args := b.Build()
_, err := conn.ExecContext(ctx, query, args...)
if err != nil {
return 0, err
}
return 1, nil
}
func softDeleteRow(ctx context.Context, conn *sql.DB, tableName string, id int64) (int64, error) {
now := time.Now()
b := sqlbuilder.NewUpdateBuilder()
b.Update(tableName)
b.Set(
b.Assign("UpdatedAt", now),
b.Assign("DeletedAt", now),
)
b.Where(
b.Equal("Id", id),
)
query, args := b.Build()
_, err := conn.ExecContext(ctx, query, args...)
if err != nil {
return 0, err
}
return 1, nil
}

View File

@ -23,8 +23,8 @@ func (r discordWebHookRepository) Create(ctx context.Context, url, server, chann
dt := time.Now() dt := time.Now()
queryBuilder := sqlbuilder.NewInsertBuilder() queryBuilder := sqlbuilder.NewInsertBuilder()
queryBuilder.InsertInto("DiscordWebHooks") queryBuilder.InsertInto("DiscordWebHooks")
queryBuilder.Cols("UpdatedAt", "CreatedAt", "Url", "Server", "Channel", "Enabled") queryBuilder.Cols("UpdatedAt", "CreatedAt", "DeletedAt", "Url", "Server", "Channel", "Enabled")
queryBuilder.Values(dt, dt, url, server, channel, enabled) queryBuilder.Values(dt, dt, timeZero, url, server, channel, enabled)
query, args := queryBuilder.Build() query, args := queryBuilder.Build()
_, err := r.conn.ExecContext(ctx, query, args...) _, err := r.conn.ExecContext(ctx, query, args...)
@ -42,6 +42,9 @@ func (r discordWebHookRepository) Enable(ctx context.Context, id int64) (int64,
b.Assign("Enabled", true), b.Assign("Enabled", true),
b.Assign("UpdatedAt", time.Now()), b.Assign("UpdatedAt", time.Now()),
) )
b.Where(
b.Equal("Id", id),
)
query, args := b.Build() query, args := b.Build()
_, err := r.conn.ExecContext(ctx, query, args...) _, err := r.conn.ExecContext(ctx, query, args...)
@ -59,6 +62,9 @@ func (r discordWebHookRepository) Disable(ctx context.Context, id int64) (int64,
b.Assign("Enabled", false), b.Assign("Enabled", false),
b.Assign("UpdatedAt", time.Now()), b.Assign("UpdatedAt", time.Now()),
) )
b.Where(
b.Equal("Id", id),
)
query, args := b.Build() query, args := b.Build()
_, err := r.conn.ExecContext(ctx, query, args...) _, err := r.conn.ExecContext(ctx, query, args...)
@ -70,61 +76,15 @@ func (r discordWebHookRepository) Disable(ctx context.Context, id int64) (int64,
} }
func (r discordWebHookRepository) SoftDelete(ctx context.Context, id int64) (int64, error) { func (r discordWebHookRepository) SoftDelete(ctx context.Context, id int64) (int64, error) {
now := time.Now() return softDeleteRow(ctx, r.conn, "DiscordWebHooks", id)
b := sqlbuilder.NewUpdateBuilder()
b.Update("DiscordWebHooks")
b.Set(
b.Assign("UpdatedAt", now),
b.Assign("DeletedAt", now),
)
b.Where(
b.Equal("Id", id),
)
query, args := b.Build()
_, err := r.conn.ExecContext(ctx, query, args...)
if err != nil {
return 0, err
}
return 1, nil
} }
func (r discordWebHookRepository) Restore(ctx context.Context, id int64) (int64, error) { func (r discordWebHookRepository) Restore(ctx context.Context, id int64) (int64, error) {
timeZero := time.Time{} return restoreRow(ctx, r.conn, "DiscordWebHooks", id)
b := sqlbuilder.NewUpdateBuilder()
b.Update("DiscordWebHooks")
b.Set(
b.Assign("UpdatedAt", time.Now()),
b.Assign("DeletedAt", timeZero),
)
b.Where(
b.Equal("Id", id),
)
query, args := b.Build()
_, err := r.conn.ExecContext(ctx, query, args...)
if err != nil {
return 0, err
}
return 1, nil
} }
func (r discordWebHookRepository) Delete(ctx context.Context, id int64) (int64, error) { func (r discordWebHookRepository) Delete(ctx context.Context, id int64) (int64, error) {
b := sqlbuilder.NewDeleteBuilder() return deleteFromTable(ctx, r.conn, "DiscordWebHooks", id)
b.DeleteFrom("DiscordWebHooks")
b.Where(
b.Equal("Id", id),
)
query, args := b.Build()
_, err := r.conn.ExecContext(ctx, query, args...)
if err != nil {
return 0, err
}
return 1, nil
} }
func (r discordWebHookRepository) GetById(ctx context.Context, id int64) (domain.DiscordWebHookEntity, error) { func (r discordWebHookRepository) GetById(ctx context.Context, id int64) (domain.DiscordWebHookEntity, error) {
@ -221,7 +181,7 @@ func (r discordWebHookRepository) processRows(rows *sql.Rows) ([]domain.DiscordW
var id int64 var id int64
var createdAt time.Time var createdAt time.Time
var updatedAt time.Time var updatedAt time.Time
var deletedAt sql.NullTime var deletedAt time.Time
var url string var url string
var server string var server string
var channel string var channel string
@ -239,16 +199,13 @@ func (r discordWebHookRepository) processRows(rows *sql.Rows) ([]domain.DiscordW
ID: id, ID: id,
CreatedAt: createdAt, CreatedAt: createdAt,
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
DeletedAt: deletedAt,
Url: url, Url: url,
Server: server, Server: server,
Channel: channel, Channel: channel,
Enabled: enabled, Enabled: enabled,
} }
if deletedAt.Valid {
item.DeletedAt = deletedAt.Time
}
items = append(items, item) items = append(items, item)
} }

View File

@ -0,0 +1,239 @@
package repository
import (
"context"
"database/sql"
"time"
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
"github.com/huandu/go-sqlbuilder"
)
type sourceRepository struct {
conn *sql.DB
}
func NewSourceRepository(conn *sql.DB) sourceRepository {
return sourceRepository{
conn: conn,
}
}
func (r sourceRepository) Create(ctx context.Context, source, displayName, url, tags string, enabled bool) (int64, error) {
dt := time.Now()
queryBuilder := sqlbuilder.NewInsertBuilder()
queryBuilder.InsertInto("Sources")
queryBuilder.Cols("CreatedAt", "UpdatedAt", "DeletedAt", "DisplayName", "Source", "Url", "Tags", "Enabled")
queryBuilder.Values(dt, dt, timeZero, displayName, source, url, tags, enabled)
query, args := queryBuilder.Build()
_, err := r.conn.ExecContext(ctx, query, args...)
if err != nil {
return 0, err
}
return 1, nil
}
func (r sourceRepository) GetById(ctx context.Context, id int64) (domain.SourceEntity, error) {
b := sqlbuilder.NewSelectBuilder()
b.Select("*")
b.From("Sources").Where(
b.Equal("Id", id),
)
b.Limit(1)
query, args := b.Build()
rows, err := r.conn.QueryContext(ctx, query, args...)
if err != nil {
return domain.SourceEntity{}, err
}
data, err := r.processRows(rows)
if len(data) == 0 {
return domain.SourceEntity{}, err
}
return data[0], nil
}
func (r sourceRepository) GetByDisplayName(ctx context.Context, displayName string) (domain.SourceEntity, error) {
b := sqlbuilder.NewSelectBuilder()
b.Select("*")
b.From("Sources").Where(
b.Equal("DisplayName", displayName),
)
b.Limit(1)
query, args := b.Build()
rows, err := r.conn.QueryContext(ctx, query, args...)
if err != nil {
return domain.SourceEntity{}, err
}
data, err := r.processRows(rows)
if len(data) == 0 {
return domain.SourceEntity{}, err
}
return data[0], nil
}
func (r sourceRepository) GetBySource(ctx context.Context, source string) (domain.SourceEntity, error) {
b := sqlbuilder.NewSelectBuilder()
b.Select("*")
b.From("Sources").Where(
b.Equal("Source", source),
)
b.Limit(1)
query, args := b.Build()
rows, err := r.conn.QueryContext(ctx, query, args...)
if err != nil {
return domain.SourceEntity{}, err
}
data, err := r.processRows(rows)
if len(data) == 0 {
return domain.SourceEntity{}, err
}
return data[0], nil
}
func (r sourceRepository) List(ctx context.Context, page, limit int) ([]domain.SourceEntity, error) {
builder := sqlbuilder.NewSelectBuilder()
builder.Select("*")
builder.From("Sources")
builder.Offset(page * limit)
builder.Limit(limit)
query, args := builder.Build()
rows, err := r.conn.QueryContext(ctx, query, args...)
if err != nil {
return []domain.SourceEntity{}, err
}
data, err := r.processRows(rows)
if len(data) == 0 {
return []domain.SourceEntity{}, err
}
return data, nil
}
func (r sourceRepository) ListBySource(ctx context.Context, page, limit int, source string) ([]domain.SourceEntity, error) {
builder := sqlbuilder.NewSelectBuilder()
builder.Select("*")
builder.From("Sources")
builder.Where(
builder.Equal("Source", source),
)
builder.Offset(page * limit)
builder.Limit(limit)
query, args := builder.Build()
rows, err := r.conn.QueryContext(ctx, query, args...)
if err != nil {
return []domain.SourceEntity{}, err
}
data, err := r.processRows(rows)
if len(data) == 0 {
return []domain.SourceEntity{}, err
}
return data, nil
}
func (r sourceRepository) Enable(ctx context.Context, id int64) (int64, error) {
b := sqlbuilder.NewUpdateBuilder()
b.Update("Sources")
b.Set(
b.Assign("Enabled", true),
b.Assign("UpdatedAt", time.Now()),
)
b.Where(
b.Equal("Id", id),
)
query, args := b.Build()
_, err := r.conn.ExecContext(ctx, query, args...)
if err != nil {
return 0, err
}
return 1, nil
}
func (r sourceRepository) Disable(ctx context.Context, id int64) (int64, error) {
b := sqlbuilder.NewUpdateBuilder()
b.Update("Sources")
b.Set(
b.Assign("Enabled", false),
b.Assign("UpdatedAt", time.Now()),
)
b.Where(
b.Equal("Id", id),
)
query, args := b.Build()
_, err := r.conn.ExecContext(ctx, query, args...)
if err != nil {
return 0, err
}
return 1, nil
}
func (r sourceRepository) SoftDelete(ctx context.Context, id int64) (int64, error) {
return softDeleteRow(ctx, r.conn, "Sources", id)
}
func (r sourceRepository) Restore(ctx context.Context, id int64) (int64, error) {
return restoreRow(ctx, r.conn, "Sources", id)
}
func (r sourceRepository) Delete(ctx context.Context, id int64) (int64, error) {
return deleteFromTable(ctx, r.conn, "Sources", id)
}
func (r sourceRepository) processRows(rows *sql.Rows) ([]domain.SourceEntity, error) {
items := []domain.SourceEntity{}
for rows.Next() {
var id int64
var createdAt time.Time
var updatedAt time.Time
var deletedAt time.Time
var displayName string
var source string
var enabled bool
var url string
var tags string
err := rows.Scan(
&id, &createdAt, &updatedAt,
&deletedAt, &displayName, &source,
&enabled, &url, &tags,
)
if err != nil {
return items, err
}
item := domain.SourceEntity{
ID: id,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
DeletedAt: deletedAt,
DisplayName: displayName,
Source: source,
Enabled: enabled,
Url: url,
Tags: tags,
}
items = append(items, item)
}
return items, nil
}

View File

@ -0,0 +1,246 @@
package repository_test
import (
"context"
"testing"
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
)
func TestSourceCreate(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
ctx := context.Background()
r := repository.NewSourceRepository(db)
rows, err := r.Create(ctx, domain.SourceCollectorRss, "Test", "www.badurl.com", "rss, badurl", true)
if err != nil {
t.Log(err)
t.FailNow()
}
if rows != 1 {
t.Log("failed to create a record, why")
t.FailNow()
}
}
func TestSourceGetById(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
ctx := context.Background()
r := repository.NewSourceRepository(db)
_, err = r.Create(ctx, domain.SourceCollectorRss, "Test", "www.badurl.com", "rss, badurl", true)
if err != nil {
t.Log(err)
t.FailNow()
}
item, err := r.GetById(ctx, 1)
if err != nil {
t.Log(err)
t.FailNow()
}
if item.ID != 1 {
t.Log("got no record or the wrong one")
t.FailNow()
}
}
func TestSourceGetByDisplayName(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
ctx := context.Background()
r := repository.NewSourceRepository(db)
_, err = r.Create(ctx, domain.SourceCollectorRss, "Test", "www.badurl.com", "rss, badurl", true)
if err != nil {
t.Log(err)
t.FailNow()
}
item, err := r.GetByDisplayName(ctx, "Test")
if err != nil {
t.Log(err)
t.FailNow()
}
if item.DisplayName != "Test" {
t.Log("got no record or the wrong one")
t.FailNow()
}
}
func TestSourceGetBySource(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
ctx := context.Background()
r := repository.NewSourceRepository(db)
_, err = r.Create(ctx, domain.SourceCollectorRss, "Test", "www.badurl.com", "rss, badurl", true)
if err != nil {
t.Log(err)
t.FailNow()
}
item, err := r.GetBySource(ctx, domain.SourceCollectorRss)
if err != nil {
t.Log(err)
t.FailNow()
}
if item.Source != domain.SourceCollectorRss {
t.Log("got no record or the wrong one")
t.FailNow()
}
}
func TestSourceList(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
ctx := context.Background()
r := repository.NewSourceRepository(db)
_, _ = r.Create(ctx, domain.SourceCollectorRss, "Test", "www.badurl.com", "rss, badurl", true)
_, _ = r.Create(ctx, domain.SourceCollectorRss, "Test", "www.badurl.com", "rss, badurl", true)
_, _ = r.Create(ctx, domain.SourceCollectorRss, "Test", "www.badurl.com", "rss, badurl", true)
_, _ = r.Create(ctx, domain.SourceCollectorRss, "Test", "www.badurl.com", "rss, badurl", true)
items, err := r.List(ctx, 0, 4)
if err != nil {
t.Log(err)
t.FailNow()
}
if len(items ) != 4 {
t.Log("something bad happened here")
t.FailNow()
}
}
func TestSourceListBySource(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
ctx := context.Background()
r := repository.NewSourceRepository(db)
_, _ = r.Create(ctx, domain.SourceCollectorRss, "Test", "www.badurl.com", "rss, badurl", true)
_, _ = r.Create(ctx, domain.SourceCollectorRss, "Test", "www.badurl.com", "rss, badurl", true)
_, _ = r.Create(ctx, domain.SourceCollectorRss, "Test", "www.badurl.com", "rss, badurl", true)
_, _ = r.Create(ctx, domain.SourceCollectorRss, "Test", "www.badurl.com", "rss, badurl", true)
items, err := r.ListBySource(ctx, 0, 4, domain.SourceCollectorRss)
if err != nil {
t.Log(err)
t.FailNow()
}
if len(items ) != 4 {
t.Log("something bad happened here")
t.FailNow()
}
}
func TestSourcesEnableRecord(t *testing.T) {
// This depends on the seed migration
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
ctx := context.Background()
r := repository.NewSourceRepository(db)
_, _ = r.Create(ctx, domain.SourceCollectorRss, "Test", "www.badurl.com", "rss, badurl", false)
item, err := r.GetByDisplayName(ctx, "Test")
if err != nil {
t.Log(err)
t.FailNow()
}
if item.Enabled != false {
t.Log("the initial record was created wrong")
t.FailNow()
}
_, err = r.Enable(ctx, item.ID)
if err != nil {
t.Log(err)
t.FailNow()
}
updated, err := r.GetById(ctx, item.ID)
if err != nil {
t.Log(err)
t.FailNow()
}
if item.Enabled == updated.Enabled {
t.Log("failed to update the enabled value")
t.FailNow()
}
}
func TestSourcesDisableRecord(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
ctx := context.Background()
r := repository.NewSourceRepository(db)
_, _ = r.Create(ctx, domain.SourceCollectorRss, "Test", "www.badurl.com", "rss, badurl", true)
item, err := r.GetByDisplayName(ctx, "Test")
if err != nil {
t.Log(err)
t.FailNow()
}
if item.Enabled != true {
t.Log("the initial record was created wrong")
t.FailNow()
}
_, err = r.Disable(ctx, 1)
if err != nil {
t.Log(err)
t.FailNow()
}
updated, err := r.GetById(ctx, 1)
if err != nil {
t.Log(err)
t.FailNow()
}
if item.Enabled == updated.Enabled {
t.Log("failed to update the enabled value")
t.FailNow()
}
}

View File

@ -0,0 +1,6 @@
package services
type RepositoryService struct {
}

View File

@ -26,7 +26,7 @@ func NewRssClient(sourceRecord domain.SourceEntity) rssClient {
//} //}
func (rc rssClient) getCacheGroup() string { func (rc rssClient) getCacheGroup() string {
return fmt.Sprintf("rss-%v", rc.SourceRecord.Name) return fmt.Sprintf("rss-%v", rc.SourceRecord.DisplayName)
} }
func (rc rssClient) GetContent() error { func (rc rssClient) GetContent() error {

View File

@ -9,7 +9,7 @@ import (
var rssRecord = domain.SourceEntity{ var rssRecord = domain.SourceEntity{
ID: 1, ID: 1,
Name: "ArsTechnica", DisplayName: "ArsTechnica",
Url: "https://feeds.arstechnica.com/arstechnica/index", Url: "https://feeds.arstechnica.com/arstechnica/index",
} }

View File

@ -3,7 +3,6 @@ help: ## Shows this help command
@egrep -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' @egrep -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
build: ## builds the application with the current go runtime build: ## builds the application with the current go runtime
sqlc generate
~/go/bin/swag f ~/go/bin/swag f
~/go/bin/swag i ~/go/bin/swag i
go build . go build .