feat: publish sanitized custom.97 source
This commit is contained in:
1022 files changed
+69903
-19207
No files matched your search
@@ -0,0 +1,244 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Keep priorities lossless in the web console's JSON number representation.
|
||||
const maxSortablePriority int64 = 1<<53 - 1
|
||||
|
||||
var ErrChannelPriorityConflict = errors.New("Channel priorities changed. Refresh and try again.")
|
||||
var ErrChannelPriorityInvalid = errors.New("Invalid channel priority move")
|
||||
var ErrChannelPriorityRange = errors.New("Channel priority is outside the sortable range")
|
||||
|
||||
type ChannelPriority struct {
|
||||
ID int `json:"id"`
|
||||
Priority *int64 `json:"priority"`
|
||||
}
|
||||
|
||||
func (item ChannelPriority) value() int64 {
|
||||
if item.Priority == nil {
|
||||
return 0
|
||||
}
|
||||
return *item.Priority
|
||||
}
|
||||
|
||||
type ChannelPriorityChange struct {
|
||||
ID int `json:"id"`
|
||||
Previous *int64 `json:"previous"`
|
||||
Priority *int64 `json:"priority"`
|
||||
}
|
||||
|
||||
type ChannelPriorityOrder struct {
|
||||
Revision string `json:"revision"`
|
||||
Items []ChannelPriority `json:"items"`
|
||||
Changes []ChannelPriorityChange `json:"changes"`
|
||||
}
|
||||
|
||||
type ChannelPriorityMove struct {
|
||||
Revision string `json:"revision"`
|
||||
ID int `json:"id"`
|
||||
TargetID int `json:"target_id"`
|
||||
Position string `json:"position"` // before / after use descending priority order
|
||||
Preview bool `json:"preview"`
|
||||
Restore []ChannelPriorityChange `json:"restore,omitempty"`
|
||||
}
|
||||
|
||||
func channelPriorityRevision(items []ChannelPriority) string {
|
||||
data, _ := common.Marshal(items)
|
||||
return fmt.Sprintf("%x", sha256.Sum256(data))
|
||||
}
|
||||
|
||||
func GetChannelPriorityOrder() (ChannelPriorityOrder, error) {
|
||||
result := ChannelPriorityOrder{Items: []ChannelPriority{}, Changes: []ChannelPriorityChange{}}
|
||||
err := DB.Model(&Channel{}).Select("id", "priority").Order("id").Find(&result.Items).Error
|
||||
result.Revision = channelPriorityRevision(result.Items)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// ReorderChannelPriority updates only priorities, including the routing copy in
|
||||
// abilities. A snapshot check also protects preview/commit and undo from stale
|
||||
// clients; row locks serialize concurrent reorders on MySQL and PostgreSQL.
|
||||
func ReorderChannelPriority(move ChannelPriorityMove) (ChannelPriorityOrder, error) {
|
||||
result := ChannelPriorityOrder{Items: []ChannelPriority{}, Changes: []ChannelPriorityChange{}}
|
||||
err := DB.Transaction(func(tx *gorm.DB) error {
|
||||
query := tx.Model(&Channel{}).Select("id", "priority").Order("id")
|
||||
if !move.Preview {
|
||||
query = lockForUpdate(query)
|
||||
}
|
||||
if err := query.Find(&result.Items).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if move.Revision == "" || move.Revision != channelPriorityRevision(result.Items) {
|
||||
return ErrChannelPriorityConflict
|
||||
}
|
||||
changes, err := planChannelPriorityMove(result.Items, move)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result.Changes = changes
|
||||
byID := make(map[int]*ChannelPriority, len(result.Items))
|
||||
for i := range result.Items {
|
||||
byID[result.Items[i].ID] = &result.Items[i]
|
||||
}
|
||||
for _, change := range changes {
|
||||
byID[change.ID].Priority = change.Priority
|
||||
if move.Preview {
|
||||
continue
|
||||
}
|
||||
if err := tx.Model(&Channel{}).Where("id = ?", change.ID).Update("priority", change.Priority).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&Ability{}).Where("channel_id = ?", change.ID).Update("priority", change.Priority).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
result.Revision = channelPriorityRevision(result.Items)
|
||||
return nil
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Equal-priority channels remain together. Opening a slot shifts the shorter
|
||||
// contiguous run of occupied integer priorities, measured by affected channels.
|
||||
func planChannelPriorityMove(items []ChannelPriority, move ChannelPriorityMove) ([]ChannelPriorityChange, error) {
|
||||
changes := []ChannelPriorityChange{}
|
||||
byID := make(map[int]ChannelPriority, len(items))
|
||||
for _, item := range items {
|
||||
if item.value() < -maxSortablePriority || item.value() > maxSortablePriority {
|
||||
return nil, ErrChannelPriorityRange
|
||||
}
|
||||
byID[item.ID] = item
|
||||
}
|
||||
if len(move.Restore) > 0 {
|
||||
if move.ID != 0 || move.TargetID != 0 || move.Position != "" {
|
||||
return nil, ErrChannelPriorityInvalid
|
||||
}
|
||||
seen := make(map[int]bool, len(move.Restore))
|
||||
for _, change := range move.Restore {
|
||||
item, ok := byID[change.ID]
|
||||
if !ok || seen[change.ID] || (change.Priority != nil && (*change.Priority < -maxSortablePriority || *change.Priority > maxSortablePriority)) {
|
||||
return nil, ErrChannelPriorityInvalid
|
||||
}
|
||||
if !equalPriority(item.Priority, change.Previous) {
|
||||
return nil, ErrChannelPriorityConflict
|
||||
}
|
||||
seen[change.ID] = true
|
||||
if !equalPriority(item.Priority, change.Priority) {
|
||||
changes = append(changes, change)
|
||||
}
|
||||
}
|
||||
return changes, nil
|
||||
}
|
||||
item, ok := byID[move.ID]
|
||||
target, targetOK := byID[move.TargetID]
|
||||
if !ok || !targetOK || move.ID == move.TargetID || !slices.Contains([]string{"before", "after", "same"}, move.Position) {
|
||||
return nil, ErrChannelPriorityInvalid
|
||||
}
|
||||
values := make(map[int]int64, len(items))
|
||||
for _, entry := range items {
|
||||
values[entry.ID] = entry.value()
|
||||
}
|
||||
if move.Position == "same" {
|
||||
values[item.ID] = target.value()
|
||||
} else {
|
||||
counts := make(map[int64]int)
|
||||
for _, entry := range items {
|
||||
if entry.ID != item.ID {
|
||||
counts[entry.value()]++
|
||||
}
|
||||
}
|
||||
levels := make([]int64, 0, len(counts))
|
||||
for level := range counts {
|
||||
levels = append(levels, level)
|
||||
}
|
||||
slices.SortFunc(levels, func(a, b int64) int {
|
||||
if a > b {
|
||||
return -1
|
||||
}
|
||||
if a < b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
index := slices.Index(levels, target.value())
|
||||
if move.Position == "after" {
|
||||
index++
|
||||
}
|
||||
hi, lo := maxSortablePriority+1, -maxSortablePriority-1
|
||||
if index > 0 {
|
||||
hi = levels[index-1]
|
||||
}
|
||||
if index < len(levels) {
|
||||
lo = levels[index]
|
||||
}
|
||||
// Dropping into the channel's existing slot must not renumber it.
|
||||
if item.value() < hi && item.value() > lo {
|
||||
return changes, nil
|
||||
}
|
||||
if hi-lo < 2 {
|
||||
up, down := index-1, index
|
||||
upCost, downCost := 0, 0
|
||||
for up >= 0 {
|
||||
upCost += counts[levels[up]]
|
||||
if (up == 0 && levels[up] < maxSortablePriority) || (up > 0 && levels[up-1]-levels[up] > 1) {
|
||||
break
|
||||
}
|
||||
up--
|
||||
}
|
||||
for down < len(levels) {
|
||||
downCost += counts[levels[down]]
|
||||
if (down == len(levels)-1 && levels[down] > -maxSortablePriority) || (down+1 < len(levels) && levels[down]-levels[down+1] > 1) {
|
||||
break
|
||||
}
|
||||
down++
|
||||
}
|
||||
canUp, canDown := up >= 0 && index > 0, down < len(levels) && index < len(levels)
|
||||
if !canUp && !canDown {
|
||||
return nil, ErrChannelPriorityRange
|
||||
}
|
||||
if canUp && (!canDown || upCost < downCost) {
|
||||
for id, value := range values {
|
||||
if id != item.ID && value >= levels[index-1] && value <= levels[up] {
|
||||
values[id]++
|
||||
}
|
||||
}
|
||||
hi++
|
||||
} else {
|
||||
for id, value := range values {
|
||||
if id != item.ID && value <= levels[index] && value >= levels[down] {
|
||||
values[id]--
|
||||
}
|
||||
}
|
||||
lo--
|
||||
}
|
||||
}
|
||||
value := lo + (hi-lo)/2
|
||||
if index == 0 {
|
||||
value = min(maxSortablePriority, lo+10)
|
||||
} else if index == len(levels) {
|
||||
value = max(-maxSortablePriority, hi-10)
|
||||
}
|
||||
values[item.ID] = value
|
||||
}
|
||||
for _, entry := range items {
|
||||
value := values[entry.ID]
|
||||
if value != entry.value() {
|
||||
changes = append(changes, ChannelPriorityChange{ID: entry.ID, Previous: entry.Priority, Priority: &value})
|
||||
}
|
||||
}
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func equalPriority(a, b *int64) bool {
|
||||
if a == nil || b == nil {
|
||||
return a == nil && b == nil
|
||||
}
|
||||
return *a == *b
|
||||
}
|
||||
Reference in new issue
Block a user