features/jwt #7

Merged
jtom38 merged 10 commits from features/jwt into main 2024-05-07 22:21:58 -07:00
7 changed files with 396 additions and 35 deletions
Showing only changes of commit f0d36eb2ab - Show all commits

View File

@ -72,15 +72,36 @@ CREATE Table Sources (
Tags TEXT NOT NULL
);
/*
CREATE TABLE Subscriptions (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
CreatedAt DATETIME NOT NULL,
UpdatedAt DATETIME NOT NULL,
DeletedAt DATETIME,
DeletedAt DATETIME NOT NULL,
DiscordWebHookID NUMBER NOT NULL,
SourceID 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 (
ID INTEGER PRIMARY KEY AUTOINCREMENT,

View File

@ -4,6 +4,18 @@ import (
"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 {
ID int64
CreatedAt time.Time
@ -35,10 +47,11 @@ type DiscordWebHookEntity struct {
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time
Url string
Server string
Channel string
Enabled bool
UserID int64
Url string
Server string
Channel string
Enabled bool
}
type IconEntity struct {
@ -61,38 +74,50 @@ type SettingEntity struct {
}
type SourceEntity struct {
ID int64
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time
ID int64
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time
// Who will collect from it. Used
// domain.SourceCollector...
Source string
Source string
// Human Readable value to state what is getting collected
DisplayName string
// Tells the parser where to look for data
Url string
Url string
// Static tags for this defined record
Tags string
Tags string
// If the record is disabled, then it will be skipped on processing
Enabled bool
Enabled bool
}
type SubscriptionEntity struct {
ID int64
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time
SourceID int64
SourceType string
SourceName string
DiscordID int64
DiscordName string
//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
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time
UserID int64
SourceID int64
}
type UserEntity struct {

View File

@ -0,0 +1,6 @@
package domain
const (
ScopeAll = "newsbot:all"
ScopeRead = "newsbot:read"
)

View 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
}

View 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()
}
}

View 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
}

View File

@ -7,19 +7,23 @@ import (
)
type RepositoryService struct {
Articles repository.ArticlesRepo
DiscordWebHooks repository.DiscordWebHookRepo
Sources repository.Sources
Users repository.Users
RefreshTokens repository.RefreshToken
AlertDiscord repository.AlertDiscordRepo
Articles repository.ArticlesRepo
DiscordWebHooks repository.DiscordWebHookRepo
RefreshTokens repository.RefreshToken
Sources repository.Sources
Users repository.Users
UserSourceSubscriptions repository.UserSourceRepo
}
func NewRepositoryService(conn *sql.DB) RepositoryService {
return RepositoryService{
Articles: repository.NewArticleRepository(conn),
DiscordWebHooks: repository.NewDiscordWebHookRepository(conn),
Sources: repository.NewSourceRepository(conn),
Users: repository.NewUserRepository(conn),
RefreshTokens: repository.NewRefreshTokenRepository(conn),
AlertDiscord: repository.NewAlertDiscordRepository(conn),
Articles: repository.NewArticleRepository(conn),
DiscordWebHooks: repository.NewDiscordWebHookRepository(conn),
RefreshTokens: repository.NewRefreshTokenRepository(conn),
Sources: repository.NewSourceRepository(conn),
Users: repository.NewUserRepository(conn),
UserSourceSubscriptions: repository.NewUserSourceRepository(conn),
}
}