Features/delete source and first dto (#36)

* updated db, added dto for ListSources, and added delete source

* updated from model > models

* updated to models

* sources now sends back a standard message

* updated subscription routes to have beter logid and swagger details

* moved the dto objects back to modles given they are not bound to the database

* cleaned up how we return the error

* cleaned up swag and updated models to take from the base apistatusmodel. less human errors this way

* cleaned up swag and updated models

* swag updated

* updated queue to return a router and also renamed it as it will hold all queue info later on

* removed config tag

* added subscription details route

* article routes have been moved to support dto

* updated discordwebhooks to use dto

* updated discordwebhookqueue to return details on the items via dto

* removed the example routes

* updated sources to use dto

* subscriptions moved to dto

* generated swag
This commit is contained in:
James Tombleson 2023-01-22 10:12:55 -08:00 committed by GitHub
parent a2147294d5
commit ada453e08a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 3423 additions and 1351 deletions

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
// sqlc v1.16.0
package database

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
// sqlc v1.16.0
package database

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
// sqlc v1.16.0
// source: query.sql
package database

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,239 @@
basePath: /api
definitions:
models.ArticleDetailsDto:
properties:
authorImage:
type: string
authorName:
type: string
description:
type: string
id:
type: string
pubdate:
type: string
source:
$ref: '#/definitions/models.SourceDto'
tags:
items:
type: string
type: array
thumbnail:
type: string
title:
type: string
url:
type: string
video:
type: string
videoHeight:
type: integer
videoWidth:
type: integer
type: object
models.ArticleDto:
properties:
authorImage:
type: string
authorName:
type: string
description:
type: string
id:
type: string
pubdate:
type: string
sourceid:
type: string
tags:
items:
type: string
type: array
thumbnail:
type: string
title:
type: string
url:
type: string
video:
type: string
videoHeight:
type: integer
videoWidth:
type: integer
type: object
models.DiscordQueueDetailsDto:
properties:
article:
$ref: '#/definitions/models.ArticleDetailsDto'
id:
type: string
type: object
models.DiscordWebHooksDto:
properties:
ID:
type: string
channel:
type: string
enabled:
type: boolean
server:
type: string
url:
type: string
type: object
models.SourceDto:
properties:
deleted:
type: boolean
enabled:
type: boolean
id:
type: string
name:
type: string
site:
type: string
source:
type: string
tags:
items:
type: string
type: array
type:
type: string
url:
type: string
value:
type: string
type: object
models.SubscriptionDetailsDto:
properties:
discordwebhook:
$ref: '#/definitions/models.DiscordWebHooksDto'
id:
type: string
source:
$ref: '#/definitions/models.SourceDto'
type: object
models.SubscriptionDto:
properties:
discordwebhookid:
type: string
id:
type: string
sourceid:
type: string
type: object
routes.ApiError:
properties:
message:
type: string
status:
type: integer
type: object
routes.ArticleDetailsResult:
properties:
message:
type: string
payload:
$ref: '#/definitions/models.ArticleDetailsDto'
status:
type: integer
type: object
routes.ArticleGetResults:
properties:
message:
type: string
payload:
$ref: '#/definitions/models.ArticleDto'
status:
type: integer
type: object
routes.ArticlesListResults:
properties:
message:
type: string
payload:
items:
$ref: '#/definitions/models.ArticleDto'
type: array
status:
type: integer
type: object
routes.GetDiscordWebhook:
properties:
message:
type: string
payload:
$ref: '#/definitions/models.DiscordWebHooksDto'
status:
type: integer
type: object
routes.GetSource:
properties:
message:
type: string
payload:
$ref: '#/definitions/models.SourceDto'
status:
type: integer
type: object
routes.ListDiscordWebHooksQueueResults:
properties:
message:
type: string
payload:
items:
$ref: '#/definitions/models.DiscordQueueDetailsDto'
type: array
status:
type: integer
type: object
routes.ListDiscordWebhooks:
properties:
message:
type: string
payload:
items:
$ref: '#/definitions/models.DiscordWebHooksDto'
type: array
status:
type: integer
type: object
routes.ListSources:
properties:
message:
type: string
payload:
items:
$ref: '#/definitions/models.SourceDto'
type: array
status:
type: integer
type: object
routes.ListSubscriptionDetails:
properties:
message:
type: string
payload:
items:
$ref: '#/definitions/models.SubscriptionDetailsDto'
type: array
status:
type: integer
type: object
routes.ListSubscriptions:
properties:
message:
type: string
payload:
items:
$ref: '#/definitions/models.SubscriptionDto'
type: array
status:
type: integer
type: object
info:
contact: {}
title: NewsBot collector
@ -8,7 +243,11 @@ paths:
get:
produces:
- application/json
responses: {}
responses:
"200":
description: OK
schema:
$ref: '#/definitions/routes.ArticlesListResults'
summary: Lists the top 50 records
tags:
- Articles
@ -17,15 +256,37 @@ paths:
parameters:
- description: uuid
in: path
name: id
name: ID
required: true
type: string
produces:
- application/json
responses: {}
responses:
"200":
description: OK
schema:
$ref: '#/definitions/routes.ArticleGetResults'
summary: Returns an article based on defined ID.
tags:
- Articles
/articles/{ID}/details:
get:
parameters:
- description: uuid
in: path
name: ID
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/routes.ArticleDetailsResult'
summary: Returns an article and source based on defined ID.
tags:
- Articles
/articles/by/sourceid:
get:
parameters:
@ -36,184 +297,15 @@ paths:
type: string
produces:
- application/json
responses: {}
responses:
"200":
description: OK
schema:
$ref: '#/definitions/routes.ArticlesListResults'
summary: Finds the articles based on the SourceID provided. Returns the top
50.
tags:
- Articles
/articles/by/tag:
get:
parameters:
- description: Tag name
in: query
name: tag
required: true
type: string
produces:
- application/json
responses: {}
summary: Finds the articles based on the SourceID provided. Returns the top
50.
tags:
- Articles
/config/sources:
get:
produces:
- application/json
responses: {}
summary: Lists the top 50 records
tags:
- Config
- Source
/config/sources/{id}:
get:
parameters:
- description: uuid
in: path
name: id
required: true
type: string
produces:
- application/json
responses: {}
summary: Returns a single entity by ID
tags:
- Config
- Source
post:
parameters:
- description: id
in: path
name: id
required: true
type: string
responses: {}
summary: Marks a source as deleted based on its ID value.
tags:
- Source
/config/sources/{id}/disable:
post:
parameters:
- description: id
in: path
name: id
required: true
type: string
responses: {}
summary: Disables a source from processing.
tags:
- Config
- Source
/config/sources/{id}/enable:
post:
parameters:
- description: id
in: path
name: id
required: true
type: string
responses: {}
summary: Enables a source to continue processing.
tags:
- Config
- Source
/config/sources/by/source:
get:
parameters:
- description: Source Name
in: query
name: source
required: true
type: string
produces:
- application/json
responses: {}
summary: 'Lists the top 50 records based on the name given. Example: reddit'
tags:
- Config
- Source
/config/sources/by/sourceAndName:
get:
parameters:
- description: dadjokes
in: query
name: name
required: true
type: string
- description: reddit
in: query
name: source
required: true
type: string
produces:
- application/json
responses: {}
summary: Returns a single entity by ID
tags:
- Config
- Source
/config/sources/new/reddit:
post:
parameters:
- description: name
in: query
name: name
required: true
type: string
- description: url
in: query
name: url
required: true
type: string
responses: {}
summary: Creates a new reddit source to monitor.
tags:
- Config
- Source
- Reddit
/config/sources/new/twitch:
post:
parameters:
- description: name
in: query
name: name
required: true
type: string
responses: {}
summary: Creates a new twitch source to monitor.
tags:
- Config
- Source
- Twitch
/config/sources/new/youtube:
post:
parameters:
- description: name
in: query
name: name
required: true
type: string
- description: url
in: query
name: url
required: true
type: string
responses: {}
summary: Creates a new youtube source to monitor.
tags:
- Config
- Source
- YouTube
/discord/queue:
get:
produces:
- application/json
responses: {}
summary: Returns the top 100 entries from the queue to be processed.
tags:
- Debug
- Discord
- Queue
/discord/webhooks:
get:
produces:
@ -221,10 +313,9 @@ paths:
responses: {}
summary: Returns the top 100 entries from the queue to be processed.
tags:
- Config
- Discord
- Webhook
/discord/webhooks/{id}:
/discord/webhooks/{ID}:
delete:
parameters:
- description: id
@ -235,9 +326,35 @@ paths:
responses: {}
summary: Deletes a record by ID.
tags:
- Config
- Discord
- Webhook
/discord/webhooks/{ID}/disable:
post:
parameters:
- description: id
in: path
name: id
required: true
type: string
responses: {}
summary: Disables a Webhook from being used.
tags:
- Discord
- Webhook
/discord/webhooks/{ID}/enable:
post:
parameters:
- description: id
in: path
name: id
required: true
type: string
responses: {}
summary: Enables a source to continue processing.
tags:
- Discord
- Webhook
/discord/webhooks/{id}:
get:
parameters:
- description: id
@ -247,10 +364,13 @@ paths:
type: string
produces:
- application/json
responses: {}
responses:
"200":
description: OK
schema:
$ref: '#/definitions/routes.GetDiscordWebhook'
summary: Returns the top 100 entries from the queue to be processed.
tags:
- Config
- Discord
- Webhook
patch:
@ -263,35 +383,6 @@ paths:
responses: {}
summary: Updates a valid discord webhook ID based on the body given.
tags:
- Config
- Discord
- Webhook
/discord/webhooks/{id}/disable:
post:
parameters:
- description: id
in: path
name: id
required: true
type: string
responses: {}
summary: Disables a Webhook from being used.
tags:
- Config
- Discord
- Webhook
/discord/webhooks/{id}/enable:
post:
parameters:
- description: id
in: path
name: id
required: true
type: string
responses: {}
summary: Enables a source to continue processing.
tags:
- Config
- Discord
- Webhook
/discord/webhooks/by/serverAndChannel:
@ -309,10 +400,13 @@ paths:
type: string
produces:
- application/json
responses: {}
responses:
"200":
description: OK
schema:
$ref: '#/definitions/routes.ListDiscordWebhooks'
summary: Returns all the known web hooks based on the Server and Channel given.
tags:
- Config
- Discord
- Webhook
/discord/webhooks/new:
@ -336,62 +430,229 @@ paths:
responses: {}
summary: Creates a new record for a discord web hook to post data to.
tags:
- Config
- Discord
- Webhook
/hello/{who}:
/queue/discord/webhooks:
get:
produces:
- application/json
responses:
"200":
description: ok
schema:
$ref: '#/definitions/routes.ListDiscordWebHooksQueueResults'
summary: Returns the top 100 entries from the queue to be processed.
tags:
- Queue
/sources:
get:
produces:
- application/json
responses:
"200":
description: ok
schema:
$ref: '#/definitions/routes.ListSources'
"400":
description: Unable to reach SQL or Data problems
schema:
$ref: '#/definitions/routes.ApiError'
summary: Lists the top 50 records
tags:
- Source
/sources/{id}:
get:
parameters:
- description: Who
- description: uuid
in: path
name: who
required: true
type: string
produces:
- text/plain
responses: {}
summary: Responds back with "Hello x" depending on param passed in.
tags:
- Debug
/helloworld:
get:
produces:
- text/plain
responses: {}
summary: Responds back with "Hello world!"
tags:
- Debug
/ping:
get:
produces:
- text/plain
responses: {}
summary: Sends back "pong". Good to test with.
tags:
- Debug
/settings/{key}:
get:
parameters:
- description: Settings Key value
in: path
name: key
name: id
required: true
type: string
produces:
- application/json
responses: {}
summary: Returns a object based on the Key that was given.
responses:
"200":
description: ok
schema:
$ref: '#/definitions/routes.GetSource'
"204":
description: No record found.
schema:
$ref: '#/definitions/routes.ApiError'
"400":
description: Unable to query SQL.
schema:
$ref: '#/definitions/routes.ApiError'
"500":
description: Failed to process data from SQL.
schema:
$ref: '#/definitions/routes.ApiError'
summary: Returns a single entity by ID
tags:
- Settings
- Source
post:
parameters:
- description: id
in: path
name: id
required: true
type: string
responses: {}
summary: Marks a source as deleted based on its ID value.
tags:
- Source
/sources/{id}/disable:
post:
parameters:
- description: id
in: path
name: id
required: true
type: string
responses: {}
summary: Disables a source from processing.
tags:
- Source
/sources/{id}/enable:
post:
parameters:
- description: id
in: path
name: id
required: true
type: string
responses: {}
summary: Enables a source to continue processing.
tags:
- Source
/sources/by/source:
get:
parameters:
- description: Source Name
in: query
name: source
required: true
type: string
produces:
- application/json
responses:
"200":
description: ok
schema:
$ref: '#/definitions/routes.ListSources'
"400":
description: Unable to query SQL.
schema:
$ref: '#/definitions/routes.ApiError'
"500":
description: Problems with data.
schema:
$ref: '#/definitions/routes.ApiError'
summary: 'Lists the top 50 records based on the name given. Example: reddit'
tags:
- Source
/sources/by/sourceAndName:
get:
parameters:
- description: dadjokes
in: query
name: name
required: true
type: string
- description: reddit
in: query
name: source
required: true
type: string
produces:
- application/json
responses:
"200":
description: ok
schema:
$ref: '#/definitions/routes.GetSource'
"204":
description: No record found.
schema:
$ref: '#/definitions/routes.ApiError'
"400":
description: Unable to query SQL.
schema:
$ref: '#/definitions/routes.ApiError'
"500":
description: Failed to process data from SQL.
schema:
$ref: '#/definitions/routes.ApiError'
summary: Returns a single entity by ID
tags:
- Source
/sources/new/reddit:
post:
parameters:
- description: name
in: query
name: name
required: true
type: string
- description: url
in: query
name: url
required: true
type: string
responses: {}
summary: Creates a new reddit source to monitor.
tags:
- Source
/sources/new/twitch:
post:
parameters:
- description: name
in: query
name: name
required: true
type: string
responses: {}
summary: Creates a new twitch source to monitor.
tags:
- Source
/sources/new/youtube:
post:
parameters:
- description: name
in: query
name: name
required: true
type: string
- description: url
in: query
name: url
required: true
type: string
responses: {}
summary: Creates a new youtube source to monitor.
tags:
- Source
/subscriptions:
get:
produces:
- application/json
responses: {}
responses:
"200":
description: ok
schema:
$ref: '#/definitions/routes.ListSubscriptions'
"400":
description: Unable to reach SQL.
schema:
$ref: '#/definitions/routes.ApiError'
"500":
description: Failed to process data from SQL.
schema:
$ref: '#/definitions/routes.ApiError'
summary: Returns the top 100 entries from the queue to be processed.
tags:
- Subscription
/subscriptions/byDiscordId:
/subscriptions/by/SourceId:
get:
parameters:
- description: id
@ -401,11 +662,15 @@ paths:
type: string
produces:
- application/json
responses: {}
responses:
"200":
description: ok
schema:
$ref: '#/definitions/routes.ListSubscriptions'
summary: Returns the top 100 entries from the queue to be processed.
tags:
- Subscription
/subscriptions/bySourceId:
/subscriptions/by/discordId:
get:
parameters:
- description: id
@ -415,26 +680,47 @@ paths:
type: string
produces:
- application/json
responses: {}
responses:
"200":
description: ok
schema:
$ref: '#/definitions/routes.ListSubscriptions'
"400":
description: Unable to reach SQL or Data problems
schema:
$ref: '#/definitions/routes.ApiError'
"500":
description: Data problems
schema:
$ref: '#/definitions/routes.ApiError'
summary: Returns the top 100 entries from the queue to be processed.
tags:
- Subscription
/subscriptions/details:
get:
produces:
- application/json
responses:
"200":
description: ok
schema:
$ref: '#/definitions/routes.ListSubscriptionDetails'
summary: Returns the top 50 entries with full deatils on the source and output.
tags:
- Subscription
/subscriptions/discord/webhook/delete:
delete:
parameters:
- description: Id
- description: id
in: query
name: Id
name: id
required: true
type: string
responses: {}
summary: Removes a Discord WebHook Subscription based on the Subscription ID.
tags:
- Config
- Source
- Discord
- Subscription
/subscriptions/new/discordwebhook:
/subscriptions/discord/webhook/new:
post:
parameters:
- description: discordWebHookId

View File

@ -1,4 +1,4 @@
package model
package models
import (
"time"

View File

@ -1,4 +1,4 @@
package model
package models
import (
"time"

129
domain/models/dto.go Normal file
View File

@ -0,0 +1,129 @@
package models
import (
"strings"
"time"
"github.com/google/uuid"
"github.com/jtom38/newsbot/collector/database"
)
type ArticleDto struct {
ID uuid.UUID `json:"id"`
Source uuid.UUID `json:"sourceid"`
Tags []string `json:"tags"`
Title string `json:"title"`
Url string `json:"url"`
Pubdate time.Time `json:"pubdate"`
Video string `json:"video"`
Videoheight int32 `json:"videoHeight"`
Videowidth int32 `json:"videoWidth"`
Thumbnail string `json:"thumbnail"`
Description string `json:"description"`
Authorname string `json:"authorName"`
Authorimage string `json:"authorImage"`
}
type ArticleDetailsDto struct {
ID uuid.UUID `json:"id"`
Source SourceDto `json:"source"`
Tags []string `json:"tags"`
Title string `json:"title"`
Url string `json:"url"`
Pubdate time.Time `json:"pubdate"`
Video string `json:"video"`
Videoheight int32 `json:"videoHeight"`
Videowidth int32 `json:"videoWidth"`
Thumbnail string `json:"thumbnail"`
Description string `json:"description"`
Authorname string `json:"authorName"`
Authorimage string `json:"authorImage"`
}
type DiscordWebHooksDto struct {
ID uuid.UUID `json:"ID"`
Url string `json:"url"`
Server string `json:"server"`
Channel string `json:"channel"`
Enabled bool `json:"enabled"`
}
func ConvertToDiscordWebhookDto(i database.Discordwebhook) DiscordWebHooksDto {
return DiscordWebHooksDto{
ID: i.ID,
Url: i.Url,
Server: i.Server,
Channel: i.Channel,
Enabled: i.Enabled,
}
}
type SourceDto struct {
ID uuid.UUID `json:"id"`
Site string `json:"site"`
Name string `json:"name"`
Source string `json:"source"`
Type string `json:"type"`
Value string `json:"value"`
Enabled bool `json:"enabled"`
Url string `json:"url"`
Tags []string `json:"tags"`
Deleted bool `json:"deleted"`
}
func ConvertToSourceDto(i database.Source) SourceDto {
var deleted bool
if !i.Deleted.Valid {
deleted = true
}
return SourceDto{
ID: i.ID,
Site: i.Site,
Name: i.Name,
Source: i.Source,
Type: i.Type,
Value: i.Value.String,
Enabled: i.Enabled,
Url: i.Url,
Tags: splitTags(i.Tags),
Deleted: deleted,
}
}
type DiscordQueueDto struct {
ID uuid.UUID `json:"id"`
Articleid uuid.UUID `json:"articleId"`
}
type DiscordQueueDetailsDto struct {
ID uuid.UUID `json:"id"`
Article ArticleDetailsDto `json:"article"`
}
type SubscriptionDto struct {
ID uuid.UUID `json:"id"`
DiscordWebhookId uuid.UUID `json:"discordwebhookid"`
SourceId uuid.UUID `json:"sourceid"`
}
func ConvertToSubscriptionDto(i database.Subscription) SubscriptionDto {
c := SubscriptionDto{
ID: i.ID,
DiscordWebhookId: i.Discordwebhookid,
SourceId: i.Sourceid,
}
return c
}
type SubscriptionDetailsDto struct {
ID uuid.UUID `json:"id"`
Source SourceDto `json:"source"`
DiscordWebHook DiscordWebHooksDto `json:"discordwebhook"`
}
func splitTags(t string) []string {
items := strings.Split(t, ", ")
return items
}

View File

@ -1,4 +1,4 @@
package model
package models
// This is the root Json object. It does not contain data that we care about though.
type RedditJsonContent struct {

115
dto/articles.go Normal file
View File

@ -0,0 +1,115 @@
// The converter package lives between the database calls and the API calls.
// This way if any new methods like RPC calls are added later, the API does not need to be reworked as much
package dto
import (
"context"
"strings"
"github.com/google/uuid"
"github.com/jtom38/newsbot/collector/database"
"github.com/jtom38/newsbot/collector/domain/models"
)
type DtoClient struct {
db *database.Queries
}
func NewDtoClient(db *database.Queries) DtoClient {
return DtoClient{
db: db,
}
}
func (c DtoClient) ListArticles(ctx context.Context, limit int) ([]models.ArticleDto, error) {
var res []models.ArticleDto
a, err := c.db.ListArticles(ctx, int32(limit))
if err != nil {
return res, err
}
for _, article := range a {
res = append(res, c.convertArticle(article))
}
return res, nil
}
func (c DtoClient) GetArticle(ctx context.Context, ID uuid.UUID) (models.ArticleDto, error) {
a, err := c.db.GetArticleByID(ctx, ID)
if err != nil {
return models.ArticleDto{}, err
}
return c.convertArticle(a), nil
}
func (c DtoClient) GetArticleDetails(ctx context.Context, ID uuid.UUID) (models.ArticleDetailsDto, error) {
a, err := c.db.GetArticleByID(ctx, ID)
if err != nil {
return models.ArticleDetailsDto{}, err
}
s, err := c.db.GetSourceByID(ctx, a.Sourceid)
if err != nil {
return models.ArticleDetailsDto{}, err
}
res := c.convertArticleDetails(a, s)
return res, nil
}
func (c DtoClient) GetArticlesBySourceId(ctx context.Context, SourceID uuid.UUID) ([]models.ArticleDto, error) {
var res []models.ArticleDto
a, err := c.db.GetArticlesBySourceId(ctx, SourceID)
if err != nil {
return res, err
}
for _, article := range a {
res = append(res, c.convertArticle(article))
}
return res, nil
}
func (c DtoClient) convertArticle(i database.Article) models.ArticleDto {
return models.ArticleDto{
ID: i.ID,
Source: i.Sourceid,
Tags: c.SplitTags(i.Tags),
Title: i.Title,
Url: i.Url,
Pubdate: i.Pubdate,
Video: i.Video.String,
Videoheight: i.Videoheight,
Videowidth: i.Videoheight,
Thumbnail: i.Thumbnail,
Description: i.Description,
Authorname: i.Authorname.String,
Authorimage: i.Authorimage.String,
}
}
func (c DtoClient) convertArticleDetails(i database.Article, s database.Source) models.ArticleDetailsDto {
return models.ArticleDetailsDto{
ID: i.ID,
Source: c.ConvertToSource(s),
Tags: c.SplitTags(i.Tags),
Title: i.Title,
Url: i.Url,
Pubdate: i.Pubdate,
Video: i.Video.String,
Videoheight: i.Videoheight,
Videowidth: i.Videoheight,
Thumbnail: i.Thumbnail,
Description: i.Description,
Authorname: i.Authorname.String,
Authorimage: i.Authorimage.String,
}
}
func (c DtoClient) SplitTags(t string) []string {
return strings.Split(t, ", ")
}

63
dto/discordwebhooks.go Normal file
View File

@ -0,0 +1,63 @@
package dto
import (
"context"
"github.com/google/uuid"
"github.com/jtom38/newsbot/collector/database"
"github.com/jtom38/newsbot/collector/domain/models"
)
func (c DtoClient) ListDiscordWebHooks(ctx context.Context, total int32) ([]models.DiscordWebHooksDto, error) {
var res []models.DiscordWebHooksDto
items, err := c.db.ListDiscordWebhooks(ctx, total)
if err != nil {
return res, nil
}
for _, item := range items {
res = append(res, c.ConvertDiscordWebhook(item))
}
return res, nil
}
func (c DtoClient) GetDiscordWebhook(ctx context.Context, id uuid.UUID) (models.DiscordWebHooksDto, error) {
var res models.DiscordWebHooksDto
item, err := c.db.GetDiscordWebHooksByID(ctx, id)
if err != nil {
return res, err
}
return c.ConvertDiscordWebhook(item), nil
}
func (c DtoClient) GetDiscordWebHookByServerAndChannel(ctx context.Context, server, channel string) ([]models.DiscordWebHooksDto, error) {
var res []models.DiscordWebHooksDto
items, err := c.db.GetDiscordWebHooksByServerAndChannel(ctx, database.GetDiscordWebHooksByServerAndChannelParams{
Server: server,
Channel: channel,
})
if err != nil {
return res, err
}
for _, item := range items {
res = append(res, c.ConvertDiscordWebhook(item))
}
return res, nil
}
func (c DtoClient) ConvertDiscordWebhook(i database.Discordwebhook) models.DiscordWebHooksDto {
return models.DiscordWebHooksDto{
ID: i.ID,
Url: i.Url,
Server: i.Server,
Channel: i.Channel,
Enabled: i.Enabled,
}
}

42
dto/queue.go Normal file
View File

@ -0,0 +1,42 @@
package dto
import (
"context"
"github.com/jtom38/newsbot/collector/database"
"github.com/jtom38/newsbot/collector/domain/models"
)
func (c DtoClient) ListDiscordWebhookQueue(ctx context.Context, limit int32) {
}
func (c DtoClient) ListDiscordWebhookQueueDetails(ctx context.Context, limit int32) ([]models.DiscordQueueDetailsDto, error) {
var res []models.DiscordQueueDetailsDto
items, err := c.db.ListDiscordQueueItems(ctx, limit)
if err != nil {
return res, err
}
for _, item := range items {
article, err := c.GetArticleDetails(ctx, item.ID)
if err != nil {
return res, err
}
res = append(res, models.DiscordQueueDetailsDto{
ID: item.ID,
Article: article,
})
}
return res, nil
}
func (c DtoClient) ConvertToDiscordQueueDto(i database.Discordqueue) models.DiscordQueueDto {
return models.DiscordQueueDto{
ID: i.ID,
Articleid: i.Articleid,
}
}

85
dto/sources.go Normal file
View File

@ -0,0 +1,85 @@
package dto
import (
"context"
"strings"
"github.com/google/uuid"
"github.com/jtom38/newsbot/collector/database"
"github.com/jtom38/newsbot/collector/domain/models"
)
func (c DtoClient) ListSources(ctx context.Context, limit int32) ([]models.SourceDto, error) {
var res []models.SourceDto
items, err := c.db.ListSources(ctx, limit)
if err != nil {
return res, err
}
for _, item := range items {
res = append(res, c.ConvertToSource(item))
}
return res, nil
}
func (c DtoClient) ListSourcesBySource(ctx context.Context, sourceName string) ([]models.SourceDto, error) {
var res []models.SourceDto
items, err := c.db.ListSourcesBySource(ctx, strings.ToLower(sourceName))
if err != nil {
return res, err
}
for _, item := range items {
res = append(res, c.ConvertToSource(item))
}
return res, nil
}
func (c DtoClient) GetSourceById(ctx context.Context, id uuid.UUID) (models.SourceDto, error) {
var res models.SourceDto
item, err := c.db.GetSourceByID(ctx, id)
if err != nil {
return res, err
}
return c.ConvertToSource(item), nil
}
func (c DtoClient) GetSourceByNameAndSource(ctx context.Context, name, source string) (models.SourceDto, error) {
var res models.SourceDto
item, err := c.db.GetSourceByNameAndSource(ctx, database.GetSourceByNameAndSourceParams{
Name: name,
Source: source,
})
if err != nil {
return res, err
}
return c.ConvertToSource(item), nil
}
func (c DtoClient) ConvertToSource(i database.Source) models.SourceDto {
var deleted bool
if !i.Deleted.Valid {
deleted = true
}
return models.SourceDto{
ID: i.ID,
Site: i.Site,
Name: i.Name,
Source: i.Source,
Type: i.Type,
Value: i.Value.String,
Enabled: i.Enabled,
Url: i.Url,
Tags: c.SplitTags(i.Tags),
Deleted: deleted,
}
}

91
dto/subscriptions.go Normal file
View File

@ -0,0 +1,91 @@
package dto
import (
"context"
"github.com/google/uuid"
"github.com/jtom38/newsbot/collector/database"
"github.com/jtom38/newsbot/collector/domain/models"
)
func (c DtoClient) ListSubscriptions(ctx context.Context, limit int32) ([]models.SubscriptionDto, error) {
var res []models.SubscriptionDto
items, err := c.db.ListSubscriptions(ctx, limit)
if err != nil {
return res, err
}
for _, item := range items {
res = append(res, c.ConvertSubscription(item))
}
return res, nil
}
func (c DtoClient) ListSubscriptionDetails(ctx context.Context, limit int32) ([]models.SubscriptionDetailsDto, error) {
var res []models.SubscriptionDetailsDto
items, err := c.ListSubscriptions(ctx, limit)
if err != nil {
return res, err
}
for _, item := range items {
dwh, err := c.GetDiscordWebhook(ctx, item.DiscordWebhookId)
if err != nil {
return res, err
}
source, err := c.GetSourceById(ctx, item.SourceId)
if err != nil {
return res, err
}
res = append(res, models.SubscriptionDetailsDto{
ID: item.ID,
Source: source,
DiscordWebHook: dwh,
})
}
return res, nil
}
func (c DtoClient) ListSubscriptionsByDiscordWebhookId(ctx context.Context, id uuid.UUID) ([]models.SubscriptionDto, error) {
var res []models.SubscriptionDto
items, err := c.db.GetSubscriptionsByDiscordWebHookId(ctx, id)
if err != nil {
return res, err
}
for _, item := range items {
res = append(res, c.ConvertSubscription(item))
}
return res, nil
}
func (c DtoClient) ListSubscriptionsBySourceId(ctx context.Context, id uuid.UUID) ([]models.SubscriptionDto, error) {
var res []models.SubscriptionDto
items, err := c.db.GetSubscriptionsBySourceID(ctx, id)
if err != nil {
return res, err
}
for _, item := range items {
res = append(res, c.ConvertSubscription(item))
}
return res, nil
}
func (c DtoClient) ConvertSubscription(i database.Subscription) models.SubscriptionDto {
return models.SubscriptionDto{
ID: i.ID,
DiscordWebhookId: i.Discordwebhookid,
SourceId: i.Sourceid,
}
}

13
main.go
View File

@ -2,9 +2,11 @@ package main
import (
"context"
"database/sql"
"fmt"
"net/http"
"github.com/jtom38/newsbot/collector/database"
"github.com/jtom38/newsbot/collector/docs"
"github.com/jtom38/newsbot/collector/routes"
"github.com/jtom38/newsbot/collector/services/config"
@ -20,16 +22,23 @@ func main() {
docs.SwaggerInfo.Host = fmt.Sprintf("%v:8081", address)
ctx := context.Background()
db, err := sql.Open("postgres", cfg.GetConfig(config.Sql_Connection_String))
if err != nil {
panic(err)
}
queries := database.New(db)
c := cron.New(ctx)
c.Start()
server := routes.NewServer(ctx)
server := routes.NewServer(ctx, queries)
fmt.Println("API is online and waiting for requests.")
fmt.Printf("API: http://%v:8081/api\r\n", address)
fmt.Printf("Swagger: http://%v:8081/swagger/index.html\r\n", address)
err := http.ListenAndServe(":8081", server.Router)
err = http.ListenAndServe(":8081", server.Router)
if err != nil {
panic(err)
}

View File

@ -6,61 +6,156 @@ import (
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/jtom38/newsbot/collector/domain/models"
)
func (s *Server) 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.GetArticlesBySourceId)
return r
}
type ArticlesListResults struct {
ApiStatusModel
Payload []models.ArticleDto `json:"payload"`
}
// ListArticles
// @Summary Lists the top 50 records
// @Produce application/json
// @Tags Articles
// @Router /articles [get]
// @Success 200 {object} ArticlesListResults "OK"
func (s *Server) listArticles(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
res, err := s.Db.ListArticlesByDate(*s.ctx, 50)
if err != nil {
w.Write([]byte(err.Error()))
panic(err)
p := ArticlesListResults{
ApiStatusModel: ApiStatusModel{
Message: "OK",
StatusCode: http.StatusOK,
},
}
bres, err := json.Marshal(res)
w.Header().Set(HeaderContentType, ApplicationJson)
res, err := s.dto.ListArticles(r.Context(), 50)
if err != nil {
w.Write([]byte(err.Error()))
panic(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
p.Payload = res
bres, err := json.Marshal(p)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(bres)
}
// GetArticleById
type ArticleGetResults struct {
ApiStatusModel
Payload models.ArticleDto `json:"payload"`
}
// GetArticle
// @Summary Returns an article based on defined ID.
// @Param id path string true "uuid"
// @Param ID path string true "uuid"
// @Produce application/json
// @Tags Articles
// @Router /articles/{ID} [get]
func (s *Server) getArticleById(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
// @Success 200 {object} ArticleGetResults "OK"
func (s *Server) getArticle(w http.ResponseWriter, r *http.Request) {
p := ArticleGetResults {
ApiStatusModel: ApiStatusModel{
Message: "OK",
StatusCode: http.StatusOK,
},
}
w.Header().Set(HeaderContentType, ApplicationJson)
id := chi.URLParam(r, "ID")
uuid, err := uuid.Parse(id)
if err != nil {
w.Write([]byte(err.Error()))
panic(err)
s.WriteError(w, err.Error(), http.StatusBadRequest)
return
}
res, err := s.Db.GetArticleByID(*s.ctx, uuid)
res, err := s.dto.GetArticle(r.Context(), uuid)
if err != nil {
w.Write([]byte(err.Error()))
panic(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
bres, err := json.Marshal(res)
p.Payload = res
bres, err := json.Marshal(p)
if err != nil {
w.Write([]byte(err.Error()))
panic(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(bres)
}
type ArticleDetailsResult struct {
ApiStatusModel
Payload models.ArticleDetailsDto `json:"payload"`
}
// GetArticleDetails
// @Summary Returns an article and source based on defined ID.
// @Param ID path string true "uuid"
// @Produce application/json
// @Tags Articles
// @Router /articles/{ID}/details [get]
// @Success 200 {object} ArticleDetailsResult "OK"
func (s *Server) getArticleDetails(w http.ResponseWriter, r *http.Request) {
p := ArticleDetailsResult {
ApiStatusModel: ApiStatusModel{
Message: "OK",
StatusCode: http.StatusOK,
},
}
w.Header().Set(HeaderContentType, ApplicationJson)
id := chi.URLParam(r, "ID")
uuid, err := uuid.Parse(id)
if err != nil {
s.WriteError(w, err.Error(), http.StatusBadRequest)
return
}
res, err := s.dto.GetArticleDetails(r.Context(), uuid)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
p.Payload = res
bres, err := json.Marshal(p)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(bres)
}
type ArticlesBySourceIDResults struct {
ApiStatusModel
Payload []models.ArticleDto `json:"payload"`
}
// TODO add page support
// GetArticlesBySourceID
// @Summary Finds the articles based on the SourceID provided. Returns the top 50.
@ -68,6 +163,7 @@ func (s *Server) getArticleById(w http.ResponseWriter, r *http.Request) {
// @Produce application/json
// @Tags Articles
// @Router /articles/by/sourceid [get]
// @Success 200 {object} ArticlesListResults "OK"
func (s *Server) GetArticlesBySourceId(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
@ -77,56 +173,20 @@ func (s *Server) GetArticlesBySourceId(w http.ResponseWriter, r *http.Request) {
uuid, err := uuid.Parse(_id)
if err != nil {
w.Write([]byte(err.Error()))
panic(err)
s.WriteError(w, err.Error(), http.StatusBadRequest)
return
}
res, err := s.Db.GetNewArticlesBySourceId(*s.ctx, uuid)
//res, err := s.Db.GetArticlesBySourceId(*s.ctx, uuid)
res, err := s.dto.GetArticlesBySourceId(r.Context(), uuid)
if err != nil {
w.Write([]byte(err.Error()))
panic(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
bres, err := json.Marshal(res)
if err != nil {
w.Write([]byte(err.Error()))
panic(err)
}
w.Write(bres)
}
// TODO add page support
// GetArticlesByTag
// @Summary Finds the articles based on the SourceID provided. Returns the top 50.
// @Param tag query string true "Tag name"
// @Produce application/json
// @Tags Articles
// @Router /articles/by/tag [get]
func (s *Server) GetArticlesByTag(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
r.URL.Query()
query := r.URL.Query()
_id := query["tag"][0]
uuid, err := uuid.Parse(_id)
if err != nil {
w.Write([]byte(err.Error()))
panic(err)
}
res, err := s.Db.GetArticlesBySourceId(*s.ctx, uuid)
if err != nil {
w.Write([]byte(err.Error()))
panic(err)
}
bres, err := json.Marshal(res)
if err != nil {
w.Write([]byte(err.Error()))
panic(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(bres)

View File

@ -1,29 +0,0 @@
package routes
import (
"encoding/json"
"net/http"
)
// GetDiscordQueue
// @Summary Returns the top 100 entries from the queue to be processed.
// @Produce application/json
// @Tags Debug, Discord, Queue
// @Router /discord/queue [get]
func (s *Server) GetDiscordQueue(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
res, err := s.Db.ListDiscordQueueItems(*s.ctx, 100)
if err != nil {
w.Write([]byte(err.Error()))
panic(err)
}
bres, err := json.Marshal(res)
if err != nil {
w.Write([]byte(err.Error()))
panic(err)
}
w.Write(bres)
}

View File

@ -1,7 +1,6 @@
package routes
import (
"context"
"encoding/json"
"log"
"net/http"
@ -10,62 +9,106 @@ import (
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/jtom38/newsbot/collector/database"
"github.com/jtom38/newsbot/collector/domain/models"
)
// GetDiscordWebHooks
// @Summary Returns the top 100 entries from the queue to be processed.
// @Produce application/json
// @Tags Config, Discord, Webhook
// @Router /discord/webhooks [get]
func (s *Server) GetDiscordWebHooks(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
func (s Server) DiscordWebHookRouter() http.Handler {
r := chi.NewRouter()
res, err := s.Db.ListDiscordWebhooks(*s.ctx, 100)
if err != nil {
w.Write([]byte(err.Error()))
panic(err)
r.Get("/", s.ListDiscordWebHooks)
r.Post("/new", s.NewDiscordWebHook)
r.Get("/by/serverAndChannel", s.GetDiscordWebHooksByServerAndChannel)
r.Route("/{ID}", func(r chi.Router) {
r.Get("/", s.GetDiscordWebHooksById)
r.Delete("/", s.deleteDiscordWebHook)
r.Post("/disable", s.disableDiscordWebHook)
r.Post("/enable", s.enableDiscordWebHook)
})
return r
}
bres, err := json.Marshal(res)
type ListDiscordWebhooks struct {
ApiStatusModel
Payload []models.DiscordWebHooksDto `json:"payload"`
}
// ListDiscordWebhooks
// @Summary Returns the top 100 entries from the queue to be processed.
// @Produce application/json
// @Tags Discord, Webhook
// @Router /discord/webhooks [get]
func (s *Server) ListDiscordWebHooks(w http.ResponseWriter, r *http.Request) {
p := ListDiscordWebhooks{
ApiStatusModel: ApiStatusModel{
Message: "OK",
StatusCode: http.StatusOK,
},
}
w.Header().Set(HeaderContentType, ApplicationJson)
res, err := s.dto.ListDiscordWebHooks(r.Context(), 50)
if err != nil {
w.Write([]byte(err.Error()))
panic(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
p.Payload = res
bres, err := json.Marshal(p)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(bres)
}
type GetDiscordWebhook struct {
ApiStatusModel
Payload models.DiscordWebHooksDto `json:"payload"`
}
// GetDiscordWebHook
// @Summary Returns the top 100 entries from the queue to be processed.
// @Produce application/json
// @Param id path string true "id"
// @Tags Config, Discord, Webhook
// @Tags Discord, Webhook
// @Router /discord/webhooks/{id} [get]
// @Success 200 {object} GetDiscordWebhook "OK"
func (s *Server) GetDiscordWebHooksById(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
p := GetDiscordWebhook{
ApiStatusModel: ApiStatusModel{
Message: "OK",
StatusCode: http.StatusOK,
},
}
w.Header().Set(HeaderContentType, ApplicationJson)
_id := chi.URLParam(r, "ID")
if _id == "" {
http.Error(w, "id is missing", http.StatusBadRequest)
s.WriteError(w, "id is missing", http.StatusBadRequest)
return
}
uuid, err := uuid.Parse(_id)
if err != nil {
http.Error(w, "unable to parse id value", http.StatusBadRequest)
s.WriteError(w, "unable to parse id value", http.StatusBadRequest)
return
}
res, err := s.Db.GetDiscordWebHooksByID(*s.ctx, uuid)
res, err := s.dto.GetDiscordWebhook(r.Context(), uuid)
if err != nil {
http.Error(w, "no record found", http.StatusBadRequest)
s.WriteError(w, "no record found", http.StatusBadRequest)
return
}
p.Payload = res
bres, err := json.Marshal(res)
bres, err := json.Marshal(p)
if err != nil {
http.Error(w, "unable to convert to json", http.StatusBadRequest)
panic(err)
s.WriteError(w, "unable to convert to json", http.StatusBadRequest)
return
}
w.Write(bres)
@ -76,49 +119,55 @@ func (s *Server) GetDiscordWebHooksById(w http.ResponseWriter, r *http.Request)
// @Produce application/json
// @Param server query string true "Fancy Server"
// @Param channel query string true "memes"
// @Tags Config, Discord, Webhook
// @Tags Discord, Webhook
// @Router /discord/webhooks/by/serverAndChannel [get]
// @Success 200 {object} ListDiscordWebhooks "OK"
func (s *Server) GetDiscordWebHooksByServerAndChannel(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
p := ListDiscordWebhooks{
ApiStatusModel: ApiStatusModel{
Message: "OK",
StatusCode: http.StatusOK,
},
}
w.Header().Set(HeaderContentType, ApplicationJson)
query := r.URL.Query()
_server := query["server"][0]
if _server == "" {
http.Error(w, "ID is missing", http.StatusInternalServerError)
s.WriteError(w, "ID is missing", http.StatusInternalServerError)
return
}
_channel := query["channel"][0]
if _channel == "" {
http.Error(w, "Channel is missing", http.StatusInternalServerError)
s.WriteError(w, "Channel is missing", http.StatusInternalServerError)
return
}
res, err := s.Db.GetDiscordWebHooksByServerAndChannel(context.Background(), database.GetDiscordWebHooksByServerAndChannelParams{
Server: _server,
Channel: _channel,
})
res, err := s.dto.GetDiscordWebHookByServerAndChannel(r.Context(), _server, _channel)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
bres, err := json.Marshal(res)
p.Payload = res
bres, err := json.Marshal(p)
if err != nil {
http.Error(w, "unable to convert to json", http.StatusInternalServerError)
panic(err)
s.WriteError(w, "unable to convert to json", http.StatusInternalServerError)
return
}
w.Write(bres)
}
// NewDiscordWebHook
// @Summary Creates a new record for a discord web hook to post data to.
// @Param url query string true "url"
// @Param server query string true "Server name"
// @Param channel query string true "Channel name"
// @Tags Config, Discord, Webhook
// @Tags Discord, Webhook
// @Router /discord/webhooks/new [post]
func (s *Server) NewDiscordWebHook(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
@ -160,82 +209,84 @@ func (s *Server) NewDiscordWebHook(w http.ResponseWriter, r *http.Request) {
// DisableDiscordWebHooks
// @Summary Disables a Webhook from being used.
// @Param id path string true "id"
// @Tags Config, Discord, Webhook
// @Router /discord/webhooks/{id}/disable [post]
// @Tags Discord, Webhook
// @Router /discord/webhooks/{ID}/disable [post]
func (s *Server) disableDiscordWebHook(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "ID")
uuid, err := uuid.Parse(id)
if err != nil {
log.Panicln(err)
s.WriteError(w, err.Error(), http.StatusBadRequest)
return
}
// Check to make sure we can find the record
_, err = s.Db.GetDiscordWebHooksByID(*s.ctx, uuid)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusBadRequest)
return
}
err = s.Db.DisableDiscordWebHook(*s.ctx, uuid)
if err != nil {
log.Panic(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
}
}
// EnableDiscordWebHook
// @Summary Enables a source to continue processing.
// @Param id path string true "id"
// @Tags Config, Discord, Webhook
// @Router /discord/webhooks/{id}/enable [post]
// @Tags Discord, Webhook
// @Router /discord/webhooks/{ID}/enable [post]
func (s *Server) enableDiscordWebHook(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "ID")
uuid, err := uuid.Parse(id)
if err != nil {
log.Panicln(err)
s.WriteError(w, err.Error(), http.StatusBadRequest)
}
// Check to make sure we can find the record
_, err = s.Db.GetDiscordWebHooksByID(*s.ctx, uuid)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusBadRequest)
}
err = s.Db.EnableDiscordWebHook(*s.ctx, uuid)
if err != nil {
log.Panic(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
}
}
// DeleteDiscordWebHook
// @Summary Deletes a record by ID.
// @Param id path string true "id"
// @Tags Config, Discord, Webhook
// @Router /discord/webhooks/{id} [delete]
// @Tags Discord, Webhook
// @Router /discord/webhooks/{ID} [delete]
func (s *Server) deleteDiscordWebHook(w http.ResponseWriter, r *http.Request) {
//var item model.Sources = model.Sources{}
id := chi.URLParam(r, "ID")
uuid, err := uuid.Parse(id)
if err != nil {
log.Panicln(err)
s.WriteError(w, err.Error(), http.StatusBadRequest)
}
// Check to make sure we can find the record
_, err = s.Db.GetDiscordQueueByID(*s.ctx, uuid)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusBadRequest)
}
// Delete the record
err = s.Db.DeleteDiscordWebHooks(*s.ctx, uuid)
if err != nil {
log.Panic(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
}
}
// UpdateDiscordWebHook
// @Summary Updates a valid discord webhook ID based on the body given.
// @Param id path string true "id"
// @Tags Config, Discord, Webhook
// @Tags Discord, Webhook
// @Router /discord/webhooks/{id} [patch]
func (s *Server) UpdateDiscordWebHook(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "ID")

57
routes/queue.go Normal file
View File

@ -0,0 +1,57 @@
package routes
import (
"encoding/json"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/jtom38/newsbot/collector/domain/models"
)
func (s *Server) GetQueueRouter() http.Handler {
r := chi.NewRouter()
r.Get("/discord/webhooks", s.ListDiscordWebhookQueue)
return r
}
type ListDiscordWebHooksQueueResults struct {
ApiStatusModel
Payload []models.DiscordQueueDetailsDto `json:"payload"`
}
// GetDiscordQueue
// @Summary Returns the top 100 entries from the queue to be processed.
// @Produce application/json
// @Tags Queue
// @Router /queue/discord/webhooks [get]
// @Success 200 {object} ListDiscordWebHooksQueueResults "ok"
func (s *Server) ListDiscordWebhookQueue(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
p := ListDiscordWebHooksQueueResults{
ApiStatusModel: ApiStatusModel{
Message: "OK",
StatusCode: http.StatusOK,
},
}
// Get the raw resp from sql
res, err := s.dto.ListDiscordWebhookQueueDetails(r.Context(), 50)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
p.Payload = res
// convert to json
b, err := json.Marshal(p)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
}

View File

@ -1,50 +0,0 @@
package routes
import (
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
)
func RootRoutes() chi.Router {
app := chi.NewRouter()
app.Route("/", func(r chi.Router) {
r.Get("/helloworld", helloWorld)
r.Get("/ping", ping)
r.Route("/hello/{who}", func(r chi.Router) {
r.Get("/", helloWho)
})
})
return app
}
// HelloWorld
// @Summary Responds back with "Hello world!"
// @Produce plain
// @Tags Debug
// @Router /helloworld [get]
func helloWorld(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World!"))
}
// Ping
// @Summary Sends back "pong". Good to test with.
// @Produce plain
// @Tags Debug
// @Router /ping [get]
func ping(w http.ResponseWriter, r *http.Request) {
msg := "pong"
w.Write([]byte(msg))
}
// HelloWho
// @Summary Responds back with "Hello x" depending on param passed in.
// @Param who path string true "Who"
// @Produce plain
// @Tags Debug
// @Router /hello/{who} [get]
func helloWho(w http.ResponseWriter, r *http.Request) {
msg := fmt.Sprintf("Hello %v", chi.URLParam(r, "who"))
w.Write([]byte(msg))
}

View File

@ -3,8 +3,8 @@ package routes
import (
"context"
"database/sql"
//"net/http"
"encoding/json"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
@ -12,15 +12,23 @@ import (
httpSwagger "github.com/swaggo/http-swagger"
"github.com/jtom38/newsbot/collector/database"
"github.com/jtom38/newsbot/collector/dto"
"github.com/jtom38/newsbot/collector/services/config"
)
type Server struct {
Router *chi.Mux
Db *database.Queries
dto dto.DtoClient
ctx *context.Context
}
const (
HeaderContentType = "Content-Type"
ApplicationJson = "application/json"
)
var (
ErrIdValueMissing string = "id value is missing"
ErrValueNotUuid string = "a value given was expected to be a uuid but was not correct."
@ -28,16 +36,18 @@ var (
ErrUnableToConvertToJson string = "Unable to convert to json"
)
func NewServer(ctx context.Context) *Server {
func NewServer(ctx context.Context, db *database.Queries) *Server {
s := &Server{
ctx: &ctx,
Db: db,
dto: dto.NewDtoClient(db),
}
db, err := openDatabase(ctx)
if err != nil {
panic(err)
}
s.Db = db
//db, err := openDatabase(ctx)
//if err != nil {
// panic(err)
//}
//s.Db = db
s.Router = chi.NewRouter()
s.MountMiddleware()
@ -68,56 +78,37 @@ func (s *Server) MountRoutes() {
httpSwagger.URL("doc.json"), //The url pointing to API definition
))
/* Root Routes */
s.Router.Get("/api/helloworld", helloWorld)
s.Router.Get("/api/hello/{who}", helloWho)
s.Router.Get("/api/ping", ping)
s.Router.Mount("/api/articles", s.GetArticleRouter())
s.Router.Mount("/api/queue", s.GetQueueRouter())
s.Router.Mount("/api/discord/webhooks", s.DiscordWebHookRouter())
/* Article Routes */
s.Router.Get("/api/articles", s.listArticles)
s.Router.Route("/api/articles/{ID}", func(r chi.Router) {
r.Get("/", s.getArticleById)
})
s.Router.Get("/api/articles/by/sourceid", s.GetArticlesBySourceId)
//s.Router.Get("/api/settings", s.getSettings)
/* Discord Queue */
s.Router.Get("/api/discord/queue", s.GetDiscordQueue)
/* Discord WebHooks */
s.Router.Post("/api/discord/webhooks/new", s.NewDiscordWebHook)
s.Router.Get("/api/discord/webhooks", s.GetDiscordWebHooks)
//s.Router.Get("/api/discord/webhooks/byId", s.GetDiscordWebHooksById)
s.Router.Get("/api/discord/webhooks/by/serverAndChannel", s.GetDiscordWebHooksByServerAndChannel)
s.Router.Route("/api/discord/webhooks/{ID}", func(r chi.Router) {
r.Get("/", s.GetDiscordWebHooksById)
r.Delete("/", s.deleteDiscordWebHook)
r.Post("/disable", s.disableDiscordWebHook)
r.Post("/enable", s.enableDiscordWebHook)
})
/* Settings */
s.Router.Get("/api/settings", s.getSettings)
/* Source Routes */
s.Router.Get("/api/config/sources", s.listSources)
s.Router.Get("/api/config/sources/by/source", s.listSourcesBySource)
s.Router.Post("/api/config/sources/new/reddit", s.newRedditSource)
s.Router.Post("/api/config/sources/new/youtube", s.newYoutubeSource)
s.Router.Post("/api/config/sources/new/twitch", s.newTwitchSource)
s.Router.Route("/api/config/sources/{ID}", func(r chi.Router) {
r.Get("/", s.getSources)
r.Delete("/", s.deleteSources)
r.Post("/disable", s.disableSource)
r.Post("/enable", s.enableSource)
//r.Post("/delete", )
})
s.Router.Get("/api/config/sources/by/sourceAndName", s.GetSourceBySourceAndName)
/* Subscriptions */
s.Router.Get("/api/subscriptions", s.ListSubscriptions)
s.Router.Get("/api/subscriptions/byDiscordId", s.GetSubscriptionsByDiscordId)
s.Router.Get("/api/subscriptions/bySourceId", s.GetSubscriptionsBySourceId)
s.Router.Post("/api/subscriptions/new/discordwebhook", s.newDiscordWebHookSubscription)
s.Router.Delete("/api/subscriptions/discord/webhook/delete", s.DeleteDiscordWebHookSubscription)
s.Router.Mount("/api/sources", s.GetSourcesRouter())
s.Router.Mount("/api/subscriptions", s.GetSubscriptionsRouter())
}
type ApiStatusModel struct {
StatusCode int `json:"status"`
Message string `json:"message"`
}
type ApiError struct {
*ApiStatusModel
}
func (s *Server) WriteError(w http.ResponseWriter, errMessage string, HttpStatusCode int) {
e := ApiError{
ApiStatusModel: &ApiStatusModel{
StatusCode: http.StatusInternalServerError,
Message: errMessage,
},
}
b, err := json.Marshal(e)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
w.Write(b)
}

View File

@ -2,43 +2,43 @@ package routes
import (
"encoding/json"
"log"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
)
func (s *Server) getSettings(w http.ResponseWriter, r *http.Request) {
// GetSettings
// @Summary Returns a object based on the Key that was given.
// @Param key path string true "Settings Key value"
// @Produce application/json
// @Tags Settings
// @Router /settings/{key} [get]
func (s *Server) getSettings(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
//var item model.Sources
id := chi.URLParam(r, "ID")
uuid, err := uuid.Parse(id)
if err != nil {
panic(err)
s.WriteError(w, err.Error(), http.StatusBadRequest)
return
}
res, err := s.Db.GetSourceByID(*s.ctx, uuid)
if err != nil {
panic(err)
s.WriteError(w, err.Error(), http.StatusNotFound)
return
}
//itemId := fmt.Sprint(item.ID)
//if id != itemId {
// log.Panicln("Unable to find the requested record. Either unable to access SQL or the record does not exist.")
//}
bResult, err := json.Marshal(res)
if err != nil {
log.Panicln(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(bResult)
}

View File

@ -1,23 +1,55 @@
package routes
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/jtom38/newsbot/collector/database"
"github.com/jtom38/newsbot/collector/domain/models"
)
func (s *Server) GetSourcesRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", s.listSources)
r.Get("/by/source", s.listSourcesBySource)
r.Get("/by/sourceAndName", s.GetSourceBySourceAndName)
r.Post("/new/reddit", s.newRedditSource)
r.Post("/new/youtube", s.newYoutubeSource)
r.Post("/new/twitch", s.newTwitchSource)
r.Route("/{ID}", func(p chi.Router) {
p.Get("/", s.getSources)
p.Delete("/", s.deleteSources)
p.Post("/disable", s.disableSource)
p.Post("/enable", s.enableSource)
})
return r
}
type ListSources struct {
ApiStatusModel
Payload []models.SourceDto `json:"payload"`
}
type GetSource struct {
ApiStatusModel
Payload models.SourceDto `json:"payload"`
}
// ListSources
// @Summary Lists the top 50 records
// @Produce application/json
// @Tags Config, Source
// @Router /config/sources [get]
// @Tags Source
// @Router /sources [get]
// @Success 200 {object} ListSources "ok"
// @Failure 400 {object} ApiError "Unable to reach SQL or Data problems"
func (s *Server) listSources(w http.ResponseWriter, r *http.Request) {
//TODO Add top?
/*
@ -29,35 +61,41 @@ func (s *Server) listSources(w http.ResponseWriter, r *http.Request) {
res, err := s.Db.ListSources(*s.ctx, int32(topInt))
*/
w.Header().Set(HeaderContentType, ApplicationJson)
result := ListSources{
ApiStatusModel: ApiStatusModel{
StatusCode: http.StatusOK,
Message: "OK",
},
}
// Default way of showing all sources
res, err := s.Db.ListSources(*s.ctx, 50)
items, err := s.dto.ListSources(r.Context(), 50)
if err != nil {
http.Error(w, "url is missing a value", http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
var dto []database.SourceDto
for _, item := range res {
dto = append(dto, database.ConvertToSourceDto(item))
}
result.Payload = items
bResult, err := json.Marshal(dto)
bResult, err := json.Marshal(result)
if err != nil {
http.Error(w, "unable to convert to json", http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(bResult)
}
// ListSourcesBySource
// @Summary Lists the top 50 records based on the name given. Example: reddit
// @Param source query string true "Source Name"
// @Produce application/json
// @Tags Config, Source
// @Router /config/sources/by/source [get]
// @Tags Source
// @Router /sources/by/source [get]
// @Success 200 {object} ListSources "ok"
// @Failure 400 {object} ApiError "Unable to query SQL."
// @Failure 500 {object} ApiError "Problems with data."
func (s *Server) listSourcesBySource(w http.ResponseWriter, r *http.Request) {
//TODO Add top?
/*
@ -68,23 +106,33 @@ func (s *Server) listSourcesBySource(w http.ResponseWriter, r *http.Request) {
}
res, err := s.Db.ListSources(*s.ctx, int32(topInt))
*/
w.Header().Set(HeaderContentType, ApplicationJson)
result := ListSources{
ApiStatusModel: ApiStatusModel{
StatusCode: http.StatusOK,
Message: "OK",
},
}
query := r.URL.Query()
_source := query["source"][0]
// Shows the list by Sources.source
res, err := s.Db.ListSourcesBySource(*s.ctx, strings.ToLower(_source))
res, err := s.dto.ListSourcesBySource(r.Context(), _source)
if err != nil {
http.Error(w, "invalid source is missing a value", http.StatusBadRequest)
return
}
bResult, err := json.Marshal(res)
if err != nil {
http.Error(w, "unable to convert to json", http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusBadRequest)
return
}
result.Payload = res
bResult, err := json.Marshal(result)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(bResult)
}
@ -92,29 +140,43 @@ func (s *Server) listSourcesBySource(w http.ResponseWriter, r *http.Request) {
// @Summary Returns a single entity by ID
// @Param id path string true "uuid"
// @Produce application/json
// @Tags Config, Source
// @Router /config/sources/{id} [get]
// @Tags Source
// @Router /sources/{id} [get]
// @Success 200 {object} GetSource "ok"
// @Failure 204 {object} ApiError "No record found."
// @Failure 400 {object} ApiError "Unable to query SQL."
// @Failure 500 {object} ApiError "Failed to process data from SQL."
func (s *Server) getSources(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "ID")
payload := GetSource{
ApiStatusModel: ApiStatusModel{
Message: "OK",
StatusCode: http.StatusOK,
},
}
w.Header().Set(HeaderContentType, ApplicationJson)
id := chi.URLParam(r, "ID")
uuid, err := uuid.Parse(id)
if err != nil {
http.Error(w, "id is not a uuid", http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusBadRequest)
return
}
res, err := s.Db.GetSourceByID(*s.ctx, uuid)
res, err := s.dto.GetSourceById(r.Context(), uuid)
if err != nil {
http.Error(w, "invalid id was given", http.StatusBadRequest)
panic(err)
s.WriteError(w, err.Error(), http.StatusNoContent)
return
}
bResult, err := json.Marshal(res)
payload.Payload = res
bResult, err := json.Marshal(payload)
if err != nil {
log.Panicln(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(bResult)
}
@ -123,37 +185,48 @@ func (s *Server) getSources(w http.ResponseWriter, r *http.Request) {
// @Param name query string true "dadjokes"
// @Param source query string true "reddit"
// @Produce application/json
// @Tags Config, Source
// @Router /config/sources/by/sourceAndName [get]
// @Tags Source
// @Router /sources/by/sourceAndName [get]
// @Success 200 {object} GetSource "ok"
// @Failure 204 {object} ApiError "No record found."
// @Failure 400 {object} ApiError "Unable to query SQL."
// @Failure 500 {object} ApiError "Failed to process data from SQL."
func (s *Server) GetSourceBySourceAndName(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
p := GetSource{
ApiStatusModel: ApiStatusModel{
Message: "OK",
StatusCode: http.StatusOK,
},
}
query := r.URL.Query()
name := query["name"][0]
if name == "" {
http.Error(w, "Parameter 'name' was missing in the query.", http.StatusInternalServerError)
s.WriteError(w, "Parameter 'name' was missing in the query.", http.StatusInternalServerError)
return
}
source := query["source"][0]
if source == "" {
http.Error(w, "The parameter 'source' was missing in the query.", http.StatusInternalServerError)
s.WriteError(w, "The parameter 'source' was missing in the query.", http.StatusInternalServerError)
return
}
item, err := s.Db.GetSourceByNameAndSource(context.Background(), database.GetSourceByNameAndSourceParams{
Name: name,
Source: source,
})
item, err := s.dto.GetSourceByNameAndSource(r.Context(), name, source)
if err != nil {
http.Error(w, "Unable to find the requested record.", http.StatusInternalServerError)
s.WriteError(w, "Unable to find the requested record.", http.StatusInternalServerError)
return
}
p.Payload = item
bResult, err := json.Marshal(item)
if err != nil {
log.Panicln(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set(HeaderContentType, ApplicationJson)
w.Write(bResult)
}
@ -161,20 +234,22 @@ func (s *Server) GetSourceBySourceAndName(w http.ResponseWriter, r *http.Request
// @Summary Creates a new reddit source to monitor.
// @Param name query string true "name"
// @Param url query string true "url"
// @Tags Config, Source, Reddit
// @Router /config/sources/new/reddit [post]
// @Tags Source
// @Router /sources/new/reddit [post]
func (s *Server) newRedditSource(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
_name := query["name"][0]
_url := query["url"][0]
//_tags := query["tags"][0]
w.Header().Set("Content-Type", "application/json")
if _url == "" {
http.Error(w, "url is missing a value", http.StatusBadRequest)
s.WriteError(w, "url is missing a value", http.StatusBadRequest)
return
}
if !strings.Contains(_url, "reddit.com") {
http.Error(w, "invalid url", http.StatusBadRequest)
s.WriteError(w, "invalid url", http.StatusBadRequest)
return
}
@ -197,13 +272,18 @@ func (s *Server) newRedditSource(w http.ResponseWriter, r *http.Request) {
Url: _url,
Tags: tags,
}
s.Db.CreateSource(*s.ctx, params)
err := s.Db.CreateSource(*s.ctx, params)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
bJson, err := json.Marshal(&params)
if err != nil {
log.Panicln(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(bJson)
}
@ -211,20 +291,21 @@ func (s *Server) newRedditSource(w http.ResponseWriter, r *http.Request) {
// @Summary Creates a new youtube source to monitor.
// @Param name query string true "name"
// @Param url query string true "url"
// @Tags Config, Source, YouTube
// @Router /config/sources/new/youtube [post]
// @Tags Source
// @Router /sources/new/youtube [post]
func (s *Server) newYoutubeSource(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
_name := query["name"][0]
_url := query["url"][0]
//_tags := query["tags"][0]
w.Header().Set("Content-Type", "application/json")
if _url == "" {
http.Error(w, "url is missing a value", http.StatusBadRequest)
s.WriteError(w, "url is missing a value", http.StatusBadRequest)
return
}
if !strings.Contains(_url, "youtube.com") {
http.Error(w, "invalid url", http.StatusBadRequest)
s.WriteError(w, "invalid url", http.StatusBadRequest)
return
}
@ -246,22 +327,29 @@ func (s *Server) newYoutubeSource(w http.ResponseWriter, r *http.Request) {
Url: _url,
Tags: tags,
}
s.Db.CreateSource(*s.ctx, params)
err := s.Db.CreateSource(*s.ctx, params)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
bJson, err := json.Marshal(&params)
if err != nil {
log.Panicln(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(bJson)
}
// NewTwitchSource
// @Summary Creates a new twitch source to monitor.
// @Param name query string true "name"
// @Tags Config, Source, Twitch
// @Router /config/sources/new/twitch [post]
// @Tags Source
// @Router /sources/new/twitch [post]
func (s *Server) newTwitchSource(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
query := r.URL.Query()
_name := query["name"][0]
@ -278,13 +366,18 @@ func (s *Server) newTwitchSource(w http.ResponseWriter, r *http.Request) {
Url: _url,
Tags: tags,
}
s.Db.CreateSource(*s.ctx, params)
err := s.Db.CreateSource(*s.ctx, params)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
bJson, err := json.Marshal(&params)
if err != nil {
log.Panicln(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(bJson)
}
@ -292,73 +385,115 @@ func (s *Server) newTwitchSource(w http.ResponseWriter, r *http.Request) {
// @Summary Marks a source as deleted based on its ID value.
// @Param id path string true "id"
// @Tags Source
// @Router /config/sources/{id} [POST]
// @Router /sources/{id} [POST]
func (s *Server) deleteSources(w http.ResponseWriter, r *http.Request) {
//var item model.Sources = model.Sources{}
id := chi.URLParam(r, "ID")
uuid, err := uuid.Parse(id)
if err != nil {
log.Panicln(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
// Check to make sure we can find the record
_, err = s.Db.GetSourceByID(*s.ctx, uuid)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
// Delete the record
err = s.Db.DeleteSource(*s.ctx, uuid)
if err != nil {
log.Panic(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
p := ApiStatusModel{
Message: "OK",
StatusCode: http.StatusOK,
}
b, err := json.Marshal(p)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
}
// DisableSource
// @Summary Disables a source from processing.
// @Param id path string true "id"
// @Tags Config, Source
// @Router /config/sources/{id}/disable [post]
// @Tags Source
// @Router /sources/{id}/disable [post]
func (s *Server) disableSource(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "ID")
uuid, err := uuid.Parse(id)
if err != nil {
log.Panicln(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
}
// Check to make sure we can find the record
_, err = s.Db.GetSourceByID(*s.ctx, uuid)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
}
err = s.Db.DisableSource(*s.ctx, uuid)
if err != nil {
log.Panic(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
}
p := ApiStatusModel{
Message: "OK",
StatusCode: http.StatusOK,
}
b, err := json.Marshal(p)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
}
// EnableSource
// @Summary Enables a source to continue processing.
// @Param id path string true "id"
// @Tags Config, Source
// @Router /config/sources/{id}/enable [post]
// @Tags Source
// @Router /sources/{id}/enable [post]
func (s *Server) enableSource(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "ID")
uuid, err := uuid.Parse(id)
if err != nil {
log.Panicln(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
}
// Check to make sure we can find the record
_, err = s.Db.GetSourceByID(*s.ctx, uuid)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
}
err = s.Db.EnableSource(*s.ctx, uuid)
if err != nil {
log.Panic(err)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
}
p := ApiStatusModel{
Message: "OK",
StatusCode: http.StatusOK,
}
b, err := json.Marshal(p)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
}

View File

@ -5,64 +5,149 @@ import (
"encoding/json"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/jtom38/newsbot/collector/database"
"github.com/jtom38/newsbot/collector/domain/models"
)
func (s *Server) GetSubscriptionsRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", s.ListSubscriptions)
r.Get("/details", s.ListSubscriptionDetails)
r.Get("/by/discordId", s.GetSubscriptionsByDiscordId)
r.Get("/by/sourceId", s.GetSubscriptionsBySourceId)
r.Post("/discord/webhook/new", s.newDiscordWebHookSubscription)
r.Delete("/discord/webhook/delete", s.DeleteDiscordWebHookSubscription)
return r
}
type ListSubscriptions struct {
ApiStatusModel
Payload []models.SubscriptionDto `json:"payload"`
}
type GetSubscription struct {
ApiStatusModel
Payload models.SubscriptionDto `json:"payload"`
}
// GetSubscriptions
// @Summary Returns the top 100 entries from the queue to be processed.
// @Produce application/json
// @Tags Subscription
// @Router /subscriptions [get]
// @Success 200 {object} ListSubscriptions "ok"
// @Failure 400 {object} ApiError "Unable to reach SQL."
// @Failure 500 {object} ApiError "Failed to process data from SQL."
func (s *Server) ListSubscriptions(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
res, err := s.Db.ListSubscriptions(*s.ctx, 100)
if err != nil {
w.Write([]byte(err.Error()))
panic(err)
payload := ListSubscriptions{
ApiStatusModel: ApiStatusModel{
StatusCode: http.StatusOK,
Message: "OK",
},
}
bres, err := json.Marshal(res)
res, err := s.dto.ListSubscriptions(r.Context(), 50)
if err != nil {
http.Error(w, ErrUnableToConvertToJson, http.StatusBadRequest)
panic(err)
s.WriteError(w, err.Error(), http.StatusBadRequest)
return
}
payload.Payload = res
bres, err := json.Marshal(payload)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(bres)
}
type ListSubscriptionDetails struct {
ApiStatusModel
Payload []models.SubscriptionDetailsDto `json:"payload"`
}
// ListSubscriptionDetails
// @Summary Returns the top 50 entries with full deatils on the source and output.
// @Produce application/json
// @Tags Subscription
// @Router /subscriptions/details [get]
// @Success 200 {object} ListSubscriptionDetails "ok"
func (s Server) ListSubscriptionDetails(w http.ResponseWriter, r *http.Request) {
w.Header().Set(HeaderContentType, ApplicationJson)
payload := ListSubscriptionDetails{
ApiStatusModel: ApiStatusModel{
StatusCode: http.StatusOK,
Message: "OK",
},
}
res, err := s.dto.ListSubscriptionDetails(r.Context(), 50)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
payload.Payload = res
b, err := json.Marshal(payload)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
}
w.Write(b)
}
// GetSubscriptionsByDiscordId
// @Summary Returns the top 100 entries from the queue to be processed.
// @Produce application/json
// @Param id query string true "id"
// @Tags Subscription
// @Router /subscriptions/byDiscordId [get]
// @Router /subscriptions/by/discordId [get]
// @Success 200 {object} ListSubscriptions "ok"
// @Failure 400 {object} ApiError "Unable to reach SQL or Data problems"
// @Failure 500 {object} ApiError "Data problems"
func (s *Server) GetSubscriptionsByDiscordId(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set(HeaderContentType, ApplicationJson)
p := ListSubscriptions{
ApiStatusModel: ApiStatusModel{
StatusCode: http.StatusOK,
Message: "OK",
},
}
query := r.URL.Query()
_id := query["id"][0]
if _id == "" {
http.Error(w, ErrIdValueMissing, http.StatusBadRequest)
s.WriteError(w, ErrIdValueMissing, http.StatusBadRequest)
return
}
uuid, err := uuid.Parse(_id)
if err != nil {
http.Error(w, ErrValueNotUuid, http.StatusBadRequest)
s.WriteError(w, ErrValueNotUuid, http.StatusBadRequest)
return
}
res, err := s.Db.GetSubscriptionsByDiscordWebHookId(*s.ctx, uuid)
res, err := s.dto.ListSubscriptionsByDiscordWebhookId(r.Context(), uuid)
if err != nil {
http.Error(w, ErrNoRecordFound, http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusNoContent)
return
}
bres, err := json.Marshal(res)
p.Payload = res
bres, err := json.Marshal(p)
if err != nil {
http.Error(w, ErrUnableToConvertToJson, http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
@ -74,32 +159,42 @@ func (s *Server) GetSubscriptionsByDiscordId(w http.ResponseWriter, r *http.Requ
// @Produce application/json
// @Param id query string true "id"
// @Tags Subscription
// @Router /subscriptions/bySourceId [get]
// @Router /subscriptions/by/SourceId [get]
// @Success 200 {object} ListSubscriptions "ok"
func (s *Server) GetSubscriptionsBySourceId(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
p := ListSubscriptions{
ApiStatusModel: ApiStatusModel{
StatusCode: http.StatusOK,
Message: "OK",
},
}
query := r.URL.Query()
_id := query["id"][0]
if _id == "" {
http.Error(w, ErrIdValueMissing, http.StatusBadRequest)
s.WriteError(w, ErrIdValueMissing, http.StatusBadRequest)
return
}
uuid, err := uuid.Parse(_id)
if err != nil {
http.Error(w, ErrValueNotUuid, http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusBadRequest)
return
}
res, err := s.Db.GetSubscriptionsByDiscordWebHookId(*s.ctx, uuid)
res, err := s.dto.ListSubscriptionsBySourceId(r.Context(), uuid)
if err != nil {
http.Error(w, ErrNoRecordFound, http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusNoContent)
return
}
bres, err := json.Marshal(res)
p.Payload = res
bres, err := json.Marshal(p)
if err != nil {
http.Error(w, ErrUnableToConvertToJson, http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
@ -111,48 +206,44 @@ func (s *Server) GetSubscriptionsBySourceId(w http.ResponseWriter, r *http.Reque
// @Param discordWebHookId query string true "discordWebHookId"
// @Param sourceId query string true "sourceId"
// @Tags Subscription
// @Router /subscriptions/new/discordwebhook [post]
// @Router /subscriptions/discord/webhook/new [post]
func (s *Server) newDiscordWebHookSubscription(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
// Extract the values given
query := r.URL.Query()
discordWebHookId := query["discordWebHookId"][0]
sourceId := query["sourceId"][0]
// Check to make we didnt get a null
// Check to make we didn't get a null
if discordWebHookId == "" {
http.Error(w, "invalid discordWebHooksId given", http.StatusBadRequest)
s.WriteError(w, "invalid discordWebHooksId given", http.StatusBadRequest)
return
}
if sourceId == "" {
http.Error(w, "invalid sourceID given", http.StatusBadRequest)
s.WriteError(w, "invalid sourceID given", http.StatusBadRequest)
return
}
// Valide they are UUID values
// Validate they are UUID values
uHook, err := uuid.Parse(discordWebHookId)
if err != nil {
http.Error(w, "DiscordWebHooksID was not a uuid value.", http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusBadRequest)
return
}
uSource, err := uuid.Parse(sourceId)
if err != nil {
http.Error(w, "SourceId was not a uuid value", http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusBadRequest)
return
}
// Check if the sub already exists
item, err := s.Db.QuerySubscriptions(*s.ctx, database.QuerySubscriptionsParams{
_, err = s.Db.QuerySubscriptions(*s.ctx, database.QuerySubscriptionsParams{
Discordwebhookid: uHook,
Sourceid: uSource,
})
if err == nil {
bJson, err := json.Marshal(&item)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(bJson)
s.WriteError(w, "a subscription already exists between these two entities", http.StatusBadRequest)
return
}
@ -162,29 +253,39 @@ func (s *Server) newDiscordWebHookSubscription(w http.ResponseWriter, r *http.Re
Discordwebhookid: uHook,
Sourceid: uSource,
}
s.Db.CreateSubscription(*s.ctx, params)
err = s.Db.CreateSubscription(*s.ctx, params)
if err != nil {
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
bJson, err := json.Marshal(&params)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(bJson)
}
// DeleteDiscordWebHookSubscription
// @Summary Removes a Discord WebHook Subscription based on the Subscription ID.
// @Param Id query string true "Id"
// @Tags Config, Source, Discord, Subscription
// @Param id query string true "id"
// @Tags Subscription
// @Router /subscriptions/discord/webhook/delete [delete]
func (s *Server) DeleteDiscordWebHookSubscription(w http.ResponseWriter, r *http.Request) {
var ErrMissingSubscriptionID string = "Request was missing a 'Id' or was a invalid UUID."
var ErrMissingSubscriptionID string = "the request was missing a 'Id'"
query := r.URL.Query()
uid, err := uuid.Parse(query["Id"][0])
id := query["id"][0]
if id == "" {
s.WriteError(w, ErrMissingSubscriptionID, http.StatusBadRequest)
return
}
uid, err := uuid.Parse(query["id"][0])
if err != nil {
http.Error(w, ErrMissingSubscriptionID, http.StatusBadRequest)
s.WriteError(w, err.Error(), http.StatusBadRequest)
return
}

View File

@ -3,7 +3,7 @@ package cache
import (
"time"
"github.com/jtom38/newsbot/collector/domain/model"
"github.com/jtom38/newsbot/collector/domain/models"
)
type CacheClient struct {
@ -19,7 +19,7 @@ func NewCacheClient(group string) CacheClient {
}
func (cc *CacheClient) Insert(key string, value string) {
item := model.CacheItem{
item := models.CacheItem{
Key: key,
Value: value,
Group: cc.group,
@ -29,7 +29,7 @@ func (cc *CacheClient) Insert(key string, value string) {
cacheStorage = append(cacheStorage, &item)
}
func (cc *CacheClient) FindByKey(key string) (*model.CacheItem, error) {
func (cc *CacheClient) FindByKey(key string) (*models.CacheItem, error) {
for _, item := range cacheStorage {
if item.Group != cc.group {
continue
@ -46,10 +46,10 @@ func (cc *CacheClient) FindByKey(key string) (*model.CacheItem, error) {
return item, nil
}
return &model.CacheItem{}, ErrCacheRecordMissing
return &models.CacheItem{}, ErrCacheRecordMissing
}
func (cc *CacheClient) FindByValue(value string) (*model.CacheItem, error) {
func (cc *CacheClient) FindByValue(value string) (*models.CacheItem, error) {
for _, item := range cacheStorage {
if item.Group != cc.group {
continue
@ -65,5 +65,5 @@ func (cc *CacheClient) FindByValue(value string) (*model.CacheItem, error) {
}
return item, nil
}
return &model.CacheItem{}, ErrCacheRecordMissing
return &models.CacheItem{}, ErrCacheRecordMissing
}

View File

@ -3,11 +3,11 @@ package cache
import (
"errors"
"github.com/jtom38/newsbot/collector/domain/model"
"github.com/jtom38/newsbot/collector/domain/models"
)
var (
cacheStorage []*model.CacheItem
cacheStorage []*models.CacheItem
ErrCacheRecordMissing = errors.New("unable to find the requested record")
)

View File

@ -3,7 +3,7 @@ package cache
import (
"time"
"github.com/jtom38/newsbot/collector/domain/model"
"github.com/jtom38/newsbot/collector/domain/models"
)
// When a record becomes tainted, it needs to be renewed or it will be dropped from the cache.
@ -36,8 +36,8 @@ func (cam CacheAgeMonitor) CheckExpiredEntries() {
}
// This creates a new slice and skips over the item that needs to be dropped
func (cam CacheAgeMonitor) removeEntry(index int) []*model.CacheItem {
var temp []*model.CacheItem
func (cam CacheAgeMonitor) removeEntry(index int) []*models.CacheItem {
var temp []*models.CacheItem
for i, item := range cacheStorage {
if i != index {
temp = append(temp, item)

View File

@ -12,7 +12,7 @@ import (
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/launcher"
"github.com/jtom38/newsbot/collector/database"
"github.com/jtom38/newsbot/collector/domain/model"
"github.com/jtom38/newsbot/collector/domain/models"
"github.com/jtom38/newsbot/collector/services/config"
)
@ -65,8 +65,8 @@ func (rc *RedditClient) GetPage(parser *rod.Browser, url string) *rod.Page {
// GetContent() reaches out to Reddit and pulls the Json data.
// It will then convert the data to a struct and return the struct.
func (rc *RedditClient) GetContent() (model.RedditJsonContent, error) {
var items model.RedditJsonContent = model.RedditJsonContent{}
func (rc *RedditClient) GetContent() (models.RedditJsonContent, error) {
var items models.RedditJsonContent = models.RedditJsonContent{}
// TODO Wire this to support the config options
Url := fmt.Sprintf("%v.json", rc.record.Url)
@ -88,7 +88,7 @@ func (rc *RedditClient) GetContent() (model.RedditJsonContent, error) {
return items, nil
}
func (rc *RedditClient) ConvertToArticles(items model.RedditJsonContent) []database.Article {
func (rc *RedditClient) ConvertToArticles(items models.RedditJsonContent) []database.Article {
var redditArticles []database.Article
for _, item := range items.Data.Children {
var article database.Article
@ -104,7 +104,7 @@ func (rc *RedditClient) ConvertToArticles(items model.RedditJsonContent) []datab
// ConvertToArticle() will take the reddit model struct and convert them over to Article structs.
// This data can be passed to the database.
func (rc *RedditClient) convertToArticle(source model.RedditPost) (database.Article, error) {
func (rc *RedditClient) convertToArticle(source models.RedditPost) (database.Article, error) {
var item database.Article
if source.Content == "" && source.Url != "" {
@ -131,7 +131,7 @@ func (rc *RedditClient) convertToArticle(source model.RedditPost) (database.Arti
return item, nil
}
func (rc *RedditClient) convertPicturePost(source model.RedditPost) database.Article {
func (rc *RedditClient) convertPicturePost(source models.RedditPost) database.Article {
var item = database.Article{
Sourceid: rc.record.ID,
Title: source.Title,
@ -149,7 +149,7 @@ func (rc *RedditClient) convertPicturePost(source model.RedditPost) database.Art
return item
}
func (rc *RedditClient) convertTextPost(source model.RedditPost) database.Article {
func (rc *RedditClient) convertTextPost(source models.RedditPost) database.Article {
var item = database.Article{
Sourceid: rc.record.ID,
Tags: "a",
@ -164,7 +164,7 @@ func (rc *RedditClient) convertTextPost(source model.RedditPost) database.Articl
return item
}
func (rc *RedditClient) convertVideoPost(source model.RedditPost) database.Article {
func (rc *RedditClient) convertVideoPost(source models.RedditPost) database.Article {
var item = database.Article{
Sourceid: rc.record.ID,
Tags: "a",
@ -180,7 +180,7 @@ func (rc *RedditClient) convertVideoPost(source model.RedditPost) database.Artic
}
// This post is nothing more then a redirect to another location.
func (rc *RedditClient) convertRedirectPost(source model.RedditPost) database.Article {
func (rc *RedditClient) convertRedirectPost(source models.RedditPost) database.Article {
var item = database.Article{
Sourceid: rc.record.ID,
Tags: "a",

View File

@ -4,16 +4,16 @@ import (
"fmt"
"log"
"github.com/jtom38/newsbot/collector/domain/model"
"github.com/jtom38/newsbot/collector/domain/models"
"github.com/jtom38/newsbot/collector/services/cache"
"github.com/mmcdole/gofeed"
)
type rssClient struct {
SourceRecord model.Sources
SourceRecord models.Sources
}
func NewRssClient(sourceRecord model.Sources) rssClient {
func NewRssClient(sourceRecord models.Sources) rssClient {
client := rssClient{
SourceRecord: sourceRecord,
}

View File

@ -3,11 +3,11 @@ package input_test
import (
"testing"
"github.com/jtom38/newsbot/collector/domain/model"
"github.com/jtom38/newsbot/collector/domain/models"
"github.com/jtom38/newsbot/collector/services/input"
)
var rssRecord = model.Sources{
var rssRecord = models.Sources{
ID: 1,
Name: "ArsTechnica",
Url: "https://feeds.arstechnica.com/arstechnica/index",