チャット・掲示板(スレッド/コメント)へファイル/画像添付を追加。 - 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(権限チェック済)で配信
344 lines
7.7 KiB
TypeScript
344 lines
7.7 KiB
TypeScript
/**
|
|
* 全Entity型定義・共有型
|
|
* functional-design.md のデータモデルに対応。
|
|
* タイムスタンプはISO8601文字列(TEXT)。真偽値はINTEGER(0/1)。
|
|
*/
|
|
|
|
// ===== 列挙型 =====
|
|
|
|
export type UserRole = 'system_admin' | 'project_admin' | 'member' | 'guest';
|
|
export type UserStatus = 'active' | 'inactive';
|
|
export type ProjectStatus = 'active' | 'on_hold' | 'completed' | 'archived';
|
|
export type ProjectMemberRole = 'admin' | 'member' | 'guest';
|
|
export type BoardCategory =
|
|
| 'notice'
|
|
| 'spec'
|
|
| 'minutes'
|
|
| 'question'
|
|
| 'decision'
|
|
| 'trouble'
|
|
| 'memo';
|
|
export type TodoPriority = 'low' | 'normal' | 'high';
|
|
export type MilestoneStatus = 'open' | 'closed';
|
|
export type CalendarEventType =
|
|
| 'meeting'
|
|
| 'deadline'
|
|
| 'milestone'
|
|
| 'todo'
|
|
| 'reminder'
|
|
| 'custom';
|
|
export type MeetingMemberStatus = 'invited' | 'accepted' | 'declined';
|
|
export type NotificationType =
|
|
| 'mention'
|
|
| 'todo_assigned'
|
|
| 'todo_due_soon'
|
|
| 'meeting_invited'
|
|
| 'board_commented'
|
|
| 'project_added'
|
|
| 'file_shared'
|
|
| 'note_updated';
|
|
|
|
// ===== エンティティ =====
|
|
|
|
export interface User {
|
|
id: number;
|
|
name: string;
|
|
email: string;
|
|
passwordHash: string | null;
|
|
avatarUrl: string | null;
|
|
role: UserRole;
|
|
status: UserStatus;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface Project {
|
|
id: number;
|
|
name: string;
|
|
description: string | null;
|
|
status: ProjectStatus;
|
|
ownerId: number;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface ProjectMember {
|
|
id: number;
|
|
projectId: number;
|
|
userId: number;
|
|
role: ProjectMemberRole;
|
|
joinedAt: string;
|
|
}
|
|
|
|
export interface BoardThread {
|
|
id: number;
|
|
projectId: number;
|
|
title: string;
|
|
bodyMd: string;
|
|
authorId: number;
|
|
category: BoardCategory | null;
|
|
isPinned: number;
|
|
isImportant: number;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
deletedAt: string | null;
|
|
}
|
|
|
|
export interface BoardComment {
|
|
id: number;
|
|
threadId: number;
|
|
authorId: number;
|
|
bodyMd: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
deletedAt: string | null;
|
|
}
|
|
|
|
export interface ChatMessage {
|
|
id: number;
|
|
projectId: number;
|
|
authorId: number;
|
|
body: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
deletedAt: string | null;
|
|
}
|
|
|
|
export interface TodoColumn {
|
|
id: number;
|
|
projectId: number;
|
|
name: string;
|
|
orderIndex: number;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface TodoItem {
|
|
id: number;
|
|
projectId: number;
|
|
columnId: number;
|
|
title: string;
|
|
description: string | null;
|
|
assigneeId: number | null;
|
|
creatorId: number;
|
|
priority: TodoPriority;
|
|
startDate: string | null;
|
|
dueDate: string | null;
|
|
completedAt: string | null;
|
|
orderIndex: number;
|
|
milestoneId: number | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
deletedAt: string | null;
|
|
}
|
|
|
|
export interface FileAsset {
|
|
id: number;
|
|
projectId: number;
|
|
uploaderId: number;
|
|
filename: string;
|
|
originalName: string;
|
|
mimeType: string;
|
|
size: number;
|
|
path: string;
|
|
source: FileAssetSource;
|
|
createdAt: string;
|
|
deletedAt: string | null;
|
|
}
|
|
|
|
/** ファイルの来源。library=Files一覧公開、attachment=添付専用(一覧に出さない)。 */
|
|
export type FileAssetSource = 'library' | 'attachment';
|
|
|
|
/** 添付ファイルの関連付け対象。 */
|
|
export type AttachmentTargetType =
|
|
| 'chat_message'
|
|
| 'board_thread'
|
|
| 'board_comment';
|
|
|
|
/** attachments エンティティ。 */
|
|
export interface Attachment {
|
|
id: number;
|
|
projectId: number;
|
|
fileId: number;
|
|
targetType: AttachmentTargetType;
|
|
targetId: number;
|
|
createdAt: string;
|
|
deletedAt: string | null;
|
|
}
|
|
|
|
/** UI表示用の添付ファイルビュー(ダウンロードURLは fileId から組み立てる)。 */
|
|
export interface AttachmentView {
|
|
id: number;
|
|
fileId: number;
|
|
targetType: AttachmentTargetType;
|
|
targetId: number;
|
|
originalName: string;
|
|
mimeType: string;
|
|
size: number;
|
|
}
|
|
|
|
/** チャットメッセージ + 添付ファイル。 */
|
|
export interface ChatMessageWithAttachments extends ChatMessage {
|
|
attachments: AttachmentView[];
|
|
}
|
|
|
|
export interface ProjectNote {
|
|
id: number;
|
|
projectId: number;
|
|
title: string;
|
|
bodyMd: string;
|
|
tags: string | null;
|
|
isPinned: number;
|
|
createdById: number;
|
|
updatedById: number;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
deletedAt: string | null;
|
|
}
|
|
|
|
export interface Milestone {
|
|
id: number;
|
|
projectId: number;
|
|
title: string;
|
|
description: string | null;
|
|
dueDate: string | null;
|
|
status: MilestoneStatus;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
deletedAt: string | null;
|
|
}
|
|
|
|
export interface CalendarEvent {
|
|
id: number;
|
|
projectId: number;
|
|
title: string;
|
|
description: string | null;
|
|
type: CalendarEventType;
|
|
startAt: string;
|
|
endAt: string | null;
|
|
createdById: number;
|
|
relatedTodoId: number | null;
|
|
relatedMilestoneId: number | null;
|
|
relatedMeetingId: number | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
deletedAt: string | null;
|
|
}
|
|
|
|
export interface Meeting {
|
|
id: number;
|
|
projectId: number;
|
|
title: string;
|
|
description: string | null;
|
|
location: string | null;
|
|
meetingUrl: string | null;
|
|
startAt: string;
|
|
endAt: string;
|
|
agendaMd: string | null;
|
|
minutesMd: string | null;
|
|
createdById: number;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
deletedAt: string | null;
|
|
}
|
|
|
|
export interface MeetingMember {
|
|
id: number;
|
|
meetingId: number;
|
|
userId: number;
|
|
status: MeetingMemberStatus;
|
|
}
|
|
|
|
export interface Notification {
|
|
id: number;
|
|
userId: number;
|
|
projectId: number | null;
|
|
type: NotificationType;
|
|
title: string;
|
|
body: string | null;
|
|
readAt: string | null;
|
|
createdAt: string;
|
|
}
|
|
|
|
export interface ActivityLog {
|
|
id: number;
|
|
projectId: number;
|
|
actorId: number;
|
|
action: string;
|
|
targetType: string;
|
|
targetId: number | null;
|
|
metadataJson: string | null;
|
|
createdAt: string;
|
|
}
|
|
|
|
export interface SchemaMigration {
|
|
id: number;
|
|
filename: string;
|
|
appliedAt: string;
|
|
}
|
|
|
|
// ===== 通知イベント =====
|
|
|
|
export interface NotificationEventBase {
|
|
projectId: number | null;
|
|
title: string;
|
|
body: string | null;
|
|
}
|
|
|
|
/**
|
|
* 通知イベントの判別共用体。
|
|
* resolveTargets で type ごとに対象ユーザーを解決する。
|
|
*/
|
|
export type NotificationEvent =
|
|
| (NotificationEventBase & {
|
|
type: 'mention';
|
|
mentionedUserId: number;
|
|
})
|
|
| (NotificationEventBase & { type: 'todo_assigned'; assigneeId: number })
|
|
| (NotificationEventBase & { type: 'todo_due_soon'; assigneeId: number })
|
|
| (NotificationEventBase & {
|
|
type: 'meeting_invited';
|
|
memberIds: number[];
|
|
})
|
|
| (NotificationEventBase & {
|
|
type: 'board_commented';
|
|
threadAuthorId: number;
|
|
})
|
|
| (NotificationEventBase & { type: 'project_added'; addedUserId: number })
|
|
| (NotificationEventBase & {
|
|
type: 'file_shared';
|
|
projectMemberIds: number[];
|
|
})
|
|
| (NotificationEventBase & {
|
|
type: 'note_updated';
|
|
projectMemberIds: number[];
|
|
});
|
|
|
|
/** SSE配信イベント(M8でSseHubが扱う)。 */
|
|
export type SseEvent =
|
|
| {
|
|
type: 'chat.message.created';
|
|
data: {
|
|
projectId: number;
|
|
message: ChatMessageWithAttachments;
|
|
};
|
|
}
|
|
| {
|
|
type: 'chat.message.updated';
|
|
data: { projectId: number; message: ChatMessage };
|
|
}
|
|
| {
|
|
type: 'chat.message.deleted';
|
|
data: { projectId: number; id: number };
|
|
}
|
|
| { type: 'todo.updated'; data: { projectId: number } }
|
|
| { type: 'file.uploaded'; data: { projectId: number } }
|
|
| { type: 'meeting.created'; data: { projectId: number } }
|
|
| { type: 'note.updated'; data: { projectId: number } }
|
|
| { type: 'notification.created'; data: { projectId: number | null } };
|
|
|
|
/** SSE配信を行うコンポーネントの抽象(M8のSseHubが実装) */
|
|
export interface SseBroadcaster {
|
|
broadcast(projectId: number, event: SseEvent): void;
|
|
}
|