feat(chat,board): file/image attachments on chat, board threads and comments
チャット・掲示板(スレッド/コメント)へファイル/画像添付を追加。 - lib/db/migrations/002_attachments.sql: attachments テーブル + file_assets.source 列 - repositories/AttachmentRepository + services/AttachmentService: 添付紐付け(プロジェクト/アップロード者チェック) - FileStorageService.uploadForAttachment: 通知/SSE/アクティビティを行わない添付専用アップロード(source='attachment') - ChatService/BoardService: fileIds 受理・添付紐付け・履歴/詳細表示・削除クリーンアップ(トランザクション) - API: POST /attachments + chat/board の fileIds 受理 - UI: AttachmentList(画像サムネイル+Lightbox/ダウンロード) + AttachmentPicker(複数アップロード・送信中は送信不可) をチャット/スレッド/コメントに統合 - 添付ファイルは既存の /api/files/[fileId]/download(権限チェック済)で配信
This commit is contained in:
@ -77,4 +77,44 @@ test.describe('board', () => {
|
||||
page.getByRole('link', { name: new RegExp(title) })
|
||||
).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('thread and comment can carry file attachments', async ({ page }) => {
|
||||
const projectId = await setupOwner(page);
|
||||
|
||||
// 添付ファイル(1x1 PNG)をアップロード
|
||||
const png = Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=',
|
||||
'base64'
|
||||
);
|
||||
const upRes = await page.request.post(
|
||||
`/api/projects/${projectId}/attachments`,
|
||||
{
|
||||
multipart: {
|
||||
file: { name: 'pic.png', mimeType: 'image/png', buffer: png },
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(upRes.ok()).toBeTruthy();
|
||||
const { file } = (await upRes.json()) as { file: { id: number } };
|
||||
|
||||
// ファイル付きスレッド作成
|
||||
const title = unique('AttachThread');
|
||||
const res = await page.request.post(
|
||||
`/api/projects/${projectId}/board/threads`,
|
||||
{ data: { title, bodyMd: 'body', fileIds: [file.id] } }
|
||||
);
|
||||
expect(res.ok()).toBeTruthy();
|
||||
const { thread } = (await res.json()) as { thread: { id: number } };
|
||||
|
||||
// ファイル付きコメント
|
||||
const cRes = await page.request.post(
|
||||
`/api/projects/${projectId}/board/threads/${thread.id}/comments`,
|
||||
{ data: { bodyMd: 'with file', fileIds: [file.id] } }
|
||||
);
|
||||
expect(cRes.ok()).toBeTruthy();
|
||||
|
||||
// 詳細ページでスレッド・コメントの添付が表示される
|
||||
await page.goto(`/projects/${projectId}/board/${thread.id}`);
|
||||
await expect(page.getByTestId('attachment-list').first()).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@ -65,4 +65,50 @@ test.describe('chat (SSE realtime)', () => {
|
||||
await ownerContext.close();
|
||||
await memberContext.close();
|
||||
});
|
||||
|
||||
test('a message can carry file attachments rendered in the chat', async ({
|
||||
page,
|
||||
}) => {
|
||||
const ownerEmail = unique('owner') + '@example.com';
|
||||
await registerAndLogin(page, ownerEmail, 'Owner');
|
||||
await page.getByLabel('プロジェクト名').fill(unique('Proj'));
|
||||
await page.getByRole('button', { name: '新規プロジェクト' }).click();
|
||||
await expect(page).toHaveURL(/\/projects\/\d+$/);
|
||||
const projectId = Number(page.url().match(/\/projects\/(\d+)/)![1]);
|
||||
|
||||
// 添付ファイル(1x1 PNG)をアップロード
|
||||
const png = Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=',
|
||||
'base64'
|
||||
);
|
||||
const upRes = await page.request.post(
|
||||
`/api/projects/${projectId}/attachments`,
|
||||
{
|
||||
multipart: {
|
||||
file: { name: 'pic.png', mimeType: 'image/png', buffer: png },
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(upRes.ok()).toBeTruthy();
|
||||
const { file } = (await upRes.json()) as { file: { id: number } };
|
||||
|
||||
// ファイル付きメッセージ送信
|
||||
const text = unique('with-attach');
|
||||
const sendRes = await page.request.post(
|
||||
`/api/projects/${projectId}/chat/messages`,
|
||||
{ data: { body: text, fileIds: [file.id] } }
|
||||
);
|
||||
expect(sendRes.ok()).toBeTruthy();
|
||||
const { message } = (await sendRes.json()) as {
|
||||
message: { attachments: { fileId: number }[] };
|
||||
};
|
||||
expect(message.attachments).toHaveLength(1);
|
||||
|
||||
// チャット画面に添付画像が表示される
|
||||
await page.goto(`/projects/${projectId}/chat`);
|
||||
await expect(page.getByTestId('chat-form')).toBeVisible();
|
||||
await expect(page.getByTestId('attachment-list')).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user