mirror of
https://github.com/ddworken/hishtory.git
synced 2024-11-29 11:44:53 +01:00
190 lines
5.9 KiB
Go
190 lines
5.9 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
type UsageData struct {
|
|
UserId string `json:"user_id" gorm:"not null; uniqueIndex:usageDataUniqueIndex"`
|
|
DeviceId string `json:"device_id" gorm:"not null; uniqueIndex:usageDataUniqueIndex"`
|
|
LastUsed time.Time `json:"last_used"`
|
|
LastIp string `json:"last_ip"`
|
|
NumEntriesHandled int `json:"num_entries_handled"`
|
|
LastQueried time.Time `json:"last_queried"`
|
|
NumQueries int `json:"num_queries"`
|
|
Version string `json:"version"`
|
|
}
|
|
|
|
func (db *DB) UsageDataFindByUserAndDevice(ctx context.Context, userId, deviceId string) ([]UsageData, error) {
|
|
var usageData []UsageData
|
|
|
|
tx := db.DB.WithContext(ctx).Where("user_id = ? AND device_id = ?", userId, deviceId).Find(&usageData)
|
|
if tx.Error != nil {
|
|
return nil, fmt.Errorf("db.WithContext.Where.Find: %w", tx.Error)
|
|
}
|
|
|
|
if err := db.Where("user_id = ? AND device_id = ?", userId, deviceId).First(&usageData).Error; err != nil {
|
|
return nil, fmt.Errorf("db.Where: %w", err)
|
|
}
|
|
|
|
return usageData, nil
|
|
}
|
|
|
|
func (db *DB) CreateUsageData(ctx context.Context, usageData *UsageData) error {
|
|
tx := db.DB.WithContext(ctx).Create(usageData)
|
|
if tx.Error != nil {
|
|
return fmt.Errorf("db.WithContext.Create: %w", tx.Error)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateUsageData updates the entry for a given userID/deviceID pair with the lastUsed and lastIP values
|
|
func (db *DB) UpdateUsageData(ctx context.Context, userId, deviceId string, lastUsed time.Time, lastIP string) error {
|
|
tx := db.DB.WithContext(ctx).Model(&UsageData{}).
|
|
Where("user_id = ? AND device_id = ?", userId, deviceId).
|
|
Update("last_used", lastUsed).
|
|
Update("last_ip", lastIP)
|
|
|
|
if tx.Error != nil {
|
|
return fmt.Errorf("db.WithContext.Model.Where.Update: %w", tx.Error)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *DB) UpdateUsageDataForNumEntriesHandled(ctx context.Context, userId, deviceId string, numEntriesHandled int) error {
|
|
tx := db.DB.WithContext(ctx).Exec("UPDATE usage_data SET num_entries_handled = COALESCE(num_entries_handled, 0) + ? WHERE user_id = ? AND device_id = ?", numEntriesHandled, userId, deviceId)
|
|
|
|
if tx.Error != nil {
|
|
return fmt.Errorf("db.WithContext.Exec: %w", tx.Error)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *DB) UpdateUsageDataClientVersion(ctx context.Context, userID, deviceID, version string) error {
|
|
tx := db.DB.WithContext(ctx).Exec("UPDATE usage_data SET version = ? WHERE user_id = ? AND device_id = ?", version, userID, deviceID)
|
|
|
|
if tx.Error != nil {
|
|
return fmt.Errorf("db.WithContext.Exec: %w", tx.Error)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *DB) UpdateUsageDataNumberQueries(ctx context.Context, userID, deviceID string) error {
|
|
tx := db.DB.WithContext(ctx).Exec("UPDATE usage_data SET num_queries = COALESCE(num_queries, 0) + 1, last_queried = ? WHERE user_id = ? AND device_id = ?", time.Now(), userID, deviceID)
|
|
|
|
if tx.Error != nil {
|
|
return fmt.Errorf("db.WithContext.Exec: %w", tx.Error)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type UsageDataStats struct {
|
|
RegistrationDate time.Time
|
|
NumDevices int
|
|
NumEntries int
|
|
LastUsedDate time.Time
|
|
IpAddresses string
|
|
NumQueries int
|
|
LastQueried time.Time
|
|
Versions string
|
|
}
|
|
|
|
const usageDataStatsQuery = `
|
|
SELECT
|
|
MIN(devices.registration_date) as registration_date,
|
|
COUNT(DISTINCT devices.device_id) as num_devices,
|
|
SUM(usage_data.num_entries_handled) as num_history_entries,
|
|
MAX(usage_data.last_used) as last_active,
|
|
COALESCE(STRING_AGG(DISTINCT usage_data.last_ip, ', ') FILTER (WHERE usage_data.last_ip != 'Unknown' AND usage_data.last_ip != 'UnknownIp'), 'Unknown') as ip_addresses,
|
|
COALESCE(SUM(usage_data.num_queries), 0) as num_queries,
|
|
COALESCE(MAX(usage_data.last_queried), 'January 1, 1970') as last_queried,
|
|
STRING_AGG(DISTINCT usage_data.version, ', ') as versions
|
|
FROM devices
|
|
INNER JOIN usage_data ON devices.device_id = usage_data.device_id
|
|
GROUP BY devices.user_id
|
|
ORDER BY registration_date
|
|
`
|
|
|
|
func (db *DB) UsageDataStats(ctx context.Context) ([]*UsageDataStats, error) {
|
|
var resp []*UsageDataStats
|
|
|
|
rows, err := db.DB.WithContext(ctx).Raw(usageDataStatsQuery).Rows()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.WithContext.Raw.Rows: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
var usageData UsageDataStats
|
|
|
|
err := rows.Scan(
|
|
&usageData.RegistrationDate,
|
|
&usageData.NumDevices,
|
|
&usageData.NumEntries,
|
|
&usageData.LastUsedDate,
|
|
&usageData.IpAddresses,
|
|
&usageData.NumQueries,
|
|
&usageData.LastQueried,
|
|
&usageData.Versions,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("rows.Scan: %w", err)
|
|
}
|
|
|
|
resp = append(resp, &usageData)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (db *DB) UsageDataTotal(ctx context.Context) (int64, error) {
|
|
type numEntriesProcessed struct {
|
|
Total int
|
|
}
|
|
nep := numEntriesProcessed{}
|
|
|
|
tx := db.WithContext(ctx).Model(&UsageData{}).Select("SUM(num_entries_handled) as total").Find(&nep)
|
|
if tx.Error != nil {
|
|
return 0, fmt.Errorf("tx.Error: %w", tx.Error)
|
|
}
|
|
|
|
return int64(nep.Total), nil
|
|
}
|
|
|
|
func (db *DB) CountActiveInstalls(ctx context.Context, since time.Duration) (int64, error) {
|
|
var activeInstalls int64
|
|
tx := db.WithContext(ctx).Model(&UsageData{}).Where("last_used > ?", time.Now().Add(-since)).Count(&activeInstalls)
|
|
if tx.Error != nil {
|
|
return 0, fmt.Errorf("tx.Error: %w", tx.Error)
|
|
}
|
|
|
|
return activeInstalls, nil
|
|
}
|
|
|
|
func (db *DB) CountQueryUsers(ctx context.Context, since time.Duration) (int64, error) {
|
|
var activeQueryUsers int64
|
|
tx := db.WithContext(ctx).Model(&UsageData{}).Where("last_queried > ?", time.Now().Add(-since)).Count(&activeQueryUsers)
|
|
if tx.Error != nil {
|
|
return 0, fmt.Errorf("tx.Error: %w", tx.Error)
|
|
}
|
|
|
|
return activeQueryUsers, nil
|
|
}
|
|
|
|
func (db *DB) DateOfLastRegistration(ctx context.Context) (string, error) {
|
|
var lastRegistration string
|
|
row := db.WithContext(ctx).Raw("SELECT to_char(max(registration_date), 'DD Month YYYY HH24:MI') FROM devices").Row()
|
|
if err := row.Scan(&lastRegistration); err != nil {
|
|
return "", fmt.Errorf("row.Scan: %w", err)
|
|
}
|
|
|
|
return lastRegistration, nil
|
|
}
|