Rename a bunch of DB functions + add error checking for DB table creation (follow up to #104)

This commit is contained in:
David Dworken 2023-09-12 08:30:43 -07:00
parent ea10050872
commit 50c74e5881
No known key found for this signature in database
5 changed files with 156 additions and 168 deletions

View File

@ -60,7 +60,7 @@ func updateUsageData(r *http.Request, userId, deviceId string, numEntriesHandled
return fmt.Errorf("db.UsageDataFindByUserAndDevice: %w", err) return fmt.Errorf("db.UsageDataFindByUserAndDevice: %w", err)
} }
if len(usageData) == 0 { if len(usageData) == 0 {
err := GLOBAL_DB.UsageDataCreate( err := GLOBAL_DB.CreateUsageData(
r.Context(), r.Context(),
&shared.UsageData{ &shared.UsageData{
UserId: userId, UserId: userId,
@ -71,28 +71,28 @@ func updateUsageData(r *http.Request, userId, deviceId string, numEntriesHandled
}, },
) )
if err != nil { if err != nil {
return fmt.Errorf("db.UsageDataCreate: %w", err) return fmt.Errorf("db.CreateUsageData: %w", err)
} }
} else { } else {
usage := usageData[0] usage := usageData[0]
if err := GLOBAL_DB.UsageDataUpdate(r.Context(), userId, deviceId, time.Now(), getRemoteAddr(r)); err != nil { if err := GLOBAL_DB.UpdateUsageData(r.Context(), userId, deviceId, time.Now(), getRemoteAddr(r)); err != nil {
return fmt.Errorf("db.UsageDataUpdate: %w", err) return fmt.Errorf("db.UpdateUsageData: %w", err)
} }
if numEntriesHandled > 0 { if numEntriesHandled > 0 {
if err := GLOBAL_DB.UsageDataUpdateNumEntriesHandled(r.Context(), userId, deviceId, numEntriesHandled); err != nil { if err := GLOBAL_DB.UpdateUsageDataForNumEntriesHandled(r.Context(), userId, deviceId, numEntriesHandled); err != nil {
return fmt.Errorf("db.UsageDataUpdateNumEntriesHandled: %w", err) return fmt.Errorf("db.UpdateUsageDataForNumEntriesHandled: %w", err)
} }
} }
if usage.Version != getHishtoryVersion(r) { if usage.Version != getHishtoryVersion(r) {
if err := GLOBAL_DB.UsageDataUpdateVersion(r.Context(), userId, deviceId, getHishtoryVersion(r)); err != nil { if err := GLOBAL_DB.UpdateUsageDataClientVersion(r.Context(), userId, deviceId, getHishtoryVersion(r)); err != nil {
return fmt.Errorf("db.UsageDataUpdateVersion: %w", err) return fmt.Errorf("db.UpdateUsageDataClientVersion: %w", err)
} }
} }
} }
if isQuery { if isQuery {
if err := GLOBAL_DB.UsageDataUpdateNumQueries(r.Context(), userId, deviceId); err != nil { if err := GLOBAL_DB.UpdateUsageDataNumberQueries(r.Context(), userId, deviceId); err != nil {
return fmt.Errorf("db.UsageDataUpdateNumQueries: %w", err) return fmt.Errorf("db.UpdateUsageDataNumberQueries: %w", err)
} }
} }
@ -125,23 +125,23 @@ func usageStatsHandler(w http.ResponseWriter, r *http.Request) {
} }
func statsHandler(w http.ResponseWriter, r *http.Request) { func statsHandler(w http.ResponseWriter, r *http.Request) {
numDevices, err := GLOBAL_DB.DevicesCount(r.Context()) numDevices, err := GLOBAL_DB.CountAllDevices(r.Context())
checkGormError(err, 0) checkGormError(err, 0)
numEntriesProcessed, err := GLOBAL_DB.UsageDataTotal(r.Context()) numEntriesProcessed, err := GLOBAL_DB.UsageDataTotal(r.Context())
checkGormError(err, 0) checkGormError(err, 0)
numDbEntries, err := GLOBAL_DB.EncHistoryEntryCount(r.Context()) numDbEntries, err := GLOBAL_DB.CountHistoryEntries(r.Context())
checkGormError(err, 0) checkGormError(err, 0)
oneWeek := time.Hour * 24 * 7 oneWeek := time.Hour * 24 * 7
weeklyActiveInstalls, err := GLOBAL_DB.WeeklyActiveInstalls(r.Context(), oneWeek) weeklyActiveInstalls, err := GLOBAL_DB.CountActiveInstalls(r.Context(), oneWeek)
checkGormError(err, 0) checkGormError(err, 0)
weeklyQueryUsers, err := GLOBAL_DB.WeeklyQueryUsers(r.Context(), oneWeek) weeklyQueryUsers, err := GLOBAL_DB.CountQueryUsers(r.Context(), oneWeek)
checkGormError(err, 0) checkGormError(err, 0)
lastRegistration, err := GLOBAL_DB.LastRegistration(r.Context()) lastRegistration, err := GLOBAL_DB.DateOfLastRegistration(r.Context())
checkGormError(err, 0) checkGormError(err, 0)
_, _ = fmt.Fprintf(w, "Num devices: %d\n", numDevices) _, _ = fmt.Fprintf(w, "Num devices: %d\n", numDevices)
@ -166,9 +166,7 @@ func apiSubmitHandler(w http.ResponseWriter, r *http.Request) {
if len(entries) == 0 { if len(entries) == 0 {
return return
} }
if err := updateUsageData(r, entries[0].UserId, entries[0].DeviceId, len(entries), false); err != nil { _ = updateUsageData(r, entries[0].UserId, entries[0].DeviceId /* numEntriesHandled = */, len(entries) /* isQuery = */, false)
fmt.Printf("updateUsageData: %v\n", err)
}
devices, err := GLOBAL_DB.DevicesForUser(r.Context(), entries[0].UserId) devices, err := GLOBAL_DB.DevicesForUser(r.Context(), entries[0].UserId)
checkGormError(err, 0) checkGormError(err, 0)
@ -178,7 +176,7 @@ func apiSubmitHandler(w http.ResponseWriter, r *http.Request) {
} }
fmt.Printf("apiSubmitHandler: Found %d devices\n", len(devices)) fmt.Printf("apiSubmitHandler: Found %d devices\n", len(devices))
err = GLOBAL_DB.DeviceEntriesCreateChunk(r.Context(), devices, entries, 1000) err = GLOBAL_DB.AddHistoryEntriesForAllDevices(r.Context(), devices, entries)
if err != nil { if err != nil {
panic(fmt.Errorf("failed to execute transaction to add entries to DB: %w", err)) panic(fmt.Errorf("failed to execute transaction to add entries to DB: %w", err))
} }
@ -193,8 +191,8 @@ func apiSubmitHandler(w http.ResponseWriter, r *http.Request) {
func apiBootstrapHandler(w http.ResponseWriter, r *http.Request) { func apiBootstrapHandler(w http.ResponseWriter, r *http.Request) {
userId := getRequiredQueryParam(r, "user_id") userId := getRequiredQueryParam(r, "user_id")
deviceId := getRequiredQueryParam(r, "device_id") deviceId := getRequiredQueryParam(r, "device_id")
updateUsageData(r, userId, deviceId, 0, false) _ = updateUsageData(r, userId, deviceId /* numEntriesHandled = */, 0 /* isQuery = */, false)
historyEntries, err := GLOBAL_DB.EncHistoryEntriesForUser(r.Context(), userId) historyEntries, err := GLOBAL_DB.AllHistoryEntriesForUser(r.Context(), userId)
checkGormError(err, 1) checkGormError(err, 1)
fmt.Printf("apiBootstrapHandler: Found %d entries\n", len(historyEntries)) fmt.Printf("apiBootstrapHandler: Found %d entries\n", len(historyEntries))
if err := json.NewEncoder(w).Encode(historyEntries); err != nil { if err := json.NewEncoder(w).Encode(historyEntries); err != nil {
@ -206,7 +204,7 @@ func apiQueryHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
userId := getRequiredQueryParam(r, "user_id") userId := getRequiredQueryParam(r, "user_id")
deviceId := getRequiredQueryParam(r, "device_id") deviceId := getRequiredQueryParam(r, "device_id")
updateUsageData(r, userId, deviceId, 0, true) _ = updateUsageData(r, userId, deviceId /* numEntriesHandled = */, 0 /* isQuery = */, true)
// Delete any entries that match a pending deletion request // Delete any entries that match a pending deletion request
deletionRequests, err := GLOBAL_DB.DeletionRequestsForUserAndDevice(r.Context(), userId, deviceId) deletionRequests, err := GLOBAL_DB.DeletionRequestsForUserAndDevice(r.Context(), userId, deviceId)
@ -217,7 +215,7 @@ func apiQueryHandler(w http.ResponseWriter, r *http.Request) {
} }
// Then retrieve // Then retrieve
historyEntries, err := GLOBAL_DB.EncHistoryEntriesForDevice(r.Context(), deviceId, 5) historyEntries, err := GLOBAL_DB.HistoryEntriesForDevice(r.Context(), deviceId, 5)
checkGormError(err, 0) checkGormError(err, 0)
fmt.Printf("apiQueryHandler: Found %d entries for %s\n", len(historyEntries), r.URL) fmt.Printf("apiQueryHandler: Found %d entries for %s\n", len(historyEntries), r.URL)
if err := json.NewEncoder(w).Encode(historyEntries); err != nil { if err := json.NewEncoder(w).Encode(historyEntries); err != nil {
@ -229,11 +227,11 @@ func apiQueryHandler(w http.ResponseWriter, r *http.Request) {
if isProductionEnvironment() { if isProductionEnvironment() {
go func() { go func() {
span, ctx := tracer.StartSpanFromContext(ctx, "apiQueryHandler.incrementReadCount") span, ctx := tracer.StartSpanFromContext(ctx, "apiQueryHandler.incrementReadCount")
err := GLOBAL_DB.DeviceIncrementReadCounts(ctx, deviceId) err := GLOBAL_DB.IncrementEntryReadCountsForDevice(ctx, deviceId)
span.Finish(tracer.WithError(err)) span.Finish(tracer.WithError(err))
}() }()
} else { } else {
err := GLOBAL_DB.DeviceIncrementReadCounts(ctx, deviceId) err := GLOBAL_DB.IncrementEntryReadCountsForDevice(ctx, deviceId)
if err != nil { if err != nil {
panic("failed to increment read counts") panic("failed to increment read counts")
} }
@ -265,10 +263,10 @@ func apiRegisterHandler(w http.ResponseWriter, r *http.Request) {
userId := getRequiredQueryParam(r, "user_id") userId := getRequiredQueryParam(r, "user_id")
deviceId := getRequiredQueryParam(r, "device_id") deviceId := getRequiredQueryParam(r, "device_id")
existingDevicesCount, err := GLOBAL_DB.DevicesCountForUser(r.Context(), userId) existingDevicesCount, err := GLOBAL_DB.CountDevicesForUser(r.Context(), userId)
checkGormError(err, 0) checkGormError(err, 0)
fmt.Printf("apiRegisterHandler: existingDevicesCount=%d\n", existingDevicesCount) fmt.Printf("apiRegisterHandler: existingDevicesCount=%d\n", existingDevicesCount)
if err := GLOBAL_DB.DeviceCreate(r.Context(), &shared.Device{UserId: userId, DeviceId: deviceId, RegistrationIp: getRemoteAddr(r), RegistrationDate: time.Now()}); err != nil { if err := GLOBAL_DB.CreateDevice(r.Context(), &shared.Device{UserId: userId, DeviceId: deviceId, RegistrationIp: getRemoteAddr(r), RegistrationDate: time.Now()}); err != nil {
checkGormError(err, 0) checkGormError(err, 0)
} }
@ -276,9 +274,7 @@ func apiRegisterHandler(w http.ResponseWriter, r *http.Request) {
err := GLOBAL_DB.DumpRequestCreate(r.Context(), &shared.DumpRequest{UserId: userId, RequestingDeviceId: deviceId, RequestTime: time.Now()}) err := GLOBAL_DB.DumpRequestCreate(r.Context(), &shared.DumpRequest{UserId: userId, RequestingDeviceId: deviceId, RequestTime: time.Now()})
checkGormError(err, 0) checkGormError(err, 0)
} }
if err := updateUsageData(r, userId, deviceId, 0, false); err != nil { _ = updateUsageData(r, userId, deviceId /* numEntriesHandled = */, 0 /* isQuery = */, false)
fmt.Printf("updateUsageData: %v\n", err)
}
if GLOBAL_STATSD != nil { if GLOBAL_STATSD != nil {
GLOBAL_STATSD.Incr("hishtory.register", []string{}, 1.0) GLOBAL_STATSD.Incr("hishtory.register", []string{}, 1.0)
@ -324,11 +320,11 @@ func apiSubmitDumpHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
err = GLOBAL_DB.EncHistoryCreateMulti(r.Context(), entries...) err = GLOBAL_DB.AddHistoryEntries(r.Context(), entries...)
checkGormError(err, 0) checkGormError(err, 0)
err = GLOBAL_DB.DumpRequestDeleteForUserAndDevice(r.Context(), userId, requestingDeviceId) err = GLOBAL_DB.DumpRequestDeleteForUserAndDevice(r.Context(), userId, requestingDeviceId)
checkGormError(err, 0) checkGormError(err, 0)
updateUsageData(r, userId, srcDeviceId, len(entries), false) _ = updateUsageData(r, userId, srcDeviceId /* numEntriesHandled = */, len(entries) /* isQuery = */, false)
w.Header().Set("Content-Length", "0") w.Header().Set("Content-Length", "0")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
@ -384,28 +380,19 @@ func addDeletionRequestHandler(w http.ResponseWriter, r *http.Request) {
func healthCheckHandler(w http.ResponseWriter, r *http.Request) { func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
if isProductionEnvironment() { if isProductionEnvironment() {
// Check that we have a reasonable looking set of devices/entries in the DB encHistoryEntryCount, err := GLOBAL_DB.CountHistoryEntries(r.Context())
//rows, err := GLOBAL_DB.Raw("SELECT true FROM enc_history_entries LIMIT 1 OFFSET 1000").Rows()
//if err != nil {
// panic(fmt.Sprintf("failed to count entries in DB: %v", err))
//}
//defer rows.Close()
//if !rows.Next() {
// panic("Suspiciously few enc history entries!")
//}
encHistoryEntryCount, err := GLOBAL_DB.EncHistoryEntryCount(r.Context())
checkGormError(err, 0) checkGormError(err, 0)
if encHistoryEntryCount < 1000 { if encHistoryEntryCount < 1000 {
panic("Suspiciously few enc history entries!") panic("Suspiciously few enc history entries!")
} }
deviceCount, err := GLOBAL_DB.DevicesCount(r.Context()) deviceCount, err := GLOBAL_DB.CountAllDevices(r.Context())
checkGormError(err, 0) checkGormError(err, 0)
if deviceCount < 100 { if deviceCount < 100 {
panic("Suspiciously few devices!") panic("Suspiciously few devices!")
} }
// Check that we can write to the DB. This entry will get written and then eventually cleaned by the cron. // Check that we can write to the DB. This entry will get written and then eventually cleaned by the cron.
err = GLOBAL_DB.EncHistoryCreate(r.Context(), &shared.EncHistoryEntry{ err = GLOBAL_DB.AddHistoryEntries(r.Context(), &shared.EncHistoryEntry{
EncryptedData: []byte("data"), EncryptedData: []byte("data"),
Nonce: []byte("nonce"), Nonce: []byte("nonce"),
DeviceId: "healthcheck_device_id", DeviceId: "healthcheck_device_id",
@ -432,7 +419,7 @@ func wipeDbEntriesHandler(w http.ResponseWriter, r *http.Request) {
panic("refusing to wipe the DB non-test environment") panic("refusing to wipe the DB non-test environment")
} }
err := GLOBAL_DB.EncHistoryClear(r.Context()) err := GLOBAL_DB.Unsafe_DeleteAllHistoryEntries(r.Context())
checkGormError(err, 0) checkGormError(err, 0)
w.Header().Set("Content-Length", "0") w.Header().Set("Content-Length", "0")
@ -468,7 +455,10 @@ func OpenDB() (*database.DB, error) {
} }
underlyingDb.SetMaxOpenConns(1) underlyingDb.SetMaxOpenConns(1)
db.Exec("PRAGMA journal_mode = WAL") db.Exec("PRAGMA journal_mode = WAL")
db.AddDatabaseTables() err = db.AddDatabaseTables()
if err != nil {
return nil, fmt.Errorf("failed to create underlying DB tables: %w", err)
}
return db, nil return db, nil
} }
@ -506,7 +496,10 @@ func OpenDB() (*database.DB, error) {
return nil, fmt.Errorf("failed to connect to the DB: %w", err) return nil, fmt.Errorf("failed to connect to the DB: %w", err)
} }
} }
db.AddDatabaseTables() err := db.AddDatabaseTables()
if err != nil {
return nil, fmt.Errorf("failed to create underlying DB tables: %w", err)
}
return db, nil return db, nil
} }
@ -901,3 +894,4 @@ func getMaximumNumberOfAllowedUsers() int {
} }
// TODO(optimization): Maybe optimize the endpoints a bit to reduce the number of round trips required? // TODO(optimization): Maybe optimize the endpoints a bit to reduce the number of round trips required?
// TODO: Add error checking for the calls to updateUsageData(...) that logs it/triggers an alert in prod, but is an error in test

View File

@ -3,21 +3,11 @@ package database
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/ddworken/hishtory/shared" "github.com/ddworken/hishtory/shared"
"gorm.io/gorm"
) )
func (db *DB) DevicesCountForUser(ctx context.Context, userID string) (int64, error) { func (db *DB) CountAllDevices(ctx context.Context) (int64, error) {
var existingDevicesCount int64
tx := db.WithContext(ctx).Model(&shared.Device{}).Where("user_id = ?", userID).Count(&existingDevicesCount)
if tx.Error != nil {
return 0, fmt.Errorf("tx.Error: %w", tx.Error)
}
return existingDevicesCount, nil
}
func (db *DB) DevicesCount(ctx context.Context) (int64, error) {
var numDevices int64 = 0 var numDevices int64 = 0
tx := db.WithContext(ctx).Model(&shared.Device{}).Count(&numDevices) tx := db.WithContext(ctx).Model(&shared.Device{}).Count(&numDevices)
if tx.Error != nil { if tx.Error != nil {
@ -27,7 +17,17 @@ func (db *DB) DevicesCount(ctx context.Context) (int64, error) {
return numDevices, nil return numDevices, nil
} }
func (db *DB) DeviceCreate(ctx context.Context, device *shared.Device) error { func (db *DB) CountDevicesForUser(ctx context.Context, userID string) (int64, error) {
var existingDevicesCount int64
tx := db.WithContext(ctx).Model(&shared.Device{}).Where("user_id = ?", userID).Count(&existingDevicesCount)
if tx.Error != nil {
return 0, fmt.Errorf("tx.Error: %w", tx.Error)
}
return existingDevicesCount, nil
}
func (db *DB) CreateDevice(ctx context.Context, device *shared.Device) error {
tx := db.WithContext(ctx).Create(device) tx := db.WithContext(ctx).Create(device)
if tx.Error != nil { if tx.Error != nil {
return fmt.Errorf("tx.Error: %w", tx.Error) return fmt.Errorf("tx.Error: %w", tx.Error)
@ -36,24 +36,6 @@ func (db *DB) DeviceCreate(ctx context.Context, device *shared.Device) error {
return nil return nil
} }
func (db *DB) DeviceEntriesCreateChunk(ctx context.Context, devices []*shared.Device, entries []*shared.EncHistoryEntry, chunkSize int) error {
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for _, device := range devices {
for _, entry := range entries {
entry.DeviceId = device.DeviceId
}
// Chunk the inserts to prevent the `extended protocol limited to 65535 parameters` error
for _, entriesChunk := range shared.Chunks(entries, chunkSize) {
resp := tx.Create(&entriesChunk)
if resp.Error != nil {
return fmt.Errorf("resp.Error: %w", resp.Error)
}
}
}
return nil
})
}
func (db *DB) DevicesForUser(ctx context.Context, userID string) ([]*shared.Device, error) { func (db *DB) DevicesForUser(ctx context.Context, userID string) ([]*shared.Device, error) {
var devices []*shared.Device var devices []*shared.Device
tx := db.WithContext(ctx).Where("user_id = ?", userID).Find(&devices) tx := db.WithContext(ctx).Where("user_id = ?", userID).Find(&devices)
@ -63,7 +45,3 @@ func (db *DB) DevicesForUser(ctx context.Context, userID string) ([]*shared.Devi
return devices, nil return devices, nil
} }
func (db *DB) DeviceIncrementReadCounts(ctx context.Context, deviceID string) error {
return db.WithContext(ctx).Exec("UPDATE enc_history_entries SET read_count = read_count + 1 WHERE device_id = ?", deviceID).Error
}

View File

@ -1,70 +0,0 @@
package database
import (
"context"
"fmt"
"github.com/ddworken/hishtory/shared"
"gorm.io/gorm"
)
func (db *DB) EncHistoryEntryCount(ctx context.Context) (int64, error) {
var numDbEntries int64
tx := db.WithContext(ctx).Model(&shared.EncHistoryEntry{}).Count(&numDbEntries)
if tx.Error != nil {
return 0, fmt.Errorf("tx.Error: %w", tx.Error)
}
return numDbEntries, nil
}
func (db *DB) EncHistoryEntriesForUser(ctx context.Context, userID string) ([]*shared.EncHistoryEntry, error) {
var historyEntries []*shared.EncHistoryEntry
tx := db.WithContext(ctx).Where("user_id = ?", userID).Find(&historyEntries)
if tx.Error != nil {
return nil, fmt.Errorf("tx.Error: %w", tx.Error)
}
return historyEntries, nil
}
func (db *DB) EncHistoryEntriesForDevice(ctx context.Context, deviceID string, limit int) ([]*shared.EncHistoryEntry, error) {
var historyEntries []*shared.EncHistoryEntry
tx := db.WithContext(ctx).Where("device_id = ? AND read_count < ?", deviceID, limit).Find(&historyEntries)
if tx.Error != nil {
return nil, fmt.Errorf("tx.Error: %w", tx.Error)
}
return historyEntries, nil
}
func (db *DB) EncHistoryCreate(ctx context.Context, entry *shared.EncHistoryEntry) error {
tx := db.WithContext(ctx).Create(entry)
if tx.Error != nil {
return fmt.Errorf("tx.Error: %w", tx.Error)
}
return nil
}
func (db *DB) EncHistoryCreateMulti(ctx context.Context, entries ...*shared.EncHistoryEntry) error {
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for _, entry := range entries {
resp := tx.Create(&entry)
if resp.Error != nil {
return fmt.Errorf("resp.Error: %w", resp.Error)
}
}
return nil
})
}
func (db *DB) EncHistoryClear(ctx context.Context) error {
tx := db.WithContext(ctx).Exec("DELETE FROM enc_history_entries")
if tx.Error != nil {
return fmt.Errorf("tx.Error: %w", tx.Error)
}
return nil
}

View File

@ -0,0 +1,85 @@
package database
import (
"context"
"fmt"
"github.com/ddworken/hishtory/shared"
"gorm.io/gorm"
)
func (db *DB) CountHistoryEntries(ctx context.Context) (int64, error) {
var numDbEntries int64
tx := db.WithContext(ctx).Model(&shared.EncHistoryEntry{}).Count(&numDbEntries)
if tx.Error != nil {
return 0, fmt.Errorf("tx.Error: %w", tx.Error)
}
return numDbEntries, nil
}
func (db *DB) AllHistoryEntriesForUser(ctx context.Context, userID string) ([]*shared.EncHistoryEntry, error) {
var historyEntries []*shared.EncHistoryEntry
tx := db.WithContext(ctx).Where("user_id = ?", userID).Find(&historyEntries)
if tx.Error != nil {
return nil, fmt.Errorf("tx.Error: %w", tx.Error)
}
return historyEntries, nil
}
func (db *DB) HistoryEntriesForDevice(ctx context.Context, deviceID string, limit int) ([]*shared.EncHistoryEntry, error) {
var historyEntries []*shared.EncHistoryEntry
tx := db.WithContext(ctx).Where("device_id = ? AND read_count < ?", deviceID, limit).Find(&historyEntries)
if tx.Error != nil {
return nil, fmt.Errorf("tx.Error: %w", tx.Error)
}
return historyEntries, nil
}
func (db *DB) AddHistoryEntries(ctx context.Context, entries ...*shared.EncHistoryEntry) error {
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for _, entry := range entries {
resp := tx.Create(&entry)
if resp.Error != nil {
return fmt.Errorf("resp.Error: %w", resp.Error)
}
}
return nil
})
}
func (db *DB) AddHistoryEntriesForAllDevices(ctx context.Context, devices []*shared.Device, entries []*shared.EncHistoryEntry) error {
chunkSize := 1000
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for _, device := range devices {
for _, entry := range entries {
entry.DeviceId = device.DeviceId
}
// Chunk the inserts to prevent the `extended protocol limited to 65535 parameters` error
for _, entriesChunk := range shared.Chunks(entries, chunkSize) {
resp := tx.Create(&entriesChunk)
if resp.Error != nil {
return fmt.Errorf("resp.Error: %w", resp.Error)
}
}
}
return nil
})
}
func (db *DB) Unsafe_DeleteAllHistoryEntries(ctx context.Context) error {
tx := db.WithContext(ctx).Exec("DELETE FROM enc_history_entries")
if tx.Error != nil {
return fmt.Errorf("tx.Error: %w", tx.Error)
}
return nil
}
func (db *DB) IncrementEntryReadCountsForDevice(ctx context.Context, deviceID string) error {
return db.WithContext(ctx).Exec("UPDATE enc_history_entries SET read_count = read_count + 1 WHERE device_id = ?", deviceID).Error
}

View File

@ -3,8 +3,9 @@ package database
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/ddworken/hishtory/shared"
"time" "time"
"github.com/ddworken/hishtory/shared"
) )
func (db *DB) UsageDataFindByUserAndDevice(ctx context.Context, userId, deviceId string) ([]shared.UsageData, error) { func (db *DB) UsageDataFindByUserAndDevice(ctx context.Context, userId, deviceId string) ([]shared.UsageData, error) {
@ -22,7 +23,7 @@ func (db *DB) UsageDataFindByUserAndDevice(ctx context.Context, userId, deviceId
return usageData, nil return usageData, nil
} }
func (db *DB) UsageDataCreate(ctx context.Context, usageData *shared.UsageData) error { func (db *DB) CreateUsageData(ctx context.Context, usageData *shared.UsageData) error {
tx := db.DB.WithContext(ctx).Create(usageData) tx := db.DB.WithContext(ctx).Create(usageData)
if tx.Error != nil { if tx.Error != nil {
return fmt.Errorf("db.WithContext.Create: %w", tx.Error) return fmt.Errorf("db.WithContext.Create: %w", tx.Error)
@ -31,8 +32,8 @@ func (db *DB) UsageDataCreate(ctx context.Context, usageData *shared.UsageData)
return nil return nil
} }
// UsageDataUpdate updates the entry for a given userID/deviceID pair with the lastUsed and lastIP values // UpdateUsageData updates the entry for a given userID/deviceID pair with the lastUsed and lastIP values
func (db *DB) UsageDataUpdate(ctx context.Context, userId, deviceId string, lastUsed time.Time, lastIP string) error { func (db *DB) UpdateUsageData(ctx context.Context, userId, deviceId string, lastUsed time.Time, lastIP string) error {
tx := db.DB.WithContext(ctx).Model(&shared.UsageData{}). tx := db.DB.WithContext(ctx).Model(&shared.UsageData{}).
Where("user_id = ? AND device_id = ?", userId, deviceId). Where("user_id = ? AND device_id = ?", userId, deviceId).
Update("last_used", lastUsed). Update("last_used", lastUsed).
@ -45,7 +46,7 @@ func (db *DB) UsageDataUpdate(ctx context.Context, userId, deviceId string, last
return nil return nil
} }
func (db *DB) UsageDataUpdateNumEntriesHandled(ctx context.Context, userId, deviceId string, numEntriesHandled int) error { 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) 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 { if tx.Error != nil {
@ -55,7 +56,7 @@ func (db *DB) UsageDataUpdateNumEntriesHandled(ctx context.Context, userId, devi
return nil return nil
} }
func (db *DB) UsageDataUpdateVersion(ctx context.Context, userID, deviceID string, version string) error { func (db *DB) UpdateUsageDataClientVersion(ctx context.Context, userID, deviceID string, version string) error {
tx := db.DB.WithContext(ctx).Exec("UPDATE usage_data SET version = ? WHERE user_id = ? AND device_id = ?", version, userID, deviceID) tx := db.DB.WithContext(ctx).Exec("UPDATE usage_data SET version = ? WHERE user_id = ? AND device_id = ?", version, userID, deviceID)
if tx.Error != nil { if tx.Error != nil {
@ -65,7 +66,7 @@ func (db *DB) UsageDataUpdateVersion(ctx context.Context, userID, deviceID strin
return nil return nil
} }
func (db *DB) UsageDataUpdateNumQueries(ctx context.Context, userID, deviceID string) error { 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) 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 { if tx.Error != nil {
@ -148,27 +149,27 @@ func (db *DB) UsageDataTotal(ctx context.Context) (int64, error) {
return int64(nep.Total), nil return int64(nep.Total), nil
} }
func (db *DB) WeeklyActiveInstalls(ctx context.Context, since time.Duration) (int64, error) { func (db *DB) CountActiveInstalls(ctx context.Context, since time.Duration) (int64, error) {
var weeklyActiveInstalls int64 var activeInstalls int64
tx := db.WithContext(ctx).Model(&shared.UsageData{}).Where("last_used > ?", time.Now().Add(-since)).Count(&weeklyActiveInstalls) tx := db.WithContext(ctx).Model(&shared.UsageData{}).Where("last_used > ?", time.Now().Add(-since)).Count(&activeInstalls)
if tx.Error != nil { if tx.Error != nil {
return 0, fmt.Errorf("tx.Error: %w", tx.Error) return 0, fmt.Errorf("tx.Error: %w", tx.Error)
} }
return weeklyActiveInstalls, nil return activeInstalls, nil
} }
func (db *DB) WeeklyQueryUsers(ctx context.Context, since time.Duration) (int64, error) { func (db *DB) CountQueryUsers(ctx context.Context, since time.Duration) (int64, error) {
var weeklyQueryUsers int64 var activeQueryUsers int64
tx := db.WithContext(ctx).Model(&shared.UsageData{}).Where("last_queried > ?", time.Now().Add(-since)).Count(&weeklyQueryUsers) tx := db.WithContext(ctx).Model(&shared.UsageData{}).Where("last_queried > ?", time.Now().Add(-since)).Count(&activeQueryUsers)
if tx.Error != nil { if tx.Error != nil {
return 0, fmt.Errorf("tx.Error: %w", tx.Error) return 0, fmt.Errorf("tx.Error: %w", tx.Error)
} }
return weeklyQueryUsers, nil return activeQueryUsers, nil
} }
func (db *DB) LastRegistration(ctx context.Context) (string, error) { func (db *DB) DateOfLastRegistration(ctx context.Context) (string, error) {
var lastRegistration string var lastRegistration string
row := db.WithContext(ctx).Raw("SELECT to_char(max(registration_date), 'DD Month YYYY HH24:MI') FROM devices").Row() 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 { if err := row.Scan(&lastRegistration); err != nil {