Cybersecurity-Projects/PROJECTS/advanced/monitor-the-situation-dashb.../backend/internal/notifications/repository.go

260 lines
6.2 KiB
Go

// ©AngelaMos | 2026
// repository.go
package notifications
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/carterperez-dev/monitor-the-situation/backend/internal/core"
)
type Repository interface {
ListChannels(ctx context.Context, userID string) ([]Channel, error)
GetChannel(ctx context.Context, id, userID string) (*Channel, error)
CreateChannel(ctx context.Context, ch *Channel) error
DeleteChannel(ctx context.Context, id, userID string) error
MarkChannelInvalid(ctx context.Context, id string) error
GetTelegramWebhook(
ctx context.Context,
userID string,
) (*TelegramWebhook, error)
GetTelegramWebhookByUUID(
ctx context.Context,
uuid string,
) (*TelegramWebhook, error)
UpsertTelegramWebhook(ctx context.Context, tw *TelegramWebhook) error
UpdateTelegramChatID(ctx context.Context, uuid string, chatID int64) error
DeleteTelegramWebhook(ctx context.Context, userID string) error
}
type repository struct {
db core.DBTX
}
func NewRepository(db core.DBTX) Repository {
return &repository{db: db}
}
func (r *repository) ListChannels(
ctx context.Context,
userID string,
) ([]Channel, error) {
query := `
SELECT id, user_id, type, label, config_enc, nonce,
invalid, created_at, updated_at
FROM alert_channels
WHERE user_id = $1
ORDER BY created_at DESC`
var channels []Channel
if err := r.db.SelectContext(ctx, &channels, query, userID); err != nil {
return nil, fmt.Errorf("list channels: %w", err)
}
return channels, nil
}
func (r *repository) GetChannel(
ctx context.Context,
id, userID string,
) (*Channel, error) {
query := `
SELECT id, user_id, type, label, config_enc, nonce,
invalid, created_at, updated_at
FROM alert_channels
WHERE id = $1 AND user_id = $2`
var ch Channel
err := r.db.GetContext(ctx, &ch, query, id, userID)
if errors.Is(err, sql.ErrNoRows) {
return nil, fmt.Errorf("get channel: %w", core.ErrNotFound)
}
if err != nil {
return nil, fmt.Errorf("get channel: %w", err)
}
return &ch, nil
}
func (r *repository) CreateChannel(ctx context.Context, ch *Channel) error {
query := `
INSERT INTO alert_channels
(id, user_id, type, label, config_enc, nonce)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING created_at, updated_at`
err := r.db.GetContext(ctx, ch, query,
ch.ID,
ch.UserID,
ch.Type,
ch.Label,
ch.ConfigEnc,
ch.Nonce,
)
if err != nil {
return fmt.Errorf("create channel: %w", err)
}
return nil
}
func (r *repository) DeleteChannel(
ctx context.Context,
id, userID string,
) error {
query := `DELETE FROM alert_channels WHERE id = $1 AND user_id = $2`
result, err := r.db.ExecContext(ctx, query, id, userID)
if err != nil {
return fmt.Errorf("delete channel: %w", err)
}
rows, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("delete channel: %w", err)
}
if rows == 0 {
return fmt.Errorf("delete channel: %w", core.ErrNotFound)
}
return nil
}
func (r *repository) MarkChannelInvalid(ctx context.Context, id string) error {
query := `
UPDATE alert_channels
SET invalid = true, updated_at = NOW()
WHERE id = $1`
_, err := r.db.ExecContext(ctx, query, id)
if err != nil {
return fmt.Errorf("mark channel invalid: %w", err)
}
return nil
}
func (r *repository) GetTelegramWebhook(
ctx context.Context,
userID string,
) (*TelegramWebhook, error) {
query := `
SELECT user_id, webhook_uuid, secret_token, bot_token_enc,
bot_token_nonce, chat_id, pending_link, created_at
FROM telegram_webhooks
WHERE user_id = $1`
var tw TelegramWebhook
err := r.db.GetContext(ctx, &tw, query, userID)
if errors.Is(err, sql.ErrNoRows) {
return nil, fmt.Errorf("get telegram webhook: %w", core.ErrNotFound)
}
if err != nil {
return nil, fmt.Errorf("get telegram webhook: %w", err)
}
return &tw, nil
}
func (r *repository) GetTelegramWebhookByUUID(
ctx context.Context,
uuid string,
) (*TelegramWebhook, error) {
query := `
SELECT user_id, webhook_uuid, secret_token, bot_token_enc,
bot_token_nonce, chat_id, pending_link, created_at
FROM telegram_webhooks
WHERE webhook_uuid = $1`
var tw TelegramWebhook
err := r.db.GetContext(ctx, &tw, query, uuid)
if errors.Is(err, sql.ErrNoRows) {
return nil, fmt.Errorf(
"get telegram webhook by uuid: %w",
core.ErrNotFound,
)
}
if err != nil {
return nil, fmt.Errorf("get telegram webhook by uuid: %w", err)
}
return &tw, nil
}
func (r *repository) UpsertTelegramWebhook(
ctx context.Context,
tw *TelegramWebhook,
) error {
query := `
INSERT INTO telegram_webhooks
(user_id, webhook_uuid, secret_token,
bot_token_enc, bot_token_nonce, pending_link)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (user_id) DO UPDATE
SET webhook_uuid = EXCLUDED.webhook_uuid,
secret_token = EXCLUDED.secret_token,
bot_token_enc = EXCLUDED.bot_token_enc,
bot_token_nonce = EXCLUDED.bot_token_nonce,
chat_id = NULL,
pending_link = true,
created_at = now()
RETURNING created_at`
err := r.db.GetContext(ctx, &tw.CreatedAt, query,
tw.UserID,
tw.WebhookUUID,
tw.SecretToken,
tw.BotTokenEnc,
tw.BotTokenNonce,
tw.PendingLink,
)
if err != nil {
return fmt.Errorf("upsert telegram webhook: %w", err)
}
return nil
}
func (r *repository) UpdateTelegramChatID(
ctx context.Context,
uuid string,
chatID int64,
) error {
query := `
UPDATE telegram_webhooks
SET chat_id = $2, pending_link = false
WHERE webhook_uuid = $1`
result, err := r.db.ExecContext(ctx, query, uuid, chatID)
if err != nil {
return fmt.Errorf("update telegram chat_id: %w", err)
}
rows, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("update telegram chat_id: %w", err)
}
if rows == 0 {
return fmt.Errorf("update telegram chat_id: %w", core.ErrNotFound)
}
return nil
}
func (r *repository) DeleteTelegramWebhook(
ctx context.Context,
userID string,
) error {
query := `DELETE FROM telegram_webhooks WHERE user_id = $1`
result, err := r.db.ExecContext(ctx, query, userID)
if err != nil {
return fmt.Errorf("delete telegram webhook: %w", err)
}
rows, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("delete telegram webhook: %w", err)
}
if rows == 0 {
return fmt.Errorf("delete telegram webhook: %w", core.ErrNotFound)
}
return nil
}