Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
10c2049
errors, user
Nzt3-gh Jan 13, 2026
c237f0b
room
Nzt3-gh Feb 10, 2026
01e21dc
room
Nzt3-gh Feb 10, 2026
7502bb2
room サービスロジックのserviceへの移動
Nzt3-gh Feb 10, 2026
21f6fd7
save RoomAdmins
Nzt3-gh Feb 10, 2026
b44693c
group
Nzt3-gh Feb 10, 2026
c9f3b04
userの関連フィールドを保存
Nzt3-gh Feb 15, 2026
2990035
token
Nzt3-gh Feb 15, 2026
3e8582a
room 整合性確認の関数化
Nzt3-gh Feb 24, 2026
e7e0267
room 修正
Nzt3-gh Feb 24, 2026
1fe4e8b
tag
Nzt3-gh Feb 24, 2026
30e0e03
event作成時のroom作成のミスを修正
Nzt3-gh Mar 3, 2026
6f7de9b
Room自動生成がEventと紐づいていなかった問題を解消
Nzt3-gh Mar 3, 2026
c9834b8
エラーハンドリング
Nzt3-gh Mar 10, 2026
ec9dcf1
フォーマット修正
Nzt3-gh Mar 10, 2026
904fe1f
エラーチェック
Nzt3-gh Mar 10, 2026
3cea8dc
要素保存時のエラーチェック
Nzt3-gh Mar 17, 2026
6f7bcc7
タグname のunique制約の達成
Nzt3-gh Mar 17, 2026
e621dc1
空token処理の修正
Nzt3-gh Mar 17, 2026
6c66f9f
イベント更新時の部屋ID利用の修正
Nzt3-gh Mar 17, 2026
7898f41
権限取得の修正
Nzt3-gh Mar 17, 2026
e84a208
EventTagを保存できるように
Nzt3-gh Mar 17, 2026
fb911d6
エラーチェック
Nzt3-gh Mar 17, 2026
db6e5cc
外部key制約
Nzt3-gh Mar 24, 2026
70f6c91
グループのadmin条件の確認タイミングの変更
Nzt3-gh Mar 24, 2026
b1982f1
テストの修正
Nzt3-gh Mar 31, 2026
0eb3a3c
provider更新条件の修正
Nzt3-gh Mar 31, 2026
c770992
lint 修正
Nzt3-gh Mar 31, 2026
74c5f96
typo
Nzt3-gh Mar 31, 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
1 change: 1 addition & 0 deletions infra/db/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func updateEvent(db *gorm.DB, eventID uuid.UUID, args domain.UpsertEventArgs) (*
event := ConvWriteEventParamsToEvent(args)
event.ID = eventID

// Event Save の明示的な呼び出し
err := db.Session(&gorm.Session{FullSaveAssociations: true}).
Omit("CreatedAt").Save(&event).Error
return &event, err
Expand Down
2 changes: 2 additions & 0 deletions infra/db/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ func createGroup(db *gorm.DB, args domain.UpsertGroupArgs) (*Group, error) {
func updateGroup(db *gorm.DB, groupID uuid.UUID, args domain.UpsertGroupArgs) (*Group, error) {
group := ConvWriteGroupParamsToGroup(args)
group.ID = groupID
// FullSaveAssociations :true だと何が更新されるか不明瞭でよくない
// 特に、 hooks のせいで影響範囲が想定以上に大きくなりうる
err := db.Session(&gorm.Session{FullSaveAssociations: true}).
Omit("CreatedAt").Save(&group).Error
return &group, err
Comment on lines +145 to 181
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

UpdateGroup を transaction で包まないと、中途半端な状態が永続化されます。

ここは GroupMember 削除 → GroupAdmin 削除 → groups 更新 → 関連再作成が別々の書き込みです。service/group_impl.go:28-42 側でも transaction を張っていないので、途中で 1 本でも失敗するとメンバー/管理者だけ消えたグループが残ります。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infra/db/group.go` around lines 124 - 145, The UpdateGroup flow performs
multiple separate DB writes (deleting GroupMember and GroupAdmin, saving groups
via db.Omit("CreatedAt").Save(&group), then recreating members/admins) without a
transaction, so partial failures can leave inconsistent state; wrap the entire
sequence in a single transaction (use db.Transaction or begin/commit/rollback)
and replace all db.* calls with the transaction handle
(tx.Where(...).Delete(&GroupMember{}), tx.Where(...).Delete(&GroupAdmin{}),
tx.Omit("CreatedAt").Save(&group), and tx.Save(...) inside the loops for
group.Members and group.Admins), ensure you return any error to trigger rollback
and only commit when all operations succeed.

Expand Down
77 changes: 55 additions & 22 deletions infra/db/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ import (
"gorm.io/gorm"
)

/*
やりたいこと

Domain/ Service で行うべき責務を分離する
dbでDomainを取り出さないでよくする

データの整合性判定を Service に切り出す
関連情報の更新をそれぞれの update/create/delete/upsert で明示的に行う
AfterSave

*/

// BeforeSave is hook
func (e *Event) BeforeSave(_ *gorm.DB) (err error) {
if e.ID == uuid.Nil {
Expand All @@ -16,6 +28,7 @@ func (e *Event) BeforeSave(_ *gorm.DB) (err error) {
}
}

// event の save のときに Room を設定している
if e.RoomID == uuid.Nil {
if e.Room.Place != "" {
e.Room.Verified = false
Expand All @@ -29,6 +42,7 @@ func (e *Event) BeforeSave(_ *gorm.DB) (err error) {
}

// 時間整合性
// これは Service でやるべきでは?
Devent := ConvEventTodomainEvent(*e)
if !Devent.TimeConsistency() {
return NewValueError(ErrTimeConsistency, "timeStart", "timeEnd")
Expand All @@ -39,6 +53,8 @@ func (e *Event) BeforeSave(_ *gorm.DB) (err error) {
// BeforeCreate is hook
func (e *Event) BeforeCreate(tx *gorm.DB) (err error) {
// 時間整合性
// だから、これは Service でやるべきでは?
// イベントの情報が満たすべきことだからワンチャン Domain もある
r, err := getRoom(tx.Preload("Events"), e.RoomID)
if errors.Is(err, gorm.ErrRecordNotFound) {
// 該当する部屋がない場合、部屋時間整合性は調べる必要がない
Expand All @@ -49,7 +65,10 @@ func (e *Event) BeforeCreate(tx *gorm.DB) (err error) {
return err
}
e.Room = *r
// dbまで下ったのに Domain を取り出すのはあまり良くなさそう
Devent := ConvEventTodomainEvent(*e)
// これ domain の整合性判定関数なのになんでDBで確認してるんだ?
// 少なくとも service でやるべきでは?
if !Devent.RoomTimeConsistency() {
return NewValueError(ErrTimeConsistency, "timeStart", "timeEnd", "room")
}
Expand Down Expand Up @@ -85,12 +104,16 @@ func (e *Event) BeforeUpdate(tx *gorm.DB) (err error) {
return nil
}

// dbが正しく動作しているかの確認
// これはdbの責務なのでここ or db/event で行う
// preloadも
func (e *Event) AfterSave(tx *gorm.DB) (err error) {
event, err := getEvent(tx.Preload("Admins"), e.ID)
if err != nil {
return err
}
Devent := ConvEventTodomainEvent(*event)
// admin validation が domain のものなのは正しいのか?
if !Devent.AdminsValidation() {
return NewValueError(ErrNoAdmins, "admins")
}
Expand All @@ -102,6 +125,9 @@ func (e *Event) AfterSave(tx *gorm.DB) (err error) {
return nil
}

// 削除するイベントの関連情報も削除する (全て 論理削除)
// DBの責務ではあるが、 hooks がやるべきことか?
// event_attendees は delete しない (deleted_at フィールドがないため物理削除になるが、行わない)
func (e *Event) BeforeDelete(tx *gorm.DB) (err error) {
// delete current m2m
err = tx.Where("event_id = ?", e.ID).Delete(&EventTag{}).Error
Expand All @@ -117,6 +143,7 @@ func (e *Event) BeforeDelete(tx *gorm.DB) (err error) {
}

// BeforeSave is hook
// CreateEvent のDBの責務なので event が妥当
func (et *EventTag) BeforeSave(tx *gorm.DB) (err error) {
if et.EventID == uuid.Nil {
return NewValueError(gorm.ErrRecordNotFound, "eventID")
Expand Down Expand Up @@ -144,6 +171,7 @@ func (et *EventTag) BeforeSave(tx *gorm.DB) (err error) {
return nil
}

// 妥当
func (et *EventTag) BeforeDelete(tx *gorm.DB) (err error) {
// タグのIDが空で名前が提供されている場合は、
// 名前に応じたタグを削除する
Expand All @@ -162,30 +190,33 @@ func (et *EventTag) BeforeDelete(tx *gorm.DB) (err error) {
}

// BeforeSave is hook
func (r *Room) BeforeSave(_ *gorm.DB) (err error) {
if r.ID != uuid.Nil {
return nil
}
r.ID, err = uuid.NewV4()
if err != nil {
return err
}
// func (r *Room) BeforeSave(_ *gorm.DB) (err error) {
// if r.ID != uuid.Nil {
// return nil
// }
// // createRoomに移動
// r.ID, err = uuid.NewV4()
// if err != nil {
// return err
// }

// 時間整合性
Droom := ConvRoomTodomainRoom(*r)
if !Droom.TimeConsistency() {
return NewValueError(ErrTimeConsistency, "timeStart", "timeEnd")
}
return nil
}
// // 時間整合性
// // Serviceに移動
// Droom := ConvRoomTodomainRoom(*r)
// if !Droom.TimeConsistency() {
// return NewValueError(ErrTimeConsistency, "timeStart", "timeEnd")
// }
// return nil
// }

// func (r *Room) BeforeUpdate(tx *gorm.DB) (err error) {
// err = tx.Where("room_id", r.ID).Delete(&RoomAdmin{}).Error
// if err != nil {
// return err
// }
// return nil
// }

func (r *Room) BeforeUpdate(tx *gorm.DB) (err error) {
err = tx.Where("room_id", r.ID).Delete(&RoomAdmin{}).Error
if err != nil {
return err
}
return nil
}

func (r *Room) AfterSave(tx *gorm.DB) (err error) {
room, err := getRoom(tx.Preload("Admins"), r.ID)
Expand Down Expand Up @@ -266,6 +297,7 @@ func (t *Tag) BeforeCreate(_ *gorm.DB) (err error) {
return nil
}

/*
// BeforeCreate is hook
func (u *User) BeforeCreate(_ *gorm.DB) (err error) {
if u.ID != uuid.Nil {
Expand All @@ -277,6 +309,7 @@ func (u *User) BeforeCreate(_ *gorm.DB) (err error) {
}
return nil
}
*/

func (t *Token) BeforeSave(_ *gorm.DB) (err error) {
if t.AccessToken != "" {
Expand Down
26 changes: 23 additions & 3 deletions infra/db/room.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,39 @@ func (repo *gormRepository) GetAllRooms(start, end time.Time, excludeEventID uui

func createRoom(db *gorm.DB, args domain.CreateRoomArgs) (*Room, error) {
room := ConvCreateRoomParamsToRoom(args)
err := db.Create(&room).Error
var err error
// IDを新規発行
room.ID ,err = uuid.NewV4()
if err != nil {
return nil,err
}
// 時間整合性は service で確認済み
err = db.Create(&room).Error
return &room, err
Comment on lines 87 to 112
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Room 本体と Admin 差し替えを 1 トランザクションにまとめてください。

createRoomroomsroom_adminsvalidateRoomupdateRoom は既存 Admin 削除 → room 更新 → Admin 再作成の順に別クエリで進むので、途中失敗時に片方だけ残ります。特に更新側は既存 Admin を先に消しているので、失敗時の破壊が大きいです。

Also applies to: 118-142

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infra/db/room.go` around lines 87 - 112, createRoom and updateRoom must
perform the room insert/update, admin deletions/creations, and validateRoom call
inside a single DB transaction to avoid partial state on failure; modify
createRoom to use db.Transaction (or Begin/Commit/Rollback) so you: start a tx,
call ConvCreateRoomParamsToRoom then tx.Create(&room), tx.Save each admin
(room.Admins), call validateRoom with the tx (change validateRoom signature or
add a tx wrapper) and commit only if all succeed, rolling back on any error;
apply the same pattern to updateRoom so the "delete existing admins → update
room → recreate admins" sequence runs in the same transaction and returns/rolls
back on errors.

}

func updateRoom(db *gorm.DB, roomID uuid.UUID, args domain.UpdateRoomArgs) (*Room, error) {
room := ConvUpdateRoomParamsToRoom(args)
room.ID = roomID
err := db.Session(&gorm.Session{FullSaveAssociations: true}).
Omit("verified", "CreatedAt").Save(&room).Error
if room.ID == uuid.Nil {
return nil,ErrRoomUndefined
}

// BeforeSave, BeforeUpdate が発火
err := db.Where("room_id", room.ID).Delete(&RoomAdmin{}).Error
if err!=nil{
return nil,err
}
// 時間整合性は service で確認済み
err = db.Omit("verified", "CreatedAt").Save(&room).Error
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the Room model and Admins association
echo "=== infra/db/room.go の内容を確認 ==="
cat -n infra/db/room.go

echo ""
echo "=== RoomAdmin モデルの定義を確認 ==="
rg -n "type RoomAdmin" --type=go -A20

echo ""
echo "=== Room モデルの定義を確認 ==="
rg -n "type Room struct" --type=go -A30

echo ""
echo "=== updateRoom のテストを確認 ==="
fd ".*test.*\.go" | xargs rg -l "UpdateRoom|updateRoom" 2>/dev/null | head -5

Repository: traPtitech/knoQ

Length of output: 12992


🏁 Script executed:

#!/bin/bash
# Find hooks.go and check BeforeSave hook for Room
echo "=== hooks.go を検索 ==="
fd "hooks\.go" --type f

echo ""
echo "=== Room 関連の BeforeSave/BeforeUpdate フック定義を確認 ==="
rg -n "BeforeSave|BeforeUpdate" --type=go -B2 -A5 | head -40

echo ""
echo "=== room_test.go でのテスト内容を確認 ==="
head -150 infra/db/room_test.go

Repository: traPtitech/knoQ

Length of output: 3811


🏁 Script executed:

#!/bin/bash
# Search for where RoomAdmin is created/updated to understand if Admins management is separate
echo "=== RoomAdmin 作成処理を検索 ==="
rg -n "RoomAdmin\{\|Append.*RoomAdmin" --type=go -B2 -A2

echo ""
echo "=== ConvUpdateRoomParamsToRoom を確認 ==="
rg -n "ConvUpdateRoomParamsToRoom" --type=go -B2 -A15

Repository: traPtitech/knoQ

Length of output: 1935


Admins 関連付けが保存されない実装上のバグと誤解釈なコメント

  1. Line 95 のコメント: BeforeSave, BeforeUpdate が発火 は誤りです。Room モデルにはこれらのフックは存在しません(Event や EventTag にのみ存在)。コメントを削除するか、正確に更新してください。

  2. Line 96-101: Admins 関連付けが保存されない: ConvUpdateRoomParamsToRoom は新しい RoomAdmin オブジェクトを room.Admins スライスに作成していますが、SaveFullSaveAssociations: true なしで呼び出すため、新しい Admins がデータベースに保存されません。コメントアウトされているコード(103-104行目)が示すとおり、以下のように修正が必要です:

-	// BeforeSave, BeforeUpdate が発火
-	err := db.Where("room_id", room.ID).Delete(&RoomAdmin{}).Error
+	err := db.Where("room_id = ?", room.ID).Delete(&RoomAdmin{}).Error
 	if err != nil {
 		return nil, err
 	}
 	// 時間整合性は service で確認済み
-	err = db.Omit("verified", "CreatedAt").Save(&room).Error
-
-	// err := db.Session(&gorm.Session{FullSaveAssociations: true}).
-		// Omit("verified", "CreatedAt").Save(&room).Error
+	err = db.Session(&gorm.Session{FullSaveAssociations: true}).
+		Omit("verified", "CreatedAt").Save(&room).Error
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infra/db/room.go` around lines 95 - 101, The comment saying "BeforeSave,
BeforeUpdate が発火" is incorrect for the Room model—remove or correct that
comment; then fix the admins-not-being-saved bug by ensuring RoomAdmin
associations created by ConvUpdateRoomParamsToRoom are persisted: after deleting
existing admins with db.Where("room_id", room.ID).Delete(&RoomAdmin{}), call
Save on the room using a GORM session with FullSaveAssociations enabled (e.g.,
use db.Session(&gorm.Session{FullSaveAssociations:
true}).Omit("verified","CreatedAt").Save(&room)) so new room.Admins are saved;
keep the omit fields and existing deletion logic intact.


// err := db.Session(&gorm.Session{FullSaveAssociations: true}).
// Omit("verified", "CreatedAt").Save(&room).Error
return &room, err
}

func updateRoomVerified(db *gorm.DB, roomID uuid.UUID, verified bool) error {
// hooksは発火しない
return db.Model(&Room{}).Where("id = ?", roomID).UpdateColumn("verified", verified).Error
}

Expand Down
15 changes: 14 additions & 1 deletion infra/db/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,18 @@ func saveUser(db *gorm.DB, user *User) (*User, error) {
err := db.Transaction(func(tx *gorm.DB) error {
existingUser, err := getUser(tx.Preload("Provider").Preload("Token"), user.ID)
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&user).Error
// BeforeCreate が呼ばれる
if user.ID != uuid.Nil {
// 単純にIDが違う場合
return ErrRecordNotFound
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.

User の BeforeCreate を再現したがっていると思われますが,その場合 user.ID が uuid.Nil ならエラーにならず  user 変数の内容をそのまま Create するのが正しい動作な気がします(というか現状 traQ ユーザーしか knoQ を使えず, traQ ユーザーの uuid をそのまま knoQ の User の ID に利用しているので, user.ID が uuid.Nil になることはないはず...)

} else {
// IDなしで新たに作成する場合
user.ID ,err = uuid.NewV4()
if err != nil {
return err
}
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.

上で述べた通り,knoQ は traQ ユーザーのみを現状受け付けているので,knoQ 独自にユーザーのIDを作る必要もないのかなと.
(多分,knoQを作成していた当初は traQ のアカウントと紐づけなくても使えるようにしようとしていたのだろうけど,今はその予定もないので...)

return tx.Create(&user).Error
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

新規ユーザー作成が成立しなくなっています。
user.ID が設定済みでも未存在なら作成すべきケース(SaveUser/SyncUsers)が想定されるため、現在の ErrRecordNotFound 返却は動作破壊になり得ます。CIの「record not found」警告とも整合します。

✅ 修正案
-    if user.ID != uuid.Nil  {
-        // 単純にIDが違う場合
-        return ErrRecordNotFound
-    } else {
-        // IDなしで新たに作成する場合
-        user.ID ,err = uuid.NewV4()
-        if err != nil {
-            return err
-        }
-        return tx.Create(&user).Error
-    }
+    if user.ID == uuid.Nil {
+        user.ID, err = uuid.NewV4()
+        if err != nil {
+            return err
+        }
+    }
+    return tx.Create(&user).Error
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// BeforeCreate が呼ばれる
if user.ID != uuid.Nil {
// 単純にIDが違う場合
return ErrRecordNotFound
} else {
// IDなしで新たに作成する場合
user.ID ,err = uuid.NewV4()
if err != nil {
return err
}
return tx.Create(&user).Error
}
// BeforeCreate が呼ばれる
if user.ID == uuid.Nil {
user.ID, err = uuid.NewV4()
if err != nil {
return err
}
}
return tx.Create(&user).Error
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infra/db/user.go` around lines 121 - 132, The BeforeCreate hook currently
returns ErrRecordNotFound whenever user.ID != uuid.Nil which prevents creating
new users that supply an ID; instead, change BeforeCreate so it only generates a
new UUID when user.ID == uuid.Nil (use uuid.NewV4) and in all other cases
attempt tx.Create(&user).Error rather than returning ErrRecordNotFound;
reference the BeforeCreate function, the user.ID check, uuid.NewV4 call,
tx.Create(&user).Error and ErrRecordNotFound and remove the early
ErrRecordNotFound branch so provided IDs are allowed and creation is attempted.

}
if err != nil {
return err
Expand All @@ -127,6 +138,8 @@ func saveUser(db *gorm.DB, user *User) (*User, error) {
if user.IcalSecret == "" {
user.IcalSecret = existingUser.IcalSecret
}
// 良くない
// 何が更新される?
return tx.Session(&gorm.Session{FullSaveAssociations: true}).Updates(user).Error
})

Expand Down
9 changes: 9 additions & 0 deletions service/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ import (
"github.com/traPtitech/knoQ/infra/traq"
)

var (
ErrInvalidArgs = errors.New("invalid args")
ErrTimeConsistency = errors.New("inconsistent time")
ErrExpression = errors.New("invalid expression")
ErrRoomUndefined = errors.New("invalid room or args")
ErrNoAdmins = errors.New("no admins")
ErrDuplicateEntry = errors.New("duplicate entry")
)
Comment on lines +12 to +17
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check service/errors.go and trace error handling flow

echo "=== service/errors.go content ==="
cat -n service/errors.go

echo ""
echo "=== ErrTimeConsistency usage locations ==="
rg -n "ErrTimeConsistency" --type=go -B1 -A1

echo ""
echo "=== handleDBError implementation ==="
rg -n "func.*handleDBError" --type=go -A20

echo ""
echo "=== defaultErrorHandling implementation ==="
rg -n "func.*defaultErrorHandling" --type=go -A20

echo ""
echo "=== Check room_impl.go for ErrTimeConsistency usage ==="
rg -n "ErrTimeConsistency" service/room_impl.go -B2 -A2

Repository: traPtitech/knoQ

Length of output: 7130


🏁 Script executed:

#!/bin/bash
# Check HTTP handler code that uses service methods and how errors are handled

echo "=== Find HTTP handlers ==="
fd -e go -path "*/infra/api*" -o -path "*/handler*" | head -20

echo ""
echo "=== Search for service calls in handlers ==="
rg -n "\.CreateRoom\(|\.UpdateRoom\(" --type=go -B2 -A5 | head -50

echo ""
echo "=== Check if service.ErrTimeConsistency is handled in handlers ==="
rg -n "service\.Err" --type=go | grep -i handle

echo ""
echo "=== Check how errors from service are handled ==="
rg -n "s\..*Room\(" --type=go -A10 | grep -A5 "if err"

Repository: traPtitech/knoQ

Length of output: 2308


🏁 Script executed:

#!/bin/bash
# Check all service.Err* direct returns vs wrapped returns

echo "=== All service.Err* returns without defaultErrorHandling wrapping ==="
rg -n "return.*service\.Err|return nil.*Err(Time|Invalid|Expression|Room|NoAdmins|Duplicate)" service/ --type=go

echo ""
echo "=== Count returns with defaultErrorHandling ==="
rg -n "return.*defaultErrorHandling" service/ --type=go | wc -l

echo ""
echo "=== Check all Err* constants usage in service layer ==="
rg -n "Err(TimeConsistency|InvalidArgs|Expression|RoomUndefined|NoAdmins|DuplicateEntry)" service/ --type=go -c | sort

Repository: traPtitech/knoQ

Length of output: 591


サービス層の検証エラーがエラーハンドリングマッピングをバイパスしている

service/room_impl.go の時間整合性チェック(14行目、32行目、59行目)で ErrTimeConsistencydefaultErrorHandling を通さずに直接返されています。これにより、DB層から返されるエラーは domain 層のエラー型にマップされる一方、サービス層の検証エラーはマップされないという不整合が発生しています。

修正案:

  1. サービス層の検証エラーを defaultErrorHandling でラップする
  2. または、handleDBErrorservice.ErrTimeConsistency もマップする
return nil, defaultErrorHandling(ErrTimeConsistency)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/errors.go` around lines 12 - 19, The service returns
ErrTimeConsistency directly from functions in room_impl.go (references:
ErrTimeConsistency) which bypasses the global mapping; change those direct
returns to wrap the error with defaultErrorHandling (e.g., return nil,
defaultErrorHandling(ErrTimeConsistency)) so they go through the same mapping
path, or alternatively update handleDBError to also map
service.ErrTimeConsistency to the appropriate domain error; adjust every
occurrence in room_impl.go where ErrTimeConsistency is returned (the three
validation sites) to use defaultErrorHandling unless you choose to extend
handleDBError instead.


func handleTraQError(err error) error {
if errors.Is(err, traq.ErrUnAuthorized) {
return domain.ErrInvalidToken
Expand Down
21 changes: 21 additions & 0 deletions service/event_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,27 @@ func (s *service) UpdateEvent(ctx context.Context, reqID uuid.UUID, eventID uuid
WriteEventParams: params,
CreatedBy: reqID,
}
// RoomIDの存在を確認 RoomがなくPlaceがあれば新たに作成

// if params.RoomID == uuid.Nil {
// if params.Place == "" {
// roomParams := domain.WriteRoomParams {
// Place: params.Place,
// TimeStart: params.TimeStart,
// TimeEnd: params.TimeEnd,
// Admins: params.Admins,
// }
// // UnVerifiedを仮定
// _,err = s.CreateUnVerifiedRoom(ctx,reqID,roomParams)
// if err != nil {
// return nil,err
// }
// } else{
// return nil, ErrRoomUndefined
// }
// }
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.

現状これがなくても Event の hooks のおかげで動作しますが,合っても正しく動作する(Room の ID を p に渡す必要があるが)と思うので,実装してもいい気がしています.

それこそ,RoomID が Nil のとき,どのように部屋が扱われるかは service 層が担うべき部分かと思うので


// Event Save を使っている
event, err := s.GormRepo.UpdateEvent(eventID, p)
if err != nil {
return nil, defaultErrorHandling(err)
Expand Down
16 changes: 16 additions & 0 deletions service/room_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import (
)

func (s *service) CreateUnVerifiedRoom(ctx context.Context, reqID uuid.UUID, params domain.WriteRoomParams) (*domain.Room, error) {
// 時間整合性の確認
if(!params.TimeStart.Before(params.TimeEnd)){
return nil,ErrTimeConsistency
}
p := domain.CreateRoomArgs{
WriteRoomParams: params,
Verified: false,
Expand All @@ -23,6 +27,10 @@ func (s *service) CreateVerifiedRoom(ctx context.Context, reqID uuid.UUID, param
if !s.IsPrivilege(ctx, reqID) {
return nil, domain.ErrForbidden
}
// 時間整合性の確認
if(!params.TimeStart.Before(params.TimeEnd)){
return nil,ErrTimeConsistency
}
p := domain.CreateRoomArgs{
WriteRoomParams: params,
Verified: true,
Expand All @@ -36,12 +44,20 @@ func (s *service) UpdateRoom(ctx context.Context, reqID uuid.UUID, roomID uuid.U
if !s.IsRoomAdmins(ctx, reqID, roomID) {
return nil, domain.ErrForbidden
}
// そもそもNilだとIsRoomAdminsで弾かれるはず
// if roomID == uuid.Nil {
// return nil,ErrRoomUndefined
// }
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.

IsRoomAdmins の前に持っていくのが良さそう.


p := domain.UpdateRoomArgs{
WriteRoomParams: params,
CreatedBy: reqID,
}

// 時間整合性の確認
if(!params.TimeStart.Before(params.TimeEnd)){
return nil,ErrTimeConsistency
}
r, err := s.GormRepo.UpdateRoom(roomID, p)
return r, defaultErrorHandling(err)
}
Expand Down
Loading