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>
293 lines
8.5 KiB
Python
293 lines
8.5 KiB
Python
"""
|
|
Servers API routes.
|
|
|
|
Provides CRUD endpoints for Gitea server management:
|
|
- POST /api/servers - Create a new server
|
|
- GET /api/servers - List all servers
|
|
- GET /api/servers/{id} - Get a specific server
|
|
- PUT /api/servers/{id} - Update a server
|
|
- DELETE /api/servers/{id} - Delete a server
|
|
"""
|
|
from typing import List
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.api.deps import get_db_session, require_auth
|
|
from app.schemas.server import ServerCreate, ServerUpdate, ServerResponse
|
|
from app.schemas.common import SuccessResponse
|
|
from app.services.server_service import ServerService
|
|
|
|
|
|
router = APIRouter(prefix="/api/servers", tags=["Servers"])
|
|
|
|
|
|
@router.post("", response_model=SuccessResponse[ServerResponse], status_code=status.HTTP_201_CREATED)
|
|
def create_server(
|
|
server_data: ServerCreate,
|
|
db: Session = Depends(get_db_session),
|
|
_auth: None = Depends(require_auth)
|
|
):
|
|
"""
|
|
Create a new Gitea server.
|
|
|
|
The API token will be encrypted before storage.
|
|
The name must be unique across all servers.
|
|
A local storage path will be automatically generated based on the server name.
|
|
|
|
Args:
|
|
server_data: Server creation data
|
|
db: Database session (injected)
|
|
_auth: Authentication requirement (injected)
|
|
|
|
Returns:
|
|
SuccessResponse containing the created server
|
|
|
|
Raises:
|
|
HTTPException 400: If validation fails or name already exists
|
|
HTTPException 401: If authentication fails
|
|
"""
|
|
service = ServerService(db)
|
|
|
|
try:
|
|
server = service.create_server(
|
|
name=server_data.name,
|
|
url=server_data.url,
|
|
api_token=server_data.api_token,
|
|
ssh_key_id=server_data.ssh_key_id,
|
|
sync_enabled=server_data.sync_enabled,
|
|
schedule_cron=server_data.schedule_cron
|
|
)
|
|
|
|
return SuccessResponse(
|
|
code=0,
|
|
data=ServerResponse(
|
|
id=server.id,
|
|
name=server.name,
|
|
url=server.url,
|
|
ssh_key_id=server.ssh_key_id,
|
|
sync_enabled=server.sync_enabled,
|
|
schedule_cron=server.schedule_cron,
|
|
local_path=server.local_path,
|
|
status=server.status,
|
|
created_at=server.created_at,
|
|
updated_at=server.updated_at
|
|
),
|
|
message="Server created successfully"
|
|
)
|
|
|
|
except ValueError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=str(e)
|
|
)
|
|
|
|
|
|
@router.get("", response_model=SuccessResponse[List[ServerResponse]])
|
|
def list_servers(
|
|
db: Session = Depends(get_db_session),
|
|
_auth: None = Depends(require_auth)
|
|
):
|
|
"""
|
|
List all servers.
|
|
|
|
Returns all servers ordered by creation time.
|
|
API tokens are not included in the response.
|
|
|
|
Args:
|
|
db: Database session (injected)
|
|
_auth: Authentication requirement (injected)
|
|
|
|
Returns:
|
|
SuccessResponse containing list of servers
|
|
|
|
Raises:
|
|
HTTPException 401: If authentication fails
|
|
"""
|
|
service = ServerService(db)
|
|
servers = service.list_servers()
|
|
|
|
return SuccessResponse(
|
|
code=0,
|
|
data=[
|
|
ServerResponse(
|
|
id=server.id,
|
|
name=server.name,
|
|
url=server.url,
|
|
ssh_key_id=server.ssh_key_id,
|
|
sync_enabled=server.sync_enabled,
|
|
schedule_cron=server.schedule_cron,
|
|
local_path=server.local_path,
|
|
status=server.status,
|
|
created_at=server.created_at,
|
|
updated_at=server.updated_at
|
|
)
|
|
for server in servers
|
|
],
|
|
message=f"Retrieved {len(servers)} server(s)"
|
|
)
|
|
|
|
|
|
@router.get("/{server_id}", response_model=SuccessResponse[ServerResponse])
|
|
def get_server(
|
|
server_id: int,
|
|
db: Session = Depends(get_db_session),
|
|
_auth: None = Depends(require_auth)
|
|
):
|
|
"""
|
|
Get a specific server by ID.
|
|
|
|
Args:
|
|
server_id: ID of the server
|
|
db: Database session (injected)
|
|
_auth: Authentication requirement (injected)
|
|
|
|
Returns:
|
|
SuccessResponse containing the server
|
|
|
|
Raises:
|
|
HTTPException 401: If authentication fails
|
|
HTTPException 404: If server not found
|
|
"""
|
|
service = ServerService(db)
|
|
server = service.get_server(server_id)
|
|
|
|
if server is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Server with ID {server_id} not found"
|
|
)
|
|
|
|
return SuccessResponse(
|
|
code=0,
|
|
data=ServerResponse(
|
|
id=server.id,
|
|
name=server.name,
|
|
url=server.url,
|
|
ssh_key_id=server.ssh_key_id,
|
|
sync_enabled=server.sync_enabled,
|
|
schedule_cron=server.schedule_cron,
|
|
local_path=server.local_path,
|
|
status=server.status,
|
|
created_at=server.created_at,
|
|
updated_at=server.updated_at
|
|
),
|
|
message="Server retrieved successfully"
|
|
)
|
|
|
|
|
|
@router.put("/{server_id}", response_model=SuccessResponse[ServerResponse])
|
|
def update_server(
|
|
server_id: int,
|
|
server_data: ServerUpdate,
|
|
db: Session = Depends(get_db_session),
|
|
_auth: None = Depends(require_auth)
|
|
):
|
|
"""
|
|
Update a server.
|
|
|
|
Only the fields provided in the request body will be updated.
|
|
If api_token is provided, it will be encrypted before storage.
|
|
If name is changed, the local_path will be updated accordingly.
|
|
|
|
Args:
|
|
server_id: ID of the server to update
|
|
server_data: Server update data (all fields optional)
|
|
db: Database session (injected)
|
|
_auth: Authentication requirement (injected)
|
|
|
|
Returns:
|
|
SuccessResponse containing the updated server
|
|
|
|
Raises:
|
|
HTTPException 400: If validation fails
|
|
HTTPException 401: If authentication fails
|
|
HTTPException 404: If server not found
|
|
"""
|
|
service = ServerService(db)
|
|
|
|
# Build update dict from non-None fields
|
|
update_data = {}
|
|
if server_data.name is not None:
|
|
update_data['name'] = server_data.name
|
|
if server_data.url is not None:
|
|
update_data['url'] = server_data.url
|
|
if server_data.api_token is not None:
|
|
update_data['api_token'] = server_data.api_token
|
|
if server_data.ssh_key_id is not None:
|
|
update_data['ssh_key_id'] = server_data.ssh_key_id
|
|
if server_data.sync_enabled is not None:
|
|
update_data['sync_enabled'] = server_data.sync_enabled
|
|
if server_data.schedule_cron is not None:
|
|
update_data['schedule_cron'] = server_data.schedule_cron
|
|
if server_data.status is not None:
|
|
update_data['status'] = server_data.status
|
|
|
|
if not update_data:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="No fields provided for update"
|
|
)
|
|
|
|
try:
|
|
server = service.update_server(server_id, **update_data)
|
|
|
|
return SuccessResponse(
|
|
code=0,
|
|
data=ServerResponse(
|
|
id=server.id,
|
|
name=server.name,
|
|
url=server.url,
|
|
ssh_key_id=server.ssh_key_id,
|
|
sync_enabled=server.sync_enabled,
|
|
schedule_cron=server.schedule_cron,
|
|
local_path=server.local_path,
|
|
status=server.status,
|
|
created_at=server.created_at,
|
|
updated_at=server.updated_at
|
|
),
|
|
message="Server updated successfully"
|
|
)
|
|
|
|
except ValueError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=str(e)
|
|
)
|
|
|
|
|
|
@router.delete("/{server_id}", response_model=SuccessResponse[dict])
|
|
def delete_server(
|
|
server_id: int,
|
|
db: Session = Depends(get_db_session),
|
|
_auth: None = Depends(require_auth)
|
|
):
|
|
"""
|
|
Delete a server.
|
|
|
|
Args:
|
|
server_id: ID of the server to delete
|
|
db: Database session (injected)
|
|
_auth: Authentication requirement (injected)
|
|
|
|
Returns:
|
|
SuccessResponse with empty data
|
|
|
|
Raises:
|
|
HTTPException 401: If authentication fails
|
|
HTTPException 404: If server not found
|
|
"""
|
|
service = ServerService(db)
|
|
deleted = service.delete_server(server_id)
|
|
|
|
if not deleted:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Server with ID {server_id} not found"
|
|
)
|
|
|
|
return SuccessResponse(
|
|
code=0,
|
|
data={},
|
|
message="Server deleted successfully"
|
|
)
|