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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +113 -0
  3. package/dist/cli/cli.d.ts +61 -0
  4. package/dist/cli/cli.js +131 -0
  5. package/dist/cli/index.d.ts +5 -0
  6. package/dist/cli/index.js +4 -0
  7. package/dist/index.d.ts +8 -0
  8. package/dist/index.js +13 -0
  9. package/dist/ports/file/file-port.d.ts +89 -0
  10. package/dist/ports/file/file-port.js +155 -0
  11. package/dist/ports/file/index.d.ts +1 -0
  12. package/dist/ports/file/index.js +1 -0
  13. package/dist/ports/http/http-port.d.ts +107 -0
  14. package/dist/ports/http/http-port.js +238 -0
  15. package/dist/ports/http/index.d.ts +1 -0
  16. package/dist/ports/http/index.js +1 -0
  17. package/dist/services/auth/auth-service.d.ts +79 -0
  18. package/dist/services/auth/auth-service.js +158 -0
  19. package/dist/services/auth/index.d.ts +1 -0
  20. package/dist/services/auth/index.js +1 -0
  21. package/dist/services/confluence/confluence-service.d.ts +152 -0
  22. package/dist/services/confluence/confluence-service.js +510 -0
  23. package/dist/services/confluence/index.d.ts +1 -0
  24. package/dist/services/confluence/index.js +1 -0
  25. package/dist/services/diff/diff-service.d.ts +84 -0
  26. package/dist/services/diff/diff-service.js +881 -0
  27. package/dist/services/diff/index.d.ts +1 -0
  28. package/dist/services/diff/index.js +1 -0
  29. package/dist/services/fetch/fetch-service.d.ts +112 -0
  30. package/dist/services/fetch/fetch-service.js +302 -0
  31. package/dist/services/fetch/index.d.ts +1 -0
  32. package/dist/services/fetch/index.js +1 -0
  33. package/dist/services/jira/index.d.ts +1 -0
  34. package/dist/services/jira/index.js +1 -0
  35. package/dist/services/jira/jira-service.d.ts +100 -0
  36. package/dist/services/jira/jira-service.js +354 -0
  37. package/dist/services/output/index.d.ts +4 -0
  38. package/dist/services/output/index.js +4 -0
  39. package/dist/services/output/output-service.d.ts +67 -0
  40. package/dist/services/output/output-service.js +228 -0
  41. package/dist/services/storage/index.d.ts +6 -0
  42. package/dist/services/storage/index.js +6 -0
  43. package/dist/services/storage/storage-service.d.ts +77 -0
  44. package/dist/services/storage/storage-service.js +738 -0
  45. package/dist/services/text-converter/index.d.ts +1 -0
  46. package/dist/services/text-converter/index.js +1 -0
  47. package/dist/services/text-converter/text-converter.d.ts +35 -0
  48. package/dist/services/text-converter/text-converter.js +681 -0
  49. package/dist/services/url-parser/index.d.ts +1 -0
  50. package/dist/services/url-parser/index.js +1 -0
  51. package/dist/services/url-parser/url-parser.d.ts +43 -0
  52. package/dist/services/url-parser/url-parser.js +283 -0
  53. package/dist/types/auth.d.ts +25 -0
  54. package/dist/types/auth.js +1 -0
  55. package/dist/types/confluence.d.ts +68 -0
  56. package/dist/types/confluence.js +1 -0
  57. package/dist/types/diff.d.ts +77 -0
  58. package/dist/types/diff.js +7 -0
  59. package/dist/types/fetch.d.ts +65 -0
  60. package/dist/types/fetch.js +1 -0
  61. package/dist/types/file.d.ts +22 -0
  62. package/dist/types/file.js +1 -0
  63. package/dist/types/http.d.ts +45 -0
  64. package/dist/types/http.js +1 -0
  65. package/dist/types/jira.d.ts +90 -0
  66. package/dist/types/jira.js +1 -0
  67. package/dist/types/output.d.ts +55 -0
  68. package/dist/types/output.js +7 -0
  69. package/dist/types/result.d.ts +104 -0
  70. package/dist/types/result.js +119 -0
  71. package/dist/types/storage.d.ts +209 -0
  72. package/dist/types/storage.js +6 -0
  73. package/dist/types/url-parser.d.ts +46 -0
  74. package/dist/types/url-parser.js +1 -0
  75. 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';