Profile for {{ .account.Username -}}
@@ -45,18 +117,14 @@- Display name
diff --git a/docs/api/swagger.yaml b/docs/api/swagger.yaml
index 367dae72f..4ce234374 100644
--- a/docs/api/swagger.yaml
+++ b/docs/api/swagger.yaml
@@ -193,6 +193,11 @@ definitions:
example: https://example.org/media/some_user/avatar/original/avatar.jpeg
type: string
x-go-name: Avatar
+ avatar_description:
+ description: Description of this account's avatar, for alt text.
+ example: A cute drawing of a smiling sloth.
+ type: string
+ x-go-name: AvatarDescription
avatar_static:
description: |-
Web location of a static version of the account's avatar.
@@ -259,6 +264,11 @@ definitions:
example: https://example.org/media/some_user/header/original/header.jpeg
type: string
x-go-name: Header
+ header_description:
+ description: Description of this account's header, for alt text.
+ example: A sunlit field with purple flowers.
+ type: string
+ x-go-name: HeaderDescription
header_static:
description: |-
Web location of a static version of the account's header.
@@ -1948,6 +1958,11 @@ definitions:
example: https://example.org/media/some_user/avatar/original/avatar.jpeg
type: string
x-go-name: Avatar
+ avatar_description:
+ description: Description of this account's avatar, for alt text.
+ example: A cute drawing of a smiling sloth.
+ type: string
+ x-go-name: AvatarDescription
avatar_static:
description: |-
Web location of a static version of the account's avatar.
@@ -2014,6 +2029,11 @@ definitions:
example: https://example.org/media/some_user/header/original/header.jpeg
type: string
x-go-name: Header
+ header_description:
+ description: Description of this account's header, for alt text.
+ example: A sunlit field with purple flowers.
+ type: string
+ x-go-name: HeaderDescription
header_static:
description: |-
Web location of a static version of the account's header.
@@ -4072,10 +4092,20 @@ paths:
in: formData
name: avatar
type: file
+ - allowEmptyValue: true
+ description: Description of avatar image, for alt-text.
+ in: formData
+ name: avatar_description
+ type: string
- description: Header of the user.
in: formData
name: header
type: file
+ - allowEmptyValue: true
+ description: Description of header image, for alt-text.
+ in: formData
+ name: header_description
+ type: string
- description: Require manual approval of follow requests.
in: formData
name: locked
diff --git a/internal/api/client/accounts/accountupdate.go b/internal/api/client/accounts/accountupdate.go
index cd8ee35f4..f81f54db0 100644
--- a/internal/api/client/accounts/accountupdate.go
+++ b/internal/api/client/accounts/accountupdate.go
@@ -78,11 +78,23 @@ import (
// description: Avatar of the user.
// type: file
// -
+// name: avatar_description
+// in: formData
+// description: Description of avatar image, for alt-text.
+// type: string
+// allowEmptyValue: true
+// -
// name: header
// in: formData
// description: Header of the user.
// type: file
// -
+// name: header_description
+// in: formData
+// description: Description of header image, for alt-text.
+// type: string
+// allowEmptyValue: true
+// -
// name: locked
// in: formData
// description: Require manual approval of follow requests.
@@ -315,7 +327,9 @@ func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateCredentialsRequest,
form.DisplayName == nil &&
form.Note == nil &&
form.Avatar == nil &&
+ form.AvatarDescription == nil &&
form.Header == nil &&
+ form.HeaderDescription == nil &&
form.Locked == nil &&
form.Source.Privacy == nil &&
form.Source.Sensitive == nil &&
diff --git a/internal/api/client/admin/accountsgetv2_test.go b/internal/api/client/admin/accountsgetv2_test.go
index fdd6c6c30..85d58cce8 100644
--- a/internal/api/client/admin/accountsgetv2_test.go
+++ b/internal/api/client/admin/accountsgetv2_test.go
@@ -234,8 +234,10 @@ func (suite *AccountsGetTestSuite) TestAccountsGetFromTop() {
"url": "http://localhost:8080/@the_mighty_zork",
"avatar": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg",
"avatar_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg",
+ "avatar_description": "a green goblin looking nasty",
"header": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
"header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
+ "header_description": "A very old-school screenshot of the original team fortress mod for quake",
"followers_count": 2,
"following_count": 2,
"statuses_count": 7,
@@ -409,6 +411,7 @@ func (suite *AccountsGetTestSuite) TestAccountsGetFromTop() {
"avatar_static": "",
"header": "http://localhost:8080/fileserver/062G5WYKY35KKD12EMSM3F8PJ8/header/original/01PFPMWK2FF0D9WMHEJHR07C3R.jpg",
"header_static": "http://localhost:8080/fileserver/062G5WYKY35KKD12EMSM3F8PJ8/header/small/01PFPMWK2FF0D9WMHEJHR07C3R.jpg",
+ "header_description": "tweet from thoughts of dog: i drank. all the water. in my bowl. earlier. but just now. i returned. to the same bowl. and it was. full again.. the bowl. is haunted",
"followers_count": 0,
"following_count": 0,
"statuses_count": 0,
diff --git a/internal/api/client/statuses/statushistory_test.go b/internal/api/client/statuses/statushistory_test.go
index a0cb3d482..a88abdb8f 100644
--- a/internal/api/client/statuses/statushistory_test.go
+++ b/internal/api/client/statuses/statushistory_test.go
@@ -108,8 +108,10 @@ func (suite *StatusHistoryTestSuite) TestGetHistory() {
"url": "http://localhost:8080/@the_mighty_zork",
"avatar": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg",
"avatar_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg",
+ "avatar_description": "a green goblin looking nasty",
"header": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
"header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
+ "header_description": "A very old-school screenshot of the original team fortress mod for quake",
"followers_count": 2,
"following_count": 2,
"statuses_count": 7,
diff --git a/internal/api/client/statuses/statusmute_test.go b/internal/api/client/statuses/statusmute_test.go
index a83720a20..83effd0c2 100644
--- a/internal/api/client/statuses/statusmute_test.go
+++ b/internal/api/client/statuses/statusmute_test.go
@@ -126,8 +126,10 @@ func (suite *StatusMuteTestSuite) TestMuteUnmuteStatus() {
"url": "http://localhost:8080/@the_mighty_zork",
"avatar": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg",
"avatar_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg",
+ "avatar_description": "a green goblin looking nasty",
"header": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
"header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
+ "header_description": "A very old-school screenshot of the original team fortress mod for quake",
"followers_count": 2,
"following_count": 2,
"statuses_count": 7,
@@ -189,8 +191,10 @@ func (suite *StatusMuteTestSuite) TestMuteUnmuteStatus() {
"url": "http://localhost:8080/@the_mighty_zork",
"avatar": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg",
"avatar_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg",
+ "avatar_description": "a green goblin looking nasty",
"header": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
"header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
+ "header_description": "A very old-school screenshot of the original team fortress mod for quake",
"followers_count": 2,
"following_count": 2,
"statuses_count": 7,
diff --git a/internal/api/model/account.go b/internal/api/model/account.go
index b3a92d36f..cf39dd08e 100644
--- a/internal/api/model/account.go
+++ b/internal/api/model/account.go
@@ -62,6 +62,9 @@ type Account struct {
// Only relevant when the account's main avatar is a video or a gif.
// example: https://example.org/media/some_user/avatar/static/avatar.png
AvatarStatic string `json:"avatar_static"`
+ // Description of this account's avatar, for alt text.
+ // example: A cute drawing of a smiling sloth.
+ AvatarDescription string `json:"avatar_description,omitempty"`
// Web location of the account's header image.
// example: https://example.org/media/some_user/header/original/header.jpeg
Header string `json:"header"`
@@ -69,6 +72,9 @@ type Account struct {
// Only relevant when the account's main header is a video or a gif.
// example: https://example.org/media/some_user/header/static/header.png
HeaderStatic string `json:"header_static"`
+ // Description of this account's header, for alt text.
+ // example: A sunlit field with purple flowers.
+ HeaderDescription string `json:"header_description,omitempty"`
// Number of accounts following this account, according to our instance.
FollowersCount int `json:"followers_count"`
// Number of account's followed by this account, according to our instance.
@@ -104,6 +110,17 @@ type Account struct {
// If set, indicates that this account is currently inactive, and has migrated to the given account.
// Key/value omitted for accounts that haven't moved, and for suspended accounts.
Moved *Account `json:"moved,omitempty"`
+
+ // Additional fields not exposed via JSON
+ // (used only internally for templating etc).
+
+ // Proper attachment model for the avatar.
+ //
+ // Only set if this model was converted via
+ // AccountToWebAccount, AND this account had
+ // an avatar set (and not just the default
+ // "blank" avatar image.)
+ AvatarAttachment *Attachment `json:"-"`
}
// MutedAccount extends Account with a field used only by the muted user list.
@@ -168,8 +185,12 @@ type UpdateCredentialsRequest struct {
Note *string `form:"note" json:"note"`
// Avatar image encoded using multipart/form-data.
Avatar *multipart.FileHeader `form:"avatar" json:"-"`
+ // Description of the avatar image, for alt-text.
+ AvatarDescription *string `form:"avatar_description" json:"avatar_description"`
// Header image encoded using multipart/form-data
Header *multipart.FileHeader `form:"header" json:"-"`
+ // Description of the header image, for alt-text.
+ HeaderDescription *string `form:"header_description" json:"header_description"`
// Require manual approval of follow requests.
Locked *bool `form:"locked" json:"locked"`
// New Source values for this account.
diff --git a/internal/processing/account/get.go b/internal/processing/account/get.go
index 500c0c2e5..32d45054d 100644
--- a/internal/processing/account/get.go
+++ b/internal/processing/account/get.go
@@ -20,7 +20,6 @@ package account
import (
"context"
"errors"
- "fmt"
"net/url"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
@@ -36,66 +35,42 @@ func (p *Processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account
targetAccount, err := p.state.DB.GetAccountByID(ctx, targetAccountID)
if err != nil {
if errors.Is(err, db.ErrNoEntries) {
- return nil, gtserror.NewErrorNotFound(errors.New("account not found"))
+ err := gtserror.New("account not found")
+ return nil, gtserror.NewErrorNotFound(err)
}
- return nil, gtserror.NewErrorInternalError(fmt.Errorf("db error: %w", err))
+ err := gtserror.Newf("db error getting account: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
}
- return p.getFor(ctx, requestingAccount, targetAccount)
-}
-
-// GetLocalByUsername processes the given request for account information targeting a local account by username.
-func (p *Processor) GetLocalByUsername(ctx context.Context, requestingAccount *gtsmodel.Account, username string) (*apimodel.Account, gtserror.WithCode) {
- targetAccount, err := p.state.DB.GetAccountByUsernameDomain(ctx, username, "")
+ blocked, err := p.state.DB.IsEitherBlocked(ctx, requestingAccount.ID, targetAccount.ID)
if err != nil {
- if errors.Is(err, db.ErrNoEntries) {
- return nil, gtserror.NewErrorNotFound(errors.New("account not found"))
- }
- return nil, gtserror.NewErrorInternalError(fmt.Errorf("db error: %w", err))
+ err := gtserror.Newf("db error checking blocks: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
}
- return p.getFor(ctx, requestingAccount, targetAccount)
-}
-
-// GetCustomCSSForUsername returns custom css for the given local username.
-func (p *Processor) GetCustomCSSForUsername(ctx context.Context, username string) (string, gtserror.WithCode) {
- customCSS, err := p.state.DB.GetAccountCustomCSSByUsername(ctx, username)
- if err != nil {
- if errors.Is(err, db.ErrNoEntries) {
- return "", gtserror.NewErrorNotFound(errors.New("account not found"))
- }
- return "", gtserror.NewErrorInternalError(fmt.Errorf("db error: %w", err))
- }
-
- return customCSS, nil
-}
-
-func (p *Processor) getFor(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (*apimodel.Account, gtserror.WithCode) {
- var err error
-
- if requestingAccount != nil {
- blocked, err := p.state.DB.IsEitherBlocked(ctx, requestingAccount.ID, targetAccount.ID)
+ if blocked {
+ apiAccount, err := p.converter.AccountToAPIAccountBlocked(ctx, targetAccount)
if err != nil {
- return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking account block: %w", err))
- }
-
- if blocked {
- apiAccount, err := p.converter.AccountToAPIAccountBlocked(ctx, targetAccount)
- if err != nil {
- return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting account: %w", err))
- }
- return apiAccount, nil
+ err := gtserror.Newf("error converting account: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
}
+ return apiAccount, nil
}
if targetAccount.Domain != "" {
targetAccountURI, err := url.Parse(targetAccount.URI)
if err != nil {
- return nil, gtserror.NewErrorInternalError(fmt.Errorf("error parsing url %s: %w", targetAccount.URI, err))
+ err := gtserror.Newf("error parsing account URI: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
}
- // Perform a last-minute fetch of target account to ensure remote account header / avatar is cached.
- latest, _, err := p.federator.GetAccountByURI(gtscontext.SetFastFail(ctx), requestingAccount.Username, targetAccountURI)
+ // Perform a last-minute fetch of target account to
+ // ensure remote account header / avatar is cached.
+ latest, _, err := p.federator.GetAccountByURI(
+ gtscontext.SetFastFail(ctx),
+ requestingAccount.Username,
+ targetAccountURI,
+ )
if err != nil {
log.Errorf(ctx, "error fetching latest target account: %v", err)
} else {
@@ -105,15 +80,53 @@ func (p *Processor) getFor(ctx context.Context, requestingAccount *gtsmodel.Acco
}
var apiAccount *apimodel.Account
-
- if requestingAccount != nil && targetAccount.ID == requestingAccount.ID {
+ if targetAccount.ID == requestingAccount.ID {
+ // This is requester's own account,
+ // show additional details.
apiAccount, err = p.converter.AccountToAPIAccountSensitive(ctx, targetAccount)
} else {
+ // This is a different account,
+ // show the "public" view.
apiAccount, err = p.converter.AccountToAPIAccountPublic(ctx, targetAccount)
}
if err != nil {
- return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting account: %w", err))
+ err := gtserror.Newf("error converting account: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
}
return apiAccount, nil
}
+
+// GetWeb returns the web model of a local account by username.
+func (p *Processor) GetWeb(ctx context.Context, username string) (*apimodel.Account, gtserror.WithCode) {
+ targetAccount, err := p.state.DB.GetAccountByUsernameDomain(ctx, username, "")
+ if err != nil {
+ if errors.Is(err, db.ErrNoEntries) {
+ err := gtserror.New("account not found")
+ return nil, gtserror.NewErrorNotFound(err)
+ }
+ err := gtserror.Newf("db error getting account: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ webAccount, err := p.converter.AccountToWebAccount(ctx, targetAccount)
+ if err != nil {
+ err := gtserror.Newf("error converting account: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ return webAccount, nil
+}
+
+// GetCustomCSSForUsername returns custom css for the given local username.
+func (p *Processor) GetCustomCSSForUsername(ctx context.Context, username string) (string, gtserror.WithCode) {
+ customCSS, err := p.state.DB.GetAccountCustomCSSByUsername(ctx, username)
+ if err != nil {
+ if errors.Is(err, db.ErrNoEntries) {
+ return "", gtserror.NewErrorNotFound(gtserror.New("account not found"))
+ }
+ return "", gtserror.NewErrorInternalError(gtserror.Newf("db error: %w", err))
+ }
+
+ return customCSS, nil
+}
diff --git a/internal/processing/account/update.go b/internal/processing/account/update.go
index 61e88501f..ba9360c36 100644
--- a/internal/processing/account/update.go
+++ b/internal/processing/account/update.go
@@ -204,11 +204,16 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
}
}
+ if form.AvatarDescription != nil {
+ desc := text.SanitizeToPlaintext(*form.AvatarDescription)
+ form.AvatarDescription = util.Ptr(desc)
+ }
+
if form.Avatar != nil && form.Avatar.Size != 0 {
avatarInfo, errWithCode := p.UpdateAvatar(ctx,
account,
form.Avatar,
- nil,
+ form.AvatarDescription,
)
if errWithCode != nil {
return nil, errWithCode
@@ -216,13 +221,29 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
account.AvatarMediaAttachmentID = avatarInfo.ID
account.AvatarMediaAttachment = avatarInfo
log.Tracef(ctx, "new avatar info for account %s is %+v", account.ID, avatarInfo)
+ } else if form.AvatarDescription != nil && account.AvatarMediaAttachment != nil {
+ // Update just existing description if possible.
+ account.AvatarMediaAttachment.Description = *form.AvatarDescription
+ if err := p.state.DB.UpdateAttachment(
+ ctx,
+ account.AvatarMediaAttachment,
+ "description",
+ ); err != nil {
+ err := gtserror.Newf("db error updating account avatar description: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+ }
+
+ if form.HeaderDescription != nil {
+ desc := text.SanitizeToPlaintext(*form.HeaderDescription)
+ form.HeaderDescription = util.Ptr(desc)
}
if form.Header != nil && form.Header.Size != 0 {
headerInfo, errWithCode := p.UpdateHeader(ctx,
account,
form.Header,
- nil,
+ form.HeaderDescription,
)
if errWithCode != nil {
return nil, errWithCode
@@ -230,6 +251,17 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
account.HeaderMediaAttachmentID = headerInfo.ID
account.HeaderMediaAttachment = headerInfo
log.Tracef(ctx, "new header info for account %s is %+v", account.ID, headerInfo)
+ } else if form.HeaderDescription != nil && account.HeaderMediaAttachment != nil {
+ // Update just existing description if possible.
+ account.HeaderMediaAttachment.Description = *form.HeaderDescription
+ if err := p.state.DB.UpdateAttachment(
+ ctx,
+ account.HeaderMediaAttachment,
+ "description",
+ ); err != nil {
+ err := gtserror.Newf("db error updating account avatar description: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
}
if form.Locked != nil {
diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go
index a8f9b7f8f..733a21b75 100644
--- a/internal/typeutils/internaltofrontend.go
+++ b/internal/typeutils/internaltofrontend.go
@@ -162,6 +162,38 @@ func (c *Converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
return account, nil
}
+// AccountToWebAccount converts a gts model account into an
+// api representation suitable for serving into a web template.
+//
+// Should only be used when preparing to template an account,
+// callers looking to serialize an account into a model for
+// serving over the client API should always use one of the
+// AccountToAPIAccount functions instead.
+func (c *Converter) AccountToWebAccount(
+ ctx context.Context,
+ a *gtsmodel.Account,
+) (*apimodel.Account, error) {
+ webAccount, err := c.AccountToAPIAccountPublic(ctx, a)
+ if err != nil {
+ return nil, err
+ }
+
+ // Set additional avatar information for
+ // serving the avatar in a nice photobox.
+ if a.AvatarMediaAttachment != nil {
+ avatarAttachment, err := c.AttachmentToAPIAttachment(ctx, a.AvatarMediaAttachment)
+ if err != nil {
+ // This is just extra data so just
+ // log but don't return any error.
+ log.Errorf(ctx, "error converting account avatar attachment: %v", err)
+ } else {
+ webAccount.AvatarAttachment = &avatarAttachment
+ }
+ }
+
+ return webAccount, nil
+}
+
// accountToAPIAccountPublic provides all the logic for AccountToAPIAccount, MINUS fetching moved account, to prevent possible recursion.
func (c *Converter) accountToAPIAccountPublic(ctx context.Context, a *gtsmodel.Account) (*apimodel.Account, error) {
@@ -210,18 +242,22 @@ func (c *Converter) accountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
var (
aviURL string
aviURLStatic string
+ aviDesc string
headerURL string
headerURLStatic string
+ headerDesc string
)
if a.AvatarMediaAttachment != nil {
aviURL = a.AvatarMediaAttachment.URL
aviURLStatic = a.AvatarMediaAttachment.Thumbnail.URL
+ aviDesc = a.AvatarMediaAttachment.Description
}
if a.HeaderMediaAttachment != nil {
headerURL = a.HeaderMediaAttachment.URL
headerURLStatic = a.HeaderMediaAttachment.Thumbnail.URL
+ headerDesc = a.HeaderMediaAttachment.Description
}
// convert account gts model fields to front api model fields
@@ -294,32 +330,34 @@ func (c *Converter) accountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
// can be populated directly below.
accountFrontend := &apimodel.Account{
- ID: a.ID,
- Username: a.Username,
- Acct: acct,
- DisplayName: a.DisplayName,
- Locked: locked,
- Discoverable: discoverable,
- Bot: bot,
- CreatedAt: util.FormatISO8601(a.CreatedAt),
- Note: a.Note,
- URL: a.URL,
- Avatar: aviURL,
- AvatarStatic: aviURLStatic,
- Header: headerURL,
- HeaderStatic: headerURLStatic,
- FollowersCount: followersCount,
- FollowingCount: followingCount,
- StatusesCount: statusesCount,
- LastStatusAt: lastStatusAt,
- Emojis: apiEmojis,
- Fields: fields,
- Suspended: !a.SuspendedAt.IsZero(),
- Theme: theme,
- CustomCSS: customCSS,
- EnableRSS: enableRSS,
- HideCollections: hideCollections,
- Role: role,
+ ID: a.ID,
+ Username: a.Username,
+ Acct: acct,
+ DisplayName: a.DisplayName,
+ Locked: locked,
+ Discoverable: discoverable,
+ Bot: bot,
+ CreatedAt: util.FormatISO8601(a.CreatedAt),
+ Note: a.Note,
+ URL: a.URL,
+ Avatar: aviURL,
+ AvatarStatic: aviURLStatic,
+ AvatarDescription: aviDesc,
+ Header: headerURL,
+ HeaderStatic: headerURLStatic,
+ HeaderDescription: headerDesc,
+ FollowersCount: followersCount,
+ FollowingCount: followingCount,
+ StatusesCount: statusesCount,
+ LastStatusAt: lastStatusAt,
+ Emojis: apiEmojis,
+ Fields: fields,
+ Suspended: !a.SuspendedAt.IsZero(),
+ Theme: theme,
+ CustomCSS: customCSS,
+ EnableRSS: enableRSS,
+ HideCollections: hideCollections,
+ Role: role,
}
// Bodge default avatar + header in,
diff --git a/internal/typeutils/internaltofrontend_test.go b/internal/typeutils/internaltofrontend_test.go
index 16dc27c87..522bf6401 100644
--- a/internal/typeutils/internaltofrontend_test.go
+++ b/internal/typeutils/internaltofrontend_test.go
@@ -57,8 +57,10 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontend() {
"url": "http://localhost:8080/@the_mighty_zork",
"avatar": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg",
"avatar_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg",
+ "avatar_description": "a green goblin looking nasty",
"header": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
"header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
+ "header_description": "A very old-school screenshot of the original team fortress mod for quake",
"followers_count": 2,
"following_count": 2,
"statuses_count": 7,
@@ -108,8 +110,10 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendAliasedAndMoved()
"url": "http://localhost:8080/@the_mighty_zork",
"avatar": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg",
"avatar_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg",
+ "avatar_description": "a green goblin looking nasty",
"header": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
"header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
+ "header_description": "A very old-school screenshot of the original team fortress mod for quake",
"followers_count": 2,
"following_count": 2,
"statuses_count": 7,
@@ -199,8 +203,10 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiStruct()
"url": "http://localhost:8080/@the_mighty_zork",
"avatar": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg",
"avatar_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg",
+ "avatar_description": "a green goblin looking nasty",
"header": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
"header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
+ "header_description": "A very old-school screenshot of the original team fortress mod for quake",
"followers_count": 2,
"following_count": 2,
"statuses_count": 7,
@@ -247,8 +253,10 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiIDs() {
"url": "http://localhost:8080/@the_mighty_zork",
"avatar": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg",
"avatar_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg",
+ "avatar_description": "a green goblin looking nasty",
"header": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
"header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
+ "header_description": "A very old-school screenshot of the original team fortress mod for quake",
"followers_count": 2,
"following_count": 2,
"statuses_count": 7,
@@ -291,8 +299,10 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendSensitive() {
"url": "http://localhost:8080/@the_mighty_zork",
"avatar": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg",
"avatar_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg",
+ "avatar_description": "a green goblin looking nasty",
"header": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
"header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
+ "header_description": "A very old-school screenshot of the original team fortress mod for quake",
"followers_count": 2,
"following_count": 2,
"statuses_count": 7,
diff --git a/internal/web/profile.go b/internal/web/profile.go
index 1dbf5c73d..ca613900f 100644
--- a/internal/web/profile.go
+++ b/internal/web/profile.go
@@ -28,7 +28,6 @@ import (
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
- "github.com/superseriousbusiness/gotosocial/internal/oauth"
)
func (m *Module) profileGETHandler(c *gin.Context) {
@@ -79,16 +78,8 @@ func (m *Module) profileGETHandler(c *gin.Context) {
// text/html has been requested. Proceed with getting the web view of the account.
- // Don't require auth for web endpoints, but do take it if it was provided.
- // authed.Account might end up nil here, but that's fine in case of public pages.
- authed, err := oauth.Authed(c, false, false, false, false)
- if err != nil {
- apiutil.WebErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
// Fetch the target account so we can do some checks on it.
- targetAccount, errWithCode := m.processor.Account().GetLocalByUsername(ctx, authed.Account, targetUsername)
+ targetAccount, errWithCode := m.processor.Account().GetWeb(ctx, targetUsername)
if errWithCode != nil {
apiutil.WebErrorHandler(c, errWithCode, instanceGet)
return
diff --git a/internal/web/thread.go b/internal/web/thread.go
index 05bd63ebe..492d40103 100644
--- a/internal/web/thread.go
+++ b/internal/web/thread.go
@@ -29,7 +29,6 @@ import (
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
- "github.com/superseriousbusiness/gotosocial/internal/oauth"
)
func (m *Module) threadGETHandler(c *gin.Context) {
@@ -88,16 +87,8 @@ func (m *Module) threadGETHandler(c *gin.Context) {
// text/html has been requested. Proceed with getting the web view of the status.
- // Don't require auth for web endpoints, but do take it if it was provided.
- // authed.Account might end up nil here, but that's fine in case of public pages.
- authed, err := oauth.Authed(c, false, false, false, false)
- if err != nil {
- apiutil.WebErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
// Fetch the target account so we can do some checks on it.
- targetAccount, errWithCode := m.processor.Account().GetLocalByUsername(ctx, authed.Account, targetUsername)
+ targetAccount, errWithCode := m.processor.Account().GetWeb(ctx, targetUsername)
if errWithCode != nil {
apiutil.WebErrorHandler(c, errWithCode, instanceGet)
return
diff --git a/testrig/testmodels.go b/testrig/testmodels.go
index 3db8ef62f..de6e97142 100644
--- a/testrig/testmodels.go
+++ b/testrig/testmodels.go
@@ -969,7 +969,7 @@ func NewTestAttachments() map[string]*gtsmodel.MediaAttachment {
},
},
AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
- Description: "A very old-school screenshot of the original team fortress mod for quake ",
+ Description: "A very old-school screenshot of the original team fortress mod for quake",
ScheduledStatusID: "",
Blurhash: "L26j{^WCs+R-N}jsxWj@4;WWxDoK",
Processing: 2,
diff --git a/web/source/css/profile.css b/web/source/css/profile.css
index a966d768a..3f7f43d0d 100644
--- a/web/source/css/profile.css
+++ b/web/source/css/profile.css
@@ -82,18 +82,37 @@
margin-top: calc(-1 * $overlap);
gap: 0 1rem;
- .avatar {
+ .avatar-image-wrapper {
grid-area: avatar;
- height: $avatar-size;
- width: $avatar-size;
+
border: 0.2rem solid $avatar-border;
border-radius: $br;
- overflow: hidden; /* prevents image extending beyond rounded borders */
+
+ /*
+ Wrapper always same
+ size + proportions no
+ matter image inside.
+ */
+ height: $avatar-size;
+ width: $avatar-size;
- img {
+ .avatar {
+ /*
+ Fit 100% of the wrapper.
+ */
height: 100%;
width: 100%;
+
+ /*
+ Normalize non-square images.
+ */
object-fit: cover;
+
+ /*
+ Prevent image extending
+ beyond rounded borders.
+ */
+ border-radius: $br-inner;
}
}
diff --git a/web/source/settings/components/profile.tsx b/web/source/settings/components/profile.tsx
index 4a5157378..24cb3c4c2 100644
--- a/web/source/settings/components/profile.tsx
+++ b/web/source/settings/components/profile.tsx
@@ -27,9 +27,11 @@ export default function FakeProfile({ avatar, header, display_name, username, ro