github_backup.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. """Pydantic schemas for GitHub backup configuration."""
  2. import re
  3. from datetime import datetime
  4. from pydantic import BaseModel, Field, model_validator
  5. from backend.app.core.compat import StrEnum
  6. class ScheduleType(StrEnum):
  7. """Backup schedule types."""
  8. HOURLY = "hourly"
  9. DAILY = "daily"
  10. WEEKLY = "weekly"
  11. class ProviderType(StrEnum):
  12. """Git hosting provider types."""
  13. GITHUB = "github"
  14. GITLAB = "gitlab"
  15. GITEA = "gitea"
  16. FORGEJO = "forgejo"
  17. class GitHubBackupConfigCreate(BaseModel):
  18. """Schema for creating/updating GitHub backup config."""
  19. repository_url: str = Field(..., min_length=1, max_length=500, description="Git repository URL")
  20. access_token: str = Field(..., min_length=1, description="Personal Access Token")
  21. branch: str = Field(default="main", max_length=100, description="Branch to push to")
  22. provider: ProviderType = Field(default=ProviderType.GITHUB, description="Git hosting provider")
  23. schedule_enabled: bool = Field(default=False, description="Enable scheduled backups")
  24. schedule_type: ScheduleType = Field(default=ScheduleType.DAILY, description="Schedule frequency")
  25. backup_kprofiles: bool = Field(default=True, description="Backup K-profiles")
  26. backup_cloud_profiles: bool = Field(default=True, description="Backup Bambu Cloud profiles")
  27. backup_settings: bool = Field(default=False, description="Backup app settings")
  28. backup_spools: bool = Field(default=False, description="Backup spool inventory")
  29. backup_archives: bool = Field(default=False, description="Backup print archive history")
  30. allow_insecure_http: bool = Field(default=False, description="Allow HTTP (non-TLS) repository URLs")
  31. enabled: bool = Field(default=True, description="Enable backup feature")
  32. @model_validator(mode="after")
  33. def validate_repo_url(self) -> "GitHubBackupConfigCreate":
  34. url = self.repository_url.strip().rstrip("/")
  35. self.repository_url = url
  36. https_or_ssh = [
  37. r"^https://[\w.-]+(:\d+)?/[\w.-]+(\/[\w.-]+)+(?:\.git)?/?$",
  38. r"^git@[\w.-]+:[\w.-]+(\/[\w.-]+)+(?:\.git)?$",
  39. ]
  40. http_pattern = r"^http://[\w.-]+(:\d+)?/[\w.-]+(\/[\w.-]+)+(?:\.git)?/?$"
  41. if any(re.match(p, url) for p in https_or_ssh):
  42. return self
  43. if re.match(http_pattern, url):
  44. if not self.allow_insecure_http:
  45. raise ValueError(
  46. "This URL uses HTTP instead of HTTPS. "
  47. "Enable 'Allow insecure HTTP' if your instance does not use TLS."
  48. )
  49. return self
  50. raise ValueError(
  51. "Invalid Git repository URL. Expected: https://host/owner/repo, "
  52. "http://host/owner/repo (with 'Allow insecure HTTP' enabled), or git@host:owner/repo"
  53. )
  54. class GitHubBackupConfigUpdate(BaseModel):
  55. """Schema for updating GitHub backup config (all fields optional)."""
  56. repository_url: str | None = Field(default=None, max_length=500)
  57. access_token: str | None = Field(default=None)
  58. branch: str | None = Field(default=None, max_length=100)
  59. provider: ProviderType | None = None
  60. schedule_enabled: bool | None = None
  61. schedule_type: ScheduleType | None = None
  62. backup_kprofiles: bool | None = None
  63. backup_cloud_profiles: bool | None = None
  64. backup_settings: bool | None = None
  65. backup_spools: bool | None = None
  66. backup_archives: bool | None = None
  67. allow_insecure_http: bool | None = None
  68. enabled: bool | None = None
  69. @model_validator(mode="after")
  70. def validate_repo_url(self) -> "GitHubBackupConfigUpdate":
  71. if self.repository_url is None:
  72. return self
  73. url = self.repository_url.strip().rstrip("/")
  74. self.repository_url = url
  75. valid_patterns = [
  76. r"^https?://[\w.-]+(:\d+)?/[\w.-]+(\/[\w.-]+)+(?:\.git)?/?$",
  77. r"^git@[\w.-]+:[\w.-]+(\/[\w.-]+)+(?:\.git)?$",
  78. ]
  79. if not any(re.match(p, url) for p in valid_patterns):
  80. raise ValueError(
  81. "Invalid repository URL. Expected: https://host/owner/repo, "
  82. "http://host/owner/repo, or git@host:owner/repo"
  83. )
  84. return self
  85. class GitHubBackupConfigResponse(BaseModel):
  86. """Schema for GitHub backup config API response."""
  87. id: int
  88. repository_url: str
  89. has_token: bool = Field(description="Whether an access token is configured")
  90. branch: str
  91. provider: str
  92. allow_insecure_http: bool
  93. schedule_enabled: bool
  94. schedule_type: str
  95. backup_kprofiles: bool
  96. backup_cloud_profiles: bool
  97. backup_settings: bool
  98. backup_spools: bool
  99. backup_archives: bool
  100. enabled: bool
  101. last_backup_at: datetime | None
  102. last_backup_status: str | None
  103. last_backup_message: str | None
  104. last_backup_commit_sha: str | None
  105. next_scheduled_run: datetime | None
  106. created_at: datetime
  107. updated_at: datetime
  108. class Config:
  109. from_attributes = True
  110. class GitHubBackupLogResponse(BaseModel):
  111. """Schema for backup log API response."""
  112. id: int
  113. config_id: int
  114. started_at: datetime
  115. completed_at: datetime | None
  116. status: str
  117. trigger: str
  118. commit_sha: str | None
  119. files_changed: int
  120. error_message: str | None
  121. class Config:
  122. from_attributes = True
  123. class GitHubBackupStatus(BaseModel):
  124. """Schema for current backup status."""
  125. configured: bool = Field(description="Whether backup is configured")
  126. enabled: bool = Field(description="Whether backup is enabled")
  127. is_running: bool = Field(description="Whether a backup is currently running")
  128. progress: str | None = Field(default=None, description="Current backup progress message")
  129. last_backup_at: datetime | None
  130. last_backup_status: str | None
  131. next_scheduled_run: datetime | None
  132. class GitHubTestConnectionResponse(BaseModel):
  133. """Schema for test connection response."""
  134. success: bool
  135. message: str
  136. repo_name: str | None = None
  137. permissions: dict | None = None
  138. class GitHubBackupTriggerResponse(BaseModel):
  139. """Schema for manual backup trigger response."""
  140. success: bool
  141. message: str
  142. log_id: int | None = None
  143. commit_sha: str | None = None
  144. files_changed: int = 0