Compare commits
6 Commits
main
...
features/h
Author | SHA1 | Date | |
---|---|---|---|
fbed111fbb | |||
43a086cc02 | |||
2afad5d761 | |||
fcf80ec5fe | |||
ce45b509d3 | |||
e48b64bbaa |
60
.drone.yaml
60
.drone.yaml
@ -1,60 +0,0 @@
|
|||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: buildLatestImage
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: buildLatestImage
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
repo: jtom38/newsbot-collector
|
|
||||||
username: jtom38
|
|
||||||
password:
|
|
||||||
from_secret: DockerPushPat
|
|
||||||
trigger:
|
|
||||||
branch:
|
|
||||||
include:
|
|
||||||
- main
|
|
||||||
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- pull_request
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: buildReleaseImage
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: buildReleaseImage
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
repo: jtom38/newsbot-collector
|
|
||||||
username: jtom38
|
|
||||||
password:
|
|
||||||
from_secret: DockerPushPat
|
|
||||||
trigger:
|
|
||||||
branch:
|
|
||||||
include:
|
|
||||||
- releases/*
|
|
||||||
ref:
|
|
||||||
include:
|
|
||||||
- refs/tags/**
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: PullRequestCompileTest
|
|
||||||
steps:
|
|
||||||
- name: Compile project
|
|
||||||
image: golang:1.22
|
|
||||||
commands:
|
|
||||||
- go test ./internal/repository
|
|
||||||
- go build ./cmd/server.go
|
|
||||||
-
|
|
||||||
trigger:
|
|
||||||
event:
|
|
||||||
- pull_request
|
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -2,8 +2,6 @@
|
|||||||
dev.session.sql
|
dev.session.sql
|
||||||
__debug_bin
|
__debug_bin
|
||||||
server
|
server
|
||||||
.vscode
|
|
||||||
openapi.json
|
|
||||||
|
|
||||||
# Binaries for programs and plugins
|
# Binaries for programs and plugins
|
||||||
*.exe
|
*.exe
|
||||||
@ -12,8 +10,7 @@ openapi.json
|
|||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
collector
|
collector
|
||||||
newsbot.db
|
*.db
|
||||||
tmp/
|
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch Package",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "auto",
|
||||||
|
"program": "./cmd/server.go"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
15
Dockerfile
15
Dockerfile
@ -5,10 +5,14 @@ WORKDIR /app
|
|||||||
|
|
||||||
# Always make sure that swagger docs are updated
|
# Always make sure that swagger docs are updated
|
||||||
RUN go install github.com/swaggo/swag/cmd/swag@latest
|
RUN go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
RUN /go/bin/swag init -g cmd/server.go
|
RUN /go/bin/swag i
|
||||||
|
|
||||||
#RUN go build .
|
# Always build the latest sql queries
|
||||||
#RUN go install github.com/pressly/goose/v3/cmd/goose@latest
|
RUN go install github.com/kyleconroy/sqlc/cmd/sqlc@latest
|
||||||
|
RUN /go/bin/sqlc generate
|
||||||
|
|
||||||
|
RUN go build .
|
||||||
|
RUN go install github.com/pressly/goose/v3/cmd/goose@latest
|
||||||
|
|
||||||
FROM alpine:latest as app
|
FROM alpine:latest as app
|
||||||
|
|
||||||
@ -17,7 +21,8 @@ RUN apk --no-cache add libc6-compat
|
|||||||
RUN apk --no-cache add chromium
|
RUN apk --no-cache add chromium
|
||||||
|
|
||||||
RUN mkdir /app && mkdir /app/migrations
|
RUN mkdir /app && mkdir /app/migrations
|
||||||
COPY --from=build /app/server /app
|
COPY --from=build /app/collector /app
|
||||||
COPY ./internal/database/migrations/ /app/migrations
|
COPY --from=build /go/bin/goose /app
|
||||||
|
COPY ./database/migrations/ /app/migrations
|
||||||
|
|
||||||
CMD [ "/app/collector" ]
|
CMD [ "/app/collector" ]
|
6
api.http
6
api.http
@ -1,6 +0,0 @@
|
|||||||
### Select Sources fro mthe top
|
|
||||||
GET http://localhost:8081/api/v1/sources/
|
|
||||||
|
|
||||||
|
|
||||||
### Select Sources by type
|
|
||||||
GET http://localhost:8081/api/v1/sources/by/source?source=rss
|
|
3756
api/api.gen.go
3756
api/api.gen.go
File diff suppressed because it is too large
Load Diff
@ -1,6 +0,0 @@
|
|||||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/deepmap/oapi-codegen/HEAD/configuration-schema.json
|
|
||||||
package: api
|
|
||||||
output: api.gen.go
|
|
||||||
generate:
|
|
||||||
models: true
|
|
||||||
client: true
|
|
@ -3,9 +3,8 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
_ "github.com/glebarez/go-sqlite"
|
_ "github.com/glebarez/go-sqlite"
|
||||||
"github.com/pressly/goose/v3"
|
"github.com/pressly/goose/v3"
|
||||||
@ -19,10 +18,6 @@ import (
|
|||||||
// @title NewsBot collector
|
// @title NewsBot collector
|
||||||
// @version 0.1
|
// @version 0.1
|
||||||
// @BasePath /api
|
// @BasePath /api
|
||||||
// @securityDefinitions.apikey Bearer
|
|
||||||
// @in header
|
|
||||||
// @name Authorization
|
|
||||||
// @description Type "Bearer" followed by a space and JWT token.
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
@ -36,11 +31,21 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = migrateDatabase(db)
|
err = goose.SetDialect("sqlite3")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Print(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = goose.Up(db, "../internal/database/migrations")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//queues := services.NewQueues(db)
|
||||||
|
//queues.RssIndex.Send(ctx, goqite.Message{
|
||||||
|
// Body: []byte("hello world"),
|
||||||
|
//})
|
||||||
|
|
||||||
c := cron.NewScheduler(ctx, db)
|
c := cron.NewScheduler(ctx, db)
|
||||||
c.Start()
|
c.Start()
|
||||||
|
|
||||||
@ -50,41 +55,8 @@ func main() {
|
|||||||
fmt.Printf("API: http://%v:8081/api\r\n", configs.ServerAddress)
|
fmt.Printf("API: http://%v:8081/api\r\n", configs.ServerAddress)
|
||||||
fmt.Printf("Swagger: http://%v:8081/swagger/index.html\r\n", configs.ServerAddress)
|
fmt.Printf("Swagger: http://%v:8081/swagger/index.html\r\n", configs.ServerAddress)
|
||||||
|
|
||||||
server.Router.Start(":8081")
|
err = http.ListenAndServe(":8081", server.Router)
|
||||||
}
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
func migrateDatabase(db *sql.DB) error {
|
}
|
||||||
err := goose.SetDialect("sqlite3")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = goose.Up(db, "../internal/database/migrations")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = os.Stat("./migrations")
|
|
||||||
if err == nil {
|
|
||||||
|
|
||||||
err = goose.Up(db, "../internal/database/migrations")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = os.Stat("../internal/database/migrations")
|
|
||||||
if err == nil {
|
|
||||||
|
|
||||||
err = goose.Up(db, "../internal/database/migrations")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New("failed to find the migration files")
|
|
||||||
}
|
}
|
||||||
|
754
docs/docs.go
754
docs/docs.go
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,4 @@
|
|||||||
|
basePath: /api
|
||||||
definitions:
|
definitions:
|
||||||
domain.ArticleAndSourceModel:
|
domain.ArticleAndSourceModel:
|
||||||
properties:
|
properties:
|
||||||
@ -8,8 +9,6 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
domain.ArticleDetailedResponse:
|
domain.ArticleDetailedResponse:
|
||||||
properties:
|
properties:
|
||||||
isError:
|
|
||||||
type: boolean
|
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
payload:
|
payload:
|
||||||
@ -42,8 +41,6 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
domain.ArticleResponse:
|
domain.ArticleResponse:
|
||||||
properties:
|
properties:
|
||||||
isError:
|
|
||||||
type: boolean
|
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
payload:
|
payload:
|
||||||
@ -53,8 +50,6 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
domain.BaseResponse:
|
domain.BaseResponse:
|
||||||
properties:
|
properties:
|
||||||
isError:
|
|
||||||
type: boolean
|
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
@ -76,8 +71,6 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
domain.DiscordWebhookResponse:
|
domain.DiscordWebhookResponse:
|
||||||
properties:
|
properties:
|
||||||
isError:
|
|
||||||
type: boolean
|
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
payload:
|
payload:
|
||||||
@ -85,35 +78,6 @@ definitions:
|
|||||||
$ref: '#/definitions/domain.DiscordWebHookDto'
|
$ref: '#/definitions/domain.DiscordWebHookDto'
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
domain.LoginResponse:
|
|
||||||
properties:
|
|
||||||
isError:
|
|
||||||
type: boolean
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
refreshToken:
|
|
||||||
type: string
|
|
||||||
token:
|
|
||||||
type: string
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.NewSourceParamRequest:
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
tags:
|
|
||||||
type: string
|
|
||||||
url:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.RefreshTokenRequest:
|
|
||||||
properties:
|
|
||||||
refreshToken:
|
|
||||||
type: string
|
|
||||||
username:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.SourceDto:
|
domain.SourceDto:
|
||||||
properties:
|
properties:
|
||||||
enabled:
|
enabled:
|
||||||
@ -131,8 +95,6 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
domain.SourcesResponse:
|
domain.SourcesResponse:
|
||||||
properties:
|
properties:
|
||||||
isError:
|
|
||||||
type: boolean
|
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
payload:
|
payload:
|
||||||
@ -140,28 +102,18 @@ definitions:
|
|||||||
$ref: '#/definitions/domain.SourceDto'
|
$ref: '#/definitions/domain.SourceDto'
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
domain.UpdateScopesRequest:
|
|
||||||
properties:
|
|
||||||
scopes:
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
username:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- scopes
|
|
||||||
type: object
|
|
||||||
info:
|
info:
|
||||||
contact: {}
|
contact: {}
|
||||||
|
title: NewsBot collector
|
||||||
|
version: "0.1"
|
||||||
paths:
|
paths:
|
||||||
/v1/articles:
|
/articles:
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
- description: page number
|
- description: page number
|
||||||
in: query
|
in: query
|
||||||
name: page
|
name: page
|
||||||
required: true
|
type: string
|
||||||
type: integer
|
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
@ -177,17 +129,15 @@ paths:
|
|||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Lists the top 25 records ordering from newest to oldest.
|
summary: Lists the top 25 records ordering from newest to oldest.
|
||||||
tags:
|
tags:
|
||||||
- Articles
|
- Articles
|
||||||
/v1/articles/{id}:
|
/articles/{ID}:
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
- description: int
|
- description: int
|
||||||
in: path
|
in: path
|
||||||
name: id
|
name: ID
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
produces:
|
produces:
|
||||||
@ -205,17 +155,15 @@ paths:
|
|||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Returns an article based on defined ID.
|
summary: Returns an article based on defined ID.
|
||||||
tags:
|
tags:
|
||||||
- Articles
|
- Articles
|
||||||
/v1/articles/{id}/details:
|
/articles/{ID}/details:
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
- description: int
|
- description: int
|
||||||
in: path
|
in: path
|
||||||
name: id
|
name: ID
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
produces:
|
produces:
|
||||||
@ -233,16 +181,14 @@ paths:
|
|||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Returns an article and source based on defined ID.
|
summary: Returns an article and source based on defined ID.
|
||||||
tags:
|
tags:
|
||||||
- Articles
|
- Articles
|
||||||
/v1/articles/by/source/{id}:
|
/articles/by/sourceid:
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
- description: source id
|
- description: source id
|
||||||
in: path
|
in: query
|
||||||
name: id
|
name: id
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
@ -265,13 +211,11 @@ paths:
|
|||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Finds the articles based on the SourceID provided. Returns the top
|
summary: Finds the articles based on the SourceID provided. Returns the top
|
||||||
25.
|
25.
|
||||||
tags:
|
tags:
|
||||||
- Articles
|
- Articles
|
||||||
/v1/discord/webhooks:
|
/discord/webhooks:
|
||||||
get:
|
get:
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
@ -283,17 +227,16 @@ paths:
|
|||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Returns the top 100
|
summary: Returns the top 100
|
||||||
tags:
|
tags:
|
||||||
- DiscordWebhook
|
- Discord
|
||||||
/v1/discord/webhooks/{id}:
|
- Webhook
|
||||||
|
/discord/webhooks/{ID}:
|
||||||
delete:
|
delete:
|
||||||
parameters:
|
parameters:
|
||||||
- description: id
|
- description: id
|
||||||
@ -309,16 +252,54 @@ paths:
|
|||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Deletes a record by ID.
|
summary: Deletes a record by ID.
|
||||||
tags:
|
tags:
|
||||||
- DiscordWebhook
|
- Discord
|
||||||
|
- Webhook
|
||||||
|
/discord/webhooks/{ID}/disable:
|
||||||
|
post:
|
||||||
|
parameters:
|
||||||
|
- description: id
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
|
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: integer
|
||||||
|
responses: {}
|
||||||
|
summary: Enables a source to continue processing.
|
||||||
|
tags:
|
||||||
|
- Discord
|
||||||
|
- Webhook
|
||||||
|
/discord/webhooks/{id}:
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
- description: id
|
- description: id
|
||||||
@ -336,69 +317,16 @@ paths:
|
|||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Returns the top 100 entries from the queue to be processed.
|
summary: Returns the top 100 entries from the queue to be processed.
|
||||||
tags:
|
tags:
|
||||||
- DiscordWebhook
|
- Discord
|
||||||
/v1/discord/webhooks/{id}/disable:
|
- Webhook
|
||||||
post:
|
/discord/webhooks/by/serverAndChannel:
|
||||||
parameters:
|
|
||||||
- description: id
|
|
||||||
in: path
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
type: integer
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Disables a Webhook from being used.
|
|
||||||
tags:
|
|
||||||
- DiscordWebhook
|
|
||||||
/v1/discord/webhooks/{id}/enable:
|
|
||||||
post:
|
|
||||||
parameters:
|
|
||||||
- description: id
|
|
||||||
in: path
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
type: integer
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Enables a source to continue processing.
|
|
||||||
tags:
|
|
||||||
- DiscordWebhook
|
|
||||||
/v1/discord/webhooks/by/serverAndChannel:
|
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
- description: Fancy Server
|
- description: Fancy Server
|
||||||
@ -421,17 +349,16 @@ paths:
|
|||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Returns all the known web hooks based on the Server and Channel given.
|
summary: Returns all the known web hooks based on the Server and Channel given.
|
||||||
tags:
|
tags:
|
||||||
- DiscordWebhook
|
- Discord
|
||||||
/v1/discord/webhooks/new:
|
- Webhook
|
||||||
|
/discord/webhooks/new:
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
- description: url
|
- description: url
|
||||||
@ -457,17 +384,30 @@ paths:
|
|||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.DiscordWebhookResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Creates a new record for a discord web hook to post data to.
|
summary: Creates a new record for a discord web hook to post data to.
|
||||||
tags:
|
tags:
|
||||||
- DiscordWebhook
|
- Discord
|
||||||
/v1/sources:
|
- Webhook
|
||||||
|
/settings/{key}:
|
||||||
|
get:
|
||||||
|
parameters:
|
||||||
|
- description: Settings Key value
|
||||||
|
in: path
|
||||||
|
name: key
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses: {}
|
||||||
|
summary: Returns a object based on the Key that was given.
|
||||||
|
tags:
|
||||||
|
- Settings
|
||||||
|
/sources:
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
- description: page number
|
- description: page number
|
||||||
@ -484,16 +424,14 @@ paths:
|
|||||||
"400":
|
"400":
|
||||||
description: Unable to reach SQL or Data problems
|
description: Unable to reach SQL or Data problems
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Lists the top 50 records
|
summary: Lists the top 50 records
|
||||||
tags:
|
tags:
|
||||||
- Source
|
- Source
|
||||||
/v1/sources/{id}:
|
/sources/{id}:
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
- description: id
|
- description: uuid
|
||||||
in: path
|
in: path
|
||||||
name: id
|
name: id
|
||||||
required: true
|
required: true
|
||||||
@ -508,13 +446,11 @@ paths:
|
|||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Returns a single entity by ID
|
summary: Returns a single entity by ID
|
||||||
tags:
|
tags:
|
||||||
- Source
|
- Source
|
||||||
@ -524,26 +460,12 @@ paths:
|
|||||||
in: path
|
in: path
|
||||||
name: id
|
name: id
|
||||||
required: true
|
required: true
|
||||||
type: integer
|
type: string
|
||||||
responses:
|
responses: {}
|
||||||
"200":
|
|
||||||
description: ok
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Marks a source as deleted based on its ID value.
|
summary: Marks a source as deleted based on its ID value.
|
||||||
tags:
|
tags:
|
||||||
- Source
|
- Source
|
||||||
/v1/sources/{id}/disable:
|
/sources/{id}/disable:
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
- description: id
|
- description: id
|
||||||
@ -559,17 +481,15 @@ paths:
|
|||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Disables a source from processing.
|
summary: Disables a source from processing.
|
||||||
tags:
|
tags:
|
||||||
- Source
|
- Source
|
||||||
/v1/sources/{id}/enable:
|
/sources/{id}/enable:
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
- description: id
|
- description: id
|
||||||
@ -585,17 +505,15 @@ paths:
|
|||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Enables a source to continue processing.
|
summary: Enables a source to continue processing.
|
||||||
tags:
|
tags:
|
||||||
- Source
|
- Source
|
||||||
/v1/sources/by/source:
|
/sources/by/source:
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
- description: Source Name
|
- description: Source Name
|
||||||
@ -617,17 +535,15 @@ paths:
|
|||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: 'Lists the top 50 records based on the name given. Example: reddit'
|
summary: 'Lists the top 50 records based on the name given. Example: reddit'
|
||||||
tags:
|
tags:
|
||||||
- Source
|
- Source
|
||||||
/v1/sources/by/sourceAndName:
|
/sources/by/sourceAndName:
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
- description: dadjokes
|
- description: dadjokes
|
||||||
@ -655,12 +571,10 @@ paths:
|
|||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Returns a single entity by ID
|
summary: Returns a single entity by ID
|
||||||
tags:
|
tags:
|
||||||
- Source
|
- Source
|
||||||
/v1/sources/new/reddit:
|
/sources/new/reddit:
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
- description: name
|
- description: name
|
||||||
@ -681,78 +595,23 @@ paths:
|
|||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Creates a new reddit source to monitor.
|
summary: Creates a new reddit source to monitor.
|
||||||
tags:
|
tags:
|
||||||
- Source
|
- Source
|
||||||
/v1/sources/new/rss:
|
/sources/new/rss:
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
- description: body
|
- description: Site Name
|
||||||
in: body
|
|
||||||
name: request
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.NewSourceParamRequest'
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: ok
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Creates a new rss source to monitor.
|
|
||||||
tags:
|
|
||||||
- Source
|
|
||||||
/v1/sources/new/twitch:
|
|
||||||
post:
|
|
||||||
parameters:
|
|
||||||
- description: name
|
|
||||||
in: query
|
in: query
|
||||||
name: name
|
name: name
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
responses:
|
- description: RSS Url
|
||||||
"200":
|
|
||||||
description: ok
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Creates a new twitch source to monitor.
|
|
||||||
tags:
|
|
||||||
- Source
|
|
||||||
/v1/sources/new/youtube:
|
|
||||||
post:
|
|
||||||
parameters:
|
|
||||||
- description: name
|
|
||||||
in: query
|
|
||||||
name: name
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- description: url
|
|
||||||
in: query
|
in: query
|
||||||
name: url
|
name: url
|
||||||
required: true
|
required: true
|
||||||
@ -765,181 +624,41 @@ paths:
|
|||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.SourcesResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
security:
|
summary: Creates a new rss source to monitor.
|
||||||
- Bearer: []
|
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.
|
summary: Creates a new youtube source to monitor.
|
||||||
tags:
|
tags:
|
||||||
- Source
|
- Source
|
||||||
/v1/users/login:
|
|
||||||
post:
|
|
||||||
parameters:
|
|
||||||
- in: formData
|
|
||||||
name: password
|
|
||||||
type: string
|
|
||||||
- in: formData
|
|
||||||
name: username
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.LoginResponse'
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.LoginResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.LoginResponse'
|
|
||||||
summary: Logs into the API and returns a bearer token if successful
|
|
||||||
tags:
|
|
||||||
- Users
|
|
||||||
/v1/users/refresh/sessionToken:
|
|
||||||
post:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Revokes the current session token and replaces it with a new one.
|
|
||||||
tags:
|
|
||||||
- Users
|
|
||||||
/v1/users/refresh/token:
|
|
||||||
post:
|
|
||||||
parameters:
|
|
||||||
- description: body
|
|
||||||
in: body
|
|
||||||
name: request
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.RefreshTokenRequest'
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.LoginResponse'
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Generates a new token
|
|
||||||
tags:
|
|
||||||
- Users
|
|
||||||
/v1/users/register:
|
|
||||||
post:
|
|
||||||
parameters:
|
|
||||||
- in: formData
|
|
||||||
name: password
|
|
||||||
type: string
|
|
||||||
- in: formData
|
|
||||||
name: username
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"201":
|
|
||||||
description: Created
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
|
||||||
summary: Creates a new user
|
|
||||||
tags:
|
|
||||||
- Users
|
|
||||||
/v1/users/scopes/add:
|
|
||||||
post:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
parameters:
|
|
||||||
- description: body
|
|
||||||
in: body
|
|
||||||
name: request
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.UpdateScopesRequest'
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Adds a new scope to a user account
|
|
||||||
tags:
|
|
||||||
- Users
|
|
||||||
/v1/users/scopes/remove:
|
|
||||||
post:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
parameters:
|
|
||||||
- description: body
|
|
||||||
in: body
|
|
||||||
name: request
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.UpdateScopesRequest'
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Adds a new scope to a user account
|
|
||||||
tags:
|
|
||||||
- Users
|
|
||||||
swagger: "2.0"
|
swagger: "2.0"
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
package domain
|
|
||||||
|
|
||||||
type LoginFormRequest struct {
|
|
||||||
Username string `form:"username"`
|
|
||||||
Password string `form:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetSourceBySourceAndNameParamRequest struct {
|
|
||||||
Name string `query:"name"`
|
|
||||||
Source string `query:"source"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type NewSourceParamRequest struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Url string `json:"url"`
|
|
||||||
Tags string `json:"tags"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RefreshTokenRequest struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
RefreshToken string `json:"refreshToken"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateScopesRequest struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
Scopes []string `json:"scopes" validate:"required"`
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package domain
|
|
||||||
|
|
||||||
const (
|
|
||||||
ScopeAll = "newsbot:all"
|
|
||||||
|
|
||||||
ScopeArticleRead = "newsbot:article:read"
|
|
||||||
ScopeArticleDisable = "newsbot:article:disable"
|
|
||||||
|
|
||||||
ScopeSourceRead = "newsbot:source:read"
|
|
||||||
ScopeSourceCreate = "newsbot:source:create"
|
|
||||||
|
|
||||||
ScopeDiscordWebHookCreate = "newsbot:discordwebhook:create"
|
|
||||||
ScopeDiscordWebhookRead = "newsbot:discordwebhook:read"
|
|
||||||
)
|
|
5
go.mod
5
go.mod
@ -6,12 +6,11 @@ require (
|
|||||||
github.com/PuerkitoBio/goquery v1.8.0
|
github.com/PuerkitoBio/goquery v1.8.0
|
||||||
github.com/glebarez/go-sqlite v1.22.0
|
github.com/glebarez/go-sqlite v1.22.0
|
||||||
github.com/go-rod/rod v0.107.1
|
github.com/go-rod/rod v0.107.1
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/huandu/go-sqlbuilder v1.27.1
|
github.com/huandu/go-sqlbuilder v1.27.1
|
||||||
github.com/joho/godotenv v1.4.0
|
github.com/joho/godotenv v1.4.0
|
||||||
github.com/labstack/echo-jwt/v4 v4.2.0
|
|
||||||
github.com/labstack/echo/v4 v4.12.0
|
github.com/labstack/echo/v4 v4.12.0
|
||||||
|
github.com/maragudk/goqite v0.1.0
|
||||||
github.com/mmcdole/gofeed v1.1.3
|
github.com/mmcdole/gofeed v1.1.3
|
||||||
github.com/nicklaw5/helix/v2 v2.4.0
|
github.com/nicklaw5/helix/v2 v2.4.0
|
||||||
github.com/pressly/goose/v3 v3.20.0
|
github.com/pressly/goose/v3 v3.20.0
|
||||||
@ -24,7 +23,6 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
|
||||||
github.com/huandu/xstrings v1.3.2 // indirect
|
github.com/huandu/xstrings v1.3.2 // indirect
|
||||||
github.com/labstack/gommon v0.4.2 // indirect
|
github.com/labstack/gommon v0.4.2 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
@ -38,7 +36,6 @@ require (
|
|||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
|
||||||
modernc.org/libc v1.41.0 // indirect
|
modernc.org/libc v1.41.0 // indirect
|
||||||
modernc.org/mathutil v1.6.0 // indirect
|
modernc.org/mathutil v1.6.0 // indirect
|
||||||
modernc.org/memory v1.7.2 // indirect
|
modernc.org/memory v1.7.2 // indirect
|
||||||
|
14
go.sum
14
go.sum
@ -31,12 +31,8 @@ github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrK
|
|||||||
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||||
github.com/go-rod/rod v0.107.1 h1:wRxTTAXJ0JUnoSGcyGAOubpdrToWIKPCnLu3av8EDFY=
|
github.com/go-rod/rod v0.107.1 h1:wRxTTAXJ0JUnoSGcyGAOubpdrToWIKPCnLu3av8EDFY=
|
||||||
github.com/go-rod/rod v0.107.1/go.mod h1:Au6ufsz7KyXUJVnw6Ljs1nFpsopy+9AJ/lBwGauYBVg=
|
github.com/go-rod/rod v0.107.1/go.mod h1:Au6ufsz7KyXUJVnw6Ljs1nFpsopy+9AJ/lBwGauYBVg=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||||
@ -62,8 +58,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/labstack/echo-jwt/v4 v4.2.0 h1:odSISV9JgcSCuhgQSV/6Io3i7nUmfM/QkBeR5GVJj5c=
|
|
||||||
github.com/labstack/echo-jwt/v4 v4.2.0/go.mod h1:MA2RqdXdEn4/uEglx0HcUOgQSyBaTh5JcaHIan3biwU=
|
|
||||||
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
|
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
|
||||||
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
|
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
|
||||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||||
@ -75,11 +69,17 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
|
|||||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/maragudk/goqite v0.1.0 h1:k/GgnJU9DBMVAx2DxBgmB+UKQVzJNjO1JPqmUJFH4+8=
|
||||||
|
github.com/maragudk/goqite v0.1.0/go.mod h1:5430TCLkycUeLE314c9fifTrTbwcJqJXdU3iyEiF6hM=
|
||||||
|
github.com/maragudk/is v0.1.0 h1:obq9anZNmOYcaNbeT0LMyjIexdNeYTw/TLAPD/BnZHA=
|
||||||
|
github.com/maragudk/is v0.1.0/go.mod h1:W/r6+TpnISu+a88OLXQy5JQGCOhXQXXLD2e5b4xMn5c=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
||||||
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
|
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
|
||||||
github.com/mmcdole/gofeed v1.1.3 h1:pdrvMb18jMSLidGp8j0pLvc9IGziX4vbmvVqmLH6z8o=
|
github.com/mmcdole/gofeed v1.1.3 h1:pdrvMb18jMSLidGp8j0pLvc9IGziX4vbmvVqmLH6z8o=
|
||||||
@ -167,8 +167,6 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||||
|
31
internal/database/db.go
Normal file
31
internal/database/db.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.16.0
|
||||||
|
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBTX interface {
|
||||||
|
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
|
||||||
|
PrepareContext(context.Context, string) (*sql.Stmt, error)
|
||||||
|
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
|
||||||
|
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db DBTX) *Queries {
|
||||||
|
return &Queries{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Queries struct {
|
||||||
|
db DBTX
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
|
||||||
|
return &Queries{
|
||||||
|
db: tx,
|
||||||
|
}
|
||||||
|
}
|
45
internal/database/dto.go
Normal file
45
internal/database/dto.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitTags(t string) []string {
|
||||||
|
items := strings.Split(t, ", ")
|
||||||
|
return items
|
||||||
|
}
|
@ -12,18 +12,30 @@ CREATE TABLE Articles (
|
|||||||
Url TEXT NOT NULL,
|
Url TEXT NOT NULL,
|
||||||
PubDate DATETIME NOT NULL,
|
PubDate DATETIME NOT NULL,
|
||||||
IsVideo TEXT NOT NULL,
|
IsVideo TEXT NOT NULL,
|
||||||
|
--VideoHeight int NOT NULL,
|
||||||
|
--VideoWidth int NOT NULL,
|
||||||
ThumbnailUrl TEXT NOT NULL,
|
ThumbnailUrl TEXT NOT NULL,
|
||||||
Description TEXT NOT NULL,
|
Description TEXT NOT NULL,
|
||||||
AuthorName TEXT NOT NULL,
|
AuthorName TEXT NOT NULL,
|
||||||
AuthorImageUrl TEXT NOT NULL
|
AuthorImageUrl TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE Table DiscordQueue (
|
||||||
|
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
CreatedAt DATETIME NOT NULL,
|
||||||
|
UpdatedAt DATETIME NOT NULL,
|
||||||
|
DeletedAt DATETIME,
|
||||||
|
ArticleId NUMBER NOT NULL,
|
||||||
|
SourceId NUMBER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
CREATE Table DiscordWebHooks (
|
CREATE Table DiscordWebHooks (
|
||||||
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
CreatedAt DATETIME NOT NULL,
|
CreatedAt DATETIME NOT NULL,
|
||||||
UpdatedAt DATETIME NOT NULL,
|
UpdatedAt DATETIME NOT NULL,
|
||||||
DeletedAt DATETIME NOT NULL,
|
DeletedAt DATETIME NOT NULL,
|
||||||
UserID INTEGER NOT NULL,
|
--Name TEXT NOT NULL, -- Defines webhook purpose
|
||||||
|
--Key TEXT,
|
||||||
Url TEXT NOT NULL, -- Webhook Url
|
Url TEXT NOT NULL, -- Webhook Url
|
||||||
Server TEXT NOT NULL, -- Defines the server its bound it. Used for reference
|
Server TEXT NOT NULL, -- Defines the server its bound it. Used for reference
|
||||||
Channel TEXT NOT NULL, -- Defines the channel its bound to. Used for reference
|
Channel TEXT NOT NULL, -- Defines the channel its bound to. Used for reference
|
||||||
@ -61,30 +73,20 @@ CREATE Table Sources (
|
|||||||
Tags TEXT NOT NULL
|
Tags TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE UserSourceSubscriptions (
|
CREATE TABLE Subscriptions (
|
||||||
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
CreatedAt DATETIME NOT NULL,
|
CreatedAt DATETIME NOT NULL,
|
||||||
UpdatedAt DATETIME NOT NULL,
|
UpdatedAt DATETIME NOT NULL,
|
||||||
DeletedAt DATETIME NOT NULL,
|
DeletedAt DATETIME,
|
||||||
UserID NUMBER NOT NULL,
|
DiscordWebHookId NUMBER NOT NULL,
|
||||||
SourceID NUMBER NOT NULL
|
SourceId NUMBER NOT NULL
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE AlertDiscord (
|
|
||||||
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
CreatedAt DATETIME NOT NULL,
|
|
||||||
UpdatedAt DATETIME NOT NULL,
|
|
||||||
DeletedAt DATETIME NOT NULL,
|
|
||||||
UserID NUMBER NOT NULL,
|
|
||||||
SourceID NUMBER NOT NULL,
|
|
||||||
DiscordWebHookID NUMBER NOT NULL
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE Users (
|
CREATE TABLE Users (
|
||||||
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
CreatedAt DATETIME NOT NULL,
|
CreatedAt DATETIME NOT NULL,
|
||||||
UpdatedAt DATETIME NOT NULL,
|
UpdatedAt DATETIME NOT NULL,
|
||||||
DeletedAt DATETIME NOT NULL,
|
DeletedAt DATETIME,
|
||||||
Name TEXT NOT NULL,
|
Name TEXT NOT NULL,
|
||||||
Hash TEXT NOT NULL,
|
Hash TEXT NOT NULL,
|
||||||
Scopes TEXT NOT NULL
|
Scopes TEXT NOT NULL
|
||||||
@ -94,7 +96,7 @@ CREATE TABLE RefreshTokens (
|
|||||||
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
CreatedAt DATETIME NOT NULL,
|
CreatedAt DATETIME NOT NULL,
|
||||||
UpdatedAt DATETIME NOT NULL,
|
UpdatedAt DATETIME NOT NULL,
|
||||||
DeletedAt DATETIME NOT NULL,
|
DeletedAt DATETIME,
|
||||||
Username TEXT NOT NULL,
|
Username TEXT NOT NULL,
|
||||||
Token TEXT NOT NULL
|
Token TEXT NOT NULL
|
||||||
);
|
);
|
||||||
@ -104,12 +106,13 @@ CREATE TABLE RefreshTokens (
|
|||||||
|
|
||||||
-- +goose Down
|
-- +goose Down
|
||||||
-- +goose StatementBegin
|
-- +goose StatementBegin
|
||||||
DROP TABLE AlertDiscord;
|
|
||||||
Drop Table Articles;
|
Drop Table Articles;
|
||||||
|
Drop Table DiscordQueue;
|
||||||
Drop Table DiscordWebHooks;
|
Drop Table DiscordWebHooks;
|
||||||
Drop Table Icons;
|
Drop Table Icons;
|
||||||
DROP TABLE RefreshTokens;
|
Drop Table Settings;
|
||||||
Drop Table Sources;
|
Drop Table Sources;
|
||||||
|
DROP TABLE Subscriptions;
|
||||||
DROP TABLE Users;
|
DROP TABLE Users;
|
||||||
DROP TABLE UserSourceSubscriptions;
|
DROP TABLE RefreshTokens;
|
||||||
-- +goose StatementEnd
|
-- +goose StatementEnd
|
||||||
|
@ -7,33 +7,33 @@ SELECT 'up SQL query';
|
|||||||
|
|
||||||
-- Final Fantasy XIV Entries
|
-- Final Fantasy XIV Entries
|
||||||
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
||||||
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'Final Fantasy XIV - NA', 'ffxiv', TRUE, 'https://na.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, na, lodestone');
|
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'ffxiv', 'Final Fantasy XIV - NA', TRUE, 'https://na.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, na, lodestone');
|
||||||
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
||||||
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'Final Fantasy XIV - JP', 'ffxiv', FALSE, 'https://jp.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, jp, lodestone');
|
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'ffxiv', 'Final Fantasy XIV - JP', FALSE, 'https://jp.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, jp, lodestone');
|
||||||
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
||||||
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'Final Fantasy XIV - EU', 'ffxiv', FALSE, 'https://eu.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, eu, lodestone');
|
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'ffxiv', 'Final Fantasy XIV - EU', FALSE, 'https://eu.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, eu, lodestone');
|
||||||
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
||||||
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'Final Fantasy XIV - FR', 'ffxiv', FALSE, 'https://fr.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, fr, lodestone');
|
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'ffxiv', 'Final Fantasy XIV - FR', FALSE, 'https://fr.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, fr, lodestone');
|
||||||
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
||||||
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'Final Fantasy XIV - DE', 'ffxiv', FALSE, 'https://de.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, de, lodestone');
|
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'ffxiv', 'Final Fantasy XIV - DE', FALSE, 'https://de.finalfantasyxiv.com/lodestone/', 'ffxiv, final, fantasy, xiv, de, lodestone');
|
||||||
|
|
||||||
-- Reddit Entries
|
-- Reddit Entries
|
||||||
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
||||||
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'dadjokes', 'reddit', TRUE, 'https://reddit.com/r/dadjokes', 'reddit, dadjokes');
|
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'reddit', 'dadjokes', TRUE, 'https://reddit.com/r/dadjokes', 'reddit, dadjokes');
|
||||||
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
||||||
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'steamdeck', 'reddit', TRUE, 'https://reddit.com/r/steamdeck', 'reddit, steam deck, steam, deck');
|
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'reddit', 'steamdeck', TRUE, 'https://reddit.com/r/steamdeck', 'reddit, steam deck, steam, deck');
|
||||||
|
|
||||||
-- Youtube Entries
|
-- Youtube Entries
|
||||||
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
||||||
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'Game Grumps', 'youtube', TRUE, 'https://www.youtube.com/user/GameGrumps', 'youtube, game grumps, game, grumps');
|
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'youtube', 'Game Grumps', TRUE, 'https://www.youtube.com/user/GameGrumps', 'youtube, game grumps, game, grumps');
|
||||||
|
|
||||||
-- RSS Entries
|
-- RSS Entries
|
||||||
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
||||||
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'steampowered - steam deck', 'rss', TRUE, 'https://store.steampowered.com/feeds/news/app/1675200/?cc=US&l=english&snr=1_2108_9__2107', 'rss, steampowered, steam, deck, steam deck');
|
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'steampowered', 'steam deck', TRUE, 'https://store.steampowered.com/feeds/news/app/1675200/?cc=US&l=english&snr=1_2108_9__2107', 'rss, steampowered, steam, deck, steam deck');
|
||||||
|
|
||||||
-- Twitch Entries
|
-- Twitch Entries
|
||||||
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabled, Url, Tags) VALUES
|
||||||
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'Nintendo', 'twitch', TRUE, 'https://twitch.tv/nintendo', 'twitch, nintendo');
|
("2024-04-25 18:37:43.852367", "2024-04-25 18:37:43.852367", "0001-01-01 00:00:00", 'twitch', 'Nintendo', TRUE, 'https://twitch.tv/nintendo', 'twitch, nintendo');
|
||||||
|
|
||||||
-- +goose StatementEnd
|
-- +goose StatementEnd
|
||||||
|
|
||||||
@ -41,10 +41,10 @@ INSERT INTO sources (CreatedAt, UpdatedAt, DeletedAt, DisplayName, Source, Enabl
|
|||||||
-- +goose StatementBegin
|
-- +goose StatementBegin
|
||||||
--SELECT 'down SQL query';
|
--SELECT 'down SQL query';
|
||||||
|
|
||||||
DELETE FROM sources where Source = 'reddit' and DisplayName = 'dadjokes';
|
DELETE FROM sources where source = 'reddit' and name = 'dadjokes';
|
||||||
DELETE FROM sources where Source = 'reddit' and DisplayName = 'steamdeck';
|
DELETE FROM sources where source = 'reddit' and name = 'steamdeck';
|
||||||
DELETE FROM sources where Source = 'ffxiv';
|
DELETE FROM sources where source = 'ffxiv';
|
||||||
DELETE FROM sources WHERE Source = 'twitch' and DisplayName = 'Nintendo';
|
DELETE FROM sources WHERE source = 'twitch' and name = 'Nintendo';
|
||||||
DELETE FROM sources WHERE Source = 'youtube' and DisplayName = 'Game Grumps';
|
DELETE FROM sources WHERE source = 'youtube' and name = 'Game Grumps';
|
||||||
DELETE FROM SOURCES WHERE Source = 'rss' and DisplayName = 'steampowered - steam deck';
|
DELETE FROM SOURCES WHERE source = 'rss' and name = 'steam deck';
|
||||||
-- +goose StatementEnd
|
-- +goose StatementEnd
|
||||||
|
25
internal/database/migrations/20240428201449_goqite.sql
Normal file
25
internal/database/migrations/20240428201449_goqite.sql
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
create table goqite (
|
||||||
|
id text primary key default ('m_' || lower(hex(randomblob(16)))),
|
||||||
|
created text not null default (strftime('%Y-%m-%dT%H:%M:%fZ')),
|
||||||
|
updated text not null default (strftime('%Y-%m-%dT%H:%M:%fZ')),
|
||||||
|
queue text not null,
|
||||||
|
body blob not null,
|
||||||
|
timeout text not null default (strftime('%Y-%m-%dT%H:%M:%fZ')),
|
||||||
|
received integer not null default 0
|
||||||
|
) strict;
|
||||||
|
|
||||||
|
create trigger goqite_updated_timestamp after update on goqite begin
|
||||||
|
update goqite set updated = strftime('%Y-%m-%dT%H:%M:%fZ') where id = old.id;
|
||||||
|
end;
|
||||||
|
|
||||||
|
create index goqite_queue_created_idx on goqite (queue, created);
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
DROP TABLE goqite
|
||||||
|
DROP INDEX goqite_queue_created_idx
|
||||||
|
DROP TRIGGER goqite_updated_timestamp
|
||||||
|
-- +goose StatementEnd
|
@ -1,9 +0,0 @@
|
|||||||
-- +goose Up
|
|
||||||
-- +goose StatementBegin
|
|
||||||
ALTER TABLE Users ADD SessionToken TEXT;
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
-- +goose StatementBegin
|
|
||||||
ALTER TABLE Users DROP SessionToken;
|
|
||||||
-- +goose StatementEnd
|
|
73
internal/database/models.go
Normal file
73
internal/database/models.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.16.0
|
||||||
|
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Article struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
Sourceid uuid.UUID
|
||||||
|
Tags string
|
||||||
|
Title string
|
||||||
|
Url string
|
||||||
|
Pubdate time.Time
|
||||||
|
Video sql.NullString
|
||||||
|
Videoheight int32
|
||||||
|
Videowidth int32
|
||||||
|
Thumbnail string
|
||||||
|
Description string
|
||||||
|
Authorname sql.NullString
|
||||||
|
Authorimage sql.NullString
|
||||||
|
}
|
||||||
|
|
||||||
|
type Discordqueue struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
Articleid uuid.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
type Discordwebhook struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
Url string
|
||||||
|
Server string
|
||||||
|
Channel string
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Icon struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
Filename string
|
||||||
|
Site string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Setting struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
Options sql.NullString
|
||||||
|
}
|
||||||
|
|
||||||
|
type Source struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
Site string
|
||||||
|
Name string
|
||||||
|
Source string
|
||||||
|
Type string
|
||||||
|
Value sql.NullString
|
||||||
|
Enabled bool
|
||||||
|
Url string
|
||||||
|
Tags string
|
||||||
|
Deleted sql.NullBool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Subscription struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
Discordwebhookid uuid.UUID
|
||||||
|
Sourceid uuid.UUID
|
||||||
|
}
|
1308
internal/database/query.sql.go
Normal file
1308
internal/database/query.sql.go
Normal file
File diff suppressed because it is too large
Load Diff
215
internal/database/schema/query.sql
Normal file
215
internal/database/schema/query.sql
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
/* Articles */
|
||||||
|
-- name: GetArticleByID :one
|
||||||
|
Select * from Articles
|
||||||
|
WHERE ID = $1 LIMIT 1;
|
||||||
|
|
||||||
|
-- name: GetArticleByUrl :one
|
||||||
|
Select * from Articles
|
||||||
|
Where Url = $1 LIMIT 1;
|
||||||
|
|
||||||
|
-- name: ListArticles :many
|
||||||
|
Select * From articles
|
||||||
|
Order By PubDate DESC
|
||||||
|
offset $2
|
||||||
|
fetch next $1 rows only;
|
||||||
|
|
||||||
|
-- name: ListArticlesByDate :many
|
||||||
|
Select * From articles
|
||||||
|
ORDER BY pubdate desc
|
||||||
|
Limit $1;
|
||||||
|
|
||||||
|
-- name: GetArticlesBySource :many
|
||||||
|
select * from articles
|
||||||
|
INNER join sources on articles.sourceid=Sources.ID
|
||||||
|
where site = $1;
|
||||||
|
|
||||||
|
-- name: ListNewArticlesBySourceId :many
|
||||||
|
SELECT * FROM articles
|
||||||
|
Where sourceid = $1
|
||||||
|
ORDER BY pubdate desc
|
||||||
|
offset $3
|
||||||
|
fetch next $2 rows only;
|
||||||
|
|
||||||
|
-- name: ListOldestArticlesBySourceId :many
|
||||||
|
SELECT * FROM articles
|
||||||
|
Where sourceid = $1
|
||||||
|
ORDER BY pubdate asc
|
||||||
|
offset $3
|
||||||
|
fetch next $2 rows only;
|
||||||
|
|
||||||
|
|
||||||
|
-- name: ListArticlesBySourceId :many
|
||||||
|
Select * From articles
|
||||||
|
Where sourceid = $1
|
||||||
|
Limit 50;
|
||||||
|
|
||||||
|
-- name: GetArticlesBySourceName :many
|
||||||
|
select
|
||||||
|
articles.ID, articles.SourceId, articles.Tags, articles.Title, articles.Url, articles.PubDate, articles.Video, articles.VideoHeight, articles.VideoWidth, articles.Thumbnail, articles.Description, articles.AuthorName, articles.AuthorImage, sources.source, sources.name
|
||||||
|
From articles
|
||||||
|
Left Join sources
|
||||||
|
On articles.sourceid = sources.id
|
||||||
|
Where name = $1;
|
||||||
|
|
||||||
|
-- name: ListArticlesByPage :many
|
||||||
|
select * from articles
|
||||||
|
order by pubdate desc
|
||||||
|
offset $2
|
||||||
|
fetch next $1 rows only;
|
||||||
|
|
||||||
|
-- name: CreateArticle :exec
|
||||||
|
INSERT INTO Articles
|
||||||
|
(ID, SourceId, Tags, Title, Url, PubDate, Video, VideoHeight, VideoWidth, Thumbnail, Description, AuthorName, AuthorImage)
|
||||||
|
Values
|
||||||
|
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13);
|
||||||
|
|
||||||
|
|
||||||
|
/* DiscordQueue */
|
||||||
|
-- name: CreateDiscordQueue :exec
|
||||||
|
Insert into DiscordQueue
|
||||||
|
(ID, ArticleId)
|
||||||
|
Values
|
||||||
|
($1, $2);
|
||||||
|
|
||||||
|
-- name: GetDiscordQueueByID :one
|
||||||
|
Select * from DiscordQueue
|
||||||
|
Where ID = $1 LIMIT 1;
|
||||||
|
|
||||||
|
-- name: DeleteDiscordQueueItem :exec
|
||||||
|
Delete From DiscordQueue Where ID = $1;
|
||||||
|
|
||||||
|
-- name: ListDiscordQueueItems :many
|
||||||
|
Select * from DiscordQueue LIMIT $1;
|
||||||
|
|
||||||
|
/* DiscordWebHooks */
|
||||||
|
-- name: CreateDiscordWebHook :exec
|
||||||
|
Insert Into DiscordWebHooks
|
||||||
|
(ID, Url, Server, Channel, Enabled)
|
||||||
|
Values
|
||||||
|
($1, $2, $3, $4, $5);
|
||||||
|
|
||||||
|
-- name: GetDiscordWebHooksByID :one
|
||||||
|
Select * from DiscordWebHooks
|
||||||
|
Where ID = $1 LIMIT 1;
|
||||||
|
|
||||||
|
-- name: ListDiscordWebHooksByServer :many
|
||||||
|
Select * From DiscordWebHooks
|
||||||
|
Where Server = $1;
|
||||||
|
|
||||||
|
-- name: GetDiscordWebHooksByServerAndChannel :many
|
||||||
|
SELECT * FROM DiscordWebHooks
|
||||||
|
WHERE Server = $1 and Channel = $2;
|
||||||
|
|
||||||
|
-- name: GetDiscordWebHookByUrl :one
|
||||||
|
Select * From DiscordWebHooks Where url = $1;
|
||||||
|
|
||||||
|
-- name: ListDiscordWebhooks :many
|
||||||
|
Select * From discordwebhooks LIMIT $1;
|
||||||
|
|
||||||
|
-- name: DeleteDiscordWebHooks :exec
|
||||||
|
Delete From discordwebhooks Where ID = $1;
|
||||||
|
|
||||||
|
-- name: DisableDiscordWebHook :exec
|
||||||
|
Update discordwebhooks Set Enabled = FALSE where ID = $1;
|
||||||
|
|
||||||
|
-- name: EnableDiscordWebHook :exec
|
||||||
|
Update discordwebhooks Set Enabled = TRUE where ID = $1;
|
||||||
|
|
||||||
|
/* Icons */
|
||||||
|
|
||||||
|
-- name: CreateIcon :exec
|
||||||
|
INSERT INTO Icons
|
||||||
|
(ID, FileName, Site)
|
||||||
|
VALUES
|
||||||
|
($1,$2,$3);
|
||||||
|
|
||||||
|
-- name: GetIconByID :one
|
||||||
|
Select * FROM Icons
|
||||||
|
Where ID = $1 Limit 1;
|
||||||
|
|
||||||
|
-- name: GetIconBySite :one
|
||||||
|
Select * FROM Icons
|
||||||
|
Where Site = $1 Limit 1;
|
||||||
|
|
||||||
|
-- name: DeleteIcon :exec
|
||||||
|
Delete From Icons where ID = $1;
|
||||||
|
|
||||||
|
/* Settings */
|
||||||
|
|
||||||
|
-- name: CreateSettings :one
|
||||||
|
Insert Into settings
|
||||||
|
(ID, Key, Value, OPTIONS)
|
||||||
|
Values
|
||||||
|
($1,$2,$3,$4)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetSettingByID :one
|
||||||
|
Select * From settings
|
||||||
|
Where ID = $1 Limit 1;
|
||||||
|
|
||||||
|
-- name: GetSettingByKey :one
|
||||||
|
Select * From settings Where
|
||||||
|
Key = $1 Limit 1;
|
||||||
|
|
||||||
|
-- name: GetSettingByValue :one
|
||||||
|
Select * From settings Where
|
||||||
|
Value = $1 Limit 1;
|
||||||
|
|
||||||
|
-- name: DeleteSetting :exec
|
||||||
|
Delete From settings Where ID = $1;
|
||||||
|
|
||||||
|
/* Sources */
|
||||||
|
|
||||||
|
-- name: CreateSource :exec
|
||||||
|
Insert Into Sources
|
||||||
|
(ID, Site, Name, Source, Type, Value, Enabled, Url, Tags)
|
||||||
|
Values
|
||||||
|
($1,$2,$3,$4,$5,$6,$7,$8,$9);
|
||||||
|
|
||||||
|
-- name: GetSourceByID :one
|
||||||
|
Select * From Sources where ID = $1 Limit 1;
|
||||||
|
|
||||||
|
-- name: GetSourceByName :one
|
||||||
|
Select * from Sources where name = $1 Limit 1;
|
||||||
|
|
||||||
|
-- name: GetSourceByNameAndSource :one
|
||||||
|
Select * from Sources WHERE name = $1 and source = $2;
|
||||||
|
|
||||||
|
-- name: ListSources :many
|
||||||
|
Select * From Sources Limit $1;
|
||||||
|
|
||||||
|
-- name: ListSourcesBySource :many
|
||||||
|
Select * From Sources where Source = $1;
|
||||||
|
|
||||||
|
-- name: DeleteSource :exec
|
||||||
|
UPDATE Sources Set Disabled = TRUE where id = $1;
|
||||||
|
|
||||||
|
-- name: DisableSource :exec
|
||||||
|
Update Sources Set Enabled = FALSE where ID = $1;
|
||||||
|
|
||||||
|
-- name: EnableSource :exec
|
||||||
|
Update Sources Set Enabled = TRUE where ID = $1;
|
||||||
|
|
||||||
|
|
||||||
|
/* Subscriptions */
|
||||||
|
|
||||||
|
-- name: CreateSubscription :exec
|
||||||
|
Insert Into subscriptions (ID, DiscordWebHookId, SourceId) Values ($1, $2, $3);
|
||||||
|
|
||||||
|
-- name: ListSubscriptions :many
|
||||||
|
Select * From subscriptions Limit $1;
|
||||||
|
|
||||||
|
-- name: ListSubscriptionsBySourceId :many
|
||||||
|
Select * From subscriptions where sourceid = $1;
|
||||||
|
|
||||||
|
-- name: QuerySubscriptions :one
|
||||||
|
Select * From subscriptions Where discordwebhookid = $1 and sourceid = $2 Limit 1;
|
||||||
|
|
||||||
|
-- name: GetSubscriptionsBySourceID :many
|
||||||
|
Select * From subscriptions Where sourceid = $1;
|
||||||
|
|
||||||
|
-- name: GetSubscriptionsByDiscordWebHookId :many
|
||||||
|
Select * from subscriptions Where discordwebhookid = $1;
|
||||||
|
|
||||||
|
-- name: DeleteSubscription :exec
|
||||||
|
Delete From subscriptions Where id = $1;
|
61
internal/database/schema/schema.sql
Normal file
61
internal/database/schema/schema.sql
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
CREATE TABLE Articles (
|
||||||
|
ID uuid PRIMARY KEY,
|
||||||
|
SourceId uuid NOT null,
|
||||||
|
Tags TEXT NOT NULL,
|
||||||
|
Title TEXT NOT NULL,
|
||||||
|
Url TEXT NOT NULL,
|
||||||
|
PubDate timestamp NOT NULL,
|
||||||
|
Video TEXT,
|
||||||
|
VideoHeight int NOT NULL,
|
||||||
|
VideoWidth int NOT NULL,
|
||||||
|
Thumbnail TEXT NOT NULL,
|
||||||
|
Description TEXT NOT NULL,
|
||||||
|
AuthorName TEXT,
|
||||||
|
AuthorImage TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE Table DiscordQueue (
|
||||||
|
ID uuid PRIMARY KEY,
|
||||||
|
ArticleId uuid NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE Table DiscordWebHooks (
|
||||||
|
ID uuid PRIMARY KEY,
|
||||||
|
Url TEXT NOT NULL, -- Webhook Url
|
||||||
|
Server TEXT NOT NULL, -- Defines the server its bound it. Used for refrence
|
||||||
|
Channel TEXT NOT NULL, -- Defines the channel its bound to. Used for refrence
|
||||||
|
Enabled BOOLEAN NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE Table Icons (
|
||||||
|
ID uuid PRIMARY Key,
|
||||||
|
FileName TEXT NOT NULL,
|
||||||
|
Site TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
Create Table Settings (
|
||||||
|
ID uuid PRIMARY Key,
|
||||||
|
Key TEXT NOT NULL,
|
||||||
|
Value TEXT NOT NULL,
|
||||||
|
Options TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
Create Table Sources (
|
||||||
|
ID uuid PRIMARY Key,
|
||||||
|
Site TEXT NOT NULL, -- Vanity name
|
||||||
|
Name TEXT NOT NULL, -- Defines the name of the source. IE: dadjokes
|
||||||
|
Source TEXT NOT NULL, -- Defines the service that will use this reocrd. IE reddit or youtube
|
||||||
|
Type TEXT NOT NULL, -- Defines what kind of feed this is. feed, user, tag
|
||||||
|
Value TEXT,
|
||||||
|
Enabled BOOLEAN NOT NULL,
|
||||||
|
Url TEXT NOT NULL,
|
||||||
|
Tags TEXT NOT NULL,
|
||||||
|
Deleted BOOLEAN
|
||||||
|
);
|
||||||
|
|
||||||
|
/* This table is used to track what the Web Hook wants to have sent by Source */;
|
||||||
|
Create TABLE Subscriptions (
|
||||||
|
ID uuid Primary Key,
|
||||||
|
DiscordWebHookID uuid Not Null,
|
||||||
|
SourceID uuid Not Null
|
||||||
|
);
|
@ -1,21 +1,9 @@
|
|||||||
package entity
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This links a source to a discord webhook.
|
|
||||||
// It is owned by a user so they can remove the link
|
|
||||||
type AlertDiscordEntity struct {
|
|
||||||
ID int64
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
DeletedAt time.Time
|
|
||||||
UserID int64
|
|
||||||
SourceID int64
|
|
||||||
DiscordWebHookId int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type ArticleEntity struct {
|
type ArticleEntity struct {
|
||||||
ID int64
|
ID int64
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
@ -47,7 +35,6 @@ type DiscordWebHookEntity struct {
|
|||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
DeletedAt time.Time
|
DeletedAt time.Time
|
||||||
UserID int64
|
|
||||||
Url string
|
Url string
|
||||||
Server string
|
Server string
|
||||||
Channel string
|
Channel string
|
||||||
@ -96,28 +83,16 @@ type SourceEntity struct {
|
|||||||
Enabled bool
|
Enabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
//type SubscriptionEntity struct {
|
type SubscriptionEntity struct {
|
||||||
// ID int64
|
|
||||||
// CreatedAt time.Time
|
|
||||||
// UpdatedAt time.Time
|
|
||||||
// DeletedAt time.Time
|
|
||||||
// UserID int64
|
|
||||||
// SourceID int64
|
|
||||||
// //SourceType string
|
|
||||||
// //SourceName string
|
|
||||||
// DiscordID int64
|
|
||||||
// //DiscordName string
|
|
||||||
//}
|
|
||||||
|
|
||||||
// This defines what sources a user wants to follow.
|
|
||||||
// These will show up for the user as a front page
|
|
||||||
type UserSourceSubscriptionEntity struct {
|
|
||||||
ID int64
|
ID int64
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
DeletedAt time.Time
|
DeletedAt time.Time
|
||||||
UserID int64
|
|
||||||
SourceID int64
|
SourceID int64
|
||||||
|
SourceType string
|
||||||
|
SourceName string
|
||||||
|
DiscordID int64
|
||||||
|
DiscordName string
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserEntity struct {
|
type UserEntity struct {
|
||||||
@ -128,7 +103,6 @@ type UserEntity struct {
|
|||||||
Username string
|
Username string
|
||||||
Hash string
|
Hash string
|
||||||
Scopes string
|
Scopes string
|
||||||
SessionToken string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RefreshTokenEntity struct {
|
type RefreshTokenEntity struct {
|
9
internal/domain/events.go
Normal file
9
internal/domain/events.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
const (
|
||||||
|
QueueRssCollection = "RssCollection"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RssCollectionEvent struct {
|
||||||
|
|
||||||
|
}
|
22
internal/domain/interfaces/source.go
Normal file
22
internal/domain/interfaces/source.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package interfaces
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-rod/rod"
|
||||||
|
"github.com/mmcdole/gofeed"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Sources interface {
|
||||||
|
CheckSource() error
|
||||||
|
PullFeed() (*gofeed.Feed, error)
|
||||||
|
|
||||||
|
GetBrowser() *rod.Browser
|
||||||
|
GetPage(parser *rod.Browser, url string) *rod.Page
|
||||||
|
|
||||||
|
ExtractThumbnail(page *rod.Page) (string, error)
|
||||||
|
ExtractPubDate(page *rod.Page) (string, error)
|
||||||
|
ExtractDescription(page *rod.Page) (string, error)
|
||||||
|
ExtractAuthor(page *rod.Page) (string, error)
|
||||||
|
ExtractAuthorImage(page *rod.Page) (string, error)
|
||||||
|
ExtractTags(page *rod.Page) (string, error)
|
||||||
|
ExtractTitle(page *rod.Page) (string, error)
|
||||||
|
}
|
129
internal/domain/models/dto.go
Normal file
129
internal/domain/models/dto.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/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
|
||||||
|
}
|
13
internal/domain/requests.go
Normal file
13
internal/domain/requests.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
type GetSourceBySourceAndNameParamRequest struct {
|
||||||
|
Name string `query:"name"`
|
||||||
|
Source string `query:"source"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NewSourceParamRequest struct {
|
||||||
|
Name string `query:"name"`
|
||||||
|
Url string `query:"url"`
|
||||||
|
Tags string `query:"tags"`
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,8 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
|
|
||||||
type BaseResponse struct {
|
type BaseResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
IsError bool `json:"isError"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LoginResponse struct {
|
|
||||||
BaseResponse
|
|
||||||
Token string `json:"token"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
RefreshToken string `json:"refreshToken"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ArticleResponse struct {
|
type ArticleResponse struct {
|
@ -4,34 +4,27 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/dtoconv"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListArticles
|
// ListArticles
|
||||||
// @Summary Lists the top 25 records ordering from newest to oldest.
|
// @Summary Lists the top 25 records ordering from newest to oldest.
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Param page query int true "page number" test
|
// @Param page query string false "page number"
|
||||||
// @Tags Articles
|
// @Tags Articles
|
||||||
// @Router /v1/articles [get]
|
// @Router /articles [get]
|
||||||
// @Success 200 {object} domain.ArticleResponse
|
// @Success 200 {object} domain.ArticleResponse
|
||||||
// @Failure 400 {object} domain.BaseResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.BaseResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) listArticles(c echo.Context) error {
|
func (s *Handler) listArticles(c echo.Context) error {
|
||||||
resp := domain.ArticleResponse{
|
resp := domain.ArticleResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
IsError: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeArticleRead)
|
|
||||||
if err != nil {
|
|
||||||
return c.JSON(http.StatusUnauthorized, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
page, err := strconv.Atoi(c.QueryParam("page"))
|
page, err := strconv.Atoi(c.QueryParam("page"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
page = 0
|
page = 0
|
||||||
@ -39,128 +32,107 @@ func (s *Handler) listArticles(c echo.Context) error {
|
|||||||
|
|
||||||
res, err := s.repo.Articles.ListByPage(c.Request().Context(), page, 25)
|
res, err := s.repo.Articles.ListByPage(c.Request().Context(), page, 25)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, resp)
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Payload = dtoconv.ArticlesToDto(res)
|
resp.Payload = services.ArticlesToDto(res)
|
||||||
resp.BaseResponse.IsError = false
|
|
||||||
return c.JSON(http.StatusOK, resp)
|
return c.JSON(http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetArticle
|
// GetArticle
|
||||||
// @Summary Returns an article based on defined ID.
|
// @Summary Returns an article based on defined ID.
|
||||||
// @Param id path string true "int"
|
// @Param ID path string true "int"
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Tags Articles
|
// @Tags Articles
|
||||||
// @Router /v1/articles/{id} [get]
|
// @Router /articles/{ID} [get]
|
||||||
// @Success 200 {object} domain.ArticleResponse "OK"
|
// @Success 200 {object} domain.ArticleResponse "OK"
|
||||||
// @Failure 400 {object} domain.BaseResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.BaseResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) getArticle(c echo.Context) error {
|
func (s *Handler) getArticle(c echo.Context) error {
|
||||||
p := domain.ArticleResponse{
|
p := domain.ArticleResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
IsError: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeArticleRead)
|
|
||||||
if err != nil {
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
id := c.Param("ID")
|
id := c.Param("ID")
|
||||||
idNumber, err := strconv.Atoi(id)
|
idNumber, err := strconv.Atoi(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
s.WriteError(c, err, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
item, err := s.repo.Articles.GetById(c.Request().Context(), int64(idNumber))
|
item, err := s.repo.Articles.GetById(c.Request().Context(), int64(idNumber))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
return c.JSON(http.StatusInternalServerError, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtos []domain.ArticleDto
|
var dtos []domain.ArticleDto
|
||||||
dtos = append(dtos, dtoconv.ArticleToDto(item))
|
dtos = append(dtos, services.ArticleToDto(item))
|
||||||
p.Payload = dtos
|
p.Payload = dtos
|
||||||
|
|
||||||
p.BaseResponse.IsError = false
|
|
||||||
return c.JSON(http.StatusOK, p)
|
return c.JSON(http.StatusOK, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetArticleDetails
|
// GetArticleDetails
|
||||||
// @Summary Returns an article and source based on defined ID.
|
// @Summary Returns an article and source based on defined ID.
|
||||||
// @Param id path string true "int"
|
// @Param ID path string true "int"
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Tags Articles
|
// @Tags Articles
|
||||||
// @Router /v1/articles/{id}/details [get]
|
// @Router /articles/{ID}/details [get]
|
||||||
// @Success 200 {object} domain.ArticleDetailedResponse "OK"
|
// @Success 200 {object} domain.ArticleDetailedResponse "OK"
|
||||||
// @Failure 400 {object} domain.BaseResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.BaseResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) getArticleDetails(c echo.Context) error {
|
func (s *Handler) getArticleDetails(c echo.Context) error {
|
||||||
p := domain.ArticleDetailedResponse{
|
p := domain.ArticleDetailedResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
IsError: true,
|
|
||||||
},
|
},
|
||||||
Payload: domain.ArticleAndSourceModel{},
|
Payload: domain.ArticleAndSourceModel{
|
||||||
}
|
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeArticleRead)
|
},
|
||||||
if err != nil {
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := strconv.Atoi(c.Param("ID"))
|
id, err := strconv.Atoi(c.Param("ID"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
s.WriteError(c, err, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
article, err := s.repo.Articles.GetById(c.Request().Context(), int64(id))
|
article, err := s.repo.Articles.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
source, err := s.repo.Sources.GetById(c.Request().Context(), article.SourceID)
|
source, err := s.repo.Sources.GetById(c.Request().Context(), article.SourceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Payload.Article = dtoconv.ArticleToDto(article)
|
p.Payload.Article = services.ArticleToDto(article)
|
||||||
p.Payload.Source = dtoconv.SourceToDto(source)
|
p.Payload.Source = services.SourceToDto(source)
|
||||||
p.BaseResponse.IsError = false
|
|
||||||
return c.JSON(http.StatusOK, p)
|
return c.JSON(http.StatusOK, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListArticlesBySourceID
|
// ListArticlesBySourceID
|
||||||
// @Summary Finds the articles based on the SourceID provided. Returns the top 25.
|
// @Summary Finds the articles based on the SourceID provided. Returns the top 25.
|
||||||
// @Param id path string true "source id"
|
// @Param id query string true "source id"
|
||||||
// @Param page query int false "Page to query"
|
// @Param page query int false "Page to query"
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Tags Articles
|
// @Tags Articles
|
||||||
// @Router /v1/articles/by/source/{id} [get]
|
// @Router /articles/by/sourceid [get]
|
||||||
// @Success 200 {object} domain.ArticleResponse "OK"
|
// @Success 200 {object} domain.ArticleResponse "OK"
|
||||||
// @Failure 400 {object} domain.BaseResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.BaseResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) ListArticlesBySourceId(c echo.Context) error {
|
func (s *Handler) ListArticlesBySourceId(c echo.Context) error {
|
||||||
p := domain.ArticleResponse{
|
p := domain.ArticleResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
IsError: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeArticleRead)
|
|
||||||
if err != nil {
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := strconv.Atoi(c.QueryParam("id"))
|
id, err := strconv.Atoi(c.QueryParam("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
s.WriteError(c, err, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the page number is missing, default to 0
|
// if the page number is missing, default to 0
|
||||||
@ -171,11 +143,9 @@ func (s *Handler) ListArticlesBySourceId(c echo.Context) error {
|
|||||||
|
|
||||||
items, err := s.repo.Articles.ListBySource(c.Request().Context(), _page, 25, id, "")
|
items, err := s.repo.Articles.ListBySource(c.Request().Context(), _page, 25, id, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
return c.JSON(http.StatusInternalServerError, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Payload = dtoconv.ArticlesToDto(items)
|
p.Payload = services.ArticlesToDto(items)
|
||||||
p.BaseResponse.IsError = false
|
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, p)
|
return c.JSON(http.StatusOK, p)
|
||||||
}
|
}
|
||||||
|
@ -5,40 +5,31 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/dtoconv"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListDiscordWebhooks
|
// ListDiscordWebhooks
|
||||||
// @Summary Returns the top 100
|
// @Summary Returns the top 100
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Tags DiscordWebhook
|
// @Tags Discord, Webhook
|
||||||
// @Router /v1/discord/webhooks [get]
|
// @Router /discord/webhooks [get]
|
||||||
// @Success 200 {object} domain.DiscordWebhookResponse
|
// @Success 200 {object} domain.DiscordWebhookResponse
|
||||||
// @Failure 400 {object} domain.DiscordWebhookResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.DiscordWebhookResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) ListDiscordWebHooks(c echo.Context) error {
|
func (s *Handler) ListDiscordWebHooks(c echo.Context) error {
|
||||||
p := domain.DiscordWebhookResponse{
|
p := domain.DiscordWebhookResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
IsError: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebhookRead)
|
|
||||||
if err != nil {
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := s.repo.DiscordWebHooks.ListByServerName(c.Request().Context(), "")
|
res, err := s.repo.DiscordWebHooks.ListByServerName(c.Request().Context(), "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
return c.JSON(http.StatusInternalServerError, err)
|
||||||
}
|
}
|
||||||
|
p.Payload = services.DiscordWebhooksToDto(res)
|
||||||
p.Payload = dtoconv.DiscordWebhooksToDto(res)
|
|
||||||
p.BaseResponse.IsError = false
|
|
||||||
return c.JSON(http.StatusOK, p)
|
return c.JSON(http.StatusOK, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,39 +37,30 @@ func (s *Handler) ListDiscordWebHooks(c echo.Context) error {
|
|||||||
// @Summary Returns the top 100 entries from the queue to be processed.
|
// @Summary Returns the top 100 entries from the queue to be processed.
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Param id path int true "id"
|
// @Param id path int true "id"
|
||||||
// @Tags DiscordWebhook
|
// @Tags Discord, Webhook
|
||||||
// @Router /v1/discord/webhooks/{id} [get]
|
// @Router /discord/webhooks/{id} [get]
|
||||||
// @Success 200 {object} domain.DiscordWebhookResponse "OK"
|
// @Success 200 {object} domain.DiscordWebhookResponse "OK"
|
||||||
// @Failure 400 {object} domain.DiscordWebhookResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.DiscordWebhookResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) GetDiscordWebHooksById(c echo.Context) error {
|
func (s *Handler) GetDiscordWebHooksById(c echo.Context) error {
|
||||||
p := domain.DiscordWebhookResponse{
|
p := domain.DiscordWebhookResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
IsError: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebhookRead)
|
|
||||||
if err != nil {
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := strconv.Atoi(c.Param("ID"))
|
id, err := strconv.Atoi(c.Param("ID"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
s.WriteError(c, err, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
res, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtos []domain.DiscordWebHookDto
|
var dtos []domain.DiscordWebHookDto
|
||||||
dtos = append(dtos, dtoconv.DiscordWebhookToDto(res))
|
dtos = append(dtos, services.DiscordWebhookToDto(res))
|
||||||
p.Payload = dtos
|
p.Payload = dtos
|
||||||
p.BaseResponse.IsError = false
|
|
||||||
return c.JSON(http.StatusOK, p)
|
return c.JSON(http.StatusOK, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,46 +69,34 @@ func (s *Handler) GetDiscordWebHooksById(c echo.Context) error {
|
|||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Param server query string true "Fancy Server"
|
// @Param server query string true "Fancy Server"
|
||||||
// @Param channel query string true "memes"
|
// @Param channel query string true "memes"
|
||||||
// @Tags DiscordWebhook
|
// @Tags Discord, Webhook
|
||||||
// @Router /v1/discord/webhooks/by/serverAndChannel [get]
|
// @Router /discord/webhooks/by/serverAndChannel [get]
|
||||||
// @Success 200 {object} domain.DiscordWebhookResponse "OK"
|
// @Success 200 {object} domain.DiscordWebhookResponse "OK"
|
||||||
// @Failure 400 {object} domain.DiscordWebhookResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.DiscordWebhookResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) GetDiscordWebHooksByServerAndChannel(c echo.Context) error {
|
func (s *Handler) GetDiscordWebHooksByServerAndChannel(c echo.Context) error {
|
||||||
p := domain.DiscordWebhookResponse{
|
p := domain.DiscordWebhookResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
IsError: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebhookRead)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
_server := c.QueryParam("server")
|
_server := c.QueryParam("server")
|
||||||
if _server == "" {
|
if _server == "" {
|
||||||
p.BaseResponse.Message = "server was not defined"
|
s.WriteMessage(c, "server was not defined", http.StatusBadRequest)
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_channel := c.QueryParam("channel")
|
_channel := c.QueryParam("channel")
|
||||||
if _channel == "" {
|
if _channel == "" {
|
||||||
p.BaseResponse.Message = "channel was not defined"
|
s.WriteMessage(c, "channel was not defined", http.StatusBadRequest)
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := s.repo.DiscordWebHooks.ListByServerAndChannel(c.Request().Context(), _server, _channel)
|
res, err := s.repo.DiscordWebHooks.ListByServerAndChannel(c.Request().Context(), _server, _channel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Payload = dtoconv.DiscordWebhooksToDto(res)
|
p.Payload = services.DiscordWebhooksToDto(res)
|
||||||
p.IsError = false
|
|
||||||
return c.JSON(http.StatusOK, p)
|
return c.JSON(http.StatusOK, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,284 +105,201 @@ func (s *Handler) GetDiscordWebHooksByServerAndChannel(c echo.Context) error {
|
|||||||
// @Param url query string true "url"
|
// @Param url query string true "url"
|
||||||
// @Param server query string true "Server name"
|
// @Param server query string true "Server name"
|
||||||
// @Param channel query string true "Channel name"
|
// @Param channel query string true "Channel name"
|
||||||
// @Tags DiscordWebhook
|
// @Tags Discord, Webhook
|
||||||
// @Router /v1/discord/webhooks/new [post]
|
// @Router /discord/webhooks/new [post]
|
||||||
// @Success 200 {object} domain.DiscordWebhookResponse "OK"
|
// @Success 200 {object} domain.DiscordWebhookResponse "OK"
|
||||||
// @Failure 400 {object} domain.DiscordWebhookResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.DiscordWebhookResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) NewDiscordWebHook(c echo.Context) error {
|
func (s *Handler) NewDiscordWebHook(c echo.Context) error {
|
||||||
p := domain.DiscordWebhookResponse{
|
|
||||||
BaseResponse: domain.BaseResponse{
|
|
||||||
Message: ResponseMessageSuccess,
|
|
||||||
IsError: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
_url := c.QueryParam("url")
|
_url := c.QueryParam("url")
|
||||||
_server := c.QueryParam("server")
|
_server := c.QueryParam("server")
|
||||||
_channel := c.QueryParam("channel")
|
_channel := c.QueryParam("channel")
|
||||||
|
|
||||||
if _url == "" {
|
if _url == "" {
|
||||||
p.Message = "url is missing a value"
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
Message: "url is missing a value",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if !strings.Contains(_url, "discord.com/api/webhooks") {
|
if !strings.Contains(_url, "discord.com/api/webhooks") {
|
||||||
p.Message = "invalid url"
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
Message: "invalid url",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if _server == "" {
|
if _server == "" {
|
||||||
p.Message = "server is missing"
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
Message: "server is missing",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if _channel == "" {
|
if _channel == "" {
|
||||||
p.Message = "channel is missing"
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
Message: "channel is missing",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := s.repo.Users.GetUser(c.Request().Context(), token.UserName)
|
rows, err := s.repo.DiscordWebHooks.Create(c.Request().Context(), _url, _server, _channel, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := s.repo.DiscordWebHooks.Create(c.Request().Context(), user.ID, _url, _server, _channel, true)
|
|
||||||
if err != nil {
|
|
||||||
p.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rows != 1 {
|
if rows != 1 {
|
||||||
p.Message = "data was not written to database"
|
s.WriteMessage(c, "data was not written to database", http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
item, err := s.repo.DiscordWebHooks.GetByUrl(c.Request().Context(), _url)
|
item, err := s.repo.DiscordWebHooks.GetByUrl(c.Request().Context(), _url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtos []domain.DiscordWebHookDto
|
var dtos []domain.DiscordWebHookDto
|
||||||
dtos = append(dtos, dtoconv.DiscordWebhookToDto(item))
|
dtos = append(dtos, services.DiscordWebhookToDto(item))
|
||||||
|
|
||||||
p.Payload = dtos
|
return c.JSON(http.StatusOK, domain.DiscordWebhookResponse{
|
||||||
p.IsError = false
|
BaseResponse: domain.BaseResponse{
|
||||||
return c.JSON(http.StatusOK, p)
|
Message: ResponseMessageSuccess,
|
||||||
|
},
|
||||||
|
Payload: dtos,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableDiscordWebHooks
|
// DisableDiscordWebHooks
|
||||||
// @Summary Disables a Webhook from being used.
|
// @Summary Disables a Webhook from being used.
|
||||||
// @Param id path int true "id"
|
// @Param id path int true "id"
|
||||||
// @Tags DiscordWebhook
|
// @Tags Discord, Webhook
|
||||||
// @Router /v1/discord/webhooks/{id}/disable [post]
|
// @Router /discord/webhooks/{ID}/disable [post]
|
||||||
// @Success 200 {object} domain.DiscordWebhookResponse "OK"
|
// @Success 200 {object} domain.DiscordWebhookResponse "OK"
|
||||||
// @Failure 400 {object} domain.DiscordWebhookResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.DiscordWebhookResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) disableDiscordWebHook(c echo.Context) error {
|
func (s *Handler) disableDiscordWebHook(c echo.Context) error {
|
||||||
p := domain.DiscordWebhookResponse{
|
|
||||||
BaseResponse: domain.BaseResponse{
|
|
||||||
Message: ResponseMessageSuccess,
|
|
||||||
IsError: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := strconv.Atoi(c.Param("ID"))
|
id, err := strconv.Atoi(c.Param("ID"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
Message: err.Error(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to make sure we can find the record
|
// Check to make sure we can find the record
|
||||||
record, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
_, err = s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
if record.UserID != s.GetUserIdFromJwtToken(c) {
|
|
||||||
p.BaseResponse.Message = ErrYouDontOwnTheRecord
|
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// flip the it
|
// flip the it
|
||||||
updated, err := s.repo.DiscordWebHooks.Disable(c.Request().Context(), int64(id))
|
updated, err := s.repo.DiscordWebHooks.Disable(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure we got a row updated
|
// make sure we got a row updated
|
||||||
if updated != 1 {
|
if updated != 1 {
|
||||||
p.BaseResponse.Message = "unexpected number of updates found"
|
s.WriteMessage(c, "unexpected number of updates found", http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
item, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
item, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtos []domain.DiscordWebHookDto
|
var dtos []domain.DiscordWebHookDto
|
||||||
dtos = append(dtos, dtoconv.DiscordWebhookToDto(item))
|
dtos = append(dtos, services.DiscordWebhookToDto(item))
|
||||||
|
return c.JSON(http.StatusOK, domain.DiscordWebhookResponse{
|
||||||
p.Payload = dtos
|
BaseResponse: domain.BaseResponse{
|
||||||
p.IsError = false
|
Message: ResponseMessageSuccess,
|
||||||
return c.JSON(http.StatusOK, p)
|
},
|
||||||
|
Payload: dtos,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnableDiscordWebHook
|
// EnableDiscordWebHook
|
||||||
// @Summary Enables a source to continue processing.
|
// @Summary Enables a source to continue processing.
|
||||||
// @Param id path int true "id"
|
// @Param id path int true "id"
|
||||||
// @Tags DiscordWebhook
|
// @Tags Discord, Webhook
|
||||||
// @Router /v1/discord/webhooks/{id}/enable [post]
|
// @Router /discord/webhooks/{ID}/enable [post]
|
||||||
// @Success 200 {object} domain.DiscordWebhookResponse "OK"
|
|
||||||
// @Failure 400 {object} domain.DiscordWebhookResponse
|
|
||||||
// @Failure 500 {object} domain.DiscordWebhookResponse
|
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) enableDiscordWebHook(c echo.Context) error {
|
func (s *Handler) enableDiscordWebHook(c echo.Context) error {
|
||||||
p := domain.DiscordWebhookResponse{
|
|
||||||
BaseResponse: domain.BaseResponse{
|
|
||||||
Message: ResponseMessageSuccess,
|
|
||||||
IsError: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := strconv.Atoi(c.Param("ID"))
|
id, err := strconv.Atoi(c.Param("ID"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusBadRequest)
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to make sure we can find the record
|
// Check to make sure we can find the record
|
||||||
record, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
_, err = s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusBadRequest)
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
if record.UserID != s.GetUserIdFromJwtToken(c) {
|
|
||||||
p.BaseResponse.Message = ErrYouDontOwnTheRecord
|
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updated, err := s.repo.DiscordWebHooks.Enable(c.Request().Context(), int64(id))
|
updated, err := s.repo.DiscordWebHooks.Enable(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if updated != 1 {
|
if updated != 1 {
|
||||||
p.BaseResponse.Message = ErrFailedToUpdateRecord
|
s.WriteMessage(c, "unexpected number of updates found", http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
item, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
item, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtos []domain.DiscordWebHookDto
|
var dtos []domain.DiscordWebHookDto
|
||||||
dtos = append(dtos, dtoconv.DiscordWebhookToDto(item))
|
dtos = append(dtos, services.DiscordWebhookToDto(item))
|
||||||
|
return c.JSON(http.StatusOK, domain.DiscordWebhookResponse{
|
||||||
p.Payload = dtos
|
BaseResponse: domain.BaseResponse{
|
||||||
p.IsError = false
|
Message: ResponseMessageSuccess,
|
||||||
return c.JSON(http.StatusOK, p)
|
},
|
||||||
|
Payload: dtos,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteDiscordWebHook
|
// DeleteDiscordWebHook
|
||||||
// @Summary Deletes a record by ID.
|
// @Summary Deletes a record by ID.
|
||||||
// @Param id path string true "id"
|
// @Param id path string true "id"
|
||||||
// @Tags DiscordWebhook
|
// @Tags Discord, Webhook
|
||||||
// @Router /v1/discord/webhooks/{id} [delete]
|
// @Router /discord/webhooks/{ID} [delete]
|
||||||
// @Success 200 {object} domain.DiscordWebhookResponse "OK"
|
// @Success 200 {object} domain.DiscordWebhookResponse "OK"
|
||||||
// @Failure 400 {object} domain.DiscordWebhookResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.DiscordWebhookResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) deleteDiscordWebHook(c echo.Context) error {
|
func (s *Handler) deleteDiscordWebHook(c echo.Context) error {
|
||||||
p := domain.DiscordWebhookResponse{
|
|
||||||
BaseResponse: domain.BaseResponse{
|
|
||||||
Message: ResponseMessageSuccess,
|
|
||||||
IsError: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := strconv.Atoi(c.Param("ID"))
|
id, err := strconv.Atoi(c.Param("ID"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusBadRequest, err.Error())
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to make sure we can find the record
|
// Check to make sure we can find the record
|
||||||
record, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
_, err = s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusInternalServerError, err.Error())
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
if record.UserID != s.GetUserIdFromJwtToken(c) {
|
|
||||||
p.BaseResponse.Message = ErrYouDontOwnTheRecord
|
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Soft delete the record
|
// Soft delete the record
|
||||||
updated, err := s.repo.DiscordWebHooks.SoftDelete(c.Request().Context(), int64(id))
|
updated, err := s.repo.DiscordWebHooks.SoftDelete(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusInternalServerError, err.Error())
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if updated != 1 {
|
if updated != 1 {
|
||||||
p.BaseResponse.Message = ErrFailedToUpdateRecord
|
s.WriteMessage(c, "unexpected number of updates found", http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
item, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
item, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtos []domain.DiscordWebHookDto
|
var dtos []domain.DiscordWebHookDto
|
||||||
dtos = append(dtos, dtoconv.DiscordWebhookToDto(item))
|
dtos = append(dtos, services.DiscordWebhookToDto(item))
|
||||||
|
return c.JSON(http.StatusOK, domain.DiscordWebhookResponse{
|
||||||
p.Payload = dtos
|
BaseResponse: domain.BaseResponse{
|
||||||
p.BaseResponse.IsError = false
|
Message: ResponseMessageSuccess,
|
||||||
return c.JSON(http.StatusOK, p)
|
},
|
||||||
|
Payload: dtos,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateDiscordWebHook
|
// UpdateDiscordWebHook
|
||||||
// @Summary Updates a valid discord webhook ID based on the body given.
|
// @Summary Updates a valid discord webhook ID based on the body given.
|
||||||
// @Param id path string true "id"
|
// @Param id path string true "id"
|
||||||
// @Tags DiscordWebhook
|
// @Tags Discord, Webhook
|
||||||
// @Router /v1/discord/webhooks/{id} [patch]
|
// @Router /discord/webhooks/{id} [patch]
|
||||||
// @Success 200 {object} domain.DiscordWebhookResponse "OK"
|
// @Success 200 {object} domain.DiscordWebhookResponse "OK"
|
||||||
// @Failure 400 {object} domain.BaseResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.BaseResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
|
@ -3,21 +3,20 @@ package v1
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
echojwt "github.com/labstack/echo-jwt/v4"
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
_ "github.com/lib/pq"
|
||||||
swagger "github.com/swaggo/echo-swagger"
|
swagger "github.com/swaggo/echo-swagger"
|
||||||
|
|
||||||
_ "git.jamestombleson.com/jtom38/newsbot-api/docs"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/database"
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/services"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
Router *echo.Echo
|
Router *echo.Echo
|
||||||
//Db *database.Queries
|
Db *database.Queries
|
||||||
|
//dto *dto.DtoClient
|
||||||
config services.Configs
|
config services.Configs
|
||||||
repo services.RepositoryService
|
repo services.RepositoryService
|
||||||
}
|
}
|
||||||
@ -26,13 +25,8 @@ const (
|
|||||||
ErrParameterIdMissing = "The requested parameter ID was not found."
|
ErrParameterIdMissing = "The requested parameter ID was not found."
|
||||||
ErrParameterMissing = "The requested parameter was not found found:"
|
ErrParameterMissing = "The requested parameter was not found found:"
|
||||||
ErrUnableToParseId = "Unable to parse the requested ID"
|
ErrUnableToParseId = "Unable to parse the requested ID"
|
||||||
|
|
||||||
ErrRecordMissing = "The requested record was not found"
|
ErrRecordMissing = "The requested record was not found"
|
||||||
ErrFailedToCreateRecord = "The record was not created due to a database problem"
|
ErrFailedToCreateRecord = "The record was not created due to a database problem"
|
||||||
ErrFailedToUpdateRecord = "The requested record was not updated due to a database problem"
|
|
||||||
|
|
||||||
ErrUserUnknown = "User is unknown"
|
|
||||||
ErrYouDontOwnTheRecord = "The record requested does not belong to you"
|
|
||||||
|
|
||||||
ResponseMessageSuccess = "Success"
|
ResponseMessageSuccess = "Success"
|
||||||
)
|
)
|
||||||
@ -46,30 +40,21 @@ var (
|
|||||||
|
|
||||||
func NewServer(ctx context.Context, configs services.Configs, conn *sql.DB) *Handler {
|
func NewServer(ctx context.Context, configs services.Configs, conn *sql.DB) *Handler {
|
||||||
s := &Handler{
|
s := &Handler{
|
||||||
|
//Db: db,
|
||||||
|
//dto: dto.NewDtoClient(db),
|
||||||
config: configs,
|
config: configs,
|
||||||
repo: services.NewRepositoryService(conn),
|
repo: services.NewRepositoryService(conn),
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtConfig := echojwt.Config{
|
|
||||||
NewClaimsFunc: func(c echo.Context) jwt.Claims {
|
|
||||||
return new(JwtToken)
|
|
||||||
},
|
|
||||||
SigningKey: []byte(configs.JwtSecret),
|
|
||||||
}
|
|
||||||
|
|
||||||
router := echo.New()
|
router := echo.New()
|
||||||
router.Pre(middleware.RemoveTrailingSlash())
|
|
||||||
router.Pre(middleware.Logger())
|
|
||||||
router.Pre(middleware.Recover())
|
|
||||||
router.GET("/swagger/*", swagger.WrapHandler)
|
router.GET("/swagger/*", swagger.WrapHandler)
|
||||||
|
|
||||||
v1 := router.Group("/api/v1")
|
v1 := router.Group("/api/v1")
|
||||||
articles := v1.Group("/articles")
|
articles := v1.Group("/articles")
|
||||||
articles.Use(echojwt.WithConfig(jwtConfig))
|
articles.GET("/", s.listArticles)
|
||||||
articles.GET("", s.listArticles)
|
articles.GET("/:id", s.getArticle)
|
||||||
articles.GET(":id", s.getArticle)
|
articles.GET("/:id/details", s.getArticleDetails)
|
||||||
articles.GET(":id/details", s.getArticleDetails)
|
articles.GET("/by/source/:id", s.ListArticlesBySourceId)
|
||||||
articles.GET("by/source/:id", s.ListArticlesBySourceId)
|
|
||||||
|
|
||||||
//dwh := v1.Group("/discord/webhooks")
|
//dwh := v1.Group("/discord/webhooks")
|
||||||
//dwh.GET("/", s.ListDiscordWebHooks)
|
//dwh.GET("/", s.ListDiscordWebHooks)
|
||||||
@ -87,111 +72,46 @@ func NewServer(ctx context.Context, configs services.Configs, conn *sql.DB) *Han
|
|||||||
//settings.GET("/", s.getSettings)
|
//settings.GET("/", s.getSettings)
|
||||||
|
|
||||||
sources := v1.Group("/sources")
|
sources := v1.Group("/sources")
|
||||||
sources.Use(echojwt.WithConfig(jwtConfig))
|
sources.GET("/", s.listSources)
|
||||||
sources.GET("", s.listSources)
|
|
||||||
sources.GET("/by/source", s.listSourcesBySource)
|
sources.GET("/by/source", s.listSourcesBySource)
|
||||||
sources.GET("/by/sourceAndName", s.GetSourceBySourceAndName)
|
sources.GET("/by/sourceAndName", s.GetSourceBySourceAndName)
|
||||||
//sources.POST("/new/reddit", s.newRedditSource)
|
//sources.POST("/new/reddit", s.newRedditSource)
|
||||||
//sources.POST("/new/youtube", s.newYoutubeSource)
|
//sources.POST("/new/youtube", s.newYoutubeSource)
|
||||||
//sources.POST("/new/twitch", s.newTwitchSource)
|
//sources.POST("/new/twitch", s.newTwitchSource)
|
||||||
sources.POST("/new/rss", s.newRssSource)
|
sources.POST("/new/rss", s.newRssSource)
|
||||||
sources.GET("/:id", s.getSource)
|
sources.GET("/:ID/", s.getSource)
|
||||||
sources.DELETE("/:id", s.deleteSources)
|
sources.DELETE("/:ID/", s.deleteSources)
|
||||||
sources.POST("/:id/disable", s.disableSource)
|
sources.POST("/:ID/disable", s.disableSource)
|
||||||
sources.POST("/:id/enable", s.enableSource)
|
sources.POST("/:ID/enable", s.enableSource)
|
||||||
|
|
||||||
users := v1.Group("/users")
|
//subs := v1.Group("/subscriptions")
|
||||||
users.POST("/login", s.AuthLogin)
|
//subs.GET("/", s.ListSubscriptions)
|
||||||
users.POST("/register", s.AuthRegister)
|
//subs.GET("/details", s.ListSubscriptionDetails)
|
||||||
users.Use(echojwt.WithConfig(jwtConfig))
|
//subs.GET("/by/discordId", s.GetSubscriptionsByDiscordId)
|
||||||
users.POST("/scopes/add", s.AddScopes)
|
//subs.GET("/by/sourceId", s.GetSubscriptionsBySourceId)
|
||||||
users.POST("/scopes/remove", s.RemoveScopes)
|
//subs.POST("/discord/webhook/new", s.newDiscordWebHookSubscription)
|
||||||
users.POST("/refresh/token", s.RefreshJwtToken)
|
//subs.DELETE("/discord/webhook/delete", s.DeleteDiscordWebHookSubscription)
|
||||||
users.POST("/refresh/sessionToken", s.NewSessionToken)
|
|
||||||
|
|
||||||
s.Router = router
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
//type ApiStatusModel struct {
|
type ApiStatusModel struct {
|
||||||
// StatusCode int `json:"status"`
|
StatusCode int `json:"status"`
|
||||||
// Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
//}
|
|
||||||
|
|
||||||
//type ApiError struct {
|
|
||||||
// *ApiStatusModel
|
|
||||||
//}
|
|
||||||
|
|
||||||
//func (s *Handler) WriteError(c echo.Context, errMessage error, HttpStatusCode int) error {
|
|
||||||
// return c.JSON(HttpStatusCode, domain.BaseResponse{
|
|
||||||
// Message: errMessage.Error(),
|
|
||||||
// IsError: true,
|
|
||||||
// })
|
|
||||||
//}
|
|
||||||
|
|
||||||
//func (s *Handler) WriteMessage(c echo.Context, msg string, HttpStatusCode int) error {
|
|
||||||
// return c.JSON(HttpStatusCode, domain.BaseResponse{
|
|
||||||
// Message: msg,
|
|
||||||
// })
|
|
||||||
//}
|
|
||||||
|
|
||||||
//func (s *Handler) InternalServerErrorResponse(c echo.Context, msg string) error {
|
|
||||||
// return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
|
|
||||||
// Message: msg,
|
|
||||||
// })
|
|
||||||
//}
|
|
||||||
|
|
||||||
//func (s *Handler) UnauthorizedResponse(c echo.Context, msg string) error {
|
|
||||||
// return c.JSON(http.StatusUnauthorized, domain.BaseResponse{
|
|
||||||
// Message: msg,
|
|
||||||
// })
|
|
||||||
//}
|
|
||||||
|
|
||||||
// If the token is not valid then an json error will be returned.
|
|
||||||
// If the token has the wrong scope, a json error will be returned.
|
|
||||||
// If the token passes all the checks, it is valid and is returned back to the caller.
|
|
||||||
func (s *Handler) ValidateJwtToken(c echo.Context, requiredScope string) (JwtToken, error) {
|
|
||||||
token, err := s.getJwtTokenFromContext(c)
|
|
||||||
if err != nil {
|
|
||||||
return JwtToken{}, errors.New(ErrJwtMissing)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = token.hasExpired()
|
|
||||||
if err != nil {
|
|
||||||
return JwtToken{}, errors.New(ErrJwtExpired)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = token.hasScope(requiredScope)
|
|
||||||
if err != nil {
|
|
||||||
return JwtToken{}, errors.New(ErrJwtScopeMissing)
|
|
||||||
}
|
|
||||||
|
|
||||||
if token.Iss != s.config.ServerAddress {
|
|
||||||
return JwtToken{}, errors.New(ErrJwtInvalidIssuer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If you are the built in admin account, skip the username and session token check
|
|
||||||
if token.UserName == "admin" {
|
|
||||||
return token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := s.repo.Users.GetUser(c.Request().Context(), token.UserName)
|
|
||||||
if err != nil {
|
|
||||||
return JwtToken{}, errors.New("user record not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.SessionToken != token.SessionToken {
|
|
||||||
return JwtToken{}, errors.New("invalid session token")
|
|
||||||
}
|
|
||||||
|
|
||||||
return token, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Handler) GetUserIdFromJwtToken(c echo.Context) int64 {
|
type ApiError struct {
|
||||||
token, err := s.getJwtTokenFromContext(c)
|
*ApiStatusModel
|
||||||
if err != nil {
|
}
|
||||||
return -1
|
|
||||||
}
|
func (s *Handler) WriteError(c echo.Context, errMessage error, HttpStatusCode int) error {
|
||||||
|
return c.JSON(HttpStatusCode, domain.BaseResponse{
|
||||||
return token.GetUserId()
|
Message: errMessage.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Handler) WriteMessage(c echo.Context, msg string, HttpStatusCode int) error {
|
||||||
|
return c.JSON(HttpStatusCode, domain.BaseResponse{
|
||||||
|
Message: msg,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,130 +0,0 @@
|
|||||||
package v1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ErrJwtMissing = "auth token is missing"
|
|
||||||
ErrJwtClaimsMissing = "claims missing on token"
|
|
||||||
ErrJwtExpired = "auth token has expired"
|
|
||||||
ErrJwtScopeMissing = "required scope is missing"
|
|
||||||
ErrJwtInvalidIssuer = "incorrect server issued the token"
|
|
||||||
)
|
|
||||||
|
|
||||||
type JwtToken struct {
|
|
||||||
Exp time.Time `json:"exp"`
|
|
||||||
Iss string `json:"iss"`
|
|
||||||
Authorized bool `json:"authorized"`
|
|
||||||
UserName string `json:"username"`
|
|
||||||
UserId int64 `json:"userId"`
|
|
||||||
Scopes []string `json:"scopes"`
|
|
||||||
SessionToken string `json:"sessionToken"`
|
|
||||||
jwt.RegisteredClaims
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j JwtToken) IsValid(scope string) error {
|
|
||||||
err := j.hasExpired()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to see if they have the scope to do anything
|
|
||||||
// if they do, let them pass
|
|
||||||
err = j.hasScope(domain.ScopeAll)
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = j.hasScope(scope)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j JwtToken) GetUsername() string {
|
|
||||||
return j.UserName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j JwtToken) GetUserId() int64 {
|
|
||||||
return j.UserId
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j JwtToken) hasExpired() error {
|
|
||||||
// Check to see if the token has expired
|
|
||||||
//hasExpired := j.Exp.Compare(time.Now())
|
|
||||||
hasExpired := time.Now().Compare(j.Exp)
|
|
||||||
if hasExpired == 1 {
|
|
||||||
return errors.New(ErrJwtExpired)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will check the users token to make sure they have the correct scope to access the handler.
|
|
||||||
// It will evaluate if you have the admin scope or the required scope for the handler.
|
|
||||||
func (j JwtToken) hasScope(scope string) error {
|
|
||||||
// they have the scope to access everything, so let them pass.
|
|
||||||
userScopes := strings.Join(j.Scopes, "")
|
|
||||||
if strings.Contains(domain.ScopeAll, userScopes) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(userScopes, scope) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New(ErrJwtScopeMissing)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) generateJwt(username, issuer, sessionToken string, userScopes []string, userId int64) (string, error) {
|
|
||||||
return h.generateJwtWithExp(username, issuer, sessionToken, userScopes, userId, time.Now().Add(10*time.Minute))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) generateJwtWithExp(username, issuer, sessionToken string, userScopes []string, userId int64, expiresAt time.Time) (string, error) {
|
|
||||||
secret := []byte(h.config.JwtSecret)
|
|
||||||
|
|
||||||
// Anyone who wants to decrypt the key needs to use the same method
|
|
||||||
token := jwt.New(jwt.SigningMethodHS256)
|
|
||||||
claims := token.Claims.(jwt.MapClaims)
|
|
||||||
claims["exp"] = expiresAt
|
|
||||||
claims["authorized"] = true
|
|
||||||
claims["username"] = username
|
|
||||||
claims["iss"] = issuer
|
|
||||||
claims["userId"] = userId
|
|
||||||
claims["sessionToken"] = sessionToken
|
|
||||||
|
|
||||||
var scopes []string
|
|
||||||
scopes = append(scopes, userScopes...)
|
|
||||||
claims["scopes"] = scopes
|
|
||||||
|
|
||||||
tokenString, err := token.SignedString(secret)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tokenString, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) getJwtTokenFromContext(c echo.Context) (JwtToken, error) {
|
|
||||||
// Make sure that the request came with a jwtToken
|
|
||||||
token, ok := c.Get("user").(*jwt.Token)
|
|
||||||
if !ok {
|
|
||||||
return JwtToken{}, errors.New(ErrJwtMissing)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the claims from the token
|
|
||||||
claims, ok := token.Claims.(*JwtToken)
|
|
||||||
if !ok {
|
|
||||||
return JwtToken{}, errors.New(ErrJwtClaimsMissing)
|
|
||||||
}
|
|
||||||
|
|
||||||
return *claims, nil
|
|
||||||
}
|
|
36
internal/handler/v1/queue.go
Normal file
36
internal/handler/v1/queue.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 *Handler) ListDiscordWebhookQueue(c echo.Context) error {
|
||||||
|
// p := ListDiscordWebHooksQueueResults{
|
||||||
|
// ApiStatusModel: ApiStatusModel{
|
||||||
|
// Message: "OK",
|
||||||
|
// StatusCode: http.StatusOK,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Get the raw resp from sql
|
||||||
|
// res, err := s.dto.ListDiscordWebhookQueueDetails(c.Request().Context(), 50)
|
||||||
|
// if err != nil {
|
||||||
|
// return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
|
||||||
|
// Message: err.Error(),
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// p.Payload = res
|
||||||
|
// return c.JSON(http.StatusOK, p)
|
||||||
|
//}
|
39
internal/handler/v1/settings.go
Normal file
39
internal/handler/v1/settings.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 *Handler) getSettings(c echo.Context) error {
|
||||||
|
id := c.Param("ID")
|
||||||
|
|
||||||
|
uuid, err := uuid.Parse(id)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := s.Db.GetSourceByID(c.Request().Context(), uuid)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
bResult, err := json.Marshal(res)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, bResult)
|
||||||
|
}
|
@ -1,40 +1,46 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/database"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/dtoconv"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
"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"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ListSources struct {
|
||||||
|
ApiStatusModel
|
||||||
|
Payload []models.SourceDto `json:"payload"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetSource struct {
|
||||||
|
ApiStatusModel
|
||||||
|
Payload models.SourceDto `json:"payload"`
|
||||||
|
}
|
||||||
|
|
||||||
// ListSources
|
// ListSources
|
||||||
// @Summary Lists the top 50 records
|
// @Summary Lists the top 50 records
|
||||||
// @Param page query string false "page number"
|
// @Param page query string false "page number"
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Tags Source
|
// @Tags Source
|
||||||
// @Router /v1/sources [get]
|
// @Router /sources [get]
|
||||||
// @Success 200 {object} domain.SourcesResponse "ok"
|
// @Success 200 {object} domain.SourcesResponse "ok"
|
||||||
// @Failure 400 {object} domain.SourcesResponse "Unable to reach SQL or Data problems"
|
// @Failure 400 {object} domain.BaseResponse "Unable to reach SQL or Data problems"
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) listSources(c echo.Context) error {
|
func (s *Handler) listSources(c echo.Context) error {
|
||||||
p := domain.SourcesResponse{
|
resp := domain.SourcesResponse {
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
IsError: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeSourceRead)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
page, err := strconv.Atoi(c.QueryParam("page"))
|
page, err := strconv.Atoi(c.QueryParam("page"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
page = 0
|
page = 0
|
||||||
@ -43,13 +49,11 @@ func (s *Handler) listSources(c echo.Context) error {
|
|||||||
// Default way of showing all sources
|
// Default way of showing all sources
|
||||||
items, err := s.repo.Sources.List(c.Request().Context(), page, 25)
|
items, err := s.repo.Sources.List(c.Request().Context(), page, 25)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Payload = dtoconv.SourcesToDto(items)
|
resp.Payload = services.SourcesToDto(items)
|
||||||
p.BaseResponse.IsError = false
|
return c.JSON(http.StatusOK, resp)
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListSourcesBySource
|
// ListSourcesBySource
|
||||||
@ -58,29 +62,20 @@ func (s *Handler) listSources(c echo.Context) error {
|
|||||||
// @Param page query string false "page number"
|
// @Param page query string false "page number"
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Tags Source
|
// @Tags Source
|
||||||
// @Router /v1/sources/by/source [get]
|
// @Router /sources/by/source [get]
|
||||||
// @Success 200 {object} domain.SourcesResponse "ok"
|
// @Success 200 {object} domain.SourcesResponse "ok"
|
||||||
// @Failure 400 {object} domain.SourcesResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.SourcesResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) listSourcesBySource(c echo.Context) error {
|
func (s *Handler) listSourcesBySource(c echo.Context) error {
|
||||||
p := domain.SourcesResponse{
|
resp := domain.SourcesResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
IsError: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeSourceRead)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
source := c.QueryParam("source")
|
source := c.QueryParam("source")
|
||||||
if source == "" {
|
if source == "" {
|
||||||
p.BaseResponse.Message = fmt.Sprintf("%s source", ErrParameterMissing)
|
s.WriteMessage(c, fmt.Sprintf("%s source", ErrParameterMissing), http.StatusBadRequest)
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
page, err := strconv.Atoi(c.QueryParam("page"))
|
page, err := strconv.Atoi(c.QueryParam("page"))
|
||||||
@ -91,55 +86,47 @@ func (s *Handler) listSourcesBySource(c echo.Context) error {
|
|||||||
// Shows the list by Sources.source
|
// Shows the list by Sources.source
|
||||||
items, err := s.repo.Sources.ListBySource(c.Request().Context(), page, 25, source)
|
items, err := s.repo.Sources.ListBySource(c.Request().Context(), page, 25, source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
Message: err.Error(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Payload = dtoconv.SourcesToDto(items)
|
resp.Payload = services.SourcesToDto(items)
|
||||||
p.BaseResponse.IsError = false
|
return c.JSON(http.StatusOK, resp)
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSource
|
// GetSource
|
||||||
// @Summary Returns a single entity by ID
|
// @Summary Returns a single entity by ID
|
||||||
// @Param id path int true "id"
|
// @Param id path int true "uuid"
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Tags Source
|
// @Tags Source
|
||||||
// @Router /v1/sources/{id} [get]
|
// @Router /sources/{id} [get]
|
||||||
// @Success 200 {object} domain.SourcesResponse "ok"
|
// @Success 200 {object} domain.SourcesResponse "ok"
|
||||||
// @Failure 400 {object} domain.SourcesResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.SourcesResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) getSource(c echo.Context) error {
|
func (s *Handler) getSource(c echo.Context) error {
|
||||||
p := domain.SourcesResponse{
|
resp := domain.SourcesResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeSourceRead)
|
id, err := strconv.Atoi(c.Param("ID"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
Message: ErrUnableToParseId,
|
||||||
}
|
})
|
||||||
|
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
item, err := s.repo.Sources.GetById(c.Request().Context(), int64(id))
|
item, err := s.repo.Sources.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var dto []domain.SourceDto
|
var dto []domain.SourceDto
|
||||||
dto = append(dto, dtoconv.SourceToDto(item))
|
dto = append(dto, services.SourceToDto(item))
|
||||||
p.Payload = dto
|
resp.Payload = dto
|
||||||
p.BaseResponse.IsError = false
|
return c.JSON(http.StatusOK, resp)
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSourceByNameAndSource
|
// GetSourceByNameAndSource
|
||||||
@ -148,43 +135,34 @@ func (s *Handler) getSource(c echo.Context) error {
|
|||||||
// @Param source query string true "reddit"
|
// @Param source query string true "reddit"
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Tags Source
|
// @Tags Source
|
||||||
// @Router /v1/sources/by/sourceAndName [get]
|
// @Router /sources/by/sourceAndName [get]
|
||||||
// @Success 200 {object} domain.SourcesResponse "ok"
|
// @Success 200 {object} domain.SourcesResponse "ok"
|
||||||
// @Failure 400 {object} domain.BaseResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.BaseResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) GetSourceBySourceAndName(c echo.Context) error {
|
func (s *Handler) GetSourceBySourceAndName(c echo.Context) error {
|
||||||
p := domain.SourcesResponse{
|
resp := domain.SourcesResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
IsError: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeSourceRead)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
var param domain.GetSourceBySourceAndNameParamRequest
|
var param domain.GetSourceBySourceAndNameParamRequest
|
||||||
err = c.Bind(¶m)
|
err := c.Bind(¶m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
Message: err.Error(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
item, err := s.repo.Sources.GetBySourceAndName(c.Request().Context(), param.Source, param.Name)
|
item, err := s.repo.Sources.GetBySourceAndName(c.Request().Context(), param.Source, param.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusInternalServerError, err.Error())
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var dto []domain.SourceDto
|
var dto []domain.SourceDto
|
||||||
dto = append(dto, dtoconv.SourceToDto(item))
|
dto = append(dto, services.SourceToDto(item))
|
||||||
p.Payload = dto
|
resp.Payload = dto
|
||||||
p.BaseResponse.IsError = false
|
return c.JSON(http.StatusOK, resp)
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRedditSource
|
// NewRedditSource
|
||||||
@ -192,73 +170,55 @@ func (s *Handler) GetSourceBySourceAndName(c echo.Context) error {
|
|||||||
// @Param name query string true "name"
|
// @Param name query string true "name"
|
||||||
// @Param url query string true "url"
|
// @Param url query string true "url"
|
||||||
// @Tags Source
|
// @Tags Source
|
||||||
// @Router /v1/sources/new/reddit [post]
|
// @Router /sources/new/reddit [post]
|
||||||
// @Success 200 {object} domain.SourcesResponse "ok"
|
// @Success 200 {object} domain.SourcesResponse "ok"
|
||||||
// @Failure 400 {object} domain.SourcesResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.SourcesResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) newRedditSource(c echo.Context) error {
|
func (s *Handler) newRedditSource(c echo.Context) error {
|
||||||
p := domain.SourcesResponse{
|
resp := domain.SourcesResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
IsError: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeSourceCreate)
|
var param domain.NewSourceParamRequest
|
||||||
|
err := c.Bind(¶m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
Message: err.Error(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var param domain.NewSourceParamRequest
|
|
||||||
err = c.Bind(¶m)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
|
||||||
if param.Url == "" {
|
if param.Url == "" {
|
||||||
p.BaseResponse.Message = "url is missing"
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
Message: "Url is missing a value",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if !strings.Contains(param.Url, "reddit.com") {
|
if !strings.Contains(param.Url, "reddit.com") {
|
||||||
p.BaseResponse.Message = "invalid url"
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
Message: "Invalid URL given",
|
||||||
}
|
})
|
||||||
|
|
||||||
// Check to see if we already have this record, if we do, return it.
|
|
||||||
item, err := s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorReddit, param.Name)
|
|
||||||
if err == nil {
|
|
||||||
var dto []domain.SourceDto
|
|
||||||
dto = append(dto, dtoconv.SourceToDto(item))
|
|
||||||
p.Payload = dto
|
|
||||||
p.BaseResponse.IsError = false
|
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := fmt.Sprintf("twitch, %v, %s", param.Name, param.Tags)
|
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)
|
rows, err := s.repo.Sources.Create(c.Request().Context(), domain.SourceCollectorReddit, param.Name, param.Url, tags, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rows != 1 {
|
if rows != 1 {
|
||||||
p.BaseResponse.Message = ErrFailedToCreateRecord
|
s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
item, err = s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorReddit, param.Name)
|
item, err := s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorReddit, param.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var dto []domain.SourceDto
|
var dto []domain.SourceDto
|
||||||
dto = append(dto, dtoconv.SourceToDto(item))
|
dto = append(dto, services.SourceToDto(item))
|
||||||
p.Payload = dto
|
resp.Payload = dto
|
||||||
p.BaseResponse.IsError = false
|
return c.JSON(http.StatusOK, resp)
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewYoutubeSource
|
// NewYoutubeSource
|
||||||
@ -266,388 +226,288 @@ func (s *Handler) newRedditSource(c echo.Context) error {
|
|||||||
// @Param name query string true "name"
|
// @Param name query string true "name"
|
||||||
// @Param url query string true "url"
|
// @Param url query string true "url"
|
||||||
// @Tags Source
|
// @Tags Source
|
||||||
// @Router /v1/sources/new/youtube [post]
|
// @Router /sources/new/youtube [post]
|
||||||
// @Success 200 {object} domain.SourcesResponse "ok"
|
|
||||||
// @Failure 400 {object} domain.SourcesResponse
|
|
||||||
// @Failure 500 {object} domain.SourcesResponse
|
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) newYoutubeSource(c echo.Context) error {
|
func (s *Handler) newYoutubeSource(c echo.Context) error {
|
||||||
p := domain.SourcesResponse{
|
|
||||||
BaseResponse: domain.BaseResponse{
|
|
||||||
Message: ResponseMessageSuccess,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the jwt
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeSourceCreate)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
var param domain.NewSourceParamRequest
|
var param domain.NewSourceParamRequest
|
||||||
err = c.Bind(¶m)
|
err := c.Bind(¶m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
Message: err.Error(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//query := r.URL.Query()
|
||||||
|
//_name := query["name"][0]
|
||||||
|
//_url := query["url"][0]
|
||||||
|
////_tags := query["tags"][0]
|
||||||
|
|
||||||
if param.Url == "" {
|
if param.Url == "" {
|
||||||
p.BaseResponse.Message = "url is missing a value"
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
Message: "url is missing a value",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if !strings.Contains(param.Url, "youtube.com") {
|
if !strings.Contains(param.Url, "youtube.com") {
|
||||||
p.BaseResponse.Message = "invalid url"
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
Message: "Invalid URL",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
item, err := s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorYoutube, param.Name)
|
/*
|
||||||
if err == nil {
|
if _tags == "" {
|
||||||
var dto []domain.SourceDto
|
tags = fmt.Sprintf("twitch, %v", _name)
|
||||||
dto = append(dto, dtoconv.SourceToDto(item))
|
} else {
|
||||||
p.Payload = dto
|
|
||||||
p.BaseResponse.IsError = false
|
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
tags := fmt.Sprintf("twitch, %v", param.Name)
|
tags := fmt.Sprintf("twitch, %v", param.Name)
|
||||||
rows, err := s.repo.Sources.Create(c.Request().Context(), domain.SourceCollectorYoutube, param.Name, param.Url, tags, true)
|
|
||||||
|
params := database.CreateSourceParams{
|
||||||
|
ID: uuid.New(),
|
||||||
|
Site: "youtube",
|
||||||
|
Name: param.Name,
|
||||||
|
Source: "youtube",
|
||||||
|
Type: "feed",
|
||||||
|
Enabled: true,
|
||||||
|
Url: param.Url,
|
||||||
|
Tags: tags,
|
||||||
|
}
|
||||||
|
err = s.Db.CreateSource(context.Background(), params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusInternalServerError, err.Error())
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rows != 1 {
|
bJson, err := json.Marshal(¶ms)
|
||||||
p.BaseResponse.Message = ErrFailedToCreateRecord
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
item, err = s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorYoutube, param.Name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
Message: err.Error(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var dto []domain.SourceDto
|
return c.JSON(http.StatusOK, bJson)
|
||||||
dto = append(dto, dtoconv.SourceToDto(item))
|
|
||||||
p.Payload = dto
|
|
||||||
p.BaseResponse.IsError = false
|
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTwitchSource
|
// NewTwitchSource
|
||||||
// @Summary Creates a new twitch source to monitor.
|
// @Summary Creates a new twitch source to monitor.
|
||||||
// @Param name query string true "name"
|
// @Param name query string true "name"
|
||||||
// @Tags Source
|
// @Tags Source
|
||||||
// @Router /v1/sources/new/twitch [post]
|
// @Router /sources/new/twitch [post]
|
||||||
// @Success 200 {object} domain.SourcesResponse "ok"
|
|
||||||
// @Failure 400 {object} domain.SourcesResponse
|
|
||||||
// @Failure 500 {object} domain.SourcesResponse
|
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) newTwitchSource(c echo.Context) error {
|
func (s *Handler) newTwitchSource(c echo.Context) error {
|
||||||
p := domain.SourcesResponse{
|
|
||||||
BaseResponse: domain.BaseResponse{
|
|
||||||
Message: ResponseMessageSuccess,
|
|
||||||
IsError: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeSourceCreate)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
var param domain.NewSourceParamRequest
|
var param domain.NewSourceParamRequest
|
||||||
err = c.Bind(¶m)
|
err := c.Bind(¶m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
Message: err.Error(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//query := r.URL.Query()
|
||||||
|
//_name := query["name"][0]
|
||||||
|
|
||||||
tags := fmt.Sprintf("twitch, %v", param.Name)
|
tags := fmt.Sprintf("twitch, %v", param.Name)
|
||||||
url := fmt.Sprintf("https://twitch.tv/%v", param.Name)
|
_url := fmt.Sprintf("https://twitch.tv/%v", param.Name)
|
||||||
|
|
||||||
// Check if the record already exists
|
params := database.CreateSourceParams{
|
||||||
item, err := s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorTwitch, param.Name)
|
ID: uuid.New(),
|
||||||
if err == nil {
|
Site: "twitch",
|
||||||
var dto []domain.SourceDto
|
Name: param.Name,
|
||||||
dto = append(dto, dtoconv.SourceToDto(item))
|
Source: "twitch",
|
||||||
p.Payload = dto
|
Type: "api",
|
||||||
p.BaseResponse.IsError = false
|
Enabled: true,
|
||||||
return c.JSON(http.StatusOK, p)
|
Url: _url,
|
||||||
|
Tags: tags,
|
||||||
}
|
}
|
||||||
|
err = s.Db.CreateSource(c.Request().Context(), params)
|
||||||
rows, err := s.repo.Sources.Create(c.Request().Context(), domain.SourceCollectorTwitch, param.Name, url, tags, true)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
Message: err.Error(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if rows != 1 {
|
bJson, err := json.Marshal(¶ms)
|
||||||
p.BaseResponse.Message = ErrFailedToCreateRecord
|
if err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
item, _ = s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorTwitch, param.Name)
|
return c.JSON(http.StatusOK, bJson)
|
||||||
var dto []domain.SourceDto
|
|
||||||
dto = append(dto, dtoconv.SourceToDto(item))
|
|
||||||
p.Payload = dto
|
|
||||||
p.BaseResponse.IsError = false
|
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRssSource
|
// NewRssSource
|
||||||
// @Summary Creates a new rss source to monitor.
|
// @Summary Creates a new rss source to monitor.
|
||||||
// @Param request body domain.NewSourceParamRequest true "body"
|
// @Param name query string true "Site Name"
|
||||||
|
// @Param url query string true "RSS Url"
|
||||||
// @Tags Source
|
// @Tags Source
|
||||||
// @Router /v1/sources/new/rss [post]
|
// @Router /sources/new/rss [post]
|
||||||
// @Success 200 {object} domain.SourcesResponse "ok"
|
// @Success 200 {object} domain.SourcesResponse "ok"
|
||||||
// @Failure 400 {object} domain.SourcesResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.SourcesResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) newRssSource(c echo.Context) error {
|
func (s *Handler) newRssSource(c echo.Context) error {
|
||||||
p := domain.SourcesResponse{
|
resp := domain.SourcesResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
IsError: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeSourceCreate)
|
var param domain.NewSourceParamRequest
|
||||||
|
err := c.Bind(¶m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
Message: err.Error(),
|
||||||
}
|
})
|
||||||
|
|
||||||
param := domain.NewSourceParamRequest{}
|
|
||||||
err = c.Bind(¶m)
|
|
||||||
//err = (&echo.DefaultBinder{}).BindBody(c, ¶m)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if param.Url == "" {
|
if param.Url == "" {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
Message: "Url is missing a value",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the record already exists
|
|
||||||
blankRecord := entity.SourceEntity{}
|
|
||||||
item, err := s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorRss, param.Name)
|
|
||||||
if err != nil {
|
|
||||||
p.IsError = true
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// No record was found, but no error returned
|
|
||||||
if item != blankRecord {
|
|
||||||
var dto []domain.SourceDto
|
|
||||||
dto = append(dto, dtoconv.SourceToDto(item))
|
|
||||||
p.Payload = dto
|
|
||||||
p.BaseResponse.IsError = false
|
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
//if err == nil {
|
|
||||||
// var dto []domain.SourceDto
|
|
||||||
// dto = append(dto, dtoconv.SourceToDto(item))
|
|
||||||
// p.Payload = dto
|
|
||||||
// p.BaseResponse.IsError = false
|
|
||||||
// return c.JSON(http.StatusOK, p)
|
|
||||||
//}
|
|
||||||
|
|
||||||
tags := fmt.Sprintf("rss, %v, %s", param.Name, param.Tags)
|
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)
|
rows, err := s.repo.Sources.Create(c.Request().Context(), domain.SourceCollectorRss, param.Name, param.Url, tags, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rows != 1 {
|
if rows != 1 {
|
||||||
p.BaseResponse.Message = ErrFailedToCreateRecord
|
s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
item, err = s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorRss, param.Name)
|
item, err := s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorRss, param.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var dto []domain.SourceDto
|
var dto []domain.SourceDto
|
||||||
dto = append(dto, dtoconv.SourceToDto(item))
|
dto = append(dto, services.SourceToDto(item))
|
||||||
p.Payload = dto
|
resp.Payload = dto
|
||||||
p.BaseResponse.IsError = false
|
return c.JSON(http.StatusOK, resp)
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSource
|
// DeleteSource
|
||||||
// @Summary Marks a source as deleted based on its ID value.
|
// @Summary Marks a source as deleted based on its ID value.
|
||||||
// @Param id path int true "id"
|
// @Param id path string true "id"
|
||||||
// @Tags Source
|
// @Tags Source
|
||||||
// @Router /v1/sources/{id} [POST]
|
// @Router /sources/{id} [POST]
|
||||||
// @Success 200 {object} domain.SourcesResponse "ok"
|
|
||||||
// @Failure 400 {object} domain.SourcesResponse
|
|
||||||
// @Failure 500 {object} domain.SourcesResponse
|
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) deleteSources(c echo.Context) error {
|
func (s *Handler) deleteSources(c echo.Context) error {
|
||||||
p := domain.SourcesResponse{
|
id := c.Param("ID")
|
||||||
BaseResponse: domain.BaseResponse{
|
uuid, err := uuid.Parse(id)
|
||||||
Message: ResponseMessageSuccess,
|
|
||||||
IsError: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeAll)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
Message: err.Error(),
|
||||||
}
|
})
|
||||||
|
|
||||||
id, err := strconv.Atoi(c.Param("ID"))
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to make sure we can find the record
|
// Check to make sure we can find the record
|
||||||
_, err = s.repo.Sources.GetById(c.Request().Context(), int64(id))
|
_, err = s.Db.GetSourceByID(c.Request().Context(), uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
Message: err.Error(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the record
|
// Delete the record
|
||||||
rows, err := s.repo.Sources.SoftDelete(c.Request().Context(), int64(id))
|
err = s.Db.DeleteSource(c.Request().Context(), uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
Message: err.Error(),
|
||||||
}
|
})
|
||||||
if rows != 1 {
|
|
||||||
p.BaseResponse.Message = ErrFailedToUpdateRecord
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pull the record with its updated value
|
p := ApiStatusModel{
|
||||||
item, err := s.repo.Sources.GetById(c.Request().Context(), int64(id))
|
Message: "OK",
|
||||||
if err != nil {
|
StatusCode: http.StatusOK,
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var items []domain.SourceDto
|
b, err := json.Marshal(p)
|
||||||
items = append(items, dtoconv.SourceToDto(item))
|
if err != nil {
|
||||||
p.Payload = items
|
return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
|
||||||
p.IsError = false
|
Message: err.Error(),
|
||||||
return c.JSON(http.StatusOK, p)
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableSource
|
// DisableSource
|
||||||
// @Summary Disables a source from processing.
|
// @Summary Disables a source from processing.
|
||||||
// @Param id path int true "id"
|
// @Param id path int true "id"
|
||||||
// @Tags Source
|
// @Tags Source
|
||||||
// @Router /v1/sources/{id}/disable [post]
|
// @Router /sources/{id}/disable [post]
|
||||||
// @Success 200 {object} domain.SourcesResponse "ok"
|
// @Success 200 {object} domain.SourcesResponse "ok"
|
||||||
// @Failure 400 {object} domain.SourcesResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.SourcesResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) disableSource(c echo.Context) error {
|
func (s *Handler) disableSource(c echo.Context) error {
|
||||||
p := domain.SourcesResponse{
|
resp := domain.SourcesResponse {
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
IsError: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeAll)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
id, err := strconv.Atoi(c.Param("ID"))
|
id, err := strconv.Atoi(c.Param("ID"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusBadRequest)
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to make sure we can find the record
|
// Check to make sure we can find the record
|
||||||
_, err = s.repo.Sources.GetById(c.Request().Context(), int64(id))
|
_, err = s.repo.Sources.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusBadRequest)
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.repo.Sources.Disable(c.Request().Context(), int64(id))
|
_, err = s.repo.Sources.Disable(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
item, err := s.repo.Sources.GetById(c.Request().Context(), int64(id))
|
item, err := s.repo.Sources.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var dto []domain.SourceDto
|
var dto []domain.SourceDto
|
||||||
dto = append(dto, dtoconv.SourceToDto(item))
|
dto = append(dto, services.SourceToDto(item))
|
||||||
p.Payload = dto
|
resp.Payload = dto
|
||||||
p.BaseResponse.IsError = false
|
return c.JSON(http.StatusOK, resp)
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnableSource
|
// EnableSource
|
||||||
// @Summary Enables a source to continue processing.
|
// @Summary Enables a source to continue processing.
|
||||||
// @Param id path string true "id"
|
// @Param id path string true "id"
|
||||||
// @Tags Source
|
// @Tags Source
|
||||||
// @Router /v1/sources/{id}/enable [post]
|
// @Router /sources/{id}/enable [post]
|
||||||
// @Success 200 {object} domain.SourcesResponse "ok"
|
// @Success 200 {object} domain.SourcesResponse "ok"
|
||||||
// @Failure 400 {object} domain.SourcesResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.SourcesResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
// @Security Bearer
|
|
||||||
func (s *Handler) enableSource(c echo.Context) error {
|
func (s *Handler) enableSource(c echo.Context) error {
|
||||||
p := domain.SourcesResponse{
|
resp := domain.SourcesResponse {
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
IsError: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_, err := s.ValidateJwtToken(c, domain.ScopeAll)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
id, err := strconv.Atoi(c.Param("ID"))
|
id, err := strconv.Atoi(c.Param("ID"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusBadRequest)
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to make sure we can find the record
|
// Check to make sure we can find the record
|
||||||
_, err = s.repo.Sources.GetById(c.Request().Context(), int64(id))
|
_, err = s.repo.Sources.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusBadRequest)
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.repo.Sources.Enable(c.Request().Context(), int64(id))
|
_, err = s.repo.Sources.Enable(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
item, err := s.repo.Sources.GetById(c.Request().Context(), int64(id))
|
item, err := s.repo.Sources.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.BaseResponse.Message = err.Error()
|
s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var dto []domain.SourceDto
|
var dto []domain.SourceDto
|
||||||
dto = append(dto, dtoconv.SourceToDto(item))
|
dto = append(dto, services.SourceToDto(item))
|
||||||
p.Payload = dto
|
resp.Payload = dto
|
||||||
p.BaseResponse.IsError = false
|
return c.JSON(http.StatusOK, resp)
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
}
|
||||||
|
232
internal/handler/v1/subscriptions.go
Normal file
232
internal/handler/v1/subscriptions.go
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
/*
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/database"
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain/models"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListSubscriptions struct {
|
||||||
|
ApiStatusModel
|
||||||
|
Payload []models.SubscriptionDto `json:"payload"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetSubscription struct {
|
||||||
|
ApiStatusModel
|
||||||
|
Payload models.SubscriptionDto `json:"payload"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListSubscriptionDetails struct {
|
||||||
|
ApiStatusModel
|
||||||
|
Payload []models.SubscriptionDetailsDto `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 *Handler) ListSubscriptions(c echo.Context) error {
|
||||||
|
payload := ListSubscriptions{
|
||||||
|
ApiStatusModel: ApiStatusModel{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Message: "OK",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := s.dto.ListSubscriptions(c.Request().Context(), 50)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.Payload = res
|
||||||
|
return c.JSON(http.StatusOK, 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 *Handler) ListSubscriptionDetails(c echo.Context) error {
|
||||||
|
payload := ListSubscriptionDetails{
|
||||||
|
ApiStatusModel: ApiStatusModel{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Message: "OK",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := s.dto.ListSubscriptionDetails(c.Request().Context(), 50)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.Payload = res
|
||||||
|
return c.JSON(http.StatusOK, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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/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 *Handler) GetSubscriptionsByDiscordId(c echo.Context) error {
|
||||||
|
p := ListSubscriptions{
|
||||||
|
ApiStatusModel: ApiStatusModel{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Message: "OK",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
id := c.QueryParam("id")
|
||||||
|
if id == "" {
|
||||||
|
return s.WriteError(c, errors.New(ErrIdValueMissing), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid, err := uuid.Parse(id)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, errors.New(ErrValueNotUuid), http.StatusBadRequest)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := s.dto.ListSubscriptionsByDiscordWebhookId(context.Background(), uuid)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Payload = res
|
||||||
|
return c.JSON(http.StatusOK, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubscriptionsBySourceId
|
||||||
|
// @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/by/SourceId [get]
|
||||||
|
// @Success 200 {object} ListSubscriptions "ok"
|
||||||
|
func (s *Handler) GetSubscriptionsBySourceId(c echo.Context) error {
|
||||||
|
p := ListSubscriptions{
|
||||||
|
ApiStatusModel: ApiStatusModel{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Message: "OK",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_id := c.QueryParam("id")
|
||||||
|
if _id == "" {
|
||||||
|
return s.WriteError(c, errors.New(ErrIdValueMissing), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid, err := uuid.Parse(_id)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := s.dto.ListSubscriptionsBySourceId(context.Background(), uuid)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Payload = res
|
||||||
|
return c.JSON(http.StatusOK, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDiscordWebHookSubscription
|
||||||
|
// @Summary Creates a new subscription to link a post from a Source to a DiscordWebHook.
|
||||||
|
// @Param discordWebHookId query string true "discordWebHookId"
|
||||||
|
// @Param sourceId query string true "sourceId"
|
||||||
|
// @Tags Subscription
|
||||||
|
// @Router /subscriptions/discord/webhook/new [post]
|
||||||
|
func (s *Handler) newDiscordWebHookSubscription(c echo.Context) error {
|
||||||
|
// Extract the values given
|
||||||
|
discordWebHookId := c.QueryParam("discordWebHookId")
|
||||||
|
sourceId := c.QueryParam("sourceId")
|
||||||
|
|
||||||
|
// Check to make we didn't get a null
|
||||||
|
if discordWebHookId == "" {
|
||||||
|
return s.WriteError(c, errors.New("invalid discordWebHooksId given"), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
if sourceId == "" {
|
||||||
|
return s.WriteError(c, errors.New("invalid sourceID given"), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate they are UUID values
|
||||||
|
uHook, err := uuid.Parse(discordWebHookId)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
uSource, err := uuid.Parse(sourceId)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the sub already exists
|
||||||
|
_, err = s.Db.QuerySubscriptions(c.Request().Context(), database.QuerySubscriptionsParams{
|
||||||
|
Discordwebhookid: uHook,
|
||||||
|
Sourceid: uSource,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
return s.WriteError(c, errors.New("a subscription already exists between these two entities"), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does not exist, so make it.
|
||||||
|
params := database.CreateSubscriptionParams{
|
||||||
|
ID: uuid.New(),
|
||||||
|
Discordwebhookid: uHook,
|
||||||
|
Sourceid: uSource,
|
||||||
|
}
|
||||||
|
err = s.Db.CreateSubscription(context.Background(), params)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
bJson, err := json.Marshal(¶ms)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, bJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDiscordWebHookSubscription
|
||||||
|
// @Summary Removes a Discord WebHook Subscription based on the Subscription ID.
|
||||||
|
// @Param id query string true "id"
|
||||||
|
// @Tags Subscription
|
||||||
|
// @Router /subscriptions/discord/webhook/delete [delete]
|
||||||
|
func (s *Handler) DeleteDiscordWebHookSubscription(c echo.Context) error {
|
||||||
|
var ErrMissingSubscriptionID string = "the request was missing a 'Id'"
|
||||||
|
|
||||||
|
id := c.QueryParam("id")
|
||||||
|
if id == "" {
|
||||||
|
return s.WriteError(c, errors.New(ErrMissingSubscriptionID), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := uuid.Parse(id)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.Db.DeleteSubscription(context.Background(), uid)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, nil)
|
||||||
|
}
|
||||||
|
*/
|
@ -1,351 +0,0 @@
|
|||||||
package v1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ErrUserNotFound = "requested user does not exist"
|
|
||||||
ErrUsernameAlreadyExists = "the requested username already exists"
|
|
||||||
)
|
|
||||||
|
|
||||||
// @Summary Creates a new user
|
|
||||||
// @Router /v1/users/register [post]
|
|
||||||
// @Param request formData domain.LoginFormRequest true "form"
|
|
||||||
// @Accepts x-www-form-urlencoded
|
|
||||||
// @Produce json
|
|
||||||
// @Tags Users
|
|
||||||
// @Success 201 {object} domain.BaseResponse
|
|
||||||
// @Failure 400 {object} domain.BaseResponse
|
|
||||||
// @Failure 500 {object} domain.BaseResponse
|
|
||||||
func (h *Handler) AuthRegister(c echo.Context) error {
|
|
||||||
p := domain.BaseResponse{
|
|
||||||
Message: ResponseMessageSuccess,
|
|
||||||
IsError: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
username := c.FormValue("username")
|
|
||||||
password := c.FormValue("password")
|
|
||||||
|
|
||||||
//username := c.QueryParam("username")
|
|
||||||
exists, err := h.repo.Users.GetUser(c.Request().Context(), username)
|
|
||||||
if err != nil {
|
|
||||||
// if we have an err, validate that if its not user not found.
|
|
||||||
// if the user is not found, we can use that name
|
|
||||||
if err.Error() != repository.ErrUserNotFound {
|
|
||||||
p.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if exists.Username == username {
|
|
||||||
p.Message = ErrUsernameAlreadyExists
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
//password := c.QueryParam("password")
|
|
||||||
err = h.repo.Users.CheckPasswordForRequirements(password)
|
|
||||||
if err != nil {
|
|
||||||
p.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.repo.Users.Create(c.Request().Context(), username, password, domain.ScopeArticleRead)
|
|
||||||
if err != nil {
|
|
||||||
p.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(http.StatusCreated, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Summary Logs into the API and returns a bearer token if successful
|
|
||||||
// @Router /v1/users/login [post]
|
|
||||||
// @Param request formData domain.LoginFormRequest true "form"
|
|
||||||
// @Accepts x-www-form-urlencoded
|
|
||||||
// @Produce json
|
|
||||||
// @Tags Users
|
|
||||||
// @Success 200 {object} domain.LoginResponse
|
|
||||||
// @Failure 400 {object} domain.LoginResponse
|
|
||||||
// @Failure 500 {object} domain.LoginResponse
|
|
||||||
func (h *Handler) AuthLogin(c echo.Context) error {
|
|
||||||
p := domain.LoginResponse{
|
|
||||||
BaseResponse: domain.BaseResponse{
|
|
||||||
Message: ResponseMessageSuccess,
|
|
||||||
IsError: true,
|
|
||||||
},
|
|
||||||
//Token: jwt,
|
|
||||||
Type: "Bearer",
|
|
||||||
//RefreshToken: refresh,
|
|
||||||
}
|
|
||||||
|
|
||||||
username := c.FormValue("username")
|
|
||||||
password := c.FormValue("password")
|
|
||||||
|
|
||||||
// Check to see if they are trying to login with the admin token
|
|
||||||
if username == "" {
|
|
||||||
return h.createAdminToken(c, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if the user exists
|
|
||||||
user, err := h.repo.Users.GetUser(c.Request().Context(), username)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the hash matches
|
|
||||||
err = h.repo.Users.DoesPasswordMatchHash(c.Request().Context(), username, password)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO think about moving this down some?
|
|
||||||
expiresAt := time.Now().Add(time.Hour * 48)
|
|
||||||
userScopes := strings.Split(user.Scopes, ",")
|
|
||||||
|
|
||||||
jwt, err := h.generateJwtWithExp(username, h.config.ServerAddress, user.SessionToken, userScopes, user.ID, expiresAt)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh, err := h.repo.RefreshTokens.Create(c.Request().Context(), username)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Token = jwt
|
|
||||||
p.RefreshToken = refresh
|
|
||||||
p.BaseResponse.IsError = false
|
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) createAdminToken(c echo.Context, password string) error {
|
|
||||||
p := domain.LoginResponse{
|
|
||||||
BaseResponse: domain.BaseResponse{
|
|
||||||
Message: ResponseMessageSuccess,
|
|
||||||
IsError: true,
|
|
||||||
},
|
|
||||||
//Token: token,
|
|
||||||
Type: "Bearer",
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the admin token is blank, then the admin wanted this disabled.
|
|
||||||
// this will fail right away and not progress.
|
|
||||||
if h.config.AdminSecret == "" {
|
|
||||||
p.BaseResponse.Message = ErrUserNotFound
|
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
if h.config.AdminSecret != password {
|
|
||||||
p.BaseResponse.Message = ErrUserNotFound
|
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
|
||||||
var userScopes []string
|
|
||||||
userScopes = append(userScopes, domain.ScopeAll)
|
|
||||||
sessionToken, err := uuid.NewV7()
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := h.generateJwt("admin", h.config.ServerAddress, sessionToken.String(), userScopes, -1)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Token = token
|
|
||||||
p.BaseResponse.IsError = false
|
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will take collect some information about the requested refresh, validate and then return a new jwt token if approved.
|
|
||||||
// Register
|
|
||||||
// @Summary Generates a new token
|
|
||||||
// @Router /v1/users/refresh/token [post]
|
|
||||||
// @Param request body domain.RefreshTokenRequest true "body"
|
|
||||||
// @Tags Users
|
|
||||||
// @Success 200 {object} domain.LoginResponse
|
|
||||||
// @Failure 400 {object} domain.BaseResponse
|
|
||||||
// @Failure 500 {object} domain.BaseResponse
|
|
||||||
// @Security Bearer
|
|
||||||
func (h *Handler) RefreshJwtToken(c echo.Context) error {
|
|
||||||
p := domain.LoginResponse{
|
|
||||||
BaseResponse: domain.BaseResponse{
|
|
||||||
Message: "OK",
|
|
||||||
},
|
|
||||||
//Token: jwt,
|
|
||||||
Type: "Bearer",
|
|
||||||
//RefreshToken: newRefreshToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := h.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the context for the refresh token
|
|
||||||
var request domain.RefreshTokenRequest
|
|
||||||
err = (&echo.DefaultBinder{}).BindBody(c, &request)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = h.repo.RefreshTokens.IsRequestValid(c.Request().Context(), request.Username, request.RefreshToken)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := h.repo.Users.GetUser(c.Request().Context(), request.Username)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
userScopes := strings.Split(user.Scopes, ",")
|
|
||||||
|
|
||||||
jwt, err := h.generateJwtWithExp(request.Username, h.config.ServerAddress, user.SessionToken, userScopes, user.ID, time.Now().Add(time.Hour*48))
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
newRefreshToken, err := h.repo.RefreshTokens.Create(c.Request().Context(), request.Username)
|
|
||||||
if err != nil {
|
|
||||||
p.BaseResponse.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Token = jwt
|
|
||||||
p.RefreshToken = newRefreshToken
|
|
||||||
p.BaseResponse.IsError = false
|
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Summary Adds a new scope to a user account
|
|
||||||
// @Router /v1/users/scopes/add [post]
|
|
||||||
// @Param request body domain.UpdateScopesRequest true "body"
|
|
||||||
// @Tags Users
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {object} domain.BaseResponse
|
|
||||||
// @Failure 400 {object} domain.BaseResponse
|
|
||||||
// @Failure 500 {object} domain.BaseResponse
|
|
||||||
// @Security Bearer
|
|
||||||
func (h *Handler) AddScopes(c echo.Context) error {
|
|
||||||
p := domain.BaseResponse{
|
|
||||||
Message: ResponseMessageSuccess,
|
|
||||||
IsError: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := h.ValidateJwtToken(c, domain.ScopeAll)
|
|
||||||
if err != nil {
|
|
||||||
p.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
request := domain.UpdateScopesRequest{}
|
|
||||||
err = (&echo.DefaultBinder{}).BindBody(c, &request)
|
|
||||||
if err != nil {
|
|
||||||
p.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = h.repo.Users.AddScopes(c.Request().Context(), request.Username, request.Scopes)
|
|
||||||
if err != nil {
|
|
||||||
p.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.IsError = false
|
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Summary Adds a new scope to a user account
|
|
||||||
// @Router /v1/users/scopes/remove [post]
|
|
||||||
// @Param request body domain.UpdateScopesRequest true "body"
|
|
||||||
// @Tags Users
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {object} domain.BaseResponse
|
|
||||||
// @Failure 400 {object} domain.BaseResponse
|
|
||||||
// @Failure 500 {object} domain.BaseResponse
|
|
||||||
// @Security Bearer
|
|
||||||
func (h *Handler) RemoveScopes(c echo.Context) error {
|
|
||||||
p := domain.BaseResponse{
|
|
||||||
Message: ResponseMessageSuccess,
|
|
||||||
IsError: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := h.getJwtTokenFromContext(c)
|
|
||||||
if err != nil {
|
|
||||||
p.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = token.IsValid(domain.ScopeAll)
|
|
||||||
if err != nil {
|
|
||||||
p.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
request := domain.UpdateScopesRequest{}
|
|
||||||
err = (&echo.DefaultBinder{}).BindBody(c, &request)
|
|
||||||
if err != nil {
|
|
||||||
p.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusBadRequest, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = h.repo.Users.RemoveScopes(c.Request().Context(), request.Username, request.Scopes)
|
|
||||||
if err != nil {
|
|
||||||
p.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.IsError = false
|
|
||||||
return c.JSON(http.StatusOK, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Summary Revokes the current session token and replaces it with a new one.
|
|
||||||
// @Router /v1/users/refresh/sessionToken [post]
|
|
||||||
// @Tags Users
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {object} domain.BaseResponse
|
|
||||||
// @Failure 400 {object} domain.BaseResponse
|
|
||||||
// @Failure 500 {object} domain.BaseResponse
|
|
||||||
// @Security Bearer
|
|
||||||
func (h *Handler) NewSessionToken(c echo.Context) error {
|
|
||||||
p := domain.BaseResponse{
|
|
||||||
Message: ResponseMessageSuccess,
|
|
||||||
IsError: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := h.getJwtTokenFromContext(c)
|
|
||||||
if err != nil {
|
|
||||||
p.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusUnauthorized, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.repo.Users.NewSessionToken(c.Request().Context(), token.UserName)
|
|
||||||
if err != nil {
|
|
||||||
p.Message = err.Error()
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.IsError = false
|
|
||||||
return c.JSON(http.StatusInternalServerError, p)
|
|
||||||
}
|
|
@ -1,122 +0,0 @@
|
|||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
|
||||||
"github.com/huandu/go-sqlbuilder"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AlertDiscordRepo interface {
|
|
||||||
Create(ctx context.Context, userId, sourceId, webhookId int64) (int64, error)
|
|
||||||
SoftDelete(ctx context.Context, id int64) (int64, error)
|
|
||||||
Restore(ctx context.Context, id int64) (int64, error)
|
|
||||||
Delete(ctx context.Context, id int64) (int64, error)
|
|
||||||
ListByUser(ctx context.Context, page, limit int, userId int64) ([]entity.AlertDiscordEntity, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type alertDiscordRepository struct {
|
|
||||||
conn *sql.DB
|
|
||||||
defaultLimit int
|
|
||||||
defaultOffset int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAlertDiscordRepository(conn *sql.DB) alertDiscordRepository {
|
|
||||||
return alertDiscordRepository{
|
|
||||||
conn: conn,
|
|
||||||
defaultLimit: 50,
|
|
||||||
defaultOffset: 50,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r alertDiscordRepository) Create(ctx context.Context, userId, sourceId, webhookId int64) (int64, error) {
|
|
||||||
dt := time.Now()
|
|
||||||
queryBuilder := sqlbuilder.NewInsertBuilder()
|
|
||||||
queryBuilder.InsertInto("AlertDiscord")
|
|
||||||
queryBuilder.Cols("UpdatedAt", "CreatedAt", "DeletedAt", "UserID", "SourceID", "DiscordWebHookID")
|
|
||||||
queryBuilder.Values(dt, dt, timeZero, userId, sourceId, webhookId)
|
|
||||||
query, args := queryBuilder.Build()
|
|
||||||
|
|
||||||
_, err := r.conn.ExecContext(ctx, query, args...)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r alertDiscordRepository) SoftDelete(ctx context.Context, id int64) (int64, error) {
|
|
||||||
return softDeleteRow(ctx, r.conn, "AlertDiscord", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r alertDiscordRepository) Restore(ctx context.Context, id int64) (int64, error) {
|
|
||||||
return restoreRow(ctx, r.conn, "AlertDiscord", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r alertDiscordRepository) Delete(ctx context.Context, id int64) (int64, error) {
|
|
||||||
return deleteFromTable(ctx, r.conn, "AlertDiscord", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r alertDiscordRepository) ListByUser(ctx context.Context, page, limit int, userId int64) ([]entity.AlertDiscordEntity, error) {
|
|
||||||
builder := sqlbuilder.NewSelectBuilder()
|
|
||||||
builder.Select("*")
|
|
||||||
builder.From("AlertDiscord")
|
|
||||||
builder.Where(
|
|
||||||
builder.Equal("UserID", userId),
|
|
||||||
)
|
|
||||||
builder.Offset(page * limit)
|
|
||||||
builder.Limit(limit)
|
|
||||||
|
|
||||||
query, args := builder.Build()
|
|
||||||
rows, err := r.conn.QueryContext(ctx, query, args...)
|
|
||||||
if err != nil {
|
|
||||||
return []entity.AlertDiscordEntity{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
data := r.processRows(rows)
|
|
||||||
if len(data) == 0 {
|
|
||||||
return []entity.AlertDiscordEntity{}, errors.New(ErrUserNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ur alertDiscordRepository) processRows(rows *sql.Rows) []entity.AlertDiscordEntity {
|
|
||||||
items := []entity.AlertDiscordEntity{}
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var id int64
|
|
||||||
var createdAt time.Time
|
|
||||||
var updatedAt time.Time
|
|
||||||
var deletedAt time.Time
|
|
||||||
var userId int64
|
|
||||||
var sourceId int64
|
|
||||||
var webhookId int64
|
|
||||||
|
|
||||||
err := rows.Scan(
|
|
||||||
&id, &createdAt, &updatedAt, &deletedAt,
|
|
||||||
&userId, &sourceId, &webhookId,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
item := entity.AlertDiscordEntity{
|
|
||||||
ID: id,
|
|
||||||
CreatedAt: createdAt,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
DeletedAt: deletedAt,
|
|
||||||
UserID: userId,
|
|
||||||
SourceID: sourceId,
|
|
||||||
DiscordWebHookId: webhookId,
|
|
||||||
}
|
|
||||||
|
|
||||||
items = append(items, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
return items
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
package repository_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAlertDiscordCreate(t *testing.T) {
|
|
||||||
t.Log(time.Time{})
|
|
||||||
db, err := setupInMemoryDb()
|
|
||||||
if err != nil {
|
|
||||||
t.Log(err)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
r := repository.NewAlertDiscordRepository(db)
|
|
||||||
created, err := r.Create(context.Background(), 1, 1, 1)
|
|
||||||
if err != nil {
|
|
||||||
t.Log(err)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
if created != 1 {
|
|
||||||
t.Log("failed to create the record")
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAlertDiscordCreateAndValidate(t *testing.T) {
|
|
||||||
t.Log(time.Time{})
|
|
||||||
db, err := setupInMemoryDb()
|
|
||||||
if err != nil {
|
|
||||||
t.Log(err)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
source := repository.NewSourceRepository(db)
|
|
||||||
source.Create(context.Background(), domain.SourceCollectorRss, "Unit Testing", "www.fake.com", "testing,units", true)
|
|
||||||
sourceRecord, _ := source.GetBySourceAndName(context.Background(), domain.SourceCollectorRss, "Unit Testing")
|
|
||||||
|
|
||||||
webhookRepo := repository.NewDiscordWebHookRepository(db)
|
|
||||||
webhookRepo.Create(context.Background(), 999, "discord.com", "Unit Testing", "memes", true)
|
|
||||||
webhook, _ := webhookRepo.GetByUrl(context.Background(), "discord.com")
|
|
||||||
|
|
||||||
r := repository.NewAlertDiscordRepository(db)
|
|
||||||
r.Create(context.Background(), 999, sourceRecord.ID, webhook.ID)
|
|
||||||
alert, err := r.ListByUser(context.Background(), 0, 10, 999)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(alert) != 1 {
|
|
||||||
t.Error("got the incorrect number of rows back")
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,7 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"github.com/huandu/go-sqlbuilder"
|
"github.com/huandu/go-sqlbuilder"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,14 +17,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ArticlesRepo interface {
|
type ArticlesRepo interface {
|
||||||
GetById(ctx context.Context, id int64) (entity.ArticleEntity, error)
|
GetById(ctx context.Context, id int64) (domain.ArticleEntity, error)
|
||||||
GetByUrl(ctx context.Context, url string) (entity.ArticleEntity, error)
|
GetByUrl(ctx context.Context, url string) (domain.ArticleEntity, error)
|
||||||
ListTop(ctx context.Context, limit int) ([]entity.ArticleEntity, error)
|
ListTop(ctx context.Context, limit int) ([]domain.ArticleEntity, error)
|
||||||
ListByPage(ctx context.Context, page, limit int) ([]entity.ArticleEntity, error)
|
ListByPage(ctx context.Context, page, limit int) ([]domain.ArticleEntity, error)
|
||||||
ListByPublishDate(ctx context.Context, page, limit int, orderBy string) ([]entity.ArticleEntity, error)
|
ListByPublishDate(ctx context.Context, page, limit int, orderBy string) ([]domain.ArticleEntity, error)
|
||||||
ListBySource(ctx context.Context, page, limit, sourceId int, orderBy string) ([]entity.ArticleEntity, error)
|
ListBySource(ctx context.Context, page, limit, sourceId int, orderBy string) ([]domain.ArticleEntity, error)
|
||||||
Create(ctx context.Context, sourceId int64, tags, title, url, thumbnailUrl, description, authorName, authorImageUrl string, pubDate time.Time, isVideo bool) (int64, error)
|
Create(ctx context.Context, sourceId int64, tags, title, url, thumbnailUrl, description, authorName, authorImageUrl string, pubDate time.Time, isVideo bool) (int64, error)
|
||||||
CreateFromEntity(ctx context.Context, entity entity.ArticleEntity) (int64, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ArticleRepository struct {
|
type ArticleRepository struct {
|
||||||
@ -41,7 +40,7 @@ func NewArticleRepository(conn *sql.DB) ArticleRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ar ArticleRepository) GetById(ctx context.Context, id int64) (entity.ArticleEntity, error) {
|
func (ar ArticleRepository) GetById(ctx context.Context, id int64) (domain.ArticleEntity, error) {
|
||||||
builder := sqlbuilder.NewSelectBuilder()
|
builder := sqlbuilder.NewSelectBuilder()
|
||||||
builder.Select("*")
|
builder.Select("*")
|
||||||
builder.From("articles").Where(
|
builder.From("articles").Where(
|
||||||
@ -52,18 +51,18 @@ func (ar ArticleRepository) GetById(ctx context.Context, id int64) (entity.Artic
|
|||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
rows, err := ar.conn.QueryContext(ctx, query, args...)
|
rows, err := ar.conn.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entity.ArticleEntity{}, err
|
return domain.ArticleEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data := ar.processRows(rows)
|
data := ar.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return entity.ArticleEntity{}, errors.New(ErrUserNotFound)
|
return domain.ArticleEntity{}, errors.New(ErrUserNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
return data[0], nil
|
return data[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ar ArticleRepository) GetByUrl(ctx context.Context, url string) (entity.ArticleEntity, error) {
|
func (ar ArticleRepository) GetByUrl(ctx context.Context, url string) (domain.ArticleEntity, error) {
|
||||||
builder := sqlbuilder.NewSelectBuilder()
|
builder := sqlbuilder.NewSelectBuilder()
|
||||||
builder.Select("*")
|
builder.Select("*")
|
||||||
builder.From("articles").Where(
|
builder.From("articles").Where(
|
||||||
@ -74,18 +73,18 @@ func (ar ArticleRepository) GetByUrl(ctx context.Context, url string) (entity.Ar
|
|||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
rows, err := ar.conn.QueryContext(ctx, query, args...)
|
rows, err := ar.conn.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entity.ArticleEntity{}, err
|
return domain.ArticleEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data := ar.processRows(rows)
|
data := ar.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return entity.ArticleEntity{}, errors.New(ErrUserNotFound)
|
return domain.ArticleEntity{}, errors.New(ErrUserNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
return data[0], nil
|
return data[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ar ArticleRepository) ListTop(ctx context.Context, limit int) ([]entity.ArticleEntity, error) {
|
func (ar ArticleRepository) ListTop(ctx context.Context, limit int) ([]domain.ArticleEntity, error) {
|
||||||
builder := sqlbuilder.NewSelectBuilder()
|
builder := sqlbuilder.NewSelectBuilder()
|
||||||
builder.Select("*")
|
builder.Select("*")
|
||||||
builder.From("articles")
|
builder.From("articles")
|
||||||
@ -94,18 +93,18 @@ func (ar ArticleRepository) ListTop(ctx context.Context, limit int) ([]entity.Ar
|
|||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
rows, err := ar.conn.QueryContext(ctx, query, args...)
|
rows, err := ar.conn.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []entity.ArticleEntity{}, err
|
return []domain.ArticleEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data := ar.processRows(rows)
|
data := ar.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return []entity.ArticleEntity{}, errors.New(ErrUserNotFound)
|
return []domain.ArticleEntity{}, errors.New(ErrUserNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ar ArticleRepository) ListByPage(ctx context.Context, page, limit int) ([]entity.ArticleEntity, error) {
|
func (ar ArticleRepository) ListByPage(ctx context.Context, page, limit int) ([]domain.ArticleEntity, error) {
|
||||||
builder := sqlbuilder.NewSelectBuilder()
|
builder := sqlbuilder.NewSelectBuilder()
|
||||||
builder.Select("*")
|
builder.Select("*")
|
||||||
builder.From("articles")
|
builder.From("articles")
|
||||||
@ -116,18 +115,18 @@ func (ar ArticleRepository) ListByPage(ctx context.Context, page, limit int) ([]
|
|||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
rows, err := ar.conn.QueryContext(ctx, query, args...)
|
rows, err := ar.conn.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []entity.ArticleEntity{}, err
|
return []domain.ArticleEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data := ar.processRows(rows)
|
data := ar.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return []entity.ArticleEntity{}, errors.New(ErrUserNotFound)
|
return []domain.ArticleEntity{}, errors.New(ErrUserNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ar ArticleRepository) ListByPublishDate(ctx context.Context, page, limit int, orderBy string) ([]entity.ArticleEntity, error) {
|
func (ar ArticleRepository) ListByPublishDate(ctx context.Context, page, limit int, orderBy string) ([]domain.ArticleEntity, error) {
|
||||||
builder := sqlbuilder.NewSelectBuilder()
|
builder := sqlbuilder.NewSelectBuilder()
|
||||||
builder.Select("*")
|
builder.Select("*")
|
||||||
builder.From("articles")
|
builder.From("articles")
|
||||||
@ -140,17 +139,17 @@ func (ar ArticleRepository) ListByPublishDate(ctx context.Context, page, limit i
|
|||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
rows, err := ar.conn.QueryContext(ctx, query, args...)
|
rows, err := ar.conn.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []entity.ArticleEntity{}, err
|
return []domain.ArticleEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data := ar.processRows(rows)
|
data := ar.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return []entity.ArticleEntity{}, errors.New(ErrUserNotFound)
|
return []domain.ArticleEntity{}, errors.New(ErrUserNotFound)
|
||||||
}
|
}
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ar ArticleRepository) ListBySource(ctx context.Context, page, limit, sourceId int, orderBy string) ([]entity.ArticleEntity, error) {
|
func (ar ArticleRepository) ListBySource(ctx context.Context, page, limit, sourceId int, orderBy string) ([]domain.ArticleEntity, error) {
|
||||||
builder := sqlbuilder.NewSelectBuilder()
|
builder := sqlbuilder.NewSelectBuilder()
|
||||||
builder.Select("*")
|
builder.Select("*")
|
||||||
builder.From("articles")
|
builder.From("articles")
|
||||||
@ -167,12 +166,12 @@ func (ar ArticleRepository) ListBySource(ctx context.Context, page, limit, sourc
|
|||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
rows, err := ar.conn.QueryContext(ctx, query, args...)
|
rows, err := ar.conn.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []entity.ArticleEntity{}, err
|
return []domain.ArticleEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data := ar.processRows(rows)
|
data := ar.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return []entity.ArticleEntity{}, errors.New(ErrUserNotFound)
|
return []domain.ArticleEntity{}, errors.New(ErrUserNotFound)
|
||||||
}
|
}
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
@ -193,24 +192,8 @@ func (ar ArticleRepository) Create(ctx context.Context, sourceId int64, tags, ti
|
|||||||
return 1, nil
|
return 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ar ArticleRepository) CreateFromEntity(ctx context.Context, entity entity.ArticleEntity) (int64, error) {
|
func (ur ArticleRepository) processRows(rows *sql.Rows) []domain.ArticleEntity {
|
||||||
dt := time.Now()
|
items := []domain.ArticleEntity{}
|
||||||
queryBuilder := sqlbuilder.NewInsertBuilder()
|
|
||||||
queryBuilder.InsertInto("articles")
|
|
||||||
queryBuilder.Cols("UpdatedAt", "CreatedAt", "DeletedAt", "SourceId", "Tags", "Title", "Url", "PubDate", "IsVideo", "ThumbnailUrl", "Description", "AuthorName", "AuthorImageUrl")
|
|
||||||
queryBuilder.Values(dt, dt, timeZero, entity.SourceID, entity.Tags, entity.Title, entity.Url, entity.PubDate, entity.IsVideo, entity.Thumbnail, entity.Description, entity.AuthorName, entity.AuthorImageUrl)
|
|
||||||
query, args := queryBuilder.Build()
|
|
||||||
|
|
||||||
_, err := ar.conn.ExecContext(ctx, query, args...)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ur ArticleRepository) processRows(rows *sql.Rows) []entity.ArticleEntity {
|
|
||||||
items := []entity.ArticleEntity{}
|
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var id int64
|
var id int64
|
||||||
@ -237,7 +220,7 @@ func (ur ArticleRepository) processRows(rows *sql.Rows) []entity.ArticleEntity {
|
|||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
item := entity.ArticleEntity{
|
item := domain.ArticleEntity{
|
||||||
ID: id,
|
ID: id,
|
||||||
CreatedAt: createdAt,
|
CreatedAt: createdAt,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
|
@ -5,21 +5,21 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"github.com/huandu/go-sqlbuilder"
|
"github.com/huandu/go-sqlbuilder"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DiscordWebHookRepo interface {
|
type DiscordWebHookRepo interface{
|
||||||
Create(ctx context.Context, userId int64, url, server, channel string, enabled bool) (int64, error)
|
Create(ctx context.Context, url, server, channel string, enabled bool) (int64, error)
|
||||||
Enable(ctx context.Context, id int64) (int64, error)
|
Enable(ctx context.Context, id int64) (int64, error)
|
||||||
Disable(ctx context.Context, id int64) (int64, error)
|
Disable(ctx context.Context, id int64) (int64, error)
|
||||||
SoftDelete(ctx context.Context, id int64) (int64, error)
|
SoftDelete(ctx context.Context, id int64) (int64, error)
|
||||||
Restore(ctx context.Context, id int64) (int64, error)
|
Restore(ctx context.Context, id int64) (int64, error)
|
||||||
Delete(ctx context.Context, id int64) (int64, error)
|
Delete(ctx context.Context, id int64) (int64, error)
|
||||||
GetById(ctx context.Context, id int64) (entity.DiscordWebHookEntity, error)
|
GetById(ctx context.Context, id int64) (domain.DiscordWebHookEntity, error)
|
||||||
GetByUrl(ctx context.Context, url string) (entity.DiscordWebHookEntity, error)
|
GetByUrl(ctx context.Context, url string) (domain.DiscordWebHookEntity, error)
|
||||||
ListByServerName(ctx context.Context, name string) ([]entity.DiscordWebHookEntity, error)
|
ListByServerName(ctx context.Context, name string) ([]domain.DiscordWebHookEntity, error)
|
||||||
ListByServerAndChannel(ctx context.Context, server, channel string) ([]entity.DiscordWebHookEntity, error)
|
ListByServerAndChannel(ctx context.Context, server, channel string) ([]domain.DiscordWebHookEntity, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type discordWebHookRepository struct {
|
type discordWebHookRepository struct {
|
||||||
@ -32,12 +32,12 @@ func NewDiscordWebHookRepository(conn *sql.DB) discordWebHookRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r discordWebHookRepository) Create(ctx context.Context, userId int64, url, server, channel string, enabled bool) (int64, error) {
|
func (r discordWebHookRepository) Create(ctx context.Context, url, server, channel string, enabled bool) (int64, error) {
|
||||||
dt := time.Now()
|
dt := time.Now()
|
||||||
queryBuilder := sqlbuilder.NewInsertBuilder()
|
queryBuilder := sqlbuilder.NewInsertBuilder()
|
||||||
queryBuilder.InsertInto("DiscordWebHooks")
|
queryBuilder.InsertInto("DiscordWebHooks")
|
||||||
queryBuilder.Cols("UpdatedAt", "CreatedAt", "DeletedAt", "UserID", "Url", "Server", "Channel", "Enabled")
|
queryBuilder.Cols("UpdatedAt", "CreatedAt", "DeletedAt", "Url", "Server", "Channel", "Enabled")
|
||||||
queryBuilder.Values(dt, dt, timeZero, userId, url, server, channel, enabled)
|
queryBuilder.Values(dt, dt, timeZero, url, server, channel, enabled)
|
||||||
query, args := queryBuilder.Build()
|
query, args := queryBuilder.Build()
|
||||||
|
|
||||||
_, err := r.conn.ExecContext(ctx, query, args...)
|
_, err := r.conn.ExecContext(ctx, query, args...)
|
||||||
@ -100,7 +100,7 @@ func (r discordWebHookRepository) Delete(ctx context.Context, id int64) (int64,
|
|||||||
return deleteFromTable(ctx, r.conn, "DiscordWebHooks", id)
|
return deleteFromTable(ctx, r.conn, "DiscordWebHooks", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r discordWebHookRepository) GetById(ctx context.Context, id int64) (entity.DiscordWebHookEntity, error) {
|
func (r discordWebHookRepository) GetById(ctx context.Context, id int64) (domain.DiscordWebHookEntity, error) {
|
||||||
builder := sqlbuilder.NewSelectBuilder()
|
builder := sqlbuilder.NewSelectBuilder()
|
||||||
builder.Select("*")
|
builder.Select("*")
|
||||||
builder.From("DiscordWebHooks").Where(
|
builder.From("DiscordWebHooks").Where(
|
||||||
@ -111,18 +111,18 @@ func (r discordWebHookRepository) GetById(ctx context.Context, id int64) (entity
|
|||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
rows, err := r.conn.QueryContext(ctx, query, args...)
|
rows, err := r.conn.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entity.DiscordWebHookEntity{}, err
|
return domain.DiscordWebHookEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := r.processRows(rows)
|
data, err := r.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return entity.DiscordWebHookEntity{}, err
|
return domain.DiscordWebHookEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return data[0], nil
|
return data[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r discordWebHookRepository) GetByUrl(ctx context.Context, url string) (entity.DiscordWebHookEntity, error) {
|
func (r discordWebHookRepository) GetByUrl(ctx context.Context, url string) (domain.DiscordWebHookEntity, error) {
|
||||||
builder := sqlbuilder.NewSelectBuilder()
|
builder := sqlbuilder.NewSelectBuilder()
|
||||||
builder.Select("*")
|
builder.Select("*")
|
||||||
builder.From("DiscordWebHooks").Where(
|
builder.From("DiscordWebHooks").Where(
|
||||||
@ -133,18 +133,18 @@ func (r discordWebHookRepository) GetByUrl(ctx context.Context, url string) (ent
|
|||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
rows, err := r.conn.QueryContext(ctx, query, args...)
|
rows, err := r.conn.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entity.DiscordWebHookEntity{}, err
|
return domain.DiscordWebHookEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := r.processRows(rows)
|
data, err := r.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return entity.DiscordWebHookEntity{}, err
|
return domain.DiscordWebHookEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return data[0], nil
|
return data[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r discordWebHookRepository) ListByServerName(ctx context.Context, name string) ([]entity.DiscordWebHookEntity, error) {
|
func (r discordWebHookRepository) ListByServerName(ctx context.Context, name string) ([]domain.DiscordWebHookEntity, error) {
|
||||||
builder := sqlbuilder.NewSelectBuilder()
|
builder := sqlbuilder.NewSelectBuilder()
|
||||||
builder.Select("*")
|
builder.Select("*")
|
||||||
builder.From("DiscordWebHooks").Where(
|
builder.From("DiscordWebHooks").Where(
|
||||||
@ -154,18 +154,18 @@ func (r discordWebHookRepository) ListByServerName(ctx context.Context, name str
|
|||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
rows, err := r.conn.QueryContext(ctx, query, args...)
|
rows, err := r.conn.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []entity.DiscordWebHookEntity{}, err
|
return []domain.DiscordWebHookEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := r.processRows(rows)
|
data, err := r.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return []entity.DiscordWebHookEntity{}, err
|
return []domain.DiscordWebHookEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r discordWebHookRepository) ListByServerAndChannel(ctx context.Context, server, channel string) ([]entity.DiscordWebHookEntity, error) {
|
func (r discordWebHookRepository) ListByServerAndChannel(ctx context.Context, server, channel string) ([]domain.DiscordWebHookEntity, error) {
|
||||||
builder := sqlbuilder.NewSelectBuilder()
|
builder := sqlbuilder.NewSelectBuilder()
|
||||||
builder.Select("*")
|
builder.Select("*")
|
||||||
builder.From("DiscordWebHooks").Where(
|
builder.From("DiscordWebHooks").Where(
|
||||||
@ -176,45 +176,43 @@ func (r discordWebHookRepository) ListByServerAndChannel(ctx context.Context, se
|
|||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
rows, err := r.conn.QueryContext(ctx, query, args...)
|
rows, err := r.conn.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []entity.DiscordWebHookEntity{}, err
|
return []domain.DiscordWebHookEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := r.processRows(rows)
|
data, err := r.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return []entity.DiscordWebHookEntity{}, err
|
return []domain.DiscordWebHookEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r discordWebHookRepository) processRows(rows *sql.Rows) ([]entity.DiscordWebHookEntity, error) {
|
func (r discordWebHookRepository) processRows(rows *sql.Rows) ([]domain.DiscordWebHookEntity, error) {
|
||||||
items := []entity.DiscordWebHookEntity{}
|
items := []domain.DiscordWebHookEntity{}
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var id int64
|
var id int64
|
||||||
var createdAt time.Time
|
var createdAt time.Time
|
||||||
var updatedAt time.Time
|
var updatedAt time.Time
|
||||||
var deletedAt time.Time
|
var deletedAt time.Time
|
||||||
var userId int64
|
|
||||||
var url string
|
var url string
|
||||||
var server string
|
var server string
|
||||||
var channel string
|
var channel string
|
||||||
var enabled bool
|
var enabled bool
|
||||||
err := rows.Scan(
|
err := rows.Scan(
|
||||||
&id, &createdAt, &updatedAt,
|
&id, &createdAt, &updatedAt,
|
||||||
&deletedAt, &userId, &url, &server,
|
&deletedAt, &url, &server,
|
||||||
&channel, &enabled,
|
&channel, &enabled,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return items, err
|
return items, err
|
||||||
}
|
}
|
||||||
|
|
||||||
item := entity.DiscordWebHookEntity{
|
item := domain.DiscordWebHookEntity{
|
||||||
ID: id,
|
ID: id,
|
||||||
CreatedAt: createdAt,
|
CreatedAt: createdAt,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
DeletedAt: deletedAt,
|
DeletedAt: deletedAt,
|
||||||
UserID: userId,
|
|
||||||
Url: url,
|
Url: url,
|
||||||
Server: server,
|
Server: server,
|
||||||
Channel: channel,
|
Channel: channel,
|
||||||
|
@ -17,7 +17,7 @@ func TestCreateDiscordWebHookRecord(t *testing.T) {
|
|||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
r := repository.NewDiscordWebHookRepository(db)
|
r := repository.NewDiscordWebHookRepository(db)
|
||||||
created, err := r.Create(context.Background(), 999, "www.discord.com/bad/webhook", "Unit Testing", "memes", true)
|
created, err := r.Create(context.Background(), "www.discord.com/bad/webhook", "Unit Testing", "memes", true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@ -38,7 +38,7 @@ func TestDiscordWebHookGetById(t *testing.T) {
|
|||||||
defer db.Close()
|
defer db.Close()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
r := repository.NewDiscordWebHookRepository(db)
|
r := repository.NewDiscordWebHookRepository(db)
|
||||||
created, err := r.Create(ctx, 999, "www.discord.com/bad/webhook", "Unit Testing", "memes", true)
|
created, err := r.Create(ctx, "www.discord.com/bad/webhook", "Unit Testing", "memes", true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@ -71,7 +71,7 @@ func TestDiscordWebHookGetByUrl(t *testing.T) {
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
r := repository.NewDiscordWebHookRepository(db)
|
r := repository.NewDiscordWebHookRepository(db)
|
||||||
_, _ = r.Create(ctx, 999, "www.discord.com/bad/webhook", "Unit Testing", "memes", true)
|
_, _ = r.Create(ctx, "www.discord.com/bad/webhook", "Unit Testing", "memes", true)
|
||||||
item, err := r.GetByUrl(ctx, "www.discord.com/bad/webhook")
|
item, err := r.GetByUrl(ctx, "www.discord.com/bad/webhook")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
@ -95,7 +95,7 @@ func TestDiscordWebHookListByServerName(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
serverName := "Unit Testing"
|
serverName := "Unit Testing"
|
||||||
r := repository.NewDiscordWebHookRepository(db)
|
r := repository.NewDiscordWebHookRepository(db)
|
||||||
_, _ = r.Create(ctx, 999, "www.discord.com/bad/webhook", serverName, "memes", true)
|
_, _ = r.Create(ctx, "www.discord.com/bad/webhook", serverName, "memes", true)
|
||||||
|
|
||||||
item, err := r.ListByServerName(ctx, serverName)
|
item, err := r.ListByServerName(ctx, serverName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -121,7 +121,7 @@ func TestDiscordWebHookListByServerAndChannel(t *testing.T) {
|
|||||||
serverName := "Unit Testing"
|
serverName := "Unit Testing"
|
||||||
channel := "memes"
|
channel := "memes"
|
||||||
r := repository.NewDiscordWebHookRepository(db)
|
r := repository.NewDiscordWebHookRepository(db)
|
||||||
_, _ = r.Create(ctx, 999, "www.discord.com/bad/webhook", serverName, channel, true)
|
_, _ = r.Create(ctx, "www.discord.com/bad/webhook", serverName, channel, true)
|
||||||
|
|
||||||
item, err := r.ListByServerAndChannel(ctx, serverName, channel)
|
item, err := r.ListByServerAndChannel(ctx, serverName, channel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -152,7 +152,7 @@ func TestDiscordWebHookEnableRecord(t *testing.T) {
|
|||||||
serverName := "Unit Testing"
|
serverName := "Unit Testing"
|
||||||
channel := "memes"
|
channel := "memes"
|
||||||
r := repository.NewDiscordWebHookRepository(db)
|
r := repository.NewDiscordWebHookRepository(db)
|
||||||
_, _ = r.Create(ctx, 999, "www.discord.com/bad/webhook", serverName, channel, false)
|
_, _ = r.Create(ctx, "www.discord.com/bad/webhook", serverName, channel, false)
|
||||||
|
|
||||||
item, err := r.GetById(ctx, 1)
|
item, err := r.GetById(ctx, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -195,7 +195,7 @@ func TestDiscordWebHookDisableRecord(t *testing.T) {
|
|||||||
serverName := "Unit Testing"
|
serverName := "Unit Testing"
|
||||||
channel := "memes"
|
channel := "memes"
|
||||||
r := repository.NewDiscordWebHookRepository(db)
|
r := repository.NewDiscordWebHookRepository(db)
|
||||||
_, _ = r.Create(ctx, 999, "www.discord.com/bad/webhook", serverName, channel, true)
|
_, _ = r.Create(ctx, "www.discord.com/bad/webhook", serverName, channel, true)
|
||||||
|
|
||||||
item, err := r.GetById(ctx, 1)
|
item, err := r.GetById(ctx, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -238,7 +238,7 @@ func TestDiscordWebHookSoftDelete(t *testing.T) {
|
|||||||
serverName := "Unit Testing"
|
serverName := "Unit Testing"
|
||||||
channel := "memes"
|
channel := "memes"
|
||||||
r := repository.NewDiscordWebHookRepository(db)
|
r := repository.NewDiscordWebHookRepository(db)
|
||||||
_, _ = r.Create(ctx, 999, "www.discord.com/bad/webhook", serverName, channel, true)
|
_, _ = r.Create(ctx, "www.discord.com/bad/webhook", serverName, channel, true)
|
||||||
_, err = r.SoftDelete(ctx, 1)
|
_, err = r.SoftDelete(ctx, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
@ -263,7 +263,7 @@ func TestDiscordWebHookRestore(t *testing.T) {
|
|||||||
timeZero := time.Time{}
|
timeZero := time.Time{}
|
||||||
|
|
||||||
r := repository.NewDiscordWebHookRepository(db)
|
r := repository.NewDiscordWebHookRepository(db)
|
||||||
_, _ = r.Create(ctx, 999, "www.discord.com/bad/webhook", serverName, channel, true)
|
_, _ = r.Create(ctx, "www.discord.com/bad/webhook", serverName, channel, true)
|
||||||
item, _ := r.GetById(ctx, 1)
|
item, _ := r.GetById(ctx, 1)
|
||||||
if item.DeletedAt != timeZero {
|
if item.DeletedAt != timeZero {
|
||||||
t.Log("DeletedAt was not zero")
|
t.Log("DeletedAt was not zero")
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"github.com/huandu/go-sqlbuilder"
|
"github.com/huandu/go-sqlbuilder"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,9 +15,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RefreshToken interface {
|
type RefreshToken interface {
|
||||||
Create(ctx context.Context, username string, token string) (int64, error)
|
Create(username string, token string) (int64, error)
|
||||||
GetByUsername(ctx context.Context, name string) (entity.RefreshTokenEntity, error)
|
GetByUsername(name string) (domain.RefreshTokenEntity, error)
|
||||||
DeleteById(ctx context.Context, id int64) (int64, error)
|
DeleteById(id int64) (int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type RefreshTokenRepository struct {
|
type RefreshTokenRepository struct {
|
||||||
@ -31,15 +30,15 @@ func NewRefreshTokenRepository(conn *sql.DB) RefreshTokenRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt RefreshTokenRepository) Create(ctx context.Context, username string, token string) (int64, error) {
|
func (rt RefreshTokenRepository) Create(username string, token string) (int64, error) {
|
||||||
dt := time.Now()
|
dt := time.Now()
|
||||||
builder := sqlbuilder.NewInsertBuilder()
|
builder := sqlbuilder.NewInsertBuilder()
|
||||||
builder.InsertInto(refreshTokenTableName)
|
builder.InsertInto(refreshTokenTableName)
|
||||||
builder.Cols("Username", "Token", "CreatedAt", "UpdatedAt", "DeletedAt")
|
builder.Cols("Username", "Token", "CreatedAt", "UpdatedAt")
|
||||||
builder.Values(username, token, dt, dt, time.Time{})
|
builder.Values(username, token, dt, dt)
|
||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
|
|
||||||
_, err := rt.connection.ExecContext(ctx, query, args...)
|
_, err := rt.connection.Exec(query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -47,27 +46,27 @@ func (rt RefreshTokenRepository) Create(ctx context.Context, username string, to
|
|||||||
return 1, nil
|
return 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt RefreshTokenRepository) GetByUsername(ctx context.Context, name string) (entity.RefreshTokenEntity, error) {
|
func (rt RefreshTokenRepository) GetByUsername(name string) (domain.RefreshTokenEntity, error) {
|
||||||
builder := sqlbuilder.NewSelectBuilder()
|
builder := sqlbuilder.NewSelectBuilder()
|
||||||
builder.Select("*").From(refreshTokenTableName).Where(
|
builder.Select("*").From(refreshTokenTableName).Where(
|
||||||
builder.E("Username", name),
|
builder.E("Username", name),
|
||||||
)
|
)
|
||||||
|
|
||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
rows, err := rt.connection.QueryContext(ctx, query, args...)
|
rows, err := rt.connection.Query(query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entity.RefreshTokenEntity{}, err
|
return domain.RefreshTokenEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data := rt.processRows(rows)
|
data := rt.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return entity.RefreshTokenEntity{}, errors.New("no token found for user")
|
return domain.RefreshTokenEntity{}, errors.New("no token found for user")
|
||||||
}
|
}
|
||||||
|
|
||||||
return data[0], nil
|
return data[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt RefreshTokenRepository) DeleteById(ctx context.Context, id int64) (int64, error) {
|
func (rt RefreshTokenRepository) DeleteById(id int64) (int64, error) {
|
||||||
builder := sqlbuilder.NewDeleteBuilder()
|
builder := sqlbuilder.NewDeleteBuilder()
|
||||||
builder.DeleteFrom(refreshTokenTableName)
|
builder.DeleteFrom(refreshTokenTableName)
|
||||||
builder.Where(
|
builder.Where(
|
||||||
@ -75,7 +74,7 @@ func (rt RefreshTokenRepository) DeleteById(ctx context.Context, id int64) (int6
|
|||||||
)
|
)
|
||||||
|
|
||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
rows, err := rt.connection.ExecContext(ctx, query, args...)
|
rows, err := rt.connection.Exec(query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
@ -83,8 +82,8 @@ func (rt RefreshTokenRepository) DeleteById(ctx context.Context, id int64) (int6
|
|||||||
return rows.RowsAffected()
|
return rows.RowsAffected()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rd RefreshTokenRepository) processRows(rows *sql.Rows) []entity.RefreshTokenEntity {
|
func (rd RefreshTokenRepository) processRows(rows *sql.Rows) []domain.RefreshTokenEntity {
|
||||||
items := []entity.RefreshTokenEntity{}
|
items := []domain.RefreshTokenEntity{}
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var id int64
|
var id int64
|
||||||
@ -99,7 +98,7 @@ func (rd RefreshTokenRepository) processRows(rows *sql.Rows) []entity.RefreshTok
|
|||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
item := entity.RefreshTokenEntity{
|
item := domain.RefreshTokenEntity{
|
||||||
ID: id,
|
ID: id,
|
||||||
Username: username,
|
Username: username,
|
||||||
Token: token,
|
Token: token,
|
||||||
@ -107,7 +106,7 @@ func (rd RefreshTokenRepository) processRows(rows *sql.Rows) []entity.RefreshTok
|
|||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
if deletedAt.Valid {
|
if (deletedAt.Valid) {
|
||||||
item.DeletedAt = deletedAt.Time
|
item.DeletedAt = deletedAt.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package repository_test
|
package repository_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
|
||||||
@ -15,7 +14,7 @@ func TestRefreshTokenCreate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
client := repository.NewRefreshTokenRepository(conn)
|
client := repository.NewRefreshTokenRepository(conn)
|
||||||
rows, err := client.Create(context.Background(), "tester", "BadTokenDontUse")
|
rows, err := client.Create("tester", "BadTokenDontUse")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@ -34,7 +33,7 @@ func TestRefreshTokenGetByUsername(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
client := repository.NewRefreshTokenRepository(conn)
|
client := repository.NewRefreshTokenRepository(conn)
|
||||||
rows, err := client.Create(context.Background(), "tester", "BadTokenDoNotUse")
|
rows, err := client.Create("tester", "BadTokenDoNotUse")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@ -45,7 +44,7 @@ func TestRefreshTokenGetByUsername(t *testing.T) {
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
model, err := client.GetByUsername(context.Background(), "tester")
|
model, err := client.GetByUsername("tester")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@ -65,7 +64,7 @@ func TestRefreshTokenDeleteById(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
client := repository.NewRefreshTokenRepository(conn)
|
client := repository.NewRefreshTokenRepository(conn)
|
||||||
created, err := client.Create(context.Background(), "tester", "BadTokenDoNotUse")
|
created, err := client.Create("tester", "BadTokenDoNotUse")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@ -74,13 +73,13 @@ func TestRefreshTokenDeleteById(t *testing.T) {
|
|||||||
t.Log("Unexpected number back for rows created")
|
t.Log("Unexpected number back for rows created")
|
||||||
}
|
}
|
||||||
|
|
||||||
model, err := client.GetByUsername(context.Background(), "tester")
|
model, err := client.GetByUsername("tester")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
updated, err := client.DeleteById(context.Background(), model.ID)
|
updated, err := client.DeleteById(model.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
@ -5,18 +5,18 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"github.com/huandu/go-sqlbuilder"
|
"github.com/huandu/go-sqlbuilder"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Sources interface {
|
type Sources interface {
|
||||||
Create(ctx context.Context, source, displayName, url, tags string, enabled bool) (int64, error)
|
Create(ctx context.Context, source, displayName, url, tags string, enabled bool) (int64, error)
|
||||||
GetById(ctx context.Context, id int64) (entity.SourceEntity, error)
|
GetById(ctx context.Context, id int64) (domain.SourceEntity, error)
|
||||||
GetByDisplayName(ctx context.Context, displayName string) (entity.SourceEntity, error)
|
GetByDisplayName(ctx context.Context, displayName string) (domain.SourceEntity, error)
|
||||||
GetBySource(ctx context.Context, source string) (entity.SourceEntity, error)
|
GetBySource(ctx context.Context, source string) (domain.SourceEntity, error)
|
||||||
GetBySourceAndName(ctx context.Context, source, name string) (entity.SourceEntity, error)
|
GetBySourceAndName(ctx context.Context, source, name string) (domain.SourceEntity, error)
|
||||||
List(ctx context.Context, page, limit int) ([]entity.SourceEntity, error)
|
List(ctx context.Context, page, limit int) ([]domain.SourceEntity, error)
|
||||||
ListBySource(ctx context.Context, page, limit int, source string) ([]entity.SourceEntity, error)
|
ListBySource(ctx context.Context, page, limit int, source string) ([]domain.SourceEntity, error)
|
||||||
Enable(ctx context.Context, id int64) (int64, error)
|
Enable(ctx context.Context, id int64) (int64, error)
|
||||||
Disable(ctx context.Context, id int64) (int64, error)
|
Disable(ctx context.Context, id int64) (int64, error)
|
||||||
SoftDelete(ctx context.Context, id int64) (int64, error)
|
SoftDelete(ctx context.Context, id int64) (int64, error)
|
||||||
@ -50,7 +50,7 @@ func (r sourceRepository) Create(ctx context.Context, source, displayName, url,
|
|||||||
return 1, nil
|
return 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r sourceRepository) GetById(ctx context.Context, id int64) (entity.SourceEntity, error) {
|
func (r sourceRepository) GetById(ctx context.Context, id int64) (domain.SourceEntity, error) {
|
||||||
b := sqlbuilder.NewSelectBuilder()
|
b := sqlbuilder.NewSelectBuilder()
|
||||||
b.Select("*")
|
b.Select("*")
|
||||||
b.From("Sources").Where(
|
b.From("Sources").Where(
|
||||||
@ -61,18 +61,18 @@ func (r sourceRepository) GetById(ctx context.Context, id int64) (entity.SourceE
|
|||||||
|
|
||||||
rows, err := r.conn.QueryContext(ctx, query, args...)
|
rows, err := r.conn.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entity.SourceEntity{}, err
|
return domain.SourceEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := r.processRows(rows)
|
data, err := r.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return entity.SourceEntity{}, err
|
return domain.SourceEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return data[0], nil
|
return data[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r sourceRepository) GetByDisplayName(ctx context.Context, displayName string) (entity.SourceEntity, error) {
|
func (r sourceRepository) GetByDisplayName(ctx context.Context, displayName string) (domain.SourceEntity, error) {
|
||||||
b := sqlbuilder.NewSelectBuilder()
|
b := sqlbuilder.NewSelectBuilder()
|
||||||
b.Select("*")
|
b.Select("*")
|
||||||
b.From("Sources").Where(
|
b.From("Sources").Where(
|
||||||
@ -83,18 +83,18 @@ func (r sourceRepository) GetByDisplayName(ctx context.Context, displayName stri
|
|||||||
|
|
||||||
rows, err := r.conn.QueryContext(ctx, query, args...)
|
rows, err := r.conn.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entity.SourceEntity{}, err
|
return domain.SourceEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := r.processRows(rows)
|
data, err := r.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return entity.SourceEntity{}, err
|
return domain.SourceEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return data[0], nil
|
return data[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r sourceRepository) GetBySource(ctx context.Context, source string) (entity.SourceEntity, error) {
|
func (r sourceRepository) GetBySource(ctx context.Context, source string) (domain.SourceEntity, error) {
|
||||||
b := sqlbuilder.NewSelectBuilder()
|
b := sqlbuilder.NewSelectBuilder()
|
||||||
b.Select("*")
|
b.Select("*")
|
||||||
b.From("Sources").Where(
|
b.From("Sources").Where(
|
||||||
@ -105,41 +105,41 @@ func (r sourceRepository) GetBySource(ctx context.Context, source string) (entit
|
|||||||
|
|
||||||
rows, err := r.conn.QueryContext(ctx, query, args...)
|
rows, err := r.conn.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entity.SourceEntity{}, err
|
return domain.SourceEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := r.processRows(rows)
|
data, err := r.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return entity.SourceEntity{}, err
|
return domain.SourceEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return data[0], nil
|
return data[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r sourceRepository) GetBySourceAndName(ctx context.Context, source, name string) (entity.SourceEntity, error) {
|
func (r sourceRepository) GetBySourceAndName(ctx context.Context, source, name string) (domain.SourceEntity, error) {
|
||||||
b := sqlbuilder.NewSelectBuilder()
|
b := sqlbuilder.NewSelectBuilder()
|
||||||
b.Select("*")
|
b.Select("*")
|
||||||
b.From("Sources").Where(
|
b.From("Sources").Where(
|
||||||
b.Equal("Source", source),
|
b.Equal("Source", source),
|
||||||
b.Equal("DisplayName", name),
|
b.Equal("Name", name),
|
||||||
)
|
)
|
||||||
b.Limit(1)
|
b.Limit(1)
|
||||||
query, args := b.Build()
|
query, args := b.Build()
|
||||||
|
|
||||||
rows, err := r.conn.QueryContext(ctx, query, args...)
|
rows, err := r.conn.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entity.SourceEntity{}, err
|
return domain.SourceEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := r.processRows(rows)
|
data, err := r.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return entity.SourceEntity{}, err
|
return domain.SourceEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return data[0], nil
|
return data[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r sourceRepository) List(ctx context.Context, page, limit int) ([]entity.SourceEntity, error) {
|
func (r sourceRepository) List(ctx context.Context, page, limit int) ([]domain.SourceEntity, error) {
|
||||||
builder := sqlbuilder.NewSelectBuilder()
|
builder := sqlbuilder.NewSelectBuilder()
|
||||||
builder.Select("*")
|
builder.Select("*")
|
||||||
builder.From("Sources")
|
builder.From("Sources")
|
||||||
@ -149,18 +149,18 @@ func (r sourceRepository) List(ctx context.Context, page, limit int) ([]entity.S
|
|||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
rows, err := r.conn.QueryContext(ctx, query, args...)
|
rows, err := r.conn.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []entity.SourceEntity{}, err
|
return []domain.SourceEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := r.processRows(rows)
|
data, err := r.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return []entity.SourceEntity{}, err
|
return []domain.SourceEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r sourceRepository) ListBySource(ctx context.Context, page, limit int, source string) ([]entity.SourceEntity, error) {
|
func (r sourceRepository) ListBySource(ctx context.Context, page, limit int, source string) ([]domain.SourceEntity, error) {
|
||||||
builder := sqlbuilder.NewSelectBuilder()
|
builder := sqlbuilder.NewSelectBuilder()
|
||||||
builder.Select("*")
|
builder.Select("*")
|
||||||
builder.From("Sources")
|
builder.From("Sources")
|
||||||
@ -173,12 +173,12 @@ func (r sourceRepository) ListBySource(ctx context.Context, page, limit int, sou
|
|||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
rows, err := r.conn.QueryContext(ctx, query, args...)
|
rows, err := r.conn.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []entity.SourceEntity{}, err
|
return []domain.SourceEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := r.processRows(rows)
|
data, err := r.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return []entity.SourceEntity{}, err
|
return []domain.SourceEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
@ -236,8 +236,8 @@ func (r sourceRepository) Delete(ctx context.Context, id int64) (int64, error) {
|
|||||||
return deleteFromTable(ctx, r.conn, "Sources", id)
|
return deleteFromTable(ctx, r.conn, "Sources", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r sourceRepository) processRows(rows *sql.Rows) ([]entity.SourceEntity, error) {
|
func (r sourceRepository) processRows(rows *sql.Rows) ([]domain.SourceEntity, error) {
|
||||||
items := []entity.SourceEntity{}
|
items := []domain.SourceEntity{}
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var id int64
|
var id int64
|
||||||
@ -258,7 +258,7 @@ func (r sourceRepository) processRows(rows *sql.Rows) ([]entity.SourceEntity, er
|
|||||||
return items, err
|
return items, err
|
||||||
}
|
}
|
||||||
|
|
||||||
item := entity.SourceEntity{
|
item := domain.SourceEntity{
|
||||||
ID: id,
|
ID: id,
|
||||||
CreatedAt: createdAt,
|
CreatedAt: createdAt,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,120 +0,0 @@
|
|||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
|
||||||
"github.com/huandu/go-sqlbuilder"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UserSourceRepo interface {
|
|
||||||
Create(ctx context.Context, userId, sourceId int64) (int64, error)
|
|
||||||
SoftDelete(ctx context.Context, id int64) (int64, error)
|
|
||||||
Restore(ctx context.Context, id int64) (int64, error)
|
|
||||||
Delete(ctx context.Context, id int64) (int64, error)
|
|
||||||
ListByUser(ctx context.Context, page, limit int, userId int64) ([]entity.UserSourceSubscriptionEntity, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type userSourceRepository struct {
|
|
||||||
conn *sql.DB
|
|
||||||
defaultLimit int
|
|
||||||
defaultOffset int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUserSourceRepository(conn *sql.DB) userSourceRepository {
|
|
||||||
return userSourceRepository{
|
|
||||||
conn: conn,
|
|
||||||
defaultLimit: 50,
|
|
||||||
defaultOffset: 50,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r userSourceRepository) Create(ctx context.Context, userId, sourceId int64) (int64, error) {
|
|
||||||
dt := time.Now()
|
|
||||||
queryBuilder := sqlbuilder.NewInsertBuilder()
|
|
||||||
queryBuilder.InsertInto("UserSourceSubscriptions")
|
|
||||||
queryBuilder.Cols("UpdatedAt", "CreatedAt", "DeletedAt", "UserID", "SourceID")
|
|
||||||
queryBuilder.Values(dt, dt, timeZero, userId, sourceId)
|
|
||||||
query, args := queryBuilder.Build()
|
|
||||||
|
|
||||||
_, err := r.conn.ExecContext(ctx, query, args...)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r userSourceRepository) SoftDelete(ctx context.Context, id int64) (int64, error) {
|
|
||||||
return softDeleteRow(ctx, r.conn, "UserSourceSubscriptions", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r userSourceRepository) Restore(ctx context.Context, id int64) (int64, error) {
|
|
||||||
return restoreRow(ctx, r.conn, "UserSourceSubscriptions", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r userSourceRepository) Delete(ctx context.Context, id int64) (int64, error) {
|
|
||||||
return deleteFromTable(ctx, r.conn, "UserSourceSubscriptions", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r userSourceRepository) ListByUser(ctx context.Context, page, limit int, userId int64) ([]entity.UserSourceSubscriptionEntity, error) {
|
|
||||||
builder := sqlbuilder.NewSelectBuilder()
|
|
||||||
builder.Select("*")
|
|
||||||
builder.From("UserSourceSubscriptions")
|
|
||||||
builder.Where(
|
|
||||||
builder.Equal("UserID", userId),
|
|
||||||
)
|
|
||||||
builder.Offset(page * limit)
|
|
||||||
builder.Limit(limit)
|
|
||||||
|
|
||||||
query, args := builder.Build()
|
|
||||||
rows, err := r.conn.QueryContext(ctx, query, args...)
|
|
||||||
if err != nil {
|
|
||||||
return []entity.UserSourceSubscriptionEntity{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
data := r.processRows(rows)
|
|
||||||
if len(data) == 0 {
|
|
||||||
return []entity.UserSourceSubscriptionEntity{}, errors.New(ErrUserNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ur userSourceRepository) processRows(rows *sql.Rows) []entity.UserSourceSubscriptionEntity {
|
|
||||||
items := []entity.UserSourceSubscriptionEntity{}
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var id int64
|
|
||||||
var createdAt time.Time
|
|
||||||
var updatedAt time.Time
|
|
||||||
var deletedAt time.Time
|
|
||||||
var userId int64
|
|
||||||
var sourceId int64
|
|
||||||
|
|
||||||
err := rows.Scan(
|
|
||||||
&id, &createdAt, &updatedAt, &deletedAt,
|
|
||||||
&userId, &sourceId,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
item := entity.UserSourceSubscriptionEntity{
|
|
||||||
ID: id,
|
|
||||||
CreatedAt: createdAt,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
DeletedAt: deletedAt,
|
|
||||||
UserID: userId,
|
|
||||||
SourceID: sourceId,
|
|
||||||
}
|
|
||||||
|
|
||||||
items = append(items, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
return items
|
|
||||||
}
|
|
@ -1,31 +1,29 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
|
|
||||||
"github.com/huandu/go-sqlbuilder"
|
"github.com/huandu/go-sqlbuilder"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
usersTableName string = "users"
|
TableName string = "users"
|
||||||
ErrUserNotFound string = "requested user was not found"
|
ErrUserNotFound string = "requested user was not found"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Users interface {
|
type Users interface {
|
||||||
GetByName(ctx context.Context, name string) (entity.UserEntity, error)
|
GetByName(name string) (domain.UserEntity, error)
|
||||||
Create(ctx context.Context, name, password, sessionTOken, scope string) (int64, error)
|
Create(name, password, scope string) (int64, error)
|
||||||
Update(ctx context.Context, id int, entity entity.UserEntity) error
|
Update(id int, entity domain.UserEntity) error
|
||||||
UpdatePassword(ctx context.Context, name, password string) error
|
UpdatePassword(name, password string) error
|
||||||
CheckUserHash(ctx context.Context, name, password string) error
|
CheckUserHash(name, password string) error
|
||||||
UpdateScopes(ctx context.Context, name, scope string) error
|
UpdateScopes(name, scope string) error
|
||||||
UpdateSessionToken(ctx context.Context, name, sessionToken string) (int64, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new instance of UserRepository with the bound sql
|
// Creates a new instance of UserRepository with the bound sql
|
||||||
@ -39,27 +37,27 @@ type userRepository struct {
|
|||||||
connection *sql.DB
|
connection *sql.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ur userRepository) GetByName(ctx context.Context, name string) (entity.UserEntity, error) {
|
func (ur userRepository) GetByName(name string) (domain.UserEntity, error) {
|
||||||
builder := sqlbuilder.NewSelectBuilder()
|
builder := sqlbuilder.NewSelectBuilder()
|
||||||
builder.Select("*").From("users").Where(
|
builder.Select("*").From("users").Where(
|
||||||
builder.E("Name", name),
|
builder.E("Name", name),
|
||||||
)
|
)
|
||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
|
|
||||||
rows, err := ur.connection.QueryContext(ctx, query, args...)
|
rows, err := ur.connection.Query(query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entity.UserEntity{}, err
|
return domain.UserEntity{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data := ur.processRows(rows)
|
data := ur.processRows(rows)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return entity.UserEntity{}, errors.New(ErrUserNotFound)
|
return domain.UserEntity{}, errors.New(ErrUserNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
return data[0], nil
|
return data[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ur userRepository) Create(ctx context.Context, name, password, sessionToken, scope string) (int64, error) {
|
func (ur userRepository) Create(name, password, scope string) (int64, error) {
|
||||||
passwordBytes := []byte(password)
|
passwordBytes := []byte(password)
|
||||||
hash, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.DefaultCost)
|
hash, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -69,11 +67,11 @@ func (ur userRepository) Create(ctx context.Context, name, password, sessionToke
|
|||||||
dt := time.Now()
|
dt := time.Now()
|
||||||
queryBuilder := sqlbuilder.NewInsertBuilder()
|
queryBuilder := sqlbuilder.NewInsertBuilder()
|
||||||
queryBuilder.InsertInto("users")
|
queryBuilder.InsertInto("users")
|
||||||
queryBuilder.Cols("Name", "Hash", "UpdatedAt", "CreatedAt", "DeletedAt", "Scopes", "SessionToken")
|
queryBuilder.Cols("Name", "Hash", "UpdatedAt", "CreatedAt", "Scopes")
|
||||||
queryBuilder.Values(name, string(hash), dt, dt, time.Time{}, scope, sessionToken)
|
queryBuilder.Values(name, string(hash), dt, dt, scope)
|
||||||
query, args := queryBuilder.Build()
|
query, args := queryBuilder.Build()
|
||||||
|
|
||||||
_, err = ur.connection.ExecContext(ctx, query, args...)
|
_, err = ur.connection.Exec(query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -81,50 +79,26 @@ func (ur userRepository) Create(ctx context.Context, name, password, sessionToke
|
|||||||
return 1, nil
|
return 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ur userRepository) Update(ctx context.Context, id int, entity entity.UserEntity) error {
|
func (ur userRepository) Update(id int, entity domain.UserEntity) error {
|
||||||
return errors.New("not implemented")
|
return errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ur userRepository) UpdatePassword(ctx context.Context, name, password string) error {
|
func (ur userRepository) UpdatePassword(name, password string) error {
|
||||||
_, err := ur.GetByName(ctx, name)
|
_, err := ur.GetByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
queryBuilder := sqlbuilder.NewUpdateBuilder()
|
queryBuilder := sqlbuilder.NewUpdateBuilder()
|
||||||
queryBuilder.Update(usersTableName)
|
queryBuilder.Update(TableName)
|
||||||
//queryBuilder.Set
|
//queryBuilder.Set
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ur userRepository) UpdateSessionToken(ctx context.Context, name, sessionToken string) (int64, error) {
|
|
||||||
_, err := ur.GetByName(ctx, name)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
q := sqlbuilder.NewUpdateBuilder()
|
|
||||||
q.Update(usersTableName)
|
|
||||||
q.Set(
|
|
||||||
q.Equal("SessionToken", sessionToken),
|
|
||||||
)
|
|
||||||
q.Where(
|
|
||||||
q.Equal("Name", name),
|
|
||||||
)
|
|
||||||
|
|
||||||
query, args := q.Build()
|
|
||||||
rowsUpdates, err := ur.connection.ExecContext(ctx, query, args...)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return rowsUpdates.RowsAffected()
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the hash matches what we have in the database, an error will not be returned.
|
// If the hash matches what we have in the database, an error will not be returned.
|
||||||
// If the user does not exist or the hash does not match, an error will be returned
|
// If the user does not exist or the hash does not match, an error will be returned
|
||||||
func (ur userRepository) CheckUserHash(ctx context.Context, name, password string) error {
|
func (ur userRepository) CheckUserHash(name, password string) error {
|
||||||
record, err := ur.GetByName(ctx, name)
|
record, err := ur.GetByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -137,7 +111,7 @@ func (ur userRepository) CheckUserHash(ctx context.Context, name, password strin
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ur userRepository) UpdateScopes(ctx context.Context, name, scope string) error {
|
func (ur userRepository) UpdateScopes(name, scope string) error {
|
||||||
builder := sqlbuilder.NewUpdateBuilder()
|
builder := sqlbuilder.NewUpdateBuilder()
|
||||||
builder.Update("users")
|
builder.Update("users")
|
||||||
builder.Set(
|
builder.Set(
|
||||||
@ -148,15 +122,15 @@ func (ur userRepository) UpdateScopes(ctx context.Context, name, scope string) e
|
|||||||
)
|
)
|
||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
|
|
||||||
_, err := ur.connection.ExecContext(ctx, query, args...)
|
_, err := ur.connection.Exec(query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ur userRepository) processRows(rows *sql.Rows) []entity.UserEntity {
|
func (ur userRepository) processRows(rows *sql.Rows) []domain.UserEntity {
|
||||||
items := []entity.UserEntity{}
|
items := []domain.UserEntity{}
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var id int64
|
var id int64
|
||||||
@ -166,20 +140,18 @@ func (ur userRepository) processRows(rows *sql.Rows) []entity.UserEntity {
|
|||||||
var updatedAt time.Time
|
var updatedAt time.Time
|
||||||
var deletedAt sql.NullTime
|
var deletedAt sql.NullTime
|
||||||
var scopes string
|
var scopes string
|
||||||
var sessionToken string
|
err := rows.Scan(&id, &createdAt, &updatedAt, &deletedAt, &username, &hash, &scopes)
|
||||||
err := rows.Scan(&id, &createdAt, &updatedAt, &deletedAt, &username, &hash, &scopes, &sessionToken)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
item := entity.UserEntity{
|
item := domain.UserEntity{
|
||||||
ID: id,
|
ID: id,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
Username: username,
|
Username: username,
|
||||||
Hash: hash,
|
Hash: hash,
|
||||||
Scopes: scopes,
|
Scopes: scopes,
|
||||||
CreatedAt: createdAt,
|
CreatedAt: createdAt,
|
||||||
SessionToken: sessionToken,
|
|
||||||
}
|
}
|
||||||
if deletedAt.Valid {
|
if deletedAt.Valid {
|
||||||
item.DeletedAt = deletedAt.Time
|
item.DeletedAt = deletedAt.Time
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package repository_test
|
package repository_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"log"
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
@ -21,7 +20,7 @@ func TestCanCreateNewUser(t *testing.T) {
|
|||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
repo := repository.NewUserRepository(db)
|
repo := repository.NewUserRepository(db)
|
||||||
updated, err := repo.Create(context.Background(), "testing", "NotSecure", "sessionToken", "placeholder")
|
updated, err := repo.Create("testing", "NotSecure", "placeholder")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@ -38,7 +37,7 @@ func TestCanFindUserInTable(t *testing.T) {
|
|||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
repo := repository.NewUserRepository(db)
|
repo := repository.NewUserRepository(db)
|
||||||
updated, err := repo.Create(context.Background(), "testing", "NotSecure", "sessionToken", "placeholder")
|
updated, err := repo.Create("testing", "NotSecure", "placeholder")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@ -49,7 +48,7 @@ func TestCanFindUserInTable(t *testing.T) {
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := repo.GetByName(context.Background(), "testing")
|
user, err := repo.GetByName("testing")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@ -66,7 +65,7 @@ func TestCheckUserHash(t *testing.T) {
|
|||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
repo := repository.NewUserRepository(db)
|
repo := repository.NewUserRepository(db)
|
||||||
repo.CheckUserHash(context.Background(), "testing", "NotSecure")
|
repo.CheckUserHash("testing", "NotSecure")
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupInMemoryDb() (*sql.DB, error) {
|
func setupInMemoryDb() (*sql.DB, error) {
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
package repositoryservices
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ErrUnexpectedAmountOfRowsUpdated = "got a unexpected of rows updated"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RefreshToken interface {
|
|
||||||
Create(ctx context.Context, username string) (string, error)
|
|
||||||
GetByName(ctx context.Context, name string) (entity.RefreshTokenEntity, error)
|
|
||||||
Delete(ctx context.Context, id int64) (int64, error)
|
|
||||||
IsRequestValid(ctx context.Context, username, refreshToken string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// A new jwt token can be made if the user has the correct refresh token for the user.
|
|
||||||
// It will also require the old JWT token so the expire time is pulled and part of the validation
|
|
||||||
type RefreshTokenService struct {
|
|
||||||
table repository.RefreshTokenRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRefreshTokenService(conn *sql.DB) RefreshTokenService {
|
|
||||||
return RefreshTokenService{
|
|
||||||
table: repository.NewRefreshTokenRepository(conn),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt RefreshTokenService) Create(ctx context.Context, username string) (string, error) {
|
|
||||||
//if a refresh token already exists for a user, reuse
|
|
||||||
existingToken, err := rt.GetByName(ctx, username)
|
|
||||||
if err == nil {
|
|
||||||
rowsRemoved, err := rt.Delete(ctx, existingToken.ID)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if rowsRemoved != 1 {
|
|
||||||
return "", errors.New(ErrUnexpectedAmountOfRowsUpdated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := uuid.NewV7()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := rt.table.Create(ctx, username, token.String())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if rows != 1 {
|
|
||||||
return "", errors.New("expected one row but got none")
|
|
||||||
}
|
|
||||||
return token.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the saved refresh token for a user and return it if it exists
|
|
||||||
func (rt RefreshTokenService) GetByName(ctx context.Context, name string) (entity.RefreshTokenEntity, error) {
|
|
||||||
return rt.table.GetByUsername(ctx, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will request that a object is removed from the database
|
|
||||||
func (rt RefreshTokenService) Delete(ctx context.Context, id int64) (int64, error) {
|
|
||||||
return rt.table.DeleteById(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt RefreshTokenService) IsRequestValid(ctx context.Context, username, refreshToken string) error {
|
|
||||||
token, err := rt.GetByName(ctx, username)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if token.Token != refreshToken {
|
|
||||||
return errors.New("the refresh token given does not match")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,198 +0,0 @@
|
|||||||
package repositoryservices
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ErrPasswordNotLongEnough = "password needs to be 8 character or longer"
|
|
||||||
ErrPasswordMissingSpecialCharacter = "password needs to contain one of the following: !, @, #"
|
|
||||||
ErrInvalidPassword = "invalid password"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UserServices interface {
|
|
||||||
DoesUserExist(ctx context.Context, username string) error
|
|
||||||
DoesPasswordMatchHash(ctx context.Context, username, password string) error
|
|
||||||
GetUser(ctx context.Context, username string) (entity.UserEntity, error)
|
|
||||||
AddScopes(ctx context.Context, username string, scopes []string) error
|
|
||||||
RemoveScopes(ctx context.Context, username string, scopes []string) error
|
|
||||||
Create(ctx context.Context, name, password, scope string) (entity.UserEntity, error)
|
|
||||||
NewSessionToken(ctx context.Context, name string) (string, error)
|
|
||||||
CheckPasswordForRequirements(password string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will handle operations that are user related, but one layer higher then the repository
|
|
||||||
type UserService struct {
|
|
||||||
repo repository.Users
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a layer on top of the Users Repository.
|
|
||||||
// Use this over directly talking to the table when ever possible.
|
|
||||||
func NewUserService(conn *sql.DB) UserService {
|
|
||||||
return UserService{
|
|
||||||
repo: repository.NewUserRepository(conn),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us UserService) DoesUserExist(ctx context.Context, username string) error {
|
|
||||||
_, err := us.repo.GetByName(ctx, username)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us UserService) DoesPasswordMatchHash(ctx context.Context, username, password string) error {
|
|
||||||
model, err := us.GetUser(ctx, username)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = bcrypt.CompareHashAndPassword([]byte(model.Hash), []byte(password))
|
|
||||||
if err != nil {
|
|
||||||
return errors.New(ErrInvalidPassword)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us UserService) GetUser(ctx context.Context, username string) (entity.UserEntity, error) {
|
|
||||||
return us.repo.GetByName(ctx, username)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us UserService) AddScopes(ctx context.Context, username string, scopes []string) error {
|
|
||||||
usr, err := us.repo.GetByName(ctx, username)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if usr.Username != username {
|
|
||||||
return errors.New(repository.ErrUserNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentScopes := strings.Split(usr.Scopes, ",")
|
|
||||||
|
|
||||||
// check the current scopes
|
|
||||||
for _, item := range scopes {
|
|
||||||
if !strings.Contains(usr.Scopes, item) {
|
|
||||||
currentScopes = append(currentScopes, item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return us.repo.UpdateScopes(ctx, username, strings.Join(currentScopes, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us UserService) RemoveScopes(ctx context.Context, username string, scopes []string) error {
|
|
||||||
usr, err := us.repo.GetByName(ctx, username)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if usr.Username != username {
|
|
||||||
return errors.New(repository.ErrUserNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
var newScopes []string
|
|
||||||
|
|
||||||
// check all the scopes that are currently assigned
|
|
||||||
for _, item := range strings.Split(usr.Scopes, ",") {
|
|
||||||
|
|
||||||
// check the scopes given, if one matches skip it
|
|
||||||
if us.doesScopeExist(scopes, item) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// did not match, add it
|
|
||||||
newScopes = append(newScopes, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
return us.repo.UpdateScopes(ctx, username, strings.Join(newScopes, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us UserService) doesScopeExist(scopes []string, target string) bool {
|
|
||||||
for _, item := range scopes {
|
|
||||||
if item == target {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us UserService) Create(ctx context.Context, name, password, scope string) (entity.UserEntity, error) {
|
|
||||||
err := us.CheckPasswordForRequirements(password)
|
|
||||||
if err != nil {
|
|
||||||
return entity.UserEntity{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := uuid.NewV7()
|
|
||||||
if err != nil {
|
|
||||||
return entity.UserEntity{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
us.repo.Create(ctx, name, password, token.String(), domain.ScopeArticleRead)
|
|
||||||
return entity.UserEntity{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us UserService) NewSessionToken(ctx context.Context, name string) (string, error) {
|
|
||||||
token, err := uuid.NewV7()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := us.repo.UpdateSessionToken(ctx, name, token.String())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if rows != 1 {
|
|
||||||
return "", fmt.Errorf("UserService.NewSessionToken %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return token.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us UserService) CheckPasswordForRequirements(password string) error {
|
|
||||||
err := us.checkPasswordLength(password)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = us.checkPasswordForSpecialCharacters(password)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us UserService) checkPasswordLength(password string) error {
|
|
||||||
if len(password) < 8 {
|
|
||||||
return errors.New(ErrPasswordNotLongEnough)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us UserService) checkPasswordForSpecialCharacters(password string) error {
|
|
||||||
var chars []string
|
|
||||||
chars = append(chars, "!")
|
|
||||||
chars = append(chars, "@")
|
|
||||||
chars = append(chars, "#")
|
|
||||||
|
|
||||||
for _, char := range chars {
|
|
||||||
if strings.Contains(password, char) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New(ErrPasswordMissingSpecialCharacter)
|
|
||||||
}
|
|
@ -11,9 +11,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ServerAddress = "ServerAddress"
|
ServerAddress = "SERVER_ADDRESS"
|
||||||
|
|
||||||
//Sql_Connection_String = "SQL_CONNECTION_STRING"
|
Sql_Connection_String = "SQL_CONNECTION_STRING"
|
||||||
|
|
||||||
FEATURE_ENABLE_REDDIT_BACKEND = "FEATURE_ENABLE_REDDIT_BACKEND"
|
FEATURE_ENABLE_REDDIT_BACKEND = "FEATURE_ENABLE_REDDIT_BACKEND"
|
||||||
REDDIT_PULL_TOP = "REDDIT_PULL_TOP"
|
REDDIT_PULL_TOP = "REDDIT_PULL_TOP"
|
||||||
@ -34,8 +34,6 @@ const (
|
|||||||
|
|
||||||
type Configs struct {
|
type Configs struct {
|
||||||
ServerAddress string
|
ServerAddress string
|
||||||
JwtSecret string
|
|
||||||
AdminSecret string
|
|
||||||
|
|
||||||
RedditEnabled bool
|
RedditEnabled bool
|
||||||
RedditPullTop bool
|
RedditPullTop bool
|
||||||
@ -66,8 +64,6 @@ func NewConfig() ConfigClient {
|
|||||||
func GetEnvConfig() Configs {
|
func GetEnvConfig() Configs {
|
||||||
return Configs{
|
return Configs{
|
||||||
ServerAddress: os.Getenv(ServerAddress),
|
ServerAddress: os.Getenv(ServerAddress),
|
||||||
JwtSecret: os.Getenv("JwtSecret"),
|
|
||||||
AdminSecret: os.Getenv("AdminSecret"),
|
|
||||||
|
|
||||||
RedditEnabled: processBoolConfig(os.Getenv(FEATURE_ENABLE_REDDIT_BACKEND)),
|
RedditEnabled: processBoolConfig(os.Getenv(FEATURE_ENABLE_REDDIT_BACKEND)),
|
||||||
RedditPullTop: processBoolConfig(os.Getenv(REDDIT_PULL_TOP)),
|
RedditPullTop: processBoolConfig(os.Getenv(REDDIT_PULL_TOP)),
|
||||||
|
@ -1,220 +0,0 @@
|
|||||||
package cron
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/input"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Cron) CollectRssPosts() {
|
|
||||||
log.Println("Starting ")
|
|
||||||
sources, err := c.repo.Sources.ListBySource(c.ctx, 0, 1000, domain.SourceCollectorRss)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for sourceIndex, source := range sources {
|
|
||||||
if !source.Enabled {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rssClient := input.NewRssClient(source)
|
|
||||||
articles, err := rssClient.GetArticles()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, article := range articles {
|
|
||||||
_, err := c.repo.Articles.GetByUrl(c.ctx, article.Url)
|
|
||||||
if err == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rowsCreated, err := c.repo.Articles.CreateFromEntity(c.ctx, article)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
if rowsCreated != 1 {
|
|
||||||
log.Println("Got back the wrong number of rows")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sourceIndex != len(sources) {
|
|
||||||
time.Sleep(time.Second * 30)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cron) CollectRedditPosts() {
|
|
||||||
sources, err := c.repo.Sources.ListBySource(c.ctx, 0, 1000, domain.SourceCollectorReddit)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[Reddit] No sources found to query - %v\r", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, source := range sources {
|
|
||||||
if !source.Enabled {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[Reddit] Checking '%v'...", source.DisplayName)
|
|
||||||
rc := input.NewRedditClient(source)
|
|
||||||
raw, err := rc.GetContent()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
redditArticles := rc.ConvertToArticles(raw)
|
|
||||||
for _, article := range redditArticles {
|
|
||||||
_, err := c.repo.Articles.GetByUrl(c.ctx, article.Url)
|
|
||||||
if err == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rowsAdded, err := c.repo.Articles.CreateFromEntity(c.ctx, article)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to add a new reddit article to the database: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rowsAdded != 1 {
|
|
||||||
log.Printf("no error came back when data was added to the database but the expected row count is wrong")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Print("[Reddit] Done!")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cron) CollectYoutubePosts() {
|
|
||||||
sources, err := c.repo.Sources.ListBySource(c.ctx, 0, 1000, domain.SourceCollectorYoutube)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[Youtube] No sources found to query - %v\r", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for sourceIndex, source := range sources {
|
|
||||||
if !source.Enabled {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[YouTube] Checking '%v'...", source.DisplayName)
|
|
||||||
yc := input.NewYoutubeClient(source)
|
|
||||||
raw, err := yc.GetContent()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, article := range raw {
|
|
||||||
_, err := c.repo.Articles.GetByUrl(c.ctx, article.Url)
|
|
||||||
if err == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rowsAdded, err := c.repo.Articles.CreateFromEntity(c.ctx, article)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to add a new youtube article to the database: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rowsAdded != 1 {
|
|
||||||
log.Printf("no error came back when data was added to the database but the expected row count is wrong")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sourceIndex != len(sources) {
|
|
||||||
time.Sleep(time.Second * 30)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Print("[YouTube] Done!")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cron) CollectFfxivPosts() {
|
|
||||||
sources, err := c.repo.Sources.ListBySource(c.ctx, 0, 1000, domain.SourceCollectorFfxiv)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[FFXIV] No sources found to query - %v\r", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for sourceIndex, source := range sources {
|
|
||||||
if !source.Enabled {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fc := input.NewFFXIVClient(source)
|
|
||||||
items, err := fc.CheckSource()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, article := range items {
|
|
||||||
_, err := c.repo.Articles.GetByUrl(c.ctx, article.Url)
|
|
||||||
if err == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rowsAdded, err := c.repo.Articles.CreateFromEntity(c.ctx, article)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to add a new FFXIV article to the database: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rowsAdded != 1 {
|
|
||||||
log.Printf("no error came back when data was added to the database but the expected row count is wrong")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sourceIndex != len(sources) {
|
|
||||||
time.Sleep(time.Second * 30)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Printf("[FFXIV Done!]")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cron) CollectTwitchPosts() {
|
|
||||||
sources, err := c.repo.Sources.ListBySource(c.ctx, 0, 1000, domain.SourceCollectorTwitch)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[Twitch] No sources found to query - %v\r", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tc, err := input.NewTwitchClient()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tc.Login()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for sourceIndex, source := range sources {
|
|
||||||
if !source.Enabled {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[Twitch] Checking '%v'...", source.DisplayName)
|
|
||||||
tc.ReplaceSourceRecord(source)
|
|
||||||
items, err := tc.GetContent()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, article := range items {
|
|
||||||
_, err := c.repo.Articles.GetByUrl(c.ctx, article.Url)
|
|
||||||
if err == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rowsAdded, err := c.repo.Articles.CreateFromEntity(c.ctx, article)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to add a new Twitch article to the database: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rowsAdded != 1 {
|
|
||||||
log.Printf("no error came back when data was added to the database but the expected row count is wrong")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sourceIndex != len(sources) {
|
|
||||||
time.Sleep(time.Second * 30)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Print("[Twitch] Done!")
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
package cron_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/services"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/cron"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRssPullsCorrectly(t *testing.T) {
|
|
||||||
conn, err := setupInMemoryDb()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
db := services.NewRepositoryService(conn)
|
|
||||||
rowsCreated, err := db.Sources.Create(ctx, domain.SourceCollectorRss, "Gitea - Newsbot.api", "https://git.jamestombleson.com/jtom38/newsbot-api.rss", "rss,gitea,newsbot.api", true)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
if rowsCreated != 1 {
|
|
||||||
t.Error("failed to create the source record")
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
client := cron.NewScheduler(ctx, conn)
|
|
||||||
client.CollectRssPosts()
|
|
||||||
|
|
||||||
articles, err := db.Articles.ListByPage(ctx, 0, 100)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Log(len(articles))
|
|
||||||
}
|
|
30
internal/services/cron/ffxivJob.go
Normal file
30
internal/services/cron/ffxivJob.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/input"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Cron) CheckFfxiv() {
|
||||||
|
sources, err := c.repo.Sources.ListBySource(*c.ctx, 0, 10, domain.SourceCollectorFfxiv)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[FFXIV] No sources found to query - %v\r", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, source := range sources {
|
||||||
|
if !source.Enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fc := input.NewFFXIVClient(source)
|
||||||
|
items, err := fc.CheckSource()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SaveNewArticles(items, domain.SourceCollectorFfxiv)
|
||||||
|
}
|
||||||
|
log.Printf("[FFXIV Done!]")
|
||||||
|
}
|
33
internal/services/cron/redditJob.go
Normal file
33
internal/services/cron/redditJob.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/input"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is the main entry point to query all the reddit services
|
||||||
|
func (c *Cron) CheckReddit() {
|
||||||
|
sources, err := c.repo.Sources.ListBySource(*c.ctx, 0, 100, domain.SourceCollectorReddit)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[Reddit] No sources found to query - %v\r", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, source := range sources {
|
||||||
|
if !source.Enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[Reddit] Checking '%v'...", source.DisplayName)
|
||||||
|
rc := input.NewRedditClient(source)
|
||||||
|
raw, err := rc.GetContent()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
redditArticles := rc.ConvertToArticles(raw)
|
||||||
|
|
||||||
|
c.SaveNewArticles(redditArticles, domain.SourceCollectorReddit)
|
||||||
|
}
|
||||||
|
log.Print("[Reddit] Done!")
|
||||||
|
}
|
34
internal/services/cron/rss.go
Normal file
34
internal/services/cron/rss.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Cron) LoadRssQueue() {
|
||||||
|
sources, err := c.repo.Sources.ListBySource(*c.ctx, 0, 1000, domain.SourceCollectorRss)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, source := range sources {
|
||||||
|
if !source.Enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cron) ListAllSourceRecords(sourceType string) ([]domain.SourceEntity, error) {
|
||||||
|
var records []domain.SourceEntity
|
||||||
|
|
||||||
|
sources, err := c.repo.Sources.ListBySource(*c.ctx, 0, 1000, sourceType)
|
||||||
|
if err != nil {
|
||||||
|
return records, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sources, nil
|
||||||
|
}
|
@ -3,37 +3,57 @@ package cron
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
|
|
||||||
//"git.jamestombleson.com/jtom38/newsbot-api/internal/database"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/services"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Cron struct {
|
type Cron struct {
|
||||||
//Db *database.Queries
|
ctx *context.Context
|
||||||
ctx context.Context
|
|
||||||
timer *cron.Cron
|
timer *cron.Cron
|
||||||
repo services.RepositoryService
|
repo services.RepositoryService
|
||||||
|
//queues services
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewScheduler(ctx context.Context, conn *sql.DB) *Cron {
|
func NewScheduler(ctx context.Context, conn *sql.DB) *Cron {
|
||||||
c := &Cron{
|
c := &Cron{
|
||||||
ctx: ctx,
|
ctx: &ctx,
|
||||||
repo: services.NewRepositoryService(conn),
|
repo: services.NewRepositoryService(conn),
|
||||||
}
|
}
|
||||||
|
|
||||||
timer := cron.New()
|
timer := cron.New()
|
||||||
|
|
||||||
//timer.AddFunc("*/5 * * * *", func() { go CheckCache() })
|
//timer.AddFunc("*/5 * * * *", func() { go CheckCache() })
|
||||||
//features := services.GetEnvConfig()
|
features := services.GetEnvConfig()
|
||||||
|
|
||||||
timer.AddFunc("5 * * * *", func() { go c.CollectRssPosts() })
|
if features.RedditEnabled {
|
||||||
//timer.AddFunc("10 * * * *", c.CollectRedditPosts)
|
timer.AddFunc("5 1-23 * * *", func() { go c.CheckReddit() })
|
||||||
//timer.AddFunc("15 * * * *", c.CheckYoutube)
|
log.Print("[Input] Reddit backend was enabled")
|
||||||
//timer.AddFunc("20 * * * *", c.CheckFfxiv)
|
//go c.CheckReddit()
|
||||||
//timer.AddFunc("25 * * * *", c.CheckTwitch)
|
}
|
||||||
//timer.AddFunc("*/5 * * * *", c.CheckDiscordQueue)
|
|
||||||
|
if features.YoutubeEnabled {
|
||||||
|
timer.AddFunc("10 1-23 * * *", func() { go c.CheckYoutube() })
|
||||||
|
log.Print("[Input] YouTube backend was enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if features.FfxivEnabled {
|
||||||
|
timer.AddFunc("5 5,10,15,20 * * *", func() { go c.CheckFfxiv() })
|
||||||
|
log.Print("[Input] FFXIV backend was enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if features.TwitchEnabled {
|
||||||
|
timer.AddFunc("15 1-23 * * *", func() { go c.CheckTwitch() })
|
||||||
|
log.Print("[Input] Twitch backend was enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
//timer.AddFunc("*/5 * * * *", func() { go c.CheckDiscordQueue() })
|
||||||
|
//log.Print("[Output] Discord Output was enabled")
|
||||||
|
|
||||||
c.timer = timer
|
c.timer = timer
|
||||||
return c
|
return c
|
||||||
@ -47,8 +67,8 @@ func (c *Cron) Stop() {
|
|||||||
c.timer.Stop()
|
c.timer.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* TODO move to the sqlite queue
|
||||||
func (c *Cron) CheckDiscordQueue() {
|
func (c *Cron) CheckDiscordQueue() error {
|
||||||
// Get items from the table
|
// Get items from the table
|
||||||
queueItems, err := c.Db.ListDiscordQueueItems(*c.ctx, 50)
|
queueItems, err := c.Db.ListDiscordQueueItems(*c.ctx, 50)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -119,13 +139,24 @@ func (c *Cron) CheckDiscordQueue() {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//func (c *Cron) addToDiscordQueue(Id uuid.UUID) error {
|
func (c Cron) SaveNewArticles(posts []domain.ArticleEntity, sourceName string) error {
|
||||||
// err := c.Db.CreateDiscordQueue(*c.ctx, database.CreateDiscordQueueParams{
|
for _, item := range posts {
|
||||||
// ID: uuid.New(),
|
_, err := c.repo.Articles.GetByUrl(*c.ctx, item.Url)
|
||||||
// Articleid: Id,
|
if err == nil {
|
||||||
// })
|
// This url is already known, so skip it
|
||||||
// if err != nil {
|
continue
|
||||||
// return err
|
}
|
||||||
// }
|
|
||||||
// return nil
|
// Load the new article in the repository
|
||||||
//}
|
rows, err := c.repo.Articles.Create(*c.ctx, item.SourceID, item.Tags, item.Title, item.Url, item.Thumbnail, item.Description, item.AuthorName, item.AuthorImageUrl, item.PubDate, item.IsVideo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rows != 1 {
|
||||||
|
return errors.New("failed to create a new record for some reason")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -1,38 +1,56 @@
|
|||||||
package cron_test
|
package cron_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/cron"
|
||||||
|
_ "github.com/glebarez/go-sqlite"
|
||||||
"github.com/pressly/goose/v3"
|
"github.com/pressly/goose/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
|
||||||
func TestInvokeTwitch(t *testing.T) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO add database mocks but not sure how to do that yet.
|
|
||||||
func TestCheckReddit(t *testing.T) {
|
func TestCheckReddit(t *testing.T) {
|
||||||
|
db, err := setupInMemoryDb()
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
c := cron.NewScheduler(ctx)
|
c := cron.NewScheduler(ctx, db)
|
||||||
c.Col()
|
c.CheckReddit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckYouTube(t *testing.T) {
|
func TestCheckYouTube(t *testing.T) {
|
||||||
|
db, err := setupInMemoryDb()
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
c := cron.NewScheduler(ctx)
|
c := cron.NewScheduler(ctx, db)
|
||||||
c.CheckYoutube()
|
c.CheckYoutube()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckTwitch(t *testing.T) {
|
func TestCheckTwitch(t *testing.T) {
|
||||||
|
db, err := setupInMemoryDb()
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
c := cron.NewScheduler(ctx)
|
c := cron.NewScheduler(ctx, db)
|
||||||
err := c.CheckTwitch()
|
err = c.CheckTwitch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
func setupInMemoryDb() (*sql.DB, error) {
|
func setupInMemoryDb() (*sql.DB, error) {
|
||||||
db, err := sql.Open("sqlite", ":memory:")
|
db, err := sql.Open("sqlite", ":memory:")
|
||||||
@ -45,7 +63,7 @@ func setupInMemoryDb() (*sql.DB, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = goose.Up(db, "../../database/migrations")
|
err = goose.Up(db, "../database/migrations")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
41
internal/services/cron/twitchJob.go
Normal file
41
internal/services/cron/twitchJob.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/input"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Cron) CheckTwitch() error {
|
||||||
|
sources, err := c.repo.Sources.ListBySource(*c.ctx, 0, 100, domain.SourceCollectorTwitch)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[Twitch] No sources found to query - %v\r", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tc, err := input.NewTwitchClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tc.Login()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, source := range sources {
|
||||||
|
if !source.Enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Printf("[Twitch] Checking '%v'...", source.DisplayName)
|
||||||
|
tc.ReplaceSourceRecord(source)
|
||||||
|
items, err := tc.GetContent()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
c.SaveNewArticles(items, domain.SourceCollectorTwitch)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Print("[Twitch] Done!")
|
||||||
|
return nil
|
||||||
|
}
|
34
internal/services/cron/youtube.go
Normal file
34
internal/services/cron/youtube.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/input"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Cron) CheckYoutube() {
|
||||||
|
// Add call to the db to request youtube sources.
|
||||||
|
sources, err := c.repo.Sources.ListBySource(*c.ctx, 0, 100, domain.SourceCollectorYoutube)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[Youtube] No sources found to query - %v\r", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, source := range sources {
|
||||||
|
if !source.Enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[YouTube] Checking '%v'...", source.DisplayName)
|
||||||
|
|
||||||
|
yc := input.NewYoutubeClient(source)
|
||||||
|
raw, err := yc.GetContent()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
c.SaveNewArticles(raw, domain.SourceCollectorYoutube)
|
||||||
|
}
|
||||||
|
log.Print("[YouTube] Done!")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -4,27 +4,22 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
|
||||||
repositoryservices "git.jamestombleson.com/jtom38/newsbot-api/internal/repositoryServices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RepositoryService struct {
|
type RepositoryService struct {
|
||||||
AlertDiscord repository.AlertDiscordRepo
|
|
||||||
Articles repository.ArticlesRepo
|
Articles repository.ArticlesRepo
|
||||||
DiscordWebHooks repository.DiscordWebHookRepo
|
DiscordWebHooks repository.DiscordWebHookRepo
|
||||||
RefreshTokens repositoryservices.RefreshToken
|
|
||||||
Sources repository.Sources
|
Sources repository.Sources
|
||||||
Users repositoryservices.UserServices
|
Users repository.Users
|
||||||
UserSourceSubscriptions repository.UserSourceRepo
|
RefreshTokens repository.RefreshToken
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRepositoryService(conn *sql.DB) RepositoryService {
|
func NewRepositoryService(conn *sql.DB) RepositoryService {
|
||||||
return RepositoryService{
|
return RepositoryService{
|
||||||
AlertDiscord: repository.NewAlertDiscordRepository(conn),
|
|
||||||
Articles: repository.NewArticleRepository(conn),
|
Articles: repository.NewArticleRepository(conn),
|
||||||
DiscordWebHooks: repository.NewDiscordWebHookRepository(conn),
|
DiscordWebHooks: repository.NewDiscordWebHookRepository(conn),
|
||||||
RefreshTokens: repositoryservices.NewRefreshTokenService(conn),
|
|
||||||
Sources: repository.NewSourceRepository(conn),
|
Sources: repository.NewSourceRepository(conn),
|
||||||
Users: repositoryservices.NewUserService(conn),
|
Users: repository.NewUserRepository(conn),
|
||||||
UserSourceSubscriptions: repository.NewUserSourceRepository(conn),
|
RefreshTokens: repository.NewRefreshTokenRepository(conn),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
package dtoconv
|
package services
|
||||||
|
|
||||||
import (
|
import "git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ArticlesToDto(items []entity.ArticleEntity) []domain.ArticleDto {
|
func ArticlesToDto(items []domain.ArticleEntity) []domain.ArticleDto {
|
||||||
var dtos []domain.ArticleDto
|
var dtos []domain.ArticleDto
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
dtos = append(dtos, ArticleToDto(item))
|
dtos = append(dtos, ArticleToDto(item))
|
||||||
@ -13,7 +10,7 @@ func ArticlesToDto(items []entity.ArticleEntity) []domain.ArticleDto {
|
|||||||
return dtos
|
return dtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func ArticleToDto(item entity.ArticleEntity) domain.ArticleDto {
|
func ArticleToDto(item domain.ArticleEntity) domain.ArticleDto {
|
||||||
return domain.ArticleDto{
|
return domain.ArticleDto{
|
||||||
ID: item.ID,
|
ID: item.ID,
|
||||||
SourceID: item.SourceID,
|
SourceID: item.SourceID,
|
||||||
@ -29,7 +26,7 @@ func ArticleToDto(item entity.ArticleEntity) domain.ArticleDto {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func DiscordWebhooksToDto(items []entity.DiscordWebHookEntity) []domain.DiscordWebHookDto{
|
func DiscordWebhooksToDto(items []domain.DiscordWebHookEntity) []domain.DiscordWebHookDto{
|
||||||
var dtos []domain.DiscordWebHookDto
|
var dtos []domain.DiscordWebHookDto
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
dtos = append(dtos, DiscordWebhookToDto(item))
|
dtos = append(dtos, DiscordWebhookToDto(item))
|
||||||
@ -37,7 +34,7 @@ func DiscordWebhooksToDto(items []entity.DiscordWebHookEntity) []domain.DiscordW
|
|||||||
return dtos
|
return dtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func DiscordWebhookToDto(item entity.DiscordWebHookEntity) domain.DiscordWebHookDto {
|
func DiscordWebhookToDto(item domain.DiscordWebHookEntity) domain.DiscordWebHookDto {
|
||||||
return domain.DiscordWebHookDto{
|
return domain.DiscordWebHookDto{
|
||||||
ID: item.ID,
|
ID: item.ID,
|
||||||
Server: item.Server,
|
Server: item.Server,
|
||||||
@ -47,7 +44,7 @@ func DiscordWebhookToDto(item entity.DiscordWebHookEntity) domain.DiscordWebHook
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SourcesToDto(items []entity.SourceEntity) []domain.SourceDto {
|
func SourcesToDto(items []domain.SourceEntity) []domain.SourceDto {
|
||||||
var dtos []domain.SourceDto
|
var dtos []domain.SourceDto
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
dtos = append(dtos, SourceToDto(item))
|
dtos = append(dtos, SourceToDto(item))
|
||||||
@ -55,7 +52,7 @@ func SourcesToDto(items []entity.SourceEntity) []domain.SourceDto {
|
|||||||
return dtos
|
return dtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func SourceToDto(item entity.SourceEntity) domain.SourceDto {
|
func SourceToDto(item domain.SourceEntity) domain.SourceDto {
|
||||||
return domain.SourceDto{
|
return domain.SourceDto{
|
||||||
ID: item.ID,
|
ID: item.ID,
|
||||||
Source: item.Source,
|
Source: item.Source,
|
@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/go-rod/rod/lib/launcher"
|
"github.com/go-rod/rod/lib/launcher"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/cache"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/cache"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FFXIVClient struct {
|
type FFXIVClient struct {
|
||||||
record entity.SourceEntity
|
record domain.SourceEntity
|
||||||
//SourceID uint
|
//SourceID uint
|
||||||
//Url string
|
//Url string
|
||||||
//Region string
|
//Region string
|
||||||
@ -32,15 +32,15 @@ type FFXIVClient struct {
|
|||||||
cacheGroup string
|
cacheGroup string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFFXIVClient(Record entity.SourceEntity) FFXIVClient {
|
func NewFFXIVClient(Record domain.SourceEntity) FFXIVClient {
|
||||||
return FFXIVClient{
|
return FFXIVClient{
|
||||||
record: Record,
|
record: Record,
|
||||||
cacheGroup: "ffxiv",
|
cacheGroup: "ffxiv",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fc *FFXIVClient) CheckSource() ([]entity.ArticleEntity, error) {
|
func (fc *FFXIVClient) CheckSource() ([]domain.ArticleEntity, error) {
|
||||||
var articles []entity.ArticleEntity
|
var articles []domain.ArticleEntity
|
||||||
|
|
||||||
parser := fc.GetBrowser()
|
parser := fc.GetBrowser()
|
||||||
defer parser.Close()
|
defer parser.Close()
|
||||||
@ -96,7 +96,7 @@ func (fc *FFXIVClient) CheckSource() ([]entity.ArticleEntity, error) {
|
|||||||
return articles, err
|
return articles, err
|
||||||
}
|
}
|
||||||
|
|
||||||
article := entity.ArticleEntity{
|
article := domain.ArticleEntity{
|
||||||
SourceID: fc.record.ID,
|
SourceID: fc.record.ID,
|
||||||
Tags: tags,
|
Tags: tags,
|
||||||
Title: title,
|
Title: title,
|
||||||
|
@ -3,13 +3,12 @@ package input_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
|
||||||
ffxiv "git.jamestombleson.com/jtom38/newsbot-api/internal/services/input"
|
ffxiv "git.jamestombleson.com/jtom38/newsbot-api/internal/services/input"
|
||||||
)
|
)
|
||||||
|
|
||||||
var FFXIVRecord entity.SourceEntity = entity.SourceEntity{
|
var FFXIVRecord domain.SourceEntity = domain.SourceEntity{
|
||||||
ID: 9999,
|
ID: 997,
|
||||||
DisplayName: "Final Fantasy XIV - NA",
|
DisplayName: "Final Fantasy XIV - NA",
|
||||||
Source: domain.SourceCollectorFfxiv,
|
Source: domain.SourceCollectorFfxiv,
|
||||||
Url: "https://na.finalfantasyxiv.com/lodestone/",
|
Url: "https://na.finalfantasyxiv.com/lodestone/",
|
||||||
|
@ -2,7 +2,7 @@ package input
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
@ -35,7 +35,7 @@ func getHttpContent(uri string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -9,15 +9,14 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/services"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services"
|
||||||
"github.com/go-rod/rod"
|
"github.com/go-rod/rod"
|
||||||
"github.com/go-rod/rod/lib/launcher"
|
"github.com/go-rod/rod/lib/launcher"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RedditClient struct {
|
type RedditClient struct {
|
||||||
config RedditConfig
|
config services.Configs
|
||||||
record entity.SourceEntity
|
record domain.SourceEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
type RedditConfig struct {
|
type RedditConfig struct {
|
||||||
@ -26,14 +25,11 @@ type RedditConfig struct {
|
|||||||
PullNSFW string
|
PullNSFW string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRedditClient(Record entity.SourceEntity) *RedditClient {
|
func NewRedditClient(record domain.SourceEntity) *RedditClient {
|
||||||
rc := RedditClient{
|
rc := RedditClient{
|
||||||
record: Record,
|
record: record,
|
||||||
|
config: services.GetEnvConfig(),
|
||||||
}
|
}
|
||||||
cc := services.NewConfig()
|
|
||||||
rc.config.PullHot = cc.GetConfig(services.REDDIT_PULL_HOT)
|
|
||||||
rc.config.PullNSFW = cc.GetConfig(services.REDDIT_PULL_NSFW)
|
|
||||||
rc.config.PullTop = cc.GetConfig(services.REDDIT_PULL_TOP)
|
|
||||||
|
|
||||||
//rc.disableHttp2Client()
|
//rc.disableHttp2Client()
|
||||||
|
|
||||||
@ -87,24 +83,28 @@ func (rc *RedditClient) GetContent() (domain.RedditJsonContent, error) {
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RedditClient) ConvertToArticles(items domain.RedditJsonContent) []entity.ArticleEntity {
|
func (rc *RedditClient) ConvertToArticles(items domain.RedditJsonContent) []domain.ArticleEntity {
|
||||||
var redditArticles []entity.ArticleEntity
|
var redditArticles []domain.ArticleEntity
|
||||||
|
|
||||||
for _, item := range items.Data.Children {
|
for _, item := range items.Data.Children {
|
||||||
var article entity.ArticleEntity
|
var article domain.ArticleEntity
|
||||||
article, err := rc.convertToArticle(item.Data)
|
article, err := rc.convertToArticle(item.Data)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[Reddit] %v", err)
|
log.Printf("[Reddit] %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
redditArticles = append(redditArticles, article)
|
redditArticles = append(redditArticles, article)
|
||||||
}
|
}
|
||||||
|
|
||||||
return redditArticles
|
return redditArticles
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConvertToArticle() will take the reddit model struct and convert them over to Article structs.
|
// ConvertToArticle() will take the reddit model struct and convert them over to Article structs.
|
||||||
// This data can be passed to the database.
|
// This data can be passed to the database.
|
||||||
func (rc *RedditClient) convertToArticle(source domain.RedditPost) (entity.ArticleEntity, error) {
|
func (rc *RedditClient) convertToArticle(source domain.RedditPost) (domain.ArticleEntity, error) {
|
||||||
var item entity.ArticleEntity
|
var item domain.ArticleEntity
|
||||||
|
|
||||||
if source.Content == "" && source.Url != "" {
|
if source.Content == "" && source.Url != "" {
|
||||||
item = rc.convertPicturePost(source)
|
item = rc.convertPicturePost(source)
|
||||||
@ -130,8 +130,8 @@ func (rc *RedditClient) convertToArticle(source domain.RedditPost) (entity.Artic
|
|||||||
return item, nil
|
return item, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RedditClient) convertPicturePost(source domain.RedditPost) entity.ArticleEntity {
|
func (rc *RedditClient) convertPicturePost(source domain.RedditPost) domain.ArticleEntity {
|
||||||
var item = entity.ArticleEntity{
|
var item = domain.ArticleEntity{
|
||||||
SourceID: rc.record.ID,
|
SourceID: rc.record.ID,
|
||||||
Title: source.Title,
|
Title: source.Title,
|
||||||
Tags: fmt.Sprintf("%v", rc.record.Tags),
|
Tags: fmt.Sprintf("%v", rc.record.Tags),
|
||||||
@ -141,13 +141,13 @@ func (rc *RedditClient) convertPicturePost(source domain.RedditPost) entity.Arti
|
|||||||
Thumbnail: source.Thumbnail,
|
Thumbnail: source.Thumbnail,
|
||||||
Description: source.Content,
|
Description: source.Content,
|
||||||
AuthorName: source.Author,
|
AuthorName: source.Author,
|
||||||
AuthorImageUrl: "null",
|
AuthorImageUrl: "",
|
||||||
}
|
}
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RedditClient) convertTextPost(source domain.RedditPost) entity.ArticleEntity {
|
func (rc *RedditClient) convertTextPost(source domain.RedditPost) domain.ArticleEntity {
|
||||||
var item = entity.ArticleEntity{
|
var item = domain.ArticleEntity{
|
||||||
SourceID: rc.record.ID,
|
SourceID: rc.record.ID,
|
||||||
Tags: "a",
|
Tags: "a",
|
||||||
Title: source.Title,
|
Title: source.Title,
|
||||||
@ -159,8 +159,8 @@ func (rc *RedditClient) convertTextPost(source domain.RedditPost) entity.Article
|
|||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RedditClient) convertVideoPost(source domain.RedditPost) entity.ArticleEntity {
|
func (rc *RedditClient) convertVideoPost(source domain.RedditPost) domain.ArticleEntity {
|
||||||
var item = entity.ArticleEntity{
|
var item = domain.ArticleEntity{
|
||||||
SourceID: rc.record.ID,
|
SourceID: rc.record.ID,
|
||||||
Tags: "a",
|
Tags: "a",
|
||||||
Title: source.Title,
|
Title: source.Title,
|
||||||
@ -173,8 +173,8 @@ func (rc *RedditClient) convertVideoPost(source domain.RedditPost) entity.Articl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This post is nothing more then a redirect to another location.
|
// This post is nothing more then a redirect to another location.
|
||||||
func (rc *RedditClient) convertRedirectPost(source domain.RedditPost) entity.ArticleEntity {
|
func (rc *RedditClient) convertRedirectPost(source domain.RedditPost) domain.ArticleEntity {
|
||||||
var item = entity.ArticleEntity{
|
var item = domain.ArticleEntity{
|
||||||
SourceID: rc.record.ID,
|
SourceID: rc.record.ID,
|
||||||
Tags: "a",
|
Tags: "a",
|
||||||
Title: source.Title,
|
Title: source.Title,
|
||||||
|
@ -3,15 +3,14 @@ package input_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/input"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/input"
|
||||||
)
|
)
|
||||||
|
|
||||||
var RedditRecord entity.SourceEntity = entity.SourceEntity{
|
var RedditRecord domain.SourceEntity = domain.SourceEntity{
|
||||||
ID: 9999,
|
ID: 999,
|
||||||
DisplayName: "dadjokes",
|
DisplayName: "dadjokes",
|
||||||
Source: domain.SourceCollectorRss,
|
Source: domain.SourceCollectorReddit,
|
||||||
Url: "https://reddit.com/r/dadjokes",
|
Url: "https://reddit.com/r/dadjokes",
|
||||||
Tags: "reddit, dadjokes",
|
Tags: "reddit, dadjokes",
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,19 @@
|
|||||||
package input
|
package input
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/cache"
|
||||||
"github.com/mmcdole/gofeed"
|
"github.com/mmcdole/gofeed"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FeedInput interface {
|
|
||||||
GetArticles() (entity.ArticleEntity, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type rssClient struct {
|
type rssClient struct {
|
||||||
SourceRecord entity.SourceEntity
|
SourceRecord domain.SourceEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRssClient(sourceRecord entity.SourceEntity) rssClient {
|
func NewRssClient(sourceRecord domain.SourceEntity) rssClient {
|
||||||
client := rssClient{
|
client := rssClient{
|
||||||
SourceRecord: sourceRecord,
|
SourceRecord: sourceRecord,
|
||||||
}
|
}
|
||||||
@ -23,55 +21,39 @@ func NewRssClient(sourceRecord entity.SourceEntity) rssClient {
|
|||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc rssClient) GetArticles() ([]entity.ArticleEntity, error) {
|
//func (rc rssClient) ReplaceSourceRecord(source model.Sources) {
|
||||||
parser := gofeed.NewParser()
|
//rc.SourceRecord = source
|
||||||
feed, err := parser.ParseURL(rc.SourceRecord.Url)
|
//}
|
||||||
|
|
||||||
|
func (rc rssClient) getCacheGroup() string {
|
||||||
|
return fmt.Sprintf("rss-%v", rc.SourceRecord.DisplayName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc rssClient) GetContent() error {
|
||||||
|
feed, err := rc.PullFeed()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheClient := cache.NewCacheClient(rc.getCacheGroup())
|
||||||
|
|
||||||
|
for _, item := range feed.Items {
|
||||||
|
log.Println(item)
|
||||||
|
|
||||||
|
cacheClient.FindByValue(item.Link)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc rssClient) PullFeed() (*gofeed.Feed, error) {
|
||||||
|
feedUri := fmt.Sprintf("%v", rc.SourceRecord.Url)
|
||||||
|
fp := gofeed.NewParser()
|
||||||
|
feed, err := fp.ParseURL(feedUri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceTags := strings.Split(rc.SourceRecord.Tags, ",")
|
return feed, nil
|
||||||
var articles []entity.ArticleEntity
|
|
||||||
for _, post := range feed.Items {
|
|
||||||
article := entity.ArticleEntity{
|
|
||||||
SourceID: rc.SourceRecord.ID,
|
|
||||||
Title: post.Title,
|
|
||||||
Description: post.Content,
|
|
||||||
Url: post.Link,
|
|
||||||
PubDate: *post.PublishedParsed,
|
|
||||||
//AuthorName: post.Authors[0].Email,
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(post.Authors) != 0 {
|
|
||||||
article.AuthorName = post.Authors[0].Email
|
|
||||||
}
|
|
||||||
|
|
||||||
var postTags []string
|
|
||||||
postTags = append(postTags, sourceTags...)
|
|
||||||
postTags = append(postTags, post.Categories...)
|
|
||||||
article.Tags = strings.Join(postTags, ",")
|
|
||||||
|
|
||||||
/*
|
|
||||||
pageContent, err := getHttpContent(article.Url)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
htmlNode, err := html.Parse(bytes.NewReader(pageContent))
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
htmlNode.
|
|
||||||
|
|
||||||
fmt.Println(htmlNode)
|
|
||||||
*/
|
|
||||||
|
|
||||||
if post.Image == nil {
|
|
||||||
article.Thumbnail = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
articles = append(articles, article)
|
|
||||||
}
|
|
||||||
|
|
||||||
return articles, nil
|
|
||||||
}
|
}
|
||||||
|
@ -3,16 +3,15 @@ package input_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/input"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/input"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rssRecord = entity.SourceEntity{
|
var rssRecord = domain.SourceEntity{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
DisplayName: "ArsTechnica",
|
DisplayName: "ArsTechnica",
|
||||||
Url: "https://feeds.arstechnica.com/arstechnica/index",
|
|
||||||
Source: domain.SourceCollectorRss,
|
Source: domain.SourceCollectorRss,
|
||||||
|
Url: "https://feeds.arstechnica.com/arstechnica/index",
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRssClientConstructor(t *testing.T) {
|
func TestRssClientConstructor(t *testing.T) {
|
||||||
@ -21,23 +20,12 @@ func TestRssClientConstructor(t *testing.T) {
|
|||||||
|
|
||||||
func TestRssGetFeed(t *testing.T) {
|
func TestRssGetFeed(t *testing.T) {
|
||||||
client := input.NewRssClient(rssRecord)
|
client := input.NewRssClient(rssRecord)
|
||||||
_, err := client.GetArticles()
|
feed, err := client.PullFeed()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}
|
if len(feed.Items) >= 0 {
|
||||||
|
t.Error("failed to collect items from the fees")
|
||||||
func TestRssAgainstGita(t *testing.T) {
|
|
||||||
client := input.NewRssClient(entity.SourceEntity{
|
|
||||||
ID: 2,
|
|
||||||
DisplayName: "Gitea - Newsbot-api",
|
|
||||||
Source: domain.SourceCollectorRss,
|
|
||||||
Url: "https://git.jamestombleson.com/jtom38/newsbot-api.rss",
|
|
||||||
Tags: "rss,gitea,newsbot-api",
|
|
||||||
})
|
|
||||||
_, err := client.GetArticles()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/services"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services"
|
||||||
"github.com/nicklaw5/helix/v2"
|
"github.com/nicklaw5/helix/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TwitchClient struct {
|
type TwitchClient struct {
|
||||||
SourceRecord entity.SourceEntity
|
SourceRecord domain.SourceEntity
|
||||||
|
|
||||||
// config
|
// config
|
||||||
monitorClips string
|
monitorClips string
|
||||||
@ -71,7 +71,7 @@ func initTwitchApi(ClientId string, ClientSecret string) (helix.Client, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This will let you replace the bound source record to keep the same session alive.
|
// This will let you replace the bound source record to keep the same session alive.
|
||||||
func (tc *TwitchClient) ReplaceSourceRecord(source entity.SourceEntity) {
|
func (tc *TwitchClient) ReplaceSourceRecord(source domain.SourceEntity) {
|
||||||
tc.SourceRecord = source
|
tc.SourceRecord = source
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,8 +86,8 @@ func (tc *TwitchClient) Login() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *TwitchClient) GetContent() ([]entity.ArticleEntity, error) {
|
func (tc *TwitchClient) GetContent() ([]domain.ArticleEntity, error) {
|
||||||
var items []entity.ArticleEntity
|
var items []domain.ArticleEntity
|
||||||
|
|
||||||
user, err := tc.GetUserDetails()
|
user, err := tc.GetUserDetails()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -100,7 +100,7 @@ func (tc *TwitchClient) GetContent() ([]entity.ArticleEntity, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, video := range posts {
|
for _, video := range posts {
|
||||||
var article entity.ArticleEntity
|
var article domain.ArticleEntity
|
||||||
|
|
||||||
AuthorName, err := tc.ExtractAuthor(video)
|
AuthorName, err := tc.ExtractAuthor(video)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -4,18 +4,17 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/input"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/input"
|
||||||
)
|
)
|
||||||
|
|
||||||
var TwitchSourceRecord = entity.SourceEntity{
|
var TwitchSourceRecord = domain.SourceEntity{
|
||||||
ID: 9999,
|
ID: 9999,
|
||||||
DisplayName: "nintendo",
|
DisplayName: "nintendo",
|
||||||
Source: domain.SourceCollectorTwitch,
|
Source: domain.SourceCollectorTwitch,
|
||||||
}
|
}
|
||||||
|
|
||||||
var TwitchInvalidRecord = entity.SourceEntity{
|
var TwitchInvalidRecord = domain.SourceEntity{
|
||||||
ID: 9999,
|
ID: 9999,
|
||||||
DisplayName: "EvilNintendo",
|
DisplayName: "EvilNintendo",
|
||||||
Source: domain.SourceCollectorTwitch,
|
Source: domain.SourceCollectorTwitch,
|
||||||
|
@ -11,11 +11,11 @@ import (
|
|||||||
"github.com/go-rod/rod/lib/launcher"
|
"github.com/go-rod/rod/lib/launcher"
|
||||||
"github.com/mmcdole/gofeed"
|
"github.com/mmcdole/gofeed"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type YoutubeClient struct {
|
type YoutubeClient struct {
|
||||||
record entity.SourceEntity
|
record domain.SourceEntity
|
||||||
|
|
||||||
// internal variables at time of collection
|
// internal variables at time of collection
|
||||||
channelID string
|
channelID string
|
||||||
@ -25,7 +25,7 @@ type YoutubeClient struct {
|
|||||||
//debug bool
|
//debug bool
|
||||||
|
|
||||||
// cache config
|
// cache config
|
||||||
//cacheGroup string
|
cacheGroup string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -36,16 +36,17 @@ var (
|
|||||||
|
|
||||||
const YOUTUBE_FEED_URL string = "https://www.youtube.com/feeds/videos.xml?channel_id="
|
const YOUTUBE_FEED_URL string = "https://www.youtube.com/feeds/videos.xml?channel_id="
|
||||||
|
|
||||||
func NewYoutubeClient(Record entity.SourceEntity) YoutubeClient {
|
func NewYoutubeClient(Record domain.SourceEntity) YoutubeClient {
|
||||||
yc := YoutubeClient{
|
yc := YoutubeClient{
|
||||||
record: Record,
|
record: Record,
|
||||||
|
cacheGroup: "youtube",
|
||||||
}
|
}
|
||||||
return yc
|
return yc
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckSource will go and run all the commands needed to process a source.
|
// CheckSource will go and run all the commands needed to process a source.
|
||||||
func (yc *YoutubeClient) GetContent() ([]entity.ArticleEntity, error) {
|
func (yc *YoutubeClient) GetContent() ([]domain.ArticleEntity, error) {
|
||||||
var items []entity.ArticleEntity
|
var items []domain.ArticleEntity
|
||||||
docParser, err := yc.GetParser(yc.record.Url)
|
docParser, err := yc.GetParser(yc.record.Url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return items, err
|
return items, err
|
||||||
@ -245,7 +246,7 @@ func (yc *YoutubeClient) CheckUriCache(uri *string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (yc *YoutubeClient) ConvertToArticle(item *gofeed.Item) entity.ArticleEntity {
|
func (yc *YoutubeClient) ConvertToArticle(item *gofeed.Item) domain.ArticleEntity {
|
||||||
parser, err := yc.GetParser(item.Link)
|
parser, err := yc.GetParser(item.Link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[YouTube] Unable to process %v, submit this link as an issue.\n", item.Link)
|
log.Printf("[YouTube] Unable to process %v, submit this link as an issue.\n", item.Link)
|
||||||
@ -263,7 +264,7 @@ func (yc *YoutubeClient) ConvertToArticle(item *gofeed.Item) entity.ArticleEntit
|
|||||||
log.Printf("[YouTube] %v", msg)
|
log.Printf("[YouTube] %v", msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
var article = entity.ArticleEntity{
|
var article = domain.ArticleEntity{
|
||||||
SourceID: yc.record.ID,
|
SourceID: yc.record.ID,
|
||||||
Tags: tags,
|
Tags: tags,
|
||||||
Title: item.Title,
|
Title: item.Title,
|
||||||
|
@ -3,15 +3,14 @@ package input_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/input"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/input"
|
||||||
)
|
)
|
||||||
|
|
||||||
var YouTubeRecord = entity.SourceEntity{
|
var YouTubeRecord domain.SourceEntity = domain.SourceEntity{
|
||||||
ID: 9999,
|
ID: 999,
|
||||||
DisplayName: "dadjokes",
|
DisplayName: "dadjokes",
|
||||||
Source: domain.SourceCollectorReddit,
|
Source: domain.SourceCollectorYoutube,
|
||||||
Url: "https://youtube.com/gamegrumps",
|
Url: "https://youtube.com/gamegrumps",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/database"
|
||||||
//"git.jamestombleson.com/jtom38/newsbot-api/internal/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type discordField struct {
|
type discordField struct {
|
||||||
@ -64,11 +63,11 @@ const (
|
|||||||
|
|
||||||
type Discord struct {
|
type Discord struct {
|
||||||
Subscriptions []string
|
Subscriptions []string
|
||||||
article entity.ArticleEntity
|
article database.Article
|
||||||
Message *DiscordMessage
|
Message *DiscordMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDiscordWebHookMessage(Article entity.ArticleEntity) Discord {
|
func NewDiscordWebHookMessage(Article database.Article) Discord {
|
||||||
return Discord{
|
return Discord{
|
||||||
article: Article,
|
article: Article,
|
||||||
}
|
}
|
||||||
|
@ -5,19 +5,22 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
//"git.jamestombleson.com/jtom38/newsbot-api/internal/database"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/database"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/output"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/output"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
article entity.ArticleEntity = entity.ArticleEntity{
|
article database.Article = database.Article{
|
||||||
ID: 999,
|
ID: uuid.New(),
|
||||||
SourceID: 1,
|
Sourceid: uuid.New(),
|
||||||
Tags: "unit, testing",
|
Tags: "unit, testing",
|
||||||
Title: "Demo",
|
Title: "Demo",
|
||||||
Url: "https://github.com/jtom38/newsbot.collector.api",
|
Url: "https://github.com/jtom38/newsbot.collector.api",
|
||||||
|
//Pubdate: time.Now(),
|
||||||
|
Videoheight: 0,
|
||||||
|
Videowidth: 0,
|
||||||
Description: "Hello World",
|
Description: "Hello World",
|
||||||
}
|
}
|
||||||
blank string = ""
|
blank string = ""
|
||||||
|
26
internal/services/queue.go
Normal file
26
internal/services/queue.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
|
"github.com/maragudk/goqite"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type queues struct {
|
||||||
|
repos RepositoryService
|
||||||
|
|
||||||
|
RssCollection goqite.Queue
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQueues(conn *sql.DB) queues {
|
||||||
|
return queues{
|
||||||
|
repos: NewRepositoryService(conn),
|
||||||
|
RssCollection: *goqite.New(goqite.NewOpts{
|
||||||
|
DB: conn,
|
||||||
|
Name: domain.QueueRssCollection,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
16
makefile
16
makefile
@ -6,26 +6,20 @@ build: ## builds the application with the current go runtime
|
|||||||
~/go/bin/swag f
|
~/go/bin/swag f
|
||||||
~/go/bin/swag init -g cmd/server.go
|
~/go/bin/swag init -g cmd/server.go
|
||||||
go build cmd/server.go
|
go build cmd/server.go
|
||||||
ls -lh server
|
|
||||||
|
|
||||||
docker-build: ## Generates the docker image
|
docker-build: ## Generates the docker image
|
||||||
docker build -t "newsbot.collector.api" .
|
docker build -t "newsbot.collector.api" .
|
||||||
docker image ls | grep newsbot.collector.api
|
docker image ls | grep newsbot.collector.api
|
||||||
|
|
||||||
migrate-dev: ## Apply sql migrations to dev db
|
migrate-dev: ## Apply sql migrations to dev db
|
||||||
goose -dir "./internal/database/migrations" sqlite3 ./cmd/newsbot.db up
|
goose -dir "./internal/database/migrations" postgres "user=postgres password=postgres dbname=postgres sslmode=disable" up
|
||||||
|
|
||||||
migrate-dev-down: ## revert sql migrations to dev db
|
migrate-dev-down: ## revert sql migrations to dev db
|
||||||
goose -dir "./internal/database/migrations" sqlite3 ./cmd/newsbot.db down
|
goose -dir "./internal/database/migrations" postgres "user=postgres password=postgres dbname=postgres sslmode=disable" down
|
||||||
|
|
||||||
swag: ## Generates the swagger documentation with the swag tool
|
swag: ## Generates the swagger documentation with the swag tool
|
||||||
~/go/bin/swag f -g cmd/server.go
|
~/go/bin/swag f
|
||||||
~/go/bin/swag init -g cmd/server.go
|
~/go/bin/swag init -g cmd/server.go
|
||||||
go run tools/swaggertoopenapi/main.go
|
|
||||||
oapi-codegen -config api/client.yaml docs/openapi.json
|
|
||||||
mv api.gen.go api/
|
|
||||||
|
|
||||||
install-tools: ## Installs the required tools for this project
|
gensql: ## Generates SQL code with sqlc
|
||||||
go install github.com/swaggo/swag/cmd/swag@v1.8.1
|
sqlc generate
|
||||||
go install github.com/pressly/goose/v3/cmd/goose@latest
|
|
||||||
go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.3.0
|
|
@ -1,17 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/tools/swaggertoopenapi/src"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := src.ConvertToOpenApi("docs/swagger.json", "docs/openapi.json", true)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
package src
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ApplicationJson = "application/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ConvertToOpenApi(filePath, outputPath string, force bool) error {
|
|
||||||
// check if the file already exists
|
|
||||||
exists, err := os.Stat(outputPath)
|
|
||||||
if exists != nil {
|
|
||||||
// if force was given, attempt to remove it
|
|
||||||
if force {
|
|
||||||
err = os.Remove(outputPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("'%s' already exists and force was not approved", outputPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Reading '%s'", filePath)
|
|
||||||
content, err := os.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := http.Client{}
|
|
||||||
req, err := http.NewRequest(http.MethodPost, "https://converter.swagger.io/api/convert", bytes.NewReader(content))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Add("Accept", ApplicationJson)
|
|
||||||
req.Header.Add("Content-Type", ApplicationJson)
|
|
||||||
|
|
||||||
log.Println("Converting to OpenAPI spec")
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Reading the response")
|
|
||||||
respContent, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Writing converted data to openapi.json")
|
|
||||||
writer, err := os.Create(outputPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer writer.Close()
|
|
||||||
|
|
||||||
_, err = writer.Write(respContent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package src_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/tools/swaggerToOpenapi/src"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConvertOnline(t *testing.T) {
|
|
||||||
err := src.ConvertToOpenApi("../../docs/swagger.json", "../../docs/openapi.json", true)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user