[bugfix] Store and expose status content type (#3870)

* Add ContentType to internal models

* Add ContentType to API models StatusSource and StatusEdit

* Add helpers to convert between API/internal StatusContentType

* Write status content type on create/edit

* Add migration

* Update API docs

go run github.com/go-swagger/go-swagger/cmd/swagger generate spec --scan-models --exclude-deps --output docs/api/swagger.yaml

* ensure ContentType is updated anywhere Text is

* Update docs, take care of TODOs

* Set ContentType in more places where Text is set

* We don't actually use ContentType on the API status model

* Update StatusSource test

* Remove unused helper function I copied

* Revert change to StatusContentType swagger annotation

I'm going to include this in a follow-on PR instead.

* Add test for updating content type in edits

* Return a value from processContentType instead of modifying the existing status

Fixes an issue that was caught by the test I just added - the recorded edit would be marked with the *new* content type instead of the old one, which is obviously bad

* Add test for handling of statuses with no stored content type

* repurpose an existing test status instead of adding a new one to avoid breaking other tests

* Add test to ensure newly created statuses always have content type saved

* Do include content type on status API model actually

This is mostly important when deleting and redrafting.

The comment on `apimodel.Status.Text` implies that it's not sent except in response to status deletion, but actually this doesn't seem to be the case; it also appears to be present in responses to creations and normal fetches and stuff. So I'm treating `ContentType` the same here.

* Update new tests to check content type on API statuses

* Check content type of API statuses in all tests where text is checked

* update other api tests with status content type field

* Add test ensuring text and content type are returned when deleting a status

* Convert processContentType to free function and remove unused parameter

* check for the correct value in the deletion test

* Be explicit about this test status having an empty content type

* Use omitempty consistently on API models

* clean up the final diff a bit

* one more swagger regen for the road

* Handle nil statuses in processContentType

* Don't pass processContentType the entire edit form, it doesn't need it

* Move processContentType to common.go and use for creation as well

* Remove unused parameters to ContentTypeToAPIContentType
This commit is contained in:
ewwwin 2025-03-06 11:31:52 -05:00 committed by GitHub
commit 424f62dd70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 560 additions and 14 deletions

View file

@ -1408,6 +1408,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R",
Content: "hello world! #welcome ! first post on the instance :rainbow: !",
Text: "hello world! #welcome ! first post on the instance :rainbow: !",
ContentType: gtsmodel.StatusContentTypePlain,
AttachmentIDs: []string{"01F8MH6NEM8D7527KZAECTCR76"},
TagIDs: []string{"01F8MHA1A2NF9MJ3WCCQ3K8BSZ"},
MentionIDs: []string{},
@ -1436,6 +1437,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@admin/statuses/01F8MHAAY43M6RJ473VQFCVH37",
Content: "🐕🐕🐕🐕🐕",
Text: "🐕🐕🐕🐕🐕",
ContentType: gtsmodel.StatusContentTypePlain,
CreatedAt: TimeMustParse("2021-10-20T12:36:45Z"),
EditedAt: time.Time{},
Local: util.Ptr(true),
@ -1459,6 +1461,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0",
Content: "hi @the_mighty_zork welcome to the instance!",
Text: "hi @the_mighty_zork welcome to the instance!",
ContentType: gtsmodel.StatusContentTypePlain,
CreatedAt: TimeMustParse("2021-11-20T13:32:16Z"),
EditedAt: time.Time{},
Local: util.Ptr(true),
@ -1506,6 +1509,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@admin/statuses/01J5QVB9VC76NPPRQ207GG4DRZ",
Content: `<p>Hi <span class="h-card"><a href="http://localhost:8080/@1happyturtle" class="u-url mention" rel="nofollow noreferrer noopener" target="_blank">@<span>1happyturtle</span></a></span>, can I reply?</p>`,
Text: "Hi @1happyturtle, can I reply?",
ContentType: gtsmodel.StatusContentTypeMarkdown,
CreatedAt: TimeMustParse("2024-02-20T12:41:37+02:00"),
EditedAt: time.Time{},
Local: util.Ptr(true),
@ -1531,6 +1535,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
Content: "hello everyone!",
Text: "hello everyone!",
ContentType: gtsmodel.StatusContentTypePlain,
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
EditedAt: time.Time{},
Local: util.Ptr(true),
@ -1552,8 +1557,9 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
ID: "01F8MHAYFKS4KMXF8K5Y1C0KRN",
URI: "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAYFKS4KMXF8K5Y1C0KRN",
URL: "http://localhost:8080/@the_mighty_zork/statuses/01F8MHAYFKS4KMXF8K5Y1C0KRN",
Content: "this is a Public local-only post that shouldn't federate, but it's still boostable, replyable, and likeable",
Text: "this is a Public local-only post that shouldn't federate, but it's still boostable, replyable, and likeable",
Content: "this is a Public local-only post that shouldn't federate, but it's still boostable, replyable, and likeable. also it has no stored content type",
Text: "this is a Public local-only post that shouldn't federate, but it's still boostable, replyable, and likeable. also it has no stored content type",
ContentType: 0,
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
EditedAt: time.Time{},
Local: util.Ptr(true),
@ -1577,6 +1583,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@the_mighty_zork/statuses/01F8MHBBN8120SYH7D5S050MGK",
Content: "this is a very personal post that I don't want anyone to interact with at all, and i only want mutuals to see it",
Text: "this is a very personal post that I don't want anyone to interact with at all, and i only want mutuals to see it",
ContentType: gtsmodel.StatusContentTypePlain,
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
EditedAt: time.Time{},
Local: util.Ptr(true),
@ -1611,6 +1618,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@the_mighty_zork/statuses/01F8MH82FYRXD2RC6108DAJ5HB",
Content: "here's a little gif of trent.... and also a cow",
Text: "here's a little gif of trent.... and also a cow",
ContentType: gtsmodel.StatusContentTypePlain,
AttachmentIDs: []string{"01F8MH7TDVANYKWVE8VVKFPJTJ", "01CDR64G398ADCHXK08WWTHEZ5"},
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
EditedAt: time.Time{},
@ -1635,6 +1643,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@the_mighty_zork/statuses/01FCTA44PW9H1TB328S9AQXKDS",
Content: "hi!",
Text: "hi!",
ContentType: gtsmodel.StatusContentTypePlain,
AttachmentIDs: []string{},
CreatedAt: TimeMustParse("2022-05-20T11:37:55Z"),
EditedAt: time.Time{},
@ -1659,6 +1668,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@the_mighty_zork/statuses/065TKBPE0H2AH8S5X8JCK4XC58",
Content: "what do you think of sloths?",
Text: "what do you think of sloths?",
ContentType: gtsmodel.StatusContentTypePlain,
AttachmentIDs: nil,
CreatedAt: TimeMustParse("2022-05-20T11:41:10Z"),
EditedAt: time.Time{},
@ -1684,6 +1694,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@the_mighty_zork/statuses/01HH9KYNQPA416TNJ53NSATP40",
Content: "<p>Here's a bunch of HTML, read it and weep, weep then!</p><pre><code class=\"language-html\">&lt;section class=&#34;about-user&#34;&gt;\n &lt;div class=&#34;col-header&#34;&gt;\n &lt;h2&gt;About&lt;/h2&gt;\n &lt;/div&gt; \n &lt;div class=&#34;fields&#34;&gt;\n &lt;h3 class=&#34;sr-only&#34;&gt;Fields&lt;/h3&gt;\n &lt;dl&gt;\n &lt;div class=&#34;field&#34;&gt;\n &lt;dt&gt;should you follow me?&lt;/dt&gt;\n &lt;dd&gt;maybe!&lt;/dd&gt;\n &lt;/div&gt;\n &lt;div class=&#34;field&#34;&gt;\n &lt;dt&gt;age&lt;/dt&gt;\n &lt;dd&gt;120&lt;/dd&gt;\n &lt;/div&gt;\n &lt;/dl&gt;\n &lt;/div&gt;\n &lt;div class=&#34;bio&#34;&gt;\n &lt;h3 class=&#34;sr-only&#34;&gt;Bio&lt;/h3&gt;\n &lt;p&gt;i post about things that concern me&lt;/p&gt;\n &lt;/div&gt;\n &lt;div class=&#34;sr-only&#34; role=&#34;group&#34;&gt;\n &lt;h3 class=&#34;sr-only&#34;&gt;Stats&lt;/h3&gt;\n &lt;span&gt;Joined in Jun, 2022.&lt;/span&gt;\n &lt;span&gt;8 posts.&lt;/span&gt;\n &lt;span&gt;Followed by 1.&lt;/span&gt;\n &lt;span&gt;Following 1.&lt;/span&gt;\n &lt;/div&gt;\n &lt;div class=&#34;accountstats&#34; aria-hidden=&#34;true&#34;&gt;\n &lt;b&gt;Joined&lt;/b&gt;&lt;time datetime=&#34;2022-06-04T13:12:00.000Z&#34;&gt;Jun, 2022&lt;/time&gt;\n &lt;b&gt;Posts&lt;/b&gt;&lt;span&gt;8&lt;/span&gt;\n &lt;b&gt;Followed by&lt;/b&gt;&lt;span&gt;1&lt;/span&gt;\n &lt;b&gt;Following&lt;/b&gt;&lt;span&gt;1&lt;/span&gt;\n &lt;/div&gt;\n&lt;/section&gt;\n</code></pre><p>There, hope you liked that!</p>",
Text: "Here's a bunch of HTML, read it and weep, weep then!\n\n```html\n<section class=\"about-user\">\n <div class=\"col-header\">\n <h2>About</h2>\n </div> \n <div class=\"fields\">\n <h3 class=\"sr-only\">Fields</h3>\n <dl>\n <div class=\"field\">\n <dt>should you follow me?</dt>\n <dd>maybe!</dd>\n </div>\n <div class=\"field\">\n <dt>age</dt>\n <dd>120</dd>\n </div>… <h3 class=\"sr-only\">Stats</h3>\n <span>Joined in Jun, 2022.</span>\n <span>8 posts.</span>\n <span>Followed by 1.</span>\n <span>Following 1.</span>\n </div>\n <div class=\"accountstats\" aria-hidden=\"true\">\n <b>Joined</b><time datetime=\"2022-06-04T13:12:00.000Z\">Jun, 2022</time>\n <b>Posts</b><span>8</span>\n <b>Followed by</b><span>1</span>\n <b>Following</b><span>1</span>\n </div>\n</section>\n```\n\nThere, hope you liked that!",
ContentType: gtsmodel.StatusContentTypeMarkdown,
CreatedAt: TimeMustParse("2023-12-10T11:24:00+02:00"),
EditedAt: time.Time{},
Local: util.Ptr(true),
@ -1707,6 +1718,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@the_mighty_zork/statuses/01J2M1HPFSS54S60Y0KYV23KJE",
Content: "<p>Thanks! Here's a NIN track</p>",
Text: "Thanks! Here's a NIN track",
ContentType: gtsmodel.StatusContentTypeMarkdown,
AttachmentIDs: []string{"01J2M20K6K9XQC4WSB961YJHV6"},
CreatedAt: TimeMustParse("2024-01-10T11:24:00+02:00"),
EditedAt: time.Time{},
@ -1732,6 +1744,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@the_mighty_zork/statuses/01JDPZC707CKDN8N4QVWM4Z1NR",
Content: "<p>this is the latest revision of the status, with a content-warning</p>",
Text: "this is the latest revision of the status, with a content-warning",
ContentType: gtsmodel.StatusContentTypeMarkdown,
ContentWarning: "edited status",
AttachmentIDs: nil,
CreatedAt: TimeMustParse("2024-11-01T11:00:00+02:00"),
@ -1758,6 +1771,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@1happyturtle/statuses/01F8MHBQCBTDKN6X5VHGMMN4MA",
Content: "🐢 hi everyone i post about turtles 🐢",
Text: "🐢 hi everyone i post about turtles 🐢",
ContentType: gtsmodel.StatusContentTypePlain,
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
EditedAt: time.Time{},
Local: util.Ptr(true),
@ -1781,6 +1795,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@1happyturtle/statuses/01F8MHC0H0A7XHTVH5F596ZKBM",
Content: "🐢 this one is federated, likeable, and boostable but not replyable 🐢",
Text: "🐢 this one is federated, likeable, and boostable but not replyable 🐢",
ContentType: gtsmodel.StatusContentTypePlain,
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
EditedAt: time.Time{},
Local: util.Ptr(true),
@ -1815,6 +1830,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@1happyturtle/statuses/01F8MHC8VWDRBQR0N1BATDDEM5",
Content: "🐢 i don't mind people sharing and liking this one but I want to moderate replies to it 🐢",
Text: "🐢 i don't mind people sharing and liking this one but I want to moderate replies to it 🐢",
ContentType: gtsmodel.StatusContentTypePlain,
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
EditedAt: time.Time{},
Local: util.Ptr(true),
@ -1850,6 +1866,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@1happyturtle/statuses/01F8MHCP5P2NWYQ416SBA0XSEV",
Content: "🐢 this is a public status but I want it local only and not boostable 🐢",
Text: "🐢 this is a public status but I want it local only and not boostable 🐢",
ContentType: gtsmodel.StatusContentTypePlain,
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
EditedAt: time.Time{},
Local: util.Ptr(true),
@ -1884,6 +1901,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@1happyturtle/statuses/01FCQSQ667XHJ9AV9T27SJJSX5",
Content: "🐢 @the_mighty_zork hi zork! 🐢",
Text: "🐢 @the_mighty_zork hi zork! 🐢",
ContentType: gtsmodel.StatusContentTypePlain,
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
EditedAt: time.Time{},
Local: util.Ptr(true),
@ -1910,6 +1928,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@1happyturtle/statuses/01FN3VJGFH10KR7S2PB0GFJZYG",
Content: "🐢 @the_mighty_zork hi zork, this is a direct message, shhhhhh! 🐢",
Text: "🐢 @the_mighty_zork hi zork, this is a direct message, shhhhhh! 🐢",
ContentType: gtsmodel.StatusContentTypePlain,
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
EditedAt: time.Time{},
Local: util.Ptr(true),
@ -1937,6 +1956,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@1happyturtle/statuses/01G20ZM733MGN8J344T4ZDDFY1",
Content: "🐢 hi followers! did u know i'm a turtle? 🐢",
Text: "🐢 hi followers! did u know i'm a turtle? 🐢",
ContentType: gtsmodel.StatusContentTypePlain,
AttachmentIDs: []string{},
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
EditedAt: time.Time{},
@ -1961,6 +1981,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@1happyturtle/statuses/01HEN2PRXT0TF4YDRA64FZZRN7",
Content: "hey everyone i got stuck in a shed. any ideas for how to get out?",
Text: "hey everyone i got stuck in a shed. any ideas for how to get out?",
ContentType: gtsmodel.StatusContentTypePlain,
AttachmentIDs: nil,
CreatedAt: TimeMustParse("2021-07-28T10:40:37+02:00"),
EditedAt: time.Time{},
@ -1986,6 +2007,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://localhost:8080/@1happyturtle/statuses/01JDPZEZ77X1NX0TY9M10BK1HM",
Content: "<p>now edited to bring back the previous edit's media!</p>",
Text: "now edited to bring back the previous edit's media!",
ContentType: gtsmodel.StatusContentTypeMarkdown,
ContentWarning: "edit with media attachments",
AttachmentIDs: []string{"01JDQ164HM08SGJ7ZEK9003Z4B"},
CreatedAt: TimeMustParse("2024-11-01T10:00:00+02:00"),
@ -2087,6 +2109,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
URL: "http://fossbros-anonymous.io/@foss_satan/statuses/______",
Content: "<p>this is the latest status edit without poll change</p>",
Text: "this is the latest status edit without poll change",
ContentType: gtsmodel.StatusContentTypeMarkdown,
ContentWarning: "",
AttachmentIDs: nil,
CreatedAt: TimeMustParse("2024-11-01T09:00:00+02:00"),
@ -3617,6 +3640,7 @@ func NewTestStatusEdits() map[string]*gtsmodel.StatusEdit {
Content: "<p>this is the original status</p>",
ContentWarning: "",
Text: "this is the original status",
ContentType: gtsmodel.StatusContentTypeMarkdown,
Language: "en",
Sensitive: util.Ptr(false),
AttachmentIDs: nil,
@ -3630,6 +3654,7 @@ func NewTestStatusEdits() map[string]*gtsmodel.StatusEdit {
Content: "<p>this is the first status edit! now with content-warning</p>",
ContentWarning: "edited status",
Text: "this is the first status edit! now with content-warning",
ContentType: gtsmodel.StatusContentTypeMarkdown,
Language: "en",
Sensitive: util.Ptr(false),
AttachmentIDs: nil,
@ -3643,6 +3668,7 @@ func NewTestStatusEdits() map[string]*gtsmodel.StatusEdit {
Content: "<p>this is the original status</p>",
ContentWarning: "",
Text: "this is the original status",
ContentType: gtsmodel.StatusContentTypeMarkdown,
Language: "en",
Sensitive: util.Ptr(false),
AttachmentIDs: nil,
@ -3656,6 +3682,7 @@ func NewTestStatusEdits() map[string]*gtsmodel.StatusEdit {
Content: "<p>now edited to have some media!</p>",
ContentWarning: "edit with media attachments",
Text: "now edited to have some media!",
ContentType: gtsmodel.StatusContentTypeMarkdown,
Language: "en",
Sensitive: util.Ptr(true),
AttachmentIDs: []string{"01JDQ164HM08SGJ7ZEK9003Z4B"},
@ -3669,6 +3696,7 @@ func NewTestStatusEdits() map[string]*gtsmodel.StatusEdit {
Content: "<p>now edited to remove the media</p>",
ContentWarning: "edit missing previous media attachments",
Text: "now edited to remove the media",
ContentType: gtsmodel.StatusContentTypeMarkdown,
Language: "en",
Sensitive: util.Ptr(false),
AttachmentIDs: nil,
@ -3682,6 +3710,7 @@ func NewTestStatusEdits() map[string]*gtsmodel.StatusEdit {
Content: "<p>this is the original status, with a poll!</p>",
ContentWarning: "",
Text: "this is the original status, with a poll!",
ContentType: gtsmodel.StatusContentTypeMarkdown,
Language: "en",
Sensitive: util.Ptr(false),
AttachmentIDs: nil,
@ -3695,6 +3724,7 @@ func NewTestStatusEdits() map[string]*gtsmodel.StatusEdit {
Content: "<p>this is the first status edit! now with a different poll!</p>",
ContentWarning: "edited status",
Text: "this is the first status edit! now with a different poll!",
ContentType: gtsmodel.StatusContentTypeMarkdown,
Language: "en",
Sensitive: util.Ptr(false),
AttachmentIDs: nil,