RohanVashisht commited on
Commit
7e44512
·
1 Parent(s): 94f3b9f

formatted files

Browse files
Files changed (1) hide show
  1. main.py +180 -122
main.py CHANGED
@@ -13,25 +13,28 @@ from datetime import datetime
13
  # Global database connection
14
  db_conn = None
15
 
 
16
  @asynccontextmanager
17
  async def lifespan(app: FastAPI):
18
  """Manage application lifecycle - initialize DB on startup."""
19
  global db_conn
20
  url = os.getenv("DATABASE_URL")
21
  auth_token = os.getenv("API_KEY")
22
-
23
  if not url or not auth_token:
24
  raise RuntimeError("DATABASE_URL and API_KEY environment variables must be set")
25
-
26
  try:
27
- db_conn = libsql.connect("zigistry-main.db", sync_url=url, auth_token=auth_token)
 
 
28
  db_conn.sync()
29
  print("Database connection established successfully")
30
  except Exception as e:
31
  raise RuntimeError(f"Failed to connect to database: {e}")
32
-
33
  yield
34
-
35
  # Cleanup on shutdown
36
  if db_conn:
37
  try:
@@ -39,11 +42,12 @@ async def lifespan(app: FastAPI):
39
  except Exception as e:
40
  print(f"Error closing database connection: {e}")
41
 
 
42
  app = FastAPI(
43
  title="Zigistry API",
44
  description="API for searching and browsing Zig packages and programs",
45
  version="1.0.0",
46
- lifespan=lifespan
47
  )
48
 
49
  # CORS middleware
@@ -55,14 +59,15 @@ app.add_middleware(
55
  allow_headers=["*"],
56
  )
57
 
 
58
  def get_default_branch_info(conn, repo_id: str, default_branch: str) -> Dict[str, Any]:
59
  """Get information about the default branch (build.zig.zon file).
60
-
61
  Args:
62
  conn: Database connection
63
  repo_id: Repository identifier
64
  default_branch: Name of the default branch
65
-
66
  Returns:
67
  Dictionary with default branch information including dependencies and minimum zig version
68
  """
@@ -81,19 +86,19 @@ def get_default_branch_info(conn, repo_id: str, default_branch: str) -> Dict[str
81
  ORDER BY published_at DESC
82
  LIMIT 1
83
  """
84
-
85
  cursor = conn.execute(sql, (repo_id,))
86
  release = cursor.fetchone()
87
-
88
  if not release:
89
  return {
90
  "minimum_zig_version": "0.0.0",
91
  "dependencies": [],
92
- "branch_name": default_branch
93
  }
94
-
95
  release_id, version, published_at, min_zig_ver, readme_url, is_prerelease = release
96
-
97
  # Get dependencies for this release
98
  deps_sql = """
99
  SELECT name, url, hash, lazy, path
@@ -102,37 +107,38 @@ def get_default_branch_info(conn, repo_id: str, default_branch: str) -> Dict[str
102
  """
103
  cursor = conn.execute(deps_sql, (release_id,))
104
  dep_rows = cursor.fetchall()
105
-
106
  dependencies = [
107
  {
108
  "name": row[0],
109
  "url": row[1],
110
  "hash": row[2],
111
  "lazy": bool(row[3]) if row[3] else False,
112
- "path": row[4]
113
  }
114
  for row in dep_rows
115
  ]
116
-
117
  return {
118
  "minimum_zig_version": min_zig_ver or "0.0.0",
119
  "dependencies": dependencies,
120
  "branch_name": default_branch,
121
  "latest_version": version,
122
- "readme_url": readme_url
123
  }
124
 
 
125
  def get_version_info(conn, repo_id: str, version: str) -> Dict[str, Any]:
126
  """Get information about a specific version/release.
127
-
128
  Args:
129
  conn: Database connection
130
  repo_id: Repository identifier
131
  version: Version string
132
-
133
  Returns:
134
  Dictionary with version-specific information
135
-
136
  Raises:
137
  HTTPException: If version not found
138
  """
@@ -147,18 +153,18 @@ def get_version_info(conn, repo_id: str, version: str) -> Dict[str, Any]:
147
  FROM releases
148
  WHERE repo_id = ? AND version = ?
149
  """
150
-
151
  cursor = conn.execute(sql, (repo_id, version))
152
  release = cursor.fetchone()
153
-
154
  if not release:
155
  raise HTTPException(
156
- status_code=404,
157
- detail=f"Version '{version}' not found for repository '{repo_id}'"
158
  )
159
-
160
  release_id, ver, published_at, min_zig_ver, readme_url, is_prerelease = release
161
-
162
  # Get dependencies for this version
163
  deps_sql = """
164
  SELECT name, url, hash, lazy, path
@@ -167,36 +173,41 @@ def get_version_info(conn, repo_id: str, version: str) -> Dict[str, Any]:
167
  """
168
  cursor = conn.execute(deps_sql, (release_id,))
169
  dep_rows = cursor.fetchall()
170
-
171
  dependencies = [
172
  {
173
  "name": row[0],
174
  "url": row[1],
175
  "hash": row[2],
176
  "lazy": bool(row[3]) if row[3] else False,
177
- "path": row[4]
178
  }
179
  for row in dep_rows
180
  ]
181
-
182
  return {
183
  "version": ver,
184
  "published_at": str(published_at) if published_at else None,
185
  "minimum_zig_version": min_zig_ver or "0.0.0",
186
  "readme_url": readme_url,
187
  "is_prerelease": bool(is_prerelease),
188
- "dependencies": dependencies
189
  }
190
 
 
191
  def row_to_repo_dict(row: tuple) -> Dict[str, Any]:
192
  """Convert database row to repository dictionary.
193
-
194
  Handles the common transformation from SQL result to API response format.
195
  """
196
  platform_raw = (row[3] or "").lower()
197
- provider = 'gh' if 'github' in platform_raw else ('cb' if 'codeberg' in platform_raw else 'gh')
 
 
 
 
198
  repo_id = row[0]
199
- repo_name = repo_id.split('/')[-1] if '/' in repo_id else repo_id
200
 
201
  return {
202
  "id": row[0],
@@ -223,6 +234,7 @@ def row_to_repo_dict(row: tuple) -> Dict[str, Any]:
223
  "dependents_count": row[18] if row[18] is not None else 0,
224
  }
225
 
 
226
  def get_type_filter(search_type: str) -> str:
227
  """Generate SQL WHERE clause filter based on search type."""
228
  if search_type == "package":
@@ -232,27 +244,27 @@ def get_type_filter(search_type: str) -> str:
232
  else: # all
233
  return "AND (pkg.repo_id IS NOT NULL OR prog.repo_id IS NOT NULL)"
234
 
 
235
  def search_repos(
236
- conn,
237
- query: str,
238
- search_type: str = "all",
239
- limit: int = 50
240
  ) -> List[Dict[str, Any]]:
241
  """Search repositories by query string.
242
-
243
  Args:
244
  conn: Database connection
245
  query: Search query string
246
  search_type: Filter by 'package', 'program', or 'all'
247
  limit: Maximum number of results to return
248
-
249
  Returns:
250
  List of repository dictionaries with default branch minimum_zig_version
251
  """
252
  search_term_like = f"%{query}%"
253
  # FTS5 query with prefix matching for each word
254
- fts_query = " ".join('"' + word.replace('"', '""') + '"*' for word in query.split() if word)
255
-
 
 
256
  type_filter = get_type_filter(search_type)
257
 
258
  sql = f"""
@@ -302,26 +314,25 @@ def search_repos(
302
  rd.stargazer_count DESC
303
  LIMIT ?5
304
  """
305
-
306
  starts_with = f"{query}%"
307
  # Parameters matches ?1 (FTS), ?2 (LIKE), ?3 (Exact), ?4 (Starts with), ?5 (Limit)
308
  cursor = conn.execute(sql, (fts_query, search_term_like, query, starts_with, limit))
309
  rows = cursor.fetchall()
310
-
311
  return [row_to_repo_dict(row) for row in rows]
312
 
 
313
  def get_latest_repos(
314
- conn,
315
- search_type: str = "all",
316
- limit: int = 10
317
  ) -> List[Dict[str, Any]]:
318
  """Get latest repositories ordered by creation date.
319
-
320
  Args:
321
  conn: Database connection
322
  search_type: Filter by 'package', 'program', or 'all'
323
  limit: Maximum number of results to return
324
-
325
  Returns:
326
  List of repository dictionaries with default branch minimum_zig_version
327
  """
@@ -362,26 +373,24 @@ def get_latest_repos(
362
  (SELECT COUNT(*) FROM repo_dependents WHERE repo_id = rd.id) as dependents_count
363
  FROM repo_data rd
364
  """
365
-
366
  cursor = conn.execute(sql, (limit,))
367
  rows = cursor.fetchall()
368
-
369
  return [row_to_repo_dict(row) for row in rows]
370
 
 
371
  def get_scroll_repos(
372
- conn,
373
- search_type: str = "all",
374
- per_page: int = 20,
375
- page: int = 1
376
  ) -> List[Dict[str, Any]]:
377
  """Get paginated repositories ordered by star count.
378
-
379
  Args:
380
  conn: Database connection
381
  search_type: Filter by 'package', 'program', or 'all'
382
  per_page: Number of items per page (max 20)
383
  page: Page number (1-indexed)
384
-
385
  Returns:
386
  List of repository dictionaries with default branch minimum_zig_version
387
  """
@@ -424,54 +433,58 @@ def get_scroll_repos(
424
  (SELECT COUNT(*) FROM repo_dependents WHERE repo_id = rd.id) as dependents_count
425
  FROM repo_data rd
426
  """
427
-
428
  cursor = conn.execute(sql, (actual_per_page, offset))
429
  rows = cursor.fetchall()
430
-
431
  return [row_to_repo_dict(row) for row in rows]
432
 
 
433
  def parse_repo_id(repo_id: str) -> tuple[str, str]:
434
  """Parse repo_id into owner and repo name.
435
-
436
  Args:
437
  repo_id: Repository ID in format 'owner/repo' or 'provider/owner/repo'
438
-
439
  Returns:
440
  Tuple of (owner_name, repo_name)
441
-
442
  Raises:
443
  HTTPException: If repo_id format is invalid
444
  """
445
- parts = repo_id.split('/')
446
-
447
  if len(parts) == 3:
448
  return parts[1], parts[2]
449
  elif len(parts) == 2:
450
  return parts[0], parts[1]
451
  else:
452
  raise HTTPException(
453
- status_code=400,
454
- detail="Invalid repo_id format. Expected 'provider/owner/repo' or 'owner/repo'"
455
  )
456
 
457
- def get_repo_details(conn, repo_id: str, version: Optional[str] = None) -> Dict[str, Any]:
 
 
 
458
  """Get detailed information about a repository.
459
-
460
  Args:
461
  conn: Database connection
462
  repo_id: Repository identifier
463
  version: Optional specific version to fetch details for
464
-
465
  Returns:
466
  Dictionary with complete repository details including:
467
  - If version is None: default branch information with dependencies
468
  - If version is specified: that specific version's information with dependencies
469
-
470
  Raises:
471
  HTTPException: If repository or version not found
472
  """
473
  owner_name, repo_name = parse_repo_id(repo_id)
474
-
475
  # Get repository details
476
  repo_sql = """
477
  SELECT
@@ -481,24 +494,42 @@ def get_repo_details(conn, repo_id: str, version: Optional[str] = None) -> Dict[
481
  license, primary_language, minimum_zig_version
482
  FROM repos WHERE id = ?
483
  """
484
-
485
  cursor = conn.execute(repo_sql, (repo_id,))
486
  repo_row = cursor.fetchone()
487
-
488
  if not repo_row:
489
  raise HTTPException(status_code=404, detail="Repository not found")
490
-
491
  (
492
- r_id, avatar_id, owner, platform, desc, issues,
493
- default_branch, forks, stars, watchers,
494
- pushed_at, created_at, is_archived, is_disabled, is_fork,
495
- license_spdx, primary_language, min_zig_ver
 
 
 
 
 
 
 
 
 
 
 
 
 
 
496
  ) = repo_row
497
-
498
  # Map platform to provider
499
  platform_raw = (platform or "").lower()
500
- provider_id = 'gh' if 'github' in platform_raw else ('cb' if 'codeberg' in platform_raw else 'gh')
501
-
 
 
 
 
502
  # Get all releases for the releases list
503
  releases_sql = """
504
  SELECT version
@@ -508,12 +539,12 @@ def get_repo_details(conn, repo_id: str, version: Optional[str] = None) -> Dict[
508
  """
509
  cursor = conn.execute(releases_sql, (repo_id,))
510
  releases_list = [row[0] for row in cursor.fetchall() if row[0]]
511
-
512
  # Get dependents (same for all versions)
513
  dependents_sql = "SELECT dependent FROM repo_dependents WHERE repo_id = ?"
514
  cursor = conn.execute(dependents_sql, (repo_id,))
515
  dependents = [row[0] for row in cursor.fetchall()]
516
-
517
  # Build response with either version-specific or default branch info
518
  response = {
519
  "id": r_id,
@@ -541,103 +572,125 @@ def get_repo_details(conn, repo_id: str, version: Optional[str] = None) -> Dict[
541
  "releases": releases_list,
542
  "dependents": dependents,
543
  }
544
-
545
  if version:
546
  # Get specific version info
547
  version_info = get_version_info(conn, repo_id, version)
548
- response.update({
549
- "version": version_info["version"],
550
- "published_at": version_info["published_at"],
551
- "minimum_zig_version": version_info["minimum_zig_version"],
552
- "readme_url": version_info["readme_url"],
553
- "is_prerelease": version_info["is_prerelease"],
554
- "dependencies": version_info["dependencies"],
555
- "requested_version": version
556
- })
 
 
557
  else:
558
  # Get default branch info
559
  branch_info = get_default_branch_info(conn, repo_id, default_branch)
560
- response.update({
561
- "minimum_zig_version": min_zig_ver or branch_info["minimum_zig_version"],
562
- "dependencies": branch_info["dependencies"],
563
- "branch_name": branch_info["branch_name"],
564
- "latest_version": branch_info.get("latest_version"),
565
- "readme_url": branch_info.get("readme_url"),
566
- "version": None # Indicates default branch, not a specific version
567
- })
568
-
 
 
 
569
  return response
570
 
 
571
  def check_db_connection():
572
  """Verify database connection is available."""
573
  if not db_conn:
574
  raise HTTPException(status_code=503, detail="Database not initialized")
575
 
 
576
  # API Endpoints
577
 
 
578
  @app.get("/search/packages", tags=["Search"])
579
  async def search_packages_endpoint(
580
  q: str = Query(..., min_length=1, description="Search query"),
581
- limit: int = Query(50, ge=1, le=100, description="Maximum results")
582
  ):
583
  """Search for Zig packages. Returns default branch minimum_zig_version for each result."""
584
  check_db_connection()
585
  return search_repos(db_conn, q, search_type="package", limit=limit)
586
 
 
587
  @app.get("/search/programs", tags=["Search"])
588
  async def search_programs_endpoint(
589
  q: str = Query(..., min_length=1, description="Search query"),
590
- limit: int = Query(50, ge=1, le=100, description="Maximum results")
591
  ):
592
  """Search for Zig programs. Returns default branch minimum_zig_version for each result."""
593
  check_db_connection()
594
  return search_repos(db_conn, q, search_type="program", limit=limit)
595
 
 
596
  @app.get("/packages/latest", tags=["Packages"])
597
  async def get_latest_packages_endpoint(
598
- limit: int = Query(10, ge=1, le=50, description="Number of packages")
599
  ):
600
  """Get latest Zig packages. Returns default branch minimum_zig_version for each result."""
601
  check_db_connection()
602
  return get_latest_repos(db_conn, search_type="package", limit=limit)
603
 
 
604
  @app.get("/programs/latest", tags=["Programs"])
605
  async def get_latest_programs_endpoint(
606
- limit: int = Query(10, ge=1, le=50, description="Number of programs")
607
  ):
608
  """Get latest Zig programs. Returns default branch minimum_zig_version for each result."""
609
  check_db_connection()
610
  return get_latest_repos(db_conn, search_type="program", limit=limit)
611
 
 
612
  @app.get("/packages/scroll", tags=["Packages"])
613
  async def scroll_packages_endpoint(
614
  per_page: int = Query(20, ge=1, le=20, description="Items per page"),
615
- page: int = Query(1, ge=1, description="Page number")
616
  ):
617
  """Get paginated list of packages sorted by stars. Returns default branch minimum_zig_version for each result."""
618
  check_db_connection()
619
- return get_scroll_repos(db_conn, search_type="package", per_page=per_page, page=page)
 
 
 
620
 
621
  @app.get("/programs/scroll", tags=["Programs"])
622
  async def scroll_programs_endpoint(
623
  per_page: int = Query(20, ge=1, le=20, description="Items per page"),
624
- page: int = Query(1, ge=1, description="Page number")
625
  ):
626
  """Get paginated list of programs sorted by stars. Returns default branch minimum_zig_version for each result."""
627
  check_db_connection()
628
- return get_scroll_repos(db_conn, search_type="program", per_page=per_page, page=page)
 
 
 
629
 
630
  @app.get("/packages", tags=["Packages"])
631
  async def get_package_details_endpoint(
632
- q: str = Query(..., alias="q", description="Repository ID (owner/repo or provider/owner/repo)"),
633
- version: Optional[str] = Query(None, description="Specific version to fetch (omit for default branch)")
 
 
 
 
634
  ):
635
  """
636
  Get detailed information about a package.
637
-
638
  - Without version parameter: Returns default branch information with dependencies
639
  - With version parameter: Returns that specific version's information with dependencies
640
-
641
  Examples:
642
  - /packages?q=gh/rohanvashisht1234/zorsig (default branch)
643
  - /packages?q=gh/rohanvashisht1234/zorsig&version=0.0.1 (specific version)
@@ -645,17 +698,22 @@ async def get_package_details_endpoint(
645
  check_db_connection()
646
  return get_repo_details(db_conn, q, version)
647
 
 
648
  @app.get("/programs", tags=["Programs"])
649
  async def get_program_details_endpoint(
650
- q: str = Query(..., alias="q", description="Repository ID (owner/repo or provider/owner/repo)"),
651
- version: Optional[str] = Query(None, description="Specific version to fetch (omit for default branch)")
 
 
 
 
652
  ):
653
  """
654
  Get detailed information about a program.
655
-
656
  - Without version parameter: Returns default branch information with dependencies
657
  - With version parameter: Returns that specific version's information with dependencies
658
-
659
  Examples:
660
  - /programs?q=gh/owner/program (default branch)
661
  - /programs?q=gh/owner/program&version=1.0.0 (specific version)
@@ -663,14 +721,14 @@ async def get_program_details_endpoint(
663
  check_db_connection()
664
  return get_repo_details(db_conn, q, version)
665
 
 
666
  @app.get("/health", tags=["System"])
667
  async def health_check():
668
  """Check API health status."""
669
- return {
670
- "status": "healthy",
671
- "database": "connected" if db_conn else "disconnected"
672
- }
673
 
674
  if __name__ == "__main__":
675
  import uvicorn
676
- uvicorn.run(app, host="0.0.0.0", port=8000)
 
 
13
  # Global database connection
14
  db_conn = None
15
 
16
+
17
  @asynccontextmanager
18
  async def lifespan(app: FastAPI):
19
  """Manage application lifecycle - initialize DB on startup."""
20
  global db_conn
21
  url = os.getenv("DATABASE_URL")
22
  auth_token = os.getenv("API_KEY")
23
+
24
  if not url or not auth_token:
25
  raise RuntimeError("DATABASE_URL and API_KEY environment variables must be set")
26
+
27
  try:
28
+ db_conn = libsql.connect(
29
+ "zigistry-main.db", sync_url=url, auth_token=auth_token
30
+ )
31
  db_conn.sync()
32
  print("Database connection established successfully")
33
  except Exception as e:
34
  raise RuntimeError(f"Failed to connect to database: {e}")
35
+
36
  yield
37
+
38
  # Cleanup on shutdown
39
  if db_conn:
40
  try:
 
42
  except Exception as e:
43
  print(f"Error closing database connection: {e}")
44
 
45
+
46
  app = FastAPI(
47
  title="Zigistry API",
48
  description="API for searching and browsing Zig packages and programs",
49
  version="1.0.0",
50
+ lifespan=lifespan,
51
  )
52
 
53
  # CORS middleware
 
59
  allow_headers=["*"],
60
  )
61
 
62
+
63
  def get_default_branch_info(conn, repo_id: str, default_branch: str) -> Dict[str, Any]:
64
  """Get information about the default branch (build.zig.zon file).
65
+
66
  Args:
67
  conn: Database connection
68
  repo_id: Repository identifier
69
  default_branch: Name of the default branch
70
+
71
  Returns:
72
  Dictionary with default branch information including dependencies and minimum zig version
73
  """
 
86
  ORDER BY published_at DESC
87
  LIMIT 1
88
  """
89
+
90
  cursor = conn.execute(sql, (repo_id,))
91
  release = cursor.fetchone()
92
+
93
  if not release:
94
  return {
95
  "minimum_zig_version": "0.0.0",
96
  "dependencies": [],
97
+ "branch_name": default_branch,
98
  }
99
+
100
  release_id, version, published_at, min_zig_ver, readme_url, is_prerelease = release
101
+
102
  # Get dependencies for this release
103
  deps_sql = """
104
  SELECT name, url, hash, lazy, path
 
107
  """
108
  cursor = conn.execute(deps_sql, (release_id,))
109
  dep_rows = cursor.fetchall()
110
+
111
  dependencies = [
112
  {
113
  "name": row[0],
114
  "url": row[1],
115
  "hash": row[2],
116
  "lazy": bool(row[3]) if row[3] else False,
117
+ "path": row[4],
118
  }
119
  for row in dep_rows
120
  ]
121
+
122
  return {
123
  "minimum_zig_version": min_zig_ver or "0.0.0",
124
  "dependencies": dependencies,
125
  "branch_name": default_branch,
126
  "latest_version": version,
127
+ "readme_url": readme_url,
128
  }
129
 
130
+
131
  def get_version_info(conn, repo_id: str, version: str) -> Dict[str, Any]:
132
  """Get information about a specific version/release.
133
+
134
  Args:
135
  conn: Database connection
136
  repo_id: Repository identifier
137
  version: Version string
138
+
139
  Returns:
140
  Dictionary with version-specific information
141
+
142
  Raises:
143
  HTTPException: If version not found
144
  """
 
153
  FROM releases
154
  WHERE repo_id = ? AND version = ?
155
  """
156
+
157
  cursor = conn.execute(sql, (repo_id, version))
158
  release = cursor.fetchone()
159
+
160
  if not release:
161
  raise HTTPException(
162
+ status_code=404,
163
+ detail=f"Version '{version}' not found for repository '{repo_id}'",
164
  )
165
+
166
  release_id, ver, published_at, min_zig_ver, readme_url, is_prerelease = release
167
+
168
  # Get dependencies for this version
169
  deps_sql = """
170
  SELECT name, url, hash, lazy, path
 
173
  """
174
  cursor = conn.execute(deps_sql, (release_id,))
175
  dep_rows = cursor.fetchall()
176
+
177
  dependencies = [
178
  {
179
  "name": row[0],
180
  "url": row[1],
181
  "hash": row[2],
182
  "lazy": bool(row[3]) if row[3] else False,
183
+ "path": row[4],
184
  }
185
  for row in dep_rows
186
  ]
187
+
188
  return {
189
  "version": ver,
190
  "published_at": str(published_at) if published_at else None,
191
  "minimum_zig_version": min_zig_ver or "0.0.0",
192
  "readme_url": readme_url,
193
  "is_prerelease": bool(is_prerelease),
194
+ "dependencies": dependencies,
195
  }
196
 
197
+
198
  def row_to_repo_dict(row: tuple) -> Dict[str, Any]:
199
  """Convert database row to repository dictionary.
200
+
201
  Handles the common transformation from SQL result to API response format.
202
  """
203
  platform_raw = (row[3] or "").lower()
204
+ provider = (
205
+ "gh"
206
+ if "github" in platform_raw
207
+ else ("cb" if "codeberg" in platform_raw else "gh")
208
+ )
209
  repo_id = row[0]
210
+ repo_name = repo_id.split("/")[-1] if "/" in repo_id else repo_id
211
 
212
  return {
213
  "id": row[0],
 
234
  "dependents_count": row[18] if row[18] is not None else 0,
235
  }
236
 
237
+
238
  def get_type_filter(search_type: str) -> str:
239
  """Generate SQL WHERE clause filter based on search type."""
240
  if search_type == "package":
 
244
  else: # all
245
  return "AND (pkg.repo_id IS NOT NULL OR prog.repo_id IS NOT NULL)"
246
 
247
+
248
  def search_repos(
249
+ conn, query: str, search_type: str = "all", limit: int = 50
 
 
 
250
  ) -> List[Dict[str, Any]]:
251
  """Search repositories by query string.
252
+
253
  Args:
254
  conn: Database connection
255
  query: Search query string
256
  search_type: Filter by 'package', 'program', or 'all'
257
  limit: Maximum number of results to return
258
+
259
  Returns:
260
  List of repository dictionaries with default branch minimum_zig_version
261
  """
262
  search_term_like = f"%{query}%"
263
  # FTS5 query with prefix matching for each word
264
+ fts_query = " ".join(
265
+ '"' + word.replace('"', '""') + '"*' for word in query.split() if word
266
+ )
267
+
268
  type_filter = get_type_filter(search_type)
269
 
270
  sql = f"""
 
314
  rd.stargazer_count DESC
315
  LIMIT ?5
316
  """
317
+
318
  starts_with = f"{query}%"
319
  # Parameters matches ?1 (FTS), ?2 (LIKE), ?3 (Exact), ?4 (Starts with), ?5 (Limit)
320
  cursor = conn.execute(sql, (fts_query, search_term_like, query, starts_with, limit))
321
  rows = cursor.fetchall()
322
+
323
  return [row_to_repo_dict(row) for row in rows]
324
 
325
+
326
  def get_latest_repos(
327
+ conn, search_type: str = "all", limit: int = 10
 
 
328
  ) -> List[Dict[str, Any]]:
329
  """Get latest repositories ordered by creation date.
330
+
331
  Args:
332
  conn: Database connection
333
  search_type: Filter by 'package', 'program', or 'all'
334
  limit: Maximum number of results to return
335
+
336
  Returns:
337
  List of repository dictionaries with default branch minimum_zig_version
338
  """
 
373
  (SELECT COUNT(*) FROM repo_dependents WHERE repo_id = rd.id) as dependents_count
374
  FROM repo_data rd
375
  """
376
+
377
  cursor = conn.execute(sql, (limit,))
378
  rows = cursor.fetchall()
379
+
380
  return [row_to_repo_dict(row) for row in rows]
381
 
382
+
383
  def get_scroll_repos(
384
+ conn, search_type: str = "all", per_page: int = 20, page: int = 1
 
 
 
385
  ) -> List[Dict[str, Any]]:
386
  """Get paginated repositories ordered by star count.
387
+
388
  Args:
389
  conn: Database connection
390
  search_type: Filter by 'package', 'program', or 'all'
391
  per_page: Number of items per page (max 20)
392
  page: Page number (1-indexed)
393
+
394
  Returns:
395
  List of repository dictionaries with default branch minimum_zig_version
396
  """
 
433
  (SELECT COUNT(*) FROM repo_dependents WHERE repo_id = rd.id) as dependents_count
434
  FROM repo_data rd
435
  """
436
+
437
  cursor = conn.execute(sql, (actual_per_page, offset))
438
  rows = cursor.fetchall()
439
+
440
  return [row_to_repo_dict(row) for row in rows]
441
 
442
+
443
  def parse_repo_id(repo_id: str) -> tuple[str, str]:
444
  """Parse repo_id into owner and repo name.
445
+
446
  Args:
447
  repo_id: Repository ID in format 'owner/repo' or 'provider/owner/repo'
448
+
449
  Returns:
450
  Tuple of (owner_name, repo_name)
451
+
452
  Raises:
453
  HTTPException: If repo_id format is invalid
454
  """
455
+ parts = repo_id.split("/")
456
+
457
  if len(parts) == 3:
458
  return parts[1], parts[2]
459
  elif len(parts) == 2:
460
  return parts[0], parts[1]
461
  else:
462
  raise HTTPException(
463
+ status_code=400,
464
+ detail="Invalid repo_id format. Expected 'provider/owner/repo' or 'owner/repo'",
465
  )
466
 
467
+
468
+ def get_repo_details(
469
+ conn, repo_id: str, version: Optional[str] = None
470
+ ) -> Dict[str, Any]:
471
  """Get detailed information about a repository.
472
+
473
  Args:
474
  conn: Database connection
475
  repo_id: Repository identifier
476
  version: Optional specific version to fetch details for
477
+
478
  Returns:
479
  Dictionary with complete repository details including:
480
  - If version is None: default branch information with dependencies
481
  - If version is specified: that specific version's information with dependencies
482
+
483
  Raises:
484
  HTTPException: If repository or version not found
485
  """
486
  owner_name, repo_name = parse_repo_id(repo_id)
487
+
488
  # Get repository details
489
  repo_sql = """
490
  SELECT
 
494
  license, primary_language, minimum_zig_version
495
  FROM repos WHERE id = ?
496
  """
497
+
498
  cursor = conn.execute(repo_sql, (repo_id,))
499
  repo_row = cursor.fetchone()
500
+
501
  if not repo_row:
502
  raise HTTPException(status_code=404, detail="Repository not found")
503
+
504
  (
505
+ r_id,
506
+ avatar_id,
507
+ owner,
508
+ platform,
509
+ desc,
510
+ issues,
511
+ default_branch,
512
+ forks,
513
+ stars,
514
+ watchers,
515
+ pushed_at,
516
+ created_at,
517
+ is_archived,
518
+ is_disabled,
519
+ is_fork,
520
+ license_spdx,
521
+ primary_language,
522
+ min_zig_ver,
523
  ) = repo_row
524
+
525
  # Map platform to provider
526
  platform_raw = (platform or "").lower()
527
+ provider_id = (
528
+ "gh"
529
+ if "github" in platform_raw
530
+ else ("cb" if "codeberg" in platform_raw else "gh")
531
+ )
532
+
533
  # Get all releases for the releases list
534
  releases_sql = """
535
  SELECT version
 
539
  """
540
  cursor = conn.execute(releases_sql, (repo_id,))
541
  releases_list = [row[0] for row in cursor.fetchall() if row[0]]
542
+
543
  # Get dependents (same for all versions)
544
  dependents_sql = "SELECT dependent FROM repo_dependents WHERE repo_id = ?"
545
  cursor = conn.execute(dependents_sql, (repo_id,))
546
  dependents = [row[0] for row in cursor.fetchall()]
547
+
548
  # Build response with either version-specific or default branch info
549
  response = {
550
  "id": r_id,
 
572
  "releases": releases_list,
573
  "dependents": dependents,
574
  }
575
+
576
  if version:
577
  # Get specific version info
578
  version_info = get_version_info(conn, repo_id, version)
579
+ response.update(
580
+ {
581
+ "version": version_info["version"],
582
+ "published_at": version_info["published_at"],
583
+ "minimum_zig_version": version_info["minimum_zig_version"],
584
+ "readme_url": version_info["readme_url"],
585
+ "is_prerelease": version_info["is_prerelease"],
586
+ "dependencies": version_info["dependencies"],
587
+ "requested_version": version,
588
+ }
589
+ )
590
  else:
591
  # Get default branch info
592
  branch_info = get_default_branch_info(conn, repo_id, default_branch)
593
+ response.update(
594
+ {
595
+ "minimum_zig_version": min_zig_ver
596
+ or branch_info["minimum_zig_version"],
597
+ "dependencies": branch_info["dependencies"],
598
+ "branch_name": branch_info["branch_name"],
599
+ "latest_version": branch_info.get("latest_version"),
600
+ "readme_url": branch_info.get("readme_url"),
601
+ "version": None, # Indicates default branch, not a specific version
602
+ }
603
+ )
604
+
605
  return response
606
 
607
+
608
  def check_db_connection():
609
  """Verify database connection is available."""
610
  if not db_conn:
611
  raise HTTPException(status_code=503, detail="Database not initialized")
612
 
613
+
614
  # API Endpoints
615
 
616
+
617
  @app.get("/search/packages", tags=["Search"])
618
  async def search_packages_endpoint(
619
  q: str = Query(..., min_length=1, description="Search query"),
620
+ limit: int = Query(50, ge=1, le=100, description="Maximum results"),
621
  ):
622
  """Search for Zig packages. Returns default branch minimum_zig_version for each result."""
623
  check_db_connection()
624
  return search_repos(db_conn, q, search_type="package", limit=limit)
625
 
626
+
627
  @app.get("/search/programs", tags=["Search"])
628
  async def search_programs_endpoint(
629
  q: str = Query(..., min_length=1, description="Search query"),
630
+ limit: int = Query(50, ge=1, le=100, description="Maximum results"),
631
  ):
632
  """Search for Zig programs. Returns default branch minimum_zig_version for each result."""
633
  check_db_connection()
634
  return search_repos(db_conn, q, search_type="program", limit=limit)
635
 
636
+
637
  @app.get("/packages/latest", tags=["Packages"])
638
  async def get_latest_packages_endpoint(
639
+ limit: int = Query(10, ge=1, le=50, description="Number of packages"),
640
  ):
641
  """Get latest Zig packages. Returns default branch minimum_zig_version for each result."""
642
  check_db_connection()
643
  return get_latest_repos(db_conn, search_type="package", limit=limit)
644
 
645
+
646
  @app.get("/programs/latest", tags=["Programs"])
647
  async def get_latest_programs_endpoint(
648
+ limit: int = Query(10, ge=1, le=50, description="Number of programs"),
649
  ):
650
  """Get latest Zig programs. Returns default branch minimum_zig_version for each result."""
651
  check_db_connection()
652
  return get_latest_repos(db_conn, search_type="program", limit=limit)
653
 
654
+
655
  @app.get("/packages/scroll", tags=["Packages"])
656
  async def scroll_packages_endpoint(
657
  per_page: int = Query(20, ge=1, le=20, description="Items per page"),
658
+ page: int = Query(1, ge=1, description="Page number"),
659
  ):
660
  """Get paginated list of packages sorted by stars. Returns default branch minimum_zig_version for each result."""
661
  check_db_connection()
662
+ return get_scroll_repos(
663
+ db_conn, search_type="package", per_page=per_page, page=page
664
+ )
665
+
666
 
667
  @app.get("/programs/scroll", tags=["Programs"])
668
  async def scroll_programs_endpoint(
669
  per_page: int = Query(20, ge=1, le=20, description="Items per page"),
670
+ page: int = Query(1, ge=1, description="Page number"),
671
  ):
672
  """Get paginated list of programs sorted by stars. Returns default branch minimum_zig_version for each result."""
673
  check_db_connection()
674
+ return get_scroll_repos(
675
+ db_conn, search_type="program", per_page=per_page, page=page
676
+ )
677
+
678
 
679
  @app.get("/packages", tags=["Packages"])
680
  async def get_package_details_endpoint(
681
+ q: str = Query(
682
+ ..., alias="q", description="Repository ID (owner/repo or provider/owner/repo)"
683
+ ),
684
+ version: Optional[str] = Query(
685
+ None, description="Specific version to fetch (omit for default branch)"
686
+ ),
687
  ):
688
  """
689
  Get detailed information about a package.
690
+
691
  - Without version parameter: Returns default branch information with dependencies
692
  - With version parameter: Returns that specific version's information with dependencies
693
+
694
  Examples:
695
  - /packages?q=gh/rohanvashisht1234/zorsig (default branch)
696
  - /packages?q=gh/rohanvashisht1234/zorsig&version=0.0.1 (specific version)
 
698
  check_db_connection()
699
  return get_repo_details(db_conn, q, version)
700
 
701
+
702
  @app.get("/programs", tags=["Programs"])
703
  async def get_program_details_endpoint(
704
+ q: str = Query(
705
+ ..., alias="q", description="Repository ID (owner/repo or provider/owner/repo)"
706
+ ),
707
+ version: Optional[str] = Query(
708
+ None, description="Specific version to fetch (omit for default branch)"
709
+ ),
710
  ):
711
  """
712
  Get detailed information about a program.
713
+
714
  - Without version parameter: Returns default branch information with dependencies
715
  - With version parameter: Returns that specific version's information with dependencies
716
+
717
  Examples:
718
  - /programs?q=gh/owner/program (default branch)
719
  - /programs?q=gh/owner/program&version=1.0.0 (specific version)
 
721
  check_db_connection()
722
  return get_repo_details(db_conn, q, version)
723
 
724
+
725
  @app.get("/health", tags=["System"])
726
  async def health_check():
727
  """Check API health status."""
728
+ return {"status": "healthy", "database": "connected" if db_conn else "disconnected"}
729
+
 
 
730
 
731
  if __name__ == "__main__":
732
  import uvicorn
733
+
734
+ uvicorn.run(app, host="0.0.0.0", port=7860)