Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fb54348
Attachments & Quotes に対応
HokubuSubway Jul 1, 2026
a3f94bc
命名を具体的に変更
HokubuSubway Jul 1, 2026
a514661
[3] 命名の短縮及び冗長性の解消
HokubuSubway Jul 1, 2026
89d0a07
[4] Wire の問題の修正
HokubuSubway Jul 2, 2026
dde926a
[5] 未使用関数の参照を削除
HokubuSubway Jul 2, 2026
5512ece
[6] api の追記
HokubuSubway Jul 2, 2026
fb9e53a
[7] GetTimeline 関数の修正
HokubuSubway Jul 2, 2026
dd100e9
[8] Renamed MessageNew -> DetailedMessage
HokubuSubway Jul 2, 2026
33cad4a
[9] 添付ファイルと引用メッセージの切り替えに対応
HokubuSubway Jul 2, 2026
9016117
[10] QuotedMessage を定義し,使用
HokubuSubway Jul 2, 2026
6152994
[11] 検索結果に引用メッセージを表示するように変更
HokubuSubway Jul 2, 2026
4b0e3e3
[12] 適切に MarshalJSON できていなかった問題を修正
HokubuSubway Jul 2, 2026
4c713b4
[13] review による修正の一部の反映:36 箇所を変更しました
HokubuSubway Jul 3, 2026
b3fa894
Merge remote-tracking branch 'origin/master' into issue2974
HokubuSubway Jul 3, 2026
63ec275
[15] テスト (Claude が書いたものをベースにいくらか手を加えた) を書きました
HokubuSubway Jul 3, 2026
4e76e48
[14] テスト (Claude が書いたものをベースにいくらか手を加えた) を書きました
HokubuSubway Jul 3, 2026
14eaf76
[17] Embedded Message in QuotedMessage and changed nomenclature of se…
HokubuSubway Jul 3, 2026
a822a71
[18] swagger.yaml : 87 行の削除
HokubuSubway Jul 5, 2026
10f3cf1
[19] MessagesQuery の値が TimelineQuery にコピーされない問題を修正
HokubuSubway Jul 5, 2026
585fd36
Merge remote-tracking branch 'origin/master' into issue2974
HokubuSubway Jul 5, 2026
a3bf598
[21] https://github.com/traPtitech/traQ/pull/3097/changes#r3524361106…
HokubuSubway Jul 8, 2026
773b305
[22] https://github.com/traPtitech/traQ/pull/3097/changes#r3524364063…
HokubuSubway Jul 8, 2026
8490669
[23] 引用の順番を sort するように変更されました
HokubuSubway Jul 8, 2026
1f126ee
[24] https://github.com/traPtitech/traQ/pull/3097/changes#r3524363516…
HokubuSubway Jul 8, 2026
48ae919
[25]
HokubuSubway Jul 8, 2026
148da8f
[26] Lint が通らない問題を修正
HokubuSubway Jul 8, 2026
a9d9395
[27] api-v3.yaml: 重複を allOf で統合しましたョ
HokubuSubway Jul 8, 2026
6c8e94b
[28] ファイルアクセス権限の確認
HokubuSubway Jul 8, 2026
580661e
[29] テストに修正を反映
HokubuSubway Jul 8, 2026
a20efd1
[30] さらなる問題を修正
HokubuSubway Jul 8, 2026
49db0cf
[31] さらなる問題を修正 2
HokubuSubway Jul 8, 2026
a0a81d9
[32] 未使用の構造体を コメントアウト
HokubuSubway Jul 8, 2026
1cdf80f
[33] includeAttachments が quotes に反映されない問題を修正
HokubuSubway Jul 8, 2026
1bf1052
[34] test に IsFileAccessible を追加
HokubuSubway Jul 15, 2026
3121ad8
[35] 引用メッセージが public channel にない場合に を返すように変更
HokubuSubway Jul 15, 2026
888c708
[36] MockFileRepository に変更 (mockgen)
HokubuSubway Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/v3-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ paths:
- $ref: "#/components/parameters/untilInQuery"
- $ref: "#/components/parameters/inclusiveInQuery"
- $ref: "#/components/parameters/orderInQuery"
- $ref: "#/components/parameters/includeAttachments"
- $ref: "#/components/parameters/includeQuotes"
responses:
"200":
description: OK
Expand Down Expand Up @@ -8241,6 +8243,21 @@ components:
type: boolean
required: false
description: 削除されたメッセージを除外するかどうか(デフォルト false)
includeAttachments:
in: query
name: include-attachments
schema:
type: boolean
required: false
description: 添付ファイルを含めるかどうか (デフォルト false)
includeQuotes:
in: query
name: include-quotes
schema:
type: boolean
required: false
description: 引用メッセージを含めるかどうか (デフォルト false)


tags:
- name: user
Expand Down
18 changes: 18 additions & 0 deletions model/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,24 @@ type Message struct {
Pin *Pin `gorm:"constraint:pins_message_id_messages_id_foreign,OnUpdate:CASCADE,OnDelete:CASCADE"`
}

type MessageNew struct {
ID uuid.UUID `gorm:"type:char(36);not null;primaryKey"`
UserID uuid.UUID `gorm:"type:char(36);not null;"`
ChannelID uuid.UUID `gorm:"type:char(36);not null;index:idx_messages_channel_id_deleted_at_created_at,priority:1"`
Text string `gorm:"type:TEXT COLLATE utf8mb4_bin NOT NULL"`
CreatedAt time.Time `gorm:"precision:6;index;index:idx_messages_channel_id_deleted_at_created_at,priority:3;index:idx_messages_deleted_at_created_at,priority:2"`
UpdatedAt time.Time `gorm:"precision:6;index:idx_messages_deleted_at_updated_at,priority:2"`
DeletedAt gorm.DeletedAt `gorm:"precision:6;index:idx_messages_channel_id_deleted_at_created_at,priority:2;index:idx_messages_deleted_at_created_at,priority:1;index:idx_messages_deleted_at_updated_at,priority:1"`

User *User `gorm:"constraint:messages_user_id_users_id_foreign,OnUpdate:CASCADE,OnDelete:CASCADE"`
Channel *Channel `gorm:"constraint:messages_channel_id_channels_id_foreign,OnUpdate:CASCADE,OnDelete:CASCADE"`
Stamps []MessageStamp `gorm:"constraint:messages_stamps_message_id_messages_id_foreign,OnUpdate:CASCADE,OnDelete:CASCADE;foreignkey:MessageID"`
Pin *Pin `gorm:"constraint:pins_message_id_messages_id_foreign,OnUpdate:CASCADE,OnDelete:CASCADE"`

Attachments []*FileMeta
Quotes []*Message
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Comment thread
cp-20 marked this conversation as resolved.

// TableName DBの名前を指定するメソッド
func (m Message) TableName() string {
return "messages"
Expand Down
3 changes: 3 additions & 0 deletions repository/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ type MessagesQuery struct {
Asc bool
ExcludeDMs bool
DisablePreload bool
IncludeAttachments bool
IncludeQuotes bool
IncludeOGPs bool
}

// ChannelLatestMessagesQuery GetChannelLatestMessages用クエリ
Expand Down
2 changes: 1 addition & 1 deletion service/message/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type Manager interface {
//
// 成功した場合、メッセージとnilを返します。
// DBによるエラーを返すことがあります。
GetIn(ctx context.Context, ids []uuid.UUID) ([]Message, error)
GetIn(ctx context.Context, ids []uuid.UUID, ia bool, iq bool) ([]MessageNew, error)
// GetTimeline タイムラインを取得します
//
// 成功した場合、タイムラインとnilを返します。
Expand Down
39 changes: 35 additions & 4 deletions service/message/manager_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/traPtitech/traQ/repository"
"github.com/traPtitech/traQ/service/channel"
"github.com/traPtitech/traQ/utils"
messageParse "github.com/traPtitech/traQ/utils/message"
Comment thread
cp-20 marked this conversation as resolved.
Outdated
"github.com/traPtitech/traQ/utils/optional"
)

Expand Down Expand Up @@ -64,13 +65,43 @@ func (m *manager) get(ctx context.Context, id uuid.UUID) (*message, error) {
return m.cache.Get(ctx, id)
}

func (m *manager) GetIn(ctx context.Context, ids []uuid.UUID) ([]Message, error) {
messages, _, err := m.R.GetMessages(ctx, repository.MessagesQuery{IDIn: optional.From(ids)})
func (m *manager) GetIn(ctx context.Context, ids []uuid.UUID, ia bool, iq bool) ([]MessageNew, error) {
messages, _, err := m.R.GetMessages(ctx, repository.MessagesQuery{IDIn: optional.From(ids), IncludeAttachments: ia, IncludeQuotes: iq})
if err != nil {
return nil, err
}
ret := utils.Map(messages, func(m *model.Message) Message {
return &message{Model: m}
ret := utils.Map(messages, func(mm *model.Message) MessageNew {
parseResult := messageParse.Parse(mm.Text)
pRa := parseResult.Attachments
aR := []*model.FileMeta{}
for i := 0; i < len(pRa); i++ {
attachment, err := m.R.GetFileMeta(ctx, pRa[i])
if err != nil {
break
}
aR = append(aR, attachment)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Attachment fetch errors are silently swallowed.

On the first GetFileMeta error, the loop breaks and any remaining attachments in pRa are silently dropped — no logging, no error surfaced to caller. Consider logging the error and using continue instead of break so a single missing file doesn't drop all subsequent valid attachments.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@service/message/manager_impl.go` around lines 77 - 83, The attachment-loading
loop in manager_impl.go currently stops on the first GetFileMeta error and
silently drops the rest of pRa. Update the loop in the attachment fetch logic to
log the GetFileMeta failure with context, then continue iterating so later valid
attachments are still appended to aR instead of being skipped.

pRc := parseResult.Citation
quotes, _, err := m.R.GetMessages(ctx, repository.MessagesQuery{IDIn: optional.From((pRc))})
if err != nil {
return nil
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
mn := &model.MessageNew{
ID: mm.ID,
UserID: mm.UserID,
ChannelID: mm.ChannelID,
Text: mm.Text,
CreatedAt: mm.CreatedAt,
UpdatedAt: mm.UpdatedAt,
DeletedAt: mm.DeletedAt,
User: mm.User,
Channel: mm.Channel,
Stamps: mm.Stamps,
Pin: mm.Pin,
Attachments: aR,
Quotes: quotes,
}
return &messageNew{Model: mn}
})
return ret, nil
}
Expand Down
15 changes: 15 additions & 0 deletions service/message/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,18 @@

json.Marshaler
}

type MessageNew interface {

Check failure on line 25 in service/message/model.go

View workflow job for this annotation

GitHub Actions / Lint

exported: type name will be used as message.MessageNew by other packages, and that stutters; consider calling this New (revive)
GetID() uuid.UUID
GetUserID() uuid.UUID
GetChannelID() uuid.UUID
GetText() string
GetCreatedAt() time.Time
GetUpdatedAt() time.Time
GetStamps() []model.MessageStamp
GetPin() *model.Pin
GetAttachments() []*model.FileMeta
GetQuotes() []*model.Message

json.Marshaler
}
110 changes: 110 additions & 0 deletions service/message/model_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,42 +21,87 @@ type message struct {
sync.RWMutex
}

type messageNew struct {
Model *model.MessageNew

stampMap map[uuid.UUID]map[uuid.UUID]model.MessageStamp
stampMapDirty bool

sync.RWMutex
}

func (m *message) GetID() uuid.UUID {
m.RLock()
defer m.RUnlock()
return m.Model.ID
}

func (m *messageNew) GetID() uuid.UUID {
m.RLock()
defer m.RUnlock()
return m.Model.ID
}

func (m *message) GetUserID() uuid.UUID {
m.RLock()
defer m.RUnlock()
return m.Model.UserID
}

func (m *messageNew) GetUserID() uuid.UUID {
m.RLock()
defer m.RUnlock()
return m.Model.UserID
}

func (m *message) GetChannelID() uuid.UUID {
m.RLock()
defer m.RUnlock()
return m.Model.ChannelID
}

func (m *messageNew) GetChannelID() uuid.UUID {
m.RLock()
defer m.RUnlock()
return m.Model.ChannelID
}

func (m *message) GetText() string {
m.RLock()
defer m.RUnlock()
return m.Model.Text
}

func (m *messageNew) GetText() string {
m.RLock()
defer m.RUnlock()
return m.Model.Text
}

func (m *message) GetCreatedAt() time.Time {
m.RLock()
defer m.RUnlock()
return m.Model.CreatedAt
}

func (m *messageNew) GetCreatedAt() time.Time {
m.RLock()
defer m.RUnlock()
return m.Model.CreatedAt
}

func (m *message) GetUpdatedAt() time.Time {
m.RLock()
defer m.RUnlock()
return m.Model.UpdatedAt
}

func (m *messageNew) GetUpdatedAt() time.Time {
m.RLock()
defer m.RUnlock()
return m.Model.UpdatedAt
}

func (m *message) GetStamps() []model.MessageStamp {
m.Lock()
defer m.Unlock()
Expand All @@ -76,6 +121,37 @@ func (m *message) GetStamps() []model.MessageStamp {
return result
}

func (m *messageNew) GetStamps() []model.MessageStamp {
m.Lock()
defer m.Unlock()
if !m.stampMapDirty {
return m.Model.Stamps
}

result := make([]model.MessageStamp, 0)
for _, us := range m.stampMap {
for _, ms := range us {
result = append(result, ms)
}
}

m.Model.Stamps = result
m.stampMapDirty = false
return result
}

func (m *messageNew) GetAttachments() []*model.FileMeta {
m.RLock()
defer m.RUnlock()
return m.Model.Attachments
}

func (m *messageNew) GetQuotes() []*model.Message {
m.RLock()
defer m.RUnlock()
return m.Model.Quotes
}

func (m *message) initStampMap() {
m.stampMap = map[uuid.UUID]map[uuid.UUID]model.MessageStamp{}
for _, ms := range m.Model.Stamps {
Expand Down Expand Up @@ -124,6 +200,12 @@ func (m *message) GetPin() *model.Pin {
return m.Model.Pin
}

func (m *messageNew) GetPin() *model.Pin {
m.RLock()
defer m.RUnlock()
return m.Model.Pin
}

func (m *message) MarshalJSON() ([]byte, error) {
type obj struct {
ID uuid.UUID `json:"id"`
Expand Down Expand Up @@ -151,3 +233,31 @@ func (m *message) MarshalJSON() ([]byte, error) {
m.RUnlock()
return jsonIter.ConfigFastest.Marshal(v)
}

func (m *messageNew) MarshalJSON() ([]byte, error) {
type obj struct {
ID uuid.UUID `json:"id"`
UserID uuid.UUID `json:"userId"`
ChannelID uuid.UUID `json:"channelId"`
Content string `json:"content"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Pinned bool `json:"pinned"`
Stamps []model.MessageStamp `json:"stamps"`
ThreadID optional.Of[uuid.UUID] `json:"threadId"` // TODO
}
stamps := m.GetStamps()
m.RLock()
v := &obj{
ID: m.Model.ID,
UserID: m.Model.UserID,
ChannelID: m.Model.ChannelID,
Content: m.Model.Text,
CreatedAt: m.Model.CreatedAt,
UpdatedAt: m.Model.UpdatedAt,
Pinned: m.Model.Pin != nil,
Stamps: stamps,
}
m.RUnlock()
return jsonIter.ConfigFastest.Marshal(v)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
4 changes: 2 additions & 2 deletions service/search/es_result.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ func (e *esEngine) parseResultFromResponse(searchRes esSearchResponse) (Result,
return uuid.Must(uuid.FromString(hit.ID))
})

messages, err := e.mm.GetIn(context.Background(), messageIDs)
messages, err := e.mm.GetIn(context.Background(), messageIDs, true, true)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
if err != nil {
return nil, err
}

messagesMap := lo.SliceToMap(messages, func(m message.Message) (uuid.UUID, message.Message) {
messagesMap := lo.SliceToMap(messages, func(m message.MessageNew) (uuid.UUID, message.Message) {
return m.GetID(), m
})
// sort result
Expand Down
Loading