[feature] Application creation + management via API + settings panel (#3906)

* [feature] Application creation + management via API + settings panel

* fix docs links

* add errnorows test

* use known application as shorter

* add comment about side effects
This commit is contained in:
tobi
2025-03-17 15:06:17 +01:00
committed by GitHub
parent d3c3d34aae
commit d5847e2d2b
61 changed files with 3036 additions and 252 deletions

View File

@@ -623,8 +623,14 @@ func (c *Converter) AppToAPIAppSensitive(ctx context.Context, a *gtsmodel.Applic
return nil, gtserror.Newf("error getting VAPID public key: %w", err)
}
createdAt, err := id.TimeFromULID(a.ID)
if err != nil {
return nil, gtserror.Newf("error converting id to time: %w", err)
}
return &apimodel.Application{
ID: a.ID,
CreatedAt: util.FormatISO8601(createdAt),
Name: a.Name,
Website: a.Website,
RedirectURI: strings.Join(a.RedirectURIs, "\n"),
@@ -1412,14 +1418,28 @@ func (c *Converter) baseStatusToFrontend(
apiStatus.InReplyToAccountID = util.PtrIf(s.InReplyToAccountID)
apiStatus.Language = util.PtrIf(s.Language)
if app := s.CreatedWithApplication; app != nil {
apiStatus.Application, err = c.AppToAPIAppPublic(ctx, app)
switch {
case s.CreatedWithApplication != nil:
// App exists for this status and is set.
apiStatus.Application, err = c.AppToAPIAppPublic(ctx, s.CreatedWithApplication)
if err != nil {
return nil, gtserror.Newf(
"error converting application %s: %w",
s.CreatedWithApplicationID, err,
)
}
case s.CreatedWithApplicationID != "":
// App existed for this status but not
// anymore, it's probably been cleaned up.
// Set a dummy application.
apiStatus.Application = &apimodel.Application{
Name: "unknown application",
}
default:
// No app stored for this (probably remote)
// status, so nothing to do (app is optional).
}
if s.Poll != nil {

View File

@@ -753,6 +753,156 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontendHTMLContentWarning
}`, string(b))
}
func (suite *InternalToFrontendTestSuite) TestStatusToFrontendApplicationDeleted() {
ctx := context.Background()
testStatus := suite.testStatuses["admin_account_status_1"]
// Delete the application this status was created with.
if err := suite.state.DB.DeleteApplicationByID(ctx, testStatus.CreatedWithApplicationID); err != nil {
suite.FailNow(err.Error())
}
requestingAccount := suite.testAccounts["local_account_1"]
apiStatus, err := suite.typeconverter.StatusToAPIStatus(ctx, testStatus, requestingAccount, statusfilter.FilterContextNone, nil, nil)
suite.NoError(err)
b, err := json.MarshalIndent(apiStatus, "", " ")
suite.NoError(err)
suite.Equal(`{
"id": "01F8MH75CBF9JFX4ZAD54N0W0R",
"created_at": "2021-10-20T11:36:45.000Z",
"edited_at": null,
"in_reply_to_id": null,
"in_reply_to_account_id": null,
"sensitive": false,
"spoiler_text": "",
"visibility": "public",
"language": "en",
"uri": "http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R",
"url": "http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R",
"replies_count": 1,
"reblogs_count": 0,
"favourites_count": 1,
"favourited": true,
"reblogged": false,
"muted": false,
"bookmarked": true,
"pinned": false,
"content": "\u003cp\u003ehello world! \u003ca href=\"http://localhost:8080/tags/welcome\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\"\u003e#\u003cspan\u003ewelcome\u003c/span\u003e\u003c/a\u003e ! first post on the instance :rainbow: !\u003c/p\u003e",
"reblog": null,
"application": {
"name": "unknown application"
},
"account": {
"id": "01F8MH17FWEB39HZJ76B6VXSKF",
"username": "admin",
"acct": "admin",
"display_name": "",
"locked": false,
"discoverable": true,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
"url": "http://localhost:8080/@admin",
"avatar": "",
"avatar_static": "",
"header": "http://localhost:8080/assets/default_header.webp",
"header_static": "http://localhost:8080/assets/default_header.webp",
"header_description": "Flat gray background (default header).",
"followers_count": 1,
"following_count": 1,
"statuses_count": 4,
"last_status_at": "2021-10-20",
"emojis": [],
"fields": [],
"enable_rss": true,
"roles": [
{
"id": "admin",
"name": "admin",
"color": ""
}
],
"group": false
},
"media_attachments": [
{
"id": "01F8MH6NEM8D7527KZAECTCR76",
"type": "image",
"url": "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg",
"text_url": "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg",
"preview_url": "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/small/01F8MH6NEM8D7527KZAECTCR76.webp",
"remote_url": null,
"preview_remote_url": null,
"meta": {
"original": {
"width": 1200,
"height": 630,
"size": "1200x630",
"aspect": 1.9047619
},
"small": {
"width": 512,
"height": 268,
"size": "512x268",
"aspect": 1.9104477
},
"focus": {
"x": 0,
"y": 0
}
},
"description": "Black and white image of some 50's style text saying: Welcome On Board",
"blurhash": "LIIE|gRj00WB-;j[t7j[4nWBj[Rj"
}
],
"mentions": [],
"tags": [
{
"name": "welcome",
"url": "http://localhost:8080/tags/welcome"
}
],
"emojis": [
{
"shortcode": "rainbow",
"url": "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png",
"static_url": "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png",
"visible_in_picker": true,
"category": "reactions"
}
],
"card": null,
"poll": null,
"text": "hello world! #welcome ! first post on the instance :rainbow: !",
"content_type": "text/plain",
"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))
}
// Modify a fixture status into a status that should be filtered,
// and then filter it, returning the API status or any error from converting it.
func (suite *InternalToFrontendTestSuite) filteredStatusToFrontend(action gtsmodel.FilterAction, boost bool) (*apimodel.Status, error) {