feat: add custom download URL for manual update method
- Add custom_download_url field to Version model - Show custom download URL input when update method is 'manual' - Use platform default URL if custom URL is not set - Add i18n translations for new field Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -229,7 +229,8 @@ type Version struct {
|
|||||||
EntryFile string `gorm:"size:255" json:"entry_file"`
|
EntryFile string `gorm:"size:255" json:"entry_file"`
|
||||||
UpdateStrategy string `gorm:"size:20;default:optional" json:"update_strategy"`
|
UpdateStrategy string `gorm:"size:20;default:optional" json:"update_strategy"`
|
||||||
UpdateType string `gorm:"size:20;default:full" json:"update_type"`
|
UpdateType string `gorm:"size:20;default:full" json:"update_type"`
|
||||||
UpdateMethod string `gorm:"size:20;default:auto" json:"update_method"`
|
UpdateMethod string `gorm:"size:20;default:auto" json:"update_method"`
|
||||||
|
CustomDownloadURL string `gorm:"size:500" json:"custom_download_url"`
|
||||||
Description string `gorm:"type:text" json:"description"`
|
Description string `gorm:"type:text" json:"description"`
|
||||||
Changelog string `gorm:"type:text" json:"changelog"`
|
Changelog string `gorm:"type:text" json:"changelog"`
|
||||||
Status string `gorm:"size:20;default:active" json:"status"`
|
Status string `gorm:"size:20;default:active" json:"status"`
|
||||||
|
|||||||
@@ -226,38 +226,40 @@ func handleGetVersionByID(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
response.Success(c, gin.H{
|
response.Success(c, gin.H{
|
||||||
"id": version.ID,
|
"id": version.ID,
|
||||||
"application_id": version.ApplicationID,
|
"application_id": version.ApplicationID,
|
||||||
"application_name": appNameMap[version.ApplicationID],
|
"application_name": appNameMap[version.ApplicationID],
|
||||||
"version": version.Version,
|
"version": version.Version,
|
||||||
"description": version.Description,
|
"description": version.Description,
|
||||||
"file_path": version.FilePath,
|
"file_path": version.FilePath,
|
||||||
"file_size": version.FileSize,
|
"file_size": version.FileSize,
|
||||||
"file_hash": version.FileHash,
|
"file_hash": version.FileHash,
|
||||||
"update_strategy": version.UpdateStrategy,
|
"update_strategy": version.UpdateStrategy,
|
||||||
"update_type": version.UpdateType,
|
"update_type": version.UpdateType,
|
||||||
"update_method": version.UpdateMethod,
|
"update_method": version.UpdateMethod,
|
||||||
"changelog": version.Changelog,
|
"custom_download_url": version.CustomDownloadURL,
|
||||||
"status": computedStatus,
|
"changelog": version.Changelog,
|
||||||
"base_version_id": version.BaseVersionID,
|
"status": computedStatus,
|
||||||
"files": version.Files,
|
"base_version_id": version.BaseVersionID,
|
||||||
"created_at": version.CreatedAt,
|
"files": version.Files,
|
||||||
"updated_at": version.UpdatedAt,
|
"created_at": version.CreatedAt,
|
||||||
|
"updated_at": version.UpdatedAt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateVersionRequest struct {
|
type CreateVersionRequest struct {
|
||||||
ApplicationID uint `json:"application_id"`
|
ApplicationID uint `json:"application_id"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
FilePath string `json:"file_path"`
|
FilePath string `json:"file_path"`
|
||||||
FileSize int64 `json:"file_size"`
|
FileSize int64 `json:"file_size"`
|
||||||
FileHash string `json:"file_hash"`
|
FileHash string `json:"file_hash"`
|
||||||
EntryFile string `json:"entry_file"`
|
EntryFile string `json:"entry_file"`
|
||||||
UpdateStrategy string `json:"update_strategy"`
|
UpdateStrategy string `json:"update_strategy"`
|
||||||
UpdateType string `json:"update_type"`
|
UpdateType string `json:"update_type"`
|
||||||
UpdateMethod string `json:"update_method"`
|
UpdateMethod string `json:"update_method"`
|
||||||
Description string `json:"description"`
|
CustomDownloadURL string `json:"custom_download_url"`
|
||||||
Changelog string `json:"changelog"`
|
Description string `json:"description"`
|
||||||
|
Changelog string `json:"changelog"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCreateVersionGlobal(c *gin.Context) {
|
func handleCreateVersionGlobal(c *gin.Context) {
|
||||||
@@ -287,18 +289,19 @@ func handleCreateVersionGlobal(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
version := model.Version{
|
version := model.Version{
|
||||||
ApplicationID: req.ApplicationID,
|
ApplicationID: req.ApplicationID,
|
||||||
Version: req.Version,
|
Version: req.Version,
|
||||||
FilePath: req.FilePath,
|
FilePath: req.FilePath,
|
||||||
FileSize: req.FileSize,
|
FileSize: req.FileSize,
|
||||||
FileHash: req.FileHash,
|
FileHash: req.FileHash,
|
||||||
EntryFile: req.EntryFile,
|
EntryFile: req.EntryFile,
|
||||||
UpdateStrategy: req.UpdateStrategy,
|
UpdateStrategy: req.UpdateStrategy,
|
||||||
UpdateType: req.UpdateType,
|
UpdateType: req.UpdateType,
|
||||||
UpdateMethod: req.UpdateMethod,
|
UpdateMethod: req.UpdateMethod,
|
||||||
Description: req.Description,
|
CustomDownloadURL: req.CustomDownloadURL,
|
||||||
Changelog: req.Changelog,
|
Description: req.Description,
|
||||||
Status: "active",
|
Changelog: req.Changelog,
|
||||||
|
Status: "active",
|
||||||
}
|
}
|
||||||
|
|
||||||
if version.UpdateStrategy == "" {
|
if version.UpdateStrategy == "" {
|
||||||
@@ -341,12 +344,13 @@ func handleCreateVersionGlobal(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UpdateVersionRequest struct {
|
type UpdateVersionRequest struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
UpdateStrategy string `json:"update_strategy"`
|
UpdateStrategy string `json:"update_strategy"`
|
||||||
UpdateType string `json:"update_type"`
|
UpdateType string `json:"update_type"`
|
||||||
UpdateMethod string `json:"update_method"`
|
UpdateMethod string `json:"update_method"`
|
||||||
Description string `json:"description"`
|
CustomDownloadURL string `json:"custom_download_url"`
|
||||||
Changelog string `json:"changelog"`
|
Description string `json:"description"`
|
||||||
|
Changelog string `json:"changelog"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleUpdateVersionGlobal(c *gin.Context) {
|
func handleUpdateVersionGlobal(c *gin.Context) {
|
||||||
@@ -403,6 +407,7 @@ func handleUpdateVersionGlobal(c *gin.Context) {
|
|||||||
if req.UpdateMethod != "" {
|
if req.UpdateMethod != "" {
|
||||||
updates["update_method"] = req.UpdateMethod
|
updates["update_method"] = req.UpdateMethod
|
||||||
}
|
}
|
||||||
|
updates["custom_download_url"] = req.CustomDownloadURL
|
||||||
if req.Description != "" {
|
if req.Description != "" {
|
||||||
updates["description"] = req.Description
|
updates["description"] = req.Description
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ const formData = ref({
|
|||||||
update_strategy: 'optional' as 'optional' | 'forced',
|
update_strategy: 'optional' as 'optional' | 'forced',
|
||||||
update_type: 'full' as 'full' | 'patch',
|
update_type: 'full' as 'full' | 'patch',
|
||||||
update_method: 'auto' as 'auto' | 'manual',
|
update_method: 'auto' as 'auto' | 'manual',
|
||||||
|
custom_download_url: '',
|
||||||
changelog: '',
|
changelog: '',
|
||||||
entry_file: '',
|
entry_file: '',
|
||||||
file: null as File | null,
|
file: null as File | null,
|
||||||
@@ -97,6 +98,7 @@ async function fetchVersion() {
|
|||||||
formData.value.update_strategy = ver.update_strategy || 'optional'
|
formData.value.update_strategy = ver.update_strategy || 'optional'
|
||||||
formData.value.update_type = ver.update_type || 'full'
|
formData.value.update_type = ver.update_type || 'full'
|
||||||
formData.value.update_method = ver.update_method || 'auto'
|
formData.value.update_method = ver.update_method || 'auto'
|
||||||
|
formData.value.custom_download_url = ver.custom_download_url || ''
|
||||||
formData.value.changelog = ver.changelog || ''
|
formData.value.changelog = ver.changelog || ''
|
||||||
formData.value.entry_file = ver.entry_file || ''
|
formData.value.entry_file = ver.entry_file || ''
|
||||||
}
|
}
|
||||||
@@ -184,6 +186,7 @@ async function handleSubmit() {
|
|||||||
update_strategy: formData.value.update_strategy,
|
update_strategy: formData.value.update_strategy,
|
||||||
update_type: formData.value.update_type,
|
update_type: formData.value.update_type,
|
||||||
update_method: formData.value.update_method,
|
update_method: formData.value.update_method,
|
||||||
|
custom_download_url: formData.value.custom_download_url,
|
||||||
changelog: formData.value.changelog,
|
changelog: formData.value.changelog,
|
||||||
entry_file: formData.value.entry_file,
|
entry_file: formData.value.entry_file,
|
||||||
}
|
}
|
||||||
@@ -460,6 +463,18 @@ onMounted(() => {
|
|||||||
</UiSelect>
|
</UiSelect>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="formData.update_method === 'manual'" class="space-y-2">
|
||||||
|
<UiLabel>{{ t('admin.versions.createForm.customDownloadUrl') }}</UiLabel>
|
||||||
|
<p class="text-sm text-muted-foreground">
|
||||||
|
{{ t('admin.versions.createForm.customDownloadUrlDesc') }}
|
||||||
|
</p>
|
||||||
|
<UiInput
|
||||||
|
v-model="formData.custom_download_url"
|
||||||
|
:placeholder="t('admin.versions.createForm.customDownloadUrlPlaceholder')"
|
||||||
|
:disabled="saving"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<UiLabel for="changelog">
|
<UiLabel for="changelog">
|
||||||
{{ t('admin.versions.createForm.changelog') }}
|
{{ t('admin.versions.createForm.changelog') }}
|
||||||
@@ -516,6 +531,10 @@ onMounted(() => {
|
|||||||
: t('admin.versions.methods.manual')
|
: t('admin.versions.methods.manual')
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="formData.update_method === 'manual'" class="flex justify-between text-sm">
|
||||||
|
<span class="text-muted-foreground">{{ t('admin.versions.createForm.previewCustomDownloadUrl') }}</span>
|
||||||
|
<span class="truncate max-w-[120px]">{{ formData.custom_download_url || t('admin.versions.createForm.useDefault') }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border-t pt-4 mt-4">
|
<div class="border-t pt-4 mt-4">
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ const formData = ref({
|
|||||||
update_strategy: 'optional' as 'optional' | 'forced',
|
update_strategy: 'optional' as 'optional' | 'forced',
|
||||||
update_type: 'full' as 'full' | 'patch',
|
update_type: 'full' as 'full' | 'patch',
|
||||||
update_method: 'auto' as 'auto' | 'manual',
|
update_method: 'auto' as 'auto' | 'manual',
|
||||||
|
custom_download_url: '',
|
||||||
changelog: '',
|
changelog: '',
|
||||||
entry_file: '',
|
entry_file: '',
|
||||||
file: null as File | null,
|
file: null as File | null,
|
||||||
@@ -188,6 +189,7 @@ async function handleSubmit() {
|
|||||||
update_strategy: formData.value.update_strategy,
|
update_strategy: formData.value.update_strategy,
|
||||||
update_type: formData.value.update_type,
|
update_type: formData.value.update_type,
|
||||||
update_method: formData.value.update_method,
|
update_method: formData.value.update_method,
|
||||||
|
custom_download_url: formData.value.custom_download_url,
|
||||||
changelog: formData.value.changelog,
|
changelog: formData.value.changelog,
|
||||||
entry_file: formData.value.entry_file,
|
entry_file: formData.value.entry_file,
|
||||||
file_path: uploadedData.value.file_path,
|
file_path: uploadedData.value.file_path,
|
||||||
@@ -454,6 +456,18 @@ onMounted(() => {
|
|||||||
</UiSelect>
|
</UiSelect>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="formData.update_method === 'manual'" class="space-y-2">
|
||||||
|
<UiLabel>{{ t('admin.versions.createForm.customDownloadUrl') }}</UiLabel>
|
||||||
|
<p class="text-sm text-muted-foreground">
|
||||||
|
{{ t('admin.versions.createForm.customDownloadUrlDesc') }}
|
||||||
|
</p>
|
||||||
|
<UiInput
|
||||||
|
v-model="formData.custom_download_url"
|
||||||
|
:placeholder="t('admin.versions.createForm.customDownloadUrlPlaceholder')"
|
||||||
|
:disabled="saving"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<UiLabel for="changelog">
|
<UiLabel for="changelog">
|
||||||
{{ t('admin.versions.createForm.changelog') }}
|
{{ t('admin.versions.createForm.changelog') }}
|
||||||
@@ -516,6 +530,10 @@ onMounted(() => {
|
|||||||
<span class="text-muted-foreground">{{ t('admin.versions.createForm.previewUpdateMethod') }}</span>
|
<span class="text-muted-foreground">{{ t('admin.versions.createForm.previewUpdateMethod') }}</span>
|
||||||
<span>{{ methodLabel }}</span>
|
<span>{{ methodLabel }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="formData.update_method === 'manual'" class="flex justify-between text-sm">
|
||||||
|
<span class="text-muted-foreground">{{ t('admin.versions.createForm.previewCustomDownloadUrl') }}</span>
|
||||||
|
<span class="truncate max-w-[120px]">{{ formData.custom_download_url || t('admin.versions.createForm.useDefault') }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border-t pt-4 mt-4">
|
<div class="border-t pt-4 mt-4">
|
||||||
|
|||||||
@@ -1499,6 +1499,11 @@
|
|||||||
"previewUpdateType": "Update Type",
|
"previewUpdateType": "Update Type",
|
||||||
"previewUpdateMethod": "Update Method",
|
"previewUpdateMethod": "Update Method",
|
||||||
"previewAutoUpdate": "Auto Update",
|
"previewAutoUpdate": "Auto Update",
|
||||||
|
"customDownloadUrl": "Custom Download URL",
|
||||||
|
"customDownloadUrlDesc": "Set a custom download URL, or use the platform default if not set",
|
||||||
|
"customDownloadUrlPlaceholder": "e.g., https://your-cdn.com/download/app.zip",
|
||||||
|
"previewCustomDownloadUrl": "Download URL",
|
||||||
|
"useDefault": "Use Default",
|
||||||
"changelogPreview": "Changelog Preview",
|
"changelogPreview": "Changelog Preview",
|
||||||
"noChangelog": "No changelog",
|
"noChangelog": "No changelog",
|
||||||
"notUploaded": "Not uploaded",
|
"notUploaded": "Not uploaded",
|
||||||
|
|||||||
@@ -1477,6 +1477,11 @@
|
|||||||
"previewUpdateType": "更新类型",
|
"previewUpdateType": "更新类型",
|
||||||
"previewUpdateMethod": "更新方式",
|
"previewUpdateMethod": "更新方式",
|
||||||
"previewAutoUpdate": "自动更新",
|
"previewAutoUpdate": "自动更新",
|
||||||
|
"customDownloadUrl": "自定义下载地址",
|
||||||
|
"customDownloadUrlDesc": "设置自定义下载地址,不设置则使用平台默认地址",
|
||||||
|
"customDownloadUrlPlaceholder": "例如: https://your-cdn.com/download/app.zip",
|
||||||
|
"previewCustomDownloadUrl": "下载地址",
|
||||||
|
"useDefault": "使用默认地址",
|
||||||
"changelogPreview": "更新日志预览",
|
"changelogPreview": "更新日志预览",
|
||||||
"noChangelog": "暂无更新日志",
|
"noChangelog": "暂无更新日志",
|
||||||
"notUploaded": "未上传",
|
"notUploaded": "未上传",
|
||||||
|
|||||||
Reference in New Issue
Block a user