features/jwt #7
60
.drone.yaml
Normal file
60
.drone.yaml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
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
|
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
// 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": "."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
15
Dockerfile
15
Dockerfile
@ -5,14 +5,10 @@ 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 i
|
RUN /go/bin/swag init -g cmd/server.go
|
||||||
|
|
||||||
# Always build the latest sql queries
|
#RUN go build .
|
||||||
RUN go install github.com/kyleconroy/sqlc/cmd/sqlc@latest
|
#RUN go install github.com/pressly/goose/v3/cmd/goose@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
|
||||||
|
|
||||||
@ -21,8 +17,7 @@ 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/collector /app
|
COPY --from=build /app/server /app
|
||||||
COPY --from=build /go/bin/goose /app
|
COPY ./internal/database/migrations/ /app/migrations
|
||||||
COPY ./database/migrations/ /app/migrations
|
|
||||||
|
|
||||||
CMD [ "/app/collector" ]
|
CMD [ "/app/collector" ]
|
@ -3,7 +3,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
_ "github.com/glebarez/go-sqlite"
|
_ "github.com/glebarez/go-sqlite"
|
||||||
"github.com/pressly/goose/v3"
|
"github.com/pressly/goose/v3"
|
||||||
@ -17,6 +19,10 @@ 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()
|
||||||
|
|
||||||
@ -30,14 +36,9 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = goose.SetDialect("sqlite3")
|
err = migrateDatabase(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
fmt.Print(err)
|
||||||
}
|
|
||||||
|
|
||||||
err = goose.Up(db, "../internal/database/migrations")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c := cron.NewScheduler(ctx, db)
|
c := cron.NewScheduler(ctx, db)
|
||||||
@ -51,3 +52,39 @@ func main() {
|
|||||||
|
|
||||||
server.Router.Start(":8081")
|
server.Router.Start(":8081")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
768
docs/docs.go
768
docs/docs.go
@ -18,6 +18,11 @@ const docTemplate = `{
|
|||||||
"paths": {
|
"paths": {
|
||||||
"/v1/articles": {
|
"/v1/articles": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -57,6 +62,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/articles/by/sourceid": {
|
"/v1/articles/by/sourceid": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -103,6 +113,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/articles/{ID}": {
|
"/v1/articles/{ID}": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -143,6 +158,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/articles/{ID}/details": {
|
"/v1/articles/{ID}/details": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -183,6 +203,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/discord/webhooks": {
|
"/v1/discord/webhooks": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -214,6 +239,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/discord/webhooks/by/serverAndChannel": {
|
"/v1/discord/webhooks/by/serverAndChannel": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -261,6 +291,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/discord/webhooks/new": {
|
"/v1/discord/webhooks/new": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"DiscordWebhook"
|
"DiscordWebhook"
|
||||||
],
|
],
|
||||||
@ -349,6 +384,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/discord/webhooks/{ID}/disable": {
|
"/v1/discord/webhooks/{ID}/disable": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"DiscordWebhook"
|
"DiscordWebhook"
|
||||||
],
|
],
|
||||||
@ -386,6 +426,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/discord/webhooks/{ID}/enable": {
|
"/v1/discord/webhooks/{ID}/enable": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"DiscordWebhook"
|
"DiscordWebhook"
|
||||||
],
|
],
|
||||||
@ -404,6 +449,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/discord/webhooks/{id}": {
|
"/v1/discord/webhooks/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -442,48 +492,13 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/v1/queue/discord/webhooks": {
|
|
||||||
"get": {
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Queue"
|
|
||||||
],
|
|
||||||
"summary": "Returns the top 100 entries from the queue to be processed.",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "ok",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ListDiscordWebHooksQueueResults"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/settings/{key}": {
|
|
||||||
"get": {
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Settings"
|
|
||||||
],
|
|
||||||
"summary": "Returns a object based on the Key that was given.",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Settings Key value",
|
|
||||||
"name": "key",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/sources": {
|
"/v1/sources": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -517,6 +532,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/sources/by/source": {
|
"/v1/sources/by/source": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -563,6 +583,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/sources/by/sourceAndName": {
|
"/v1/sources/by/sourceAndName": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -610,6 +635,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/sources/new/reddit": {
|
"/v1/sources/new/reddit": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Source"
|
"Source"
|
||||||
],
|
],
|
||||||
@ -654,6 +684,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/sources/new/rss": {
|
"/v1/sources/new/rss": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Source"
|
"Source"
|
||||||
],
|
],
|
||||||
@ -698,6 +733,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/sources/new/twitch": {
|
"/v1/sources/new/twitch": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Source"
|
"Source"
|
||||||
],
|
],
|
||||||
@ -716,6 +756,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/sources/new/youtube": {
|
"/v1/sources/new/youtube": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Source"
|
"Source"
|
||||||
],
|
],
|
||||||
@ -741,6 +786,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/sources/{id}": {
|
"/v1/sources/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -779,24 +829,53 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Source"
|
"Source"
|
||||||
],
|
],
|
||||||
"summary": "Marks a source as deleted based on its ID value.",
|
"summary": "Marks a source as deleted based on its ID value.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "integer",
|
||||||
"description": "id",
|
"description": "id",
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {}
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "ok",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.SourcesResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.BaseResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.BaseResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/v1/sources/{id}/disable": {
|
"/v1/sources/{id}/disable": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Source"
|
"Source"
|
||||||
],
|
],
|
||||||
@ -834,6 +913,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/v1/sources/{id}/enable": {
|
"/v1/sources/{id}/enable": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Source"
|
"Source"
|
||||||
],
|
],
|
||||||
@ -869,165 +953,234 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/v1/subscriptions": {
|
"/v1/users/login": {
|
||||||
"get": {
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Subscription"
|
|
||||||
],
|
|
||||||
"summary": "Returns the top 100 entries from the queue to be processed.",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "ok",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ListSubscriptions"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Unable to reach SQL.",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ApiError"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Failed to process data from SQL.",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ApiError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/subscriptions/by/SourceId": {
|
|
||||||
"get": {
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Subscription"
|
|
||||||
],
|
|
||||||
"summary": "Returns the top 100 entries from the queue to be processed.",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "id",
|
|
||||||
"name": "id",
|
|
||||||
"in": "query",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "ok",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ListSubscriptions"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/subscriptions/by/discordId": {
|
|
||||||
"get": {
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Subscription"
|
|
||||||
],
|
|
||||||
"summary": "Returns the top 100 entries from the queue to be processed.",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "id",
|
|
||||||
"name": "id",
|
|
||||||
"in": "query",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "ok",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ListSubscriptions"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Unable to reach SQL or Data problems",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ApiError"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Data problems",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ApiError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/subscriptions/details": {
|
|
||||||
"get": {
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Subscription"
|
|
||||||
],
|
|
||||||
"summary": "Returns the top 50 entries with full deatils on the source and output.",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "ok",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ListSubscriptionDetails"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/subscriptions/discord/webhook/delete": {
|
|
||||||
"delete": {
|
|
||||||
"tags": [
|
|
||||||
"Subscription"
|
|
||||||
],
|
|
||||||
"summary": "Removes a Discord WebHook Subscription based on the Subscription ID.",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "id",
|
|
||||||
"name": "id",
|
|
||||||
"in": "query",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/subscriptions/discord/webhook/new": {
|
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"produces": [
|
||||||
"Subscription"
|
"application/json"
|
||||||
],
|
],
|
||||||
"summary": "Creates a new subscription to link a post from a Source to a DiscordWebHook.",
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Logs into the API and returns a bearer token if successful",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "discordWebHookId",
|
"name": "password",
|
||||||
"name": "discordWebHookId",
|
"in": "formData"
|
||||||
"in": "query",
|
|
||||||
"required": true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "sourceId",
|
"name": "username",
|
||||||
"name": "sourceId",
|
"in": "formData"
|
||||||
"in": "query",
|
|
||||||
"required": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {}
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/users/refreshToken": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Generates a new token",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "body",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/users/register": {
|
||||||
|
"post": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Creates a new user",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "password",
|
||||||
|
"in": "formData"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "username",
|
||||||
|
"in": "formData"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/users/scopes/add": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Adds a new scope to a user account",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "body",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.UpdateScopesRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/users/scopes/remove": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Adds a new scope to a user account",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "body",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.UpdateScopesRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1149,6 +1302,34 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.LoginResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"refreshToken": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.RefreshTokenRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"refreshToken": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.SourceDto": {
|
"domain.SourceDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -1186,211 +1367,30 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"models.ArticleDetailsDto": {
|
"domain.UpdateScopesRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"scopes"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"authorImage": {
|
"scopes": {
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"authorName": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"pubdate": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"source": {
|
|
||||||
"$ref": "#/definitions/models.SourceDto"
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"thumbnail": {
|
"username": {
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"video": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"videoHeight": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"videoWidth": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"models.DiscordQueueDetailsDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"article": {
|
|
||||||
"$ref": "#/definitions/models.ArticleDetailsDto"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"models.DiscordWebHooksDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"ID": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"channel": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"enabled": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"server": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"models.SourceDto": {
|
"securityDefinitions": {
|
||||||
"type": "object",
|
"Bearer": {
|
||||||
"properties": {
|
"description": "Type \"Bearer\" followed by a space and JWT token.",
|
||||||
"deleted": {
|
"type": "apiKey",
|
||||||
"type": "boolean"
|
"name": "Authorization",
|
||||||
},
|
"in": "header"
|
||||||
"enabled": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"site": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"source": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"models.SubscriptionDetailsDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"discordwebhook": {
|
|
||||||
"$ref": "#/definitions/models.DiscordWebHooksDto"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"source": {
|
|
||||||
"$ref": "#/definitions/models.SourceDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"models.SubscriptionDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"discordwebhookid": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"sourceid": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"v1.ApiError": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"v1.ListDiscordWebHooksQueueResults": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"payload": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/models.DiscordQueueDetailsDto"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"v1.ListSubscriptionDetails": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"payload": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/models.SubscriptionDetailsDto"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"v1.ListSubscriptions": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"payload": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/models.SubscriptionDto"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
@ -9,6 +9,11 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"/v1/articles": {
|
"/v1/articles": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -48,6 +53,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/articles/by/sourceid": {
|
"/v1/articles/by/sourceid": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -94,6 +104,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/articles/{ID}": {
|
"/v1/articles/{ID}": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -134,6 +149,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/articles/{ID}/details": {
|
"/v1/articles/{ID}/details": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -174,6 +194,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/discord/webhooks": {
|
"/v1/discord/webhooks": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -205,6 +230,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/discord/webhooks/by/serverAndChannel": {
|
"/v1/discord/webhooks/by/serverAndChannel": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -252,6 +282,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/discord/webhooks/new": {
|
"/v1/discord/webhooks/new": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"DiscordWebhook"
|
"DiscordWebhook"
|
||||||
],
|
],
|
||||||
@ -340,6 +375,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/discord/webhooks/{ID}/disable": {
|
"/v1/discord/webhooks/{ID}/disable": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"DiscordWebhook"
|
"DiscordWebhook"
|
||||||
],
|
],
|
||||||
@ -377,6 +417,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/discord/webhooks/{ID}/enable": {
|
"/v1/discord/webhooks/{ID}/enable": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"DiscordWebhook"
|
"DiscordWebhook"
|
||||||
],
|
],
|
||||||
@ -395,6 +440,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/discord/webhooks/{id}": {
|
"/v1/discord/webhooks/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -433,48 +483,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/v1/queue/discord/webhooks": {
|
|
||||||
"get": {
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Queue"
|
|
||||||
],
|
|
||||||
"summary": "Returns the top 100 entries from the queue to be processed.",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "ok",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ListDiscordWebHooksQueueResults"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/settings/{key}": {
|
|
||||||
"get": {
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Settings"
|
|
||||||
],
|
|
||||||
"summary": "Returns a object based on the Key that was given.",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Settings Key value",
|
|
||||||
"name": "key",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/sources": {
|
"/v1/sources": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -508,6 +523,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/sources/by/source": {
|
"/v1/sources/by/source": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -554,6 +574,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/sources/by/sourceAndName": {
|
"/v1/sources/by/sourceAndName": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -601,6 +626,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/sources/new/reddit": {
|
"/v1/sources/new/reddit": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Source"
|
"Source"
|
||||||
],
|
],
|
||||||
@ -645,6 +675,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/sources/new/rss": {
|
"/v1/sources/new/rss": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Source"
|
"Source"
|
||||||
],
|
],
|
||||||
@ -689,6 +724,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/sources/new/twitch": {
|
"/v1/sources/new/twitch": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Source"
|
"Source"
|
||||||
],
|
],
|
||||||
@ -707,6 +747,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/sources/new/youtube": {
|
"/v1/sources/new/youtube": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Source"
|
"Source"
|
||||||
],
|
],
|
||||||
@ -732,6 +777,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/sources/{id}": {
|
"/v1/sources/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@ -770,24 +820,53 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Source"
|
"Source"
|
||||||
],
|
],
|
||||||
"summary": "Marks a source as deleted based on its ID value.",
|
"summary": "Marks a source as deleted based on its ID value.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "integer",
|
||||||
"description": "id",
|
"description": "id",
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {}
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "ok",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.SourcesResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.BaseResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.BaseResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/v1/sources/{id}/disable": {
|
"/v1/sources/{id}/disable": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Source"
|
"Source"
|
||||||
],
|
],
|
||||||
@ -825,6 +904,11 @@
|
|||||||
},
|
},
|
||||||
"/v1/sources/{id}/enable": {
|
"/v1/sources/{id}/enable": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Source"
|
"Source"
|
||||||
],
|
],
|
||||||
@ -860,165 +944,234 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/v1/subscriptions": {
|
"/v1/users/login": {
|
||||||
"get": {
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Subscription"
|
|
||||||
],
|
|
||||||
"summary": "Returns the top 100 entries from the queue to be processed.",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "ok",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ListSubscriptions"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Unable to reach SQL.",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ApiError"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Failed to process data from SQL.",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ApiError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/subscriptions/by/SourceId": {
|
|
||||||
"get": {
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Subscription"
|
|
||||||
],
|
|
||||||
"summary": "Returns the top 100 entries from the queue to be processed.",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "id",
|
|
||||||
"name": "id",
|
|
||||||
"in": "query",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "ok",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ListSubscriptions"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/subscriptions/by/discordId": {
|
|
||||||
"get": {
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Subscription"
|
|
||||||
],
|
|
||||||
"summary": "Returns the top 100 entries from the queue to be processed.",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "id",
|
|
||||||
"name": "id",
|
|
||||||
"in": "query",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "ok",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ListSubscriptions"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Unable to reach SQL or Data problems",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ApiError"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Data problems",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ApiError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/subscriptions/details": {
|
|
||||||
"get": {
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Subscription"
|
|
||||||
],
|
|
||||||
"summary": "Returns the top 50 entries with full deatils on the source and output.",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "ok",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1.ListSubscriptionDetails"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/subscriptions/discord/webhook/delete": {
|
|
||||||
"delete": {
|
|
||||||
"tags": [
|
|
||||||
"Subscription"
|
|
||||||
],
|
|
||||||
"summary": "Removes a Discord WebHook Subscription based on the Subscription ID.",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "id",
|
|
||||||
"name": "id",
|
|
||||||
"in": "query",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/subscriptions/discord/webhook/new": {
|
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"produces": [
|
||||||
"Subscription"
|
"application/json"
|
||||||
],
|
],
|
||||||
"summary": "Creates a new subscription to link a post from a Source to a DiscordWebHook.",
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Logs into the API and returns a bearer token if successful",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "discordWebHookId",
|
"name": "password",
|
||||||
"name": "discordWebHookId",
|
"in": "formData"
|
||||||
"in": "query",
|
|
||||||
"required": true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "sourceId",
|
"name": "username",
|
||||||
"name": "sourceId",
|
"in": "formData"
|
||||||
"in": "query",
|
|
||||||
"required": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {}
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/users/refreshToken": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Generates a new token",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "body",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/users/register": {
|
||||||
|
"post": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Creates a new user",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "password",
|
||||||
|
"in": "formData"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "username",
|
||||||
|
"in": "formData"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/users/scopes/add": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Adds a new scope to a user account",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "body",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.UpdateScopesRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/users/scopes/remove": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Adds a new scope to a user account",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "body",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.UpdateScopesRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1140,6 +1293,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.LoginResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"refreshToken": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.RefreshTokenRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"refreshToken": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.SourceDto": {
|
"domain.SourceDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -1177,211 +1358,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"models.ArticleDetailsDto": {
|
"domain.UpdateScopesRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"scopes"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"authorImage": {
|
"scopes": {
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"authorName": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"pubdate": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"source": {
|
|
||||||
"$ref": "#/definitions/models.SourceDto"
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"thumbnail": {
|
"username": {
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"video": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"videoHeight": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"videoWidth": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"models.DiscordQueueDetailsDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"article": {
|
|
||||||
"$ref": "#/definitions/models.ArticleDetailsDto"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"models.DiscordWebHooksDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"ID": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"channel": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"enabled": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"server": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"models.SourceDto": {
|
"securityDefinitions": {
|
||||||
"type": "object",
|
"Bearer": {
|
||||||
"properties": {
|
"description": "Type \"Bearer\" followed by a space and JWT token.",
|
||||||
"deleted": {
|
"type": "apiKey",
|
||||||
"type": "boolean"
|
"name": "Authorization",
|
||||||
},
|
"in": "header"
|
||||||
"enabled": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"site": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"source": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"models.SubscriptionDetailsDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"discordwebhook": {
|
|
||||||
"$ref": "#/definitions/models.DiscordWebHooksDto"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"source": {
|
|
||||||
"$ref": "#/definitions/models.SourceDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"models.SubscriptionDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"discordwebhookid": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"sourceid": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"v1.ApiError": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"v1.ListDiscordWebHooksQueueResults": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"payload": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/models.DiscordQueueDetailsDto"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"v1.ListSubscriptionDetails": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"payload": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/models.SubscriptionDetailsDto"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"v1.ListSubscriptions": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"payload": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/models.SubscriptionDto"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -78,6 +78,24 @@ definitions:
|
|||||||
$ref: '#/definitions/domain.DiscordWebHookDto'
|
$ref: '#/definitions/domain.DiscordWebHookDto'
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
|
domain.LoginResponse:
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
refreshToken:
|
||||||
|
type: string
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
domain.RefreshTokenRequest:
|
||||||
|
properties:
|
||||||
|
refreshToken:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
domain.SourceDto:
|
domain.SourceDto:
|
||||||
properties:
|
properties:
|
||||||
enabled:
|
enabled:
|
||||||
@ -102,139 +120,16 @@ definitions:
|
|||||||
$ref: '#/definitions/domain.SourceDto'
|
$ref: '#/definitions/domain.SourceDto'
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
models.ArticleDetailsDto:
|
domain.UpdateScopesRequest:
|
||||||
properties:
|
properties:
|
||||||
authorImage:
|
scopes:
|
||||||
type: string
|
|
||||||
authorName:
|
|
||||||
type: string
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
pubdate:
|
|
||||||
type: string
|
|
||||||
source:
|
|
||||||
$ref: '#/definitions/models.SourceDto'
|
|
||||||
tags:
|
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
thumbnail:
|
username:
|
||||||
type: string
|
type: string
|
||||||
title:
|
required:
|
||||||
type: string
|
- scopes
|
||||||
url:
|
|
||||||
type: string
|
|
||||||
video:
|
|
||||||
type: string
|
|
||||||
videoHeight:
|
|
||||||
type: integer
|
|
||||||
videoWidth:
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
models.DiscordQueueDetailsDto:
|
|
||||||
properties:
|
|
||||||
article:
|
|
||||||
$ref: '#/definitions/models.ArticleDetailsDto'
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
models.DiscordWebHooksDto:
|
|
||||||
properties:
|
|
||||||
ID:
|
|
||||||
type: string
|
|
||||||
channel:
|
|
||||||
type: string
|
|
||||||
enabled:
|
|
||||||
type: boolean
|
|
||||||
server:
|
|
||||||
type: string
|
|
||||||
url:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
models.SourceDto:
|
|
||||||
properties:
|
|
||||||
deleted:
|
|
||||||
type: boolean
|
|
||||||
enabled:
|
|
||||||
type: boolean
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
site:
|
|
||||||
type: string
|
|
||||||
source:
|
|
||||||
type: string
|
|
||||||
tags:
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
url:
|
|
||||||
type: string
|
|
||||||
value:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
models.SubscriptionDetailsDto:
|
|
||||||
properties:
|
|
||||||
discordwebhook:
|
|
||||||
$ref: '#/definitions/models.DiscordWebHooksDto'
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
source:
|
|
||||||
$ref: '#/definitions/models.SourceDto'
|
|
||||||
type: object
|
|
||||||
models.SubscriptionDto:
|
|
||||||
properties:
|
|
||||||
discordwebhookid:
|
|
||||||
type: string
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
sourceid:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
v1.ApiError:
|
|
||||||
properties:
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
status:
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
v1.ListDiscordWebHooksQueueResults:
|
|
||||||
properties:
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
payload:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/models.DiscordQueueDetailsDto'
|
|
||||||
type: array
|
|
||||||
status:
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
v1.ListSubscriptionDetails:
|
|
||||||
properties:
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
payload:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/models.SubscriptionDetailsDto'
|
|
||||||
type: array
|
|
||||||
status:
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
v1.ListSubscriptions:
|
|
||||||
properties:
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
payload:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/models.SubscriptionDto'
|
|
||||||
type: array
|
|
||||||
status:
|
|
||||||
type: integer
|
|
||||||
type: object
|
type: object
|
||||||
info:
|
info:
|
||||||
contact: {}
|
contact: {}
|
||||||
@ -263,6 +158,8 @@ 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
|
||||||
@ -289,6 +186,8 @@ 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
|
||||||
@ -315,6 +214,8 @@ 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
|
||||||
@ -345,6 +246,8 @@ 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:
|
||||||
@ -366,6 +269,8 @@ paths:
|
|||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
summary: Returns the top 100
|
summary: Returns the top 100
|
||||||
tags:
|
tags:
|
||||||
- DiscordWebhook
|
- DiscordWebhook
|
||||||
@ -414,6 +319,8 @@ paths:
|
|||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
summary: Disables a Webhook from being used.
|
summary: Disables a Webhook from being used.
|
||||||
tags:
|
tags:
|
||||||
- DiscordWebhook
|
- DiscordWebhook
|
||||||
@ -426,6 +333,8 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: integer
|
type: integer
|
||||||
responses: {}
|
responses: {}
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
summary: Enables a source to continue processing.
|
summary: Enables a source to continue processing.
|
||||||
tags:
|
tags:
|
||||||
- DiscordWebhook
|
- DiscordWebhook
|
||||||
@ -452,6 +361,8 @@ paths:
|
|||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
$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
|
- DiscordWebhook
|
||||||
@ -483,6 +394,8 @@ paths:
|
|||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
$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
|
- DiscordWebhook
|
||||||
@ -517,35 +430,11 @@ paths:
|
|||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
$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
|
- DiscordWebhook
|
||||||
/v1/queue/discord/webhooks:
|
|
||||||
get:
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: ok
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/v1.ListDiscordWebHooksQueueResults'
|
|
||||||
summary: Returns the top 100 entries from the queue to be processed.
|
|
||||||
tags:
|
|
||||||
- Queue
|
|
||||||
/v1/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
|
|
||||||
/v1/sources:
|
/v1/sources:
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
@ -564,6 +453,8 @@ paths:
|
|||||||
description: Unable to reach SQL or Data problems
|
description: Unable to reach SQL or Data problems
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
summary: Lists the top 50 records
|
summary: Lists the top 50 records
|
||||||
tags:
|
tags:
|
||||||
- Source
|
- Source
|
||||||
@ -590,6 +481,8 @@ 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
|
||||||
@ -599,8 +492,22 @@ paths:
|
|||||||
in: path
|
in: path
|
||||||
name: id
|
name: id
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: integer
|
||||||
responses: {}
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ok
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.SourcesResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
|
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
|
||||||
@ -625,6 +532,8 @@ paths:
|
|||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
summary: Disables a source from processing.
|
summary: Disables a source from processing.
|
||||||
tags:
|
tags:
|
||||||
- Source
|
- Source
|
||||||
@ -649,6 +558,8 @@ paths:
|
|||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
summary: Enables a source to continue processing.
|
summary: Enables a source to continue processing.
|
||||||
tags:
|
tags:
|
||||||
- Source
|
- Source
|
||||||
@ -679,6 +590,8 @@ 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 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
|
||||||
@ -710,6 +623,8 @@ 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
|
||||||
@ -739,6 +654,8 @@ paths:
|
|||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
$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
|
||||||
@ -768,6 +685,8 @@ paths:
|
|||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.BaseResponse'
|
$ref: '#/definitions/domain.BaseResponse'
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
summary: Creates a new rss source to monitor.
|
summary: Creates a new rss source to monitor.
|
||||||
tags:
|
tags:
|
||||||
- Source
|
- Source
|
||||||
@ -780,6 +699,8 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
responses: {}
|
responses: {}
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
summary: Creates a new twitch source to monitor.
|
summary: Creates a new twitch source to monitor.
|
||||||
tags:
|
tags:
|
||||||
- Source
|
- Source
|
||||||
@ -797,112 +718,158 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
responses: {}
|
responses: {}
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
summary: Creates a new youtube source to monitor.
|
summary: Creates a new youtube source to monitor.
|
||||||
tags:
|
tags:
|
||||||
- Source
|
- Source
|
||||||
/v1/subscriptions:
|
/v1/users/login:
|
||||||
get:
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: ok
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/v1.ListSubscriptions'
|
|
||||||
"400":
|
|
||||||
description: Unable to reach SQL.
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/v1.ApiError'
|
|
||||||
"500":
|
|
||||||
description: Failed to process data from SQL.
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/v1.ApiError'
|
|
||||||
summary: Returns the top 100 entries from the queue to be processed.
|
|
||||||
tags:
|
|
||||||
- Subscription
|
|
||||||
/v1/subscriptions/by/SourceId:
|
|
||||||
get:
|
|
||||||
parameters:
|
|
||||||
- description: id
|
|
||||||
in: query
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: ok
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/v1.ListSubscriptions'
|
|
||||||
summary: Returns the top 100 entries from the queue to be processed.
|
|
||||||
tags:
|
|
||||||
- Subscription
|
|
||||||
/v1/subscriptions/by/discordId:
|
|
||||||
get:
|
|
||||||
parameters:
|
|
||||||
- description: id
|
|
||||||
in: query
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: ok
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/v1.ListSubscriptions'
|
|
||||||
"400":
|
|
||||||
description: Unable to reach SQL or Data problems
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/v1.ApiError'
|
|
||||||
"500":
|
|
||||||
description: Data problems
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/v1.ApiError'
|
|
||||||
summary: Returns the top 100 entries from the queue to be processed.
|
|
||||||
tags:
|
|
||||||
- Subscription
|
|
||||||
/v1/subscriptions/details:
|
|
||||||
get:
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: ok
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/v1.ListSubscriptionDetails'
|
|
||||||
summary: Returns the top 50 entries with full deatils on the source and output.
|
|
||||||
tags:
|
|
||||||
- Subscription
|
|
||||||
/v1/subscriptions/discord/webhook/delete:
|
|
||||||
delete:
|
|
||||||
parameters:
|
|
||||||
- description: id
|
|
||||||
in: query
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
responses: {}
|
|
||||||
summary: Removes a Discord WebHook Subscription based on the Subscription ID.
|
|
||||||
tags:
|
|
||||||
- Subscription
|
|
||||||
/v1/subscriptions/discord/webhook/new:
|
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
- description: discordWebHookId
|
- in: formData
|
||||||
in: query
|
name: password
|
||||||
name: discordWebHookId
|
|
||||||
required: true
|
|
||||||
type: string
|
type: string
|
||||||
- description: sourceId
|
- in: formData
|
||||||
in: query
|
name: username
|
||||||
name: sourceId
|
|
||||||
required: true
|
|
||||||
type: string
|
type: string
|
||||||
responses: {}
|
produces:
|
||||||
summary: Creates a new subscription to link a post from a Source to a DiscordWebHook.
|
- application/json
|
||||||
|
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'
|
||||||
|
summary: Logs into the API and returns a bearer token if successful
|
||||||
tags:
|
tags:
|
||||||
- Subscription
|
- Users
|
||||||
|
/v1/users/refreshToken:
|
||||||
|
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:
|
||||||
|
"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'
|
||||||
|
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
|
||||||
|
securityDefinitions:
|
||||||
|
Bearer:
|
||||||
|
description: Type "Bearer" followed by a space and JWT token.
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
type: apiKey
|
||||||
swagger: "2.0"
|
swagger: "2.0"
|
||||||
|
2
go.mod
2
go.mod
@ -6,9 +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/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
|
||||||
|
4
go.sum
4
go.sum
@ -35,6 +35,8 @@ github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keL
|
|||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
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=
|
||||||
@ -60,6 +62,8 @@ 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=
|
||||||
|
@ -18,23 +18,12 @@ CREATE TABLE Articles (
|
|||||||
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,
|
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
|
||||||
@ -72,21 +61,30 @@ CREATE Table Sources (
|
|||||||
Tags TEXT NOT NULL
|
Tags TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE Subscriptions (
|
CREATE TABLE UserSourceSubscriptions (
|
||||||
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,
|
DeletedAt DATETIME NOT NULL,
|
||||||
DiscordWebHookID NUMBER NOT NULL,
|
UserID 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,
|
SourceID NUMBER NOT NULL,
|
||||||
UserID 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,
|
DeletedAt DATETIME NOT NULL,
|
||||||
Name TEXT NOT NULL,
|
Name TEXT NOT NULL,
|
||||||
Hash TEXT NOT NULL,
|
Hash TEXT NOT NULL,
|
||||||
Scopes TEXT NOT NULL
|
Scopes TEXT NOT NULL
|
||||||
@ -96,7 +94,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,
|
DeletedAt DATETIME NOT NULL,
|
||||||
Username TEXT NOT NULL,
|
Username TEXT NOT NULL,
|
||||||
Token TEXT NOT NULL
|
Token TEXT NOT NULL
|
||||||
);
|
);
|
||||||
@ -106,13 +104,12 @@ 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 Settings;
|
|
||||||
Drop Table Sources;
|
|
||||||
DROP TABLE Subscriptions;
|
|
||||||
DROP TABLE Users;
|
|
||||||
DROP TABLE RefreshTokens;
|
DROP TABLE RefreshTokens;
|
||||||
|
Drop Table Sources;
|
||||||
|
DROP TABLE Users;
|
||||||
|
DROP TABLE UserSourceSubscriptions;
|
||||||
-- +goose StatementEnd
|
-- +goose StatementEnd
|
||||||
|
@ -4,6 +4,18 @@ 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
|
||||||
@ -35,6 +47,7 @@ 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
|
||||||
@ -83,16 +96,28 @@ 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 {
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,129 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,5 +1,10 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
|
type LoginFormRequest struct {
|
||||||
|
Username string `form:"username"`
|
||||||
|
Password string `form:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
type GetSourceBySourceAndNameParamRequest struct {
|
type GetSourceBySourceAndNameParamRequest struct {
|
||||||
Name string `query:"name"`
|
Name string `query:"name"`
|
||||||
Source string `query:"source"`
|
Source string `query:"source"`
|
||||||
@ -11,3 +16,12 @@ type NewSourceParamRequest struct {
|
|||||||
Tags string `query:"tags"`
|
Tags string `query:"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"`
|
||||||
|
}
|
@ -5,6 +5,13 @@ type BaseResponse struct {
|
|||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LoginResponse struct {
|
||||||
|
BaseResponse
|
||||||
|
Token string `json:"token"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
RefreshToken string `json:"refreshToken"`
|
||||||
|
}
|
||||||
|
|
||||||
type ArticleResponse struct {
|
type ArticleResponse struct {
|
||||||
BaseResponse
|
BaseResponse
|
||||||
Payload []ArticleDto `json:"payload"`
|
Payload []ArticleDto `json:"payload"`
|
||||||
|
14
internal/domain/scopes.go
Normal file
14
internal/domain/scopes.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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"
|
||||||
|
)
|
@ -18,7 +18,13 @@ import (
|
|||||||
// @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 {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeArticleRead)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
resp := domain.ArticleResponse{
|
resp := domain.ArticleResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
@ -32,7 +38,7 @@ 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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Payload = services.ArticlesToDto(res)
|
resp.Payload = services.ArticlesToDto(res)
|
||||||
@ -48,7 +54,13 @@ func (s *Handler) listArticles(c echo.Context) error {
|
|||||||
// @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 {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeArticleRead)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
p := domain.ArticleResponse{
|
p := domain.ArticleResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
@ -58,7 +70,7 @@ func (s *Handler) getArticle(c echo.Context) error {
|
|||||||
id := c.Param("ID")
|
id := c.Param("ID")
|
||||||
idNumber, err := strconv.Atoi(id)
|
idNumber, err := strconv.Atoi(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.WriteError(c, err, http.StatusBadRequest)
|
return 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))
|
||||||
@ -82,29 +94,33 @@ func (s *Handler) getArticle(c echo.Context) error {
|
|||||||
// @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 {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeArticleRead)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
p := domain.ArticleDetailedResponse{
|
p := domain.ArticleDetailedResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
},
|
},
|
||||||
Payload: domain.ArticleAndSourceModel{
|
Payload: domain.ArticleAndSourceModel{},
|
||||||
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := strconv.Atoi(c.Param("ID"))
|
id, err := strconv.Atoi(c.Param("ID"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.WriteError(c, err, http.StatusBadRequest)
|
return 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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return 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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Payload.Article = services.ArticleToDto(article)
|
p.Payload.Article = services.ArticleToDto(article)
|
||||||
@ -123,7 +139,13 @@ func (s *Handler) getArticleDetails(c echo.Context) error {
|
|||||||
// @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 {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeArticleRead)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
p := domain.ArticleResponse{
|
p := domain.ArticleResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
@ -132,7 +154,7 @@ func (s *Handler) ListArticlesBySourceId(c echo.Context) error {
|
|||||||
|
|
||||||
id, err := strconv.Atoi(c.QueryParam("id"))
|
id, err := strconv.Atoi(c.QueryParam("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.WriteError(c, err, http.StatusBadRequest)
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the page number is missing, default to 0
|
// if the page number is missing, default to 0
|
||||||
|
264
internal/handler/v1/auth.go
Normal file
264
internal/handler/v1/auth.go
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
|
||||||
|
|
||||||
|
"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 200 {object} domain.BaseResponse
|
||||||
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
|
func (h *Handler) AuthRegister(c echo.Context) error {
|
||||||
|
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 {
|
||||||
|
return h.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if exists.Username == username {
|
||||||
|
return h.InternalServerErrorResponse(c, ErrUsernameAlreadyExists)
|
||||||
|
}
|
||||||
|
|
||||||
|
//password := c.QueryParam("password")
|
||||||
|
err = h.repo.Users.CheckPasswordForRequirements(password)
|
||||||
|
if err != nil {
|
||||||
|
return h.WriteError(c, err, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.repo.Users.Create(c.Request().Context(), username, password, domain.ScopeArticleRead)
|
||||||
|
if err != nil {
|
||||||
|
return h.InternalServerErrorResponse(c, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusCreated, domain.BaseResponse{
|
||||||
|
Message: "OK",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @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.BaseResponse
|
||||||
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
|
func (h *Handler) AuthLogin(c echo.Context) error {
|
||||||
|
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 {
|
||||||
|
return h.InternalServerErrorResponse(c, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the hash matches
|
||||||
|
err = h.repo.Users.DoesPasswordMatchHash(c.Request().Context(), username, password)
|
||||||
|
if err != nil {
|
||||||
|
return h.InternalServerErrorResponse(c, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, userScopes, user.ID, expiresAt)
|
||||||
|
if err != nil {
|
||||||
|
return h.InternalServerErrorResponse(c, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh, err := h.repo.RefreshTokens.Create(c.Request().Context(), username)
|
||||||
|
if err != nil {
|
||||||
|
return h.InternalServerErrorResponse(c, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, domain.LoginResponse{
|
||||||
|
BaseResponse: domain.BaseResponse{
|
||||||
|
Message: "OK",
|
||||||
|
},
|
||||||
|
Token: jwt,
|
||||||
|
Type: "Bearer",
|
||||||
|
RefreshToken: refresh,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) createAdminToken(c echo.Context, password string) error {
|
||||||
|
// if the admin token is blank, then the admin wanted this disabled.
|
||||||
|
// this will fail right away and not progress.
|
||||||
|
if h.config.AdminSecret == "" {
|
||||||
|
return h.InternalServerErrorResponse(c, ErrUserNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.config.AdminSecret != password {
|
||||||
|
return h.UnauthorizedResponse(c, ErrUserNotFound)
|
||||||
|
}
|
||||||
|
var userScopes []string
|
||||||
|
userScopes = append(userScopes, domain.ScopeAll)
|
||||||
|
|
||||||
|
token, err := h.generateJwt("admin", h.config.ServerAddress, userScopes, -1)
|
||||||
|
if err != nil {
|
||||||
|
return h.InternalServerErrorResponse(c, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, domain.LoginResponse{
|
||||||
|
BaseResponse: domain.BaseResponse{
|
||||||
|
Message: "OK",
|
||||||
|
},
|
||||||
|
Token: token,
|
||||||
|
Type: "Bearer",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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/refreshToken [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 {
|
||||||
|
_, err := h.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate)
|
||||||
|
if err != nil {
|
||||||
|
return h.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the context for the refresh token
|
||||||
|
var request domain.RefreshTokenRequest
|
||||||
|
err = (&echo.DefaultBinder{}).BindBody(c, &request)
|
||||||
|
if err != nil {
|
||||||
|
return h.InternalServerErrorResponse(c, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.repo.RefreshTokens.IsRequestValid(c.Request().Context(), request.Username, request.RefreshToken)
|
||||||
|
if err != nil {
|
||||||
|
return h.InternalServerErrorResponse(c, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := h.repo.Users.GetUser(c.Request().Context(), request.Username)
|
||||||
|
if err != nil {
|
||||||
|
return h.InternalServerErrorResponse(c, err.Error())
|
||||||
|
}
|
||||||
|
userScopes := strings.Split(user.Scopes, ",")
|
||||||
|
|
||||||
|
jwt, err := h.generateJwtWithExp(request.Username, h.config.ServerAddress, userScopes, user.ID, time.Now().Add(time.Hour*48))
|
||||||
|
if err != nil {
|
||||||
|
return h.InternalServerErrorResponse(c, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
newRefreshToken, err := h.repo.RefreshTokens.Create(c.Request().Context(), request.Username)
|
||||||
|
if err != nil {
|
||||||
|
return h.InternalServerErrorResponse(c, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, domain.LoginResponse{
|
||||||
|
BaseResponse: domain.BaseResponse{
|
||||||
|
Message: "OK",
|
||||||
|
},
|
||||||
|
Token: jwt,
|
||||||
|
Type: "Bearer",
|
||||||
|
RefreshToken: newRefreshToken,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @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 {
|
||||||
|
_, err := h.ValidateJwtToken(c, domain.ScopeAll)
|
||||||
|
if err != nil {
|
||||||
|
return h.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
request := domain.UpdateScopesRequest{}
|
||||||
|
err = (&echo.DefaultBinder{}).BindBody(c, &request)
|
||||||
|
if err != nil {
|
||||||
|
return h.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.repo.Users.AddScopes(c.Request().Context(), request.Username, request.Scopes)
|
||||||
|
if err != nil {
|
||||||
|
return h.InternalServerErrorResponse(c, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, domain.BaseResponse{
|
||||||
|
Message: "OK",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @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 {
|
||||||
|
token, err := h.getJwtTokenFromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
return h.WriteError(c, err, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = token.IsValid(domain.ScopeAll)
|
||||||
|
if err != nil {
|
||||||
|
return h.WriteError(c, err, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
request := domain.UpdateScopesRequest{}
|
||||||
|
err = (&echo.DefaultBinder{}).BindBody(c, &request)
|
||||||
|
if err != nil {
|
||||||
|
h.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.repo.Users.RemoveScopes(c.Request().Context(), request.Username, request.Scopes)
|
||||||
|
if err != nil {
|
||||||
|
return h.InternalServerErrorResponse(c, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, domain.BaseResponse{
|
||||||
|
Message: "OK",
|
||||||
|
})
|
||||||
|
}
|
@ -18,7 +18,13 @@ import (
|
|||||||
// @Success 200 {object} domain.DiscordWebhookResponse
|
// @Success 200 {object} domain.DiscordWebhookResponse
|
||||||
// @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) ListDiscordWebHooks(c echo.Context) error {
|
func (s *Handler) ListDiscordWebHooks(c echo.Context) error {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebhookRead)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
p := domain.DiscordWebhookResponse{
|
p := domain.DiscordWebhookResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
@ -42,7 +48,13 @@ func (s *Handler) ListDiscordWebHooks(c echo.Context) error {
|
|||||||
// @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
|
||||||
|
// @Security Bearer
|
||||||
func (s *Handler) GetDiscordWebHooksById(c echo.Context) error {
|
func (s *Handler) GetDiscordWebHooksById(c echo.Context) error {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebhookRead)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
p := domain.DiscordWebhookResponse{
|
p := domain.DiscordWebhookResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
@ -51,12 +63,12 @@ func (s *Handler) GetDiscordWebHooksById(c echo.Context) error {
|
|||||||
|
|
||||||
id, err := strconv.Atoi(c.Param("ID"))
|
id, err := strconv.Atoi(c.Param("ID"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.WriteError(c, err, http.StatusBadRequest)
|
return 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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
var dtos []domain.DiscordWebHookDto
|
var dtos []domain.DiscordWebHookDto
|
||||||
dtos = append(dtos, services.DiscordWebhookToDto(res))
|
dtos = append(dtos, services.DiscordWebhookToDto(res))
|
||||||
@ -74,7 +86,13 @@ func (s *Handler) GetDiscordWebHooksById(c echo.Context) error {
|
|||||||
// @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
|
||||||
|
// @Security Bearer
|
||||||
func (s *Handler) GetDiscordWebHooksByServerAndChannel(c echo.Context) error {
|
func (s *Handler) GetDiscordWebHooksByServerAndChannel(c echo.Context) error {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebhookRead)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
p := domain.DiscordWebhookResponse{
|
p := domain.DiscordWebhookResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
@ -83,17 +101,17 @@ func (s *Handler) GetDiscordWebHooksByServerAndChannel(c echo.Context) error {
|
|||||||
|
|
||||||
_server := c.QueryParam("server")
|
_server := c.QueryParam("server")
|
||||||
if _server == "" {
|
if _server == "" {
|
||||||
s.WriteMessage(c, "server was not defined", http.StatusBadRequest)
|
return s.WriteMessage(c, "server was not defined", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
_channel := c.QueryParam("channel")
|
_channel := c.QueryParam("channel")
|
||||||
if _channel == "" {
|
if _channel == "" {
|
||||||
s.WriteMessage(c, "channel was not defined", http.StatusBadRequest)
|
return s.WriteMessage(c, "channel was not defined", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Payload = services.DiscordWebhooksToDto(res)
|
p.Payload = services.DiscordWebhooksToDto(res)
|
||||||
@ -110,7 +128,13 @@ func (s *Handler) GetDiscordWebHooksByServerAndChannel(c echo.Context) error {
|
|||||||
// @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
|
||||||
|
// @Security Bearer
|
||||||
func (s *Handler) NewDiscordWebHook(c echo.Context) error {
|
func (s *Handler) NewDiscordWebHook(c echo.Context) error {
|
||||||
|
token, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
_url := c.QueryParam("url")
|
_url := c.QueryParam("url")
|
||||||
_server := c.QueryParam("server")
|
_server := c.QueryParam("server")
|
||||||
_channel := c.QueryParam("channel")
|
_channel := c.QueryParam("channel")
|
||||||
@ -136,18 +160,23 @@ func (s *Handler) NewDiscordWebHook(c echo.Context) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := s.repo.DiscordWebHooks.Create(c.Request().Context(), _url, _server, _channel, true)
|
user, err := s.repo.Users.GetUser(c.Request().Context(), token.UserName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteMessage(c, ErrUserUnknown, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := s.repo.DiscordWebHooks.Create(c.Request().Context(), user.ID, _url, _server, _channel, true)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rows != 1 {
|
if rows != 1 {
|
||||||
s.WriteMessage(c, "data was not written to database", http.StatusInternalServerError)
|
return s.WriteMessage(c, "data was not written to database", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtos []domain.DiscordWebHookDto
|
var dtos []domain.DiscordWebHookDto
|
||||||
@ -169,7 +198,13 @@ func (s *Handler) NewDiscordWebHook(c echo.Context) error {
|
|||||||
// @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
|
||||||
|
// @Security Bearer
|
||||||
func (s *Handler) disableDiscordWebHook(c echo.Context) error {
|
func (s *Handler) disableDiscordWebHook(c echo.Context) error {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
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, domain.BaseResponse{
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
@ -178,25 +213,29 @@ func (s *Handler) disableDiscordWebHook(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check to make sure we can find the record
|
// Check to make sure we can find the record
|
||||||
_, err = s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
record, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.UserID != s.GetUserIdFromJwtToken(c) {
|
||||||
|
return s.WriteMessage(c, ErrYouDontOwnTheRecord, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure we got a row updated
|
// make sure we got a row updated
|
||||||
if updated != 1 {
|
if updated != 1 {
|
||||||
s.WriteMessage(c, "unexpected number of updates found", http.StatusInternalServerError)
|
return s.WriteMessage(c, "unexpected number of updates found", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtos []domain.DiscordWebHookDto
|
var dtos []domain.DiscordWebHookDto
|
||||||
@ -214,30 +253,40 @@ func (s *Handler) disableDiscordWebHook(c echo.Context) error {
|
|||||||
// @Param id path int true "id"
|
// @Param id path int true "id"
|
||||||
// @Tags DiscordWebhook
|
// @Tags DiscordWebhook
|
||||||
// @Router /v1/discord/webhooks/{ID}/enable [post]
|
// @Router /v1/discord/webhooks/{ID}/enable [post]
|
||||||
|
// @Security Bearer
|
||||||
func (s *Handler) enableDiscordWebHook(c echo.Context) error {
|
func (s *Handler) enableDiscordWebHook(c echo.Context) error {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
id, err := strconv.Atoi(c.Param("ID"))
|
id, err := strconv.Atoi(c.Param("ID"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.WriteError(c, err, http.StatusBadRequest)
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to make sure we can find the record
|
// Check to make sure we can find the record
|
||||||
_, err = s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
record, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.WriteError(c, err, http.StatusBadRequest)
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.UserID != s.GetUserIdFromJwtToken(c) {
|
||||||
|
return s.WriteMessage(c, ErrYouDontOwnTheRecord, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if updated != 1 {
|
if updated != 1 {
|
||||||
s.WriteMessage(c, "unexpected number of updates found", http.StatusInternalServerError)
|
return s.WriteMessage(c, ErrFailedToUpdateRecord, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtos []domain.DiscordWebHookDto
|
var dtos []domain.DiscordWebHookDto
|
||||||
@ -259,17 +308,26 @@ func (s *Handler) enableDiscordWebHook(c echo.Context) error {
|
|||||||
// @Failure 400 {object} domain.BaseResponse
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
// @Failure 500 {object} domain.BaseResponse
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
func (s *Handler) deleteDiscordWebHook(c echo.Context) error {
|
func (s *Handler) deleteDiscordWebHook(c echo.Context) error {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
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, err.Error())
|
return c.JSON(http.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to make sure we can find the record
|
// Check to make sure we can find the record
|
||||||
_, err = s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
record, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, err.Error())
|
return c.JSON(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if record.UserID != s.GetUserIdFromJwtToken(c) {
|
||||||
|
return s.WriteMessage(c, ErrYouDontOwnTheRecord, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
@ -277,12 +335,12 @@ func (s *Handler) deleteDiscordWebHook(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if updated != 1 {
|
if updated != 1 {
|
||||||
s.WriteMessage(c, "unexpected number of updates found", http.StatusInternalServerError)
|
return s.WriteMessage(c, ErrFailedToUpdateRecord, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtos []domain.DiscordWebHookDto
|
var dtos []domain.DiscordWebHookDto
|
||||||
|
@ -3,21 +3,23 @@ package v1
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"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/labstack/echo/v4/middleware"
|
||||||
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/docs"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/database"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/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,8 +28,13 @@ 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"
|
||||||
)
|
)
|
||||||
@ -47,6 +54,13 @@ func NewServer(ctx context.Context, configs services.Configs, conn *sql.DB) *Han
|
|||||||
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.RemoveTrailingSlash())
|
||||||
router.Pre(middleware.Logger())
|
router.Pre(middleware.Logger())
|
||||||
@ -55,6 +69,7 @@ func NewServer(ctx context.Context, configs services.Configs, conn *sql.DB) *Han
|
|||||||
|
|
||||||
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)
|
||||||
@ -76,6 +91,7 @@ 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)
|
||||||
@ -88,26 +104,26 @@ func NewServer(ctx context.Context, configs services.Configs, conn *sql.DB) *Han
|
|||||||
sources.POST("/:ID/disable", s.disableSource)
|
sources.POST("/:ID/disable", s.disableSource)
|
||||||
sources.POST("/:ID/enable", s.enableSource)
|
sources.POST("/:ID/enable", s.enableSource)
|
||||||
|
|
||||||
subs := v1.Group("/subscriptions")
|
users := v1.Group("/users")
|
||||||
subs.GET("/", s.ListSubscriptions)
|
users.POST("/login", s.AuthLogin)
|
||||||
subs.GET("/details", s.ListSubscriptionDetails)
|
users.POST("/register", s.AuthRegister)
|
||||||
subs.GET("/by/discordId", s.GetSubscriptionsByDiscordId)
|
users.Use(echojwt.WithConfig(jwtConfig))
|
||||||
subs.GET("/by/sourceId", s.GetSubscriptionsBySourceId)
|
users.POST("/scopes/add", s.AddScopes)
|
||||||
subs.POST("/discord/webhook/new", s.newDiscordWebHookSubscription)
|
users.POST("/scopes/remove", s.RemoveScopes)
|
||||||
subs.DELETE("/discord/webhook/delete", s.DeleteDiscordWebHookSubscription)
|
users.POST("/refreshToken", s.RefreshJwtToken)
|
||||||
|
|
||||||
s.Router = router
|
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 {
|
//type ApiError struct {
|
||||||
*ApiStatusModel
|
// *ApiStatusModel
|
||||||
}
|
//}
|
||||||
|
|
||||||
func (s *Handler) WriteError(c echo.Context, errMessage error, HttpStatusCode int) error {
|
func (s *Handler) WriteError(c echo.Context, errMessage error, HttpStatusCode int) error {
|
||||||
return c.JSON(HttpStatusCode, domain.BaseResponse{
|
return c.JSON(HttpStatusCode, domain.BaseResponse{
|
||||||
@ -120,3 +136,53 @@ func (s *Handler) WriteMessage(c echo.Context, msg string, HttpStatusCode int) e
|
|||||||
Message: msg,
|
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 {
|
||||||
|
s.WriteMessage(c, ErrJwtMissing, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = token.hasExpired()
|
||||||
|
if err != nil {
|
||||||
|
return JwtToken{}, errors.New(ErrJwtExpired)
|
||||||
|
//s.WriteMessage(c, ErrJwtExpired, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = token.hasScope(requiredScope)
|
||||||
|
if err != nil {
|
||||||
|
return JwtToken{}, errors.New(ErrJwtScopeMissing)
|
||||||
|
//s.WriteMessage(c, ErrJwtScopeMissing, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
if token.Iss != s.config.ServerAddress {
|
||||||
|
return JwtToken{}, errors.New(ErrJwtInvalidIssuer)
|
||||||
|
//s.WriteMessage(c, ErrJwtInvalidIssuer, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Handler) GetUserIdFromJwtToken(c echo.Context) int64 {
|
||||||
|
token, err := s.getJwtTokenFromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
s.WriteMessage(c, ErrJwtMissing, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.GetUserId()
|
||||||
|
}
|
||||||
|
128
internal/handler/v1/jwt.go
Normal file
128
internal/handler/v1/jwt.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/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"`
|
||||||
|
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 string, userScopes []string, userId int64) (string, error) {
|
||||||
|
return h.generateJwtWithExp(username, issuer, userScopes, userId, time.Now().Add(10*time.Minute))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) generateJwtWithExp(username, issuer 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
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -1,39 +0,0 @@
|
|||||||
package v1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain/models"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 /v1/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)
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
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 /v1/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,31 +1,16 @@
|
|||||||
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/internal/database"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain/models"
|
|
||||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/services"
|
"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"
|
||||||
@ -34,7 +19,13 @@ type GetSource struct {
|
|||||||
// @Router /v1/sources [get]
|
// @Router /v1/sources [get]
|
||||||
// @Success 200 {object} domain.SourcesResponse "ok"
|
// @Success 200 {object} domain.SourcesResponse "ok"
|
||||||
// @Failure 400 {object} domain.BaseResponse "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 {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeSourceRead)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
resp := domain.SourcesResponse{
|
resp := domain.SourcesResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
@ -49,7 +40,7 @@ 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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Payload = services.SourcesToDto(items)
|
resp.Payload = services.SourcesToDto(items)
|
||||||
@ -66,7 +57,13 @@ func (s *Handler) listSources(c echo.Context) error {
|
|||||||
// @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) listSourcesBySource(c echo.Context) error {
|
func (s *Handler) listSourcesBySource(c echo.Context) error {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeSourceRead)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
resp := domain.SourcesResponse{
|
resp := domain.SourcesResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
@ -75,7 +72,7 @@ func (s *Handler) listSourcesBySource(c echo.Context) error {
|
|||||||
|
|
||||||
source := c.QueryParam("source")
|
source := c.QueryParam("source")
|
||||||
if source == "" {
|
if source == "" {
|
||||||
s.WriteMessage(c, fmt.Sprintf("%s source", ErrParameterMissing), http.StatusBadRequest)
|
return s.WriteMessage(c, fmt.Sprintf("%s source", ErrParameterMissing), http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
page, err := strconv.Atoi(c.QueryParam("page"))
|
page, err := strconv.Atoi(c.QueryParam("page"))
|
||||||
@ -104,7 +101,13 @@ func (s *Handler) listSourcesBySource(c echo.Context) error {
|
|||||||
// @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) getSource(c echo.Context) error {
|
func (s *Handler) getSource(c echo.Context) error {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeSourceRead)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
resp := domain.SourcesResponse{
|
resp := domain.SourcesResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
@ -120,7 +123,7 @@ func (s *Handler) getSource(c echo.Context) error {
|
|||||||
|
|
||||||
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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dto []domain.SourceDto
|
var dto []domain.SourceDto
|
||||||
@ -139,7 +142,13 @@ func (s *Handler) getSource(c echo.Context) error {
|
|||||||
// @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 {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeSourceRead)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
resp := domain.SourcesResponse{
|
resp := domain.SourcesResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
@ -147,7 +156,7 @@ func (s *Handler) GetSourceBySourceAndName(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var param domain.GetSourceBySourceAndNameParamRequest
|
var param domain.GetSourceBySourceAndNameParamRequest
|
||||||
err := c.Bind(¶m)
|
err = c.Bind(¶m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
@ -174,7 +183,13 @@ func (s *Handler) GetSourceBySourceAndName(c echo.Context) error {
|
|||||||
// @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) newRedditSource(c echo.Context) error {
|
func (s *Handler) newRedditSource(c echo.Context) error {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeSourceCreate)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
resp := domain.SourcesResponse{
|
resp := domain.SourcesResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
@ -182,37 +197,30 @@ func (s *Handler) newRedditSource(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var param domain.NewSourceParamRequest
|
var param domain.NewSourceParamRequest
|
||||||
err := c.Bind(¶m)
|
err = c.Bind(¶m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
Message: err.Error(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if param.Url == "" {
|
if param.Url == "" {
|
||||||
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
return s.WriteMessage(c, "url is missing", http.StatusBadRequest)
|
||||||
Message: "Url is missing a value",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if !strings.Contains(param.Url, "reddit.com") {
|
if !strings.Contains(param.Url, "reddit.com") {
|
||||||
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
return s.WriteMessage(c, "invalid url", http.StatusBadRequest)
|
||||||
Message: "Invalid URL given",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rows != 1 {
|
if rows != 1 {
|
||||||
s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError)
|
return s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dto []domain.SourceDto
|
var dto []domain.SourceDto
|
||||||
@ -227,62 +235,59 @@ func (s *Handler) newRedditSource(c echo.Context) error {
|
|||||||
// @Param url query string true "url"
|
// @Param url query string true "url"
|
||||||
// @Tags Source
|
// @Tags Source
|
||||||
// @Router /v1/sources/new/youtube [post]
|
// @Router /v1/sources/new/youtube [post]
|
||||||
|
// @Security Bearer
|
||||||
func (s *Handler) newYoutubeSource(c echo.Context) error {
|
func (s *Handler) newYoutubeSource(c echo.Context) error {
|
||||||
var param domain.NewSourceParamRequest
|
// Validate the jwt
|
||||||
err := c.Bind(¶m)
|
_, err := s.ValidateJwtToken(c, domain.ScopeSourceCreate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
Message: err.Error(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//query := r.URL.Query()
|
var param domain.NewSourceParamRequest
|
||||||
//_name := query["name"][0]
|
err = c.Bind(¶m)
|
||||||
//_url := query["url"][0]
|
if err != nil {
|
||||||
////_tags := query["tags"][0]
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
if param.Url == "" {
|
if param.Url == "" {
|
||||||
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
return s.WriteMessage(c, "url is missing a value", http.StatusBadRequest)
|
||||||
Message: "url is missing a value",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if !strings.Contains(param.Url, "youtube.com") {
|
if !strings.Contains(param.Url, "youtube.com") {
|
||||||
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
return s.WriteMessage(c, "invalid url", http.StatusBadRequest)
|
||||||
Message: "Invalid URL",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
resp := domain.SourcesResponse{
|
||||||
if _tags == "" {
|
BaseResponse: domain.BaseResponse{
|
||||||
tags = fmt.Sprintf("twitch, %v", _name)
|
Message: ResponseMessageSuccess,
|
||||||
} else {
|
},
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
item, err := s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorYoutube, param.Name)
|
||||||
|
if err == nil {
|
||||||
|
var dto []domain.SourceDto
|
||||||
|
dto = append(dto, services.SourceToDto(item))
|
||||||
|
resp.Payload = dto
|
||||||
|
return c.JSON(http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
return c.JSON(http.StatusInternalServerError, err.Error())
|
return c.JSON(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
bJson, err := json.Marshal(¶ms)
|
if rows != 1 {
|
||||||
if err != nil {
|
return s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
|
|
||||||
Message: err.Error(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, bJson)
|
item, err = s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorYoutube, param.Name)
|
||||||
|
if err == nil {
|
||||||
|
var dto []domain.SourceDto
|
||||||
|
dto = append(dto, services.SourceToDto(item))
|
||||||
|
resp.Payload = dto
|
||||||
|
return c.JSON(http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTwitchSource
|
// NewTwitchSource
|
||||||
@ -290,46 +295,56 @@ func (s *Handler) newYoutubeSource(c echo.Context) error {
|
|||||||
// @Param name query string true "name"
|
// @Param name query string true "name"
|
||||||
// @Tags Source
|
// @Tags Source
|
||||||
// @Router /v1/sources/new/twitch [post]
|
// @Router /v1/sources/new/twitch [post]
|
||||||
|
// @Security Bearer
|
||||||
func (s *Handler) newTwitchSource(c echo.Context) error {
|
func (s *Handler) newTwitchSource(c echo.Context) error {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeSourceCreate)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
var param domain.NewSourceParamRequest
|
var param domain.NewSourceParamRequest
|
||||||
err := c.Bind(¶m)
|
err = c.Bind(¶m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//query := r.URL.Query()
|
resp := domain.SourcesResponse{
|
||||||
//_name := query["name"][0]
|
BaseResponse: domain.BaseResponse{
|
||||||
|
Message: ResponseMessageSuccess,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
params := database.CreateSourceParams{
|
// Check if the record already exists
|
||||||
ID: uuid.New(),
|
item, err := s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorTwitch, param.Name)
|
||||||
Site: "twitch",
|
if err == nil {
|
||||||
Name: param.Name,
|
var dto []domain.SourceDto
|
||||||
Source: "twitch",
|
dto = append(dto, services.SourceToDto(item))
|
||||||
Type: "api",
|
resp.Payload = dto
|
||||||
Enabled: true,
|
return c.JSON(http.StatusOK, resp)
|
||||||
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 {
|
||||||
return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
|
return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
bJson, err := json.Marshal(¶ms)
|
if rows != 1 {
|
||||||
if err != nil {
|
return s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError)
|
||||||
return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
|
|
||||||
Message: err.Error(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, bJson)
|
item, _ = s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorTwitch, param.Name)
|
||||||
|
var dto []domain.SourceDto
|
||||||
|
dto = append(dto, services.SourceToDto(item))
|
||||||
|
resp.Payload = dto
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRssSource
|
// NewRssSource
|
||||||
@ -341,7 +356,13 @@ func (s *Handler) newTwitchSource(c echo.Context) error {
|
|||||||
// @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) newRssSource(c echo.Context) error {
|
func (s *Handler) newRssSource(c echo.Context) error {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeSourceCreate)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
resp := domain.SourcesResponse{
|
resp := domain.SourcesResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
@ -349,7 +370,7 @@ func (s *Handler) newRssSource(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var param domain.NewSourceParamRequest
|
var param domain.NewSourceParamRequest
|
||||||
err := c.Bind(¶m)
|
err = c.Bind(¶m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
@ -365,16 +386,16 @@ func (s *Handler) newRssSource(c echo.Context) error {
|
|||||||
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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rows != 1 {
|
if rows != 1 {
|
||||||
s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError)
|
return s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dto []domain.SourceDto
|
var dto []domain.SourceDto
|
||||||
@ -385,49 +406,56 @@ func (s *Handler) newRssSource(c echo.Context) error {
|
|||||||
|
|
||||||
// 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 string true "id"
|
// @Param id path int true "id"
|
||||||
// @Tags Source
|
// @Tags Source
|
||||||
// @Router /v1/sources/{id} [POST]
|
// @Router /v1/sources/{id} [POST]
|
||||||
|
// @Success 200 {object} domain.SourcesResponse "ok"
|
||||||
|
// @Failure 400 {object} domain.BaseResponse
|
||||||
|
// @Failure 500 {object} domain.BaseResponse
|
||||||
|
// @Security Bearer
|
||||||
func (s *Handler) deleteSources(c echo.Context) error {
|
func (s *Handler) deleteSources(c echo.Context) error {
|
||||||
id := c.Param("ID")
|
_, err := s.ValidateJwtToken(c, domain.ScopeAll)
|
||||||
uuid, err := uuid.Parse(id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusBadRequest, domain.BaseResponse{
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
Message: err.Error(),
|
}
|
||||||
})
|
|
||||||
|
id, err := strconv.Atoi(c.Param("ID"))
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to make sure we can find the record
|
// Check to make sure we can find the record
|
||||||
_, err = s.Db.GetSourceByID(c.Request().Context(), uuid)
|
_, err = s.repo.Sources.GetById(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
Message: err.Error(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the record
|
// Delete the record
|
||||||
err = s.Db.DeleteSource(c.Request().Context(), uuid)
|
rows, err := s.repo.Sources.SoftDelete(c.Request().Context(), int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
Message: err.Error(),
|
}
|
||||||
})
|
if rows != 1 {
|
||||||
|
return s.WriteMessage(c, ErrFailedToUpdateRecord, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := ApiStatusModel{
|
// pull the record with its updated value
|
||||||
|
item, err := s.repo.Sources.GetById(c.Request().Context(), int64(id))
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
var items []domain.SourceDto
|
||||||
|
items = append(items, services.SourceToDto(item))
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, domain.SourcesResponse{
|
||||||
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: "OK",
|
Message: "OK",
|
||||||
StatusCode: http.StatusOK,
|
},
|
||||||
}
|
Payload: items,
|
||||||
|
|
||||||
b, err := json.Marshal(p)
|
|
||||||
if err != nil {
|
|
||||||
return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
|
|
||||||
Message: err.Error(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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"
|
||||||
@ -436,7 +464,13 @@ func (s *Handler) deleteSources(c echo.Context) error {
|
|||||||
// @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) disableSource(c echo.Context) error {
|
func (s *Handler) disableSource(c echo.Context) error {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeAll)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
resp := domain.SourcesResponse{
|
resp := domain.SourcesResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
@ -445,23 +479,23 @@ func (s *Handler) disableSource(c echo.Context) error {
|
|||||||
|
|
||||||
id, err := strconv.Atoi(c.Param("ID"))
|
id, err := strconv.Atoi(c.Param("ID"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.WriteError(c, err, http.StatusBadRequest)
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
s.WriteError(c, err, http.StatusBadRequest)
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, 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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dto []domain.SourceDto
|
var dto []domain.SourceDto
|
||||||
@ -478,7 +512,13 @@ func (s *Handler) disableSource(c echo.Context) error {
|
|||||||
// @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) enableSource(c echo.Context) error {
|
func (s *Handler) enableSource(c echo.Context) error {
|
||||||
|
_, err := s.ValidateJwtToken(c, domain.ScopeAll)
|
||||||
|
if err != nil {
|
||||||
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
resp := domain.SourcesResponse{
|
resp := domain.SourcesResponse{
|
||||||
BaseResponse: domain.BaseResponse{
|
BaseResponse: domain.BaseResponse{
|
||||||
Message: ResponseMessageSuccess,
|
Message: ResponseMessageSuccess,
|
||||||
@ -487,23 +527,23 @@ func (s *Handler) enableSource(c echo.Context) error {
|
|||||||
|
|
||||||
id, err := strconv.Atoi(c.Param("ID"))
|
id, err := strconv.Atoi(c.Param("ID"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.WriteError(c, err, http.StatusBadRequest)
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
s.WriteError(c, err, http.StatusBadRequest)
|
return s.WriteError(c, err, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, 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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
s.WriteError(c, err, http.StatusInternalServerError)
|
return s.WriteError(c, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dto []domain.SourceDto
|
var dto []domain.SourceDto
|
||||||
|
@ -1,225 +0,0 @@
|
|||||||
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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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)
|
|
||||||
}
|
|
122
internal/repository/alertDiscord.go
Normal file
122
internal/repository/alertDiscord.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
|
"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) ([]domain.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) ([]domain.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 []domain.AlertDiscordEntity{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := r.processRows(rows)
|
||||||
|
if len(data) == 0 {
|
||||||
|
return []domain.AlertDiscordEntity{}, errors.New(ErrUserNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ur alertDiscordRepository) processRows(rows *sql.Rows) []domain.AlertDiscordEntity {
|
||||||
|
items := []domain.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 := domain.AlertDiscordEntity{
|
||||||
|
ID: id,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
DeletedAt: deletedAt,
|
||||||
|
UserID: userId,
|
||||||
|
SourceID: sourceId,
|
||||||
|
DiscordWebHookId: webhookId,
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
63
internal/repository/alertDiscord_test.go
Normal file
63
internal/repository/alertDiscord_test.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package repository_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/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()
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DiscordWebHookRepo interface {
|
type DiscordWebHookRepo interface {
|
||||||
Create(ctx context.Context, url, server, channel string, enabled bool) (int64, error)
|
Create(ctx context.Context, userId int64, 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)
|
||||||
@ -32,12 +32,12 @@ func NewDiscordWebHookRepository(conn *sql.DB) discordWebHookRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r discordWebHookRepository) Create(ctx context.Context, url, server, channel string, enabled bool) (int64, error) {
|
func (r discordWebHookRepository) Create(ctx context.Context, userId int64, 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", "Url", "Server", "Channel", "Enabled")
|
queryBuilder.Cols("UpdatedAt", "CreatedAt", "DeletedAt", "UserID", "Url", "Server", "Channel", "Enabled")
|
||||||
queryBuilder.Values(dt, dt, timeZero, url, server, channel, enabled)
|
queryBuilder.Values(dt, dt, timeZero, userId, 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...)
|
||||||
@ -195,13 +195,14 @@ func (r discordWebHookRepository) processRows(rows *sql.Rows) ([]domain.DiscordW
|
|||||||
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, &url, &server,
|
&deletedAt, &userId, &url, &server,
|
||||||
&channel, &enabled,
|
&channel, &enabled,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -213,6 +214,7 @@ func (r discordWebHookRepository) processRows(rows *sql.Rows) ([]domain.DiscordW
|
|||||||
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(), "www.discord.com/bad/webhook", "Unit Testing", "memes", true)
|
created, err := r.Create(context.Background(), 999, "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, "www.discord.com/bad/webhook", "Unit Testing", "memes", true)
|
created, err := r.Create(ctx, 999, "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, "www.discord.com/bad/webhook", "Unit Testing", "memes", true)
|
_, _ = r.Create(ctx, 999, "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, "www.discord.com/bad/webhook", serverName, "memes", true)
|
_, _ = r.Create(ctx, 999, "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, "www.discord.com/bad/webhook", serverName, channel, true)
|
_, _ = r.Create(ctx, 999, "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, "www.discord.com/bad/webhook", serverName, channel, false)
|
_, _ = r.Create(ctx, 999, "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, "www.discord.com/bad/webhook", serverName, channel, true)
|
_, _ = r.Create(ctx, 999, "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, "www.discord.com/bad/webhook", serverName, channel, true)
|
_, _ = r.Create(ctx, 999, "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, "www.discord.com/bad/webhook", serverName, channel, true)
|
_, _ = r.Create(ctx, 999, "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,6 +1,7 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -15,9 +16,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RefreshToken interface {
|
type RefreshToken interface {
|
||||||
Create(username string, token string) (int64, error)
|
Create(ctx context.Context, username string, token string) (int64, error)
|
||||||
GetByUsername(name string) (domain.RefreshTokenEntity, error)
|
GetByUsername(ctx context.Context, name string) (domain.RefreshTokenEntity, error)
|
||||||
DeleteById(id int64) (int64, error)
|
DeleteById(ctx context.Context, id int64) (int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type RefreshTokenRepository struct {
|
type RefreshTokenRepository struct {
|
||||||
@ -30,15 +31,15 @@ func NewRefreshTokenRepository(conn *sql.DB) RefreshTokenRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt RefreshTokenRepository) Create(username string, token string) (int64, error) {
|
func (rt RefreshTokenRepository) Create(ctx context.Context, 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")
|
builder.Cols("Username", "Token", "CreatedAt", "UpdatedAt", "DeletedAt")
|
||||||
builder.Values(username, token, dt, dt)
|
builder.Values(username, token, dt, dt, time.Time{})
|
||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
|
|
||||||
_, err := rt.connection.Exec(query, args...)
|
_, err := rt.connection.ExecContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -46,14 +47,14 @@ func (rt RefreshTokenRepository) Create(username string, token string) (int64, e
|
|||||||
return 1, nil
|
return 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt RefreshTokenRepository) GetByUsername(name string) (domain.RefreshTokenEntity, error) {
|
func (rt RefreshTokenRepository) GetByUsername(ctx context.Context, 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.Query(query, args...)
|
rows, err := rt.connection.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.RefreshTokenEntity{}, err
|
return domain.RefreshTokenEntity{}, err
|
||||||
}
|
}
|
||||||
@ -66,7 +67,7 @@ func (rt RefreshTokenRepository) GetByUsername(name string) (domain.RefreshToken
|
|||||||
return data[0], nil
|
return data[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt RefreshTokenRepository) DeleteById(id int64) (int64, error) {
|
func (rt RefreshTokenRepository) DeleteById(ctx context.Context, id int64) (int64, error) {
|
||||||
builder := sqlbuilder.NewDeleteBuilder()
|
builder := sqlbuilder.NewDeleteBuilder()
|
||||||
builder.DeleteFrom(refreshTokenTableName)
|
builder.DeleteFrom(refreshTokenTableName)
|
||||||
builder.Where(
|
builder.Where(
|
||||||
@ -74,7 +75,7 @@ func (rt RefreshTokenRepository) DeleteById(id int64) (int64, error) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
rows, err := rt.connection.Exec(query, args...)
|
rows, err := rt.connection.ExecContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
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"
|
||||||
@ -14,7 +15,7 @@ func TestRefreshTokenCreate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
client := repository.NewRefreshTokenRepository(conn)
|
client := repository.NewRefreshTokenRepository(conn)
|
||||||
rows, err := client.Create("tester", "BadTokenDontUse")
|
rows, err := client.Create(context.Background(), "tester", "BadTokenDontUse")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@ -33,7 +34,7 @@ func TestRefreshTokenGetByUsername(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
client := repository.NewRefreshTokenRepository(conn)
|
client := repository.NewRefreshTokenRepository(conn)
|
||||||
rows, err := client.Create("tester", "BadTokenDoNotUse")
|
rows, err := client.Create(context.Background(), "tester", "BadTokenDoNotUse")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@ -44,7 +45,7 @@ func TestRefreshTokenGetByUsername(t *testing.T) {
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
model, err := client.GetByUsername("tester")
|
model, err := client.GetByUsername(context.Background(), "tester")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@ -64,7 +65,7 @@ func TestRefreshTokenDeleteById(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
client := repository.NewRefreshTokenRepository(conn)
|
client := repository.NewRefreshTokenRepository(conn)
|
||||||
created, err := client.Create("tester", "BadTokenDoNotUse")
|
created, err := client.Create(context.Background(), "tester", "BadTokenDoNotUse")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@ -73,13 +74,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("tester")
|
model, err := client.GetByUsername(context.Background(), "tester")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
updated, err := client.DeleteById(model.ID)
|
updated, err := client.DeleteById(context.Background(), model.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
120
internal/repository/userSourceSubscription.go
Normal file
120
internal/repository/userSourceSubscription.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
|
"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) ([]domain.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) ([]domain.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 []domain.UserSourceSubscriptionEntity{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := r.processRows(rows)
|
||||||
|
if len(data) == 0 {
|
||||||
|
return []domain.UserSourceSubscriptionEntity{}, errors.New(ErrUserNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ur userSourceRepository) processRows(rows *sql.Rows) []domain.UserSourceSubscriptionEntity {
|
||||||
|
items := []domain.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 := domain.UserSourceSubscriptionEntity{
|
||||||
|
ID: id,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
DeletedAt: deletedAt,
|
||||||
|
UserID: userId,
|
||||||
|
SourceID: sourceId,
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -18,12 +19,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Users interface {
|
type Users interface {
|
||||||
GetByName(name string) (domain.UserEntity, error)
|
GetByName(ctx context.Context, name string) (domain.UserEntity, error)
|
||||||
Create(name, password, scope string) (int64, error)
|
Create(ctx context.Context, name, password, scope string) (int64, error)
|
||||||
Update(id int, entity domain.UserEntity) error
|
Update(ctx context.Context, id int, entity domain.UserEntity) error
|
||||||
UpdatePassword(name, password string) error
|
UpdatePassword(ctx context.Context, name, password string) error
|
||||||
CheckUserHash(name, password string) error
|
CheckUserHash(ctx context.Context, name, password string) error
|
||||||
UpdateScopes(name, scope string) error
|
UpdateScopes(ctx context.Context, name, scope string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new instance of UserRepository with the bound sql
|
// Creates a new instance of UserRepository with the bound sql
|
||||||
@ -37,14 +38,14 @@ type userRepository struct {
|
|||||||
connection *sql.DB
|
connection *sql.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ur userRepository) GetByName(name string) (domain.UserEntity, error) {
|
func (ur userRepository) GetByName(ctx context.Context, 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.Query(query, args...)
|
rows, err := ur.connection.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.UserEntity{}, err
|
return domain.UserEntity{}, err
|
||||||
}
|
}
|
||||||
@ -57,7 +58,7 @@ func (ur userRepository) GetByName(name string) (domain.UserEntity, error) {
|
|||||||
return data[0], nil
|
return data[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ur userRepository) Create(name, password, scope string) (int64, error) {
|
func (ur userRepository) Create(ctx context.Context,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 {
|
||||||
@ -67,11 +68,11 @@ func (ur userRepository) Create(name, password, scope string) (int64, error) {
|
|||||||
dt := time.Now()
|
dt := time.Now()
|
||||||
queryBuilder := sqlbuilder.NewInsertBuilder()
|
queryBuilder := sqlbuilder.NewInsertBuilder()
|
||||||
queryBuilder.InsertInto("users")
|
queryBuilder.InsertInto("users")
|
||||||
queryBuilder.Cols("Name", "Hash", "UpdatedAt", "CreatedAt", "Scopes")
|
queryBuilder.Cols("Name", "Hash", "UpdatedAt", "CreatedAt", "DeletedAt", "Scopes")
|
||||||
queryBuilder.Values(name, string(hash), dt, dt, scope)
|
queryBuilder.Values(name, string(hash), dt, dt, time.Time{}, scope)
|
||||||
query, args := queryBuilder.Build()
|
query, args := queryBuilder.Build()
|
||||||
|
|
||||||
_, err = ur.connection.Exec(query, args...)
|
_, err = ur.connection.ExecContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -79,12 +80,12 @@ func (ur userRepository) Create(name, password, scope string) (int64, error) {
|
|||||||
return 1, nil
|
return 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ur userRepository) Update(id int, entity domain.UserEntity) error {
|
func (ur userRepository) Update(ctx context.Context, id int, entity domain.UserEntity) error {
|
||||||
return errors.New("not implemented")
|
return errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ur userRepository) UpdatePassword(name, password string) error {
|
func (ur userRepository) UpdatePassword(ctx context.Context, name, password string) error {
|
||||||
_, err := ur.GetByName(name)
|
_, err := ur.GetByName(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -97,8 +98,8 @@ func (ur userRepository) UpdatePassword(name, password string) error {
|
|||||||
|
|
||||||
// If the hash matches what we have in the database, an error will not be returned.
|
// If the hash matches what we have in the database, an error will not be returned.
|
||||||
// If the user does not exist or the hash does not match, an error will be returned
|
// If the user does not exist or the hash does not match, an error will be returned
|
||||||
func (ur userRepository) CheckUserHash(name, password string) error {
|
func (ur userRepository) CheckUserHash(ctx context.Context,name, password string) error {
|
||||||
record, err := ur.GetByName(name)
|
record, err := ur.GetByName(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -111,7 +112,7 @@ func (ur userRepository) CheckUserHash(name, password string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ur userRepository) UpdateScopes(name, scope string) error {
|
func (ur userRepository) UpdateScopes(ctx context.Context,name, scope string) error {
|
||||||
builder := sqlbuilder.NewUpdateBuilder()
|
builder := sqlbuilder.NewUpdateBuilder()
|
||||||
builder.Update("users")
|
builder.Update("users")
|
||||||
builder.Set(
|
builder.Set(
|
||||||
@ -122,7 +123,7 @@ func (ur userRepository) UpdateScopes(name, scope string) error {
|
|||||||
)
|
)
|
||||||
query, args := builder.Build()
|
query, args := builder.Build()
|
||||||
|
|
||||||
_, err := ur.connection.Exec(query, args...)
|
_, err := ur.connection.ExecContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package repository_test
|
package repository_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"log"
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
@ -20,7 +21,7 @@ func TestCanCreateNewUser(t *testing.T) {
|
|||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
repo := repository.NewUserRepository(db)
|
repo := repository.NewUserRepository(db)
|
||||||
updated, err := repo.Create("testing", "NotSecure", "placeholder")
|
updated, err := repo.Create(context.Background(), "testing", "NotSecure", "placeholder")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@ -37,7 +38,7 @@ func TestCanFindUserInTable(t *testing.T) {
|
|||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
repo := repository.NewUserRepository(db)
|
repo := repository.NewUserRepository(db)
|
||||||
updated, err := repo.Create("testing", "NotSecure", "placeholder")
|
updated, err := repo.Create(context.Background(), "testing", "NotSecure", "placeholder")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@ -48,7 +49,7 @@ func TestCanFindUserInTable(t *testing.T) {
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := repo.GetByName("testing")
|
user, err := repo.GetByName(context.Background(), "testing")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@ -65,7 +66,7 @@ func TestCheckUserHash(t *testing.T) {
|
|||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
repo := repository.NewUserRepository(db)
|
repo := repository.NewUserRepository(db)
|
||||||
repo.CheckUserHash("testing", "NotSecure")
|
repo.CheckUserHash(context.Background(), "testing", "NotSecure")
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupInMemoryDb() (*sql.DB, error) {
|
func setupInMemoryDb() (*sql.DB, error) {
|
||||||
|
87
internal/repositoryServices/refreshTokens.go
Normal file
87
internal/repositoryServices/refreshTokens.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package repositoryservices
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
|
"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) (domain.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) (domain.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
|
||||||
|
}
|
171
internal/repositoryServices/userService.go
Normal file
171
internal/repositoryServices/userService.go
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
package repositoryservices
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
||||||
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
|
||||||
|
|
||||||
|
"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) (domain.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) (domain.UserEntity, 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) (domain.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) (domain.UserEntity, error) {
|
||||||
|
err := us.CheckPasswordForRequirements(password)
|
||||||
|
if err != nil {
|
||||||
|
return domain.UserEntity{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
us.repo.Create(ctx, name, password, domain.ScopeArticleRead)
|
||||||
|
return domain.UserEntity{}, 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)
|
||||||
|
}
|
@ -34,6 +34,8 @@ const (
|
|||||||
|
|
||||||
type Configs struct {
|
type Configs struct {
|
||||||
ServerAddress string
|
ServerAddress string
|
||||||
|
JwtSecret string
|
||||||
|
AdminSecret string
|
||||||
|
|
||||||
RedditEnabled bool
|
RedditEnabled bool
|
||||||
RedditPullTop bool
|
RedditPullTop bool
|
||||||
@ -64,6 +66,8 @@ 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)),
|
||||||
|
@ -4,22 +4,27 @@ 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 repository.Users
|
Users repositoryservices.UserServices
|
||||||
RefreshTokens repository.RefreshToken
|
UserSourceSubscriptions repository.UserSourceRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
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: repository.NewUserRepository(conn),
|
Users: repositoryservices.NewUserService(conn),
|
||||||
RefreshTokens: repository.NewRefreshTokenRepository(conn),
|
UserSourceSubscriptions: repository.NewUserSourceRepository(conn),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
makefile
1
makefile
@ -6,6 +6,7 @@ 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" .
|
||||||
|
Loading…
Reference in New Issue
Block a user