Skip to content
27 changes: 20 additions & 7 deletions docs/v3-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -714,10 +714,23 @@ paths:
Not Found
メッセージ、またはスタンプが見つかりません。
operationId: removeMessageStamp
parameters:
- schema:
type: boolean
default: true
in: query
name: include-me
description: "自分が押したスタンプを削除します"
- schema:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: パラメーターが2つある理由はある? (include-me, include-other 片方だけじゃだめ?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

userIds?: uuid

type: boolean
default: false
in: query
name: include-other

@Takeno-hito Takeno-hito Jul 1, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo: スキーマの部分事前に乗れなくてごめんだったけど、利用用途を考えるならどちらかというと "user_id" を指定して消せるようにする、の方が綺麗じゃないかなぁ…? ( include-me, include-other はちょっとむずかしそうな使い方に見える)

MUST: これ見てるだけだと Bot 以外も使えそうだけど Bot 以外も使えるようにするのは微妙な気もするから、Bot 限定ってしたいかも!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

命名でどうにかするとしたら "remove-mine" "remove-others" とかの方がいいかも (include って入ってるのが変かもと思いました)

description: "自分以外が押したスタンプを削除します"
tags:
- message
- stamp
description: 指定したメッセージから指定した自身が押したスタンプを削除します
description: 指定したメッセージから指定したスタンプを削除します
"/stamps/{stampId}":
parameters:
- $ref: "#/components/parameters/stampIdInPath"
Expand Down Expand Up @@ -4607,7 +4620,7 @@ paths:
post:
summary: LiveKit Webhook受信
description: >
LiveKit側で設定したWebhookから呼び出されるエンドポイントです。
LiveKit側で設定したWebhookから呼び出されるエンドポイントです。
参加者の入室・退出などのイベントを受け取り、サーバ内で処理を行います。
operationId: liveKitWebhook
tags:
Expand All @@ -4630,7 +4643,7 @@ paths:
get:
summary: サウンドボード用の音声一覧を取得
description: >
DBに保存されたサウンドボード情報を取得します。
DBに保存されたサウンドボード情報を取得します。
各アイテムには soundId, soundName, stampId が含まれます。
operationId: getSoundboardList
tags:
Expand All @@ -4648,8 +4661,8 @@ paths:
post:
summary: サウンドボード用の短い音声ファイルをアップロード
description: >
15秒程度の短い音声ファイルを multipart/form-data で送信し、S3(互換ストレージ)にアップロードします。
クライアントは「soundName」というフィールドを送信し、それをDBに保存して関連付けを行います。
15秒程度の短い音声ファイルを multipart/form-data で送信し、S3(互換ストレージ)にアップロードします。
クライアントは「soundName」というフィールドを送信し、それをDBに保存して関連付けを行います。
また、サーバ側で soundId を自動生成し、S3のファイル名に使用します。
operationId: postSoundboard
tags:
Expand All @@ -4676,8 +4689,8 @@ paths:
post:
summary: アップロード済み音声を LiveKit ルームで再生
description: >
S3上にある音声ファイルの署名付きURLを生成し、
Ingressを介して指定ルームに音声を流します。
S3上にある音声ファイルの署名付きURLを生成し、
Ingressを介して指定ルームに音声を流します。
該当ルームに参加しているユーザであれば再生可能とします。
operationId: postSoundboardPlay
tags:
Expand Down
40 changes: 40 additions & 0 deletions repository/gorm/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,46 @@ func (repo *Repository) RemoveStampFromMessage(ctx context.Context, messageID, s
return nil
}

// RemoveOtherStampFromMessage implements MessageRepository interface.
func (repo *Repository) RemoveOtherStampFromMessage(ctx context.Context, messageID, stampID, userID uuid.UUID) (err error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MUST* 命名が微妙かも せめて RemoveOtherUsersStamp と他の "USER" であることがわかるようにしてほしい!
もしくは、 API の形式を自分の提案通りにしたら、こういう例外処理を作る必要がそもそもなくなるはず!

fmt.Println()
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

if messageID == uuid.Nil || stampID == uuid.Nil || userID == uuid.Nil {
return repository.ErrNilID
}

var ms []model.MessageStamp
if err := repo.db.WithContext(ctx).Find(&ms, &model.MessageStamp{MessageID: messageID, StampID: stampID}).Error; err != nil {
return err
}

repo.logger.Info(userID.String())

result := repo.db.WithContext(ctx).
Where("user_id <> ? AND message_id = ? AND stamp_id = ?", userID, messageID, stampID).
Delete(&model.MessageStamp{})
if result.Error != nil {
return result.Error
}

if result.RowsAffected > 0 {
for _, stamp := range ms {
if stamp.UserID == userID {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: この条件要る?要らない気がする!

continue
}
repo.hub.Publish(hub.Message{
Name: event.MessageUnstamped,
Fields: hub.Fields{
"message_id": stamp.MessageID,
"stamp_id": stamp.StampID,
"user_id": stamp.UserID,
},
})
}
}
return nil
}

func messagePreloads(db *gorm.DB) *gorm.DB {
return db.
Preload("Stamps").
Expand Down
6 changes: 6 additions & 0 deletions repository/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ type MessageRepository interface {
// 引数にuuid.Nilを指定するとErrNilIDを返します。
// DBによるエラーを返すことがあります。
RemoveStampFromMessage(ctx context.Context, messageID, stampID, userID uuid.UUID) (err error)
// RemoveStampFromMessage 指定したメッセージから指定したユーザー以外の指定したスタンプを全て削除します
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
//
// 成功した、或いは既に削除されていた場合、nilを返します。
// 引数にuuid.Nilを指定するとErrNilIDを返します。
// DBによるエラーを返すことがあります。
RemoveOtherStampFromMessage(ctx context.Context, messageID, stampID, userID uuid.UUID) (err error)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// UserUnreadChannel ユーザーの未読チャンネル構造体
Expand Down
14 changes: 14 additions & 0 deletions repository/mock_repository/mock_message.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 20 additions & 1 deletion router/v3/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ import (
"github.com/traPtitech/traQ/service/search"
)

type DeleteStampsQuery struct {
IncludeMe string `query:"include-me"`
IncludeOther string `query:"include-other"`
}

// GetMyUnreadChannels GET /users/me/unread
func (h *Handlers) GetMyUnreadChannels(c *echo.Context) error {
userID := getRequestUserID(c)
Expand Down Expand Up @@ -285,16 +290,30 @@ func (h *Handlers) AddMessageStamp(c *echo.Context) error {

// RemoveMessageStamp DELETE /messages/:messageID/stamps/:stampID
func (h *Handlers) RemoveMessageStamp(c *echo.Context) error {
var q DeleteStampsQuery
if err := bindAndValidate(c, &q); err != nil {
return herror.BadRequest(err)
}

if len(q.IncludeMe) == 0 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: bindAndValidate でこのバインド処理ってできない?(デフォ値って設定できない?)ちょっと実装が不思議かも…?

せめて、 `q.IncludeMe == "" かな len==0 の書き方はちょっと見づらい

q.IncludeMe = "1"
}
if len(q.IncludeOther) == 0 {
q.IncludeOther = "0"
}

ctx := c.Request().Context()
userID := getRequestUserID(c)
messageID := getParamAsUUID(c, consts.ParamMessageID)
stampID := getParamAsUUID(c, consts.ParamStampID)

// スタンプをメッセージから削除
if err := h.MessageManager.RemoveStamps(ctx, messageID, stampID, userID); err != nil {
if err := h.MessageManager.RemoveStamps(ctx, messageID, stampID, userID, isTrue(q.IncludeMe), isTrue(q.IncludeOther)); err != nil {
switch err {
case message.ErrChannelArchived:
return herror.BadRequest("the channel of this message has been archived")
case message.ErrCannotRemoveStamp:
return herror.Forbidden("you are not allowed to remove this stamp")
default:
return herror.InternalServerError(err)
}
Expand Down
17 changes: 11 additions & 6 deletions service/message/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import (
)

var (
ErrNotFound = errors.New("not found")
ErrAlreadyExists = errors.New("already exists")
ErrChannelArchived = errors.New("channel archived")
ErrPinLimitExceeded = errors.New("the pin limit exceeded")
ErrNotFound = errors.New("not found")
ErrAlreadyExists = errors.New("already exists")
ErrChannelArchived = errors.New("channel archived")
ErrPinLimitExceeded = errors.New("the pin limit exceeded")
ErrCannotRemoveStamp = errors.New("cannot remove stamps")
)

type TimelineQuery struct {
Expand Down Expand Up @@ -101,13 +102,17 @@ type Manager interface {
// 存在しないメッセージを指定した場合は、ErrNotFoundを返します。
// DBによるエラーを返すことがあります。
AddStamps(ctx context.Context, id, stampID, userID uuid.UUID, n int) (*model.MessageStamp, error)
// RemoveStamps 指定したメッセージから指定したユーザーの指定したスタンプを全て削除します
// RemoveStamps 指定したメッセージから指定したスタンプを削除します
// includeMeをtrueに指定すると自分のスタンプを全て削除します。
// includeOtherをtrueに指定すると自分以外のスタンプを全て削除します。
// 自分以外のスタンプを削除できるのは、そのメッセージの投稿者であるBotのみです。
//
// 成功した場合、或いは既に削除されていた場合、nilを返します。
// アーカイブされているチャンネルを指定すると、ErrChannelArchivedを返します。
// 存在しないメッセージを指定した場合は、ErrNotFoundを返します。
// スタンプを削除する権限がない場合は、ErrCannotRemoveStampを返します。
// DBによるエラーを返すことがあります。
RemoveStamps(ctx context.Context, id, stampID, userID uuid.UUID) error
RemoveStamps(ctx context.Context, id, stampID, userID uuid.UUID, includeMe bool, includeOther bool) error

Wait(ctx context.Context) error
}
24 changes: 21 additions & 3 deletions service/message/manager_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ func (m *manager) AddStamps(ctx context.Context, id, stampID, userID uuid.UUID,
return ms, nil
}

func (m *manager) RemoveStamps(ctx context.Context, id, stampID, userID uuid.UUID) error {
func (m *manager) RemoveStamps(ctx context.Context, id, stampID, userID uuid.UUID, includeMe bool, includeOther bool) error {
// メッセージ取得
msg, err := m.get(ctx, id)
if err != nil {
Expand All @@ -305,9 +305,27 @@ func (m *manager) RemoveStamps(ctx context.Context, id, stampID, userID uuid.UUI
return ErrChannelArchived
}

// 自分以外のスタンプを削除できるのは、Botかつ自分のメッセージのみ
if includeOther {
user, err := m.R.GetUser(ctx, userID, false)
if err != nil {
return fmt.Errorf("failed to GetUser: %w", err)
}
if !user.IsBot() || msg.GetUserID() != userID {
return ErrCannotRemoveStamp
}
}

// スタンプを消す
if err := m.R.RemoveStampFromMessage(ctx, id, stampID, userID); err != nil {
return fmt.Errorf("failed to RemoveStampFromMessage: %w", err)
if includeMe {
if err := m.R.RemoveStampFromMessage(ctx, id, stampID, userID); err != nil {
return fmt.Errorf("failed to RemoveStampFromMessage: %w", err)
}
}
if includeOther {
if err := m.R.RemoveOtherStampFromMessage(ctx, id, stampID, userID); err != nil {
return fmt.Errorf("failed to RemoveStampFromMessage: %w", err)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment on lines +320 to +328

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

memo: transaction って traQ ないんだっけ

}

// キャッシュ削除
Expand Down
Loading