-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: 投稿の編集に対応 #14011
base: develop
Are you sure you want to change the base?
feat: 投稿の編集に対応 #14011
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #14011 +/- ##
============================================
+ Coverage 20.17% 42.08% +21.90%
============================================
Files 725 1560 +835
Lines 100502 199746 +99244
Branches 1045 3620 +2575
============================================
+ Hits 20279 84063 +63784
- Misses 79681 115090 +35409
- Partials 542 593 +51 ☔ View full report in Codecov by Sentry. |
このPRによるapi.jsonの差分 差分はこちら--- base
+++ head
@@ -56808,6 +56808,183 @@
}
}
},
+ "/notes/histories": {
+ "post": {
+ "operationId": "notes___histories",
+ "summary": "notes/histories",
+ "description": "No description provided.\n\n**Credential required**: *No*",
+ "externalDocs": {
+ "description": "Source code",
+ "url": "https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/notes/histories.ts"
+ },
+ "tags": [
+ "notes"
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "limit": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 100,
+ "default": 10
+ },
+ "noteId": {
+ "type": "string",
+ "format": "misskey:id"
+ },
+ "sinceId": {
+ "type": "string",
+ "format": "misskey:id"
+ },
+ "untilId": {
+ "type": "string",
+ "format": "misskey:id"
+ }
+ },
+ "required": [
+ "noteId"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "OK (with results)",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "$ref": "#/components/schemas/NoteHistory"
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Client error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "NO_SUCH_NOTE": {
+ "value": {
+ "error": {
+ "message": "No such note.",
+ "code": "NO_SUCH_NOTE",
+ "id": "24fcbfc6-2e37-42b6-8388-c29b3861a08d"
+ }
+ }
+ },
+ "INVALID_PARAM": {
+ "value": {
+ "error": {
+ "message": "Invalid param.",
+ "code": "INVALID_PARAM",
+ "id": "3d81ceae-475f-4600-b2a8-2bc116157532"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authentication error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "CREDENTIAL_REQUIRED": {
+ "value": {
+ "error": {
+ "message": "Credential required.",
+ "code": "CREDENTIAL_REQUIRED",
+ "id": "1384574d-a912-4b81-8601-c7b1c4085df1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "AUTHENTICATION_FAILED": {
+ "value": {
+ "error": {
+ "message": "Authentication failed. Please ensure your token is correct.",
+ "code": "AUTHENTICATION_FAILED",
+ "id": "b0a7f5f8-dc2f-4171-b91f-de88ad238e14"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "418": {
+ "description": "I'm Ai",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "I_AM_AI": {
+ "value": {
+ "error": {
+ "message": "You sent a request to Ai-chan, Misskey's showgirl, instead of the server.",
+ "code": "I_AM_AI",
+ "id": "60c46cd1-f23a-46b1-bebe-5d2b73951a84"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal server error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "INTERNAL_ERROR": {
+ "value": {
+ "error": {
+ "message": "Internal error occurred. Please contact us if the error persists.",
+ "code": "INTERNAL_ERROR",
+ "id": "5d37dbcb-891e-41ca-a3d6-e690c97775ac"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/notes/hybrid-timeline": {
"post": {
"operationId": "notes___hybrid-timeline",
@@ -60509,6 +60686,302 @@
}
}
},
+ "/notes/update": {
+ "post": {
+ "operationId": "notes___update",
+ "summary": "notes/update",
+ "description": "No description provided.\n\n**Credential required**: *Yes* / **Permission**: *write:notes*",
+ "externalDocs": {
+ "description": "Source code",
+ "url": "https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/notes/update.ts"
+ },
+ "tags": [
+ "notes"
+ ],
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "noteId": {
+ "type": "string",
+ "format": "misskey:id"
+ },
+ "text": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "minLength": 1,
+ "maxLength": 3000
+ },
+ "cw": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 100
+ },
+ "fileIds": {
+ "type": "array",
+ "uniqueItems": true,
+ "minItems": 1,
+ "maxItems": 16,
+ "items": {
+ "type": "string",
+ "format": "misskey:id"
+ }
+ },
+ "poll": {
+ "type": [
+ "object",
+ "null"
+ ],
+ "properties": {
+ "choices": {
+ "type": "array",
+ "uniqueItems": true,
+ "minItems": 2,
+ "maxItems": 10,
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 50
+ }
+ },
+ "multiple": {
+ "type": "boolean"
+ },
+ "expiresAt": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "expiredAfter": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "minimum": 1
+ }
+ },
+ "required": [
+ "choices"
+ ]
+ }
+ },
+ "if": {
+ "properties": {
+ "renoteId": {
+ "type": "null"
+ },
+ "fileIds": {
+ "type": "null"
+ },
+ "poll": {
+ "type": "null"
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "text": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 3000,
+ "pattern": "[^\\s]+"
+ }
+ },
+ "required": [
+ "text"
+ ]
+ },
+ "required": [
+ "noteId",
+ "text",
+ "cw"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "OK (without any results)"
+ },
+ "400": {
+ "description": "Client error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "NO_SUCH_NOTE": {
+ "value": {
+ "error": {
+ "message": "No such note.",
+ "code": "NO_SUCH_NOTE",
+ "id": "a6584e14-6e01-4ad3-b566-851e7bf0d474"
+ }
+ }
+ },
+ "ACCESS_DENIED": {
+ "value": {
+ "error": {
+ "message": "Access denied.",
+ "code": "ACCESS_DENIED",
+ "id": "fe8d7103-0ea8-4ec3-814d-f8b401dc69e9"
+ }
+ }
+ },
+ "CANNOT_EDIT_NOTE": {
+ "value": {
+ "error": {
+ "message": "Editing notes are not allowed by the role policy.",
+ "code": "CANNOT_EDIT_NOTE",
+ "id": "59ece09c-56ab-4bd5-905c-0f6bbf5af143"
+ }
+ }
+ },
+ "CONTAINS_PROHIBITED_WORDS": {
+ "value": {
+ "error": {
+ "message": "Cannot post because it contains prohibited words.",
+ "code": "CONTAINS_PROHIBITED_WORDS",
+ "id": "aa6e01d3-a85c-669d-758a-76aab43af334"
+ }
+ }
+ },
+ "INVALID_PARAM": {
+ "value": {
+ "error": {
+ "message": "Invalid param.",
+ "code": "INVALID_PARAM",
+ "id": "3d81ceae-475f-4600-b2a8-2bc116157532"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authentication error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "CREDENTIAL_REQUIRED": {
+ "value": {
+ "error": {
+ "message": "Credential required.",
+ "code": "CREDENTIAL_REQUIRED",
+ "id": "1384574d-a912-4b81-8601-c7b1c4085df1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "AUTHENTICATION_FAILED": {
+ "value": {
+ "error": {
+ "message": "Authentication failed. Please ensure your token is correct.",
+ "code": "AUTHENTICATION_FAILED",
+ "id": "b0a7f5f8-dc2f-4171-b91f-de88ad238e14"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "418": {
+ "description": "I'm Ai",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "I_AM_AI": {
+ "value": {
+ "error": {
+ "message": "You sent a request to Ai-chan, Misskey's showgirl, instead of the server.",
+ "code": "I_AM_AI",
+ "id": "60c46cd1-f23a-46b1-bebe-5d2b73951a84"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "429": {
+ "description": "To many requests",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "RATE_LIMIT_EXCEEDED": {
+ "value": {
+ "error": {
+ "message": "Rate limit exceeded. Please try again later.",
+ "code": "RATE_LIMIT_EXCEEDED",
+ "id": "d5826d14-3982-4d2e-8011-b9e9f02499ef"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal server error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "INTERNAL_ERROR": {
+ "value": {
+ "error": {
+ "message": "Internal error occurred. Please contact us if the error persists.",
+ "code": "INTERNAL_ERROR",
+ "id": "5d37dbcb-891e-41ca-a3d6-e690c97775ac"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/notes/user-list-timeline": {
"post": {
"operationId": "notes___user-list-timeline",
@@ -77706,6 +78179,13 @@
"type": "string",
"format": "date-time"
},
+ "updatedAt": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
"deletedAt": {
"type": [
"string",
@@ -77998,6 +78478,123 @@
"repliesCount"
]
},
+ "NoteHistory": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "id",
+ "example": "xxxxxxxxxx"
+ },
+ "targetId": {
+ "type": "string",
+ "format": "id",
+ "example": "xxxxxxxxxx"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "text": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cw": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "mentions": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "id"
+ }
+ },
+ "fileIds": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "id"
+ }
+ },
+ "files": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "$ref": "#/components/schemas/DriveFile"
+ }
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "poll": {
+ "type": [
+ "object",
+ "null"
+ ],
+ "properties": {
+ "expiresAt": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "multiple": {
+ "type": "boolean"
+ },
+ "choices": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "isVoted": {
+ "type": "boolean"
+ },
+ "text": {
+ "type": "string"
+ },
+ "votes": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "isVoted",
+ "text",
+ "votes"
+ ]
+ }
+ }
+ },
+ "required": [
+ "multiple",
+ "choices"
+ ]
+ },
+ "emojis": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "type": "string"
+ }
+ ]
+ }
+ }
+ },
+ "required": [
+ "id",
+ "targetId",
+ "createdAt"
+ ]
+ },
"NoteReaction": {
"type": "object",
"properties": {
@@ -80445,6 +81042,9 @@
"canPublicNote": {
"type": "boolean"
},
+ "canEditNote": {
+ "type": "boolean"
+ },
"mentionLimit": {
"type": "integer"
},
@@ -80519,6 +81119,7 @@
"gtlAvailable",
"ltlAvailable",
"canPublicNote",
+ "canEditNote",
"mentionLimit",
"canInvite",
"inviteLimit", |
(ある場合)検索インデックスの更新とかハッシュタグテーブルの更新とかって、これでできてるのかしら(以前はそのへんまで対応が回らなかったのでお流れになったため) |
やってるっぽかった🙏 |
実装できたとしても編集を実現するためだけにしてはコード量が多くなりすぎるから、テストをかなり充実させるとかでない限り採用は難しいかもしれない |
修正できそうな部分は修正しました🙏 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
とりあえず
|
||
if (data.poll && data.poll.expiresAt) { | ||
const delay = data.poll.expiresAt.getTime() - Date.now(); | ||
this.queueService.endedPollNotificationQueue.add(note.id, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
元のノートに期限付き投票が含まれている、かつその期限を迎えていない場合、キューがダブってしまうので元のキューを消す必要がありそうです
const showContent = ref(false); | ||
const menuButton = ref<HTMLButtonElement | null>(null); | ||
const props = defineProps<{ | ||
text: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
text
は nullable なので要修正
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ファイル・投票の編集も表示されてほしい気がします
@@ -68,6 +68,7 @@ export const moderationLogTypes = [ | |||
'promoteQueue', | |||
'deleteDriveFile', | |||
'deleteNote', | |||
'editNote', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ロケールの追加必要そう & modlog.ModLog.vue でいい感じに差分表示できると良さそうです
: null, | ||
name: data.name, | ||
text: data.text, | ||
hasPoll: data.poll != null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
現状 notes/update
では poll も編集可能になっていて、それに応じてノートの hasPoll
も更新されるようになっていますが、現状その投票の変更についてはデータベースに登録されていません。よって例えば、投票のないノートを編集して新たな投票を付けた場合、hasPoll
が true
になる一方でそのノートに紐づく投票が存在しないので後続の pack で失敗してしまいます。
とはいえ、そもそも投票の選択肢が編集できてしまうのはマズイ気がする(例えば2択の投票の選択肢を終了間際に反転させるなどができる)ので API で禁止すべきかと思います(これは issue で議論すべき)。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
そもそも投票の選択肢が編集できてしまうのはマズイ気がする(例えば2択の投票の選択肢を終了間際に反転させるなどができる)
Mastodon では投票の選択肢を編集するとそれまでに入った票はリセットされるようなので、その方向性の実装でこの懸念は解消できるかもしれません
投票の編集(投票を含む投稿の編集)はMastodonの場合だとNoteではなくQuestion型で飛んでくるので、別途対応する必要がありそうです |
Thank you for sending us a great Pull Request! 👍 example: pnpm run build-misskey-js-with-types |
これは別の PR でやるべき内容かなと個人的には思います(そもそもちょっと懸念がある) |
(とはいえ、センシティブフラグを反映させるためだけにノートに適当な編集を加えないといけないのが変な挙動なのは確か) |
やりたい:
|
What
ActivityPub経由の投稿の編集に対応しました。
Why
Mastodonなどからの投稿の編集が反映されないため
Resolve: #8364
Related: #11944
Additional info (optional)
Checklist