ghost-listmonk-connector/services/son_execution_logger.go
troneras ff5b39241a first commit
fixed cache

Refactoring from app router to page router

Refactoring from app router to page router

Add authentication with JWT

base ui done

protect the dashboard

added transitions

styling

type trick added

cleanup

Add enable-disable logic

add transactional emails

Improvements on delay and frontend

added description on select

implement auth with magic link

migrate to mariadb

webhook signature working

Adding async processing with queues

Implemented webhook reply functionality

Add son execution logs

update status logic

Add recent activity monitoring to sons

show son performance in dashboard

add readme

implement listmonk connector

listmonk-connector-v1

Create CODE_OF_CONDUCT.md

Create LICENSE
2024-08-21 02:09:24 +02:00

187 lines
5 KiB
Go

// services/son_execution_logger.go
package services
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"time"
"github.com/redis/go-redis/v9"
"github.com/troneras/ghost-listmonk-connector/database"
"github.com/troneras/ghost-listmonk-connector/models"
"github.com/troneras/ghost-listmonk-connector/utils"
)
type SonExecutionLogger struct {
db *sql.DB
redis *redis.Client
}
func NewSonExecutionLogger(redisAddr string) *SonExecutionLogger {
return &SonExecutionLogger{
db: database.GetDB(),
redis: redis.NewClient(&redis.Options{Addr: redisAddr}),
}
}
type SonStats struct {
Name string `json:"name"`
Executions int `json:"executions"`
Success int `json:"success"`
Failure int `json:"failure"`
}
func (l *SonExecutionLogger) LogSonExecution(sonID, webhookLogID string, status string, errorMessage string) (string, error) {
executionID := utils.GenerateUUID()
_, err := l.db.Exec(`
INSERT INTO son_execution_logs (id, son_id, webhook_log_id, execution_status, error_message)
VALUES (?, ?, ?, ?, ?)
`, executionID, sonID, webhookLogID, status, errorMessage)
if err != nil {
return "", err
}
return executionID, nil
}
func (l *SonExecutionLogger) LogActionExecution(executionID string, actionType string, status string, errorMessage string) error {
_, err := l.db.Exec(`
INSERT INTO son_execution_action_logs (id, son_execution_log_id, action_type, action_status, error_message)
VALUES (?, ?, ?, ?, ?)
`, utils.GenerateUUID(), executionID, actionType, status, errorMessage)
if status == "failure" {
l.UpdateSonExecutionStatus(executionID, "failure", errorMessage)
}
return err
}
func (l *SonExecutionLogger) GetSonExecutionLogs(userID string, limit, offset int) ([]models.SonExecutionLog, int, error) {
var total int
err := l.db.QueryRow(`
SELECT COUNT(*)
FROM son_execution_logs sel
JOIN sons s ON sel.son_id = s.id
WHERE s.user_id = ?
`, userID).Scan(&total)
if err != nil {
return nil, 0, err
}
rows, err := l.db.Query(`
SELECT sel.id, sel.son_id, sel.webhook_log_id, sel.execution_status, sel.executed_at, sel.error_message
FROM son_execution_logs sel
JOIN sons s ON sel.son_id = s.id
WHERE s.user_id = ?
ORDER BY sel.executed_at DESC
LIMIT ? OFFSET ?
`, userID, limit, offset)
if err != nil {
return nil, 0, err
}
defer rows.Close()
var logs []models.SonExecutionLog
for rows.Next() {
var log models.SonExecutionLog
err := rows.Scan(&log.ID, &log.SonID, &log.WebhookLogID, &log.Status, &log.ExecutedAt, &log.ErrorMessage)
if err != nil {
return nil, 0, err
}
logs = append(logs, log)
}
return logs, total, nil
}
func (l *SonExecutionLogger) GetActionExecutionLogs(executionID string) ([]models.ActionExecutionLog, error) {
rows, err := l.db.Query(`
SELECT id, son_execution_log_id, action_type, action_status, executed_at, error_message
FROM son_execution_action_logs
WHERE son_execution_log_id = ?
ORDER BY executed_at ASC
`, executionID)
if err != nil {
return nil, err
}
defer rows.Close()
var logs []models.ActionExecutionLog
for rows.Next() {
var log models.ActionExecutionLog
err := rows.Scan(&log.ID, &log.ExecutionLogID, &log.ActionType, &log.Status, &log.ExecutedAt, &log.ErrorMessage)
if err != nil {
return nil, err
}
logs = append(logs, log)
}
return logs, nil
}
func (l *SonExecutionLogger) UpdateSonExecutionStatus(executionID string, status string, errorMessage string) error {
_, err := l.db.Exec(`
UPDATE son_execution_logs
SET execution_status = ?, error_message = ?
WHERE id = ?
`, status, errorMessage, executionID)
return err
}
func (l *SonExecutionLogger) GetSonStats(ctx context.Context, userID string, timeframe string) ([]SonStats, error) {
cacheKey := fmt.Sprintf("son_stats:%s:%s", userID, timeframe)
// Try to get from cache
cachedStats, err := l.redis.Get(ctx, cacheKey).Result()
if err == nil {
var stats []SonStats
err = json.Unmarshal([]byte(cachedStats), &stats)
if err == nil {
return stats, nil
}
}
// If not in cache or error, fetch from database
duration, err := utils.ParseDuration(timeframe)
if err != nil {
return nil, err
}
query := `
SELECT s.id, s.name,
COUNT(*) as executions,
SUM(CASE WHEN sel.execution_status = 'success' THEN 1 ELSE 0 END) as success,
SUM(CASE WHEN sel.execution_status = 'failure' THEN 1 ELSE 0 END) as failure
FROM sons s
LEFT JOIN son_execution_logs sel ON s.id = sel.son_id
WHERE s.user_id = ? AND sel.executed_at >= ?
GROUP BY s.id, s.name
`
rows, err := l.db.Query(query, userID, time.Now().Add(-duration))
if err != nil {
return nil, err
}
defer rows.Close()
var stats []SonStats
for rows.Next() {
var stat SonStats
var sonID string
err := rows.Scan(&sonID, &stat.Name, &stat.Executions, &stat.Success, &stat.Failure)
if err != nil {
return nil, err
}
stats = append(stats, stat)
}
// Cache the result
statsJSON, err := json.Marshal(stats)
if err == nil {
l.redis.Set(ctx, cacheKey, statsJSON, 5*time.Minute)
}
return stats, nil
}