atl-fetch 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/cli/cli.d.ts +61 -0
- package/dist/cli/cli.js +131 -0
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +4 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +13 -0
- package/dist/ports/file/file-port.d.ts +89 -0
- package/dist/ports/file/file-port.js +155 -0
- package/dist/ports/file/index.d.ts +1 -0
- package/dist/ports/file/index.js +1 -0
- package/dist/ports/http/http-port.d.ts +107 -0
- package/dist/ports/http/http-port.js +238 -0
- package/dist/ports/http/index.d.ts +1 -0
- package/dist/ports/http/index.js +1 -0
- package/dist/services/auth/auth-service.d.ts +79 -0
- package/dist/services/auth/auth-service.js +158 -0
- package/dist/services/auth/index.d.ts +1 -0
- package/dist/services/auth/index.js +1 -0
- package/dist/services/confluence/confluence-service.d.ts +152 -0
- package/dist/services/confluence/confluence-service.js +510 -0
- package/dist/services/confluence/index.d.ts +1 -0
- package/dist/services/confluence/index.js +1 -0
- package/dist/services/diff/diff-service.d.ts +84 -0
- package/dist/services/diff/diff-service.js +881 -0
- package/dist/services/diff/index.d.ts +1 -0
- package/dist/services/diff/index.js +1 -0
- package/dist/services/fetch/fetch-service.d.ts +112 -0
- package/dist/services/fetch/fetch-service.js +302 -0
- package/dist/services/fetch/index.d.ts +1 -0
- package/dist/services/fetch/index.js +1 -0
- package/dist/services/jira/index.d.ts +1 -0
- package/dist/services/jira/index.js +1 -0
- package/dist/services/jira/jira-service.d.ts +100 -0
- package/dist/services/jira/jira-service.js +354 -0
- package/dist/services/output/index.d.ts +4 -0
- package/dist/services/output/index.js +4 -0
- package/dist/services/output/output-service.d.ts +67 -0
- package/dist/services/output/output-service.js +228 -0
- package/dist/services/storage/index.d.ts +6 -0
- package/dist/services/storage/index.js +6 -0
- package/dist/services/storage/storage-service.d.ts +77 -0
- package/dist/services/storage/storage-service.js +738 -0
- package/dist/services/text-converter/index.d.ts +1 -0
- package/dist/services/text-converter/index.js +1 -0
- package/dist/services/text-converter/text-converter.d.ts +35 -0
- package/dist/services/text-converter/text-converter.js +681 -0
- package/dist/services/url-parser/index.d.ts +1 -0
- package/dist/services/url-parser/index.js +1 -0
- package/dist/services/url-parser/url-parser.d.ts +43 -0
- package/dist/services/url-parser/url-parser.js +283 -0
- package/dist/types/auth.d.ts +25 -0
- package/dist/types/auth.js +1 -0
- package/dist/types/confluence.d.ts +68 -0
- package/dist/types/confluence.js +1 -0
- package/dist/types/diff.d.ts +77 -0
- package/dist/types/diff.js +7 -0
- package/dist/types/fetch.d.ts +65 -0
- package/dist/types/fetch.js +1 -0
- package/dist/types/file.d.ts +22 -0
- package/dist/types/file.js +1 -0
- package/dist/types/http.d.ts +45 -0
- package/dist/types/http.js +1 -0
- package/dist/types/jira.d.ts +90 -0
- package/dist/types/jira.js +1 -0
- package/dist/types/output.d.ts +55 -0
- package/dist/types/output.js +7 -0
- package/dist/types/result.d.ts +104 -0
- package/dist/types/result.js +119 -0
- package/dist/types/storage.d.ts +209 -0
- package/dist/types/storage.js +6 -0
- package/dist/types/url-parser.d.ts +46 -0
- package/dist/types/url-parser.js +1 -0
- package/package.json +106 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { type Result } from 'neverthrow';
|
|
2
|
+
import type { ConfluenceAttachment, ConfluenceError, ConfluencePage, ConfluenceVersion } from '../../types/confluence.js';
|
|
3
|
+
/**
|
|
4
|
+
* Confluence ページを取得する
|
|
5
|
+
*
|
|
6
|
+
* Confluence Cloud API を使用してページの基本情報(タイトル、本文、バージョン)を取得する。
|
|
7
|
+
*
|
|
8
|
+
* @param organization - 組織名(.atlassian.net のサブドメイン)
|
|
9
|
+
* @param pageId - ページ ID
|
|
10
|
+
* @returns 成功時は {@link ConfluencePage} を含む Ok、失敗時は {@link ConfluenceError} を含む Err
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // 基本的な使用例
|
|
15
|
+
* const result = await fetchConfluencePage('my-company', '123456');
|
|
16
|
+
* if (result.isOk()) {
|
|
17
|
+
* console.log(result.value.title);
|
|
18
|
+
* console.log(result.value.body);
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* // エラーハンドリング
|
|
25
|
+
* const result = await fetchConfluencePage('my-company', 'invalid-id');
|
|
26
|
+
* if (result.isErr()) {
|
|
27
|
+
* if (result.error.kind === 'NOT_FOUND') {
|
|
28
|
+
* console.log('ページが見つかりません');
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function fetchConfluencePage(organization: string, pageId: string): Promise<Result<ConfluencePage, ConfluenceError>>;
|
|
34
|
+
/**
|
|
35
|
+
* Confluence ページのバージョン一覧を取得する
|
|
36
|
+
*
|
|
37
|
+
* Confluence Cloud API を使用してページのバージョン履歴を取得する。
|
|
38
|
+
*
|
|
39
|
+
* @param organization - 組織名(.atlassian.net のサブドメイン)
|
|
40
|
+
* @param pageId - ページ ID
|
|
41
|
+
* @returns 成功時は {@link ConfluenceVersion} の配列を含む Ok、失敗時は {@link ConfluenceError} を含む Err
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* // 基本的な使用例
|
|
46
|
+
* const result = await fetchConfluenceVersions('my-company', '123456');
|
|
47
|
+
* if (result.isOk()) {
|
|
48
|
+
* for (const version of result.value) {
|
|
49
|
+
* console.log(`v${version.number}: ${version.by} - ${version.when}`);
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* // エラーハンドリング
|
|
57
|
+
* const result = await fetchConfluenceVersions('my-company', 'invalid-id');
|
|
58
|
+
* if (result.isErr()) {
|
|
59
|
+
* if (result.error.kind === 'NOT_FOUND') {
|
|
60
|
+
* console.log('ページが見つかりません');
|
|
61
|
+
* }
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function fetchConfluenceVersions(organization: string, pageId: string): Promise<Result<readonly ConfluenceVersion[], ConfluenceError>>;
|
|
66
|
+
/**
|
|
67
|
+
* Confluence ページの特定バージョンの本文を取得する
|
|
68
|
+
*
|
|
69
|
+
* Confluence Cloud API を使用して特定バージョンのページ本文を取得する。
|
|
70
|
+
*
|
|
71
|
+
* @param organization - 組織名(.atlassian.net のサブドメイン)
|
|
72
|
+
* @param pageId - ページ ID
|
|
73
|
+
* @param versionNumber - バージョン番号
|
|
74
|
+
* @returns 成功時は本文(HTML 形式)を含む Ok、失敗時は {@link ConfluenceError} を含む Err
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* // 基本的な使用例
|
|
79
|
+
* const result = await fetchConfluenceVersionContent('my-company', '123456', 2);
|
|
80
|
+
* if (result.isOk()) {
|
|
81
|
+
* console.log(result.value); // バージョン 2 の本文
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export declare function fetchConfluenceVersionContent(organization: string, pageId: string, versionNumber: number): Promise<Result<string, ConfluenceError>>;
|
|
86
|
+
/**
|
|
87
|
+
* Confluence ページの添付ファイル一覧を取得する
|
|
88
|
+
*
|
|
89
|
+
* Confluence Cloud API を使用してページの添付ファイル一覧を取得する。
|
|
90
|
+
*
|
|
91
|
+
* @param organization - 組織名(.atlassian.net のサブドメイン)
|
|
92
|
+
* @param pageId - ページ ID
|
|
93
|
+
* @returns 成功時は {@link ConfluenceAttachment} の配列を含む Ok、失敗時は {@link ConfluenceError} を含む Err
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* // 基本的な使用例
|
|
98
|
+
* const result = await fetchConfluenceAttachments('my-company', '123456');
|
|
99
|
+
* if (result.isOk()) {
|
|
100
|
+
* for (const attachment of result.value) {
|
|
101
|
+
* console.log(`${attachment.title}: ${attachment.fileSize} bytes`);
|
|
102
|
+
* }
|
|
103
|
+
* }
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* // エラーハンドリング
|
|
109
|
+
* const result = await fetchConfluenceAttachments('my-company', 'invalid-id');
|
|
110
|
+
* if (result.isErr()) {
|
|
111
|
+
* if (result.error.kind === 'NOT_FOUND') {
|
|
112
|
+
* console.log('ページが見つかりません');
|
|
113
|
+
* }
|
|
114
|
+
* }
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export declare function fetchConfluenceAttachments(organization: string, pageId: string): Promise<Result<readonly ConfluenceAttachment[], ConfluenceError>>;
|
|
118
|
+
/**
|
|
119
|
+
* Confluence 添付ファイルをダウンロードする
|
|
120
|
+
*
|
|
121
|
+
* Confluence Cloud API を使用して添付ファイルを指定パスにダウンロードする。
|
|
122
|
+
*
|
|
123
|
+
* @param attachment - ダウンロード対象の添付ファイル情報
|
|
124
|
+
* @param destPath - 保存先ファイルパス
|
|
125
|
+
* @param onProgress - 進捗コールバック関数(オプション)
|
|
126
|
+
* @returns 成功時は void を含む Ok、失敗時は {@link ConfluenceError} を含む Err
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* // 基本的な使用例
|
|
131
|
+
* const attachment = page.attachments[0];
|
|
132
|
+
* const result = await downloadConfluenceAttachment(attachment, '/path/to/save/file.png');
|
|
133
|
+
* if (result.isOk()) {
|
|
134
|
+
* console.log('ダウンロード完了');
|
|
135
|
+
* }
|
|
136
|
+
* ```
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* // 進捗表示付き
|
|
141
|
+
* const result = await downloadConfluenceAttachment(
|
|
142
|
+
* attachment,
|
|
143
|
+
* '/path/to/save/file.png',
|
|
144
|
+
* (transferred, total) => {
|
|
145
|
+
* if (total) {
|
|
146
|
+
* console.log(`Progress: ${(transferred / total * 100).toFixed(1)}%`);
|
|
147
|
+
* }
|
|
148
|
+
* }
|
|
149
|
+
* );
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export declare function downloadConfluenceAttachment(attachment: ConfluenceAttachment, destPath: string, onProgress?: (transferred: number, total: number | undefined) => void): Promise<Result<void, ConfluenceError>>;
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import { err, ok } from 'neverthrow';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { httpDownload, httpRequest } from '../../ports/http/http-port.js';
|
|
4
|
+
import { getAuthHeader } from '../auth/auth-service.js';
|
|
5
|
+
/**
|
|
6
|
+
* Confluence Cloud API のベース URL を構築する
|
|
7
|
+
*
|
|
8
|
+
* @param organization - 組織名(.atlassian.net のサブドメイン)
|
|
9
|
+
* @returns Confluence Cloud API のベース URL
|
|
10
|
+
*/
|
|
11
|
+
function getConfluenceApiBaseUrl(organization) {
|
|
12
|
+
return `https://${organization}.atlassian.net/wiki/rest/api`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Confluence API レスポンスのスキーマ
|
|
16
|
+
*
|
|
17
|
+
* Confluence Cloud REST API v1 のレスポンス形式に対応
|
|
18
|
+
*/
|
|
19
|
+
const confluenceApiResponseSchema = z.object({
|
|
20
|
+
body: z.object({
|
|
21
|
+
storage: z.object({
|
|
22
|
+
representation: z.string(),
|
|
23
|
+
value: z.string(),
|
|
24
|
+
}),
|
|
25
|
+
}),
|
|
26
|
+
id: z.string(),
|
|
27
|
+
space: z.object({
|
|
28
|
+
key: z.string(),
|
|
29
|
+
}),
|
|
30
|
+
title: z.string(),
|
|
31
|
+
version: z.object({
|
|
32
|
+
number: z.number(),
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* HTTP エラーを ConfluenceError に変換する
|
|
37
|
+
*
|
|
38
|
+
* @param status - HTTP ステータスコード
|
|
39
|
+
* @param message - エラーメッセージ
|
|
40
|
+
* @returns ConfluenceError
|
|
41
|
+
*/
|
|
42
|
+
function mapHttpStatusToConfluenceError(status, message) {
|
|
43
|
+
switch (status) {
|
|
44
|
+
case 401:
|
|
45
|
+
return {
|
|
46
|
+
kind: 'AUTH_FAILED',
|
|
47
|
+
message: '認証に失敗しました。API トークンとメールアドレスを確認してください。',
|
|
48
|
+
};
|
|
49
|
+
case 403:
|
|
50
|
+
return {
|
|
51
|
+
kind: 'FORBIDDEN',
|
|
52
|
+
message: 'このページへのアクセス権限がありません。権限を確認してください。',
|
|
53
|
+
};
|
|
54
|
+
case 404:
|
|
55
|
+
return {
|
|
56
|
+
kind: 'NOT_FOUND',
|
|
57
|
+
message: '指定されたページが見つかりません。ページ ID を確認してください。',
|
|
58
|
+
};
|
|
59
|
+
default:
|
|
60
|
+
return {
|
|
61
|
+
kind: 'NETWORK_ERROR',
|
|
62
|
+
message: `API リクエストに失敗しました: ${message}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Confluence ページを取得する
|
|
68
|
+
*
|
|
69
|
+
* Confluence Cloud API を使用してページの基本情報(タイトル、本文、バージョン)を取得する。
|
|
70
|
+
*
|
|
71
|
+
* @param organization - 組織名(.atlassian.net のサブドメイン)
|
|
72
|
+
* @param pageId - ページ ID
|
|
73
|
+
* @returns 成功時は {@link ConfluencePage} を含む Ok、失敗時は {@link ConfluenceError} を含む Err
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* // 基本的な使用例
|
|
78
|
+
* const result = await fetchConfluencePage('my-company', '123456');
|
|
79
|
+
* if (result.isOk()) {
|
|
80
|
+
* console.log(result.value.title);
|
|
81
|
+
* console.log(result.value.body);
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* // エラーハンドリング
|
|
88
|
+
* const result = await fetchConfluencePage('my-company', 'invalid-id');
|
|
89
|
+
* if (result.isErr()) {
|
|
90
|
+
* if (result.error.kind === 'NOT_FOUND') {
|
|
91
|
+
* console.log('ページが見つかりません');
|
|
92
|
+
* }
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export async function fetchConfluencePage(organization, pageId) {
|
|
97
|
+
// 認証ヘッダーを取得
|
|
98
|
+
const authHeaderResult = getAuthHeader();
|
|
99
|
+
if (authHeaderResult.isErr()) {
|
|
100
|
+
return err({
|
|
101
|
+
kind: 'AUTH_FAILED',
|
|
102
|
+
message: authHeaderResult.error.message,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
const authHeader = authHeaderResult.value;
|
|
106
|
+
const baseUrl = getConfluenceApiBaseUrl(organization);
|
|
107
|
+
const url = `${baseUrl}/content/${pageId}?expand=body.storage,version,space`;
|
|
108
|
+
// API リクエストを実行
|
|
109
|
+
const response = await httpRequest(url, {
|
|
110
|
+
headers: {
|
|
111
|
+
Accept: 'application/json',
|
|
112
|
+
Authorization: authHeader,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
if (response.isErr()) {
|
|
116
|
+
const error = response.error;
|
|
117
|
+
if (error.kind === 'HTTP_ERROR') {
|
|
118
|
+
return err(mapHttpStatusToConfluenceError(error.status, error.message));
|
|
119
|
+
}
|
|
120
|
+
return err({
|
|
121
|
+
kind: 'NETWORK_ERROR',
|
|
122
|
+
message: error.message,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// レスポンスをパース
|
|
126
|
+
const parseResult = confluenceApiResponseSchema.safeParse(response.value.data);
|
|
127
|
+
if (!parseResult.success) {
|
|
128
|
+
return err({
|
|
129
|
+
kind: 'PARSE_ERROR',
|
|
130
|
+
message: `API レスポンスのパースに失敗しました: ${parseResult.error.message}`,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const apiResponse = parseResult.data;
|
|
134
|
+
// バージョン一覧を取得(本文も含める)
|
|
135
|
+
const versionsResult = await fetchConfluenceVersions(organization, pageId);
|
|
136
|
+
let versions = [];
|
|
137
|
+
if (versionsResult.isOk()) {
|
|
138
|
+
// 各バージョンの本文を取得
|
|
139
|
+
const versionsWithBody = await Promise.all(versionsResult.value.map(async (v) => {
|
|
140
|
+
const bodyResult = await fetchConfluenceVersionContent(organization, pageId, v.number);
|
|
141
|
+
if (bodyResult.isOk()) {
|
|
142
|
+
return { ...v, body: bodyResult.value };
|
|
143
|
+
}
|
|
144
|
+
return v;
|
|
145
|
+
}));
|
|
146
|
+
versions = versionsWithBody;
|
|
147
|
+
}
|
|
148
|
+
// 添付ファイル一覧を取得
|
|
149
|
+
const attachmentsResult = await fetchConfluenceAttachments(organization, pageId);
|
|
150
|
+
const attachments = attachmentsResult.isOk() ? attachmentsResult.value : [];
|
|
151
|
+
// ConfluencePage に変換
|
|
152
|
+
const page = {
|
|
153
|
+
attachments,
|
|
154
|
+
body: apiResponse.body.storage.value,
|
|
155
|
+
currentVersion: apiResponse.version.number,
|
|
156
|
+
id: apiResponse.id,
|
|
157
|
+
spaceKey: apiResponse.space.key,
|
|
158
|
+
title: apiResponse.title,
|
|
159
|
+
versions,
|
|
160
|
+
};
|
|
161
|
+
return ok(page);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Confluence バージョン一覧 API レスポンスのスキーマ
|
|
165
|
+
*
|
|
166
|
+
* Confluence Cloud REST API v1 の /content/{id}/version 形式に対応
|
|
167
|
+
*/
|
|
168
|
+
const confluenceVersionsResponseSchema = z.object({
|
|
169
|
+
results: z.array(z.object({
|
|
170
|
+
by: z.object({
|
|
171
|
+
displayName: z.string(),
|
|
172
|
+
}),
|
|
173
|
+
message: z.string().nullable(),
|
|
174
|
+
number: z.number(),
|
|
175
|
+
when: z.string(),
|
|
176
|
+
})),
|
|
177
|
+
});
|
|
178
|
+
/**
|
|
179
|
+
* Confluence ページのバージョン一覧を取得する
|
|
180
|
+
*
|
|
181
|
+
* Confluence Cloud API を使用してページのバージョン履歴を取得する。
|
|
182
|
+
*
|
|
183
|
+
* @param organization - 組織名(.atlassian.net のサブドメイン)
|
|
184
|
+
* @param pageId - ページ ID
|
|
185
|
+
* @returns 成功時は {@link ConfluenceVersion} の配列を含む Ok、失敗時は {@link ConfluenceError} を含む Err
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```typescript
|
|
189
|
+
* // 基本的な使用例
|
|
190
|
+
* const result = await fetchConfluenceVersions('my-company', '123456');
|
|
191
|
+
* if (result.isOk()) {
|
|
192
|
+
* for (const version of result.value) {
|
|
193
|
+
* console.log(`v${version.number}: ${version.by} - ${version.when}`);
|
|
194
|
+
* }
|
|
195
|
+
* }
|
|
196
|
+
* ```
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```typescript
|
|
200
|
+
* // エラーハンドリング
|
|
201
|
+
* const result = await fetchConfluenceVersions('my-company', 'invalid-id');
|
|
202
|
+
* if (result.isErr()) {
|
|
203
|
+
* if (result.error.kind === 'NOT_FOUND') {
|
|
204
|
+
* console.log('ページが見つかりません');
|
|
205
|
+
* }
|
|
206
|
+
* }
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
export async function fetchConfluenceVersions(organization, pageId) {
|
|
210
|
+
// 認証ヘッダーを取得
|
|
211
|
+
const authHeaderResult = getAuthHeader();
|
|
212
|
+
if (authHeaderResult.isErr()) {
|
|
213
|
+
return err({
|
|
214
|
+
kind: 'AUTH_FAILED',
|
|
215
|
+
message: authHeaderResult.error.message,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
const authHeader = authHeaderResult.value;
|
|
219
|
+
const baseUrl = getConfluenceApiBaseUrl(organization);
|
|
220
|
+
const url = `${baseUrl}/content/${pageId}/version`;
|
|
221
|
+
// API リクエストを実行
|
|
222
|
+
const response = await httpRequest(url, {
|
|
223
|
+
headers: {
|
|
224
|
+
Accept: 'application/json',
|
|
225
|
+
Authorization: authHeader,
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
if (response.isErr()) {
|
|
229
|
+
const error = response.error;
|
|
230
|
+
if (error.kind === 'HTTP_ERROR') {
|
|
231
|
+
return err(mapHttpStatusToConfluenceError(error.status, error.message));
|
|
232
|
+
}
|
|
233
|
+
return err({
|
|
234
|
+
kind: 'NETWORK_ERROR',
|
|
235
|
+
message: error.message,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
// レスポンスをパース
|
|
239
|
+
const parseResult = confluenceVersionsResponseSchema.safeParse(response.value.data);
|
|
240
|
+
if (!parseResult.success) {
|
|
241
|
+
return err({
|
|
242
|
+
kind: 'PARSE_ERROR',
|
|
243
|
+
message: `API レスポンスのパースに失敗しました: ${parseResult.error.message}`,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
const apiResponse = parseResult.data;
|
|
247
|
+
// ConfluenceVersion の配列に変換
|
|
248
|
+
const versions = apiResponse.results.map((v) => ({
|
|
249
|
+
by: v.by.displayName,
|
|
250
|
+
message: v.message,
|
|
251
|
+
number: v.number,
|
|
252
|
+
when: v.when,
|
|
253
|
+
}));
|
|
254
|
+
return ok(versions);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Confluence ページの特定バージョンの本文を取得する
|
|
258
|
+
*
|
|
259
|
+
* Confluence Cloud API を使用して特定バージョンのページ本文を取得する。
|
|
260
|
+
*
|
|
261
|
+
* @param organization - 組織名(.atlassian.net のサブドメイン)
|
|
262
|
+
* @param pageId - ページ ID
|
|
263
|
+
* @param versionNumber - バージョン番号
|
|
264
|
+
* @returns 成功時は本文(HTML 形式)を含む Ok、失敗時は {@link ConfluenceError} を含む Err
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```typescript
|
|
268
|
+
* // 基本的な使用例
|
|
269
|
+
* const result = await fetchConfluenceVersionContent('my-company', '123456', 2);
|
|
270
|
+
* if (result.isOk()) {
|
|
271
|
+
* console.log(result.value); // バージョン 2 の本文
|
|
272
|
+
* }
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
export async function fetchConfluenceVersionContent(organization, pageId, versionNumber) {
|
|
276
|
+
// 認証ヘッダーを取得
|
|
277
|
+
const authHeaderResult = getAuthHeader();
|
|
278
|
+
if (authHeaderResult.isErr()) {
|
|
279
|
+
return err({
|
|
280
|
+
kind: 'AUTH_FAILED',
|
|
281
|
+
message: authHeaderResult.error.message,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
const authHeader = authHeaderResult.value;
|
|
285
|
+
const baseUrl = getConfluenceApiBaseUrl(organization);
|
|
286
|
+
const url = `${baseUrl}/content/${pageId}?status=historical&version=${versionNumber}&expand=body.storage`;
|
|
287
|
+
// API リクエストを実行
|
|
288
|
+
const response = await httpRequest(url, {
|
|
289
|
+
headers: {
|
|
290
|
+
Accept: 'application/json',
|
|
291
|
+
Authorization: authHeader,
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
if (response.isErr()) {
|
|
295
|
+
const error = response.error;
|
|
296
|
+
if (error.kind === 'HTTP_ERROR') {
|
|
297
|
+
return err(mapHttpStatusToConfluenceError(error.status, error.message));
|
|
298
|
+
}
|
|
299
|
+
return err({
|
|
300
|
+
kind: 'NETWORK_ERROR',
|
|
301
|
+
message: error.message,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
// レスポンスをパース(本文のみ取得するため簡易スキーマを使用)
|
|
305
|
+
const versionContentSchema = z.object({
|
|
306
|
+
body: z.object({
|
|
307
|
+
storage: z.object({
|
|
308
|
+
value: z.string(),
|
|
309
|
+
}),
|
|
310
|
+
}),
|
|
311
|
+
});
|
|
312
|
+
const parseResult = versionContentSchema.safeParse(response.value.data);
|
|
313
|
+
if (!parseResult.success) {
|
|
314
|
+
return err({
|
|
315
|
+
kind: 'PARSE_ERROR',
|
|
316
|
+
message: `API レスポンスのパースに失敗しました: ${parseResult.error.message}`,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
return ok(parseResult.data.body.storage.value);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Confluence 添付ファイル一覧 API レスポンスのスキーマ
|
|
323
|
+
*
|
|
324
|
+
* Confluence Cloud REST API v1 の /content/{id}/child/attachment 形式に対応
|
|
325
|
+
*/
|
|
326
|
+
const confluenceAttachmentsResponseSchema = z.object({
|
|
327
|
+
results: z.array(z.object({
|
|
328
|
+
_links: z.object({
|
|
329
|
+
download: z.string(),
|
|
330
|
+
}),
|
|
331
|
+
extensions: z.object({
|
|
332
|
+
fileSize: z.number(),
|
|
333
|
+
mediaType: z.string(),
|
|
334
|
+
}),
|
|
335
|
+
id: z.string(),
|
|
336
|
+
title: z.string(),
|
|
337
|
+
})),
|
|
338
|
+
});
|
|
339
|
+
/**
|
|
340
|
+
* Confluence ページの添付ファイル一覧を取得する
|
|
341
|
+
*
|
|
342
|
+
* Confluence Cloud API を使用してページの添付ファイル一覧を取得する。
|
|
343
|
+
*
|
|
344
|
+
* @param organization - 組織名(.atlassian.net のサブドメイン)
|
|
345
|
+
* @param pageId - ページ ID
|
|
346
|
+
* @returns 成功時は {@link ConfluenceAttachment} の配列を含む Ok、失敗時は {@link ConfluenceError} を含む Err
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* ```typescript
|
|
350
|
+
* // 基本的な使用例
|
|
351
|
+
* const result = await fetchConfluenceAttachments('my-company', '123456');
|
|
352
|
+
* if (result.isOk()) {
|
|
353
|
+
* for (const attachment of result.value) {
|
|
354
|
+
* console.log(`${attachment.title}: ${attachment.fileSize} bytes`);
|
|
355
|
+
* }
|
|
356
|
+
* }
|
|
357
|
+
* ```
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* ```typescript
|
|
361
|
+
* // エラーハンドリング
|
|
362
|
+
* const result = await fetchConfluenceAttachments('my-company', 'invalid-id');
|
|
363
|
+
* if (result.isErr()) {
|
|
364
|
+
* if (result.error.kind === 'NOT_FOUND') {
|
|
365
|
+
* console.log('ページが見つかりません');
|
|
366
|
+
* }
|
|
367
|
+
* }
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
export async function fetchConfluenceAttachments(organization, pageId) {
|
|
371
|
+
// 認証ヘッダーを取得
|
|
372
|
+
const authHeaderResult = getAuthHeader();
|
|
373
|
+
if (authHeaderResult.isErr()) {
|
|
374
|
+
return err({
|
|
375
|
+
kind: 'AUTH_FAILED',
|
|
376
|
+
message: authHeaderResult.error.message,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
const authHeader = authHeaderResult.value;
|
|
380
|
+
const baseUrl = getConfluenceApiBaseUrl(organization);
|
|
381
|
+
const url = `${baseUrl}/content/${pageId}/child/attachment`;
|
|
382
|
+
// API リクエストを実行
|
|
383
|
+
const response = await httpRequest(url, {
|
|
384
|
+
headers: {
|
|
385
|
+
Accept: 'application/json',
|
|
386
|
+
Authorization: authHeader,
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
if (response.isErr()) {
|
|
390
|
+
const error = response.error;
|
|
391
|
+
if (error.kind === 'HTTP_ERROR') {
|
|
392
|
+
return err(mapHttpStatusToConfluenceError(error.status, error.message));
|
|
393
|
+
}
|
|
394
|
+
return err({
|
|
395
|
+
kind: 'NETWORK_ERROR',
|
|
396
|
+
message: error.message,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
// レスポンスをパース
|
|
400
|
+
const parseResult = confluenceAttachmentsResponseSchema.safeParse(response.value.data);
|
|
401
|
+
if (!parseResult.success) {
|
|
402
|
+
return err({
|
|
403
|
+
kind: 'PARSE_ERROR',
|
|
404
|
+
message: `API レスポンスのパースに失敗しました: ${parseResult.error.message}`,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
const apiResponse = parseResult.data;
|
|
408
|
+
// ConfluenceAttachment の配列に変換
|
|
409
|
+
// download URL は相対パスで返されるため、絶対 URL に変換する
|
|
410
|
+
// /wiki プレフィックスが必要(API から返されるパスは /wiki なしの相対パス)
|
|
411
|
+
const attachments = apiResponse.results.map((att) => ({
|
|
412
|
+
downloadUrl: `https://${organization}.atlassian.net/wiki${att._links.download}`,
|
|
413
|
+
fileSize: att.extensions.fileSize,
|
|
414
|
+
id: att.id,
|
|
415
|
+
mediaType: att.extensions.mediaType,
|
|
416
|
+
title: att.title,
|
|
417
|
+
}));
|
|
418
|
+
return ok(attachments);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* HTTP エラーステータスをダウンロード用の ConfluenceError に変換する
|
|
422
|
+
*
|
|
423
|
+
* @param status - HTTP ステータスコード
|
|
424
|
+
* @returns ConfluenceError
|
|
425
|
+
*/
|
|
426
|
+
function mapHttpStatusToDownloadError(status) {
|
|
427
|
+
switch (status) {
|
|
428
|
+
case 401:
|
|
429
|
+
return {
|
|
430
|
+
kind: 'AUTH_FAILED',
|
|
431
|
+
message: '認証に失敗しました。API トークンとメールアドレスを確認してください。',
|
|
432
|
+
};
|
|
433
|
+
case 403:
|
|
434
|
+
return {
|
|
435
|
+
kind: 'FORBIDDEN',
|
|
436
|
+
message: 'この添付ファイルへのアクセス権限がありません。権限を確認してください。',
|
|
437
|
+
};
|
|
438
|
+
case 404:
|
|
439
|
+
return {
|
|
440
|
+
kind: 'NOT_FOUND',
|
|
441
|
+
message: '指定された添付ファイルが見つかりません。',
|
|
442
|
+
};
|
|
443
|
+
default:
|
|
444
|
+
return {
|
|
445
|
+
kind: 'NETWORK_ERROR',
|
|
446
|
+
message: `添付ファイルのダウンロードに失敗しました(HTTP ${status})`,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Confluence 添付ファイルをダウンロードする
|
|
452
|
+
*
|
|
453
|
+
* Confluence Cloud API を使用して添付ファイルを指定パスにダウンロードする。
|
|
454
|
+
*
|
|
455
|
+
* @param attachment - ダウンロード対象の添付ファイル情報
|
|
456
|
+
* @param destPath - 保存先ファイルパス
|
|
457
|
+
* @param onProgress - 進捗コールバック関数(オプション)
|
|
458
|
+
* @returns 成功時は void を含む Ok、失敗時は {@link ConfluenceError} を含む Err
|
|
459
|
+
*
|
|
460
|
+
* @example
|
|
461
|
+
* ```typescript
|
|
462
|
+
* // 基本的な使用例
|
|
463
|
+
* const attachment = page.attachments[0];
|
|
464
|
+
* const result = await downloadConfluenceAttachment(attachment, '/path/to/save/file.png');
|
|
465
|
+
* if (result.isOk()) {
|
|
466
|
+
* console.log('ダウンロード完了');
|
|
467
|
+
* }
|
|
468
|
+
* ```
|
|
469
|
+
*
|
|
470
|
+
* @example
|
|
471
|
+
* ```typescript
|
|
472
|
+
* // 進捗表示付き
|
|
473
|
+
* const result = await downloadConfluenceAttachment(
|
|
474
|
+
* attachment,
|
|
475
|
+
* '/path/to/save/file.png',
|
|
476
|
+
* (transferred, total) => {
|
|
477
|
+
* if (total) {
|
|
478
|
+
* console.log(`Progress: ${(transferred / total * 100).toFixed(1)}%`);
|
|
479
|
+
* }
|
|
480
|
+
* }
|
|
481
|
+
* );
|
|
482
|
+
* ```
|
|
483
|
+
*/
|
|
484
|
+
export async function downloadConfluenceAttachment(attachment, destPath, onProgress) {
|
|
485
|
+
// 認証ヘッダーを取得
|
|
486
|
+
const authHeaderResult = getAuthHeader();
|
|
487
|
+
if (authHeaderResult.isErr()) {
|
|
488
|
+
return err({
|
|
489
|
+
kind: 'AUTH_FAILED',
|
|
490
|
+
message: authHeaderResult.error.message,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
const authHeader = authHeaderResult.value;
|
|
494
|
+
// ファイルをダウンロード
|
|
495
|
+
const downloadResult = await httpDownload(attachment.downloadUrl, destPath, {
|
|
496
|
+
Accept: '*/*',
|
|
497
|
+
Authorization: authHeader,
|
|
498
|
+
}, onProgress);
|
|
499
|
+
if (downloadResult.isErr()) {
|
|
500
|
+
const error = downloadResult.error;
|
|
501
|
+
if (error.kind === 'HTTP_ERROR') {
|
|
502
|
+
return err(mapHttpStatusToDownloadError(error.status));
|
|
503
|
+
}
|
|
504
|
+
return err({
|
|
505
|
+
kind: 'NETWORK_ERROR',
|
|
506
|
+
message: error.message,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
return ok(undefined);
|
|
510
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { downloadConfluenceAttachment, fetchConfluenceAttachments, fetchConfluencePage, fetchConfluenceVersionContent, fetchConfluenceVersions, } from './confluence-service.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { downloadConfluenceAttachment, fetchConfluenceAttachments, fetchConfluencePage, fetchConfluenceVersionContent, fetchConfluenceVersions, } from './confluence-service.js';
|