feat: git label import

This commit is contained in:
null 2026-05-19 21:34:11 -05:00
parent d6d094a67d
commit 017ab4951a
7 changed files with 99 additions and 0 deletions

View File

@ -339,6 +339,7 @@ def _mask_repository(repository: ForgejoRepository, connection: ForgejoConnectio
"active": repository.active, "active": repository.active,
"connection": _create_connection_info(connection) if connection is not None else None, "connection": _create_connection_info(connection) if connection is not None else None,
"has_webhook_secret": bool(repository.webhook_secret), "has_webhook_secret": bool(repository.webhook_secret),
"labels": repository.labels if repository.labels is not None else [],
"last_sync_at": repository.last_sync_at, "last_sync_at": repository.last_sync_at,
"last_sync_error": repository.last_sync_error, "last_sync_error": repository.last_sync_error,
"created_at": repository.created_at, "created_at": repository.created_at,

View File

@ -5,6 +5,8 @@ from __future__ import annotations
from datetime import datetime from datetime import datetime
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from sqlalchemy import Column
from sqlalchemy import JSON
from sqlmodel import Field from sqlmodel import Field
from app.core.time import utcnow from app.core.time import utcnow
@ -27,6 +29,10 @@ class ForgejoRepository(QueryModel, table=True):
default_branch: str = Field(default="main") default_branch: str = Field(default="main")
active: bool = Field(default=True) active: bool = Field(default=True)
webhook_secret: str | None = Field(default=None) webhook_secret: str | None = Field(default=None)
labels: list[dict[str, object]] = Field(
default_factory=list,
sa_column=Column(JSON, nullable=False, server_default="[]"),
)
last_sync_at: datetime | None = Field(default=None) last_sync_at: datetime | None = Field(default=None)
last_sync_error: str | None = Field(default=None) last_sync_error: str | None = Field(default=None)
created_at: datetime = Field(default_factory=utcnow) created_at: datetime = Field(default_factory=utcnow)

View File

@ -108,6 +108,7 @@ class ForgejoRepositoryRead(ForgejoRepositoryBase):
connection_id: UUID connection_id: UUID
connection: ForgejoRepositoryConnectionInfo connection: ForgejoRepositoryConnectionInfo
has_webhook_secret: bool = False has_webhook_secret: bool = False
labels: list[dict[str, object]] = Field(default_factory=list)
last_sync_at: datetime | None last_sync_at: datetime | None
last_sync_error: str | None last_sync_error: str | None
created_at: datetime created_at: datetime

View File

@ -157,6 +157,32 @@ class ForgejoAPIClient:
response.raise_for_status() response.raise_for_status()
return response.json() return response.json()
async def list_labels(
self,
owner: str,
repo: str,
limit: int = 50,
) -> list[dict[str, object]]:
"""
List all labels defined on a repository.
Args:
owner: Repository owner
repo: Repository name
limit: Max labels to fetch per page
Returns:
List of label dicts with id, name, color, description.
"""
client = await self._get_client()
response = await client.get(
f"/api/v1/repos/{owner}/{repo}/labels",
params={"limit": limit, "page": 1},
)
response.raise_for_status()
data = response.json()
return list(data) if isinstance(data, list) else []
async def list_user_repos( async def list_user_repos(
self, self,
limit: int = 50, limit: int = 50,

View File

@ -146,6 +146,30 @@ class IssueSyncService:
break break
current_page += 1 current_page += 1
# Sync repository label catalog
try:
async with get_forgejo_client(connection) as client:
fetched_labels = await client.list_labels(
owner=repository.owner,
repo=repository.repo,
)
repository.labels = [
{
"id": lbl.get("id"),
"name": lbl.get("name", ""),
"color": lbl.get("color", ""),
"description": lbl.get("description") or "",
}
for lbl in fetched_labels
if lbl.get("name")
]
except Exception as exc:
logger.warning(
"label_sync_failed",
repository_id=str(repository_id),
error=str(exc),
)
# Update repository sync metadata # Update repository sync metadata
repository.last_sync_at = utcnow() repository.last_sync_at = utcnow()
repository.last_sync_error = None repository.last_sync_error = None

View File

@ -0,0 +1,33 @@
"""add labels column to forgejo_repositories
Revision ID: a1b2c3d4e5f7
Revises: f5a2b3c4d5e6
Create Date: 2026-05-19 00:00:00.000000
"""
from __future__ import annotations
import sqlalchemy as sa
from alembic import op
revision = "a1b2c3d4e5f7"
down_revision = "f5a2b3c4d5e6"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column(
"forgejo_repositories",
sa.Column(
"labels",
sa.JSON(),
nullable=False,
server_default="[]",
),
)
def downgrade() -> None:
op.drop_column("forgejo_repositories", "labels")

View File

@ -27,6 +27,13 @@ export interface ForgejoConnectionUpdate {
} }
// Forgejo Repository types // Forgejo Repository types
export interface ForgejoRepoLabel {
id: number | null;
name: string;
color: string;
description: string;
}
export interface ForgejoRepository { export interface ForgejoRepository {
id: string; id: string;
organization_id: string; organization_id: string;
@ -37,6 +44,7 @@ export interface ForgejoRepository {
default_branch: string; default_branch: string;
active: boolean; active: boolean;
has_webhook_secret: boolean; has_webhook_secret: boolean;
labels: ForgejoRepoLabel[];
connection: ForgejoConnection; connection: ForgejoConnection;
last_sync_at: string | null; last_sync_at: string | null;
last_sync_error: string | null; last_sync_error: string | null;