github.com/status-im/status-go@v1.1.0/centralizedmetrics/sqlite_persistence.go (about)

     1  package centralizedmetrics
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"encoding/json"
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/google/uuid"
    11  
    12  	"github.com/status-im/status-go/centralizedmetrics/common"
    13  )
    14  
    15  type SQLiteMetricRepository struct {
    16  	db *sql.DB
    17  }
    18  
    19  func NewSQLiteMetricRepository(db *sql.DB) *SQLiteMetricRepository {
    20  	return &SQLiteMetricRepository{db: db}
    21  }
    22  
    23  func (r *SQLiteMetricRepository) Poll() ([]common.Metric, error) {
    24  	tx, err := r.db.BeginTx(context.Background(), &sql.TxOptions{})
    25  	if err != nil {
    26  		return nil, err
    27  	}
    28  
    29  	defer func() {
    30  		if err == nil {
    31  			err = tx.Commit()
    32  			return
    33  		}
    34  		// don't shadow original error
    35  		_ = tx.Rollback()
    36  	}()
    37  
    38  	userID, err := r.UserID(tx)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	rows, err := tx.Query("SELECT id, event_name, event_value, platform, app_version, timestamp FROM centralizedmetrics_metrics limit 10")
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	defer rows.Close()
    48  
    49  	var metrics []common.Metric
    50  	for rows.Next() {
    51  		var metric common.Metric
    52  		var eventValue string
    53  
    54  		if err := rows.Scan(&metric.ID, &metric.EventName, &eventValue, &metric.Platform, &metric.AppVersion, &metric.Timestamp); err != nil {
    55  			return nil, err
    56  		}
    57  
    58  		// Deserialize eventValue
    59  		if err := json.Unmarshal([]byte(eventValue), &metric.EventValue); err != nil {
    60  			return nil, err
    61  		}
    62  
    63  		metric.UserID = userID
    64  
    65  		metrics = append(metrics, metric)
    66  	}
    67  
    68  	return metrics, rows.Err()
    69  }
    70  
    71  func (r *SQLiteMetricRepository) Delete(metrics []common.Metric) error {
    72  	tx, err := r.db.BeginTx(context.Background(), nil)
    73  	if err != nil {
    74  		return err
    75  	}
    76  	defer func() {
    77  		if err == nil {
    78  			err = tx.Commit()
    79  			return
    80  		}
    81  		// don't shadow original error
    82  		_ = tx.Rollback()
    83  	}()
    84  
    85  	stmt, err := tx.Prepare("DELETE FROM centralizedmetrics_metrics WHERE id = ?")
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	defer stmt.Close()
    91  
    92  	for _, metric := range metrics {
    93  		if _, err := stmt.Exec(metric.ID); err != nil {
    94  			return err
    95  		}
    96  	}
    97  
    98  	return tx.Commit()
    99  }
   100  
   101  func (r *SQLiteMetricRepository) Add(metric common.Metric) error {
   102  	eventValue, err := json.Marshal(metric.EventValue)
   103  	if err != nil {
   104  		return err
   105  	}
   106  
   107  	_, err = r.db.Exec("INSERT INTO centralizedmetrics_metrics (id, event_name, event_value, platform, app_version, timestamp) VALUES (?, ?, ?, ?, ?, ?)",
   108  		metric.ID, metric.EventName, string(eventValue), metric.Platform, metric.AppVersion, time.Now().UnixNano()/int64(time.Millisecond))
   109  	return err
   110  }
   111  
   112  func (r *SQLiteMetricRepository) UserID(tx *sql.Tx) (string, error) {
   113  	var err error
   114  	if tx == nil {
   115  		tx, err = r.db.BeginTx(context.Background(), &sql.TxOptions{})
   116  		if err != nil {
   117  			return "", err
   118  		}
   119  		defer func() {
   120  			if err == nil {
   121  				err = tx.Commit()
   122  				return
   123  			}
   124  			// don't shadow original error
   125  			_ = tx.Rollback()
   126  		}()
   127  	}
   128  
   129  	var userID string
   130  
   131  	// Check if a UUID already exists in the table
   132  	err = tx.QueryRow("SELECT uuid FROM centralizedmetrics_uuid LIMIT 1").Scan(&userID)
   133  	if err != nil {
   134  		if err == sql.ErrNoRows {
   135  			// clean up err
   136  			err = nil
   137  			// Generate a new UUID
   138  			newUUID := uuid.New().String()
   139  
   140  			// Insert the new UUID into the table
   141  			_, err := tx.Exec("INSERT INTO centralizedmetrics_uuid (uuid) VALUES (?)", newUUID)
   142  			if err != nil {
   143  				return "", fmt.Errorf("failed to insert new UUID: %v", err)
   144  			}
   145  
   146  			return newUUID, nil
   147  		}
   148  		return "", fmt.Errorf("failed to query for existing UUID: %v", err)
   149  	}
   150  
   151  	return userID, nil
   152  }
   153  
   154  func (r *SQLiteMetricRepository) ToggleEnabled(enabled bool) error {
   155  	tx, err := r.db.BeginTx(context.Background(), &sql.TxOptions{})
   156  	if err != nil {
   157  		return err
   158  	}
   159  
   160  	defer func() {
   161  		if err == nil {
   162  			err = tx.Commit()
   163  			return
   164  		}
   165  		// don't shadow original error
   166  		_ = tx.Rollback()
   167  	}()
   168  
   169  	// make sure row is present
   170  	userID, err := r.UserID(tx)
   171  	if err != nil {
   172  		return err
   173  	}
   174  	_, err = tx.Exec("UPDATE centralizedmetrics_uuid SET enabled = ?, user_confirmed = 1 WHERE uuid = ?", enabled, userID)
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	// if we are enabling them, nothing else to do
   180  	if enabled {
   181  		return nil
   182  	}
   183  
   184  	// otherwise clean up metrics that might have been collected in the meantime
   185  	_, err = tx.Exec("DELETE FROM centralizedmetrics_metrics")
   186  	return err
   187  
   188  }
   189  
   190  func (r *SQLiteMetricRepository) Info() (*MetricsInfo, error) {
   191  	info := MetricsInfo{}
   192  	err := r.db.QueryRow("SELECT enabled,user_confirmed FROM centralizedmetrics_uuid LIMIT 1").Scan(&info.Enabled, &info.UserConfirmed)
   193  	if err == sql.ErrNoRows {
   194  		return &info, nil
   195  	}
   196  
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	return &info, nil
   201  }