diff options
Diffstat (limited to 'internal/typeutils/internaltofrontend.go')
-rw-r--r-- | internal/typeutils/internaltofrontend.go | 226 |
1 files changed, 141 insertions, 85 deletions
diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go index 74b061fb0..88646c311 100644 --- a/internal/typeutils/internaltofrontend.go +++ b/internal/typeutils/internaltofrontend.go @@ -19,6 +19,7 @@ package typeutils import ( "context" + "errors" "fmt" "math" "strconv" @@ -26,6 +27,7 @@ import ( apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/log" @@ -83,99 +85,110 @@ func (c *converter) AccountToAPIAccountSensitive(ctx context.Context, a *gtsmode } func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.Account) (*apimodel.Account, error) { - // count followers + if err := c.db.PopulateAccount(ctx, a); err != nil { + log.Errorf(ctx, "error(s) populating account, will continue: %s", err) + } + + // Basic account stats: + // - Followers count + // - Following count + // - Statuses count + // - Last status time + followersCount, err := c.db.CountAccountFollowers(ctx, a.ID) - if err != nil { - return nil, fmt.Errorf("error counting followers: %s", err) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + return nil, fmt.Errorf("AccountToAPIAccountPublic: error counting followers: %w", err) } - // count following followingCount, err := c.db.CountAccountFollows(ctx, a.ID) - if err != nil { - return nil, fmt.Errorf("error counting following: %s", err) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + return nil, fmt.Errorf("AccountToAPIAccountPublic: error counting following: %w", err) } - // count statuses statusesCount, err := c.db.CountAccountStatuses(ctx, a.ID) - if err != nil { - return nil, fmt.Errorf("error counting statuses: %s", err) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + return nil, fmt.Errorf("AccountToAPIAccountPublic: error counting statuses: %w", err) } - // check when the last status was var lastStatusAt *string lastPosted, err := c.db.GetAccountLastPosted(ctx, a.ID, false) - if err == nil && !lastPosted.IsZero() { - lastStatusAtTemp := util.FormatISO8601(lastPosted) - lastStatusAt = &lastStatusAtTemp + if err != nil && !errors.Is(err, db.ErrNoEntries) { + return nil, fmt.Errorf("AccountToAPIAccountPublic: error counting statuses: %w", err) } - // set account avatar fields if available - var aviURL string - var aviURLStatic string - if a.AvatarMediaAttachmentID != "" { - if a.AvatarMediaAttachment == nil { - avi, err := c.db.GetAttachmentByID(ctx, a.AvatarMediaAttachmentID) - if err != nil { - log.Errorf(ctx, "error getting Avatar with id %s: %s", a.AvatarMediaAttachmentID, err) - } - a.AvatarMediaAttachment = avi - } - if a.AvatarMediaAttachment != nil { - aviURL = a.AvatarMediaAttachment.URL - aviURLStatic = a.AvatarMediaAttachment.Thumbnail.URL - } + if !lastPosted.IsZero() { + lastStatusAt = func() *string { t := util.FormatISO8601(lastPosted); return &t }() } - // set account header fields if available - var headerURL string - var headerURLStatic string - if a.HeaderMediaAttachmentID != "" { - if a.HeaderMediaAttachment == nil { - avi, err := c.db.GetAttachmentByID(ctx, a.HeaderMediaAttachmentID) - if err != nil { - log.Errorf(ctx, "error getting Header with id %s: %s", a.HeaderMediaAttachmentID, err) - } - a.HeaderMediaAttachment = avi - } - if a.HeaderMediaAttachment != nil { - headerURL = a.HeaderMediaAttachment.URL - headerURLStatic = a.HeaderMediaAttachment.Thumbnail.URL - } + // Profile media + nice extras: + // - Avatar + // - Header + // - Fields + // - Emojis + + var ( + aviURL string + aviURLStatic string + headerURL string + headerURLStatic string + fields = make([]apimodel.Field, len(a.Fields)) + ) + + if a.AvatarMediaAttachment != nil { + aviURL = a.AvatarMediaAttachment.URL + aviURLStatic = a.AvatarMediaAttachment.Thumbnail.URL } - // preallocate frontend fields slice - fields := make([]apimodel.Field, len(a.Fields)) + if a.HeaderMediaAttachment != nil { + headerURL = a.HeaderMediaAttachment.URL + headerURLStatic = a.HeaderMediaAttachment.Thumbnail.URL + } - // Convert account GTS model fields to frontend + // GTS model fields -> frontend. for i, field := range a.Fields { mField := apimodel.Field{ Name: field.Name, Value: field.Value, } + if !field.VerifiedAt.IsZero() { mField.VerifiedAt = util.FormatISO8601(field.VerifiedAt) } + fields[i] = mField } - // convert account gts model emojis to frontend api model emojis + // GTS model emojis -> frontend. apiEmojis, err := c.convertEmojisToAPIEmojis(ctx, a.Emojis, a.EmojiIDs) if err != nil { log.Errorf(ctx, "error converting account emojis: %v", err) } - var acct string - var role *apimodel.AccountRole + // Bits that vary between remote + local accounts: + // - Account (acct) string. + // - Role. + + var ( + acct string + role *apimodel.AccountRole + ) + + if a.IsRemote() { + // Domain may be in Punycode, + // de-punify it just in case. + d, err := util.DePunify(a.Domain) + if err != nil { + return nil, fmt.Errorf("AccountToAPIAccountPublic: error de-punifying domain %s for account id %s: %w", a.Domain, a.ID, err) + } - if a.Domain != "" { - // this is a remote user - acct = a.Username + "@" + a.Domain + acct = a.Username + "@" + d } else { - // this is a local user + // This is a local user. acct = a.Username + user, err := c.db.GetUserByAccountID(ctx, a.ID) if err != nil { - return nil, fmt.Errorf("AccountToAPIAccountPublic: error getting user from database for account id %s: %s", a.ID, err) + return nil, fmt.Errorf("AccountToAPIAccountPublic: error getting user from database for account id %s: %w", a.ID, err) } switch { @@ -188,10 +201,8 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A } } - var suspended bool - if !a.SuspendedAt.IsZero() { - suspended = true - } + // Remaining properties are simple and + // can be populated directly below. accountFrontend := &apimodel.Account{ ID: a.ID, @@ -214,12 +225,14 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A LastStatusAt: lastStatusAt, Emojis: apiEmojis, Fields: fields, - Suspended: suspended, + Suspended: !a.SuspendedAt.IsZero(), CustomCSS: a.CustomCSS, EnableRSS: *a.EnableRSS, Role: role, } + // Bodge default avatar + header in, + // if we didn't have one already. c.ensureAvatar(accountFrontend) c.ensureHeader(accountFrontend) @@ -227,18 +240,37 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A } func (c *converter) AccountToAPIAccountBlocked(ctx context.Context, a *gtsmodel.Account) (*apimodel.Account, error) { - var acct string - if a.Domain != "" { - // this is a remote user - acct = fmt.Sprintf("%s@%s", a.Username, a.Domain) + var ( + acct string + role *apimodel.AccountRole + ) + + if a.IsRemote() { + // Domain may be in Punycode, + // de-punify it just in case. + d, err := util.DePunify(a.Domain) + if err != nil { + return nil, fmt.Errorf("AccountToAPIAccountPublic: error de-punifying domain %s for account id %s: %w", a.Domain, a.ID, err) + } + + acct = a.Username + "@" + d } else { - // this is a local user + // This is a local user. acct = a.Username - } - var suspended bool - if !a.SuspendedAt.IsZero() { - suspended = true + user, err := c.db.GetUserByAccountID(ctx, a.ID) + if err != nil { + return nil, fmt.Errorf("AccountToAPIAccountPublic: error getting user from database for account id %s: %s", a.ID, err) + } + + switch { + case *user.Admin: + role = &apimodel.AccountRole{Name: apimodel.AccountRoleAdmin} + case *user.Moderator: + role = &apimodel.AccountRole{Name: apimodel.AccountRoleModerator} + default: + role = &apimodel.AccountRole{Name: apimodel.AccountRoleUser} + } } return &apimodel.Account{ @@ -249,7 +281,8 @@ func (c *converter) AccountToAPIAccountBlocked(ctx context.Context, a *gtsmodel. Bot: *a.Bot, CreatedAt: util.FormatISO8601(a.CreatedAt), URL: a.URL, - Suspended: suspended, + Suspended: !a.SuspendedAt.IsZero(), + Role: role, }, nil } @@ -263,15 +296,20 @@ func (c *converter) AccountToAdminAPIAccount(ctx context.Context, a *gtsmodel.Ac inviteRequest *string approved bool disabled bool - silenced bool - suspended bool role = apimodel.AccountRole{Name: apimodel.AccountRoleUser} // assume user by default createdByApplicationID string ) // take user-level information if possible if a.IsRemote() { - domain = &a.Domain + // Domain may be in Punycode, + // de-punify it just in case. + d, err := util.DePunify(a.Domain) + if err != nil { + return nil, fmt.Errorf("AccountToAdminAPIAccount: error de-punifying domain %s for account id %s: %w", a.Domain, a.ID, err) + } + + domain = &d } else { user, err := c.db.GetUserByAccountID(ctx, a.ID) if err != nil { @@ -303,9 +341,6 @@ func (c *converter) AccountToAdminAPIAccount(ctx context.Context, a *gtsmodel.Ac createdByApplicationID = user.CreatedByApplicationID } - silenced = !a.SilencedAt.IsZero() - suspended = !a.SuspendedAt.IsZero() - apiAccount, err := c.AccountToAPIAccountPublic(ctx, a) if err != nil { return nil, fmt.Errorf("AccountToAdminAPIAccount: error converting account to api account for account id %s: %w", a.ID, err) @@ -325,8 +360,8 @@ func (c *converter) AccountToAdminAPIAccount(ctx context.Context, a *gtsmodel.Ac Confirmed: confirmed, Approved: approved, Disabled: disabled, - Silenced: silenced, - Suspended: suspended, + Silenced: !a.SilencedAt.IsZero(), + Suspended: !a.SuspendedAt.IsZero(), Account: apiAccount, CreatedByApplicationID: createdByApplicationID, InvitedByAccountID: "", // not implemented (yet) @@ -428,16 +463,19 @@ func (c *converter) MentionToAPIMention(ctx context.Context, m *gtsmodel.Mention m.TargetAccount = targetAccount } - var local bool - if m.TargetAccount.Domain == "" { - local = true - } - var acct string - if local { + if m.TargetAccount.IsLocal() { acct = m.TargetAccount.Username } else { - acct = fmt.Sprintf("%s@%s", m.TargetAccount.Username, m.TargetAccount.Domain) + // Domain may be in Punycode, + // de-punify it just in case. + d, err := util.DePunify(m.TargetAccount.Domain) + if err != nil { + err = fmt.Errorf("MentionToAPIMention: error de-punifying domain %s for account id %s: %w", m.TargetAccount.Domain, m.TargetAccountID, err) + return apimodel.Mention{}, err + } + + acct = m.TargetAccount.Username + "@" + d } return apimodel.Mention{ @@ -476,6 +514,17 @@ func (c *converter) EmojiToAdminAPIEmoji(ctx context.Context, e *gtsmodel.Emoji) return nil, err } + if e.Domain != "" { + // Domain may be in Punycode, + // de-punify it just in case. + var err error + e.Domain, err = util.DePunify(e.Domain) + if err != nil { + err = fmt.Errorf("EmojiToAdminAPIEmoji: error de-punifying domain %s for emoji id %s: %w", e.Domain, e.ID, err) + return nil, err + } + } + return &apimodel.AdminEmoji{ Emoji: emoji, ID: e.ID, @@ -942,9 +991,16 @@ func (c *converter) NotificationToAPINotification(ctx context.Context, n *gtsmod } func (c *converter) DomainBlockToAPIDomainBlock(ctx context.Context, b *gtsmodel.DomainBlock, export bool) (*apimodel.DomainBlock, error) { + // Domain may be in Punycode, + // de-punify it just in case. + d, err := util.DePunify(b.Domain) + if err != nil { + return nil, fmt.Errorf("DomainBlockToAPIDomainBlock: error de-punifying domain %s: %w", b.Domain, err) + } + domainBlock := &apimodel.DomainBlock{ Domain: apimodel.Domain{ - Domain: b.Domain, + Domain: d, PublicComment: b.PublicComment, }, } |