Compare commits

...

6 commits

Author SHA1 Message Date
Daenney 8946576c41
Merge 9acf9ddc75 into d3d6e3f920 2024-10-04 22:17:24 -07:00
tobi d3d6e3f920
[bugfix] Don't try to add nil filtered statuses to context (#3388) 2024-10-04 19:23:18 +02:00
tobi 8bd8c6fb45
[bugfix] Include own account in conversation when no other accounts involved (#3387) 2024-10-04 19:22:52 +02:00
kim f550f596fa
[performance] remove the pragma optimize analysis limit on connection close (#3386) 2024-10-04 19:05:42 +02:00
cui fliter 23b6d2cc64
fix: fix slice init length (#3382) 2024-10-03 17:22:26 +00:00
Daenney 9acf9ddc75 [docs] Set the right site_url
Though the entry point is docs.gotosocial.org, that's redirected by RTD
to docs.gotosocial.org/en/latest/ which is where the actual site is
served from. However, other URLs like docs.gotosocial.org/admin aren't
redirected to docs.gotosocial.org/en/latest/admin. They just 404.

Without us including the /en/latest/ all the generated og:img URLs as
well as the link rel=canonical result in URLs that all 404.

This means that currently the social cards aren't working well, but
indexing the docs site by search engines is probably also partially
broken, since our sitemap.xml is also pointing at things that don't
exist.
2024-07-02 16:30:18 +02:00
8 changed files with 395 additions and 34 deletions

View file

@ -950,7 +950,12 @@ definitions:
with "direct message" visibility.
properties:
accounts:
description: Participants in the conversation.
description: |-
Participants in the conversation.
If this is a conversation between no accounts (ie., a self-directed DM),
this will include only the requesting account itself. Otherwise, it will
include every other account in the conversation *except* the requester.
items:
$ref: '#/definitions/account'
type: array

View file

@ -27,6 +27,10 @@ type Conversation struct {
// Is the conversation currently marked as unread?
Unread bool `json:"unread"`
// Participants in the conversation.
//
// If this is a conversation between no accounts (ie., a self-directed DM),
// this will include only the requesting account itself. Otherwise, it will
// include every other account in the conversation *except* the requester.
Accounts []Account `json:"accounts"`
// The last status in the conversation. May be `null`.
LastStatus *Status `json:"last_status"`

View file

@ -112,7 +112,7 @@ func (c *sqliteConn) Close() (err error) {
raw := c.connIface.(sqlite3driver.Conn).Raw()
// see: https://www.sqlite.org/pragma.html#pragma_optimize
const onClose = "PRAGMA analysis_limit=1000; PRAGMA optimize;"
const onClose = "PRAGMA optimize;"
_ = raw.Exec(onClose)
// Finally, close.

View file

@ -247,6 +247,12 @@ func (p *Processor) GetVisibleAPIStatuses(
continue
}
if apiStatus == nil {
// Status was
// filtered out.
continue
}
// Append converted status to return slice.
apiStatuses = append(apiStatuses, *apiStatus)
}

View file

@ -1832,46 +1832,23 @@ func (c *Converter) NotificationToAPINotification(
func (c *Converter) ConversationToAPIConversation(
ctx context.Context,
conversation *gtsmodel.Conversation,
requestingAccount *gtsmodel.Account,
requester *gtsmodel.Account,
filters []*gtsmodel.Filter,
mutes *usermute.CompiledUserMuteList,
) (*apimodel.Conversation, error) {
apiConversation := &apimodel.Conversation{
ID: conversation.ID,
Unread: !*conversation.Read,
Accounts: []apimodel.Account{},
}
for _, account := range conversation.OtherAccounts {
var apiAccount *apimodel.Account
blocked, err := c.state.DB.IsEitherBlocked(ctx, requestingAccount.ID, account.ID)
if err != nil {
return nil, gtserror.Newf(
"DB error checking blocks between accounts %s and %s: %w",
requestingAccount.ID,
account.ID,
err,
)
}
if blocked || account.IsSuspended() {
apiAccount, err = c.AccountToAPIAccountBlocked(ctx, account)
} else {
apiAccount, err = c.AccountToAPIAccountPublic(ctx, account)
}
if err != nil {
return nil, gtserror.Newf(
"error converting account %s to API representation: %w",
account.ID,
err,
)
}
apiConversation.Accounts = append(apiConversation.Accounts, *apiAccount)
ID: conversation.ID,
Unread: !*conversation.Read,
}
// Populate most recent status in convo;
// can be nil if this status is filtered.
if conversation.LastStatus != nil {
var err error
apiConversation.LastStatus, err = c.StatusToAPIStatus(
ctx,
conversation.LastStatus,
requestingAccount,
requester,
statusfilter.FilterContextNotifications,
filters,
mutes,
@ -1885,6 +1862,60 @@ func (c *Converter) ConversationToAPIConversation(
}
}
// If no other accounts are involved in this convo,
// just include the requesting account and return.
//
// See: https://github.com/superseriousbusiness/gotosocial/issues/3385#issuecomment-2394033477
otherAcctsLen := len(conversation.OtherAccounts)
if otherAcctsLen == 0 {
apiAcct, err := c.AccountToAPIAccountPublic(ctx, requester)
if err != nil {
err := gtserror.Newf(
"error converting account %s to API representation: %w",
requester.ID, err,
)
return nil, err
}
apiConversation.Accounts = []apimodel.Account{*apiAcct}
return apiConversation, nil
}
// Other accounts are involved in the
// convo. Convert each to API model.
apiConversation.Accounts = make([]apimodel.Account, otherAcctsLen)
for i, account := range conversation.OtherAccounts {
blocked, err := c.state.DB.IsEitherBlocked(ctx,
requester.ID, account.ID,
)
if err != nil {
err := gtserror.Newf(
"db error checking blocks between accounts %s and %s: %w",
requester.ID, account.ID, err,
)
return nil, err
}
// API account model varies depending
// on status of conversation participant.
var apiAcct *apimodel.Account
if blocked || account.IsSuspended() {
apiAcct, err = c.AccountToAPIAccountBlocked(ctx, account)
} else {
apiAcct, err = c.AccountToAPIAccountPublic(ctx, account)
}
if err != nil {
err := gtserror.Newf(
"error converting account %s to API representation: %w",
account.ID, err,
)
return nil, err
}
apiConversation.Accounts[i] = *apiAcct
}
return apiConversation, nil
}

View file

@ -3358,6 +3358,321 @@ func (suite *InternalToFrontendTestSuite) TestIntReqToAPI() {
}`, string(b))
}
func (suite *InternalToFrontendTestSuite) TestConversationToAPISelfConvo() {
var (
ctx = context.Background()
requester = suite.testAccounts["local_account_1"]
lastStatus = suite.testStatuses["local_account_1_status_1"]
filters []*gtsmodel.Filter = nil
mutes *usermute.CompiledUserMuteList = nil
)
convo := &gtsmodel.Conversation{
ID: "01J9C6K86PKZ5GY5WXV94DGH6R",
CreatedAt: testrig.TimeMustParse("2022-06-10T15:22:08Z"),
UpdatedAt: testrig.TimeMustParse("2022-06-10T15:22:08Z"),
AccountID: requester.ID,
Account: requester,
OtherAccounts: nil,
LastStatus: lastStatus,
Read: util.Ptr(true),
}
apiConvo, err := suite.typeconverter.ConversationToAPIConversation(
ctx,
convo,
requester,
filters,
mutes,
)
if err != nil {
suite.FailNow(err.Error())
}
b, err := json.MarshalIndent(apiConvo, "", " ")
if err != nil {
suite.FailNow(err.Error())
}
// No other accounts involved, so we should only
// have our own account in the "accounts" field.
suite.Equal(`{
"id": "01J9C6K86PKZ5GY5WXV94DGH6R",
"unread": false,
"accounts": [
{
"id": "01F8MH1H7YV1Z7D2C8K2730QBF",
"username": "the_mighty_zork",
"acct": "the_mighty_zork",
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
"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.webp",
"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.webp",
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
"followers_count": 2,
"following_count": 2,
"statuses_count": 8,
"last_status_at": "2024-01-10T09:24:00.000Z",
"emojis": [],
"fields": [],
"enable_rss": true
}
],
"last_status": {
"id": "01F8MHAMCHF6Y650WCRSCP4WMY",
"created_at": "2021-10-20T10:40:37.000Z",
"in_reply_to_id": null,
"in_reply_to_account_id": null,
"sensitive": true,
"spoiler_text": "introduction post",
"visibility": "public",
"language": "en",
"uri": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
"url": "http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
"replies_count": 2,
"reblogs_count": 1,
"favourites_count": 1,
"favourited": false,
"reblogged": false,
"muted": false,
"bookmarked": false,
"pinned": false,
"content": "hello everyone!",
"reblog": null,
"application": {
"name": "really cool gts application",
"website": "https://reallycool.app"
},
"account": {
"id": "01F8MH1H7YV1Z7D2C8K2730QBF",
"username": "the_mighty_zork",
"acct": "the_mighty_zork",
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
"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.webp",
"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.webp",
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
"followers_count": 2,
"following_count": 2,
"statuses_count": 8,
"last_status_at": "2024-01-10T09:24:00.000Z",
"emojis": [],
"fields": [],
"enable_rss": true
},
"media_attachments": [],
"mentions": [],
"tags": [],
"emojis": [],
"card": null,
"poll": null,
"text": "hello everyone!",
"interaction_policy": {
"can_favourite": {
"always": [
"public",
"me"
],
"with_approval": []
},
"can_reply": {
"always": [
"public",
"me"
],
"with_approval": []
},
"can_reblog": {
"always": [
"public",
"me"
],
"with_approval": []
}
}
}
}`, string(b))
}
func (suite *InternalToFrontendTestSuite) TestConversationToAPI() {
var (
ctx = context.Background()
requester = suite.testAccounts["local_account_1"]
lastStatus = suite.testStatuses["local_account_1_status_1"]
filters []*gtsmodel.Filter = nil
mutes *usermute.CompiledUserMuteList = nil
)
convo := &gtsmodel.Conversation{
ID: "01J9C6K86PKZ5GY5WXV94DGH6R",
CreatedAt: testrig.TimeMustParse("2022-06-10T15:22:08Z"),
UpdatedAt: testrig.TimeMustParse("2022-06-10T15:22:08Z"),
AccountID: requester.ID,
Account: requester,
OtherAccounts: []*gtsmodel.Account{
suite.testAccounts["local_account_2"],
},
LastStatus: lastStatus,
Read: util.Ptr(false),
}
apiConvo, err := suite.typeconverter.ConversationToAPIConversation(
ctx,
convo,
requester,
filters,
mutes,
)
if err != nil {
suite.FailNow(err.Error())
}
b, err := json.MarshalIndent(apiConvo, "", " ")
if err != nil {
suite.FailNow(err.Error())
}
// One other account is involved, so they
// should in the "accounts" field and not us.
suite.Equal(`{
"id": "01J9C6K86PKZ5GY5WXV94DGH6R",
"unread": true,
"accounts": [
{
"id": "01F8MH5NBDF2MV7CTC4Q5128HF",
"username": "1happyturtle",
"acct": "1happyturtle",
"display_name": "happy little turtle :3",
"locked": true,
"discoverable": false,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
"url": "http://localhost:8080/@1happyturtle",
"avatar": "",
"avatar_static": "",
"header": "http://localhost:8080/assets/default_header.webp",
"header_static": "http://localhost:8080/assets/default_header.webp",
"followers_count": 1,
"following_count": 1,
"statuses_count": 8,
"last_status_at": "2021-07-28T08:40:37.000Z",
"emojis": [],
"fields": [
{
"name": "should you follow me?",
"value": "maybe!",
"verified_at": null
},
{
"name": "age",
"value": "120",
"verified_at": null
}
],
"hide_collections": true
}
],
"last_status": {
"id": "01F8MHAMCHF6Y650WCRSCP4WMY",
"created_at": "2021-10-20T10:40:37.000Z",
"in_reply_to_id": null,
"in_reply_to_account_id": null,
"sensitive": true,
"spoiler_text": "introduction post",
"visibility": "public",
"language": "en",
"uri": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
"url": "http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
"replies_count": 2,
"reblogs_count": 1,
"favourites_count": 1,
"favourited": false,
"reblogged": false,
"muted": false,
"bookmarked": false,
"pinned": false,
"content": "hello everyone!",
"reblog": null,
"application": {
"name": "really cool gts application",
"website": "https://reallycool.app"
},
"account": {
"id": "01F8MH1H7YV1Z7D2C8K2730QBF",
"username": "the_mighty_zork",
"acct": "the_mighty_zork",
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
"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.webp",
"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.webp",
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
"followers_count": 2,
"following_count": 2,
"statuses_count": 8,
"last_status_at": "2024-01-10T09:24:00.000Z",
"emojis": [],
"fields": [],
"enable_rss": true
},
"media_attachments": [],
"mentions": [],
"tags": [],
"emojis": [],
"card": null,
"poll": null,
"text": "hello everyone!",
"interaction_policy": {
"can_favourite": {
"always": [
"public",
"me"
],
"with_approval": []
},
"can_reply": {
"always": [
"public",
"me"
],
"with_approval": []
},
"can_reblog": {
"always": [
"public",
"me"
],
"with_approval": []
}
}
}
}`, string(b))
}
func TestInternalToFrontendTestSuite(t *testing.T) {
suite.Run(t, new(InternalToFrontendTestSuite))
}

View file

@ -1,5 +1,5 @@
site_name: GoToSocial Documentation
site_url: https://docs.gotosocial.org
site_url: https://docs.gotosocial.org/en/latest/
theme:
name: material
language: en

View file

@ -618,7 +618,7 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
}
if diff := len(accountsSorted) - len(preserializedKeys); diff > 0 {
keyStrings := make([]string, diff)
keyStrings := make([]string, 0, diff)
for i := 0; i < diff; i++ {
priv, _ := rsa.GenerateKey(rand.Reader, 2048)
key, _ := x509.MarshalPKCS8PrivateKey(priv)