Compare commits

...

8 Commits

19 changed files with 342 additions and 304 deletions

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

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

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

@ -1,6 +1,7 @@
package repository package repository
import ( import (
"context"
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
@ -12,9 +13,19 @@ import (
const ( const (
ArticleOrderByPublishDateDesc = "pubDate desc" ArticleOrderByPublishDateDesc = "pubDate desc"
ArticleOrderByPublishDatAsc = "pubDate asc" 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 { type ArticleRepository struct {
conn *sql.DB conn *sql.DB
defaultLimit int defaultLimit int
@ -29,7 +40,7 @@ func NewArticleRepository(conn *sql.DB) ArticleRepository {
} }
} }
func (ar ArticleRepository) GetById(id int64) (domain.ArticleEntity, error) { func (ar ArticleRepository) GetById(ctx context.Context, id int64) (domain.ArticleEntity, error) {
builder := sqlbuilder.NewSelectBuilder() builder := sqlbuilder.NewSelectBuilder()
builder.Select("*") builder.Select("*")
builder.From("articles").Where( builder.From("articles").Where(
@ -38,7 +49,7 @@ func (ar ArticleRepository) GetById(id int64) (domain.ArticleEntity, error) {
builder.Limit(1) builder.Limit(1)
query, args := builder.Build() query, args := builder.Build()
rows, err := ar.conn.Query(query, args...) rows, err := ar.conn.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
return domain.ArticleEntity{}, err return domain.ArticleEntity{}, err
} }
@ -51,7 +62,7 @@ func (ar ArticleRepository) GetById(id int64) (domain.ArticleEntity, error) {
return data[0], nil return data[0], nil
} }
func (ar ArticleRepository) GetByUrl(url string) (domain.ArticleEntity, error) { func (ar ArticleRepository) GetByUrl(ctx context.Context, url string) (domain.ArticleEntity, error) {
builder := sqlbuilder.NewSelectBuilder() builder := sqlbuilder.NewSelectBuilder()
builder.Select("*") builder.Select("*")
builder.From("articles").Where( builder.From("articles").Where(
@ -60,7 +71,7 @@ func (ar ArticleRepository) GetByUrl(url string) (domain.ArticleEntity, error) {
builder.Limit(1) builder.Limit(1)
query, args := builder.Build() query, args := builder.Build()
rows, err := ar.conn.Query(query, args...) rows, err := ar.conn.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
return domain.ArticleEntity{}, err return domain.ArticleEntity{}, err
} }
@ -73,14 +84,14 @@ func (ar ArticleRepository) GetByUrl(url string) (domain.ArticleEntity, error) {
return data[0], nil return data[0], nil
} }
func (ar ArticleRepository) ListTop(limit int) ([]domain.ArticleEntity, error) { func (ar ArticleRepository) ListTop(ctx context.Context, limit int) ([]domain.ArticleEntity, error) {
builder := sqlbuilder.NewSelectBuilder() builder := sqlbuilder.NewSelectBuilder()
builder.Select("*") builder.Select("*")
builder.From("articles") builder.From("articles")
builder.Limit(limit) builder.Limit(limit)
query, args := builder.Build() query, args := builder.Build()
rows, err := ar.conn.Query(query, args...) rows, err := ar.conn.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
return []domain.ArticleEntity{}, err return []domain.ArticleEntity{}, err
} }
@ -93,16 +104,16 @@ func (ar ArticleRepository) ListTop(limit int) ([]domain.ArticleEntity, error) {
return data, nil return data, nil
} }
func (ar ArticleRepository) ListByPage(page, limit int) ([]domain.ArticleEntity, error) { func (ar ArticleRepository) ListByPage(ctx context.Context, page, limit int) ([]domain.ArticleEntity, error) {
builder := sqlbuilder.NewSelectBuilder() builder := sqlbuilder.NewSelectBuilder()
builder.Select("*") builder.Select("*")
builder.From("articles") builder.From("articles")
builder.OrderBy("pubdate desc") builder.OrderBy(ArticleOrderByPublishDateDesc)
builder.Offset(page * limit) builder.Offset(page * limit)
builder.Limit(limit) builder.Limit(limit)
query, args := builder.Build() query, args := builder.Build()
rows, err := ar.conn.Query(query, args...) rows, err := ar.conn.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
return []domain.ArticleEntity{}, err return []domain.ArticleEntity{}, err
} }
@ -115,7 +126,7 @@ func (ar ArticleRepository) ListByPage(page, limit int) ([]domain.ArticleEntity,
return data, nil return data, nil
} }
func (ar ArticleRepository) ListByPublishDate(page, limit int, orderBy string) ([]domain.ArticleEntity, error) { func (ar ArticleRepository) ListByPublishDate(ctx context.Context, page, limit int, orderBy string) ([]domain.ArticleEntity, error) {
builder := sqlbuilder.NewSelectBuilder() builder := sqlbuilder.NewSelectBuilder()
builder.Select("*") builder.Select("*")
builder.From("articles") builder.From("articles")
@ -126,7 +137,7 @@ func (ar ArticleRepository) ListByPublishDate(page, limit int, orderBy string) (
builder.Limit(limit) builder.Limit(limit)
query, args := builder.Build() query, args := builder.Build()
rows, err := ar.conn.Query(query, args...) rows, err := ar.conn.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
return []domain.ArticleEntity{}, err return []domain.ArticleEntity{}, err
} }
@ -138,7 +149,7 @@ func (ar ArticleRepository) ListByPublishDate(page, limit int, orderBy string) (
return data, nil return data, nil
} }
func (ar ArticleRepository) ListBySource(page, limit int, orderBy string) ([]domain.ArticleEntity, error) { func (ar ArticleRepository) ListBySource(ctx context.Context, page, limit, sourceId int, orderBy string) ([]domain.ArticleEntity, error) {
builder := sqlbuilder.NewSelectBuilder() builder := sqlbuilder.NewSelectBuilder()
builder.Select("*") builder.Select("*")
builder.From("articles") builder.From("articles")
@ -146,11 +157,14 @@ func (ar ArticleRepository) ListBySource(page, limit int, orderBy string) ([]dom
if orderBy != "" { if orderBy != "" {
builder.OrderBy(orderBy) builder.OrderBy(orderBy)
} }
builder.Where(
builder.Equal("SourceId", sourceId),
)
builder.Offset(50) builder.Offset(50)
builder.Limit(page * limit) builder.Limit(page * limit)
query, args := builder.Build() query, args := builder.Build()
rows, err := ar.conn.Query(query, args...) rows, err := ar.conn.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
return []domain.ArticleEntity{}, err return []domain.ArticleEntity{}, err
} }
@ -162,7 +176,7 @@ func (ar ArticleRepository) ListBySource(page, limit int, orderBy string) ([]dom
return data, nil return data, nil
} }
func (ar ArticleRepository) Create(sourceId int64, tags, title, url, thumbnailUrl, description, authorName, authorImageUrl string, pubDate time.Time, isVideo bool) (int64, error) { 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() dt := time.Now()
queryBuilder := sqlbuilder.NewInsertBuilder() queryBuilder := sqlbuilder.NewInsertBuilder()
queryBuilder.InsertInto("articles") queryBuilder.InsertInto("articles")
@ -170,7 +184,7 @@ func (ar ArticleRepository) Create(sourceId int64, tags, title, url, thumbnailUr
queryBuilder.Values(dt, dt, timeZero, sourceId, tags, title, url, pubDate, isVideo, thumbnailUrl, description, authorName, authorImageUrl) queryBuilder.Values(dt, dt, timeZero, sourceId, tags, title, url, pubDate, isVideo, thumbnailUrl, description, authorName, authorImageUrl)
query, args := queryBuilder.Build() query, args := queryBuilder.Build()
_, err := ar.conn.Exec(query, args...) _, err := ar.conn.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
return 0, err return 0, err
} }

View File

@ -1,6 +1,7 @@
package repository_test package repository_test
import ( import (
"context"
"testing" "testing"
"time" "time"
@ -21,7 +22,7 @@ func TestCreateArticle(t *testing.T) {
defer db.Close() defer db.Close()
r := repository.NewArticleRepository(db) r := repository.NewArticleRepository(db)
created, err := r.Create(1, "", "unit test", articleFakeDotCom, "", "testing", "", "", time.Now(), false) created, err := r.Create(context.Background(), 1, "", "unit test", articleFakeDotCom, "", "testing", "", "", time.Now(), false)
if err != nil { if err != nil {
t.Log(err) t.Log(err)
t.FailNow() t.FailNow()
@ -48,7 +49,7 @@ func TestArticleByUrl(t *testing.T) {
t.FailNow() t.FailNow()
} }
article, err := r.GetByUrl(articleFakeDotCom) article, err := r.GetByUrl(context.Background(), articleFakeDotCom)
if err != nil { if err != nil {
t.Log(err) t.Log(err)
t.FailNow() t.FailNow()
@ -73,7 +74,7 @@ func TestPullingMultipleArticlesWithLimit(t *testing.T) {
insertFakeArticles(r, "u3", 0) insertFakeArticles(r, "u3", 0)
insertFakeArticles(r, "u4", 0) insertFakeArticles(r, "u4", 0)
items, err := r.ListTop(3) items, err := r.ListTop(context.Background(), 3)
if err != nil { if err != nil {
t.Log(err) t.Log(err)
t.FailNow() t.FailNow()
@ -98,7 +99,7 @@ func TestPullingMultipleArticlesWithPaging(t *testing.T) {
insertFakeArticles(r, "u3", 0) insertFakeArticles(r, "u3", 0)
insertFakeArticles(r, "u4", 0) insertFakeArticles(r, "u4", 0)
items, err := r.ListByPage(2, 1) items, err := r.ListByPage(context.Background(), 2, 1)
if err != nil { if err != nil {
t.Log(err) t.Log(err)
t.FailNow() t.FailNow()
@ -125,7 +126,7 @@ func TestPullingByPublishDate(t *testing.T) {
insertFakeArticles(r, "u1", -1) insertFakeArticles(r, "u1", -1)
insertFakeArticles(r, "u1", -2) insertFakeArticles(r, "u1", -2)
items, err := r.ListByPublishDate(0, 2, repository.ArticleOrderByPublishDateDesc) items, err := r.ListByPublishDate(context.Background(), 0, 2, repository.ArticleOrderByPublishDateDesc)
if err != nil { if err != nil {
t.Log(err) t.Log(err)
t.FailNow() t.FailNow()
@ -147,7 +148,7 @@ func TestPullingByPublishDate(t *testing.T) {
func insertFakeArticles(r repository.ArticleRepository, title string, daysOld int) error { func insertFakeArticles(r repository.ArticleRepository, title string, daysOld int) error {
pubDate := time.Now().AddDate(0,0, daysOld) pubDate := time.Now().AddDate(0,0, daysOld)
_, err := r.Create(1, "", title, articleFakeDotCom, "", "testing", "", "", pubDate, false) _, err := r.Create(context.Background(), 1, "", title, articleFakeDotCom, "", "testing", "", "", pubDate, false)
if err != nil { if err != nil {
return err return err
} }

View File

@ -9,6 +9,19 @@ import (
"github.com/huandu/go-sqlbuilder" "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 { type discordWebHookRepository struct {
conn *sql.DB conn *sql.DB
} }

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

@ -9,6 +9,20 @@ import (
"github.com/huandu/go-sqlbuilder" "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 { type sourceRepository struct {
conn *sql.DB conn *sql.DB
} }

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

@ -1,6 +1,25 @@
package services package services
type RepositoryService struct { 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

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