github-issue-tower-defence-management 1.35.2 → 1.36.1
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/CHANGELOG.md +22 -0
- package/bin/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.js +1 -27
- package/bin/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +1 -27
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/repositories/BaseGitHubRepository.js +3 -7
- package/bin/adapter/repositories/BaseGitHubRepository.js.map +1 -1
- package/bin/adapter/repositories/CheerioProjectRepository.js +11 -11
- package/bin/adapter/repositories/CheerioProjectRepository.js.map +1 -1
- package/bin/adapter/repositories/FetchWebhookRepository.js +5 -1
- package/bin/adapter/repositories/FetchWebhookRepository.js.map +1 -1
- package/bin/adapter/repositories/GraphqlProjectRepository.js +14 -16
- package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -1
- package/bin/adapter/repositories/KySlackRepository.js +212 -0
- package/bin/adapter/repositories/KySlackRepository.js.map +1 -0
- package/bin/adapter/repositories/issue/ApiV3IssueRepository.js +11 -13
- package/bin/adapter/repositories/issue/ApiV3IssueRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/CheerioIssueRepository.js +2 -3
- package/bin/adapter/repositories/issue/CheerioIssueRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js +47 -63
- package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js +12 -8
- package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/RestIssueRepository.js +36 -69
- package/bin/adapter/repositories/issue/RestIssueRepository.js.map +1 -1
- package/bin/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.js +9 -1
- package/bin/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.js.map +1 -1
- package/jest.config.js +2 -1
- package/package.json +2 -3
- package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.test.ts +0 -78
- package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.ts +1 -33
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +0 -78
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +1 -33
- package/src/adapter/repositories/BaseGitHubRepository.test.ts +43 -33
- package/src/adapter/repositories/BaseGitHubRepository.ts +4 -8
- package/src/adapter/repositories/CheerioProjectRepository.ts +19 -23
- package/src/adapter/repositories/FetchWebhookRepository.ts +2 -1
- package/src/adapter/repositories/GraphqlProjectRepository.ts +63 -69
- package/src/adapter/repositories/{AxiosSlackRepository.test.ts → KySlackRepository.test.ts} +4 -4
- package/src/adapter/repositories/KySlackRepository.ts +297 -0
- package/src/adapter/repositories/issue/ApiV3IssueRepository.ts +31 -33
- package/src/adapter/repositories/issue/CheerioIssueRepository.test.ts +3 -3
- package/src/adapter/repositories/issue/CheerioIssueRepository.ts +2 -3
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +30 -22
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +178 -191
- package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.ts +12 -8
- package/src/adapter/repositories/issue/RestIssueRepository.ts +51 -91
- package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.test.ts +20 -0
- package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.ts +10 -1
- package/types/adapter/entry-points/function/getStoryObjectMap.d.ts +1 -1
- package/types/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.d.ts +1 -1
- package/types/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts +1 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/repositories/BaseGitHubRepository.d.ts +1 -1
- package/types/adapter/repositories/BaseGitHubRepository.d.ts.map +1 -1
- package/types/adapter/repositories/CheerioProjectRepository.d.ts.map +1 -1
- package/types/adapter/repositories/FetchWebhookRepository.d.ts.map +1 -1
- package/types/adapter/repositories/GraphqlProjectRepository.d.ts.map +1 -1
- package/types/adapter/repositories/{AxiosSlackRepository.d.ts → KySlackRepository.d.ts} +3 -2
- package/types/adapter/repositories/KySlackRepository.d.ts.map +1 -0
- package/types/adapter/repositories/issue/ApiV3IssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/InternalGraphqlIssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/RestIssueRepository.d.ts.map +1 -1
- package/types/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.d.ts.map +1 -1
- package/types/index.d.ts +1 -1
- package/bin/adapter/repositories/AxiosSlackRepository.js +0 -188
- package/bin/adapter/repositories/AxiosSlackRepository.js.map +0 -1
- package/src/adapter/repositories/AxiosSlackRepository.ts +0 -218
- package/types/adapter/repositories/AxiosSlackRepository.d.ts.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import ky from 'ky';
|
|
2
2
|
import { BaseGitHubRepository } from './BaseGitHubRepository';
|
|
3
3
|
import { ProjectRepository } from '../../domain/usecases/adapter-interfaces/ProjectRepository';
|
|
4
4
|
import { FieldOption, Project } from '../../domain/entities/Project';
|
|
@@ -46,34 +46,33 @@ export class GraphqlProjectRepository
|
|
|
46
46
|
},
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
const response = await
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
const response = await ky
|
|
50
|
+
.post('https://api.github.com/graphql', {
|
|
51
|
+
json: graphqlQuery,
|
|
52
|
+
headers: {
|
|
53
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
.json<{
|
|
57
|
+
data: {
|
|
58
|
+
organization: {
|
|
59
|
+
projectV2: {
|
|
60
|
+
id: string;
|
|
61
|
+
databaseId: number;
|
|
62
|
+
};
|
|
55
63
|
};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
user: {
|
|
65
|
+
projectV2: {
|
|
66
|
+
id: string;
|
|
67
|
+
databaseId: number;
|
|
68
|
+
};
|
|
61
69
|
};
|
|
62
70
|
};
|
|
63
|
-
};
|
|
64
|
-
}>({
|
|
65
|
-
url: 'https://api.github.com/graphql',
|
|
66
|
-
method: 'post',
|
|
67
|
-
headers: {
|
|
68
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
69
|
-
'Content-Type': 'application/json',
|
|
70
|
-
},
|
|
71
|
-
data: JSON.stringify(graphqlQuery),
|
|
72
|
-
});
|
|
71
|
+
}>();
|
|
73
72
|
|
|
74
73
|
const projectId =
|
|
75
|
-
response.data.
|
|
76
|
-
response.data.
|
|
74
|
+
response.data.organization?.projectV2?.id ||
|
|
75
|
+
response.data.user?.projectV2?.id;
|
|
77
76
|
if (!projectId) {
|
|
78
77
|
throw new Error('projectId is not found');
|
|
79
78
|
}
|
|
@@ -142,56 +141,51 @@ export class GraphqlProjectRepository
|
|
|
142
141
|
const variables = {
|
|
143
142
|
projectId: projectId,
|
|
144
143
|
};
|
|
145
|
-
const response = await
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
duration: string;
|
|
168
|
-
title: string;
|
|
169
|
-
}[];
|
|
170
|
-
};
|
|
171
|
-
options: {
|
|
144
|
+
const response = await ky
|
|
145
|
+
.post('https://api.github.com/graphql', {
|
|
146
|
+
json: { query, variables },
|
|
147
|
+
headers: {
|
|
148
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
149
|
+
},
|
|
150
|
+
})
|
|
151
|
+
.json<{
|
|
152
|
+
data: {
|
|
153
|
+
node: {
|
|
154
|
+
id: string;
|
|
155
|
+
databaseId: number;
|
|
156
|
+
title: string;
|
|
157
|
+
shortDescription: string;
|
|
158
|
+
public: boolean;
|
|
159
|
+
closed: boolean;
|
|
160
|
+
createdAt: string;
|
|
161
|
+
updatedAt: string;
|
|
162
|
+
number: number;
|
|
163
|
+
url: string;
|
|
164
|
+
fields: {
|
|
165
|
+
nodes: {
|
|
172
166
|
id: string;
|
|
167
|
+
databaseId: number;
|
|
173
168
|
name: string;
|
|
174
|
-
|
|
175
|
-
|
|
169
|
+
dataType: string;
|
|
170
|
+
configuration: {
|
|
171
|
+
iterations: {
|
|
172
|
+
startDate: string;
|
|
173
|
+
duration: string;
|
|
174
|
+
title: string;
|
|
175
|
+
}[];
|
|
176
|
+
};
|
|
177
|
+
options: {
|
|
178
|
+
id: string;
|
|
179
|
+
name: string;
|
|
180
|
+
description: string;
|
|
181
|
+
color: string;
|
|
182
|
+
}[];
|
|
176
183
|
}[];
|
|
177
|
-
}
|
|
184
|
+
};
|
|
178
185
|
};
|
|
179
186
|
};
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
'https://api.github.com/graphql',
|
|
183
|
-
{
|
|
184
|
-
query,
|
|
185
|
-
variables,
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
headers: {
|
|
189
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
190
|
-
'Content-Type': 'application/json',
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
);
|
|
194
|
-
const project = response.data.data.node;
|
|
187
|
+
}>();
|
|
188
|
+
const project = response.data.node;
|
|
195
189
|
if (!project) {
|
|
196
190
|
return null;
|
|
197
191
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import dotenv from 'dotenv';
|
|
2
|
-
import {
|
|
2
|
+
import { KySlackRepository } from './KySlackRepository';
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import https from 'https';
|
|
5
5
|
import path from 'path';
|
|
@@ -16,13 +16,13 @@ if (!SLACK_USER_TOKEN) {
|
|
|
16
16
|
throw new Error('SLACK_USER_TOKEN is required');
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
describe('
|
|
19
|
+
describe('KySlackRepository Integration Tests', () => {
|
|
20
20
|
jest.setTimeout(60 * 1000);
|
|
21
21
|
jest.retryTimes(3, { logErrorsBeforeRetry: true });
|
|
22
|
-
let slackRepository:
|
|
22
|
+
let slackRepository: KySlackRepository;
|
|
23
23
|
|
|
24
24
|
beforeAll(() => {
|
|
25
|
-
slackRepository = new
|
|
25
|
+
slackRepository = new KySlackRepository(SLACK_USER_TOKEN);
|
|
26
26
|
});
|
|
27
27
|
beforeEach(async () => {
|
|
28
28
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import ky from 'ky';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { SlackRepository } from '../../domain/usecases/adapter-interfaces/SlackRepository';
|
|
4
|
+
|
|
5
|
+
type ConversationsListResponse = {
|
|
6
|
+
ok: boolean;
|
|
7
|
+
channels: { id: string; name: string }[];
|
|
8
|
+
};
|
|
9
|
+
type UsersListResponse = {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
members: { id: string; name: string }[];
|
|
12
|
+
};
|
|
13
|
+
type PostMessageResponse = {
|
|
14
|
+
ok: boolean;
|
|
15
|
+
ts: string;
|
|
16
|
+
};
|
|
17
|
+
type UploadUrlResponse = {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
upload_url: string;
|
|
20
|
+
file_id: string;
|
|
21
|
+
};
|
|
22
|
+
type CompleteUploadResponse = {
|
|
23
|
+
ok: boolean;
|
|
24
|
+
};
|
|
25
|
+
type FileInfoResponse = {
|
|
26
|
+
ok: boolean;
|
|
27
|
+
file: { url_private: string };
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const isRecord = (data: unknown): data is Record<string, unknown> => {
|
|
31
|
+
return typeof data === 'object' && data !== null;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const isConversationsListResponse = (
|
|
35
|
+
data: unknown,
|
|
36
|
+
): data is ConversationsListResponse => {
|
|
37
|
+
return (
|
|
38
|
+
isRecord(data) &&
|
|
39
|
+
typeof data['ok'] === 'boolean' &&
|
|
40
|
+
Array.isArray(data['channels'])
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const isUsersListResponse = (data: unknown): data is UsersListResponse => {
|
|
45
|
+
return (
|
|
46
|
+
isRecord(data) &&
|
|
47
|
+
typeof data['ok'] === 'boolean' &&
|
|
48
|
+
Array.isArray(data['members'])
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const isPostMessageResponse = (data: unknown): data is PostMessageResponse => {
|
|
53
|
+
return isRecord(data) && typeof data['ok'] === 'boolean';
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const isUploadUrlResponse = (data: unknown): data is UploadUrlResponse => {
|
|
57
|
+
return (
|
|
58
|
+
isRecord(data) &&
|
|
59
|
+
typeof data['ok'] === 'boolean' &&
|
|
60
|
+
'upload_url' in data &&
|
|
61
|
+
'file_id' in data
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const isCompleteUploadResponse = (
|
|
66
|
+
data: unknown,
|
|
67
|
+
): data is CompleteUploadResponse => {
|
|
68
|
+
return isRecord(data) && typeof data['ok'] === 'boolean';
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const isFileInfoResponse = (data: unknown): data is FileInfoResponse => {
|
|
72
|
+
return isRecord(data) && typeof data['ok'] === 'boolean' && 'file' in data;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export class KySlackRepository implements SlackRepository {
|
|
76
|
+
private readonly client: typeof ky;
|
|
77
|
+
private readonly authHeader: string;
|
|
78
|
+
private readonly baseUrl = 'https://slack.com/api';
|
|
79
|
+
|
|
80
|
+
constructor(userToken: string) {
|
|
81
|
+
if (!userToken.startsWith('xoxp-')) {
|
|
82
|
+
throw new Error('Invalid user token. It should start with xoxp-');
|
|
83
|
+
}
|
|
84
|
+
this.authHeader = `Bearer ${userToken}`;
|
|
85
|
+
this.client = ky.extend({
|
|
86
|
+
retry: {
|
|
87
|
+
limit: 3,
|
|
88
|
+
methods: ['get', 'post'],
|
|
89
|
+
statusCodes: [429, 500, 502, 503, 504],
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async postMessageToChannel(
|
|
95
|
+
message: string,
|
|
96
|
+
channelName: string,
|
|
97
|
+
): Promise<{
|
|
98
|
+
threadTs: string;
|
|
99
|
+
}> {
|
|
100
|
+
const data = await this.client
|
|
101
|
+
.get(`${this.baseUrl}/conversations.list`, {
|
|
102
|
+
headers: { Authorization: this.authHeader },
|
|
103
|
+
})
|
|
104
|
+
.json<ConversationsListResponse>();
|
|
105
|
+
|
|
106
|
+
if (!isConversationsListResponse(data)) {
|
|
107
|
+
throw new Error(`Invalid response: ${JSON.stringify(data)}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const channel = data.channels.find((c) => c.name === channelName);
|
|
111
|
+
if (!channel) throw new Error(`Channel ${channelName} not found`);
|
|
112
|
+
|
|
113
|
+
const res = await this.client
|
|
114
|
+
.post(`${this.baseUrl}/chat.postMessage`, {
|
|
115
|
+
json: { channel: channel.id, text: message },
|
|
116
|
+
headers: { Authorization: this.authHeader },
|
|
117
|
+
})
|
|
118
|
+
.json<PostMessageResponse>();
|
|
119
|
+
|
|
120
|
+
if (!isPostMessageResponse(res)) {
|
|
121
|
+
throw new Error(`Invalid response: ${JSON.stringify(res)}`);
|
|
122
|
+
}
|
|
123
|
+
if (!res.ok) {
|
|
124
|
+
throw new Error(`Failed to post message: ${JSON.stringify(res)}`);
|
|
125
|
+
}
|
|
126
|
+
return { threadTs: res.ts };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async postMessageToChannelThread(
|
|
130
|
+
message: string,
|
|
131
|
+
channelName: string,
|
|
132
|
+
threadTs: string,
|
|
133
|
+
): Promise<void> {
|
|
134
|
+
const data = await this.client
|
|
135
|
+
.get(`${this.baseUrl}/conversations.list`, {
|
|
136
|
+
headers: { Authorization: this.authHeader },
|
|
137
|
+
})
|
|
138
|
+
.json<ConversationsListResponse>();
|
|
139
|
+
|
|
140
|
+
if (!isConversationsListResponse(data)) {
|
|
141
|
+
throw new Error(`Invalid response: ${JSON.stringify(data)}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const channel = data.channels.find((c) => c.name === channelName);
|
|
145
|
+
if (!channel) throw new Error(`Channel ${channelName} not found`);
|
|
146
|
+
|
|
147
|
+
const res = await this.client
|
|
148
|
+
.post(`${this.baseUrl}/chat.postMessage`, {
|
|
149
|
+
json: { channel: channel.id, text: message, thread_ts: threadTs },
|
|
150
|
+
headers: { Authorization: this.authHeader },
|
|
151
|
+
})
|
|
152
|
+
.json<PostMessageResponse>();
|
|
153
|
+
|
|
154
|
+
if (!isPostMessageResponse(res)) {
|
|
155
|
+
throw new Error(`Invalid response: ${JSON.stringify(res)}`);
|
|
156
|
+
}
|
|
157
|
+
if (!res.ok) {
|
|
158
|
+
throw new Error(`Failed to post message: ${JSON.stringify(res)}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async postMessageToDirectMessage(
|
|
163
|
+
message: string,
|
|
164
|
+
userName: string,
|
|
165
|
+
): Promise<void> {
|
|
166
|
+
const data = await this.client
|
|
167
|
+
.get(`${this.baseUrl}/users.list`, {
|
|
168
|
+
headers: { Authorization: this.authHeader },
|
|
169
|
+
})
|
|
170
|
+
.json<UsersListResponse>();
|
|
171
|
+
|
|
172
|
+
if (!isUsersListResponse(data)) {
|
|
173
|
+
throw new Error(`Invalid response: ${JSON.stringify(data)}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const user = data.members.find((u) => u.name === userName);
|
|
177
|
+
if (!user) throw new Error(`User ${userName} not found`);
|
|
178
|
+
|
|
179
|
+
const res = await this.client
|
|
180
|
+
.post(`${this.baseUrl}/chat.postMessage`, {
|
|
181
|
+
json: { channel: user.id, text: message },
|
|
182
|
+
headers: { Authorization: this.authHeader },
|
|
183
|
+
})
|
|
184
|
+
.json<PostMessageResponse>();
|
|
185
|
+
|
|
186
|
+
if (!isPostMessageResponse(res)) {
|
|
187
|
+
throw new Error(`Invalid response: ${JSON.stringify(res)}`);
|
|
188
|
+
}
|
|
189
|
+
if (!res.ok) {
|
|
190
|
+
throw new Error(`Failed to post message: ${JSON.stringify(res)}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async postMessageToChannelWithImage(
|
|
195
|
+
message: string,
|
|
196
|
+
channelName: string,
|
|
197
|
+
imageFilePath: string,
|
|
198
|
+
): Promise<void> {
|
|
199
|
+
const data = await this.client
|
|
200
|
+
.get(`${this.baseUrl}/conversations.list`, {
|
|
201
|
+
headers: { Authorization: this.authHeader },
|
|
202
|
+
})
|
|
203
|
+
.json<ConversationsListResponse>();
|
|
204
|
+
|
|
205
|
+
if (!isConversationsListResponse(data)) {
|
|
206
|
+
throw new Error(`Invalid response: ${JSON.stringify(data)}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const channel = data.channels.find((c) => c.name === channelName);
|
|
210
|
+
if (!channel) throw new Error(`Channel ${channelName} not found`);
|
|
211
|
+
|
|
212
|
+
const fileStats = fs.statSync(imageFilePath);
|
|
213
|
+
const fileName = imageFilePath.split('/').pop();
|
|
214
|
+
|
|
215
|
+
const formBody = new URLSearchParams({
|
|
216
|
+
filename: fileName ?? '',
|
|
217
|
+
length: String(fileStats.size),
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const uploadUrlRes = await this.client
|
|
221
|
+
.post(`${this.baseUrl}/files.getUploadURLExternal`, {
|
|
222
|
+
body: formBody,
|
|
223
|
+
headers: {
|
|
224
|
+
Authorization: this.authHeader,
|
|
225
|
+
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
|
|
226
|
+
},
|
|
227
|
+
})
|
|
228
|
+
.json<UploadUrlResponse>();
|
|
229
|
+
|
|
230
|
+
if (!isUploadUrlResponse(uploadUrlRes)) {
|
|
231
|
+
throw new Error(`Invalid response: ${JSON.stringify(uploadUrlRes)}`);
|
|
232
|
+
}
|
|
233
|
+
if (!uploadUrlRes.ok) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`Failed to get upload URL: ${JSON.stringify(uploadUrlRes)}`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
239
|
+
|
|
240
|
+
const fileContent = fs.readFileSync(imageFilePath);
|
|
241
|
+
await ky.post(uploadUrlRes.upload_url, {
|
|
242
|
+
body: new Uint8Array(fileContent),
|
|
243
|
+
headers: { 'Content-Type': 'application/octet-stream' },
|
|
244
|
+
});
|
|
245
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
246
|
+
|
|
247
|
+
const completeRes = await this.client
|
|
248
|
+
.post(`${this.baseUrl}/files.completeUploadExternal`, {
|
|
249
|
+
json: { files: [{ id: uploadUrlRes.file_id }] },
|
|
250
|
+
headers: { Authorization: this.authHeader },
|
|
251
|
+
})
|
|
252
|
+
.json<CompleteUploadResponse>();
|
|
253
|
+
|
|
254
|
+
if (!isCompleteUploadResponse(completeRes)) {
|
|
255
|
+
throw new Error(`Invalid response: ${JSON.stringify(completeRes)}`);
|
|
256
|
+
}
|
|
257
|
+
if (!completeRes.ok) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
`Failed to complete upload: ${JSON.stringify(completeRes)}`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const fileInfo = await this.client
|
|
264
|
+
.get(`${this.baseUrl}/files.info?file=${uploadUrlRes.file_id}`, {
|
|
265
|
+
headers: { Authorization: this.authHeader },
|
|
266
|
+
})
|
|
267
|
+
.json<FileInfoResponse>();
|
|
268
|
+
|
|
269
|
+
if (!isFileInfoResponse(fileInfo)) {
|
|
270
|
+
throw new Error(`Invalid response: ${JSON.stringify(fileInfo)}`);
|
|
271
|
+
}
|
|
272
|
+
if (!fileInfo.ok) {
|
|
273
|
+
throw new Error(`Failed to get file info: ${JSON.stringify(fileInfo)}`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const imageUrl = fileInfo.file.url_private;
|
|
277
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
278
|
+
|
|
279
|
+
const res = await this.client
|
|
280
|
+
.post(`${this.baseUrl}/chat.postMessage`, {
|
|
281
|
+
json: {
|
|
282
|
+
channel: channel.id,
|
|
283
|
+
text: message,
|
|
284
|
+
blocks: [{ type: 'image', image_url: imageUrl, alt_text: fileName }],
|
|
285
|
+
},
|
|
286
|
+
headers: { Authorization: this.authHeader },
|
|
287
|
+
})
|
|
288
|
+
.json<PostMessageResponse>();
|
|
289
|
+
|
|
290
|
+
if (!isPostMessageResponse(res)) {
|
|
291
|
+
throw new Error(`Invalid response: ${JSON.stringify(res)}`);
|
|
292
|
+
}
|
|
293
|
+
if (!res.ok) {
|
|
294
|
+
throw new Error(`Failed to post message: ${JSON.stringify(res)}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import ky from 'ky';
|
|
2
2
|
import { BaseGitHubRepository } from '../BaseGitHubRepository';
|
|
3
3
|
|
|
4
4
|
export class ApiV3IssueRepository extends BaseGitHubRepository {
|
|
@@ -35,22 +35,21 @@ export class ApiV3IssueRepository extends BaseGitHubRepository {
|
|
|
35
35
|
url += `&assignee=${query.assignee}`;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const response = await
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return response.data.items.map((item) => ({
|
|
38
|
+
const response = await ky
|
|
39
|
+
.get(url, {
|
|
40
|
+
headers: {
|
|
41
|
+
Authorization: `token ${this.ghToken}`,
|
|
42
|
+
Accept: 'application/vnd.github.v3+json',
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
.json<{
|
|
46
|
+
items: {
|
|
47
|
+
html_url: string;
|
|
48
|
+
title: string;
|
|
49
|
+
number: string;
|
|
50
|
+
}[];
|
|
51
|
+
}>();
|
|
52
|
+
return response.items.map((item) => ({
|
|
54
53
|
url: item.html_url,
|
|
55
54
|
title: item.title,
|
|
56
55
|
number: item.number,
|
|
@@ -66,22 +65,21 @@ export class ApiV3IssueRepository extends BaseGitHubRepository {
|
|
|
66
65
|
}[]
|
|
67
66
|
> => {
|
|
68
67
|
const url = `https://api.github.com/search/issues?q=${query}`;
|
|
69
|
-
const response = await
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return response.data.items.map((item) => ({
|
|
68
|
+
const response = await ky
|
|
69
|
+
.get(url, {
|
|
70
|
+
headers: {
|
|
71
|
+
Authorization: `token ${this.ghToken}`,
|
|
72
|
+
Accept: 'application/vnd.github.v3+json',
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
.json<{
|
|
76
|
+
items: {
|
|
77
|
+
html_url: string;
|
|
78
|
+
title: string;
|
|
79
|
+
number: string;
|
|
80
|
+
}[];
|
|
81
|
+
}>();
|
|
82
|
+
return response.items.map((item) => ({
|
|
85
83
|
url: item.html_url,
|
|
86
84
|
title: item.title,
|
|
87
85
|
number: item.number,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CheerioIssueRepository } from './CheerioIssueRepository';
|
|
2
2
|
import * as cheerio from 'cheerio';
|
|
3
|
-
import
|
|
3
|
+
import ky from 'ky';
|
|
4
4
|
import { InternalGraphqlIssueRepository } from './InternalGraphqlIssueRepository';
|
|
5
5
|
import dotenv from 'dotenv';
|
|
6
6
|
import { LocalStorageRepository } from '../LocalStorageRepository';
|
|
@@ -186,8 +186,8 @@ describe('CheerioIssueRepository', () => {
|
|
|
186
186
|
describe('getStatusTimelineEvents', () => {
|
|
187
187
|
it('should return status timeline events', async () => {
|
|
188
188
|
const headers = await repository.createHeader();
|
|
189
|
-
const
|
|
190
|
-
const $ = cheerio.load(
|
|
189
|
+
const html = await ky.get(issueUrl, { headers }).text();
|
|
190
|
+
const $ = cheerio.load(html);
|
|
191
191
|
const statusTimeline = await repository.getStatusTimelineEvents($);
|
|
192
192
|
expect(statusTimeline).toEqual([]);
|
|
193
193
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import ky from 'ky';
|
|
2
2
|
import * as cheerio from 'cheerio';
|
|
3
3
|
import { BaseGitHubRepository } from '../BaseGitHubRepository';
|
|
4
4
|
import { InternalGraphqlIssueRepository } from './InternalGraphqlIssueRepository';
|
|
@@ -44,8 +44,7 @@ export class CheerioIssueRepository extends BaseGitHubRepository {
|
|
|
44
44
|
}
|
|
45
45
|
getIssue = async (issueUrl: string): Promise<Issue> => {
|
|
46
46
|
const headers = await this.createHeader();
|
|
47
|
-
const
|
|
48
|
-
const html = content.data;
|
|
47
|
+
const html = await ky.get(issueUrl, { headers }).text();
|
|
49
48
|
const $ = cheerio.load(html);
|
|
50
49
|
if (html.includes('react-app.embeddedData')) {
|
|
51
50
|
const issue =
|