-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.ts
194 lines (173 loc) · 5.67 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import * as functions from 'firebase-functions';
import { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
const axiosBase = require('axios');
const region = 'asia-northeast1';
type EsaConfig = {
teamName: string;
accessToken: string;
}
function getEsaConfig(): EsaConfig {
const teamName = functions.config().esa.team_name;
const accessToken = functions.config().esa.access_token;
const config: EsaConfig = { teamName, accessToken };
return config;
}
function createAxiosClient(accessToken: string): AxiosInstance {
const axios = axiosBase.create({
baseURL: 'https://api.esa.io',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
responseType: 'json',
});
return axios;
}
type EsaPost = {
// esaのレスポンスを全部camelcaseに変換するのは面倒なので、ここだけlintは無視する
body_md: string; // eslint-disable-line camelcase
body_html: string; // eslint-disable-line camelcase
number: number;
name: string;
tags: string[];
}
export type EsaSearchResult = {
posts: EsaPost[];
total_count: number; // eslint-disable-line camelcase
}
export type Tag = {
name: string;
posts_count: number; // eslint-disable-line camelcase
}
export type EsaTags = {
tags: Tag[]
}
// ref: https://docs.esa.io/posts/102#%E3%82%A8%E3%83%A9%E3%83%BC%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9
type EsaErrorResponse = {
error: string;
message: string;
}
function transformTitle(oldTitle: string, newTitle: string): string {
const result = Array.from(new Set(oldTitle.split(/,\s?|、/).concat(newTitle.split(/,\s?|、/))));
if (JSON.stringify(result) === JSON.stringify(['日報'])) {
return '日報';
}
return result.filter((item) => {
return item !== '日報';
}).join('、');
}
async function createOrUpdatePost(
axios: AxiosInstance,
esaConfig: EsaConfig,
category: string,
tags: string[],
title: string,
text: string,
): Promise<EsaPost> {
const response = await axios.get<EsaSearchResult>(`/v1/teams/${esaConfig.teamName}/posts`, {
params: {
q: `category:${category}`,
},
});
if (response.data.total_count === 0) {
return axios.post<EsaPost>(`/v1/teams/${esaConfig.teamName}/posts`, {
post: {
name: title,
category,
tags,
body_md: text,
wip: false,
},
}).then((res: AxiosResponse<EsaPost>) => {
return res.data;
}).catch((err: AxiosError<EsaErrorResponse>) => {
throw new functions.https.HttpsError('invalid-argument', `${err.response?.data.error}: ${err.response?.data.message}`);
});
}
if (response.data.total_count === 1) {
const latestEsaPost: EsaPost = response.data.posts[0];
return axios.patch<EsaPost>(`/v1/teams/${esaConfig.teamName}/posts/${latestEsaPost.number}`, {
post: {
name: transformTitle(latestEsaPost.name, title),
category,
tags: Array.from(new Set(tags.concat(latestEsaPost.tags))),
body_md: (text !== '' ? `${text}\n${latestEsaPost.body_md}` : latestEsaPost.body_md),
wip: false,
},
}).then((res: AxiosResponse<EsaPost>) => {
return res.data;
}).catch((err: AxiosError<EsaErrorResponse>) => {
throw new functions.https.HttpsError('invalid-argument', `${err.response?.data.error}: ${err.response?.data.message}`);
});
}
throw new functions.https.HttpsError('already-exists', '複数の日報が存在します');
}
async function getDailyReport(
axios: AxiosInstance,
esaConfig: EsaConfig,
category: string,
): Promise<EsaPost> {
const response = await axios.get<EsaSearchResult>(`/v1/teams/${esaConfig.teamName}/posts`, {
params: {
q: `category:${category}`,
},
});
if (response.data.total_count === 0) {
throw new functions.https.HttpsError('not-found', '今日の日報はまだありません');
} else if (response.data.total_count > 1) {
throw new functions.https.HttpsError('already-exists', '複数の日報が存在します');
} else {
return axios.get<EsaPost>(`/v1/teams/${esaConfig.teamName}/posts/${response.data.posts[0].number}`).then((res: AxiosResponse<EsaPost>) => {
return res.data;
});
}
}
async function getTagList(
axios: AxiosInstance,
esaConfig: EsaConfig,
): Promise<EsaTags> {
const response = await axios.get<EsaTags>(`/v1/teams/${esaConfig.teamName}/tags`);
return response.data;
}
function checkAuthTokenEmail(context: functions.https.CallableContext) {
if (!context.auth || context.auth.token.email !== functions.config().context.valid_email) {
throw new functions.https.HttpsError('permission-denied', 'Auth Error');
}
}
export const submitTextToEsa = functions.region(region).https.onCall(async (
req,
context: functions.https.CallableContext,
) => {
checkAuthTokenEmail(context);
const esaConfig = getEsaConfig();
const axios = createAxiosClient(esaConfig.accessToken);
const result = await createOrUpdatePost(
axios,
esaConfig,
req.category,
req.tags,
req.title,
req.text,
);
return result;
});
export const dailyReport = functions.region(region).https.onCall(async (
req,
context: functions.https.CallableContext,
) => {
checkAuthTokenEmail(context);
const esaConfig = getEsaConfig();
const axios = createAxiosClient(esaConfig.accessToken);
const result = await getDailyReport(axios, esaConfig, req.category);
return result;
});
export const tagList = functions.region(region).https.onCall(async (
req,
context: functions.https.CallableContext,
) => {
checkAuthTokenEmail(context);
const esaConfig = getEsaConfig();
const axios = createAxiosClient(esaConfig.accessToken);
const result = await getTagList(axios, esaConfig);
return result;
});