Skip to content

Commit 96158be

Browse files
authored
Merge pull request #48 from DevSecNinja/copilot/add-footer-repo-link
Add repository link with cached star count to footer
2 parents 6513c19 + 6d416a5 commit 96158be

5 files changed

Lines changed: 155 additions & 6 deletions

File tree

backend/app/api/routes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ class StatisticsResponse(BaseModel):
9999
total_repositories: int
100100
total_automations: int
101101
last_indexed_at: Optional[str] = None
102+
repo_star_count: int
102103

103104

104105
class IndexResponse(BaseModel):

backend/app/services/indexer.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from datetime import datetime
55
from typing import List
66

7+
import httpx
78
from app.models.database import Automation, IndexingMetadata, Repository
89
from app.services.github_service import GitHubRateLimitError, GitHubService
910
from app.services.parser import AutomationParser
@@ -69,6 +70,8 @@ async def index_repositories(self, db: Session) -> dict:
6970
# Only store completion timestamp if indexing was not rate limited
7071
if not stats["rate_limited"]:
7172
self._store_completion_timestamp(db)
73+
# Also fetch and store hadiscover repo star count
74+
await self._store_repo_star_count(db)
7275
logger.info("Indexing completed successfully")
7376
else:
7477
logger.warning(
@@ -123,6 +126,56 @@ def _store_completion_timestamp(self, db: Session) -> None:
123126
logger.error(f"Error storing completion timestamp: {e}")
124127
db.rollback()
125128

129+
async def _store_repo_star_count(self, db: Session) -> None:
130+
"""
131+
Fetch and store the hadiscover repository star count.
132+
133+
Args:
134+
db: Database session
135+
"""
136+
try:
137+
async with httpx.AsyncClient() as client:
138+
url = f"{self.github_service.BASE_URL}/repos/DevSecNinja/hadiscover"
139+
response = await client.get(
140+
url, headers=self.github_service.headers, timeout=10.0
141+
)
142+
143+
if response.status_code == 200:
144+
data = response.json()
145+
star_count = data.get("stargazers_count", 0)
146+
147+
# Check if metadata record exists
148+
metadata = (
149+
db.query(IndexingMetadata)
150+
.filter_by(key="repo_star_count")
151+
.first()
152+
)
153+
154+
current_time = datetime.utcnow()
155+
156+
if metadata:
157+
# Update existing record
158+
metadata.value = str(star_count)
159+
metadata.updated_at = current_time
160+
else:
161+
# Create new record
162+
metadata = IndexingMetadata(
163+
key="repo_star_count",
164+
value=str(star_count),
165+
updated_at=current_time,
166+
)
167+
db.add(metadata)
168+
169+
db.commit()
170+
logger.info(f"Stored repository star count: {star_count}")
171+
else:
172+
logger.warning(
173+
f"Failed to fetch repo star count. Status: {response.status_code}"
174+
)
175+
except Exception as e:
176+
logger.error(f"Error storing repo star count: {e}")
177+
db.rollback()
178+
126179
async def _index_repository(self, db: Session, repo_data: dict) -> dict:
127180
"""
128181
Index a single repository.

backend/app/services/search_service.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,17 +224,25 @@ def get_statistics(db: Session) -> Dict[str, Any]:
224224
db.query(IndexingMetadata).filter_by(key="last_completed_at").first()
225225
)
226226

227+
# Get repository star count
228+
star_count_metadata = (
229+
db.query(IndexingMetadata).filter_by(key="repo_star_count").first()
230+
)
231+
star_count = int(star_count_metadata.value) if star_count_metadata else 0
232+
227233
return {
228234
"total_repositories": repo_count or 0,
229235
"total_automations": automation_count or 0,
230236
"last_indexed_at": last_indexed.value if last_indexed else None,
237+
"repo_star_count": star_count,
231238
}
232239
except Exception as e:
233240
logger.error(f"Error getting statistics: {e}")
234241
return {
235242
"total_repositories": 0,
236243
"total_automations": 0,
237244
"last_indexed_at": None,
245+
"repo_star_count": 0,
238246
}
239247

240248
@staticmethod

backend/tests/test_api.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,17 @@ def test_index_endpoint_blocked_in_production():
105105
os.environ["ENVIRONMENT"] = original_env
106106
elif "ENVIRONMENT" in os.environ:
107107
del os.environ["ENVIRONMENT"]
108+
109+
110+
def test_statistics_includes_star_count():
111+
"""Test statistics endpoint includes repo star count."""
112+
client = TestClient(app)
113+
response = client.get("/api/v1/statistics")
114+
assert response.status_code == 200
115+
116+
data = response.json()
117+
assert "total_repositories" in data
118+
assert "total_automations" in data
119+
assert "last_indexed_at" in data
120+
assert "repo_star_count" in data
121+
assert isinstance(data["repo_star_count"], int)

frontend/app/page.tsx

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ interface Statistics {
5757
total_repositories: number;
5858
total_automations: number;
5959
last_indexed_at: string | null;
60+
repo_star_count: number;
6061
}
6162

6263
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL;
@@ -214,6 +215,7 @@ export default function Home() {
214215
day: "numeric",
215216
hour: "2-digit",
216217
minute: "2-digit",
218+
hour12: false,
217219
timeZoneName: "short",
218220
});
219221
} catch (error) {
@@ -1697,29 +1699,100 @@ Here's my automation YAML:
16971699
</details>
16981700
</div>
16991701

1700-
{statistics?.last_indexed_at && (
1702+
{/* Repository Info */}
1703+
{statistics && (
17011704
<div className="text-center mt-6">
1705+
<a
1706+
href="https://github.com/DevSecNinja/hadiscover"
1707+
target="_blank"
1708+
rel="noopener noreferrer"
1709+
className="inline-flex items-center gap-2 px-5 py-3 rounded-xl transition-all duration-200"
1710+
style={{
1711+
color: isDark ? "rgba(255, 255, 255, 0.7)" : "#1f2937",
1712+
background: isDark
1713+
? "rgba(255, 255, 255, 0.05)"
1714+
: "rgba(0, 0, 0, 0.04)",
1715+
border: isDark
1716+
? "1px solid rgba(255, 255, 255, 0.08)"
1717+
: "1px solid rgba(0, 0, 0, 0.08)",
1718+
}}
1719+
onMouseEnter={(e) => {
1720+
e.currentTarget.style.background = isDark
1721+
? "rgba(255, 255, 255, 0.08)"
1722+
: "rgba(0, 0, 0, 0.06)";
1723+
e.currentTarget.style.transform = "translateY(-1px)";
1724+
}}
1725+
onMouseLeave={(e) => {
1726+
e.currentTarget.style.background = isDark
1727+
? "rgba(255, 255, 255, 0.05)"
1728+
: "rgba(0, 0, 0, 0.04)";
1729+
e.currentTarget.style.transform = "translateY(0)";
1730+
}}
1731+
>
1732+
<svg
1733+
className="w-5 h-5"
1734+
fill="currentColor"
1735+
viewBox="0 0 24 24"
1736+
role="img"
1737+
aria-label="GitHub logo"
1738+
>
1739+
<path
1740+
fillRule="evenodd"
1741+
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
1742+
clipRule="evenodd"
1743+
/>
1744+
</svg>
1745+
<span className="font-medium">DevSecNinja/hadiscover</span>
1746+
<span className="flex items-center gap-1">
1747+
<svg
1748+
className="w-4 h-4"
1749+
style={{ color: "#f59e0b" }}
1750+
fill="currentColor"
1751+
viewBox="0 0 24 24"
1752+
role="img"
1753+
aria-label="Star icon"
1754+
>
1755+
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
1756+
</svg>
1757+
{statistics.repo_star_count}
1758+
</span>
1759+
</a>
17021760
<p
1703-
className="text-sm"
1761+
className="text-sm mt-3"
17041762
style={{
17051763
color: isDark
1706-
? "rgba(255, 255, 255, 0.3)"
1707-
: "rgba(0, 0, 0, 0.4)",
1764+
? "rgba(255, 255, 255, 0.4)"
1765+
: "rgba(0, 0, 0, 0.5)",
17081766
}}
17091767
>
1710-
Last indexed: {formatLastIndexed(statistics.last_indexed_at)}
1768+
⭐ Star us on GitHub to show your support!
17111769
</p>
17121770
</div>
17131771
)}
17141772

17151773
<div
1716-
className="text-center mt-8 text-sm"
1774+
className="text-center mt-6 text-sm"
17171775
style={{
17181776
color: isDark ? "rgba(255, 255, 255, 0.3)" : "rgba(0, 0, 0, 0.4)",
17191777
}}
17201778
>
17211779
<p>Built with 💙 for the Home Assistant community</p>
17221780
</div>
1781+
1782+
{statistics?.last_indexed_at && (
1783+
<div className="text-center mt-6">
1784+
<p
1785+
className="text-sm"
1786+
style={{
1787+
color: isDark
1788+
? "rgba(255, 255, 255, 0.3)"
1789+
: "rgba(0, 0, 0, 0.4)",
1790+
}}
1791+
>
1792+
Last indexed: {formatLastIndexed(statistics.last_indexed_at)}
1793+
</p>
1794+
</div>
1795+
)}
17231796
</footer>
17241797
</div>
17251798
</div>

0 commit comments

Comments
 (0)