From 84d108f2dd40702c75a615bf47ddf59632be6986 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Sun, 28 Apr 2024 19:29:49 -0700 Subject: [PATCH] source handlers have been updated --- docs/docs.go | 214 ++++++++++++++------- docs/swagger.json | 214 ++++++++++++++------- docs/swagger.yaml | 153 ++++++++++----- internal/domain/responses.go | 5 + internal/handler/v1/handler.go | 23 +-- internal/handler/v1/sources.go | 335 +++++++++++++++++---------------- internal/repository/source.go | 24 +++ 7 files changed, 620 insertions(+), 348 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 2ee6122..85bff2f 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -498,11 +498,19 @@ const docTemplate = `{ "Source" ], "summary": "Lists the top 50 records", + "parameters": [ + { + "type": "string", + "description": "page number", + "name": "page", + "in": "query" + } + ], "responses": { "200": { "description": "ok", "schema": { - "$ref": "#/definitions/v1.ListSources" + "$ref": "#/definitions/domain.SourcesResponse" } }, "400": { @@ -530,25 +538,31 @@ const docTemplate = `{ "name": "source", "in": "query", "required": true + }, + { + "type": "string", + "description": "page number", + "name": "page", + "in": "query" } ], "responses": { "200": { "description": "ok", "schema": { - "$ref": "#/definitions/v1.ListSources" + "$ref": "#/definitions/domain.SourcesResponse" } }, "400": { - "description": "Unable to query SQL.", + "description": "Bad Request", "schema": { - "$ref": "#/definitions/v1.ApiError" + "$ref": "#/definitions/domain.BaseResponse" } }, "500": { - "description": "Problems with data.", + "description": "Internal Server Error", "schema": { - "$ref": "#/definitions/v1.ApiError" + "$ref": "#/definitions/domain.BaseResponse" } } } @@ -583,25 +597,19 @@ const docTemplate = `{ "200": { "description": "ok", "schema": { - "$ref": "#/definitions/v1.GetSource" - } - }, - "204": { - "description": "No record found.", - "schema": { - "$ref": "#/definitions/v1.ApiError" + "$ref": "#/definitions/domain.SourcesResponse" } }, "400": { - "description": "Unable to query SQL.", + "description": "Bad Request", "schema": { - "$ref": "#/definitions/v1.ApiError" + "$ref": "#/definitions/domain.BaseResponse" } }, "500": { - "description": "Failed to process data from SQL.", + "description": "Internal Server Error", "schema": { - "$ref": "#/definitions/v1.ApiError" + "$ref": "#/definitions/domain.BaseResponse" } } } @@ -629,7 +637,70 @@ const docTemplate = `{ "required": true } ], - "responses": {} + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/domain.SourcesResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } + } + }, + "/sources/new/rss": { + "post": { + "tags": [ + "Source" + ], + "summary": "Creates a new rss source to monitor.", + "parameters": [ + { + "type": "string", + "description": "Site Name", + "name": "name", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "RSS Url", + "name": "url", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/domain.SourcesResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } } }, "/sources/new/twitch": { @@ -686,7 +757,7 @@ const docTemplate = `{ "summary": "Returns a single entity by ID", "parameters": [ { - "type": "string", + "type": "integer", "description": "uuid", "name": "id", "in": "path", @@ -697,25 +768,19 @@ const docTemplate = `{ "200": { "description": "ok", "schema": { - "$ref": "#/definitions/v1.GetSource" - } - }, - "204": { - "description": "No record found.", - "schema": { - "$ref": "#/definitions/v1.ApiError" + "$ref": "#/definitions/domain.SourcesResponse" } }, "400": { - "description": "Unable to query SQL.", + "description": "Bad Request", "schema": { - "$ref": "#/definitions/v1.ApiError" + "$ref": "#/definitions/domain.BaseResponse" } }, "500": { - "description": "Failed to process data from SQL.", + "description": "Internal Server Error", "schema": { - "$ref": "#/definitions/v1.ApiError" + "$ref": "#/definitions/domain.BaseResponse" } } } @@ -745,14 +810,33 @@ const docTemplate = `{ "summary": "Disables a source from processing.", "parameters": [ { - "type": "string", + "type": "integer", "description": "id", "name": "id", "in": "path", "required": true } ], - "responses": {} + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/domain.SourcesResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } } }, "/sources/{id}/enable": { @@ -770,7 +854,26 @@ const docTemplate = `{ "required": true } ], - "responses": {} + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/domain.SourcesResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } } }, "/subscriptions": { @@ -1076,6 +1179,20 @@ const docTemplate = `{ } } }, + "domain.SourcesResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.SourceDto" + } + } + } + }, "models.ArticleDetailsDto": { "type": "object", "properties": { @@ -1231,20 +1348,6 @@ const docTemplate = `{ } } }, - "v1.GetSource": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "payload": { - "$ref": "#/definitions/models.SourceDto" - }, - "status": { - "type": "integer" - } - } - }, "v1.ListDiscordWebHooksQueueResults": { "type": "object", "properties": { @@ -1262,23 +1365,6 @@ const docTemplate = `{ } } }, - "v1.ListSources": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "payload": { - "type": "array", - "items": { - "$ref": "#/definitions/models.SourceDto" - } - }, - "status": { - "type": "integer" - } - } - }, "v1.ListSubscriptionDetails": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 141111c..b4260d7 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -489,11 +489,19 @@ "Source" ], "summary": "Lists the top 50 records", + "parameters": [ + { + "type": "string", + "description": "page number", + "name": "page", + "in": "query" + } + ], "responses": { "200": { "description": "ok", "schema": { - "$ref": "#/definitions/v1.ListSources" + "$ref": "#/definitions/domain.SourcesResponse" } }, "400": { @@ -521,25 +529,31 @@ "name": "source", "in": "query", "required": true + }, + { + "type": "string", + "description": "page number", + "name": "page", + "in": "query" } ], "responses": { "200": { "description": "ok", "schema": { - "$ref": "#/definitions/v1.ListSources" + "$ref": "#/definitions/domain.SourcesResponse" } }, "400": { - "description": "Unable to query SQL.", + "description": "Bad Request", "schema": { - "$ref": "#/definitions/v1.ApiError" + "$ref": "#/definitions/domain.BaseResponse" } }, "500": { - "description": "Problems with data.", + "description": "Internal Server Error", "schema": { - "$ref": "#/definitions/v1.ApiError" + "$ref": "#/definitions/domain.BaseResponse" } } } @@ -574,25 +588,19 @@ "200": { "description": "ok", "schema": { - "$ref": "#/definitions/v1.GetSource" - } - }, - "204": { - "description": "No record found.", - "schema": { - "$ref": "#/definitions/v1.ApiError" + "$ref": "#/definitions/domain.SourcesResponse" } }, "400": { - "description": "Unable to query SQL.", + "description": "Bad Request", "schema": { - "$ref": "#/definitions/v1.ApiError" + "$ref": "#/definitions/domain.BaseResponse" } }, "500": { - "description": "Failed to process data from SQL.", + "description": "Internal Server Error", "schema": { - "$ref": "#/definitions/v1.ApiError" + "$ref": "#/definitions/domain.BaseResponse" } } } @@ -620,7 +628,70 @@ "required": true } ], - "responses": {} + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/domain.SourcesResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } + } + }, + "/sources/new/rss": { + "post": { + "tags": [ + "Source" + ], + "summary": "Creates a new rss source to monitor.", + "parameters": [ + { + "type": "string", + "description": "Site Name", + "name": "name", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "RSS Url", + "name": "url", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/domain.SourcesResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } } }, "/sources/new/twitch": { @@ -677,7 +748,7 @@ "summary": "Returns a single entity by ID", "parameters": [ { - "type": "string", + "type": "integer", "description": "uuid", "name": "id", "in": "path", @@ -688,25 +759,19 @@ "200": { "description": "ok", "schema": { - "$ref": "#/definitions/v1.GetSource" - } - }, - "204": { - "description": "No record found.", - "schema": { - "$ref": "#/definitions/v1.ApiError" + "$ref": "#/definitions/domain.SourcesResponse" } }, "400": { - "description": "Unable to query SQL.", + "description": "Bad Request", "schema": { - "$ref": "#/definitions/v1.ApiError" + "$ref": "#/definitions/domain.BaseResponse" } }, "500": { - "description": "Failed to process data from SQL.", + "description": "Internal Server Error", "schema": { - "$ref": "#/definitions/v1.ApiError" + "$ref": "#/definitions/domain.BaseResponse" } } } @@ -736,14 +801,33 @@ "summary": "Disables a source from processing.", "parameters": [ { - "type": "string", + "type": "integer", "description": "id", "name": "id", "in": "path", "required": true } ], - "responses": {} + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/domain.SourcesResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } } }, "/sources/{id}/enable": { @@ -761,7 +845,26 @@ "required": true } ], - "responses": {} + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/domain.SourcesResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } } }, "/subscriptions": { @@ -1067,6 +1170,20 @@ } } }, + "domain.SourcesResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.SourceDto" + } + } + } + }, "models.ArticleDetailsDto": { "type": "object", "properties": { @@ -1222,20 +1339,6 @@ } } }, - "v1.GetSource": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "payload": { - "$ref": "#/definitions/models.SourceDto" - }, - "status": { - "type": "integer" - } - } - }, "v1.ListDiscordWebHooksQueueResults": { "type": "object", "properties": { @@ -1253,23 +1356,6 @@ } } }, - "v1.ListSources": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "payload": { - "type": "array", - "items": { - "$ref": "#/definitions/models.SourceDto" - } - }, - "status": { - "type": "integer" - } - } - }, "v1.ListSubscriptionDetails": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index c0a9f74..9638e3f 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -93,6 +93,15 @@ definitions: url: type: string type: object + domain.SourcesResponse: + properties: + message: + type: string + payload: + items: + $ref: '#/definitions/domain.SourceDto' + type: array + type: object models.ArticleDetailsDto: properties: authorImage: @@ -194,15 +203,6 @@ definitions: status: type: integer type: object - v1.GetSource: - properties: - message: - type: string - payload: - $ref: '#/definitions/models.SourceDto' - status: - type: integer - type: object v1.ListDiscordWebHooksQueueResults: properties: message: @@ -214,17 +214,6 @@ definitions: status: type: integer type: object - v1.ListSources: - properties: - message: - type: string - payload: - items: - $ref: '#/definitions/models.SourceDto' - type: array - status: - type: integer - type: object v1.ListSubscriptionDetails: properties: message: @@ -566,13 +555,18 @@ paths: - Settings /sources: get: + parameters: + - description: page number + in: query + name: page + type: string produces: - application/json responses: "200": description: ok schema: - $ref: '#/definitions/v1.ListSources' + $ref: '#/definitions/domain.SourcesResponse' "400": description: Unable to reach SQL or Data problems schema: @@ -587,26 +581,22 @@ paths: in: path name: id required: true - type: string + type: integer produces: - application/json responses: "200": description: ok schema: - $ref: '#/definitions/v1.GetSource' - "204": - description: No record found. - schema: - $ref: '#/definitions/v1.ApiError' + $ref: '#/definitions/domain.SourcesResponse' "400": - description: Unable to query SQL. + description: Bad Request schema: - $ref: '#/definitions/v1.ApiError' + $ref: '#/definitions/domain.BaseResponse' "500": - description: Failed to process data from SQL. + description: Internal Server Error schema: - $ref: '#/definitions/v1.ApiError' + $ref: '#/definitions/domain.BaseResponse' summary: Returns a single entity by ID tags: - Source @@ -628,8 +618,20 @@ paths: in: path name: id required: true - type: string - responses: {} + type: integer + responses: + "200": + description: ok + schema: + $ref: '#/definitions/domain.SourcesResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/domain.BaseResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/domain.BaseResponse' summary: Disables a source from processing. tags: - Source @@ -641,7 +643,19 @@ paths: name: id required: true type: string - responses: {} + responses: + "200": + description: ok + schema: + $ref: '#/definitions/domain.SourcesResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/domain.BaseResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/domain.BaseResponse' summary: Enables a source to continue processing. tags: - Source @@ -653,21 +667,25 @@ paths: name: source required: true type: string + - description: page number + in: query + name: page + type: string produces: - application/json responses: "200": description: ok schema: - $ref: '#/definitions/v1.ListSources' + $ref: '#/definitions/domain.SourcesResponse' "400": - description: Unable to query SQL. + description: Bad Request schema: - $ref: '#/definitions/v1.ApiError' + $ref: '#/definitions/domain.BaseResponse' "500": - description: Problems with data. + description: Internal Server Error schema: - $ref: '#/definitions/v1.ApiError' + $ref: '#/definitions/domain.BaseResponse' summary: 'Lists the top 50 records based on the name given. Example: reddit' tags: - Source @@ -690,19 +708,15 @@ paths: "200": description: ok schema: - $ref: '#/definitions/v1.GetSource' - "204": - description: No record found. - schema: - $ref: '#/definitions/v1.ApiError' + $ref: '#/definitions/domain.SourcesResponse' "400": - description: Unable to query SQL. + description: Bad Request schema: - $ref: '#/definitions/v1.ApiError' + $ref: '#/definitions/domain.BaseResponse' "500": - description: Failed to process data from SQL. + description: Internal Server Error schema: - $ref: '#/definitions/v1.ApiError' + $ref: '#/definitions/domain.BaseResponse' summary: Returns a single entity by ID tags: - Source @@ -719,10 +733,51 @@ paths: name: url required: true type: string - responses: {} + responses: + "200": + description: ok + schema: + $ref: '#/definitions/domain.SourcesResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/domain.BaseResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/domain.BaseResponse' summary: Creates a new reddit source to monitor. tags: - Source + /sources/new/rss: + post: + parameters: + - description: Site Name + in: query + name: name + required: true + type: string + - description: RSS Url + in: query + name: url + required: true + type: string + responses: + "200": + description: ok + schema: + $ref: '#/definitions/domain.SourcesResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/domain.BaseResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/domain.BaseResponse' + summary: Creates a new rss source to monitor. + tags: + - Source /sources/new/twitch: post: parameters: diff --git a/internal/domain/responses.go b/internal/domain/responses.go index 7c8693a..f6ca7ef 100644 --- a/internal/domain/responses.go +++ b/internal/domain/responses.go @@ -23,4 +23,9 @@ type ArticleDetailedResponse struct { type DiscordWebhookResponse struct { BaseResponse Payload []DiscordWebHookDto `json:"payload"` +} + +type SourcesResponse struct { + BaseResponse + Payload []SourceDto `json:"payload"` } \ No newline at end of file diff --git a/internal/handler/v1/handler.go b/internal/handler/v1/handler.go index 41bddcb..c510f65 100644 --- a/internal/handler/v1/handler.go +++ b/internal/handler/v1/handler.go @@ -23,14 +23,11 @@ type Handler struct { } const ( - HeaderContentType = "Content-Type" - - //ApplicationJson = "application/json" - - ErrParameterIdMissing = "The requested parameter ID was not found." - ErrParameterMissing = "The requested parameter was found found:" - ErrUnableToParseId = "Unable to parse the requested ID." - ErrRecordMissing = "The requested record was not found" + ErrParameterIdMissing = "The requested parameter ID was not found." + ErrParameterMissing = "The requested parameter was not found found:" + ErrUnableToParseId = "Unable to parse the requested ID" + ErrRecordMissing = "The requested record was not found" + ErrFailedToCreateRecord = "The record was not created due to a database problem" ResponseMessageSuccess = "Success" ) @@ -79,11 +76,11 @@ func NewServer(ctx context.Context, db *database.Queries, configs services.Confi sources.GET("/", s.listSources) sources.GET("/by/source", s.listSourcesBySource) sources.GET("/by/sourceAndName", s.GetSourceBySourceAndName) - sources.POST("/new/reddit", s.newRedditSource) - sources.POST("/new/youtube", s.newYoutubeSource) - sources.POST("/new/twitch", s.newTwitchSource) - - sources.GET("/:ID/", s.getSources) + //sources.POST("/new/reddit", s.newRedditSource) + //sources.POST("/new/youtube", s.newYoutubeSource) + //sources.POST("/new/twitch", s.newTwitchSource) + sources.POST("/new/rss", s.newRssSource) + sources.GET("/:ID/", s.getSource) sources.DELETE("/:ID/", s.deleteSources) sources.POST("/:ID/disable", s.disableSource) sources.POST("/:ID/enable", s.enableSource) diff --git a/internal/handler/v1/sources.go b/internal/handler/v1/sources.go index c725ddf..49e4d8f 100644 --- a/internal/handler/v1/sources.go +++ b/internal/handler/v1/sources.go @@ -5,11 +5,13 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" "strings" "git.jamestombleson.com/jtom38/newsbot-api/internal/database" "git.jamestombleson.com/jtom38/newsbot-api/internal/domain" "git.jamestombleson.com/jtom38/newsbot-api/internal/domain/models" + "git.jamestombleson.com/jtom38/newsbot-api/internal/services" "github.com/google/uuid" "github.com/labstack/echo/v4" ) @@ -26,115 +28,105 @@ type GetSource struct { // ListSources // @Summary Lists the top 50 records +// @Param page query string false "page number" // @Produce application/json // @Tags Source // @Router /sources [get] -// @Success 200 {object} ListSources "ok" -// @Failure 400 {object} domain.BaseResponse "Unable to reach SQL or Data problems" +// @Success 200 {object} domain.SourcesResponse "ok" +// @Failure 400 {object} domain.BaseResponse "Unable to reach SQL or Data problems" func (s *Handler) listSources(c echo.Context) error { - //TODO Add top? - /* - top := chi.URLParam(r, "top") - topInt, err := strconv.ParseInt(top, 0, 32) - if err != nil { - panic(err) - } - res, err := s.Db.ListSources(r.Context(), int32(topInt)) - */ - - p := ListSources{ - ApiStatusModel: ApiStatusModel{ - StatusCode: http.StatusOK, - Message: "OK", + resp := domain.SourcesResponse { + BaseResponse: domain.BaseResponse{ + Message: ResponseMessageSuccess, }, } + page, err := strconv.Atoi(c.QueryParam("page")) + if err != nil { + page = 0 + } + // Default way of showing all sources - items, err := s.dto.ListSources(c.Request().Context(), 50) + items, err := s.repo.Sources.List(c.Request().Context(), page, 25) if err != nil { s.WriteError(c, err, http.StatusInternalServerError) } - p.Payload = items - return c.JSON(http.StatusOK, p) + resp.Payload = services.SourcesToDto(items) + return c.JSON(http.StatusOK, resp) } // ListSourcesBySource // @Summary Lists the top 50 records based on the name given. Example: reddit -// @Param source query string true "Source Name" +// @Param source query string true "Source Name" +// @Param page query string false "page number" // @Produce application/json // @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." +// @Success 200 {object} domain.SourcesResponse "ok" +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse func (s *Handler) listSourcesBySource(c echo.Context) error { - //TODO Add top? - /* - top := chi.URLParam(r, "top") - topInt, err := strconv.ParseInt(top, 0, 32) - if err != nil { - panic(err) - } - res, err := s.Db.ListSources(r.Context(), int32(topInt)) - */ - - p := ListSources{ - ApiStatusModel: ApiStatusModel{ - StatusCode: http.StatusOK, - Message: "OK", + resp := domain.SourcesResponse{ + BaseResponse: domain.BaseResponse{ + Message: ResponseMessageSuccess, }, } source := c.QueryParam("source") + if source == "" { + s.WriteMessage(c, fmt.Sprintf("%s source", ErrParameterMissing), http.StatusBadRequest) + } + + page, err := strconv.Atoi(c.QueryParam("page")) + if err != nil { + page = 0 + } // Shows the list by Sources.source - res, err := s.dto.ListSourcesBySource(c.Request().Context(), source) + items, err := s.repo.Sources.ListBySource(c.Request().Context(), page, 25, source) if err != nil { return c.JSON(http.StatusInternalServerError, domain.BaseResponse{ Message: err.Error(), }) } - p.Payload = res - return c.JSON(http.StatusOK, p) + resp.Payload = services.SourcesToDto(items) + return c.JSON(http.StatusOK, resp) } // GetSource // @Summary Returns a single entity by ID -// @Param id path string true "uuid" +// @Param id path int true "uuid" // @Produce application/json // @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 *Handler) getSources(c echo.Context) error { - payload := GetSource{ - ApiStatusModel: ApiStatusModel{ - Message: "OK", - StatusCode: http.StatusOK, +// @Success 200 {object} domain.SourcesResponse "ok" +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse +func (s *Handler) getSource(c echo.Context) error { + resp := domain.SourcesResponse{ + BaseResponse: domain.BaseResponse{ + Message: ResponseMessageSuccess, }, } - id := c.Param("ID") - uuid, err := uuid.Parse(id) + id, err := strconv.Atoi(c.Param("ID")) if err != nil { return c.JSON(http.StatusBadRequest, domain.BaseResponse{ Message: ErrUnableToParseId, }) } - res, err := s.dto.GetSourceById(c.Request().Context(), uuid) + item, err := s.repo.Sources.GetById(c.Request().Context(), int64(id)) if err != nil { - return c.JSON(http.StatusInternalServerError, domain.BaseResponse{ - Message: ErrNoRecordFound, - }) + s.WriteError(c, err, http.StatusInternalServerError) } - payload.Payload = res - return c.JSON(http.StatusOK, payload) + var dto []domain.SourceDto + dto = append(dto, services.SourceToDto(item)) + resp.Payload = dto + return c.JSON(http.StatusOK, resp) } // GetSourceByNameAndSource @@ -144,15 +136,13 @@ func (s *Handler) getSources(c echo.Context) error { // @Produce application/json // @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." +// @Success 200 {object} domain.SourcesResponse "ok" +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse func (s *Handler) GetSourceBySourceAndName(c echo.Context) error { - p := GetSource{ - ApiStatusModel: ApiStatusModel{ - Message: "OK", - StatusCode: http.StatusOK, + resp := domain.SourcesResponse{ + BaseResponse: domain.BaseResponse{ + Message: ResponseMessageSuccess, }, } @@ -164,25 +154,15 @@ func (s *Handler) GetSourceBySourceAndName(c echo.Context) error { }) } - //name := c.QueryParam("name") - //if name == "" { - // s.WriteError(w, "Parameter 'name' was missing in the query.", http.StatusInternalServerError) - // return c.JSON(http.bad) - //} - - //source := query["source"][0] - //if source == "" { - // s.WriteError(w, "The parameter 'source' was missing in the query.", http.StatusInternalServerError) - // return - //} - - item, err := s.dto.GetSourceByNameAndSource(c.Request().Context(), param.Name, param.Source) + item, err := s.repo.Sources.GetBySourceAndName(c.Request().Context(), param.Source, param.Name) if err != nil { return c.JSON(http.StatusInternalServerError, err.Error()) } - p.Payload = item - return c.JSON(http.StatusOK, p) + var dto []domain.SourceDto + dto = append(dto, services.SourceToDto(item)) + resp.Payload = dto + return c.JSON(http.StatusOK, resp) } // NewRedditSource @@ -191,7 +171,16 @@ func (s *Handler) GetSourceBySourceAndName(c echo.Context) error { // @Param url query string true "url" // @Tags Source // @Router /sources/new/reddit [post] +// @Success 200 {object} domain.SourcesResponse "ok" +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse func (s *Handler) newRedditSource(c echo.Context) error { + resp := domain.SourcesResponse{ + BaseResponse: domain.BaseResponse{ + Message: ResponseMessageSuccess, + }, + } + var param domain.NewSourceParamRequest err := c.Bind(¶m) if err != nil { @@ -199,10 +188,6 @@ func (s *Handler) newRedditSource(c echo.Context) error { Message: err.Error(), }) } - //query := r.URL.Query() - //_name := query["name"][0] - //_url := query["url"][0] - //_tags := query["tags"][0] if param.Url == "" { return c.JSON(http.StatusBadRequest, domain.BaseResponse{ @@ -215,40 +200,25 @@ func (s *Handler) newRedditSource(c echo.Context) error { }) } - /* - var tags string - if _tags == "" { - tags = fmt.Sprintf("twitch, %v", _name) - } else { - } - */ - - tags := fmt.Sprintf("twitch, %v", param.Name) - - params := database.CreateSourceParams{ - ID: uuid.New(), - Site: "reddit", - Name: param.Name, - Source: "reddit", - Type: "feed", - Enabled: true, - Url: param.Url, - Tags: tags, - } - err = s.Db.CreateSource(c.Request().Context(), params) + tags := fmt.Sprintf("twitch, %v, %s", param.Name, param.Tags) + rows, err := s.repo.Sources.Create(c.Request().Context(), domain.SourceCollectorReddit, param.Name, param.Url, tags, true) if err != nil { - return c.JSON(http.StatusInternalServerError, domain.BaseResponse{ - Message: err.Error(), - }) + s.WriteError(c, err, http.StatusInternalServerError) } - //s.WriteJson(w, ¶ms) - bJson, err := json.Marshal(¶ms) + if rows != 1 { + s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError) + } + + item, err := s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorReddit, param.Name) if err != nil { - return c.JSON(http.StatusInternalServerError, err.Error()) + s.WriteError(c, err, http.StatusInternalServerError) } - return c.JSON(http.StatusOK, bJson) + var dto []domain.SourceDto + dto = append(dto, services.SourceToDto(item)) + resp.Payload = dto + return c.JSON(http.StatusOK, resp) } // NewYoutubeSource @@ -362,6 +332,57 @@ func (s *Handler) newTwitchSource(c echo.Context) error { return c.JSON(http.StatusOK, bJson) } +// NewRssSource +// @Summary Creates a new rss source to monitor. +// @Param name query string true "Site Name" +// @Param url query string true "RSS Url" +// @Tags Source +// @Router /sources/new/rss [post] +// @Success 200 {object} domain.SourcesResponse "ok" +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse +func (s *Handler) newRssSource(c echo.Context) error { + resp := domain.SourcesResponse{ + BaseResponse: domain.BaseResponse{ + Message: ResponseMessageSuccess, + }, + } + + var param domain.NewSourceParamRequest + err := c.Bind(¶m) + if err != nil { + return c.JSON(http.StatusBadRequest, domain.BaseResponse{ + Message: err.Error(), + }) + } + + if param.Url == "" { + return c.JSON(http.StatusBadRequest, domain.BaseResponse{ + Message: "Url is missing a value", + }) + } + + tags := fmt.Sprintf("rss, %v, %s", param.Name, param.Tags) + rows, err := s.repo.Sources.Create(c.Request().Context(), domain.SourceCollectorRss, param.Name, param.Url, tags, true) + if err != nil { + s.WriteError(c, err, http.StatusInternalServerError) + } + + if rows != 1 { + s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError) + } + + item, err := s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorRss, param.Name) + if err != nil { + s.WriteError(c, err, http.StatusInternalServerError) + } + + var dto []domain.SourceDto + dto = append(dto, services.SourceToDto(item)) + resp.Payload = dto + return c.JSON(http.StatusOK, resp) +} + // DeleteSource // @Summary Marks a source as deleted based on its ID value. // @Param id path string true "id" @@ -409,46 +430,44 @@ func (s *Handler) deleteSources(c echo.Context) error { // DisableSource // @Summary Disables a source from processing. -// @Param id path string true "id" +// @Param id path int true "id" // @Tags Source // @Router /sources/{id}/disable [post] +// @Success 200 {object} domain.SourcesResponse "ok" +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse func (s *Handler) disableSource(c echo.Context) error { - id := c.Param("ID") - uuid, err := uuid.Parse(id) + resp := domain.SourcesResponse { + BaseResponse: domain.BaseResponse{ + Message: ResponseMessageSuccess, + }, + } + + id, err := strconv.Atoi(c.Param("ID")) if err != nil { - return c.JSON(http.StatusBadRequest, domain.BaseResponse{ - Message: err.Error(), - }) + s.WriteError(c, err, http.StatusBadRequest) } // Check to make sure we can find the record - _, err = s.Db.GetSourceByID(c.Request().Context(), uuid) + _, err = s.repo.Sources.GetById(c.Request().Context(), int64(id)) if err != nil { - return c.JSON(http.StatusInternalServerError, domain.BaseResponse{ - Message: err.Error(), - }) + s.WriteError(c, err, http.StatusBadRequest) } - err = s.Db.DisableSource(context.Background(), uuid) + _, err = s.repo.Sources.Disable(c.Request().Context(), int64(id)) if err != nil { - return c.JSON(http.StatusInternalServerError, domain.BaseResponse{ - Message: err.Error(), - }) + s.WriteError(c, err, http.StatusInternalServerError) } - p := ApiStatusModel{ - Message: "OK", - StatusCode: http.StatusOK, - } - - b, err := json.Marshal(p) + item, err := s.repo.Sources.GetById(c.Request().Context(), int64(id)) if err != nil { - return c.JSON(http.StatusInternalServerError, domain.BaseResponse{ - Message: err.Error(), - }) + s.WriteError(c, err, http.StatusInternalServerError) } - return c.JSON(http.StatusOK, b) + var dto []domain.SourceDto + dto = append(dto, services.SourceToDto(item)) + resp.Payload = dto + return c.JSON(http.StatusOK, resp) } // EnableSource @@ -456,39 +475,39 @@ func (s *Handler) disableSource(c echo.Context) error { // @Param id path string true "id" // @Tags Source // @Router /sources/{id}/enable [post] +// @Success 200 {object} domain.SourcesResponse "ok" +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse func (s *Handler) enableSource(c echo.Context) error { - id := c.Param("ID") - uuid, err := uuid.Parse(id) + resp := domain.SourcesResponse { + BaseResponse: domain.BaseResponse{ + Message: ResponseMessageSuccess, + }, + } + + id, err := strconv.Atoi(c.Param("ID")) if err != nil { - return c.JSON(http.StatusBadRequest, err.Error()) + s.WriteError(c, err, http.StatusBadRequest) } // Check to make sure we can find the record - _, err = s.Db.GetSourceByID(c.Request().Context(), uuid) + _, err = s.repo.Sources.GetById(c.Request().Context(), int64(id)) if err != nil { - return c.JSON(http.StatusInternalServerError, domain.BaseResponse{ - Message: err.Error(), - }) + s.WriteError(c, err, http.StatusBadRequest) } - err = s.Db.EnableSource(c.Request().Context(), uuid) + _, err = s.repo.Sources.Enable(c.Request().Context(), int64(id)) if err != nil { - return c.JSON(http.StatusInternalServerError, domain.BaseResponse{ - Message: err.Error(), - }) + s.WriteError(c, err, http.StatusInternalServerError) } - p := ApiStatusModel{ - Message: "OK", - StatusCode: http.StatusOK, - } - - b, err := json.Marshal(p) + item, err := s.repo.Sources.GetById(c.Request().Context(), int64(id)) if err != nil { - return c.JSON(http.StatusInternalServerError, domain.BaseResponse{ - Message: err.Error(), - }) + s.WriteError(c, err, http.StatusInternalServerError) } - return c.JSON(http.StatusOK, b) + var dto []domain.SourceDto + dto = append(dto, services.SourceToDto(item)) + resp.Payload = dto + return c.JSON(http.StatusOK, resp) } diff --git a/internal/repository/source.go b/internal/repository/source.go index e17bd61..b534c4b 100644 --- a/internal/repository/source.go +++ b/internal/repository/source.go @@ -14,6 +14,7 @@ type Sources interface { GetById(ctx context.Context, id int64) (domain.SourceEntity, error) GetByDisplayName(ctx context.Context, displayName string) (domain.SourceEntity, error) GetBySource(ctx context.Context, source string) (domain.SourceEntity, error) + GetBySourceAndName(ctx context.Context, source, name string) (domain.SourceEntity, error) List(ctx context.Context, page, limit int) ([]domain.SourceEntity, error) ListBySource(ctx context.Context, page, limit int, source string) ([]domain.SourceEntity, error) Enable(ctx context.Context, id int64) (int64, error) @@ -115,6 +116,29 @@ func (r sourceRepository) GetBySource(ctx context.Context, source string) (domai return data[0], nil } +func (r sourceRepository) GetBySourceAndName(ctx context.Context, source, name string) (domain.SourceEntity, error) { + b := sqlbuilder.NewSelectBuilder() + b.Select("*") + b.From("Sources").Where( + b.Equal("Source", source), + b.Equal("Name", name), + ) + b.Limit(1) + query, args := b.Build() + + rows, err := r.conn.QueryContext(ctx, query, args...) + if err != nil { + return domain.SourceEntity{}, err + } + + data, err := r.processRows(rows) + if len(data) == 0 { + return domain.SourceEntity{}, err + } + + return data[0], nil +} + func (r sourceRepository) List(ctx context.Context, page, limit int) ([]domain.SourceEntity, error) { builder := sqlbuilder.NewSelectBuilder() builder.Select("*")