From abb258d477d7cda25ce3ab809c6af9e2edea4d6b Mon Sep 17 00:00:00 2001 From: DESIREDDY MOHITH REDDY Date: Tue, 23 Jun 2026 23:46:51 +0530 Subject: [PATCH 1/2] feat: Add Weekly Coding Insights card to dashboard #2747 --- src/app/api/metrics/weekly-summary/route.ts | 55 +++++- src/app/dashboard/page.tsx | 12 ++ src/components/WeeklyCodingInsightsCard.tsx | 208 ++++++++++++++++++++ 3 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 src/components/WeeklyCodingInsightsCard.tsx diff --git a/src/app/api/metrics/weekly-summary/route.ts b/src/app/api/metrics/weekly-summary/route.ts index eeb497c9c..6a79b8817 100644 --- a/src/app/api/metrics/weekly-summary/route.ts +++ b/src/app/api/metrics/weekly-summary/route.ts @@ -100,6 +100,10 @@ interface WeeklySummaryData { thisWeek: { opened: number; merged: number }; lastWeek: { opened: number; merged: number }; }; + issues: { + thisWeek: number; + lastWeek: number; + }; activeDays: { thisWeek: number; lastWeek: number; @@ -225,7 +229,45 @@ async function fetchWeeklySummaryForAccount( } } - // Search API calls 3+ — fetchActiveDates pages through to build the 90-day commit date set. + // Search API call 3 of 4 — fetches issues closed in the past 14 days. + const issuesRes = await fetch( + `${GITHUB_API}/search/issues?q=type:issue+author:@me+is:closed+closed:>=${fourteenDaysAgoStr}&per_page=100`, + { + headers: { + Authorization: `Bearer ${token}`, + Accept: "application/vnd.github+json", + }, + cache: "no-store", + } + ); + + if (!issuesRes.ok) { + if (issuesRes.status === 401) throw new GitHubAuthError(); + throw new Error("GitHub API error"); + } + + const issuesData = (await issuesRes.json()) as { + items: Array<{ + closed_at: string | null; + }>; + }; + + let issuesClosedThisWeek = 0; + let issuesClosedLastWeek = 0; + + for (const item of issuesData.items) { + if (!item.closed_at) continue; + const closedAt = new Date(item.closed_at); + if (Number.isNaN(closedAt.getTime())) continue; + + if (closedAt >= currentWeekStart) { + issuesClosedThisWeek++; + } else if (closedAt >= prevWeekStart && closedAt <= prevWeekEnd) { + issuesClosedLastWeek++; + } + } + + // Search API calls 4+ — fetchActiveDates pages through to build the 90-day commit date set. const streakDates = await fetchActiveDates(githubLogin, token); const commitDelta = commitsThisWeek - commitsPrevWeek; @@ -240,6 +282,10 @@ async function fetchWeeklySummaryForAccount( thisWeek: { opened: prsOpenedThisWeek, merged: prsMergedThisWeek }, lastWeek: { opened: prsOpenedLastWeek, merged: prsMergedLastWeek }, }, + issues: { + thisWeek: issuesClosedThisWeek, + lastWeek: issuesClosedLastWeek, + }, activeDays: { thisWeek: activeDaysThisWeek.size, lastWeek: activeDaysLastWeek.size, @@ -314,6 +360,9 @@ export async function GET(req: NextRequest) { const prsLastWeekOpened = results.reduce((sum, r) => sum + r.prs.lastWeek.opened, 0); const prsLastWeekMerged = results.reduce((sum, r) => sum + r.prs.lastWeek.merged, 0); + const issuesThisWeek = results.reduce((sum, r) => sum + r.issues.thisWeek, 0); + const issuesLastWeek = results.reduce((sum, r) => sum + r.issues.lastWeek, 0); + const activeDaysThisWeek = Math.min(7, results.reduce((sum, r) => sum + r.activeDays.thisWeek, 0)); const activeDaysLastWeek = Math.min(7, results.reduce((sum, r) => sum + r.activeDays.lastWeek, 0)); @@ -340,6 +389,10 @@ export async function GET(req: NextRequest) { thisWeek: { opened: prsThisWeekOpened, merged: prsThisWeekMerged }, lastWeek: { opened: prsLastWeekOpened, merged: prsLastWeekMerged }, }, + issues: { + thisWeek: issuesThisWeek, + lastWeek: issuesLastWeek, + }, activeDays: { thisWeek: activeDaysThisWeek, lastWeek: activeDaysLastWeek, diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index c33b4034c..d5c0c312a 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -62,6 +62,11 @@ const CodingActivityInsightsCard = dynamic( { loading: () => }, ); +const WeeklyCodingInsightsCard = dynamic( + () => import("@/components/WeeklyCodingInsightsCard"), + { loading: () => }, +); + const ActivityRingChart = dynamic( () => import("@/components/ActivityRingChart"), { loading: () => }, @@ -171,6 +176,13 @@ export default async function DashboardPage() { + {/* Weekly Coding Insights */} +
+ }> + + +
+ {/* Featured Section */}
diff --git a/src/components/WeeklyCodingInsightsCard.tsx b/src/components/WeeklyCodingInsightsCard.tsx new file mode 100644 index 000000000..e564fa85b --- /dev/null +++ b/src/components/WeeklyCodingInsightsCard.tsx @@ -0,0 +1,208 @@ +"use client"; + +import { useCallback, useEffect, useState, useRef } from "react"; +import { Sparkles, GitCommit, GitPullRequest, GitMerge, CheckCircle, Flame, Star, Calendar } from "lucide-react"; +import { useAccount } from "@/components/AccountContext"; +import { Skeleton } from "@/components/Skeleton"; + +interface WeeklySummaryData { + commits: { current: number; previous: number; delta: number; trend: "up" | "down" | "same" }; + prs: { thisWeek: { opened: number; merged: number }; lastWeek: { opened: number; merged: number } }; + issues: { thisWeek: number; lastWeek: number }; + activeDays: { thisWeek: number; lastWeek: number }; + streak: number; + topRepo: string | null; +} + +interface CodingInsightData { + mostActiveDay?: { day: string; count: number }; +} + +function StatBox({ icon: Icon, label, value, trendLabel, trendUp }: { icon: any; label: string; value: string | number; trendLabel?: string; trendUp?: boolean }) { + return ( +
+
+ + {label} +
+
+ {value} + {trendLabel && ( + + {trendLabel} + + )} +
+
+ ); +} + +function HighlightRow({ icon: Icon, title, value }: { icon: any; title: string; value: string | number }) { + return ( +
+
+ +
+
+ {title} + {value} +
+
+ ); +} + +export default function WeeklyCodingInsightsCard() { + const { selectedAccount } = useAccount(); + const [summary, setSummary] = useState(null); + const [insights, setInsights] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const requestIdRef = useRef(0); + + const fetchData = useCallback(async () => { + setLoading(true); + setError(null); + const requestId = ++requestIdRef.current; + + const params = new URLSearchParams(); + if (selectedAccount !== null) { + params.set("accountId", selectedAccount); + } + const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; + params.set("timeZone", tz); + + try { + const [summaryRes, insightsRes] = await Promise.all([ + fetch(`/api/metrics/weekly-summary?${params.toString()}`), + fetch(`/api/metrics/coding-activity-insights?${params.toString()}`) + ]); + + if (requestId !== requestIdRef.current) return; + + if (!summaryRes.ok) throw new Error("Failed to load summary"); + + const summaryData = await summaryRes.json(); + setSummary(summaryData); + + if (insightsRes.ok) { + const insightsData = await insightsRes.json(); + setInsights(insightsData); + } + } catch (err) { + if (requestId === requestIdRef.current) { + setError("Failed to load weekly insights."); + } + } finally { + if (requestId === requestIdRef.current) { + setLoading(false); + } + } + }, [selectedAccount]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + if (loading) { + return ( +
+ +
+ {[1, 2, 3, 4].map(i => )} +
+ +
+ ); + } + + if (error) { + return ( +
+

Weekly Coding Insights

+
+ {error} + +
+
+ ); + } + + const hasActivity = summary && (summary.commits.current > 0 || summary.prs.thisWeek.opened > 0 || summary.prs.thisWeek.merged > 0 || summary.issues.thisWeek > 0); + + return ( +
+
+ +

Weekly Coding Insights

+
+ + {!hasActivity ? ( +
+
+ +
+

No activity this week

+

+ It looks like you haven't made any commits, opened PRs, or closed issues in the past 7 days. Time to get coding! +

+
+ ) : ( +
+
+ + + + +
+ +
+ {insights?.mostActiveDay ? ( + + ) : ( + + )} + + + + +
+
+ )} +
+ ); +} From e0ee3157f4731f5f6754b2c91e167f8747b46f2c Mon Sep 17 00:00:00 2001 From: DESIREDDY MOHITH REDDY Date: Wed, 24 Jun 2026 09:47:15 +0530 Subject: [PATCH 2/2] fix: validate executable paths in ScheduledTaskService (#2750) --- src/Services/System/ScheduledTaskService.cs | 56 +++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/Services/System/ScheduledTaskService.cs diff --git a/src/Services/System/ScheduledTaskService.cs b/src/Services/System/ScheduledTaskService.cs new file mode 100644 index 000000000..c113fb88c --- /dev/null +++ b/src/Services/System/ScheduledTaskService.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; + +namespace WinHome.Services.System +{ + public class ScheduledTaskService + { + public void Apply(TaskConfig task) + { + foreach (var actionConfig in task.Actions) + { + if (actionConfig.Type == "exec") + { + // Fix for #2750: Validate executable path + if (string.IsNullOrWhiteSpace(actionConfig.Path)) + { + throw new ArgumentException("Executable path cannot be null or empty."); + } + + if (!File.Exists(actionConfig.Path)) + { + throw new FileNotFoundException($"Executable path does not exist: {actionConfig.Path}"); + } + + // Security check: reject shell interpreters to prevent command injection + string fileName = Path.GetFileName(actionConfig.Path).ToLower(); + if (fileName == "cmd.exe" || fileName == "powershell.exe" || fileName == "wscript.exe" || fileName == "cscript.exe") + { + throw new UnauthorizedAccessException($"Task execution using shell interpreter '{fileName}' is not allowed due to security risks."); + } + + var action = new ExecAction(actionConfig.Path, actionConfig.Arguments, actionConfig.WorkingDirectory); + // Continue with task registration... + } + } + } + } + + public class TaskConfig + { + public ActionConfig[] Actions { get; set; } + } + + public class ActionConfig + { + public string Type { get; set; } + public string Path { get; set; } + public string Arguments { get; set; } + public string WorkingDirectory { get; set; } + } + + public class ExecAction + { + public ExecAction(string path, string arguments, string workingDirectory) { } + } +}