mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-19 08:01:16 +00:00
299 lines
9.2 KiB
Go
299 lines
9.2 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/warmbly/warmbly/internal/errx"
|
|
"github.com/warmbly/warmbly/internal/infrastructure/db"
|
|
)
|
|
|
|
// EmailAccountError represents an error associated with an email account
|
|
type EmailAccountError struct {
|
|
ID uuid.UUID `json:"id"`
|
|
EmailAccountID uuid.UUID `json:"email_account_id"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
ErrorCode string `json:"error_code"`
|
|
Severity string `json:"severity"`
|
|
ResolveMethod string `json:"resolve_method"`
|
|
Title string `json:"title"`
|
|
Message string `json:"message"`
|
|
UserMessage *string `json:"user_message,omitempty"`
|
|
ActionRequired *string `json:"action_required,omitempty"`
|
|
TaskID *uuid.UUID `json:"task_id,omitempty"`
|
|
ResolvedAt *time.Time `json:"resolved_at,omitempty"`
|
|
ResolvedBy *string `json:"resolved_by,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// CreateEmailAccountError contains data for creating a new error record
|
|
type CreateEmailAccountError struct {
|
|
EmailAccountID uuid.UUID
|
|
UserID uuid.UUID
|
|
ErrorCode string
|
|
Severity string
|
|
ResolveMethod string
|
|
Title string
|
|
Message string
|
|
UserMessage *string
|
|
ActionRequired *string
|
|
TaskID *uuid.UUID
|
|
}
|
|
|
|
// EmailAccountErrorRepository defines operations for email account errors
|
|
type EmailAccountErrorRepository interface {
|
|
Create(ctx context.Context, err *CreateEmailAccountError) (*EmailAccountError, *errx.Error)
|
|
GetByAccountID(ctx context.Context, accountID uuid.UUID, unresolvedOnly bool) ([]EmailAccountError, *errx.Error)
|
|
GetByUserID(ctx context.Context, userID uuid.UUID, limit int) ([]EmailAccountError, *errx.Error)
|
|
Resolve(ctx context.Context, errorID uuid.UUID, resolvedBy string) *errx.Error
|
|
ResolveByMethod(ctx context.Context, accountID uuid.UUID, method string) *errx.Error
|
|
ResolveAllForAccount(ctx context.Context, accountID uuid.UUID, resolvedBy string) *errx.Error
|
|
}
|
|
|
|
type emailAccountErrorRepository struct {
|
|
DB *db.DB
|
|
}
|
|
|
|
// NewEmailAccountErrorRepository creates a new email account error repository
|
|
func NewEmailAccountErrorRepository(database *db.DB) EmailAccountErrorRepository {
|
|
return &emailAccountErrorRepository{
|
|
DB: database,
|
|
}
|
|
}
|
|
|
|
// Create stores a new email account error
|
|
func (r *emailAccountErrorRepository) Create(ctx context.Context, data *CreateEmailAccountError) (*EmailAccountError, *errx.Error) {
|
|
query := `
|
|
INSERT INTO email_account_errors (
|
|
email_account_id, user_id, error_code, severity, resolve_method,
|
|
title, message, user_message, action_required, task_id
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
RETURNING id, email_account_id, user_id, error_code, severity, resolve_method,
|
|
title, message, user_message, action_required, task_id,
|
|
resolved_at, resolved_by, created_at
|
|
`
|
|
|
|
params := []any{
|
|
data.EmailAccountID,
|
|
data.UserID,
|
|
data.ErrorCode,
|
|
data.Severity,
|
|
data.ResolveMethod,
|
|
data.Title,
|
|
data.Message,
|
|
data.UserMessage,
|
|
data.ActionRequired,
|
|
data.TaskID,
|
|
}
|
|
|
|
var e EmailAccountError
|
|
err := r.DB.QueryRow(ctx, query, params...).Scan(
|
|
&e.ID, &e.EmailAccountID, &e.UserID, &e.ErrorCode, &e.Severity, &e.ResolveMethod,
|
|
&e.Title, &e.Message, &e.UserMessage, &e.ActionRequired, &e.TaskID,
|
|
&e.ResolvedAt, &e.ResolvedBy, &e.CreatedAt,
|
|
)
|
|
if err != nil {
|
|
db.CaptureError(err, query, params, "queryrow")
|
|
return nil, errx.InternalError()
|
|
}
|
|
|
|
return &e, nil
|
|
}
|
|
|
|
// GetByAccountID retrieves errors for a specific email account
|
|
func (r *emailAccountErrorRepository) GetByAccountID(ctx context.Context, accountID uuid.UUID, unresolvedOnly bool) ([]EmailAccountError, *errx.Error) {
|
|
query := `
|
|
SELECT id, email_account_id, user_id, error_code, severity, resolve_method,
|
|
title, message, user_message, action_required, task_id,
|
|
resolved_at, resolved_by, created_at
|
|
FROM email_account_errors
|
|
WHERE email_account_id = $1
|
|
`
|
|
|
|
if unresolvedOnly {
|
|
query += " AND resolved_at IS NULL"
|
|
}
|
|
|
|
query += " ORDER BY created_at DESC"
|
|
|
|
rows, err := r.DB.Query(ctx, query, accountID)
|
|
if err != nil {
|
|
db.CaptureError(err, query, []any{accountID}, "query")
|
|
return nil, errx.InternalError()
|
|
}
|
|
defer rows.Close()
|
|
|
|
var errors []EmailAccountError
|
|
for rows.Next() {
|
|
var e EmailAccountError
|
|
err := rows.Scan(
|
|
&e.ID, &e.EmailAccountID, &e.UserID, &e.ErrorCode, &e.Severity, &e.ResolveMethod,
|
|
&e.Title, &e.Message, &e.UserMessage, &e.ActionRequired, &e.TaskID,
|
|
&e.ResolvedAt, &e.ResolvedBy, &e.CreatedAt,
|
|
)
|
|
if err != nil {
|
|
db.CaptureError(err, "", nil, "scan")
|
|
return nil, errx.InternalError()
|
|
}
|
|
errors = append(errors, e)
|
|
}
|
|
|
|
return errors, nil
|
|
}
|
|
|
|
// GetByUserID retrieves recent errors for a user across all their email accounts
|
|
func (r *emailAccountErrorRepository) GetByUserID(ctx context.Context, userID uuid.UUID, limit int) ([]EmailAccountError, *errx.Error) {
|
|
query := `
|
|
SELECT id, email_account_id, user_id, error_code, severity, resolve_method,
|
|
title, message, user_message, action_required, task_id,
|
|
resolved_at, resolved_by, created_at
|
|
FROM email_account_errors
|
|
WHERE user_id = $1
|
|
ORDER BY created_at DESC
|
|
LIMIT $2
|
|
`
|
|
|
|
rows, err := r.DB.Query(ctx, query, userID, limit)
|
|
if err != nil {
|
|
db.CaptureError(err, query, []any{userID, limit}, "query")
|
|
return nil, errx.InternalError()
|
|
}
|
|
defer rows.Close()
|
|
|
|
var errors []EmailAccountError
|
|
for rows.Next() {
|
|
var e EmailAccountError
|
|
err := rows.Scan(
|
|
&e.ID, &e.EmailAccountID, &e.UserID, &e.ErrorCode, &e.Severity, &e.ResolveMethod,
|
|
&e.Title, &e.Message, &e.UserMessage, &e.ActionRequired, &e.TaskID,
|
|
&e.ResolvedAt, &e.ResolvedBy, &e.CreatedAt,
|
|
)
|
|
if err != nil {
|
|
db.CaptureError(err, "", nil, "scan")
|
|
return nil, errx.InternalError()
|
|
}
|
|
errors = append(errors, e)
|
|
}
|
|
|
|
return errors, nil
|
|
}
|
|
|
|
// Resolve marks a specific error as resolved
|
|
func (r *emailAccountErrorRepository) Resolve(ctx context.Context, errorID uuid.UUID, resolvedBy string) *errx.Error {
|
|
query := `
|
|
UPDATE email_account_errors
|
|
SET resolved_at = NOW(), resolved_by = $1
|
|
WHERE id = $2 AND resolved_at IS NULL
|
|
`
|
|
|
|
cmd, err := r.DB.Exec(ctx, query, resolvedBy, errorID)
|
|
if err != nil {
|
|
db.CaptureError(err, query, []any{resolvedBy, errorID}, "exec")
|
|
return errx.InternalError()
|
|
}
|
|
if cmd.RowsAffected() == 0 {
|
|
return errx.ErrNotFound
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ResolveByMethod resolves all errors for an account that have the specified resolve method
|
|
func (r *emailAccountErrorRepository) ResolveByMethod(ctx context.Context, accountID uuid.UUID, method string) *errx.Error {
|
|
query := `
|
|
UPDATE email_account_errors
|
|
SET resolved_at = NOW(), resolved_by = $1
|
|
WHERE email_account_id = $2
|
|
AND resolve_method = $3
|
|
AND resolved_at IS NULL
|
|
`
|
|
|
|
resolvedBy := "system:" + method
|
|
_, err := r.DB.Exec(ctx, query, resolvedBy, accountID, method)
|
|
if err != nil {
|
|
db.CaptureError(err, query, []any{resolvedBy, accountID, method}, "exec")
|
|
return errx.InternalError()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ResolveAllForAccount resolves all unresolved errors for an email account
|
|
func (r *emailAccountErrorRepository) ResolveAllForAccount(ctx context.Context, accountID uuid.UUID, resolvedBy string) *errx.Error {
|
|
query := `
|
|
UPDATE email_account_errors
|
|
SET resolved_at = NOW(), resolved_by = $1
|
|
WHERE email_account_id = $2 AND resolved_at IS NULL
|
|
`
|
|
|
|
_, err := r.DB.Exec(ctx, query, resolvedBy, accountID)
|
|
if err != nil {
|
|
db.CaptureError(err, query, []any{resolvedBy, accountID}, "exec")
|
|
return errx.InternalError()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Helper to map errx.MailErrorResolveMethod to DB enum value
|
|
func MapResolveMethod(method errx.MailErrorResolveMethod) string {
|
|
switch method {
|
|
case errx.MailErrorResolveMethodAuth:
|
|
return "OAUTH"
|
|
case errx.MailErrorResolveMethodRetry:
|
|
return "RETRY"
|
|
case errx.MailErrorResolveMethodReload:
|
|
return "RELOAD"
|
|
default:
|
|
return "NONE"
|
|
}
|
|
}
|
|
|
|
// Helper to map errx.MailErrorType to DB enum value
|
|
func MapSeverity(errType errx.MailErrorType) string {
|
|
switch errType {
|
|
case errx.MailErrorCritical:
|
|
return "CRITICAL"
|
|
case errx.MailErrorWarning:
|
|
return "WARNING"
|
|
case errx.MailErrorInformational:
|
|
return "INFORMATIONAL"
|
|
default:
|
|
return "WARNING"
|
|
}
|
|
}
|
|
|
|
// GetUnresolvedByCode checks if there's already an unresolved error with the same code
|
|
func (r *emailAccountErrorRepository) GetUnresolvedByCode(ctx context.Context, accountID uuid.UUID, errorCode string) (*EmailAccountError, *errx.Error) {
|
|
query := `
|
|
SELECT id, email_account_id, user_id, error_code, severity, resolve_method,
|
|
title, message, user_message, action_required, task_id,
|
|
resolved_at, resolved_by, created_at
|
|
FROM email_account_errors
|
|
WHERE email_account_id = $1
|
|
AND error_code = $2
|
|
AND resolved_at IS NULL
|
|
ORDER BY created_at DESC
|
|
LIMIT 1
|
|
`
|
|
|
|
var e EmailAccountError
|
|
err := r.DB.QueryRow(ctx, query, accountID, errorCode).Scan(
|
|
&e.ID, &e.EmailAccountID, &e.UserID, &e.ErrorCode, &e.Severity, &e.ResolveMethod,
|
|
&e.Title, &e.Message, &e.UserMessage, &e.ActionRequired, &e.TaskID,
|
|
&e.ResolvedAt, &e.ResolvedBy, &e.CreatedAt,
|
|
)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return nil, nil // No existing error found
|
|
}
|
|
db.CaptureError(err, query, []any{accountID, errorCode}, "queryrow")
|
|
return nil, errx.InternalError()
|
|
}
|
|
|
|
return &e, nil
|
|
}
|