From ada453e08af759089c5ffe28907c13835e51eb36 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Sun, 22 Jan 2023 10:12:55 -0800 Subject: [PATCH] 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 --- database/db.go | 2 +- database/models.go | 2 +- database/query.sql.go | 2 +- docs/docs.go | 1128 ++++++++++++++++++-------- docs/swagger.json | 1128 ++++++++++++++++++-------- docs/swagger.yaml | 808 ++++++++++++------ domain/{model => models}/cache.go | 2 +- domain/{model => models}/database.go | 2 +- domain/models/dto.go | 129 +++ domain/{model => models}/reddit.go | 2 +- dto/articles.go | 115 +++ dto/discordwebhooks.go | 63 ++ dto/queue.go | 42 + dto/sources.go | 85 ++ dto/subscriptions.go | 91 +++ main.go | 13 +- routes/articles.go | 188 +++-- routes/discordQueue.go | 29 - routes/discordwebhooks.go | 163 ++-- routes/queue.go | 57 ++ routes/root.go | 50 -- routes/server.go | 107 ++- routes/settings.go | 32 +- routes/sources.go | 293 +++++-- routes/subscriptions.go | 191 ++++- services/cache/cache.go | 12 +- services/cache/common.go | 4 +- services/cache/monitor.go | 6 +- services/input/reddit.go | 18 +- services/input/rss.go | 6 +- services/input/rss_test.go | 4 +- 31 files changed, 3423 insertions(+), 1351 deletions(-) rename domain/{model => models}/cache.go (93%) rename domain/{model => models}/database.go (99%) create mode 100644 domain/models/dto.go rename domain/{model => models}/reddit.go (99%) create mode 100644 dto/articles.go create mode 100644 dto/discordwebhooks.go create mode 100644 dto/queue.go create mode 100644 dto/sources.go create mode 100644 dto/subscriptions.go delete mode 100644 routes/discordQueue.go create mode 100644 routes/queue.go delete mode 100644 routes/root.go diff --git a/database/db.go b/database/db.go index c048805..fa4f825 100644 --- a/database/db.go +++ b/database/db.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.13.0 +// sqlc v1.16.0 package database diff --git a/database/models.go b/database/models.go index 6624941..b743218 100644 --- a/database/models.go +++ b/database/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.13.0 +// sqlc v1.16.0 package database diff --git a/database/query.sql.go b/database/query.sql.go index 390e13e..96b4f9b 100644 --- a/database/query.sql.go +++ b/database/query.sql.go @@ -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 diff --git a/docs/docs.go b/docs/docs.go index 0178183..f8eba2b 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -25,7 +25,14 @@ const docTemplate = `{ "Articles" ], "summary": "Lists the top 50 records", - "responses": {} + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/routes.ArticlesListResults" + } + } + } } }, "/articles/by/sourceid": { @@ -46,28 +53,14 @@ const docTemplate = `{ "required": true } ], - "responses": {} - } - }, - "/articles/by/tag": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "Articles" - ], - "summary": "Finds the articles based on the SourceID provided. Returns the top 50.", - "parameters": [ - { - "type": "string", - "description": "Tag name", - "name": "tag", - "in": "query", - "required": true + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/routes.ArticlesListResults" + } } - ], - "responses": {} + } } }, "/articles/{ID}": { @@ -83,240 +76,47 @@ const docTemplate = `{ { "type": "string", "description": "uuid", - "name": "id", + "name": "ID", "in": "path", "required": true } ], - "responses": {} + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/routes.ArticleGetResults" + } + } + } } }, - "/config/sources": { + "/articles/{ID}/details": { "get": { "produces": [ "application/json" ], "tags": [ - "Config", - "Source" + "Articles" ], - "summary": "Lists the top 50 records", - "responses": {} - } - }, - "/config/sources/by/source": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "Config", - "Source" - ], - "summary": "Lists the top 50 records based on the name given. Example: reddit", - "parameters": [ - { - "type": "string", - "description": "Source Name", - "name": "source", - "in": "query", - "required": true - } - ], - "responses": {} - } - }, - "/config/sources/by/sourceAndName": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "Config", - "Source" - ], - "summary": "Returns a single entity by ID", - "parameters": [ - { - "type": "string", - "description": "dadjokes", - "name": "name", - "in": "query", - "required": true - }, - { - "type": "string", - "description": "reddit", - "name": "source", - "in": "query", - "required": true - } - ], - "responses": {} - } - }, - "/config/sources/new/reddit": { - "post": { - "tags": [ - "Config", - "Source", - "Reddit" - ], - "summary": "Creates a new reddit source to monitor.", - "parameters": [ - { - "type": "string", - "description": "name", - "name": "name", - "in": "query", - "required": true - }, - { - "type": "string", - "description": "url", - "name": "url", - "in": "query", - "required": true - } - ], - "responses": {} - } - }, - "/config/sources/new/twitch": { - "post": { - "tags": [ - "Config", - "Source", - "Twitch" - ], - "summary": "Creates a new twitch source to monitor.", - "parameters": [ - { - "type": "string", - "description": "name", - "name": "name", - "in": "query", - "required": true - } - ], - "responses": {} - } - }, - "/config/sources/new/youtube": { - "post": { - "tags": [ - "Config", - "Source", - "YouTube" - ], - "summary": "Creates a new youtube source to monitor.", - "parameters": [ - { - "type": "string", - "description": "name", - "name": "name", - "in": "query", - "required": true - }, - { - "type": "string", - "description": "url", - "name": "url", - "in": "query", - "required": true - } - ], - "responses": {} - } - }, - "/config/sources/{id}": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "Config", - "Source" - ], - "summary": "Returns a single entity by ID", + "summary": "Returns an article and source based on defined ID.", "parameters": [ { "type": "string", "description": "uuid", - "name": "id", + "name": "ID", "in": "path", "required": true } ], - "responses": {} - }, - "post": { - "tags": [ - "Source" - ], - "summary": "Marks a source as deleted based on its ID value.", - "parameters": [ - { - "type": "string", - "description": "id", - "name": "id", - "in": "path", - "required": true + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/routes.ArticleDetailsResult" + } } - ], - "responses": {} - } - }, - "/config/sources/{id}/disable": { - "post": { - "tags": [ - "Config", - "Source" - ], - "summary": "Disables a source from processing.", - "parameters": [ - { - "type": "string", - "description": "id", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": {} - } - }, - "/config/sources/{id}/enable": { - "post": { - "tags": [ - "Config", - "Source" - ], - "summary": "Enables a source to continue processing.", - "parameters": [ - { - "type": "string", - "description": "id", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": {} - } - }, - "/discord/queue": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "Debug", - "Discord", - "Queue" - ], - "summary": "Returns the top 100 entries from the queue to be processed.", - "responses": {} + } } }, "/discord/webhooks": { @@ -325,7 +125,6 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Config", "Discord", "Webhook" ], @@ -339,7 +138,6 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Config", "Discord", "Webhook" ], @@ -360,13 +158,19 @@ const docTemplate = `{ "required": true } ], - "responses": {} + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/routes.ListDiscordWebhooks" + } + } + } } }, "/discord/webhooks/new": { "post": { "tags": [ - "Config", "Discord", "Webhook" ], @@ -397,31 +201,9 @@ const docTemplate = `{ "responses": {} } }, - "/discord/webhooks/{id}": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "Config", - "Discord", - "Webhook" - ], - "summary": "Returns the top 100 entries from the queue to be processed.", - "parameters": [ - { - "type": "string", - "description": "id", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": {} - }, + "/discord/webhooks/{ID}": { "delete": { "tags": [ - "Config", "Discord", "Webhook" ], @@ -436,30 +218,11 @@ const docTemplate = `{ } ], "responses": {} - }, - "patch": { - "tags": [ - "Config", - "Discord", - "Webhook" - ], - "summary": "Updates a valid discord webhook ID based on the body given.", - "parameters": [ - { - "type": "string", - "description": "id", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": {} } }, - "/discord/webhooks/{id}/disable": { + "/discord/webhooks/{ID}/disable": { "post": { "tags": [ - "Config", "Discord", "Webhook" ], @@ -476,10 +239,9 @@ const docTemplate = `{ "responses": {} } }, - "/discord/webhooks/{id}/enable": { + "/discord/webhooks/{ID}/enable": { "post": { "tags": [ - "Config", "Discord", "Webhook" ], @@ -496,20 +258,45 @@ const docTemplate = `{ "responses": {} } }, - "/hello/{who}": { + "/discord/webhooks/{id}": { "get": { "produces": [ - "text/plain" + "application/json" ], "tags": [ - "Debug" + "Discord", + "Webhook" ], - "summary": "Responds back with \"Hello x\" depending on param passed in.", + "summary": "Returns the top 100 entries from the queue to be processed.", "parameters": [ { "type": "string", - "description": "Who", - "name": "who", + "description": "id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/routes.GetDiscordWebhook" + } + } + } + }, + "patch": { + "tags": [ + "Discord", + "Webhook" + ], + "summary": "Updates a valid discord webhook ID based on the body given.", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", "in": "path", "required": true } @@ -517,44 +304,302 @@ const docTemplate = `{ "responses": {} } }, - "/helloworld": { - "get": { - "produces": [ - "text/plain" - ], - "tags": [ - "Debug" - ], - "summary": "Responds back with \"Hello world!\"", - "responses": {} - } - }, - "/ping": { - "get": { - "produces": [ - "text/plain" - ], - "tags": [ - "Debug" - ], - "summary": "Sends back \"pong\". Good to test with.", - "responses": {} - } - }, - "/settings/{key}": { + "/queue/discord/webhooks": { "get": { "produces": [ "application/json" ], "tags": [ - "Settings" + "Queue" ], - "summary": "Returns a object based on the Key that was given.", + "summary": "Returns the top 100 entries from the queue to be processed.", + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/routes.ListDiscordWebHooksQueueResults" + } + } + } + } + }, + "/sources": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Source" + ], + "summary": "Lists the top 50 records", + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/routes.ListSources" + } + }, + "400": { + "description": "Unable to reach SQL or Data problems", + "schema": { + "$ref": "#/definitions/routes.ApiError" + } + } + } + } + }, + "/sources/by/source": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Source" + ], + "summary": "Lists the top 50 records based on the name given. Example: reddit", "parameters": [ { "type": "string", - "description": "Settings Key value", - "name": "key", + "description": "Source Name", + "name": "source", + "in": "query", + "required": true + } + ], + "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" + } + } + } + } + }, + "/sources/by/sourceAndName": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Source" + ], + "summary": "Returns a single entity by ID", + "parameters": [ + { + "type": "string", + "description": "dadjokes", + "name": "name", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "reddit", + "name": "source", + "in": "query", + "required": true + } + ], + "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" + } + } + } + } + }, + "/sources/new/reddit": { + "post": { + "tags": [ + "Source" + ], + "summary": "Creates a new reddit source to monitor.", + "parameters": [ + { + "type": "string", + "description": "name", + "name": "name", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "url", + "name": "url", + "in": "query", + "required": true + } + ], + "responses": {} + } + }, + "/sources/new/twitch": { + "post": { + "tags": [ + "Source" + ], + "summary": "Creates a new twitch source to monitor.", + "parameters": [ + { + "type": "string", + "description": "name", + "name": "name", + "in": "query", + "required": true + } + ], + "responses": {} + } + }, + "/sources/new/youtube": { + "post": { + "tags": [ + "Source" + ], + "summary": "Creates a new youtube source to monitor.", + "parameters": [ + { + "type": "string", + "description": "name", + "name": "name", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "url", + "name": "url", + "in": "query", + "required": true + } + ], + "responses": {} + } + }, + "/sources/{id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Source" + ], + "summary": "Returns a single entity by ID", + "parameters": [ + { + "type": "string", + "description": "uuid", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "post": { + "tags": [ + "Source" + ], + "summary": "Marks a source as deleted based on its ID value.", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": {} + } + }, + "/sources/{id}/disable": { + "post": { + "tags": [ + "Source" + ], + "summary": "Disables a source from processing.", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": {} + } + }, + "/sources/{id}/enable": { + "post": { + "tags": [ + "Source" + ], + "summary": "Enables a source to continue processing.", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", "in": "path", "required": true } @@ -571,10 +616,29 @@ const docTemplate = `{ "Subscription" ], "summary": "Returns the top 100 entries from the queue to be processed.", - "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" + } + } + } } }, - "/subscriptions/byDiscordId": { + "/subscriptions/by/SourceId": { "get": { "produces": [ "application/json" @@ -592,10 +656,17 @@ const docTemplate = `{ "required": true } ], - "responses": {} + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/routes.ListSubscriptions" + } + } + } } }, - "/subscriptions/bySourceId": { + "/subscriptions/by/discordId": { "get": { "produces": [ "application/json" @@ -613,23 +684,58 @@ const docTemplate = `{ "required": true } ], - "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" + } + } + } + } + }, + "/subscriptions/details": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Subscription" + ], + "summary": "Returns the top 50 entries with full deatils on the source and output.", + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/routes.ListSubscriptionDetails" + } + } + } } }, "/subscriptions/discord/webhook/delete": { "delete": { "tags": [ - "Config", - "Source", - "Discord", "Subscription" ], "summary": "Removes a Discord WebHook Subscription based on the Subscription ID.", "parameters": [ { "type": "string", - "description": "Id", - "name": "Id", + "description": "id", + "name": "id", "in": "query", "required": true } @@ -637,7 +743,7 @@ const docTemplate = `{ "responses": {} } }, - "/subscriptions/new/discordwebhook": { + "/subscriptions/discord/webhook/new": { "post": { "tags": [ "Subscription" @@ -662,6 +768,368 @@ const docTemplate = `{ "responses": {} } } + }, + "definitions": { + "models.ArticleDetailsDto": { + "type": "object", + "properties": { + "authorImage": { + "type": "string" + }, + "authorName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "pubdate": { + "type": "string" + }, + "source": { + "$ref": "#/definitions/models.SourceDto" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "thumbnail": { + "type": "string" + }, + "title": { + "type": "string" + }, + "url": { + "type": "string" + }, + "video": { + "type": "string" + }, + "videoHeight": { + "type": "integer" + }, + "videoWidth": { + "type": "integer" + } + } + }, + "models.ArticleDto": { + "type": "object", + "properties": { + "authorImage": { + "type": "string" + }, + "authorName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "pubdate": { + "type": "string" + }, + "sourceid": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "thumbnail": { + "type": "string" + }, + "title": { + "type": "string" + }, + "url": { + "type": "string" + }, + "video": { + "type": "string" + }, + "videoHeight": { + "type": "integer" + }, + "videoWidth": { + "type": "integer" + } + } + }, + "models.DiscordQueueDetailsDto": { + "type": "object", + "properties": { + "article": { + "$ref": "#/definitions/models.ArticleDetailsDto" + }, + "id": { + "type": "string" + } + } + }, + "models.DiscordWebHooksDto": { + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "channel": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "server": { + "type": "string" + }, + "url": { + "type": "string" + } + } + }, + "models.SourceDto": { + "type": "object", + "properties": { + "deleted": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "site": { + "type": "string" + }, + "source": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "models.SubscriptionDetailsDto": { + "type": "object", + "properties": { + "discordwebhook": { + "$ref": "#/definitions/models.DiscordWebHooksDto" + }, + "id": { + "type": "string" + }, + "source": { + "$ref": "#/definitions/models.SourceDto" + } + } + }, + "models.SubscriptionDto": { + "type": "object", + "properties": { + "discordwebhookid": { + "type": "string" + }, + "id": { + "type": "string" + }, + "sourceid": { + "type": "string" + } + } + }, + "routes.ApiError": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "status": { + "type": "integer" + } + } + }, + "routes.ArticleDetailsResult": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/definitions/models.ArticleDetailsDto" + }, + "status": { + "type": "integer" + } + } + }, + "routes.ArticleGetResults": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/definitions/models.ArticleDto" + }, + "status": { + "type": "integer" + } + } + }, + "routes.ArticlesListResults": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/definitions/models.ArticleDto" + } + }, + "status": { + "type": "integer" + } + } + }, + "routes.GetDiscordWebhook": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/definitions/models.DiscordWebHooksDto" + }, + "status": { + "type": "integer" + } + } + }, + "routes.GetSource": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/definitions/models.SourceDto" + }, + "status": { + "type": "integer" + } + } + }, + "routes.ListDiscordWebHooksQueueResults": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/definitions/models.DiscordQueueDetailsDto" + } + }, + "status": { + "type": "integer" + } + } + }, + "routes.ListDiscordWebhooks": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/definitions/models.DiscordWebHooksDto" + } + }, + "status": { + "type": "integer" + } + } + }, + "routes.ListSources": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SourceDto" + } + }, + "status": { + "type": "integer" + } + } + }, + "routes.ListSubscriptionDetails": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SubscriptionDetailsDto" + } + }, + "status": { + "type": "integer" + } + } + }, + "routes.ListSubscriptions": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SubscriptionDto" + } + }, + "status": { + "type": "integer" + } + } + } } }` diff --git a/docs/swagger.json b/docs/swagger.json index b8b53e9..4eed4d9 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -16,7 +16,14 @@ "Articles" ], "summary": "Lists the top 50 records", - "responses": {} + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/routes.ArticlesListResults" + } + } + } } }, "/articles/by/sourceid": { @@ -37,28 +44,14 @@ "required": true } ], - "responses": {} - } - }, - "/articles/by/tag": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "Articles" - ], - "summary": "Finds the articles based on the SourceID provided. Returns the top 50.", - "parameters": [ - { - "type": "string", - "description": "Tag name", - "name": "tag", - "in": "query", - "required": true + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/routes.ArticlesListResults" + } } - ], - "responses": {} + } } }, "/articles/{ID}": { @@ -74,240 +67,47 @@ { "type": "string", "description": "uuid", - "name": "id", + "name": "ID", "in": "path", "required": true } ], - "responses": {} + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/routes.ArticleGetResults" + } + } + } } }, - "/config/sources": { + "/articles/{ID}/details": { "get": { "produces": [ "application/json" ], "tags": [ - "Config", - "Source" + "Articles" ], - "summary": "Lists the top 50 records", - "responses": {} - } - }, - "/config/sources/by/source": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "Config", - "Source" - ], - "summary": "Lists the top 50 records based on the name given. Example: reddit", - "parameters": [ - { - "type": "string", - "description": "Source Name", - "name": "source", - "in": "query", - "required": true - } - ], - "responses": {} - } - }, - "/config/sources/by/sourceAndName": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "Config", - "Source" - ], - "summary": "Returns a single entity by ID", - "parameters": [ - { - "type": "string", - "description": "dadjokes", - "name": "name", - "in": "query", - "required": true - }, - { - "type": "string", - "description": "reddit", - "name": "source", - "in": "query", - "required": true - } - ], - "responses": {} - } - }, - "/config/sources/new/reddit": { - "post": { - "tags": [ - "Config", - "Source", - "Reddit" - ], - "summary": "Creates a new reddit source to monitor.", - "parameters": [ - { - "type": "string", - "description": "name", - "name": "name", - "in": "query", - "required": true - }, - { - "type": "string", - "description": "url", - "name": "url", - "in": "query", - "required": true - } - ], - "responses": {} - } - }, - "/config/sources/new/twitch": { - "post": { - "tags": [ - "Config", - "Source", - "Twitch" - ], - "summary": "Creates a new twitch source to monitor.", - "parameters": [ - { - "type": "string", - "description": "name", - "name": "name", - "in": "query", - "required": true - } - ], - "responses": {} - } - }, - "/config/sources/new/youtube": { - "post": { - "tags": [ - "Config", - "Source", - "YouTube" - ], - "summary": "Creates a new youtube source to monitor.", - "parameters": [ - { - "type": "string", - "description": "name", - "name": "name", - "in": "query", - "required": true - }, - { - "type": "string", - "description": "url", - "name": "url", - "in": "query", - "required": true - } - ], - "responses": {} - } - }, - "/config/sources/{id}": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "Config", - "Source" - ], - "summary": "Returns a single entity by ID", + "summary": "Returns an article and source based on defined ID.", "parameters": [ { "type": "string", "description": "uuid", - "name": "id", + "name": "ID", "in": "path", "required": true } ], - "responses": {} - }, - "post": { - "tags": [ - "Source" - ], - "summary": "Marks a source as deleted based on its ID value.", - "parameters": [ - { - "type": "string", - "description": "id", - "name": "id", - "in": "path", - "required": true + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/routes.ArticleDetailsResult" + } } - ], - "responses": {} - } - }, - "/config/sources/{id}/disable": { - "post": { - "tags": [ - "Config", - "Source" - ], - "summary": "Disables a source from processing.", - "parameters": [ - { - "type": "string", - "description": "id", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": {} - } - }, - "/config/sources/{id}/enable": { - "post": { - "tags": [ - "Config", - "Source" - ], - "summary": "Enables a source to continue processing.", - "parameters": [ - { - "type": "string", - "description": "id", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": {} - } - }, - "/discord/queue": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "Debug", - "Discord", - "Queue" - ], - "summary": "Returns the top 100 entries from the queue to be processed.", - "responses": {} + } } }, "/discord/webhooks": { @@ -316,7 +116,6 @@ "application/json" ], "tags": [ - "Config", "Discord", "Webhook" ], @@ -330,7 +129,6 @@ "application/json" ], "tags": [ - "Config", "Discord", "Webhook" ], @@ -351,13 +149,19 @@ "required": true } ], - "responses": {} + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/routes.ListDiscordWebhooks" + } + } + } } }, "/discord/webhooks/new": { "post": { "tags": [ - "Config", "Discord", "Webhook" ], @@ -388,31 +192,9 @@ "responses": {} } }, - "/discord/webhooks/{id}": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "Config", - "Discord", - "Webhook" - ], - "summary": "Returns the top 100 entries from the queue to be processed.", - "parameters": [ - { - "type": "string", - "description": "id", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": {} - }, + "/discord/webhooks/{ID}": { "delete": { "tags": [ - "Config", "Discord", "Webhook" ], @@ -427,30 +209,11 @@ } ], "responses": {} - }, - "patch": { - "tags": [ - "Config", - "Discord", - "Webhook" - ], - "summary": "Updates a valid discord webhook ID based on the body given.", - "parameters": [ - { - "type": "string", - "description": "id", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": {} } }, - "/discord/webhooks/{id}/disable": { + "/discord/webhooks/{ID}/disable": { "post": { "tags": [ - "Config", "Discord", "Webhook" ], @@ -467,10 +230,9 @@ "responses": {} } }, - "/discord/webhooks/{id}/enable": { + "/discord/webhooks/{ID}/enable": { "post": { "tags": [ - "Config", "Discord", "Webhook" ], @@ -487,20 +249,45 @@ "responses": {} } }, - "/hello/{who}": { + "/discord/webhooks/{id}": { "get": { "produces": [ - "text/plain" + "application/json" ], "tags": [ - "Debug" + "Discord", + "Webhook" ], - "summary": "Responds back with \"Hello x\" depending on param passed in.", + "summary": "Returns the top 100 entries from the queue to be processed.", "parameters": [ { "type": "string", - "description": "Who", - "name": "who", + "description": "id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/routes.GetDiscordWebhook" + } + } + } + }, + "patch": { + "tags": [ + "Discord", + "Webhook" + ], + "summary": "Updates a valid discord webhook ID based on the body given.", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", "in": "path", "required": true } @@ -508,44 +295,302 @@ "responses": {} } }, - "/helloworld": { - "get": { - "produces": [ - "text/plain" - ], - "tags": [ - "Debug" - ], - "summary": "Responds back with \"Hello world!\"", - "responses": {} - } - }, - "/ping": { - "get": { - "produces": [ - "text/plain" - ], - "tags": [ - "Debug" - ], - "summary": "Sends back \"pong\". Good to test with.", - "responses": {} - } - }, - "/settings/{key}": { + "/queue/discord/webhooks": { "get": { "produces": [ "application/json" ], "tags": [ - "Settings" + "Queue" ], - "summary": "Returns a object based on the Key that was given.", + "summary": "Returns the top 100 entries from the queue to be processed.", + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/routes.ListDiscordWebHooksQueueResults" + } + } + } + } + }, + "/sources": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Source" + ], + "summary": "Lists the top 50 records", + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/routes.ListSources" + } + }, + "400": { + "description": "Unable to reach SQL or Data problems", + "schema": { + "$ref": "#/definitions/routes.ApiError" + } + } + } + } + }, + "/sources/by/source": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Source" + ], + "summary": "Lists the top 50 records based on the name given. Example: reddit", "parameters": [ { "type": "string", - "description": "Settings Key value", - "name": "key", + "description": "Source Name", + "name": "source", + "in": "query", + "required": true + } + ], + "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" + } + } + } + } + }, + "/sources/by/sourceAndName": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Source" + ], + "summary": "Returns a single entity by ID", + "parameters": [ + { + "type": "string", + "description": "dadjokes", + "name": "name", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "reddit", + "name": "source", + "in": "query", + "required": true + } + ], + "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" + } + } + } + } + }, + "/sources/new/reddit": { + "post": { + "tags": [ + "Source" + ], + "summary": "Creates a new reddit source to monitor.", + "parameters": [ + { + "type": "string", + "description": "name", + "name": "name", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "url", + "name": "url", + "in": "query", + "required": true + } + ], + "responses": {} + } + }, + "/sources/new/twitch": { + "post": { + "tags": [ + "Source" + ], + "summary": "Creates a new twitch source to monitor.", + "parameters": [ + { + "type": "string", + "description": "name", + "name": "name", + "in": "query", + "required": true + } + ], + "responses": {} + } + }, + "/sources/new/youtube": { + "post": { + "tags": [ + "Source" + ], + "summary": "Creates a new youtube source to monitor.", + "parameters": [ + { + "type": "string", + "description": "name", + "name": "name", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "url", + "name": "url", + "in": "query", + "required": true + } + ], + "responses": {} + } + }, + "/sources/{id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Source" + ], + "summary": "Returns a single entity by ID", + "parameters": [ + { + "type": "string", + "description": "uuid", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "post": { + "tags": [ + "Source" + ], + "summary": "Marks a source as deleted based on its ID value.", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": {} + } + }, + "/sources/{id}/disable": { + "post": { + "tags": [ + "Source" + ], + "summary": "Disables a source from processing.", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": {} + } + }, + "/sources/{id}/enable": { + "post": { + "tags": [ + "Source" + ], + "summary": "Enables a source to continue processing.", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", "in": "path", "required": true } @@ -562,10 +607,29 @@ "Subscription" ], "summary": "Returns the top 100 entries from the queue to be processed.", - "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" + } + } + } } }, - "/subscriptions/byDiscordId": { + "/subscriptions/by/SourceId": { "get": { "produces": [ "application/json" @@ -583,10 +647,17 @@ "required": true } ], - "responses": {} + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/routes.ListSubscriptions" + } + } + } } }, - "/subscriptions/bySourceId": { + "/subscriptions/by/discordId": { "get": { "produces": [ "application/json" @@ -604,23 +675,58 @@ "required": true } ], - "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" + } + } + } + } + }, + "/subscriptions/details": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Subscription" + ], + "summary": "Returns the top 50 entries with full deatils on the source and output.", + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/routes.ListSubscriptionDetails" + } + } + } } }, "/subscriptions/discord/webhook/delete": { "delete": { "tags": [ - "Config", - "Source", - "Discord", "Subscription" ], "summary": "Removes a Discord WebHook Subscription based on the Subscription ID.", "parameters": [ { "type": "string", - "description": "Id", - "name": "Id", + "description": "id", + "name": "id", "in": "query", "required": true } @@ -628,7 +734,7 @@ "responses": {} } }, - "/subscriptions/new/discordwebhook": { + "/subscriptions/discord/webhook/new": { "post": { "tags": [ "Subscription" @@ -653,5 +759,367 @@ "responses": {} } } + }, + "definitions": { + "models.ArticleDetailsDto": { + "type": "object", + "properties": { + "authorImage": { + "type": "string" + }, + "authorName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "pubdate": { + "type": "string" + }, + "source": { + "$ref": "#/definitions/models.SourceDto" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "thumbnail": { + "type": "string" + }, + "title": { + "type": "string" + }, + "url": { + "type": "string" + }, + "video": { + "type": "string" + }, + "videoHeight": { + "type": "integer" + }, + "videoWidth": { + "type": "integer" + } + } + }, + "models.ArticleDto": { + "type": "object", + "properties": { + "authorImage": { + "type": "string" + }, + "authorName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "pubdate": { + "type": "string" + }, + "sourceid": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "thumbnail": { + "type": "string" + }, + "title": { + "type": "string" + }, + "url": { + "type": "string" + }, + "video": { + "type": "string" + }, + "videoHeight": { + "type": "integer" + }, + "videoWidth": { + "type": "integer" + } + } + }, + "models.DiscordQueueDetailsDto": { + "type": "object", + "properties": { + "article": { + "$ref": "#/definitions/models.ArticleDetailsDto" + }, + "id": { + "type": "string" + } + } + }, + "models.DiscordWebHooksDto": { + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "channel": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "server": { + "type": "string" + }, + "url": { + "type": "string" + } + } + }, + "models.SourceDto": { + "type": "object", + "properties": { + "deleted": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "site": { + "type": "string" + }, + "source": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "models.SubscriptionDetailsDto": { + "type": "object", + "properties": { + "discordwebhook": { + "$ref": "#/definitions/models.DiscordWebHooksDto" + }, + "id": { + "type": "string" + }, + "source": { + "$ref": "#/definitions/models.SourceDto" + } + } + }, + "models.SubscriptionDto": { + "type": "object", + "properties": { + "discordwebhookid": { + "type": "string" + }, + "id": { + "type": "string" + }, + "sourceid": { + "type": "string" + } + } + }, + "routes.ApiError": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "status": { + "type": "integer" + } + } + }, + "routes.ArticleDetailsResult": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/definitions/models.ArticleDetailsDto" + }, + "status": { + "type": "integer" + } + } + }, + "routes.ArticleGetResults": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/definitions/models.ArticleDto" + }, + "status": { + "type": "integer" + } + } + }, + "routes.ArticlesListResults": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/definitions/models.ArticleDto" + } + }, + "status": { + "type": "integer" + } + } + }, + "routes.GetDiscordWebhook": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/definitions/models.DiscordWebHooksDto" + }, + "status": { + "type": "integer" + } + } + }, + "routes.GetSource": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "$ref": "#/definitions/models.SourceDto" + }, + "status": { + "type": "integer" + } + } + }, + "routes.ListDiscordWebHooksQueueResults": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/definitions/models.DiscordQueueDetailsDto" + } + }, + "status": { + "type": "integer" + } + } + }, + "routes.ListDiscordWebhooks": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/definitions/models.DiscordWebHooksDto" + } + }, + "status": { + "type": "integer" + } + } + }, + "routes.ListSources": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SourceDto" + } + }, + "status": { + "type": "integer" + } + } + }, + "routes.ListSubscriptionDetails": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SubscriptionDetailsDto" + } + }, + "status": { + "type": "integer" + } + } + }, + "routes.ListSubscriptions": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SubscriptionDto" + } + }, + "status": { + "type": "integer" + } + } + } } } \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index fe4860b..3996964 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -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 diff --git a/domain/model/cache.go b/domain/models/cache.go similarity index 93% rename from domain/model/cache.go rename to domain/models/cache.go index b88ba02..1c192b1 100644 --- a/domain/model/cache.go +++ b/domain/models/cache.go @@ -1,4 +1,4 @@ -package model +package models import ( "time" diff --git a/domain/model/database.go b/domain/models/database.go similarity index 99% rename from domain/model/database.go rename to domain/models/database.go index 824a02d..1ebd6c1 100644 --- a/domain/model/database.go +++ b/domain/models/database.go @@ -1,4 +1,4 @@ -package model +package models import ( "time" diff --git a/domain/models/dto.go b/domain/models/dto.go new file mode 100644 index 0000000..4775204 --- /dev/null +++ b/domain/models/dto.go @@ -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 +} diff --git a/domain/model/reddit.go b/domain/models/reddit.go similarity index 99% rename from domain/model/reddit.go rename to domain/models/reddit.go index 9bc7637..316967e 100644 --- a/domain/model/reddit.go +++ b/domain/models/reddit.go @@ -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 { diff --git a/dto/articles.go b/dto/articles.go new file mode 100644 index 0000000..f04797a --- /dev/null +++ b/dto/articles.go @@ -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, ", ") +} diff --git a/dto/discordwebhooks.go b/dto/discordwebhooks.go new file mode 100644 index 0000000..5ddac2a --- /dev/null +++ b/dto/discordwebhooks.go @@ -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, + } +} diff --git a/dto/queue.go b/dto/queue.go new file mode 100644 index 0000000..d5697f2 --- /dev/null +++ b/dto/queue.go @@ -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, + } +} diff --git a/dto/sources.go b/dto/sources.go new file mode 100644 index 0000000..35403a8 --- /dev/null +++ b/dto/sources.go @@ -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, + } +} diff --git a/dto/subscriptions.go b/dto/subscriptions.go new file mode 100644 index 0000000..7a08ae6 --- /dev/null +++ b/dto/subscriptions.go @@ -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, + } +} diff --git a/main.go b/main.go index bdabf89..d5c35a0 100644 --- a/main.go +++ b/main.go @@ -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) } diff --git a/routes/articles.go b/routes/articles.go index 1fe6acf..c7accd6 100644 --- a/routes/articles.go +++ b/routes/articles.go @@ -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) diff --git a/routes/discordQueue.go b/routes/discordQueue.go deleted file mode 100644 index 2efdb44..0000000 --- a/routes/discordQueue.go +++ /dev/null @@ -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) -} diff --git a/routes/discordwebhooks.go b/routes/discordwebhooks.go index 5e1963c..eaee584 100644 --- a/routes/discordwebhooks.go +++ b/routes/discordwebhooks.go @@ -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 +func (s Server) DiscordWebHookRouter() http.Handler { + r := chi.NewRouter() + + 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 +} + +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 Config, Discord, Webhook +// @Tags Discord, Webhook // @Router /discord/webhooks [get] -func (s *Server) GetDiscordWebHooks(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - res, err := s.Db.ListDiscordWebhooks(*s.ctx, 100) - if err != nil { - w.Write([]byte(err.Error())) - panic(err) +func (s *Server) ListDiscordWebHooks(w http.ResponseWriter, r *http.Request) { + p := ListDiscordWebhooks{ + ApiStatusModel: ApiStatusModel{ + Message: "OK", + StatusCode: http.StatusOK, + }, } - bres, err := json.Marshal(res) + 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) - return - } - - res, err := s.Db.GetDiscordWebHooksByServerAndChannel(context.Background(), database.GetDiscordWebHooksByServerAndChannelParams{ - Server: _server, - Channel: _channel, - }) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + s.WriteError(w, "Channel is missing", http.StatusInternalServerError) return } - bres, err := json.Marshal(res) + res, err := s.dto.GetDiscordWebHookByServerAndChannel(r.Context(), _server, _channel) if err != nil { - http.Error(w, "unable to convert to json", http.StatusInternalServerError) - panic(err) + s.WriteError(w, err.Error(), http.StatusInternalServerError) + return + } + + p.Payload = res + + bres, err := json.Marshal(p) + if err != nil { + 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") diff --git a/routes/queue.go b/routes/queue.go new file mode 100644 index 0000000..dddb5a6 --- /dev/null +++ b/routes/queue.go @@ -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) +} diff --git a/routes/root.go b/routes/root.go deleted file mode 100644 index ae0626d..0000000 --- a/routes/root.go +++ /dev/null @@ -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)) -} diff --git a/routes/server.go b/routes/server.go index a68082e..5b74b47 100644 --- a/routes/server.go +++ b/routes/server.go @@ -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) - - /* 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) - - /* 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.Mount("/api/articles", s.GetArticleRouter()) + s.Router.Mount("/api/queue", s.GetQueueRouter()) + s.Router.Mount("/api/discord/webhooks", s.DiscordWebHookRouter()) - 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) - }) + //s.Router.Get("/api/settings", s.getSettings) - /* 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) } diff --git a/routes/settings.go b/routes/settings.go index 267af99..bb6aad2 100644 --- a/routes/settings.go +++ b/routes/settings.go @@ -2,43 +2,43 @@ package routes import ( "encoding/json" - "log" "net/http" "github.com/go-chi/chi/v5" "github.com/google/uuid" ) -// 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) { + // 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] + + + 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) } diff --git a/routes/sources.go b/routes/sources.go index 9586dc3..6d11892 100644 --- a/routes/sources.go +++ b/routes/sources.go @@ -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(¶ms) 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(¶ms) 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(¶ms) 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) } diff --git a/routes/subscriptions.go b/routes/subscriptions.go index d5f3f99..029a797 100644 --- a/routes/subscriptions.go +++ b/routes/subscriptions.go @@ -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(¶ms) 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 } diff --git a/services/cache/cache.go b/services/cache/cache.go index 8c0b22d..eddefdf 100644 --- a/services/cache/cache.go +++ b/services/cache/cache.go @@ -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 } diff --git a/services/cache/common.go b/services/cache/common.go index 0be3815..b53d5fe 100644 --- a/services/cache/common.go +++ b/services/cache/common.go @@ -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") ) diff --git a/services/cache/monitor.go b/services/cache/monitor.go index 450a3ee..e6481f2 100644 --- a/services/cache/monitor.go +++ b/services/cache/monitor.go @@ -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) diff --git a/services/input/reddit.go b/services/input/reddit.go index cd68313..145c978 100644 --- a/services/input/reddit.go +++ b/services/input/reddit.go @@ -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", diff --git a/services/input/rss.go b/services/input/rss.go index e71c979..7d46f22 100644 --- a/services/input/rss.go +++ b/services/input/rss.go @@ -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, } diff --git a/services/input/rss_test.go b/services/input/rss_test.go index de6ded5..05c0731 100644 --- a/services/input/rss_test.go +++ b/services/input/rss_test.go @@ -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",