a fun bot for the hc slack
1import { slackApp, slackClient } from "../../../index";
2import { db } from "../../../libs/db";
3import { eq } from "drizzle-orm";
4import { users as usersTable } from "../../../libs/schema";
5import {
6 fetchRecentProjectKeys,
7 getHackatimeName,
8 HACKATIME_VERSIONS,
9 type HackatimeVersion,
10} from "../../../libs/hackatime";
11import { deployToHackClubCDN } from "../../../libs/cdn";
12
13export async function handleSettings(
14 triggerID: string,
15 user: string,
16 prefill = false,
17) {
18 let initialValues: {
19 project_name: string;
20 project_description: string;
21 repo_link: string | undefined;
22 demo_link: string | undefined;
23 hackatime_version: HackatimeVersion;
24 hackatime_keys: string[];
25 } = {
26 project_name: "",
27 project_description: "",
28 repo_link: undefined,
29 demo_link: undefined,
30 hackatime_version: "v1",
31 hackatime_keys: [],
32 };
33
34 if (prefill) {
35 try {
36 // Check if user already has a project in the database
37 const existingUser = (
38 await db
39 .select()
40 .from(usersTable)
41 .where(eq(usersTable.id, user))
42 )[0];
43
44 if (existingUser) {
45 initialValues = {
46 project_name: existingUser.projectName,
47 project_description: existingUser.projectDescription,
48 repo_link: existingUser.repoLink || undefined,
49 demo_link: existingUser.demoLink || undefined,
50 hackatime_version:
51 existingUser.hackatimeVersion as HackatimeVersion,
52 hackatime_keys: existingUser.hackatimeKeys
53 ? JSON.parse(existingUser.hackatimeKeys)
54 : [],
55 };
56 }
57 } catch (error) {
58 console.error("Error prefilling form:", error);
59 }
60 }
61
62 await slackClient.views.open({
63 trigger_id: triggerID,
64 view: {
65 type: "modal",
66 title: {
67 type: "plain_text",
68 text: "Setup Project",
69 },
70 submit: {
71 type: "plain_text",
72 text: "Submit",
73 },
74 callback_id: "takes_setup_submit",
75 blocks: [
76 {
77 type: "input",
78 block_id: "project_name",
79 label: {
80 type: "plain_text",
81 text: "Project Name",
82 },
83 element: {
84 type: "plain_text_input",
85 action_id: "project_name_input",
86 initial_value: initialValues.project_name,
87 placeholder: {
88 type: "plain_text",
89 text: "Enter your project name",
90 },
91 },
92 },
93 {
94 type: "input",
95 block_id: "project_description",
96 label: {
97 type: "plain_text",
98 text: "Project Description",
99 },
100 element: {
101 type: "plain_text_input",
102 action_id: "project_description_input",
103 multiline: true,
104 initial_value: initialValues.project_description,
105 placeholder: {
106 type: "plain_text",
107 text: "Describe your project",
108 },
109 },
110 },
111 {
112 type: "input",
113 block_id: "project_banner",
114 label: {
115 type: "plain_text",
116 text: `Banner Image${prefill ? " (this will replace your current banner)" : ""}`,
117 },
118 element: {
119 type: "file_input",
120 action_id: "project_banner_input",
121 },
122 optional: prefill,
123 },
124 {
125 type: "input",
126 block_id: "repo_link",
127 optional: true,
128 label: {
129 type: "plain_text",
130 text: "Repository Link",
131 },
132 element: {
133 type: "plain_text_input",
134 action_id: "repo_link_input",
135 initial_value: initialValues.repo_link,
136 placeholder: {
137 type: "plain_text",
138 text: "Optional: Add a link to your repository",
139 },
140 },
141 },
142 {
143 type: "input",
144 block_id: "demo_link",
145 optional: true,
146 label: {
147 type: "plain_text",
148 text: "Demo Link",
149 },
150 element: {
151 type: "plain_text_input",
152 action_id: "demo_link_input",
153 initial_value: initialValues.demo_link,
154 placeholder: {
155 type: "plain_text",
156 text: "Optional: Add a link to your demo",
157 },
158 },
159 },
160 {
161 type: "input",
162 block_id: "hackatime_version",
163 label: {
164 type: "plain_text",
165 text: "Hackatime Version",
166 },
167 element: {
168 type: "static_select",
169 action_id: "hackatime_version_input",
170 initial_option: {
171 text: {
172 type: "plain_text",
173 text: getHackatimeName(
174 initialValues.hackatime_version,
175 ),
176 },
177 value: initialValues.hackatime_version,
178 },
179 options: Object.values(HACKATIME_VERSIONS).map((v) => ({
180 text: {
181 type: "plain_text",
182 text: v.name,
183 },
184 value: v.id,
185 })),
186 },
187 },
188 {
189 type: "input",
190 block_id: "project_keys",
191 label: {
192 type: "plain_text",
193 text: "Project Keys",
194 },
195 element: {
196 type: "multi_static_select",
197 action_id: "project_keys_input",
198 initial_options:
199 initialValues.hackatime_keys.length === 0
200 ? undefined
201 : initialValues.hackatime_keys.map((key) => ({
202 text: {
203 type: "plain_text",
204 text: key,
205 },
206 value: key,
207 })),
208 options: (
209 await fetchRecentProjectKeys(
210 user,
211 10,
212 initialValues.hackatime_version as HackatimeVersion,
213 )
214 ).map((key) => ({
215 text: {
216 type: "plain_text",
217 text: key,
218 },
219 value: key,
220 })),
221 },
222 },
223 ],
224 },
225 });
226}
227
228export async function setupSubmitListener() {
229 slackApp.view("takes_setup_submit", async ({ payload, context }) => {
230 if (payload.type !== "view_submission") return;
231 const values = payload.view.state.values;
232 const userId = payload.user.id;
233
234 const file = values.project_banner?.project_banner_input?.files?.[0]
235 ?.url_private_download as string;
236
237 const hackatimeKeys = JSON.stringify(
238 values.project_keys?.project_keys_input?.selected_options?.map(
239 (option) => option.value,
240 ) || [],
241 );
242
243 try {
244 const projectBannerUrl = file
245 ? await deployToHackClubCDN([file]).then(
246 (res) => res.files[0]?.deployedUrl,
247 )
248 : undefined;
249
250 const hackatimeVersion = values.hackatime_version
251 ?.hackatime_version_input?.selected_option
252 ?.value as HackatimeVersion;
253
254 await db
255 .insert(usersTable)
256 .values({
257 id: userId,
258 projectName: values.project_name?.project_name_input?.value,
259 projectDescription:
260 values.project_description?.project_description_input
261 ?.value,
262 projectBannerUrl,
263 repoLink: values.repo_link?.repo_link_input?.value,
264 demoLink: values.demo_link?.demo_link_input?.value,
265 hackatimeVersion,
266 hackatimeKeys,
267 })
268 .onConflictDoUpdate({
269 target: usersTable.id,
270 set: {
271 projectName:
272 values.project_name?.project_name_input?.value,
273 projectDescription:
274 values.project_description
275 ?.project_description_input?.value,
276 projectBannerUrl,
277 repoLink: values.repo_link?.repo_link_input?.value,
278 demoLink: values.demo_link?.demo_link_input?.value,
279 hackatimeVersion,
280 hackatimeKeys,
281 },
282 });
283 } catch (error) {
284 console.error("Error processing file:", error);
285 throw error;
286 }
287 });
288}