mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-22 11:46:40 +00:00
[chore] make csv export ordering determinate (#3318)
This commit is contained in:
parent
2f56455eed
commit
f819229988
|
@ -161,8 +161,8 @@ type testCase struct {
|
||||||
user: suite.testUsers["local_account_1"],
|
user: suite.testUsers["local_account_1"],
|
||||||
account: suite.testAccounts["local_account_1"],
|
account: suite.testAccounts["local_account_1"],
|
||||||
expect: `Account address,Show boosts,Notify on new posts,Languages
|
expect: `Account address,Show boosts,Notify on new posts,Languages
|
||||||
admin@localhost:8080,true,false,
|
|
||||||
1happyturtle@localhost:8080,true,false,
|
1happyturtle@localhost:8080,true,false,
|
||||||
|
admin@localhost:8080,true,false,
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
// Export Followers.
|
// Export Followers.
|
||||||
|
|
|
@ -18,12 +18,13 @@
|
||||||
package typeutils
|
package typeutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
|
@ -77,6 +78,8 @@ func (c *Converter) AccountToExportStats(
|
||||||
|
|
||||||
// FollowingToCSV converts a slice of follows into
|
// FollowingToCSV converts a slice of follows into
|
||||||
// a slice of CSV-compatible Following records.
|
// a slice of CSV-compatible Following records.
|
||||||
|
//
|
||||||
|
// Each follow should be populated.
|
||||||
func (c *Converter) FollowingToCSV(
|
func (c *Converter) FollowingToCSV(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
following []*gtsmodel.Follow,
|
following []*gtsmodel.Follow,
|
||||||
|
@ -101,24 +104,19 @@ func (c *Converter) FollowingToCSV(
|
||||||
thisDomain = config.GetHost()
|
thisDomain = config.GetHost()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-sort the follows
|
||||||
|
// by domain and username.
|
||||||
|
slices.SortFunc(
|
||||||
|
following,
|
||||||
|
func(a *gtsmodel.Follow, b *gtsmodel.Follow) int {
|
||||||
|
aStr := a.TargetAccount.Domain + "/" + a.TargetAccount.Username
|
||||||
|
bStr := b.TargetAccount.Domain + "/" + b.TargetAccount.Username
|
||||||
|
return cmp.Compare(aStr, bStr)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// For each item, add a record.
|
// For each item, add a record.
|
||||||
for _, follow := range following {
|
for _, follow := range following {
|
||||||
if follow.TargetAccount == nil {
|
|
||||||
// Retrieve target account.
|
|
||||||
var err error
|
|
||||||
follow.TargetAccount, err = c.state.DB.GetAccountByID(
|
|
||||||
// Barebones is fine here.
|
|
||||||
gtscontext.SetBarebones(ctx),
|
|
||||||
follow.TargetAccountID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, gtserror.Newf(
|
|
||||||
"db error getting target account for follow %s: %w",
|
|
||||||
follow.ID, err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
domain := follow.TargetAccount.Domain
|
domain := follow.TargetAccount.Domain
|
||||||
if domain == "" {
|
if domain == "" {
|
||||||
// Local account,
|
// Local account,
|
||||||
|
@ -144,6 +142,8 @@ func (c *Converter) FollowingToCSV(
|
||||||
|
|
||||||
// FollowersToCSV converts a slice of follows into
|
// FollowersToCSV converts a slice of follows into
|
||||||
// a slice of CSV-compatible Followers records.
|
// a slice of CSV-compatible Followers records.
|
||||||
|
//
|
||||||
|
// Each follow should be populated.
|
||||||
func (c *Converter) FollowersToCSV(
|
func (c *Converter) FollowersToCSV(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
followers []*gtsmodel.Follow,
|
followers []*gtsmodel.Follow,
|
||||||
|
@ -165,24 +165,19 @@ func (c *Converter) FollowersToCSV(
|
||||||
thisDomain = config.GetHost()
|
thisDomain = config.GetHost()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-sort the follows
|
||||||
|
// by domain and username.
|
||||||
|
slices.SortFunc(
|
||||||
|
followers,
|
||||||
|
func(a *gtsmodel.Follow, b *gtsmodel.Follow) int {
|
||||||
|
aStr := a.Account.Domain + "/" + a.Account.Username
|
||||||
|
bStr := b.Account.Domain + "/" + b.Account.Username
|
||||||
|
return cmp.Compare(aStr, bStr)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// For each item, add a record.
|
// For each item, add a record.
|
||||||
for _, follow := range followers {
|
for _, follow := range followers {
|
||||||
if follow.Account == nil {
|
|
||||||
// Retrieve account.
|
|
||||||
var err error
|
|
||||||
follow.Account, err = c.state.DB.GetAccountByID(
|
|
||||||
// Barebones is fine here.
|
|
||||||
gtscontext.SetBarebones(ctx),
|
|
||||||
follow.AccountID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, gtserror.Newf(
|
|
||||||
"db error getting account for follow %s: %w",
|
|
||||||
follow.ID, err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
domain := follow.Account.Domain
|
domain := follow.Account.Domain
|
||||||
if domain == "" {
|
if domain == "" {
|
||||||
// Local account,
|
// Local account,
|
||||||
|
@ -218,6 +213,15 @@ func (c *Converter) ListsToCSV(
|
||||||
// CSV doesn't use column headers.
|
// CSV doesn't use column headers.
|
||||||
records := make([][]string, 0)
|
records := make([][]string, 0)
|
||||||
|
|
||||||
|
// Pre-sort the lists
|
||||||
|
// alphabetically.
|
||||||
|
slices.SortFunc(
|
||||||
|
lists,
|
||||||
|
func(a *gtsmodel.List, b *gtsmodel.List) int {
|
||||||
|
return cmp.Compare(a.Title, b.Title)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// For each item, add a record.
|
// For each item, add a record.
|
||||||
for _, list := range lists {
|
for _, list := range lists {
|
||||||
|
|
||||||
|
@ -231,6 +235,17 @@ func (c *Converter) ListsToCSV(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-sort the follows
|
||||||
|
// by domain and username.
|
||||||
|
slices.SortFunc(
|
||||||
|
follows,
|
||||||
|
func(a *gtsmodel.Follow, b *gtsmodel.Follow) int {
|
||||||
|
aStr := a.TargetAccount.Domain + "/" + a.TargetAccount.Username
|
||||||
|
bStr := b.TargetAccount.Domain + "/" + b.TargetAccount.Username
|
||||||
|
return cmp.Compare(aStr, bStr)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// Append each follow as CSV record.
|
// Append each follow as CSV record.
|
||||||
for _, follow := range follows {
|
for _, follow := range follows {
|
||||||
var (
|
var (
|
||||||
|
@ -263,6 +278,8 @@ func (c *Converter) ListsToCSV(
|
||||||
|
|
||||||
// BlocksToCSV converts a slice of blocks into
|
// BlocksToCSV converts a slice of blocks into
|
||||||
// a slice of CSV-compatible blocks records.
|
// a slice of CSV-compatible blocks records.
|
||||||
|
//
|
||||||
|
// Each block should be populated.
|
||||||
func (c *Converter) BlocksToCSV(
|
func (c *Converter) BlocksToCSV(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
blocks []*gtsmodel.Block,
|
blocks []*gtsmodel.Block,
|
||||||
|
@ -278,24 +295,19 @@ func (c *Converter) BlocksToCSV(
|
||||||
// CSV doesn't use column headers.
|
// CSV doesn't use column headers.
|
||||||
records := make([][]string, 0, len(blocks))
|
records := make([][]string, 0, len(blocks))
|
||||||
|
|
||||||
|
// Pre-sort the blocks
|
||||||
|
// by domain and username.
|
||||||
|
slices.SortFunc(
|
||||||
|
blocks,
|
||||||
|
func(a *gtsmodel.Block, b *gtsmodel.Block) int {
|
||||||
|
aStr := a.TargetAccount.Domain + "/" + a.TargetAccount.Username
|
||||||
|
bStr := b.TargetAccount.Domain + "/" + b.TargetAccount.Username
|
||||||
|
return cmp.Compare(aStr, bStr)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// For each item, add a record.
|
// For each item, add a record.
|
||||||
for _, block := range blocks {
|
for _, block := range blocks {
|
||||||
if block.TargetAccount == nil {
|
|
||||||
// Retrieve target account.
|
|
||||||
var err error
|
|
||||||
block.TargetAccount, err = c.state.DB.GetAccountByID(
|
|
||||||
// Barebones is fine here.
|
|
||||||
gtscontext.SetBarebones(ctx),
|
|
||||||
block.TargetAccountID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, gtserror.Newf(
|
|
||||||
"db error getting target account for block %s: %w",
|
|
||||||
block.ID, err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
domain := block.TargetAccount.Domain
|
domain := block.TargetAccount.Domain
|
||||||
if domain == "" {
|
if domain == "" {
|
||||||
// Local account,
|
// Local account,
|
||||||
|
@ -315,6 +327,8 @@ func (c *Converter) BlocksToCSV(
|
||||||
|
|
||||||
// MutesToCSV converts a slice of mutes into
|
// MutesToCSV converts a slice of mutes into
|
||||||
// a slice of CSV-compatible mute records.
|
// a slice of CSV-compatible mute records.
|
||||||
|
//
|
||||||
|
// Each mute should be populated.
|
||||||
func (c *Converter) MutesToCSV(
|
func (c *Converter) MutesToCSV(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
mutes []*gtsmodel.UserMute,
|
mutes []*gtsmodel.UserMute,
|
||||||
|
@ -337,24 +351,19 @@ func (c *Converter) MutesToCSV(
|
||||||
thisDomain = config.GetHost()
|
thisDomain = config.GetHost()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-sort the mutes
|
||||||
|
// by domain and username.
|
||||||
|
slices.SortFunc(
|
||||||
|
mutes,
|
||||||
|
func(a *gtsmodel.UserMute, b *gtsmodel.UserMute) int {
|
||||||
|
aStr := a.TargetAccount.Domain + "/" + a.TargetAccount.Username
|
||||||
|
bStr := b.TargetAccount.Domain + "/" + b.TargetAccount.Username
|
||||||
|
return cmp.Compare(aStr, bStr)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// For each item, add a record.
|
// For each item, add a record.
|
||||||
for _, mute := range mutes {
|
for _, mute := range mutes {
|
||||||
if mute.TargetAccount == nil {
|
|
||||||
// Retrieve target account.
|
|
||||||
var err error
|
|
||||||
mute.TargetAccount, err = c.state.DB.GetAccountByID(
|
|
||||||
// Barebones is fine here.
|
|
||||||
gtscontext.SetBarebones(ctx),
|
|
||||||
mute.TargetAccountID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, gtserror.Newf(
|
|
||||||
"db error getting target account for mute %s: %w",
|
|
||||||
mute.ID, err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
domain := mute.TargetAccount.Domain
|
domain := mute.TargetAccount.Domain
|
||||||
if domain == "" {
|
if domain == "" {
|
||||||
// Local account,
|
// Local account,
|
||||||
|
|
Loading…
Reference in a new issue