Adds comprehensive data structures for Alert and Box functionality across models.
189 lines
4.8 KiB
Go
189 lines
4.8 KiB
Go
package metastore
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/dgraph-io/badger/v4"
|
|
)
|
|
|
|
func (store *Store) UpsertBoxRecord(record BoxRecord) error {
|
|
record.ID = strings.TrimSpace(record.ID)
|
|
if record.ID == "" {
|
|
return errors.New("box id cannot be empty")
|
|
}
|
|
record.OwnerID = strings.TrimSpace(record.OwnerID)
|
|
record.OwnerUsername = strings.TrimSpace(record.OwnerUsername)
|
|
record.FileNames = uniqueStrings(record.FileNames)
|
|
record.UpdatedAt = time.Now().UTC()
|
|
return store.db.Update(func(txn *badger.Txn) error {
|
|
return putJSON(txn, boxRecordKey(record.ID), record)
|
|
})
|
|
}
|
|
|
|
func (store *Store) GetBoxRecord(id string) (BoxRecord, bool, error) {
|
|
var record BoxRecord
|
|
err := store.db.View(func(txn *badger.Txn) error {
|
|
return getJSON(txn, boxRecordKey(id), &record)
|
|
})
|
|
if errors.Is(err, ErrNotFound) {
|
|
return BoxRecord{}, false, nil
|
|
}
|
|
return record, err == nil, err
|
|
}
|
|
|
|
func (store *Store) DeleteBoxRecord(id string) error {
|
|
return store.db.Update(func(txn *badger.Txn) error {
|
|
err := txn.Delete(boxRecordKey(id))
|
|
if errors.Is(err, badger.ErrKeyNotFound) {
|
|
return nil
|
|
}
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (store *Store) ListBoxRecords(filters BoxFilters, page BoxPageRequest) (BoxRecordPage, error) {
|
|
if page.Page < 1 {
|
|
page.Page = 1
|
|
}
|
|
switch page.PageSize {
|
|
case 25, 50, 100:
|
|
default:
|
|
page.PageSize = 25
|
|
}
|
|
|
|
rows := []BoxRecord{}
|
|
err := store.db.View(func(txn *badger.Txn) error {
|
|
opts := badger.DefaultIteratorOptions
|
|
opts.Prefix = []byte("box_record/")
|
|
it := txn.NewIterator(opts)
|
|
defer it.Close()
|
|
|
|
for it.Rewind(); it.Valid(); it.Next() {
|
|
var record BoxRecord
|
|
if err := it.Item().Value(func(data []byte) error {
|
|
return json.Unmarshal(data, &record)
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
if boxRecordMatches(record, filters) {
|
|
rows = append(rows, record)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return BoxRecordPage{}, err
|
|
}
|
|
|
|
sortBoxRecords(rows, filters.Sort)
|
|
total := len(rows)
|
|
start := (page.Page - 1) * page.PageSize
|
|
if start > total {
|
|
start = total
|
|
}
|
|
end := start + page.PageSize
|
|
if end > total {
|
|
end = total
|
|
}
|
|
totalPages := 1
|
|
if total > 0 {
|
|
totalPages = (total + page.PageSize - 1) / page.PageSize
|
|
}
|
|
return BoxRecordPage{
|
|
Rows: rows[start:end],
|
|
Page: page.Page,
|
|
PageSize: page.PageSize,
|
|
Total: total,
|
|
HasPrev: page.Page > 1,
|
|
HasNext: end < total,
|
|
PrevPage: maxInt(page.Page-1, 1),
|
|
NextPage: page.Page + 1,
|
|
TotalPages: totalPages,
|
|
}, nil
|
|
}
|
|
|
|
func boxRecordMatches(record BoxRecord, filters BoxFilters) bool {
|
|
query := strings.ToLower(strings.TrimSpace(filters.Query))
|
|
if query != "" {
|
|
haystack := strings.ToLower(record.ID + " " + record.OwnerUsername + " " + strings.Join(record.FileNames, " "))
|
|
if !strings.Contains(haystack, query) {
|
|
return false
|
|
}
|
|
}
|
|
owner := strings.ToLower(strings.TrimSpace(filters.Owner))
|
|
if owner != "" && owner != "all" && strings.ToLower(record.OwnerUsername) != owner && strings.ToLower(record.OwnerID) != owner {
|
|
return false
|
|
}
|
|
status := strings.ToLower(strings.TrimSpace(filters.Status))
|
|
if status != "" && status != "all" && boxRecordStatus(record) != status {
|
|
return false
|
|
}
|
|
switch strings.ToLower(strings.TrimSpace(filters.Flag)) {
|
|
case "", "all":
|
|
return true
|
|
case "password":
|
|
return record.PasswordProtected
|
|
case "one-time":
|
|
return record.OneTimeDownload
|
|
case "zip-disabled":
|
|
return record.DisableZip
|
|
case "expired":
|
|
return boxRecordExpired(record)
|
|
case "refreshable":
|
|
return !record.OneTimeDownload && !boxRecordExpired(record)
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func sortBoxRecords(rows []BoxRecord, sortKey string) {
|
|
switch strings.ToLower(strings.TrimSpace(sortKey)) {
|
|
case "oldest":
|
|
sort.Slice(rows, func(i int, j int) bool { return rows[i].CreatedAt.Before(rows[j].CreatedAt) })
|
|
case "largest":
|
|
sort.Slice(rows, func(i int, j int) bool { return rows[i].TotalSize > rows[j].TotalSize })
|
|
case "expires":
|
|
sort.Slice(rows, func(i int, j int) bool { return rows[i].ExpiresAt.Before(rows[j].ExpiresAt) })
|
|
case "expired":
|
|
sort.Slice(rows, func(i int, j int) bool {
|
|
left := boxRecordExpired(rows[i])
|
|
right := boxRecordExpired(rows[j])
|
|
if left == right {
|
|
return rows[i].CreatedAt.After(rows[j].CreatedAt)
|
|
}
|
|
return left
|
|
})
|
|
default:
|
|
sort.Slice(rows, func(i int, j int) bool { return rows[i].CreatedAt.After(rows[j].CreatedAt) })
|
|
}
|
|
}
|
|
|
|
func boxRecordStatus(record BoxRecord) string {
|
|
if boxRecordExpired(record) {
|
|
return "expired"
|
|
}
|
|
if record.ExpiresAt.IsZero() {
|
|
return "pending"
|
|
}
|
|
return "active"
|
|
}
|
|
|
|
func boxRecordExpired(record BoxRecord) bool {
|
|
return !record.ExpiresAt.IsZero() && time.Now().UTC().After(record.ExpiresAt)
|
|
}
|
|
|
|
func boxRecordKey(id string) []byte {
|
|
return []byte("box_record/" + strings.TrimSpace(id))
|
|
}
|
|
|
|
func maxInt(a int, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|