Merge pull request 'features/repo-updates' (#4) from features/repo-updates into main

Reviewed-on: #4
This commit is contained in:
jtom38 2024-04-28 11:42:57 -07:00
commit 8fc2e56ad5
30 changed files with 1849 additions and 347 deletions

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

@ -1,4 +1,4 @@
FROM golang:1.18.4 as build FROM golang:1.22 as build
COPY . /app COPY . /app
WORKDIR /app WORKDIR /app

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 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,
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 (
@ -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

@ -3,33 +3,26 @@ package domain
import "time" import "time"
type ArticleDto struct { type ArticleDto struct {
ID int64 `json:"id"` ID int64 `json:"id"`
SourceID int64 `json:"sourceId"` SourceID int64 `json:"sourceId"`
Tags string `json:"tags"` Tags string `json:"tags"`
Title string `json:"title"` Title string `json:"title"`
Url string `json:"url"` Url string `json:"url"`
PubDate time.Time `json:"pubDate"` PubDate time.Time `json:"pubDate"`
Video string `json:"video"` IsVideo bool `json:"isVideo"`
VideoHeight uint16 `json:"videoHeight"` Thumbnail string `json:"thumbnail"`
VideoWidth uint16 `json:"videoWidth"` Description string `json:"description"`
Thumbnail string `json:"thumbnail"` AuthorName string `json:"authorName"`
Description string `json:"description"` AuthorImageUrl string `json:"authorImage"`
AuthorName string `json:"authorName"`
AuthorImage string `json:"authorImage"`
} }
type DiscordQueueDto struct { type DiscordQueueDto struct {
//CreatedAt time.Time `json:"createdAt"`
//UpdatedAt time.Time `json:"updatedAt"`
ID int64 `json:"id"` ID int64 `json:"id"`
ArticleId int64 `json:"articleId"` ArticleId int64 `json:"articleId"`
SourceId int64 `json:"sourceId"` SourceId int64 `json:"sourceId"`
} }
type DiscordWebHookDto struct { type DiscordWebHookDto struct {
//CreatedAt time.Time `json:"CreatedAt"`
//UpdatedAt time.Time `json:"UpdatedAt"`
//DeletedAt time.Time `json:"DeletedAt"`
ID uint `json:"id"` ID uint `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Key string `json:"key"` Key string `json:"key"`
@ -40,18 +33,12 @@ type DiscordWebHookDto struct {
} }
type IconDto struct { type IconDto struct {
//CreatedAt time.Time `json:"CreatedAt"`
//UpdatedAt time.Time `json:"UpdatedAt"`
//DeletedAt time.Time `json:"DeletedAt"`
ID int64 `json:"id"` ID int64 `json:"id"`
FileName string `json:"fileName"` FileName string `json:"fileName"`
Site string `json:"site"` Site string `json:"site"`
} }
type SettingDto struct { type SettingDto struct {
//CreatedAt time.Time `json:"CreatedAt"`
//UpdatedAt time.Time `json:"UpdatedAt"`
//DeletedAt time.Time `json:"DeletedAt"`
ID int64 `json:"id"` ID int64 `json:"id"`
Key string `json:"key"` Key string `json:"key"`
Value string `json:"value"` Value string `json:"value"`
@ -59,9 +46,6 @@ type SettingDto struct {
} }
type SubscriptionDto struct { type SubscriptionDto struct {
//CreatedAt time.Time `json:"CreatedAt"`
//UpdatedAt time.Time `json:"UpdatedAt"`
//DeletedAt time.Time `json:"DeletedAt"`
ID int64 `json:"id"` ID int64 `json:"id"`
SourceID int64 `json:"sourceId"` SourceID int64 `json:"sourceId"`
SourceType string `json:"sourceType"` SourceType string `json:"sourceType"`
@ -71,16 +55,10 @@ type SubscriptionDto struct {
} }
type SourceDto struct { type SourceDto struct {
//CreatedAt time.Time `json:"CreatedAt"` ID int64 `json:"id"`
//UpdatedAt time.Time `json:"UpdatedAt"` Source string `json:"source"`
//DeletedAt time.Time `json:"DeletedAt"` DisplayName string `json:"name"`
ID int64 `json:"id"` Url string `json:"url"`
Site string `json:"site"` Tags string `json:"tags"`
Name string `json:"name"` Enabled bool `json:"enabled"`
Source string `json:"source"`
Type string `json:"type"`
Value string `json:"value"`
Enabled bool `json:"enabled"`
Url string `json:"url"`
Tags string `json:"tags"`
} }

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 {
@ -34,12 +35,12 @@ type DiscordWebHookEntity struct {
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
DeletedAt time.Time DeletedAt time.Time
Name string //Name string
Key string //Key string
Url string Url string
Server string Server string
Channel string Channel string
Enabled bool Enabled bool
} }
type IconEntity struct { type IconEntity struct {
@ -62,18 +63,26 @@ type SettingEntity struct {
} }
type SourceEntity struct { type SourceEntity struct {
ID int64 ID int64
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
Source string // domain.SourceCollector...
Type string Source string
Value string
Enabled bool // Human Readable value to state what is getting collected
Url string DisplayName string
Tags string
// Tells the parser where to look for data
Url string
// Static tags for this defined record
Tags string
// If the record is disabled, then it will be skipped on processing
Enabled bool
} }
type SubscriptionEntity struct { type SubscriptionEntity struct {

View File

@ -1,5 +1,26 @@
package domain package domain
type ErrorResponse struct {
type BaseResponse struct {
Message string `json:"message"` Message string `json:"message"`
}
type ArticleResponse struct {
BaseResponse
Payload []ArticleDto `json:"payload"`
}
type ArticleAndSourceModel struct {
Article ArticleDto `json:"article"`
Source SourceDto `json:"source"`
}
type ArticleDetailResponse struct {
BaseResponse
Payload ArticleAndSourceModel `json:"payload"`
}
type DiscordWebhookResponse struct {
BaseResponse
Payload []DiscordWebHookDto `json:"payload"`
} }

View File

@ -4,186 +4,148 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain/models" "git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
"github.com/google/uuid" "git.jamestombleson.com/jtom38/newsbot-api/internal/services"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
//func (s *Handler) GetArticleRouter() http.Handler {
// r := chi.NewRouter()
//
// r.Get("/", s.listArticles)
// r.Route("/{ID}", func(r chi.Router) {
// r.Get("/", s.getArticle)
// r.Get("/details", s.getArticleDetails)
// })
// r.Get("/by/sourceid", s.ListArticlesBySourceId)
//
// return r
//}
type ArticlesListResults struct {
ApiStatusModel
Payload []models.ArticleDto `json:"payload"`
}
type ArticleGetResults struct {
ApiStatusModel
Payload models.ArticleDto `json:"payload"`
}
type ArticleDetailsResult struct {
ApiStatusModel
Payload models.ArticleDetailsDto `json:"payload"`
}
// ListArticles // ListArticles
// @Summary Lists the top 25 records ordering from newest to oldest. // @Summary Lists the top 25 records ordering from newest to oldest.
// @Produce application/json // @Produce application/json
// @Param page query string false "page number" // @Param page query string false "page number"
// @Tags Articles // @Tags Articles
// @Router /articles [get] // @Router /articles [get]
// @Success 200 {object} ArticlesListResults "OK" // @Success 200 {object} domain.ArticleResponse
// @Failure 400 {object} domain.BaseResponse
// @Failure 500 {object} domain.BaseResponse
func (s *Handler) listArticles(c echo.Context) error { func (s *Handler) listArticles(c echo.Context) error {
p := ArticlesListResults{ resp := domain.ArticleResponse{
ApiStatusModel: ApiStatusModel{ BaseResponse: domain.BaseResponse{
Message: "OK", Message: ResponseMessageSuccess,
StatusCode: http.StatusOK,
}, },
} }
queryPage := c.QueryParam("page") page, err := strconv.Atoi(c.QueryParam("page"))
if err != nil {
// if a page number was sent, process it page = 0
if len(queryPage) >= 1 {
page, err := strconv.Atoi(queryPage)
if err != nil {
return c.JSON(http.StatusBadRequest, err)
}
res, err := s.dto.ListArticles(c.Request().Context(), 25, page)
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
p.Payload = res
return c.JSON(http.StatusOK, p)
} else {
res, err := s.dto.ListArticles(c.Request().Context(), 25, 0)
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
p.Payload = res
return c.JSON(http.StatusOK, p)
} }
res, err := s.repo.Articles.ListByPage(c.Request().Context(), page, 25)
if err != nil {
s.WriteError(c, err, http.StatusInternalServerError)
}
resp.Payload = services.ArticlesToDto(res)
return c.JSON(http.StatusOK, resp)
} }
// GetArticle // GetArticle
// @Summary Returns an article based on defined ID. // @Summary Returns an article based on defined ID.
// @Param ID path string true "uuid" // @Param ID path string true "int"
// @Produce application/json // @Produce application/json
// @Tags Articles // @Tags Articles
// @Router /articles/{ID} [get] // @Router /articles/{ID} [get]
// @Success 200 {object} ArticleGetResults "OK" // @Success 200 {object} ArticleGetResults "OK"
// @Failure 400 {object} domain.BaseResponse
// @Failure 500 {object} domain.BaseResponse
func (s *Handler) getArticle(c echo.Context) error { func (s *Handler) getArticle(c echo.Context) error {
p := ArticleGetResults{ p := domain.ArticleResponse{
ApiStatusModel: ApiStatusModel{ BaseResponse: domain.BaseResponse{
Message: "OK", Message: ResponseMessageSuccess,
StatusCode: http.StatusOK,
}, },
} }
id := c.Param("ID") id := c.Param("ID")
uuid, err := uuid.Parse(id) idNumber, err := strconv.Atoi(id)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, err) s.WriteError(c, err, http.StatusBadRequest)
} }
res, err := s.dto.GetArticle(c.Request().Context(), uuid) item, err := s.repo.Articles.GetById(c.Request().Context(), int64(idNumber))
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, err) return c.JSON(http.StatusInternalServerError, err)
} }
p.Payload = res var dtos []domain.ArticleDto
dtos = append(dtos, services.ArticleToDto(item))
p.Payload = dtos
return c.JSON(http.StatusOK, p) return c.JSON(http.StatusOK, p)
} }
// GetArticleDetails // GetArticleDetails
// @Summary Returns an article and source based on defined ID. // @Summary Returns an article and source based on defined ID.
// @Param ID path string true "uuid" // @Param ID path string true "int"
// @Produce application/json // @Produce application/json
// @Tags Articles // @Tags Articles
// @Router /articles/{ID}/details [get] // @Router /articles/{ID}/details [get]
// @Success 200 {object} ArticleDetailsResult "OK" // @Success 200 {object} ArticleDetailsResult "OK"
// @Failure 400 {object} domain.BaseResponse
// @Failure 500 {object} domain.BaseResponse
func (s *Handler) getArticleDetails(c echo.Context) error { func (s *Handler) getArticleDetails(c echo.Context) error {
p := ArticleDetailsResult{ p := domain.ArticleDetailResponse{
ApiStatusModel: ApiStatusModel{ BaseResponse: domain.BaseResponse{
Message: "OK", Message: ResponseMessageSuccess,
StatusCode: http.StatusOK, },
Payload: domain.ArticleAndSourceModel{
}, },
} }
id := c.Param("ID") id, err := strconv.Atoi(c.Param("ID"))
uuid, err := uuid.Parse(id)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, err) s.WriteError(c, err, http.StatusBadRequest)
} }
res, err := s.dto.GetArticleDetails(c.Request().Context(), uuid) article, err := s.repo.Articles.GetById(c.Request().Context(), int64(id))
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, err) s.WriteError(c, err, http.StatusInternalServerError)
} }
p.Payload = res source, err := s.repo.Sources.GetById(c.Request().Context(), article.SourceID)
if err != nil {
s.WriteError(c, err, http.StatusInternalServerError)
}
p.Payload.Article = services.ArticleToDto(article)
p.Payload.Source = services.SourceToDto(source)
return c.JSON(http.StatusOK, p) return c.JSON(http.StatusOK, p)
} }
// TODO add page support
// ListArticlesBySourceID // ListArticlesBySourceID
// @Summary Finds the articles based on the SourceID provided. Returns the top 25. // @Summary Finds the articles based on the SourceID provided. Returns the top 25.
// @Param id query string true "Source ID UUID" // @Param id query string true
// @Param page query int false "Page to query" // @Param page query int false "Page to query"
// @Produce application/json // @Produce application/json
// @Tags Articles // @Tags Articles
// @Router /articles/by/sourceid [get] // @Router /articles/by/sourceid [get]
// @Success 200 {object} ArticlesListResults "OK" // @Success 200 {object} ArticlesListResults "OK"
// @Failure 400 {object} domain.BaseResponse
// @Failure 500 {object} domain.BaseResponse
func (s *Handler) ListArticlesBySourceId(c echo.Context) error { func (s *Handler) ListArticlesBySourceId(c echo.Context) error {
p := ArticlesListResults{ p := domain.ArticleResponse{
ApiStatusModel: ApiStatusModel{ BaseResponse: domain.BaseResponse{
Message: "OK", Message: ResponseMessageSuccess,
StatusCode: http.StatusOK,
}, },
} }
id := c.QueryParam("id") id, err := strconv.Atoi(c.QueryParam("id"))
uuid, err := uuid.Parse(id)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, err) s.WriteError(c, err, http.StatusBadRequest)
} }
// if a page number was sent, process it // if the page number is missing, default to 0
if len(c.QueryParam("page")) >= 1 { _page, err := strconv.Atoi(c.QueryParam("page"))
_page, err := strconv.Atoi(c.QueryParam("page")) if err != nil {
if err != nil { _page = 0
return c.JSON(http.StatusBadRequest, err)
}
res, err := s.dto.ListNewArticlesBySourceId(c.Request().Context(), uuid, 25, _page)
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
p.Payload = res
} else {
res, err := s.dto.ListNewArticlesBySourceId(c.Request().Context(), uuid, 25, 0)
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
p.Payload = res
} }
items, err := s.repo.Articles.ListBySource(c.Request().Context(), _page, 25, id, "")
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
p.Payload = services.ArticlesToDto(items)
return c.JSON(http.StatusOK, p) return c.JSON(http.StatusOK, p)
} }

View File

@ -60,21 +60,21 @@ func (s *Handler) GetDiscordWebHooksById(c echo.Context) error {
_id := c.Param("ID") _id := c.Param("ID")
if _id == "" { if _id == "" {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: ErrIdValueMissing, Message: ErrIdValueMissing,
}) })
} }
uuid, err := uuid.Parse(_id) uuid, err := uuid.Parse(_id)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: ErrUnableToParseId, Message: ErrUnableToParseId,
}) })
} }
res, err := s.dto.GetDiscordWebhook(c.Request().Context(), uuid) res, err := s.dto.GetDiscordWebhook(c.Request().Context(), uuid)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: ErrNoRecordFound, Message: ErrNoRecordFound,
}) })
} }
@ -100,21 +100,21 @@ func (s *Handler) GetDiscordWebHooksByServerAndChannel(c echo.Context) error {
_server := c.QueryParam("server") _server := c.QueryParam("server")
if _server == "" { if _server == "" {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: ErrIdValueMissing, Message: ErrIdValueMissing,
}) })
} }
_channel := c.QueryParam("channel") _channel := c.QueryParam("channel")
if _channel == "" { if _channel == "" {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: fmt.Sprintf("%s channel", ErrParameterMissing), Message: fmt.Sprintf("%s channel", ErrParameterMissing),
}) })
} }
res, err := s.dto.GetDiscordWebHookByServerAndChannel(c.Request().Context(), _server, _channel) res, err := s.dto.GetDiscordWebHookByServerAndChannel(c.Request().Context(), _server, _channel)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -136,22 +136,22 @@ func (s *Handler) NewDiscordWebHook(c echo.Context) error {
_channel := c.QueryParam("channel") _channel := c.QueryParam("channel")
if _url == "" { if _url == "" {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: "url is missing a value", Message: "url is missing a value",
}) })
} }
if !strings.Contains(_url, "discord.com/api/webhooks") { if !strings.Contains(_url, "discord.com/api/webhooks") {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: "invalid url", Message: "invalid url",
}) })
} }
if _server == "" { if _server == "" {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: "server is missing", Message: "server is missing",
}) })
} }
if _channel == "" { if _channel == "" {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: "channel is missing", Message: "channel is missing",
}) })
} }
@ -176,7 +176,7 @@ func (s *Handler) disableDiscordWebHook(c echo.Context) error {
id := c.Param("ID") id := c.Param("ID")
uuid, err := uuid.Parse(id) uuid, err := uuid.Parse(id)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -189,7 +189,7 @@ func (s *Handler) disableDiscordWebHook(c echo.Context) error {
err = s.Db.DisableDiscordWebHook(c.Request().Context(), uuid) err = s.Db.DisableDiscordWebHook(c.Request().Context(), uuid)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -205,7 +205,7 @@ func (s *Handler) enableDiscordWebHook(c echo.Context) error {
id := c.Param("ID") id := c.Param("ID")
uuid, err := uuid.Parse(id) uuid, err := uuid.Parse(id)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }

View File

@ -15,22 +15,24 @@ import (
) )
type Handler struct { type Handler struct {
Router *echo.Echo Router *echo.Echo
Db *database.Queries Db *database.Queries
dto *dto.DtoClient dto *dto.DtoClient
config services.Configs config services.Configs
sqlConnection *sql.DB repo services.RepositoryService
} }
const ( const (
HeaderContentType = "Content-Type" HeaderContentType = "Content-Type"
ApplicationJson = "application/json" //ApplicationJson = "application/json"
ErrParameterIdMissing = "The requested parameter ID was not found." ErrParameterIdMissing = "The requested parameter ID was not found."
ErrParameterMissing = "The requested parameter was found found:" ErrParameterMissing = "The requested parameter was found found:"
ErrUnableToParseId = "Unable to parse the requested ID." ErrUnableToParseId = "Unable to parse the requested ID."
ErrRecordMissing = "The requested record was not found" ErrRecordMissing = "The requested record was not found"
ResponseMessageSuccess = "Success"
) )
var ( var (
@ -42,18 +44,12 @@ var (
func NewServer(ctx context.Context, db *database.Queries, configs services.Configs, conn *sql.DB) *Handler { func NewServer(ctx context.Context, db *database.Queries, configs services.Configs, conn *sql.DB) *Handler {
s := &Handler{ s := &Handler{
Db: db, Db: db,
dto: dto.NewDtoClient(db), dto: dto.NewDtoClient(db),
config: configs, config: configs,
sqlConnection: conn, repo: services.NewRepositoryService(conn),
} }
db, err := openDatabase(ctx)
if err != nil {
panic(err)
}
s.Db = db
router := echo.New() router := echo.New()
router.GET("/swagger/*", swagger.WrapHandler) router.GET("/swagger/*", swagger.WrapHandler)
@ -73,11 +69,11 @@ func NewServer(ctx context.Context, db *database.Queries, configs services.Confi
dwh.POST("/:ID/disable", s.disableDiscordWebHook) dwh.POST("/:ID/disable", s.disableDiscordWebHook)
dwh.POST("/:ID/enable", s.enableDiscordWebHook) dwh.POST("/:ID/enable", s.enableDiscordWebHook)
queue := v1.Group("/queue") //queue := v1.Group("/queue")
queue.GET("/discord/webhooks", s.ListDiscordWebhookQueue) // TODO this needs to be reworked //queue.GET("/discord/webhooks", s.ListDiscordWebhookQueue) // TODO this needs to be reworked
settings := v1.Group("/settings") //settings := v1.Group("/settings")
settings.GET("/", s.getSettings) //settings.GET("/", s.getSettings)
sources := v1.Group("/sources") sources := v1.Group("/sources")
sources.GET("/", s.listSources) sources.GET("/", s.listSources)
@ -103,28 +99,6 @@ func NewServer(ctx context.Context, db *database.Queries, configs services.Confi
return s return s
} }
func openDatabase(ctx context.Context) (*database.Queries, error) {
_env := services.NewConfig()
connString := _env.GetConfig(services.Sql_Connection_String)
db, err := sql.Open("postgres", connString)
if err != nil {
panic(err)
}
queries := database.New(db)
return queries, err
}
func (s *Handler) MountRoutes() {
//s.Router.Get("/swagger/*", httpSwagger.Handler(
// httpSwagger.URL("doc.json"), //The url pointing to API definition
//))
//s.Router.Get("/api/settings", s.getSettings)
//s.Router.Mount("/api/sources", s.GetSourcesRouter())
//s.Router.Mount("/api/subscriptions", s.GetSubscriptionsRouter())
}
type ApiStatusModel struct { type ApiStatusModel struct {
StatusCode int `json:"status"` StatusCode int `json:"status"`
Message string `json:"message"` Message string `json:"message"`
@ -134,8 +108,14 @@ type ApiError struct {
*ApiStatusModel *ApiStatusModel
} }
func (s *Handler) WriteError(c echo.Context, errMessage string, HttpStatusCode int) error { func (s *Handler) WriteError(c echo.Context, errMessage error, HttpStatusCode int) error {
return c.JSON(HttpStatusCode, domain.ErrorResponse{ return c.JSON(HttpStatusCode, domain.BaseResponse{
Message: errMessage, Message: errMessage.Error(),
})
}
func (s *Handler) WriteMessage(c echo.Context, msg string, HttpStatusCode int) error {
return c.JSON(HttpStatusCode, domain.BaseResponse{
Message: msg,
}) })
} }

View File

@ -30,7 +30,7 @@ func (s *Handler) ListDiscordWebhookQueue(c echo.Context) error {
// Get the raw resp from sql // Get the raw resp from sql
res, err := s.dto.ListDiscordWebhookQueueDetails(c.Request().Context(), 50) res, err := s.dto.ListDiscordWebhookQueueDetails(c.Request().Context(), 50)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }

View File

@ -20,7 +20,7 @@ func (s *Handler) getSettings(c echo.Context) error {
uuid, err := uuid.Parse(id) uuid, err := uuid.Parse(id)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }

View File

@ -10,19 +10,10 @@ import (
"git.jamestombleson.com/jtom38/newsbot-api/internal/database" "git.jamestombleson.com/jtom38/newsbot-api/internal/database"
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain" "git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain/models" "git.jamestombleson.com/jtom38/newsbot-api/internal/domain/models"
"github.com/go-chi/chi/v5"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
func (s *Handler) GetSourcesRouter() http.Handler {
r := chi.NewRouter()
return r
}
type ListSources struct { type ListSources struct {
ApiStatusModel ApiStatusModel
Payload []models.SourceDto `json:"payload"` Payload []models.SourceDto `json:"payload"`
@ -38,8 +29,8 @@ type GetSource struct {
// @Produce application/json // @Produce application/json
// @Tags Source // @Tags Source
// @Router /sources [get] // @Router /sources [get]
// @Success 200 {object} ListSources "ok" // @Success 200 {object} ListSources "ok"
// @Failure 400 {object} ApiError "Unable to reach SQL or Data problems" // @Failure 400 {object} domain.BaseResponse "Unable to reach SQL or Data problems"
func (s *Handler) listSources(c echo.Context) error { func (s *Handler) listSources(c echo.Context) error {
//TODO Add top? //TODO Add top?
/* /*
@ -61,9 +52,7 @@ func (s *Handler) listSources(c echo.Context) error {
// Default way of showing all sources // Default way of showing all sources
items, err := s.dto.ListSources(c.Request().Context(), 50) items, err := s.dto.ListSources(c.Request().Context(), 50)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ s.WriteError(c, err, http.StatusInternalServerError)
Message: err.Error(),
})
} }
p.Payload = items p.Payload = items
@ -102,7 +91,7 @@ func (s *Handler) listSourcesBySource(c echo.Context) error {
// Shows the list by Sources.source // Shows the list by Sources.source
res, err := s.dto.ListSourcesBySource(c.Request().Context(), source) res, err := s.dto.ListSourcesBySource(c.Request().Context(), source)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -132,14 +121,14 @@ func (s *Handler) getSources(c echo.Context) error {
id := c.Param("ID") id := c.Param("ID")
uuid, err := uuid.Parse(id) uuid, err := uuid.Parse(id)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: ErrUnableToParseId, Message: ErrUnableToParseId,
}) })
} }
res, err := s.dto.GetSourceById(c.Request().Context(), uuid) res, err := s.dto.GetSourceById(c.Request().Context(), uuid)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: ErrNoRecordFound, Message: ErrNoRecordFound,
}) })
} }
@ -170,7 +159,7 @@ func (s *Handler) GetSourceBySourceAndName(c echo.Context) error {
var param domain.GetSourceBySourceAndNameParamRequest var param domain.GetSourceBySourceAndNameParamRequest
err := c.Bind(&param) err := c.Bind(&param)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -206,7 +195,7 @@ func (s *Handler) newRedditSource(c echo.Context) error {
var param domain.NewSourceParamRequest var param domain.NewSourceParamRequest
err := c.Bind(&param) err := c.Bind(&param)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -216,12 +205,12 @@ func (s *Handler) newRedditSource(c echo.Context) error {
//_tags := query["tags"][0] //_tags := query["tags"][0]
if param.Url == "" { if param.Url == "" {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: "Url is missing a value", Message: "Url is missing a value",
}) })
} }
if !strings.Contains(param.Url, "reddit.com") { if !strings.Contains(param.Url, "reddit.com") {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: "Invalid URL given", Message: "Invalid URL given",
}) })
} }
@ -248,7 +237,7 @@ func (s *Handler) newRedditSource(c echo.Context) error {
} }
err = s.Db.CreateSource(c.Request().Context(), params) err = s.Db.CreateSource(c.Request().Context(), params)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -272,7 +261,7 @@ func (s *Handler) newYoutubeSource(c echo.Context) error {
var param domain.NewSourceParamRequest var param domain.NewSourceParamRequest
err := c.Bind(&param) err := c.Bind(&param)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -283,12 +272,12 @@ func (s *Handler) newYoutubeSource(c echo.Context) error {
////_tags := query["tags"][0] ////_tags := query["tags"][0]
if param.Url == "" { if param.Url == "" {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: "url is missing a value", Message: "url is missing a value",
}) })
} }
if !strings.Contains(param.Url, "youtube.com") { if !strings.Contains(param.Url, "youtube.com") {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: "Invalid URL", Message: "Invalid URL",
}) })
} }
@ -318,7 +307,7 @@ func (s *Handler) newYoutubeSource(c echo.Context) error {
bJson, err := json.Marshal(&params) bJson, err := json.Marshal(&params)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -335,7 +324,7 @@ func (s *Handler) newTwitchSource(c echo.Context) error {
var param domain.NewSourceParamRequest var param domain.NewSourceParamRequest
err := c.Bind(&param) err := c.Bind(&param)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -358,14 +347,14 @@ func (s *Handler) newTwitchSource(c echo.Context) error {
} }
err = s.Db.CreateSource(c.Request().Context(), params) err = s.Db.CreateSource(c.Request().Context(), params)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
bJson, err := json.Marshal(&params) bJson, err := json.Marshal(&params)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -382,7 +371,7 @@ func (s *Handler) deleteSources(c echo.Context) error {
id := c.Param("ID") id := c.Param("ID")
uuid, err := uuid.Parse(id) uuid, err := uuid.Parse(id)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -390,7 +379,7 @@ func (s *Handler) deleteSources(c echo.Context) error {
// Check to make sure we can find the record // Check to make sure we can find the record
_, err = s.Db.GetSourceByID(c.Request().Context(), uuid) _, err = s.Db.GetSourceByID(c.Request().Context(), uuid)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -398,7 +387,7 @@ func (s *Handler) deleteSources(c echo.Context) error {
// Delete the record // Delete the record
err = s.Db.DeleteSource(c.Request().Context(), uuid) err = s.Db.DeleteSource(c.Request().Context(), uuid)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -410,7 +399,7 @@ func (s *Handler) deleteSources(c echo.Context) error {
b, err := json.Marshal(p) b, err := json.Marshal(p)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -427,7 +416,7 @@ func (s *Handler) disableSource(c echo.Context) error {
id := c.Param("ID") id := c.Param("ID")
uuid, err := uuid.Parse(id) uuid, err := uuid.Parse(id)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, domain.ErrorResponse{ return c.JSON(http.StatusBadRequest, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -435,14 +424,14 @@ func (s *Handler) disableSource(c echo.Context) error {
// Check to make sure we can find the record // Check to make sure we can find the record
_, err = s.Db.GetSourceByID(c.Request().Context(), uuid) _, err = s.Db.GetSourceByID(c.Request().Context(), uuid)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
err = s.Db.DisableSource(context.Background(), uuid) err = s.Db.DisableSource(context.Background(), uuid)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -454,7 +443,7 @@ func (s *Handler) disableSource(c echo.Context) error {
b, err := json.Marshal(p) b, err := json.Marshal(p)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -477,14 +466,14 @@ func (s *Handler) enableSource(c echo.Context) error {
// Check to make sure we can find the record // Check to make sure we can find the record
_, err = s.Db.GetSourceByID(c.Request().Context(), uuid) _, err = s.Db.GetSourceByID(c.Request().Context(), uuid)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
err = s.Db.EnableSource(c.Request().Context(), uuid) err = s.Db.EnableSource(c.Request().Context(), uuid)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }
@ -496,7 +485,7 @@ func (s *Handler) enableSource(c echo.Context) error {
b, err := json.Marshal(p) b, err := json.Marshal(p)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, domain.ErrorResponse{ return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: err.Error(), Message: err.Error(),
}) })
} }

View File

@ -3,6 +3,7 @@ package v1
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"net/http" "net/http"
"git.jamestombleson.com/jtom38/newsbot-api/internal/database" "git.jamestombleson.com/jtom38/newsbot-api/internal/database"
@ -44,7 +45,7 @@ func (s *Handler) ListSubscriptions(c echo.Context) error {
res, err := s.dto.ListSubscriptions(c.Request().Context(), 50) res, err := s.dto.ListSubscriptions(c.Request().Context(), 50)
if err != nil { if err != nil {
return s.WriteError(c, err.Error(), http.StatusBadRequest) return s.WriteError(c, err, http.StatusBadRequest)
} }
payload.Payload = res payload.Payload = res
@ -67,7 +68,7 @@ func (s *Handler) ListSubscriptionDetails(c echo.Context) error {
res, err := s.dto.ListSubscriptionDetails(c.Request().Context(), 50) res, err := s.dto.ListSubscriptionDetails(c.Request().Context(), 50)
if err != nil { if err != nil {
return s.WriteError(c, err.Error(), http.StatusInternalServerError) return s.WriteError(c, err, http.StatusInternalServerError)
} }
payload.Payload = res payload.Payload = res
@ -93,18 +94,18 @@ func (s *Handler) GetSubscriptionsByDiscordId(c echo.Context) error {
id := c.QueryParam("id") id := c.QueryParam("id")
if id == "" { if id == "" {
return s.WriteError(c, ErrIdValueMissing, http.StatusBadRequest) return s.WriteError(c, errors.New(ErrIdValueMissing), http.StatusBadRequest)
} }
uuid, err := uuid.Parse(id) uuid, err := uuid.Parse(id)
if err != nil { if err != nil {
return s.WriteError(c, ErrValueNotUuid, http.StatusBadRequest) return s.WriteError(c, errors.New(ErrValueNotUuid), http.StatusBadRequest)
} }
res, err := s.dto.ListSubscriptionsByDiscordWebhookId(context.Background(), uuid) res, err := s.dto.ListSubscriptionsByDiscordWebhookId(context.Background(), uuid)
if err != nil { if err != nil {
return s.WriteError(c, err.Error(), http.StatusNoContent) return s.WriteError(c, err, http.StatusNoContent)
} }
p.Payload = res p.Payload = res
@ -128,17 +129,17 @@ func (s *Handler) GetSubscriptionsBySourceId(c echo.Context) error {
_id := c.QueryParam("id") _id := c.QueryParam("id")
if _id == "" { if _id == "" {
return s.WriteError(c, ErrIdValueMissing, http.StatusBadRequest) return s.WriteError(c, errors.New(ErrIdValueMissing), http.StatusBadRequest)
} }
uuid, err := uuid.Parse(_id) uuid, err := uuid.Parse(_id)
if err != nil { if err != nil {
return s.WriteError(c, err.Error(), http.StatusBadRequest) return s.WriteError(c, err, http.StatusBadRequest)
} }
res, err := s.dto.ListSubscriptionsBySourceId(context.Background(), uuid) res, err := s.dto.ListSubscriptionsBySourceId(context.Background(), uuid)
if err != nil { if err != nil {
return s.WriteError(c, err.Error(), http.StatusNoContent) return s.WriteError(c, err, http.StatusNoContent)
} }
p.Payload = res p.Payload = res
@ -158,20 +159,20 @@ func (s *Handler) newDiscordWebHookSubscription(c echo.Context) error {
// Check to make we didn't get a null // Check to make we didn't get a null
if discordWebHookId == "" { if discordWebHookId == "" {
return s.WriteError(c, "invalid discordWebHooksId given", http.StatusBadRequest) return s.WriteError(c, errors.New("invalid discordWebHooksId given"), http.StatusBadRequest)
} }
if sourceId == "" { if sourceId == "" {
return s.WriteError(c, "invalid sourceID given", http.StatusBadRequest) return s.WriteError(c, errors.New("invalid sourceID given"), http.StatusBadRequest)
} }
// Validate they are UUID values // Validate they are UUID values
uHook, err := uuid.Parse(discordWebHookId) uHook, err := uuid.Parse(discordWebHookId)
if err != nil { if err != nil {
return s.WriteError(c, err.Error(), http.StatusBadRequest) return s.WriteError(c, err, http.StatusBadRequest)
} }
uSource, err := uuid.Parse(sourceId) uSource, err := uuid.Parse(sourceId)
if err != nil { if err != nil {
return s.WriteError(c, err.Error(), http.StatusBadRequest) return s.WriteError(c, err, http.StatusBadRequest)
} }
// Check if the sub already exists // Check if the sub already exists
@ -180,7 +181,7 @@ func (s *Handler) newDiscordWebHookSubscription(c echo.Context) error {
Sourceid: uSource, Sourceid: uSource,
}) })
if err == nil { if err == nil {
return s.WriteError(c, "a subscription already exists between these two entities", http.StatusBadRequest) return s.WriteError(c, errors.New("a subscription already exists between these two entities"), http.StatusBadRequest)
} }
// Does not exist, so make it. // Does not exist, so make it.
@ -191,12 +192,12 @@ func (s *Handler) newDiscordWebHookSubscription(c echo.Context) error {
} }
err = s.Db.CreateSubscription(context.Background(), params) err = s.Db.CreateSubscription(context.Background(), params)
if err != nil { if err != nil {
return s.WriteError(c, err.Error(), http.StatusInternalServerError) return s.WriteError(c, err, http.StatusInternalServerError)
} }
bJson, err := json.Marshal(&params) bJson, err := json.Marshal(&params)
if err != nil { if err != nil {
return s.WriteError(c, err.Error(), http.StatusInternalServerError) return s.WriteError(c, err, http.StatusInternalServerError)
} }
return c.JSON(http.StatusOK, bJson) return c.JSON(http.StatusOK, bJson)
@ -212,17 +213,17 @@ func (s *Handler) DeleteDiscordWebHookSubscription(c echo.Context) error {
id := c.QueryParam("id") id := c.QueryParam("id")
if id == "" { if id == "" {
return s.WriteError(c, ErrMissingSubscriptionID, http.StatusBadRequest) return s.WriteError(c, errors.New(ErrMissingSubscriptionID), http.StatusBadRequest)
} }
uid, err := uuid.Parse(id) uid, err := uuid.Parse(id)
if err != nil { if err != nil {
return s.WriteError(c, err.Error(), http.StatusBadRequest) return s.WriteError(c, err, http.StatusBadRequest)
} }
err = s.Db.DeleteSubscription(context.Background(), uid) err = s.Db.DeleteSubscription(context.Background(), uid)
if err != nil { if err != nil {
return s.WriteError(c, err.Error(), http.StatusInternalServerError) return s.WriteError(c, err, http.StatusInternalServerError)
} }
return c.JSON(http.StatusOK, nil) return c.JSON(http.StatusOK, nil)

View File

@ -0,0 +1,244 @@
package repository
import (
"context"
"database/sql"
"errors"
"fmt"
"time"
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
"github.com/huandu/go-sqlbuilder"
)
const (
ArticleOrderByPublishDateDesc = "pubDate desc"
ArticleOrderByPublishDateAsc = "pubDate asc"
)
type ArticlesRepo interface {
GetById(ctx context.Context, id int64) (domain.ArticleEntity, error)
GetByUrl(ctx context.Context, url string) (domain.ArticleEntity, error)
ListTop(ctx context.Context, limit int) ([]domain.ArticleEntity, error)
ListByPage(ctx context.Context, page, limit int) ([]domain.ArticleEntity, error)
ListByPublishDate(ctx context.Context, page, limit int, orderBy string) ([]domain.ArticleEntity, error)
ListBySource(ctx context.Context, page, limit, sourceId int, orderBy string) ([]domain.ArticleEntity, error)
Create(ctx context.Context, sourceId int64, tags, title, url, thumbnailUrl, description, authorName, authorImageUrl string, pubDate time.Time, isVideo bool) (int64, error)
}
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(ctx context.Context, 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.QueryContext(ctx, 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(ctx context.Context, 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.QueryContext(ctx, 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) ListTop(ctx context.Context, limit int) ([]domain.ArticleEntity, error) {
builder := sqlbuilder.NewSelectBuilder()
builder.Select("*")
builder.From("articles")
builder.Limit(limit)
query, args := builder.Build()
rows, err := ar.conn.QueryContext(ctx, 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(ctx context.Context, page, limit int) ([]domain.ArticleEntity, error) {
builder := sqlbuilder.NewSelectBuilder()
builder.Select("*")
builder.From("articles")
builder.OrderBy(ArticleOrderByPublishDateDesc)
builder.Offset(page * limit)
builder.Limit(limit)
query, args := builder.Build()
rows, err := ar.conn.QueryContext(ctx, 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(ctx context.Context, page, limit int, orderBy string) ([]domain.ArticleEntity, error) {
builder := sqlbuilder.NewSelectBuilder()
builder.Select("*")
builder.From("articles")
if orderBy != "" {
builder.OrderBy(orderBy)
}
builder.Offset(page * limit)
builder.Limit(limit)
query, args := builder.Build()
rows, err := ar.conn.QueryContext(ctx, 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(ctx context.Context, page, limit, sourceId int, orderBy string) ([]domain.ArticleEntity, error) {
builder := sqlbuilder.NewSelectBuilder()
builder.Select("*")
builder.From("articles")
builder.JoinWithOption("InnerJoin", "sources", "articles.sourceId=sources.Id")
if orderBy != "" {
builder.OrderBy(orderBy)
}
builder.Where(
builder.Equal("SourceId", sourceId),
)
builder.Offset(50)
builder.Limit(page * limit)
query, args := builder.Build()
rows, err := ar.conn.QueryContext(ctx, 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(ctx context.Context, 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", "DeletedAt", "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()
_, err := ar.conn.ExecContext(ctx, 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 time.Time
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,
DeletedAt: deletedAt,
SourceID: sourceId,
Tags: tags,
Title: title,
Url: url,
PubDate: pubDate,
IsVideo: isVideo,
Thumbnail: thumbnail,
Description: description,
AuthorName: authorName,
AuthorImageUrl: authorImageUrl,
}
items = append(items, item)
}
return items
}

View File

@ -0,0 +1,156 @@
package repository_test
import (
"context"
"testing"
"time"
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
)
const (
articleFakeDotCom = "www.fake.com"
)
func TestCreateArticle(t *testing.T) {
t.Log(time.Time{})
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
r := repository.NewArticleRepository(db)
created, err := r.Create(context.Background(), 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", 0)
if err != nil {
t.Log(err)
t.FailNow()
}
article, err := r.GetByUrl(context.Background(), 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", 0)
insertFakeArticles(r, "u2", 0)
insertFakeArticles(r, "u3", 0)
insertFakeArticles(r, "u4", 0)
items, err := r.ListTop(context.Background(), 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", 0)
insertFakeArticles(r, "u2", 0)
insertFakeArticles(r, "u3", 0)
insertFakeArticles(r, "u4", 0)
items, err := r.ListByPage(context.Background(), 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 TestPullingByPublishDate(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
r := repository.NewArticleRepository(db)
today := time.Now()
insertFakeArticles(r, "u1", 0)
insertFakeArticles(r, "u1", -1)
insertFakeArticles(r, "u1", -2)
items, err := r.ListByPublishDate(context.Background(), 0, 2, repository.ArticleOrderByPublishDateDesc)
if err != nil {
t.Log(err)
t.FailNow()
}
if len(items) != 2 {
t.Log("expected two items back")
t.FailNow()
}
pubDate := items[1].PubDate.Day()
expectedDay := today.Day() - 1
if pubDate != expectedDay {
t.Log("expected a record that was 2 days old")
t.FailNow()
}
}
//func TestArticleBySource
func insertFakeArticles(r repository.ArticleRepository, title string, daysOld int) error {
pubDate := time.Now().AddDate(0,0, daysOld)
_, err := r.Create(context.Background(), 1, "", title, articleFakeDotCom, "", "testing", "", "", pubDate, false)
if err != nil {
return err
}
return 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

@ -0,0 +1,226 @@
package repository
import (
"context"
"database/sql"
"time"
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
"github.com/huandu/go-sqlbuilder"
)
type DiscordWebHookRepo interface{
Create(ctx context.Context, url, server, channel string, enabled bool) (int64, error)
Enable(ctx context.Context, id int64) (int64, error)
Disable(ctx context.Context, id int64) (int64, error)
SoftDelete(ctx context.Context, id int64) (int64, error)
Restore(ctx context.Context, id int64) (int64, error)
Delete(ctx context.Context, id int64) (int64, error)
GetById(ctx context.Context, id int64) (domain.DiscordWebHookEntity, error)
GetByUrl(ctx context.Context, url string) (domain.DiscordWebHookEntity, error)
ListByServerName(ctx context.Context, name string) ([]domain.DiscordWebHookEntity, error)
ListByServerAndChannel(ctx context.Context, server, channel string) ([]domain.DiscordWebHookEntity, error)
}
type discordWebHookRepository struct {
conn *sql.DB
}
func NewDiscordWebHookRepository(conn *sql.DB) discordWebHookRepository {
return discordWebHookRepository{
conn: conn,
}
}
func (r discordWebHookRepository) Create(ctx context.Context, url, server, channel string, enabled bool) (int64, error) {
dt := time.Now()
queryBuilder := sqlbuilder.NewInsertBuilder()
queryBuilder.InsertInto("DiscordWebHooks")
queryBuilder.Cols("UpdatedAt", "CreatedAt", "DeletedAt", "Url", "Server", "Channel", "Enabled")
queryBuilder.Values(dt, dt, timeZero, url, server, channel, enabled)
query, args := queryBuilder.Build()
_, err := r.conn.ExecContext(ctx, query, args...)
if err != nil {
return 0, err
}
return 1, nil
}
func (r discordWebHookRepository) Enable(ctx context.Context, id int64) (int64, error) {
b := sqlbuilder.NewUpdateBuilder()
b.Update("DiscordWebHooks")
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 discordWebHookRepository) Disable(ctx context.Context, id int64) (int64, error) {
b := sqlbuilder.NewUpdateBuilder()
b.Update("DiscordWebHooks")
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 discordWebHookRepository) SoftDelete(ctx context.Context, id int64) (int64, error) {
return softDeleteRow(ctx, r.conn, "DiscordWebHooks", id)
}
func (r discordWebHookRepository) Restore(ctx context.Context, id int64) (int64, error) {
return restoreRow(ctx, r.conn, "DiscordWebHooks", id)
}
func (r discordWebHookRepository) Delete(ctx context.Context, id int64) (int64, error) {
return deleteFromTable(ctx, r.conn, "DiscordWebHooks", id)
}
func (r discordWebHookRepository) GetById(ctx context.Context, id int64) (domain.DiscordWebHookEntity, error) {
builder := sqlbuilder.NewSelectBuilder()
builder.Select("*")
builder.From("DiscordWebHooks").Where(
builder.E("id", id),
)
builder.Limit(1)
query, args := builder.Build()
rows, err := r.conn.QueryContext(ctx, query, args...)
if err != nil {
return domain.DiscordWebHookEntity{}, err
}
data, err := r.processRows(rows)
if len(data) == 0 {
return domain.DiscordWebHookEntity{}, err
}
return data[0], nil
}
func (r discordWebHookRepository) GetByUrl(ctx context.Context, url string) (domain.DiscordWebHookEntity, error) {
builder := sqlbuilder.NewSelectBuilder()
builder.Select("*")
builder.From("DiscordWebHooks").Where(
builder.E("Url", url),
)
builder.Limit(1)
query, args := builder.Build()
rows, err := r.conn.QueryContext(ctx, query, args...)
if err != nil {
return domain.DiscordWebHookEntity{}, err
}
data, err := r.processRows(rows)
if len(data) == 0 {
return domain.DiscordWebHookEntity{}, err
}
return data[0], nil
}
func (r discordWebHookRepository) ListByServerName(ctx context.Context, name string) ([]domain.DiscordWebHookEntity, error) {
builder := sqlbuilder.NewSelectBuilder()
builder.Select("*")
builder.From("DiscordWebHooks").Where(
builder.E("Server", name),
)
query, args := builder.Build()
rows, err := r.conn.QueryContext(ctx, query, args...)
if err != nil {
return []domain.DiscordWebHookEntity{}, err
}
data, err := r.processRows(rows)
if len(data) == 0 {
return []domain.DiscordWebHookEntity{}, err
}
return data, nil
}
func (r discordWebHookRepository) ListByServerAndChannel(ctx context.Context, server, channel string) ([]domain.DiscordWebHookEntity, error) {
builder := sqlbuilder.NewSelectBuilder()
builder.Select("*")
builder.From("DiscordWebHooks").Where(
builder.Equal("Server", server),
builder.Equal("Channel", channel),
)
query, args := builder.Build()
rows, err := r.conn.QueryContext(ctx, query, args...)
if err != nil {
return []domain.DiscordWebHookEntity{}, err
}
data, err := r.processRows(rows)
if len(data) == 0 {
return []domain.DiscordWebHookEntity{}, err
}
return data, nil
}
func (r discordWebHookRepository) processRows(rows *sql.Rows) ([]domain.DiscordWebHookEntity, error) {
items := []domain.DiscordWebHookEntity{}
for rows.Next() {
var id int64
var createdAt time.Time
var updatedAt time.Time
var deletedAt time.Time
var url string
var server string
var channel string
var enabled bool
err := rows.Scan(
&id, &createdAt, &updatedAt,
&deletedAt, &url, &server,
&channel, &enabled,
)
if err != nil {
return items, err
}
item := domain.DiscordWebHookEntity{
ID: id,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
DeletedAt: deletedAt,
Url: url,
Server: server,
Channel: channel,
Enabled: enabled,
}
items = append(items, item)
}
return items, nil
}

View File

@ -0,0 +1,287 @@
package repository_test
import (
"context"
"testing"
"time"
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
)
func TestCreateDiscordWebHookRecord(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
r := repository.NewDiscordWebHookRepository(db)
created, err := r.Create(context.Background(), "www.discord.com/bad/webhook", "Unit Testing", "memes", true)
if err != nil {
t.Log(err)
t.FailNow()
}
if created != 1 {
t.Log("failed to create the record")
t.FailNow()
}
}
func TestDiscordWebHookGetById(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
ctx := context.Background()
r := repository.NewDiscordWebHookRepository(db)
created, err := r.Create(ctx, "www.discord.com/bad/webhook", "Unit Testing", "memes", true)
if err != nil {
t.Log(err)
t.FailNow()
}
if created != 1 {
t.Log("failed to create the record")
t.FailNow()
}
item, err := r.GetById(ctx, 1)
if err != nil {
t.Log(err)
t.FailNow()
}
if item.ID != 1 {
t.Log("got the wrong record back")
t.FailNow()
}
}
func TestDiscordWebHookGetByUrl(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
ctx := context.Background()
r := repository.NewDiscordWebHookRepository(db)
_, _ = r.Create(ctx, "www.discord.com/bad/webhook", "Unit Testing", "memes", true)
item, err := r.GetByUrl(ctx, "www.discord.com/bad/webhook")
if err != nil {
t.Log(err)
t.FailNow()
}
if item.Url != "www.discord.com/bad/webhook" {
t.Log("got the wrong record back")
t.FailNow()
}
}
func TestDiscordWebHookListByServerName(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
ctx := context.Background()
serverName := "Unit Testing"
r := repository.NewDiscordWebHookRepository(db)
_, _ = r.Create(ctx, "www.discord.com/bad/webhook", serverName, "memes", true)
item, err := r.ListByServerName(ctx, serverName)
if err != nil {
t.Log(err)
t.FailNow()
}
if item[0].Server != serverName {
t.Log("got the wrong record back")
t.FailNow()
}
}
func TestDiscordWebHookListByServerAndChannel(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
ctx := context.Background()
serverName := "Unit Testing"
channel := "memes"
r := repository.NewDiscordWebHookRepository(db)
_, _ = r.Create(ctx, "www.discord.com/bad/webhook", serverName, channel, true)
item, err := r.ListByServerAndChannel(ctx, serverName, channel)
if err != nil {
t.Log(err)
t.FailNow()
}
if item[0].Server != serverName {
t.Log("got the wrong wrong server back")
t.FailNow()
}
if item[0].Channel != channel {
t.Log("got the wrong channel back")
t.FailNow()
}
}
func TestDiscordWebHookEnableRecord(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
ctx := context.Background()
serverName := "Unit Testing"
channel := "memes"
r := repository.NewDiscordWebHookRepository(db)
_, _ = r.Create(ctx, "www.discord.com/bad/webhook", serverName, channel, false)
item, err := r.GetById(ctx, 1)
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, 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()
}
}
func TestDiscordWebHookDisableRecord(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
ctx := context.Background()
serverName := "Unit Testing"
channel := "memes"
r := repository.NewDiscordWebHookRepository(db)
_, _ = r.Create(ctx, "www.discord.com/bad/webhook", serverName, channel, true)
item, err := r.GetById(ctx, 1)
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()
}
}
func TestDiscordWebHookSoftDelete(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
ctx := context.Background()
serverName := "Unit Testing"
channel := "memes"
r := repository.NewDiscordWebHookRepository(db)
_, _ = r.Create(ctx, "www.discord.com/bad/webhook", serverName, channel, true)
_, err = r.SoftDelete(ctx, 1)
if err != nil {
t.Log(err)
t.FailNow()
}
updated, _ := r.GetById(ctx, 1)
t.Log(updated.DeletedAt)
}
func TestDiscordWebHookRestore(t *testing.T) {
db, err := setupInMemoryDb()
if err != nil {
t.Log(err)
t.FailNow()
}
defer db.Close()
ctx := context.Background()
serverName := "Unit Testing"
channel := "memes"
timeZero := time.Time{}
r := repository.NewDiscordWebHookRepository(db)
_, _ = r.Create(ctx, "www.discord.com/bad/webhook", serverName, channel, true)
item, _ := r.GetById(ctx, 1)
if item.DeletedAt != timeZero {
t.Log("DeletedAt was not zero")
t.FailNow()
}
_, _ = r.SoftDelete(ctx, 1)
softDeleted, _ := r.GetById(ctx, 1)
if softDeleted.ID != 1 {
t.Log("record went boom")
t.FailNow()
}
_, err = r.Restore(ctx, 1)
if err != nil {
t.Log(err)
t.FailNow()
}
updated, _ := r.GetById(ctx, 1)
t.Log(updated.DeletedAt)
}

View File

@ -14,7 +14,7 @@ const (
refreshTokenTableName = "RefreshTokens" refreshTokenTableName = "RefreshTokens"
) )
type RefreshTokenTable interface { type RefreshToken interface {
Create(username string, token string) (int64, error) Create(username string, token string) (int64, error)
GetByUsername(name string) (domain.RefreshTokenEntity, error) GetByUsername(name string) (domain.RefreshTokenEntity, error)
DeleteById(id int64) (int64, error) DeleteById(id int64) (int64, error)

View File

@ -0,0 +1,253 @@
package repository
import (
"context"
"database/sql"
"time"
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
"github.com/huandu/go-sqlbuilder"
)
type Sources interface {
Create(ctx context.Context, source, displayName, url, tags string, enabled bool) (int64, error)
GetById(ctx context.Context, id int64) (domain.SourceEntity, error)
GetByDisplayName(ctx context.Context, displayName string) (domain.SourceEntity, error)
GetBySource(ctx context.Context, source string) (domain.SourceEntity, error)
List(ctx context.Context, page, limit int) ([]domain.SourceEntity, error)
ListBySource(ctx context.Context, page, limit int, source string) ([]domain.SourceEntity, error)
Enable(ctx context.Context, id int64) (int64, error)
Disable(ctx context.Context, id int64) (int64, error)
SoftDelete(ctx context.Context, id int64) (int64, error)
Restore(ctx context.Context, id int64) (int64, error)
Delete(ctx context.Context, id int64) (int64, error)
}
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

@ -17,7 +17,7 @@ const (
ErrUserNotFound string = "requested user was not found" ErrUserNotFound string = "requested user was not found"
) )
type IUserTable interface { type Users interface {
GetByName(name string) (domain.UserEntity, error) GetByName(name string) (domain.UserEntity, error)
Create(name, password, scope string) (int64, error) Create(name, password, scope string) (int64, error)
Update(id int, entity domain.UserEntity) error Update(id int, entity domain.UserEntity) error
@ -27,17 +27,17 @@ type IUserTable interface {
} }
// Creates a new instance of UserRepository with the bound sql // Creates a new instance of UserRepository with the bound sql
func NewUserRepository(conn *sql.DB) UserRepository { func NewUserRepository(conn *sql.DB) userRepository {
return UserRepository{ return userRepository{
connection: conn, connection: conn,
} }
} }
type UserRepository struct { type userRepository struct {
connection *sql.DB connection *sql.DB
} }
func (ur UserRepository) GetByName(name string) (domain.UserEntity, error) { func (ur userRepository) GetByName(name string) (domain.UserEntity, error) {
builder := sqlbuilder.NewSelectBuilder() builder := sqlbuilder.NewSelectBuilder()
builder.Select("*").From("users").Where( builder.Select("*").From("users").Where(
builder.E("Name", name), builder.E("Name", name),
@ -57,7 +57,7 @@ func (ur UserRepository) GetByName(name string) (domain.UserEntity, error) {
return data[0], nil return data[0], nil
} }
func (ur UserRepository) Create(name, password, scope string) (int64, error) { func (ur userRepository) Create(name, password, scope string) (int64, error) {
passwordBytes := []byte(password) passwordBytes := []byte(password)
hash, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.DefaultCost) hash, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.DefaultCost)
if err != nil { if err != nil {
@ -79,11 +79,11 @@ func (ur UserRepository) Create(name, password, scope string) (int64, error) {
return 1, nil return 1, nil
} }
func (ur UserRepository) Update(id int, entity domain.UserEntity) error { func (ur userRepository) Update(id int, entity domain.UserEntity) error {
return errors.New("not implemented") return errors.New("not implemented")
} }
func (ur UserRepository) UpdatePassword(name, password string) error { func (ur userRepository) UpdatePassword(name, password string) error {
_, err := ur.GetByName(name) _, err := ur.GetByName(name)
if err != nil { if err != nil {
return nil return nil
@ -97,7 +97,7 @@ func (ur UserRepository) UpdatePassword(name, password string) error {
// If the hash matches what we have in the database, an error will not be returned. // If the hash matches what we have in the database, an error will not be returned.
// If the user does not exist or the hash does not match, an error will be returned // If the user does not exist or the hash does not match, an error will be returned
func (ur UserRepository) CheckUserHash(name, password string) error { func (ur userRepository) CheckUserHash(name, password string) error {
record, err := ur.GetByName(name) record, err := ur.GetByName(name)
if err != nil { if err != nil {
return err return err
@ -111,7 +111,7 @@ func (ur UserRepository) CheckUserHash(name, password string) error {
return nil return nil
} }
func (ur UserRepository) UpdateScopes(name, scope string) error { func (ur userRepository) UpdateScopes(name, scope string) error {
builder := sqlbuilder.NewUpdateBuilder() builder := sqlbuilder.NewUpdateBuilder()
builder.Update("users") builder.Update("users")
builder.Set( builder.Set(
@ -129,7 +129,7 @@ func (ur UserRepository) UpdateScopes(name, scope string) error {
return nil return nil
} }
func (ur UserRepository) processRows(rows *sql.Rows) []domain.UserEntity { func (ur userRepository) processRows(rows *sql.Rows) []domain.UserEntity {
items := []domain.UserEntity{} items := []domain.UserEntity{}
for rows.Next() { for rows.Next() {

View File

@ -12,7 +12,6 @@ import (
) )
func TestCanCreateNewUser(t *testing.T) { func TestCanCreateNewUser(t *testing.T) {
//t.Log(time.Now().String())
db, err := setupInMemoryDb() db, err := setupInMemoryDb()
if err != nil { if err != nil {
t.Log(err) t.Log(err)

View File

@ -0,0 +1,25 @@
package services
import (
"database/sql"
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
)
type RepositoryService struct {
Articles repository.ArticlesRepo
DiscordWebHooks repository.DiscordWebHookRepo
Sources repository.Sources
Users repository.Users
RefreshTokens repository.RefreshToken
}
func NewRepositoryService(conn *sql.DB) RepositoryService {
return RepositoryService{
Articles: repository.NewArticleRepository(conn),
DiscordWebHooks: repository.NewDiscordWebHookRepository(conn),
Sources: repository.NewSourceRepository(conn),
Users: repository.NewUserRepository(conn),
RefreshTokens: repository.NewRefreshTokenRepository(conn),
}
}

View File

@ -0,0 +1,46 @@
package services
import "git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
func ArticlesToDto(items []domain.ArticleEntity) []domain.ArticleDto {
var dtos []domain.ArticleDto
for _, item := range items {
dtos = append(dtos, ArticleToDto(item))
}
return dtos
}
func ArticleToDto(item domain.ArticleEntity) domain.ArticleDto {
return domain.ArticleDto{
ID: item.ID,
SourceID: item.SourceID,
Tags: item.Tags,
Title: item.Title,
Url: item.Url,
PubDate: item.PubDate,
IsVideo: item.IsVideo,
Thumbnail: item.Thumbnail,
Description: item.Description,
AuthorName: item.AuthorName,
AuthorImageUrl: item.AuthorImageUrl,
}
}
func SourcesToDto(items []domain.SourceEntity) []domain.SourceDto {
var dtos []domain.SourceDto
for _, item := range items {
dtos = append(dtos, SourceToDto(item))
}
return dtos
}
func SourceToDto(item domain.SourceEntity) domain.SourceDto {
return domain.SourceDto{
ID: item.ID,
Source: item.Source,
DisplayName: item.DisplayName,
Url: item.Url,
Tags: item.Tags,
Enabled: item.Enabled,
}
}

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 .
@ -19,8 +18,8 @@ migrate-dev-down: ## revert sql migrations to dev db
goose -dir "./internal/database/migrations" postgres "user=postgres password=postgres dbname=postgres sslmode=disable" down goose -dir "./internal/database/migrations" postgres "user=postgres password=postgres dbname=postgres sslmode=disable" down
swag: ## Generates the swagger documentation with the swag tool swag: ## Generates the swagger documentation with the swag tool
~/go/bin/swag f ~/go/bin/swag f
~/go/bin/swag i ~/go/bin/swag init -g cmd/server.go
gensql: ## Generates SQL code with sqlc gensql: ## Generates SQL code with sqlc
sqlc generate sqlc generate