features/jwt #7
@ -72,15 +72,36 @@ CREATE Table Sources (
|
|||||||
Tags TEXT NOT NULL
|
Tags TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
CREATE TABLE Subscriptions (
|
CREATE TABLE Subscriptions (
|
||||||
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
CreatedAt DATETIME NOT NULL,
|
CreatedAt DATETIME NOT NULL,
|
||||||
UpdatedAt DATETIME NOT NULL,
|
UpdatedAt DATETIME NOT NULL,
|
||||||
DeletedAt DATETIME,
|
DeletedAt DATETIME NOT NULL,
|
||||||
DiscordWebHookID NUMBER NOT NULL,
|
DiscordWebHookID NUMBER NOT NULL,
|
||||||
SourceID NUMBER NOT NULL,
|
SourceID NUMBER NOT NULL,
|
||||||
UserID NUMBER NOT NULL
|
UserID NUMBER NOT NULL
|
||||||
);
|
);
|
||||||
|
*/
|
||||||
|
|
||||||
|
CREATE TABLE UserSourceSubscriptions (
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE AlertDiscord (
|
||||||
|
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
CreatedAt DATETIME NOT NULL,
|
||||||
|
UpdatedAt DATETIME NOT NULL,
|
||||||
|
DeletedAt DATETIME NOT NULL,
|
||||||
|
UserID NUMBER NOT NULL,
|
||||||
|
SourceID NUMBER NOT NULL,
|
||||||
|
DiscordWebHookID NUMBER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE Users (
|
CREATE TABLE Users (
|
||||||
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
@ -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 {
|
||||||
|
6
internal/domain/scopes.go
Normal file
6
internal/domain/scopes.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScopeAll = "newsbot:all"
|
||||||
|
ScopeRead = "newsbot:read"
|
||||||
|
)
|
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()
|
||||||
|
}
|
||||||
|
}
|
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
|
||||||
|
}
|
@ -7,19 +7,23 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RepositoryService struct {
|
type RepositoryService struct {
|
||||||
|
AlertDiscord repository.AlertDiscordRepo
|
||||||
Articles repository.ArticlesRepo
|
Articles repository.ArticlesRepo
|
||||||
DiscordWebHooks repository.DiscordWebHookRepo
|
DiscordWebHooks repository.DiscordWebHookRepo
|
||||||
|
RefreshTokens repository.RefreshToken
|
||||||
Sources repository.Sources
|
Sources repository.Sources
|
||||||
Users repository.Users
|
Users repository.Users
|
||||||
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: repository.NewRefreshTokenRepository(conn),
|
||||||
Sources: repository.NewSourceRepository(conn),
|
Sources: repository.NewSourceRepository(conn),
|
||||||
Users: repository.NewUserRepository(conn),
|
Users: repository.NewUserRepository(conn),
|
||||||
RefreshTokens: repository.NewRefreshTokenRepository(conn),
|
UserSourceSubscriptions: repository.NewUserSourceRepository(conn),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user