This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

feat: select hackatime keys

+133 -11
+87 -2
src/features/takes/handlers/settings.ts
··· 3 3 import { eq } from "drizzle-orm"; 4 4 import { users as usersTable } from "../../../libs/schema"; 5 5 import { 6 + fetchRecentProjectKeys, 6 7 getHackatimeName, 7 8 HACKATIME_VERSIONS, 8 9 type HackatimeVersion, ··· 20 21 repo_link: string | undefined; 21 22 demo_link: string | undefined; 22 23 hackatime_version: string; 24 + hackatime_keys: string[]; 23 25 } = { 24 26 project_name: "", 25 27 project_description: "", 26 28 repo_link: undefined, 27 29 demo_link: undefined, 28 30 hackatime_version: "v2", 31 + hackatime_keys: [], 29 32 }; 30 33 31 34 if (prefill) { ··· 45 48 repo_link: existingUser.repoLink || undefined, 46 49 demo_link: existingUser.demoLink || undefined, 47 50 hackatime_version: existingUser.hackatimeVersion, 51 + hackatime_keys: existingUser.hackatimeKeys 52 + ? JSON.parse(existingUser.hackatimeKeys) 53 + : [], 48 54 }; 49 55 } 50 56 } catch (error) { ··· 64 70 type: "plain_text", 65 71 text: "Submit", 66 72 }, 67 - clear_on_close: true, 68 73 callback_id: "takes_setup_submit", 69 74 blocks: [ 70 75 { ··· 179 184 })), 180 185 }, 181 186 }, 187 + { 188 + type: "input", 189 + block_id: "project_keys", 190 + label: { 191 + type: "plain_text", 192 + text: "Project Keys", 193 + }, 194 + element: { 195 + type: "multi_static_select", 196 + action_id: "project_keys_input", 197 + initial_options: initialValues.hackatime_keys.map( 198 + (key) => ({ 199 + text: { 200 + type: "plain_text", 201 + text: key, 202 + }, 203 + value: key, 204 + }), 205 + ), 206 + options: ( 207 + await fetchRecentProjectKeys( 208 + user, 209 + 10, 210 + initialValues.hackatime_version as HackatimeVersion, 211 + ) 212 + ).map((key) => ({ 213 + text: { 214 + type: "plain_text", 215 + text: key, 216 + }, 217 + value: key, 218 + })), 219 + }, 220 + }, 182 221 ], 183 222 }, 184 223 }); ··· 193 232 const file = values.project_banner?.project_banner_input?.files?.[0] 194 233 ?.url_private_download as string; 195 234 196 - console.log(file); 235 + const hackatimeKeys = JSON.stringify( 236 + values.project_keys?.project_keys_input?.selected_options?.map( 237 + (option) => option.value, 238 + ) || [], 239 + ); 197 240 198 241 try { 199 242 const projectBannerUrl = file ··· 222 265 | string 223 266 | undefined, 224 267 hackatimeVersion, 268 + hackatimeKeys, 225 269 }) 226 270 .onConflictDoUpdate({ 227 271 target: usersTable.id, ··· 238 282 | string 239 283 | undefined, 240 284 hackatimeVersion, 285 + hackatimeKeys, 241 286 }, 242 287 }); 288 + 289 + // Update the view to show the latest Hackatime project keys 290 + await slackClient.views.update({ 291 + view_id: payload.view.id, 292 + view: { 293 + type: "modal", 294 + title: { 295 + type: "plain_text", 296 + text: "Add your hackatime keys", 297 + }, 298 + blocks: [ 299 + { 300 + type: "section", 301 + text: { 302 + type: "mrkdwn", 303 + text: ":white_check_mark: Your project has been updated successfully!", 304 + }, 305 + }, 306 + { 307 + type: "section", 308 + text: { 309 + type: "mrkdwn", 310 + text: "*Hackatime Project Keys:*", 311 + }, 312 + }, 313 + { 314 + type: "section", 315 + text: { 316 + type: "mrkdwn", 317 + text: Object.values(HACKATIME_VERSIONS) 318 + .map( 319 + (v) => 320 + `• *${getHackatimeName(v.id)}*: \`${v.id}\``, 321 + ) 322 + .join("\n"), 323 + }, 324 + }, 325 + ], 326 + }, 327 + }); 243 328 } catch (error) { 244 329 console.error("Error processing file:", error); 245 330 throw error;
+46 -9
src/libs/hackatime.ts
··· 59 59 userId: string, 60 60 version: HackatimeVersion = "v2", 61 61 projectKeys?: string[], 62 + from?: Date, 63 + to?: Date, 62 64 ) { 63 65 const apiUrl = getHackatimeApiUrl(version); 64 - const response = await fetch( 65 - `${apiUrl}/summary?user=${userId}&interval=month`, 66 - { 67 - headers: { 68 - accept: "application/json", 69 - Authorization: "Bearer 2ce9e698-8a16-46f0-b49a-ac121bcfd608", 70 - }, 66 + const params = new URLSearchParams({ 67 + user: userId, 68 + }); 69 + if (!from || !to) { 70 + params.append("interval", "month"); 71 + } else if (from && to) { 72 + params.append("from", from.toISOString()); 73 + params.append("to", to.toISOString()); 74 + } 75 + 76 + const response = await fetch(`${apiUrl}/summary?${params.toString()}`, { 77 + headers: { 78 + accept: "application/json", 79 + Authorization: "Bearer 2ce9e698-8a16-46f0-b49a-ac121bcfd608", 71 80 }, 72 - ); 81 + }); 73 82 74 83 if (!response.ok) { 75 84 throw new Error( 76 - `Failed to fetch Hackatime summary: ${response.status} ${response.statusText}`, 85 + `Failed to fetch Hackatime summary: ${response.status} ${response.statusText}: ${await response.text()}`, 77 86 ); 78 87 } 79 88 ··· 110 119 projectsKeys: projectsKeys, 111 120 }; 112 121 } 122 + 123 + /** 124 + * Fetches the most recent project keys from a user's Hackatime data 125 + * @param userId The user ID to fetch the project keys for 126 + * @param limit The maximum number of projects to return (defaults to 10) 127 + * @param version The Hackatime version to use (defaults to v2) 128 + * @returns A promise that resolves to an array of recent project keys 129 + */ 130 + export async function fetchRecentProjectKeys( 131 + userId: string, 132 + limit = 10, 133 + version: HackatimeVersion = "v2", 134 + ): Promise<string[]> { 135 + const summary = await fetchHackatimeSummary(userId, version); 136 + 137 + // Extract projects and sort by most recent 138 + const sortedProjects = 139 + summary.projects?.sort( 140 + (a: { last_used_at: string }, b: { last_used_at: string }) => 141 + new Date(b.last_used_at).getTime() - 142 + new Date(a.last_used_at).getTime(), 143 + ) || []; 144 + 145 + // Return the keys of the most recent projects up to the limit 146 + return sortedProjects 147 + .slice(0, limit) 148 + .map((project: { key: string }) => project.key); 149 + }