Files
GitMa/backend/app/services/repo_service.py
panw 44921c5646 feat: complete Git Repo Manager MVP implementation
Backend (Phase 1-6):
- Pydantic schemas for request/response validation
- Service layer (SSH Key, Server, Repo, Sync)
- API routes with authentication
- FastAPI main application with lifespan management
- ORM models (SshKey, Server, Repo, SyncLog)

Frontend (Phase 7):
- Vue 3 + Element Plus + Pinia + Vue Router
- API client with Axios and interceptors
- State management stores
- All page components (Dashboard, Servers, Repos, SyncLogs, SshKeys, Settings)

Deployment (Phase 8):
- README with quick start guide
- Startup script (start.sh)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 16:30:13 +08:00

228 lines
5.8 KiB
Python

"""
Repo Service.
Business logic for repository management including:
- Creating repository records
- Listing repositories by server
- Retrieving repository details
- Updating repository status
- Deleting repository records
"""
import time
from typing import List, Optional
from pathlib import Path
from sqlalchemy.orm import Session
from app.models.repo import Repo
from app.models.server import Server
from app.config import get_settings
class RepoService:
"""
Service for managing repository records.
Handles CRUD operations for repositories that are being
mirrored from Gitea servers.
"""
def __init__(self, db: Session):
"""
Initialize the service with a database session.
Args:
db: SQLAlchemy database session
"""
self.db = db
self.settings = get_settings()
def create_repo(
self,
server_id: int,
name: str,
full_name: str,
clone_url: str,
local_path: Optional[str] = None
) -> Repo:
"""
Create a new repository record.
Args:
server_id: ID of the server this repo belongs to
name: Repository name (e.g., "my-repo")
full_name: Full repository name (e.g., "owner/my-repo")
clone_url: Git clone URL (typically SSH format)
local_path: Optional local path for the mirrored repo.
If not provided, will be generated based on server and repo name.
Returns:
Created Repo model instance
Raises:
ValueError: If server_id is invalid
"""
# Verify server exists
server = self.db.query(Server).filter_by(id=server_id).first()
if not server:
raise ValueError(f"Server not found with ID {server_id}")
# Generate local_path if not provided
if local_path is None:
local_path = str(Path(server.local_path) / name)
# Create the repo record
current_time = int(time.time())
repo = Repo(
server_id=server_id,
name=name,
full_name=full_name,
clone_url=clone_url,
local_path=local_path,
status="pending",
last_sync_at=None,
created_at=current_time
)
self.db.add(repo)
self.db.commit()
self.db.refresh(repo)
return repo
def list_repos(self, server_id: int) -> List[Repo]:
"""
List all repositories for a specific server.
Args:
server_id: ID of the server
Returns:
List of Repo model instances for the server, ordered by creation time
"""
return self.db.query(Repo).filter_by(
server_id=server_id
).order_by(Repo.created_at).all()
def get_repo(self, repo_id: int) -> Optional[Repo]:
"""
Get a repository by ID.
Args:
repo_id: ID of the repository
Returns:
Repo model instance or None if not found
"""
return self.db.query(Repo).filter_by(id=repo_id).first()
def update_repo_status(self, repo_id: int, status: str) -> Repo:
"""
Update the status of a repository.
Common status values:
- "pending": Initial state, not yet synced
- "syncing": Currently being synced
- "success": Last sync was successful
- "failed": Last sync failed
Args:
repo_id: ID of the repository
status: New status value
Returns:
Updated Repo model instance
Raises:
ValueError: If repo not found
"""
repo = self.get_repo(repo_id)
if not repo:
raise ValueError(f"Repo not found with ID {repo_id}")
repo.status = status
# If status is success, update last_sync_at
if status == "success":
repo.last_sync_at = int(time.time())
self.db.commit()
self.db.refresh(repo)
return repo
def delete_repo(self, repo_id: int) -> bool:
"""
Delete a repository.
Args:
repo_id: ID of the repository to delete
Returns:
True if deleted, False if not found
"""
repo = self.get_repo(repo_id)
if not repo:
return False
self.db.delete(repo)
self.db.commit()
return True
def get_repo_by_name(self, server_id: int, name: str) -> Optional[Repo]:
"""
Get a repository by server and name.
Args:
server_id: ID of the server
name: Repository name
Returns:
Repo model instance or None if not found
"""
return self.db.query(Repo).filter_by(
server_id=server_id,
name=name
).first()
def list_all_repos(self) -> List[Repo]:
"""
List all repositories across all servers.
Returns:
List of all Repo model instances, ordered by creation time
"""
return self.db.query(Repo).order_by(Repo.created_at).all()
def update_repo(
self,
repo_id: int,
**kwargs
) -> Repo:
"""
Update a repository's configuration.
Args:
repo_id: ID of the repository to update
**kwargs: Fields to update (name, full_name, clone_url, local_path, status)
Returns:
Updated Repo model instance
Raises:
ValueError: If repo not found
"""
repo = self.get_repo(repo_id)
if not repo:
raise ValueError(f"Repo not found with ID {repo_id}")
# Update fields
for key, value in kwargs.items():
if hasattr(repo, key):
setattr(repo, key, value)
self.db.commit()
self.db.refresh(repo)
return repo