feat(artifacts): add S3-based artifact management system
Implement artifact storage and distribution for θ-cycle outputs: - src/artifacts/types.ts: Artifact, ArtifactMetadata interfaces * ArtifactType enum (file, code, report, image, log, json) * SaveArtifactOptions, DownloadUrlOptions, ArtifactFilter - src/artifacts/manager.ts: S3 + DynamoDB client * save() / saveFile() - Upload to S3, store metadata in DynamoDB * getDownloadUrl() - Generate presigned URL (default: 1 hour) * get() - Retrieve artifact metadata * listBySession() / listByUser() - Query artifacts * deleteArtifact() / deleteBySession() - Cleanup * initializeTable() - DynamoDB table setup - src/artifacts/manager.test.ts: Unit tests (skeleton) - src/artifacts/index.ts: Module exports Features: - S3 storage with automatic expiration - Presigned URLs for secure downloads - DynamoDB metadata with TTL (default: 24 hours) - GSI for sessionId/userId/type queries - Session-based cleanup Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
0dcc2b4c8d
commit
2931f600b4
8
src/artifacts/index.ts
Normal file
8
src/artifacts/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* 成果物管理モジュール
|
||||||
|
*
|
||||||
|
* @module artifacts
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from "./types.js";
|
||||||
|
export * from "./manager.js";
|
||||||
140
src/artifacts/manager.test.ts
Normal file
140
src/artifacts/manager.test.ts
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* 成果物マネージャーテスト
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import {
|
||||||
|
save,
|
||||||
|
saveFile,
|
||||||
|
getDownloadUrl,
|
||||||
|
get,
|
||||||
|
listBySession,
|
||||||
|
listByUser,
|
||||||
|
deleteArtifact,
|
||||||
|
deleteBySession,
|
||||||
|
initializeTable,
|
||||||
|
} from "./manager.js";
|
||||||
|
import { ArtifactType } from "./types.js";
|
||||||
|
|
||||||
|
// AWS SDKモック
|
||||||
|
vi.mock("@aws-sdk/client-s3");
|
||||||
|
vi.mock("@aws-sdk/client-dynamodb");
|
||||||
|
vi.mock("@aws-sdk/lib-dynamodb");
|
||||||
|
vi.mock("@aws-sdk/s3-request-presigner");
|
||||||
|
|
||||||
|
describe("artifact manager", () => {
|
||||||
|
const mockSessionId = "test-session-123";
|
||||||
|
const mockUserId = "user-456";
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("saveFile", () => {
|
||||||
|
it("should save text file and return download URL", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should save binary file and return download URL", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should save with custom TTL", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should save with tags and description", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getDownloadUrl", () => {
|
||||||
|
it("should return presigned URL for existing artifact", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return null for non-existent artifact", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return null for expired artifact", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should support custom expiration time", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("get", () => {
|
||||||
|
it("should return artifact metadata", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return null for non-existent artifact", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("listBySession", () => {
|
||||||
|
it("should list artifacts for session", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should exclude expired artifacts", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return empty array for session with no artifacts", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("listByUser", () => {
|
||||||
|
it("should list artifacts for user", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should exclude expired artifacts", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("deleteArtifact", () => {
|
||||||
|
it("should delete artifact from S3 and DynamoDB", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be idempotent for non-existent artifact", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("deleteBySession", () => {
|
||||||
|
it("should delete all artifacts for session", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return count of deleted artifacts", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle empty session gracefully", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("initializeTable", () => {
|
||||||
|
it("should create table if not exists", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should skip if table already exists", async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
379
src/artifacts/manager.ts
Normal file
379
src/artifacts/manager.ts
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
/**
|
||||||
|
* 成果物管理マネージャー
|
||||||
|
*
|
||||||
|
* S3 + DynamoDBでθサイクルの成果物を保存・配信する
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
S3Client,
|
||||||
|
PutObjectCommand,
|
||||||
|
GetObjectCommand,
|
||||||
|
DeleteObjectCommand,
|
||||||
|
} from "@aws-sdk/client-s3";
|
||||||
|
import { DynamoDBClient, DescribeTableCommand } from "@aws-sdk/client-dynamodb";
|
||||||
|
import {
|
||||||
|
DynamoDBDocumentClient,
|
||||||
|
GetCommand,
|
||||||
|
PutCommand,
|
||||||
|
DeleteCommand,
|
||||||
|
QueryCommand,
|
||||||
|
} from "@aws-sdk/lib-dynamodb";
|
||||||
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
||||||
|
import type {
|
||||||
|
Artifact,
|
||||||
|
ArtifactMetadata,
|
||||||
|
ArtifactType,
|
||||||
|
SaveArtifactOptions,
|
||||||
|
DownloadUrlOptions,
|
||||||
|
ArtifactFilter,
|
||||||
|
} from "./types.js";
|
||||||
|
|
||||||
|
/** デフォルトTTL (24時間) */
|
||||||
|
const DEFAULT_TTL_SECONDS = 24 * 60 * 60;
|
||||||
|
|
||||||
|
/** デフォルトURL有効期限 (1時間) */
|
||||||
|
const DEFAULT_URL_EXPIRES_SECONDS = 60 * 60;
|
||||||
|
|
||||||
|
/** テーブル名 */
|
||||||
|
const ARTIFACTS_TABLE = process.env.ARTIFACTS_TABLE_NAME || "clawdbot-artifacts";
|
||||||
|
|
||||||
|
/** S3バケット */
|
||||||
|
const S3_BUCKET = process.env.ARTIFACTS_S3_BUCKET || "clawdbot-artifacts";
|
||||||
|
|
||||||
|
/** DynamoDBクライアント */
|
||||||
|
const ddbClient = new DynamoDBClient({});
|
||||||
|
const ddbDocClient = DynamoDBDocumentClient.from(ddbClient);
|
||||||
|
|
||||||
|
/** S3クライアント */
|
||||||
|
const s3Client = new S3Client({});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* テーブル初期化(開発環境用)
|
||||||
|
*/
|
||||||
|
export async function initializeTable(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await ddbClient.send(new DescribeTableCommand({ TableName: ARTIFACTS_TABLE }));
|
||||||
|
console.log(`[ArtifactManager] Table ${ARTIFACTS_TABLE} exists`);
|
||||||
|
} catch {
|
||||||
|
console.log(`[ArtifactManager] Creating table ${ARTIFACTS_TABLE}`);
|
||||||
|
await ddbClient.send(
|
||||||
|
new CreateTableCommand({
|
||||||
|
TableName: ARTIFACTS_TABLE,
|
||||||
|
AttributeDefinitions: [
|
||||||
|
{ AttributeName: "id", AttributeType: "S" },
|
||||||
|
{ AttributeName: "sessionId", AttributeType: "S" },
|
||||||
|
{ AttributeName: "userId", AttributeType: "S" },
|
||||||
|
{ AttributeName: "type", AttributeType: "S" },
|
||||||
|
{ AttributeName: "expiresAt", AttributeType: "N" },
|
||||||
|
],
|
||||||
|
KeySchema: [{ AttributeName: "id", KeyType: "HASH" }],
|
||||||
|
GlobalSecondaryIndexes: [
|
||||||
|
{
|
||||||
|
IndexName: "SessionIndex",
|
||||||
|
KeySchema: [
|
||||||
|
{ AttributeName: "sessionId", KeyType: "HASH" },
|
||||||
|
{ AttributeName: "createdAt", KeyType: "RANGE" },
|
||||||
|
],
|
||||||
|
Projection: { ProjectionType: "ALL" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
IndexName: "UserIndex",
|
||||||
|
KeySchema: [
|
||||||
|
{ AttributeName: "userId", KeyType: "HASH" },
|
||||||
|
{ AttributeName: "createdAt", KeyType: "RANGE" },
|
||||||
|
],
|
||||||
|
Projection: { ProjectionType: "ALL" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
IndexName: "TypeIndex",
|
||||||
|
KeySchema: [
|
||||||
|
{ AttributeName: "type", KeyType: "HASH" },
|
||||||
|
{ AttributeName: "expiresAt", KeyType: "RANGE" },
|
||||||
|
],
|
||||||
|
Projection: { ProjectionType: "ALL" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
BillingMode: "PAY_PER_REQUEST",
|
||||||
|
TimeToLiveSpecification: {
|
||||||
|
AttributeName: "expiresAt",
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
console.log(`[ArtifactManager] Table ${ARTIFACTS_TABLE} created`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* S3にアップロード
|
||||||
|
*/
|
||||||
|
async function uploadToS3(key: string, data: Buffer | string, mimeType: string): Promise<void> {
|
||||||
|
const body = typeof data === "string" ? Buffer.from(data) : data;
|
||||||
|
|
||||||
|
await s3Client.send(
|
||||||
|
new PutObjectCommand({
|
||||||
|
Bucket: S3_BUCKET,
|
||||||
|
Key: key,
|
||||||
|
Body: body,
|
||||||
|
ContentType: mimeType,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* S3から署名付きURLを生成
|
||||||
|
*/
|
||||||
|
async function generatePresignedUrl(
|
||||||
|
key: string,
|
||||||
|
expiresIn: number = DEFAULT_URL_EXPIRES_SECONDS,
|
||||||
|
): Promise<string> {
|
||||||
|
const command = new GetObjectCommand({
|
||||||
|
Bucket: S3_BUCKET,
|
||||||
|
Key: key,
|
||||||
|
});
|
||||||
|
|
||||||
|
return getSignedUrl(s3Client, command, { expiresIn });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成果物を保存
|
||||||
|
*
|
||||||
|
* @param artifact - 成果物データ
|
||||||
|
* @param options - 保存オプション
|
||||||
|
* @returns ダウンロードURL
|
||||||
|
*/
|
||||||
|
export async function save(artifact: Artifact, options: SaveArtifactOptions = {}): Promise<string> {
|
||||||
|
const { ttl = DEFAULT_TTL_SECONDS, tags, description, public: isPublic = false } = options;
|
||||||
|
|
||||||
|
const id = `artifact-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
||||||
|
const now = Date.now();
|
||||||
|
const expiresAt = Math.floor(now / 1000) + ttl;
|
||||||
|
|
||||||
|
// S3キー生成: {sessionId}/{type}/{filename} または {type}/{id}/{filename}
|
||||||
|
const sessionPrefix = artifact.metadata.sessionId ? `${artifact.metadata.sessionId}/` : "";
|
||||||
|
const s3Key = `${sessionPrefix}${artifact.metadata.type}/${id}/${artifact.metadata.filename}`;
|
||||||
|
|
||||||
|
// S3にアップロード
|
||||||
|
const data = artifact.buffer ?? artifact.text ?? "";
|
||||||
|
await uploadToS3(s3Key, data, artifact.metadata.mimeType);
|
||||||
|
|
||||||
|
// メタデータ構築
|
||||||
|
const metadata: ArtifactMetadata = {
|
||||||
|
...artifact.metadata,
|
||||||
|
id,
|
||||||
|
s3Key,
|
||||||
|
s3Bucket: S3_BUCKET,
|
||||||
|
createdAt: now,
|
||||||
|
expiresAt,
|
||||||
|
tags,
|
||||||
|
description,
|
||||||
|
};
|
||||||
|
|
||||||
|
// DynamoDBに保存
|
||||||
|
await ddbDocClient.send(
|
||||||
|
new PutCommand({
|
||||||
|
TableName: ARTIFACTS_TABLE,
|
||||||
|
Item: metadata,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ダウンロードURL生成
|
||||||
|
if (isPublic) {
|
||||||
|
// 公開URL(S3バケットが公開設定されている場合)
|
||||||
|
return `https://${S3_BUCKET}.s3.amazonaws.com/${s3Key}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return generatePresignedUrl(s3Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成果物を保存 (ヘルパー)
|
||||||
|
*
|
||||||
|
* @param sessionId - セッションID
|
||||||
|
* @param type - 種類
|
||||||
|
* @param filename - ファイル名
|
||||||
|
* @param data - データ
|
||||||
|
* @param mimeType - MIMEタイプ
|
||||||
|
* @param options - オプション
|
||||||
|
* @returns ダウンロードURL
|
||||||
|
*/
|
||||||
|
export async function saveFile(
|
||||||
|
sessionId: string,
|
||||||
|
type: ArtifactType,
|
||||||
|
filename: string,
|
||||||
|
data: Buffer | string,
|
||||||
|
mimeType: string,
|
||||||
|
options: SaveArtifactOptions = {},
|
||||||
|
): Promise<string> {
|
||||||
|
const size = typeof data === "string" ? data.length : data.length;
|
||||||
|
|
||||||
|
const artifact: Artifact = {
|
||||||
|
metadata: {
|
||||||
|
sessionId,
|
||||||
|
type,
|
||||||
|
filename,
|
||||||
|
mimeType,
|
||||||
|
size,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
expiresAt: 0, // 一時的な値、save()で上書き
|
||||||
|
} as ArtifactMetadata,
|
||||||
|
buffer: typeof data === "string" ? undefined : data,
|
||||||
|
text: typeof data === "string" ? data : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
return save(artifact, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ダウンロードURLを取得
|
||||||
|
*
|
||||||
|
* @param artifactId - 成果物ID
|
||||||
|
* @param options - オプション
|
||||||
|
* @returns ダウンロードURL、存在しない場合はnull
|
||||||
|
*/
|
||||||
|
export async function getDownloadUrl(
|
||||||
|
artifactId: string,
|
||||||
|
options: DownloadUrlOptions = {},
|
||||||
|
): Promise<string | null> {
|
||||||
|
const { expires = DEFAULT_URL_EXPIRES_SECONDS } = options;
|
||||||
|
|
||||||
|
const response = await ddbDocClient.send(
|
||||||
|
new GetCommand({
|
||||||
|
TableName: ARTIFACTS_TABLE,
|
||||||
|
Key: { id: artifactId },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.Item) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadata = response.Item as unknown as ArtifactMetadata;
|
||||||
|
|
||||||
|
// 期限切れチェック
|
||||||
|
if (Date.now() > metadata.expiresAt * 1000) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return generatePresignedUrl(metadata.s3Key, expires);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成果物を取得
|
||||||
|
*
|
||||||
|
* @param artifactId - 成果物ID
|
||||||
|
* @returns メタデータ、存在しない場合はnull
|
||||||
|
*/
|
||||||
|
export async function get(artifactId: string): Promise<ArtifactMetadata | null> {
|
||||||
|
const response = await ddbDocClient.send(
|
||||||
|
new GetCommand({
|
||||||
|
TableName: ARTIFACTS_TABLE,
|
||||||
|
Key: { id: artifactId },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.Item) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadata = response.Item as unknown as ArtifactMetadata;
|
||||||
|
|
||||||
|
// 期限切れチェック
|
||||||
|
if (Date.now() > metadata.expiresAt * 1000) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* セッションの成果物を一覧取得
|
||||||
|
*
|
||||||
|
* @param sessionId - セッションID
|
||||||
|
* @returns 成果物メタデータ一覧
|
||||||
|
*/
|
||||||
|
export async function listBySession(sessionId: string): Promise<ArtifactMetadata[]> {
|
||||||
|
const response = await ddbDocClient.send(
|
||||||
|
new QueryCommand({
|
||||||
|
TableName: ARTIFACTS_TABLE,
|
||||||
|
IndexName: "SessionIndex",
|
||||||
|
KeyConditionExpression: "sessionId = :sessionId",
|
||||||
|
ExpressionAttributeValues: {
|
||||||
|
":sessionId": sessionId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const items = (response.Items as unknown as ArtifactMetadata[]) || [];
|
||||||
|
|
||||||
|
// 期限切れを除外
|
||||||
|
return items.filter((item) => Date.now() <= item.expiresAt * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ユーザーの成果物を一覧取得
|
||||||
|
*
|
||||||
|
* @param userId - ユーザーID
|
||||||
|
* @returns 成果物メタデータ一覧
|
||||||
|
*/
|
||||||
|
export async function listByUser(userId: string): Promise<ArtifactMetadata[]> {
|
||||||
|
const response = await ddbDocClient.send(
|
||||||
|
new QueryCommand({
|
||||||
|
TableName: ARTIFACTS_TABLE,
|
||||||
|
IndexName: "UserIndex",
|
||||||
|
KeyConditionExpression: "userId = :userId",
|
||||||
|
ExpressionAttributeValues: {
|
||||||
|
":userId": userId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const items = (response.Items as unknown as ArtifactMetadata[]) || [];
|
||||||
|
|
||||||
|
// 期限切れを除外
|
||||||
|
return items.filter((item) => Date.now() <= item.expiresAt * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成果物を削除
|
||||||
|
*
|
||||||
|
* @param artifactId - 成果物ID
|
||||||
|
*/
|
||||||
|
export async function deleteArtifact(artifactId: string): Promise<void> {
|
||||||
|
// メタデータ取得
|
||||||
|
const metadata = await get(artifactId);
|
||||||
|
if (!metadata) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// S3から削除
|
||||||
|
await s3Client.send(
|
||||||
|
new DeleteObjectCommand({
|
||||||
|
Bucket: metadata.s3Bucket,
|
||||||
|
Key: metadata.s3Key,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// DynamoDBから削除
|
||||||
|
await ddbDocClient.send(
|
||||||
|
new DeleteCommand({
|
||||||
|
TableName: ARTIFACTS_TABLE,
|
||||||
|
Key: { id: artifactId },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* セッションの成果物を全削除
|
||||||
|
*
|
||||||
|
* @param sessionId - セッションID
|
||||||
|
*/
|
||||||
|
export async function deleteBySession(sessionId: string): Promise<number> {
|
||||||
|
const artifacts = await listBySession(sessionId);
|
||||||
|
|
||||||
|
for (const artifact of artifacts) {
|
||||||
|
await deleteArtifact(artifact.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return artifacts.length;
|
||||||
|
}
|
||||||
103
src/artifacts/types.ts
Normal file
103
src/artifacts/types.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* 成果物管理の型定義
|
||||||
|
*
|
||||||
|
* S3 + DynamoDBでθサイクルの成果物を保存・配信する
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成果物の種類
|
||||||
|
*/
|
||||||
|
export enum ArtifactType {
|
||||||
|
/** ファイル (汎用) */
|
||||||
|
FILE = "file",
|
||||||
|
/** コードスニペット */
|
||||||
|
CODE = "code",
|
||||||
|
/** レポート */
|
||||||
|
REPORT = "report",
|
||||||
|
/** 画像 */
|
||||||
|
IMAGE = "image",
|
||||||
|
/** ログ */
|
||||||
|
LOG = "log",
|
||||||
|
/** JSONデータ */
|
||||||
|
JSON = "json",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成果物メタデータ
|
||||||
|
*/
|
||||||
|
export interface ArtifactMetadata {
|
||||||
|
/** 成果物ID */
|
||||||
|
id: string;
|
||||||
|
/** セッションID (θサイクル) */
|
||||||
|
sessionId?: string;
|
||||||
|
/** ユーザーID */
|
||||||
|
userId?: string;
|
||||||
|
/** 種類 */
|
||||||
|
type: ArtifactType;
|
||||||
|
/** ファイル名 */
|
||||||
|
filename: string;
|
||||||
|
/** MIMEタイプ */
|
||||||
|
mimeType: string;
|
||||||
|
/** サイズ (bytes) */
|
||||||
|
size: number;
|
||||||
|
/** S3キー */
|
||||||
|
s3Key: string;
|
||||||
|
/** S3バケット */
|
||||||
|
s3Bucket: string;
|
||||||
|
/** 作成時刻 */
|
||||||
|
createdAt: number;
|
||||||
|
/** 有効期限 (Unix timestamp) */
|
||||||
|
expiresAt: number;
|
||||||
|
/** タグ */
|
||||||
|
tags?: string[];
|
||||||
|
/** 説明 */
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成果物
|
||||||
|
*/
|
||||||
|
export interface Artifact {
|
||||||
|
/** メタデータ */
|
||||||
|
metadata: ArtifactMetadata;
|
||||||
|
/** バイナリデータ (保存時のみ使用) */
|
||||||
|
buffer?: Buffer;
|
||||||
|
/** テキストデータ (保存時のみ使用) */
|
||||||
|
text?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存オプション
|
||||||
|
*/
|
||||||
|
export interface SaveArtifactOptions {
|
||||||
|
/** TTL (秒), デフォルト: 24時間 */
|
||||||
|
ttl?: number;
|
||||||
|
/** タグ */
|
||||||
|
tags?: string[];
|
||||||
|
/** 説明 */
|
||||||
|
description?: string;
|
||||||
|
/** 公開設定 */
|
||||||
|
public?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ダウンロードURLオプション
|
||||||
|
*/
|
||||||
|
export interface DownloadUrlOptions {
|
||||||
|
/** 有効期限 (秒), デフォルト: 1時間 */
|
||||||
|
expires?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成果物検索フィルタ
|
||||||
|
*/
|
||||||
|
export interface ArtifactFilter {
|
||||||
|
/** セッションID */
|
||||||
|
sessionId?: string;
|
||||||
|
/** ユーザーID */
|
||||||
|
userId?: string;
|
||||||
|
/** 種類 */
|
||||||
|
type?: ArtifactType;
|
||||||
|
/** タグ */
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user