diff --git a/server/im_http.go b/server/im_http.go index b50781d..004275a 100644 --- a/server/im_http.go +++ b/server/im_http.go @@ -8332,7 +8332,18 @@ func meetingNoteListItemPayload(meeting *types.Meeting, files []types.MeetingNot func meetingNotesDetailPayload(tenantID types.TenantID, meeting *types.Meeting, files []types.MeetingNoteFile, viewer types.Uid) map[string]any { userSummaries := meetingUserSummaries(tenantID, []types.Meeting{*meeting}) - data := meetingNoteListItemPayload(meeting, files, viewer, userSummaries) + meetingData := meetingPayloadWithUserSummaries(meeting, viewer, userSummaries) + data := make(map[string]any, len(meetingData)+4) + for k, v := range meetingData { + data[k] = v + } + data["meeting"] = meetingData + data["note_status"] = types.MeetingNoteStatusEmpty + if len(files) > 0 { + data["note_status"] = types.MeetingNoteStatusAvailable + } + data["file_count"] = len(files) + data["permissions"] = meetingNotePermissionsPayload(meeting, viewer) fileItems := make([]map[string]any, 0, len(files)) for i := range files { fileItems = append(fileItems, meetingNoteFilePayload(&files[i], viewer, userSummaries)) diff --git a/server/im_http_test.go b/server/im_http_test.go index d8f9e06..1470171 100644 --- a/server/im_http_test.go +++ b/server/im_http_test.go @@ -278,6 +278,7 @@ type testMeetingNotesStore struct { createdMeetingID string createdFileID string createdIdempotencyKey string + createdFile *types.MeetingNoteFile } type testDailyReportsStore struct { @@ -685,11 +686,18 @@ func (s *testMeetingNotesStore) CreateFile(_ types.TenantID, uid types.Uid, meet s.createdMeetingID = meetingID s.createdFileID = fileID s.createdIdempotencyKey = idempotencyKey - return &types.MeetingNoteFile{ + if s.createdFile != nil && s.createdFile.MeetingID == meetingID && s.createdFile.UploaderID == uid && s.createdFile.IdempotencyKey == idempotencyKey { + file := *s.createdFile + return &file, nil + } + file := &types.MeetingNoteFile{ NoteFileID: noteFileID, MeetingID: meetingID, FileID: fileID, UploaderID: uid, FileName: fileID, Status: types.MeetingNoteFileStatusActive, PreviewStatus: "ready", PreviewMode: "native", IdempotencyKey: idempotencyKey, Version: 1, CreatedAt: now, UpdatedAt: now, - }, nil + } + s.createdFile = file + s.files = append(s.files, *file) + return file, nil } func (s *testMeetingNotesStore) ReplaceFile(_ types.TenantID, uid types.Uid, meetingID, noteFileID, fileID, idempotencyKey string, version int) (*types.MeetingNoteFile, error) { @@ -2996,7 +3004,9 @@ func TestServeIMMeetingNotesListAndDetail(t *testing.T) { } data = decodeIMHTTPResponse(t, rec).Data.(map[string]any) files := data["files"].([]any) - if data["recorder_user_id"] != viewer.UserId() || len(files) != 1 || files[0].(map[string]any)["file_id"] != "file_test" { + meetingData := data["meeting"].(map[string]any) + if data["recorder_user_id"] != viewer.UserId() || meetingData["recorder_user_id"] != viewer.UserId() || meetingData["start_at"] == "" || + len(files) != 1 || files[0].(map[string]any)["file_id"] != "file_test" { t.Fatalf("unexpected meeting notes detail: %#v", data) } } @@ -3007,9 +3017,30 @@ func TestServeIMMeetingNoteFilesCreate(t *testing.T) { viewer := types.Uid(42) mockStore := expectIMBearer(t, ctrl, "token-1", 7, viewer) mockStore.EXPECT().GetUidString().Return("note-1") + mockStore.EXPECT().GetUidString().Return("note-2") + now := time.Now().UTC().Round(time.Millisecond) + meeting := types.Meeting{ + MeetingID: "mtg_test", + OrganizerID: viewer, + RecorderID: viewer, + Title: "纪要会", + StartAt: now.Add(-2 * time.Hour), + EndAt: now.Add(-time.Hour), + State: types.MeetingStateEnded, + Version: 1, + CreatedAt: now.Add(-3 * time.Hour), + UpdatedAt: now, + Participants: []types.MeetingParticipant{ + {MeetingID: "mtg_test", UserID: viewer, Response: types.MeetingResponseAccepted}, + }, + } + oldMeetings := store.Meetings oldMeetingNotes := store.MeetingNotes - store.MeetingNotes = &testMeetingNotesStore{} + notes := &testMeetingNotesStore{} + store.Meetings = &testMeetingsStore{meeting: &meeting} + store.MeetingNotes = notes defer func() { + store.Meetings = oldMeetings store.MeetingNotes = oldMeetingNotes }() @@ -3025,6 +3056,34 @@ func TestServeIMMeetingNoteFilesCreate(t *testing.T) { if data["note_file_id"] != "mnote_note-1" || data["file_id"] != "file_test" || data["version"].(float64) != 1 { t.Fatalf("unexpected note file payload: %#v", data) } + + req = httptest.NewRequest(http.MethodPost, "/v1/im/meetings/mtg_test/notes/files", strings.NewReader(`{"file_id":"file_test"}`)) + req.Header.Set("Authorization", "Bearer token-1") + req.Header.Set("Idempotency-Key", "note-file-1") + rec = httptest.NewRecorder() + serveIMMeetingResource(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("duplicate status=%d body=%s", rec.Code, rec.Body.String()) + } + duplicate := decodeIMHTTPResponse(t, rec).Data.(map[string]any) + if duplicate["note_file_id"] != "mnote_note-1" || len(notes.files) != 1 { + t.Fatalf("unexpected idempotent note file payload: data=%#v files=%#v", duplicate, notes.files) + } + + req = httptest.NewRequest(http.MethodGet, "/v1/im/meetings/mtg_test/notes", nil) + req.Header.Set("Authorization", "Bearer token-1") + rec = httptest.NewRecorder() + serveIMMeetingResource(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("detail status=%d body=%s", rec.Code, rec.Body.String()) + } + detail := decodeIMHTTPResponse(t, rec).Data.(map[string]any) + files := detail["files"].([]any) + meetingData := detail["meeting"].(map[string]any) + if len(files) != 1 || files[0].(map[string]any)["note_file_id"] != "mnote_note-1" || + meetingData["meeting_id"] != "mtg_test" || meetingData["recorder_user_id"] != viewer.UserId() { + t.Fatalf("unexpected detail after create: %#v", detail) + } } func TestServeIMDailyReportsCreateAndList(t *testing.T) {