|
1 |
| -import { |
2 |
| - deleteChannelPins, |
3 |
| - deleteChannelSubscriptions, |
4 |
| - deleteTags, |
5 |
| - deleteUserGroupRelations, |
6 |
| - getChannelPins, |
7 |
| - getChannelSubscriptions, |
8 |
| - getLastMessageCreatedAt, |
9 |
| - getTags, |
10 |
| - getUserGroupRelations, |
11 |
| - insertChannelPins, |
12 |
| - insertChannelSubscriptions, |
13 |
| - insertMessageStamps, |
14 |
| - insertMessages, |
15 |
| - insertTags, |
16 |
| - insertUserGroupRelations, |
17 |
| - updateMaterializedViews, |
18 |
| -} from '@traq-ing/database'; |
19 |
| -import { api } from './api'; |
20 |
| - |
21 |
| -const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); |
22 |
| - |
23 |
| -export const updateMessages = async () => { |
24 |
| - const lastMessage = await getLastMessageCreatedAt(); |
25 |
| - const after = lastMessage ? new Date(lastMessage.getTime() - 1000 * 60 * 60 * 24 * 7).toISOString() : undefined; |
26 |
| - let before = new Date().toISOString(); |
27 |
| - |
28 |
| - const res = await api.users.getUsers(); |
29 |
| - if (!res.ok) { |
30 |
| - throw new Error('Failed to fetch users'); |
31 |
| - } |
32 |
| - const users = res.data; |
33 |
| - const isBot = (id: string) => users.find((u) => u.id === id)?.bot ?? false; |
34 |
| - |
35 |
| - let offset = 0; |
36 |
| - while (true) { |
37 |
| - const res = await api.messages.searchMessages({ |
38 |
| - sort: 'createdAt', |
39 |
| - limit: 100, |
40 |
| - after, |
41 |
| - before, |
42 |
| - }); |
43 |
| - if (!res.ok) continue; |
44 |
| - |
45 |
| - const searched = res.data.hits; |
46 |
| - if (searched.length === 0) break; |
47 |
| - |
48 |
| - const messages = searched.map((m) => ({ |
49 |
| - id: m.id, |
50 |
| - userId: m.userId, |
51 |
| - channelId: m.channelId, |
52 |
| - content: m.content.replaceAll('\u0000', ''), |
53 |
| - createdAt: new Date(m.createdAt), |
54 |
| - pinned: m.pinned, |
55 |
| - isBot: isBot(m.userId), |
56 |
| - })); |
57 |
| - |
58 |
| - await insertMessages(messages); |
59 |
| - |
60 |
| - const stamps = searched.flatMap((m) => |
61 |
| - m.stamps.map((s) => ({ |
62 |
| - messageId: m.id, |
63 |
| - userId: s.userId, |
64 |
| - stampId: s.stampId, |
65 |
| - channelId: m.channelId, |
66 |
| - messageUserId: m.userId, |
67 |
| - count: s.count, |
68 |
| - createdAt: new Date(s.createdAt), |
69 |
| - isBot: isBot(m.userId), |
70 |
| - isBotMessage: isBot(s.userId), |
71 |
| - })), |
72 |
| - ); |
73 |
| - |
74 |
| - if (stamps.length > 0) { |
75 |
| - await insertMessageStamps(stamps); |
76 |
| - } |
77 |
| - |
78 |
| - offset += searched.length; |
79 |
| - before = searched[searched.length - 1].createdAt; |
80 |
| - |
81 |
| - console.log(offset, searched[searched.length - 1].createdAt); |
82 |
| - await sleep(100); |
83 |
| - } |
84 |
| - |
85 |
| - await updateMaterializedViews(); |
86 |
| -}; |
87 |
| - |
88 |
| -const getDiff = <T>(before: T[], after: T[], comp: (a: T, b: T) => boolean) => { |
89 |
| - const added = after.filter((a) => !before.some((b) => comp(a, b))); |
90 |
| - const deleted = before.filter((b) => !after.some((a) => comp(a, b))); |
91 |
| - return { added, deleted }; |
92 |
| -}; |
93 |
| - |
94 |
| -const chunkArray = <T>(array: T[], size = 1000) => { |
95 |
| - const chunks = []; |
96 |
| - for (let i = 0; i < array.length; i += size) { |
97 |
| - chunks.push(array.slice(i, i + size)); |
98 |
| - } |
99 |
| - return chunks; |
100 |
| -}; |
101 |
| - |
102 |
| -export const updateStatistics = async () => { |
103 |
| - // users |
104 |
| - const userRes = await api.users.getUsers({ 'include-suspended': true }); |
105 |
| - if (!userRes.ok) throw new Error('Failed to fetch users'); |
106 |
| - const users = userRes.data; |
107 |
| - console.log(`Successfully fetched and inserted ${users.length} users`); |
108 |
| - |
109 |
| - // groups |
110 |
| - const groupRes = await api.groups.getUserGroups(); |
111 |
| - if (!groupRes.ok) throw new Error('Failed to fetch groups'); |
112 |
| - const groups = groupRes.data; |
113 |
| - const insertedUserGroupRelations = await getUserGroupRelations(); |
114 |
| - console.log(`Successfully fetched ${groups.length} groups`); |
115 |
| - |
116 |
| - const newUserGroupRelations = groups.flatMap((group) => |
117 |
| - group.members.map((user) => ({ |
118 |
| - userId: user.id, |
119 |
| - groupId: group.id, |
120 |
| - isAdmin: group.admins.includes(user.id), |
121 |
| - })), |
122 |
| - ); |
123 |
| - const userGroupRelationsDiff = getDiff( |
124 |
| - insertedUserGroupRelations, |
125 |
| - newUserGroupRelations, |
126 |
| - (a, b) => a.userId === b.userId && a.groupId === b.groupId, |
127 |
| - ); |
128 |
| - if (userGroupRelationsDiff.added.length > 0) await insertUserGroupRelations(userGroupRelationsDiff.added); |
129 |
| - console.log(`Successfully inserted ${userGroupRelationsDiff.added.length} groups`); |
130 |
| - if (userGroupRelationsDiff.deleted.length > 0) await deleteUserGroupRelations(userGroupRelationsDiff.deleted); |
131 |
| - console.log(`Successfully deleted ${userGroupRelationsDiff.deleted.length} groups`); |
132 |
| - |
133 |
| - // tags |
134 |
| - const newTags = []; |
135 |
| - for (const user of users) { |
136 |
| - const res = await api.users.getUser(user.id); |
137 |
| - if (!res.ok) throw new Error('Failed to fetch user'); |
138 |
| - const userData = res.data; |
139 |
| - newTags.push( |
140 |
| - ...userData.tags.map((tag) => ({ |
141 |
| - userId: user.id, |
142 |
| - name: tag.tag, |
143 |
| - })), |
144 |
| - ); |
145 |
| - await sleep(100); |
146 |
| - } |
147 |
| - console.log(`Successfully fetched ${newTags.length} tags`); |
148 |
| - const insertedTags = await getTags(); |
149 |
| - const tagsDiff = getDiff(insertedTags, newTags, (a, b) => a.userId === b.userId && a.name === b.name); |
150 |
| - if (tagsDiff.added.length > 0) await insertTags(tagsDiff.added); |
151 |
| - console.log(`Successfully inserted ${tagsDiff.added.length} tags`); |
152 |
| - if (tagsDiff.deleted.length > 0) await deleteTags(tagsDiff.deleted); |
153 |
| - console.log(`Successfully deleted ${tagsDiff.deleted.length} tags`); |
154 |
| - |
155 |
| - // channels |
156 |
| - const channelRes = await api.channels.getChannels(); |
157 |
| - if (!channelRes.ok) throw new Error('Failed to fetch channels'); |
158 |
| - const channels = channelRes.data.public; |
159 |
| - console.log(`Successfully fetched ${channels.length} channels`); |
160 |
| - |
161 |
| - // channel subscriptions |
162 |
| - const newChannelSubscriptions = []; |
163 |
| - for (const channel of channels) { |
164 |
| - try { |
165 |
| - const res = await api.channels.getChannelSubscribers(channel.id); |
166 |
| - if (!res.ok) throw new Error('Failed to fetch channel subscriptions'); |
167 |
| - const subscriptions = res.data; |
168 |
| - newChannelSubscriptions.push( |
169 |
| - ...subscriptions.map((subscription) => ({ |
170 |
| - userId: subscription, |
171 |
| - channelId: channel.id, |
172 |
| - })), |
173 |
| - ); |
174 |
| - } catch (err) { |
175 |
| - // 403エラーの場合はスキップ |
176 |
| - } |
177 |
| - await sleep(100); |
178 |
| - } |
179 |
| - const insertedChannelSubscriptions = await getChannelSubscriptions(); |
180 |
| - const channelSubscriptionsDiff = getDiff( |
181 |
| - insertedChannelSubscriptions, |
182 |
| - newChannelSubscriptions, |
183 |
| - (a, b) => a.userId === b.userId && a.channelId === b.channelId, |
184 |
| - ); |
185 |
| - // 一度に大量のデータを挿入するとエラーが発生するため、1000件ずつ挿入する |
186 |
| - for (const chunk of chunkArray(channelSubscriptionsDiff.added)) { |
187 |
| - await insertChannelSubscriptions(chunk); |
188 |
| - } |
189 |
| - console.log(`Successfully inserted ${channelSubscriptionsDiff.added.length} channel subscriptions`); |
190 |
| - if (channelSubscriptionsDiff.deleted.length > 0) await deleteChannelSubscriptions(channelSubscriptionsDiff.deleted); |
191 |
| - console.log(`Successfully deleted ${channelSubscriptionsDiff.deleted.length} channel subscriptions`); |
192 |
| - |
193 |
| - // channel pins |
194 |
| - const newChannelPins = []; |
195 |
| - for (const channel of channels) { |
196 |
| - const res = await api.channels.getChannelPins(channel.id); |
197 |
| - if (!res.ok) throw new Error('Failed to fetch channel pins'); |
198 |
| - const pins = res.data; |
199 |
| - newChannelPins.push( |
200 |
| - ...pins.map((pin) => ({ |
201 |
| - channelId: channel.id, |
202 |
| - messageId: pin.message.id, |
203 |
| - })), |
204 |
| - ); |
205 |
| - await sleep(100); |
206 |
| - } |
207 |
| - const insertedChannelPins = await getChannelPins(); |
208 |
| - const channelPinsDiff = getDiff( |
209 |
| - insertedChannelPins, |
210 |
| - newChannelPins, |
211 |
| - (a, b) => a.channelId === b.channelId && a.messageId === b.messageId, |
212 |
| - ); |
213 |
| - for (const chunk of chunkArray(channelPinsDiff.added)) { |
214 |
| - await insertChannelPins(chunk); |
215 |
| - } |
216 |
| - console.log(`Successfully inserted ${channelPinsDiff.added.length} channel pins`); |
217 |
| - if (channelPinsDiff.deleted.length > 0) await deleteChannelPins(channelPinsDiff.deleted); |
218 |
| - console.log(`Successfully deleted ${channelPinsDiff.deleted.length} channel pins`); |
219 |
| -}; |
| 1 | +export * from './update-messages'; |
| 2 | +export * from './update-statistics'; |
| 3 | +export * from './drop-messages-and-stamps'; |
0 commit comments