summaryrefslogtreecommitdiff
path: root/internal/typeutils
diff options
context:
space:
mode:
Diffstat (limited to 'internal/typeutils')
-rw-r--r--internal/typeutils/csv.go385
1 files changed, 385 insertions, 0 deletions
diff --git a/internal/typeutils/csv.go b/internal/typeutils/csv.go
new file mode 100644
index 000000000..2ef56cb0c
--- /dev/null
+++ b/internal/typeutils/csv.go
@@ -0,0 +1,385 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package typeutils
+
+import (
+ "context"
+ "strconv"
+
+ apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ "github.com/superseriousbusiness/gotosocial/internal/config"
+ "github.com/superseriousbusiness/gotosocial/internal/gtscontext"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+func (c *Converter) AccountToExportStats(
+ ctx context.Context,
+ a *gtsmodel.Account,
+) (*apimodel.AccountExportStats, error) {
+ // Ensure account stats populated.
+ if a.Stats == nil {
+ if err := c.state.DB.PopulateAccountStats(ctx, a); err != nil {
+ return nil, gtserror.Newf(
+ "error getting stats for account %s: %w",
+ a.ID, err,
+ )
+ }
+ }
+
+ listsCount, err := c.state.DB.CountListsForAccountID(ctx, a.ID)
+ if err != nil {
+ return nil, gtserror.Newf(
+ "error counting lists for account %s: %w",
+ a.ID, err,
+ )
+ }
+
+ blockingCount, err := c.state.DB.CountAccountBlocks(ctx, a.ID)
+ if err != nil {
+ return nil, gtserror.Newf(
+ "error counting lists for account %s: %w",
+ a.ID, err,
+ )
+ }
+
+ mutingCount, err := c.state.DB.CountAccountMutes(ctx, a.ID)
+ if err != nil {
+ return nil, gtserror.Newf(
+ "error counting lists for account %s: %w",
+ a.ID, err,
+ )
+ }
+
+ return &apimodel.AccountExportStats{
+ FollowersCount: *a.Stats.FollowersCount,
+ FollowingCount: *a.Stats.FollowingCount,
+ StatusesCount: *a.Stats.StatusesCount,
+ ListsCount: listsCount,
+ BlocksCount: blockingCount,
+ MutesCount: mutingCount,
+ }, nil
+}
+
+// FollowingToCSV converts a slice of follows into
+// a slice of CSV-compatible Following records.
+func (c *Converter) FollowingToCSV(
+ ctx context.Context,
+ following []*gtsmodel.Follow,
+) ([][]string, error) {
+ // Records should be length of
+ // input + 1 so we can add headers.
+ records := make([][]string, 1, len(following)+1)
+
+ // Add headers at the
+ // top of records.
+ records[0] = []string{
+ "Account address",
+ "Show boosts",
+ }
+
+ // We need to know our own domain for this.
+ // Try account domain, fall back to host.
+ thisDomain := config.GetAccountDomain()
+ if thisDomain == "" {
+ thisDomain = config.GetHost()
+ }
+
+ // For each item, add a record.
+ 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
+ if domain == "" {
+ // Local account,
+ // use our domain.
+ domain = thisDomain
+ }
+
+ records = append(records, []string{
+ // Account address: eg., someone@example.org
+ // -- NOTE: without the leading '@'!
+ follow.TargetAccount.Username + "@" + domain,
+ // Show boosts: eg., true
+ strconv.FormatBool(*follow.ShowReblogs),
+ })
+ }
+
+ return records, nil
+}
+
+// FollowersToCSV converts a slice of follows into
+// a slice of CSV-compatible Followers records.
+func (c *Converter) FollowersToCSV(
+ ctx context.Context,
+ followers []*gtsmodel.Follow,
+) ([][]string, error) {
+ // Records should be length of
+ // input + 1 so we can add headers.
+ records := make([][]string, 1, len(followers)+1)
+
+ // Add header at the
+ // top of records.
+ records[0] = []string{
+ "Account address",
+ }
+
+ // We need to know our own domain for this.
+ // Try account domain, fall back to host.
+ thisDomain := config.GetAccountDomain()
+ if thisDomain == "" {
+ thisDomain = config.GetHost()
+ }
+
+ // For each item, add a record.
+ 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
+ if domain == "" {
+ // Local account,
+ // use our domain.
+ domain = thisDomain
+ }
+
+ records = append(records, []string{
+ // Account address: eg., someone@example.org
+ // -- NOTE: without the leading '@'!
+ follow.Account.Username + "@" + domain,
+ })
+ }
+
+ return records, nil
+}
+
+// FollowersToCSV converts a slice of follows into
+// a slice of CSV-compatible Followers records.
+func (c *Converter) ListsToCSV(
+ ctx context.Context,
+ lists []*gtsmodel.List,
+) ([][]string, error) {
+ // We need to know our own domain for this.
+ // Try account domain, fall back to host.
+ thisDomain := config.GetAccountDomain()
+ if thisDomain == "" {
+ thisDomain = config.GetHost()
+ }
+
+ // NOTE: Mastodon-compatible lists
+ // CSV doesn't use column headers.
+ records := make([][]string, 0)
+
+ // For each item, add a record.
+ for _, list := range lists {
+ for _, entry := range list.ListEntries {
+ if entry.Follow == nil {
+ // Retrieve follow.
+ var err error
+ entry.Follow, err = c.state.DB.GetFollowByID(
+ ctx,
+ entry.FollowID,
+ )
+ if err != nil {
+ return nil, gtserror.Newf(
+ "db error getting follow for list entry %s: %w",
+ entry.ID, err,
+ )
+ }
+ }
+
+ if entry.Follow.TargetAccount == nil {
+ // Retrieve account.
+ var err error
+ entry.Follow.TargetAccount, err = c.state.DB.GetAccountByID(
+ // Barebones is fine here.
+ gtscontext.SetBarebones(ctx),
+ entry.Follow.TargetAccountID,
+ )
+ if err != nil {
+ return nil, gtserror.Newf(
+ "db error getting target account for list entry %s: %w",
+ entry.ID, err,
+ )
+ }
+ }
+
+ var (
+ username = entry.Follow.TargetAccount.Username
+ domain = entry.Follow.TargetAccount.Domain
+ )
+
+ if domain == "" {
+ // Local account,
+ // use our domain.
+ domain = thisDomain
+ }
+
+ records = append(records, []string{
+ // List title: eg., Very cool list
+ list.Title,
+ // Account address: eg., someone@example.org
+ // -- NOTE: without the leading '@'!
+ username + "@" + domain,
+ })
+ }
+
+ }
+
+ return records, nil
+}
+
+// BlocksToCSV converts a slice of blocks into
+// a slice of CSV-compatible blocks records.
+func (c *Converter) BlocksToCSV(
+ ctx context.Context,
+ blocks []*gtsmodel.Block,
+) ([][]string, error) {
+ // We need to know our own domain for this.
+ // Try account domain, fall back to host.
+ thisDomain := config.GetAccountDomain()
+ if thisDomain == "" {
+ thisDomain = config.GetHost()
+ }
+
+ // NOTE: Mastodon-compatible blocks
+ // CSV doesn't use column headers.
+ records := make([][]string, 0, len(blocks))
+
+ // For each item, add a record.
+ 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
+ if domain == "" {
+ // Local account,
+ // use our domain.
+ domain = thisDomain
+ }
+
+ records = append(records, []string{
+ // Account address: eg., someone@example.org
+ // -- NOTE: without the leading '@'!
+ block.TargetAccount.Username + "@" + domain,
+ })
+ }
+
+ return records, nil
+}
+
+// MutesToCSV converts a slice of mutes into
+// a slice of CSV-compatible mute records.
+func (c *Converter) MutesToCSV(
+ ctx context.Context,
+ mutes []*gtsmodel.UserMute,
+) ([][]string, error) {
+ // Records should be length of
+ // input + 1 so we can add headers.
+ records := make([][]string, 1, len(mutes)+1)
+
+ // Add headers at the
+ // top of records.
+ records[0] = []string{
+ "Account address",
+ "Hide notifications",
+ }
+
+ // We need to know our own domain for this.
+ // Try account domain, fall back to host.
+ thisDomain := config.GetAccountDomain()
+ if thisDomain == "" {
+ thisDomain = config.GetHost()
+ }
+
+ // For each item, add a record.
+ 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
+ if domain == "" {
+ // Local account,
+ // use our domain.
+ domain = thisDomain
+ }
+
+ records = append(records, []string{
+ // Account address: eg., someone@example.org
+ // -- NOTE: without the leading '@'!
+ mute.TargetAccount.Username + "@" + domain,
+ // Hide notifications: eg., true
+ strconv.FormatBool(*mute.Notifications),
+ })
+ }
+
+ return records, nil
+}