package accounts import ( "context" "errors" "fmt" "ruben/inventory2/internal/consts" "slices" "strings" "github.com/jackc/pgx/v5" ) // TODO: simpify, if possible (single query ideal) func (db *Store) SetOrderOfPlatformOnAccountPage(ctx context.Context, acctID int64, platform Platform, orderIndex int) error { tx, err := db.db.Begin(ctx) if err != nil { return fmt.Errorf("failed to start transaction: %w", err) } defer tx.Rollback(ctx) // look up specified indexes rows, err := tx.Query( ctx, ` SELECT platform, order_index FROM accounts_page_platform_order_indexes WHERE account_id = @account_id ORDER BY order_index ASC `, pgx.NamedArgs{ "account_id": acctID, }, ) if err != nil { return fmt.Errorf("failed to perform query to look up existing indexes: %w", err) } indexes, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[struct { Platform Platform Order_index int }]) if err != nil { return fmt.Errorf("failed to scan rows for query to look up existing indexes: %w", err) } // compute the implied indexes, // and construct the full sequence of platforms indexPerPlatform := make(map[Platform]int, len(allPlatforms)) platformPerIndex := make(map[int]Platform, len(allPlatforms)) for _, v := range indexes { indexPerPlatform[v.Platform] = v.Order_index platformPerIndex[v.Order_index] = v.Platform } prevIndex := -1 for _, p := range allPlatforms { if _, indexSet := indexPerPlatform[p]; !indexSet { index := prevIndex for indexUsed := true; indexUsed; _, indexUsed = platformPerIndex[index] { index += 1 } prevIndex = index indexPerPlatform[p] = index platformPerIndex[index] = p } } prevOrderIndex := indexPerPlatform[platform] if indexPerPlatform[platform] == orderIndex { return nil } if increased := orderIndex > prevOrderIndex; increased { // decrement in indexes between the previous index and new index for index := prevOrderIndex + 1; index <= orderIndex; index += 1 { p := platformPerIndex[index] indexPerPlatform[p] = index - 1 platformPerIndex[index-1] = p } indexPerPlatform[platform] = orderIndex platformPerIndex[orderIndex] = platform } else { // increment in indexes between the previous index and new index for index := prevOrderIndex - 1; index >= orderIndex; index -= 1 { p := platformPerIndex[index] indexPerPlatform[p] = index + 1 platformPerIndex[index+1] = p } indexPerPlatform[platform] = orderIndex platformPerIndex[orderIndex] = platform } // delete all indexes for the acct in the db, // then insert all updated indexes valuesLines := make([]string, len(allPlatforms)) args := pgx.NamedArgs{ "account_id": acctID, } for i, p := range allPlatforms { valuesLines[i] = fmt.Sprintf("(@account_id, @platform_%d, @order_index_%d::smallint)", i, i) args[fmt.Sprintf("platform_%d", i)] = p args[fmt.Sprintf("order_index_%d", i)] = indexPerPlatform[p] } _, err = tx.Exec( ctx, fmt.Sprintf(` WITH deleted_indexes AS ( DELETE FROM accounts_page_platform_order_indexes WHERE account_id = @account_id RETURNING account_id ), new_indexes(account_id, platform, order_index) AS ( SELECT DISTINCT x.account_id, x.platform, x.order_index FROM (VALUES %s) AS x(account_id, platform, order_index) LEFT JOIN deleted_indexes ON x.account_id = deleted_indexes.account_id ) INSERT INTO accounts_page_platform_order_indexes ( account_id, platform, order_index ) SELECT account_id, platform::Platform, order_index FROM new_indexes `, strings.Join(valuesLines, ", ")), args, ) if err != nil { return fmt.Errorf("failed to perform query to delete old indexes and insert new indexes: %w", err) } if tx.Commit(ctx); err != nil { return fmt.Errorf("failed to commit txn: %w", err) } return nil } func (db *Store) GetOrderOfPlatformsOnAccountPage(ctx context.Context, acctID int64) ([]Platform, error) { // get specified order indexes per platform rows, err := db.db.Query( ctx, ` SELECT platform, order_index FROM accounts_page_platform_order_indexes WHERE account_id = @account_id `, pgx.NamedArgs{ "account_id": acctID, }, ) if err != nil { return nil, fmt.Errorf("failed to perform query: %w", err) } type Row struct { Platform Platform Order_index int } platformsWithIndexes, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[struct { Platform Platform Order_index int }]) if err != nil { return nil, fmt.Errorf("failed to scan rows: %w", err) } // hash the specified platforms by index platformsByIndex := make(map[int]Platform, len(platformsWithIndexes)) for _, v := range platformsWithIndexes { platformsByIndex[v.Order_index] = v.Platform } // form a sorted list of platforms without indexes specified, sorted alphabetically allPlatformsByName := make(map[Platform]struct{}, len(allPlatforms)) for _, p := range allPlatforms { allPlatformsByName[p] = struct{}{} } for _, v := range platformsWithIndexes { delete(allPlatformsByName, v.Platform) } platformsWithoutAnIndexSpecified := make([]Platform, 0, len(allPlatformsByName)) for p := range allPlatformsByName { platformsWithoutAnIndexSpecified = append(platformsWithoutAnIndexSpecified, p) } slices.SortFunc(platformsWithoutAnIndexSpecified, func(a, b Platform) int { al := strings.ToLower(string(a)) bl := strings.ToLower(string(b)) if al < bl { return -1 } if bl < al { return 1 } return 0 }) // construct the list of the ordered platforms res := make([]Platform, len(allPlatforms)) nextIndex := 0 for i := range res { if p, ok := platformsByIndex[i]; ok { res[i] = p } else { res[i] = platformsWithoutAnIndexSpecified[nextIndex] nextIndex += 1 } } return res, nil } func (db *Store) GetOrderOfPlatformOnAccountPage(ctx context.Context, acctID int64, platform Platform) (int, error) { rows, err := db.db.Query( ctx, ` SELECT order_index FROM accounts_page_platform_order_indexes WHERE account_id = @account_id AND platform = @platform `, pgx.NamedArgs{ "account_id": acctID, "platform": platform, }, ) if err != nil { return 0, fmt.Errorf("failed to perform query: %w", err) } i, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[int]) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return 0, consts.ErrNotFound } return 0, fmt.Errorf("failed to scan rows: %w", err) } return i, nil }