feat: 提交详情能看 diff,也能丢给 AI 审这一次改动。文件检查可单独排除路径,TODO 只认大写,避免把 todo 表和模块当成待办标记。
热力图按容器宽度铺满一年,不再横向滚动。托盘右键换成可换皮肤的弹层;更新下载显示进度,退出后再由脚本拉起安装器,避免还占着 exe。
This commit is contained in:
54
admin.go
54
admin.go
@@ -3,7 +3,6 @@ package main
|
||||
// admin.go 云端管理员(id=1)运营后台:TOTP/stepup、统计、用户/团队、发版。
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
@@ -253,38 +252,59 @@ func (a *App) AdminUploadRelease(version, channel, changelog, filePath string) (
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
w := multipart.NewWriter(&buf)
|
||||
_ = w.WriteField("version", strings.TrimSpace(version))
|
||||
_ = w.WriteField("channel", strings.TrimSpace(channel))
|
||||
_ = w.WriteField("changelog", changelog)
|
||||
part, e := w.CreateFormFile("file", filepath.Base(filePath))
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
if _, e = io.Copy(part, f); e != nil {
|
||||
return nil, errors.New("SAVE_FAILED")
|
||||
}
|
||||
_ = w.Close()
|
||||
|
||||
base := a.apiBaseURL()
|
||||
if base == "" {
|
||||
return nil, errors.New("SYNC_NOT_CONFIGURED")
|
||||
}
|
||||
req, e := http.NewRequest(http.MethodPost, base+"/api/v1/admin/releases", &buf)
|
||||
pr, pw := io.Pipe()
|
||||
w := multipart.NewWriter(pw)
|
||||
go func() {
|
||||
var copyErr error
|
||||
defer func() {
|
||||
_ = w.Close()
|
||||
if copyErr != nil {
|
||||
_ = pw.CloseWithError(copyErr)
|
||||
} else {
|
||||
_ = pw.Close()
|
||||
}
|
||||
}()
|
||||
if e := w.WriteField("version", strings.TrimSpace(version)); e != nil {
|
||||
copyErr = e
|
||||
return
|
||||
}
|
||||
if e := w.WriteField("channel", strings.TrimSpace(channel)); e != nil {
|
||||
copyErr = e
|
||||
return
|
||||
}
|
||||
if e := w.WriteField("changelog", changelog); e != nil {
|
||||
copyErr = e
|
||||
return
|
||||
}
|
||||
part, e := w.CreateFormFile("file", filepath.Base(filePath))
|
||||
if e != nil {
|
||||
copyErr = e
|
||||
return
|
||||
}
|
||||
if _, e = io.Copy(part, f); e != nil {
|
||||
copyErr = errors.New("SAVE_FAILED")
|
||||
}
|
||||
}()
|
||||
req, e := http.NewRequest(http.MethodPost, base+"/api/v1/admin/releases", pr)
|
||||
if e != nil {
|
||||
_ = pw.Close()
|
||||
return nil, errors.New("SYNC_OFFLINE")
|
||||
}
|
||||
req.Header.Set("Content-Type", w.FormDataContentType())
|
||||
tok := strings.TrimSpace(a.store.Meta("sync_access_token"))
|
||||
if tok == "" {
|
||||
_ = pw.Close()
|
||||
return nil, errors.New("SYNC_NOT_LOGGED_IN")
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+tok)
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
client := &http.Client{Timeout: 10 * time.Minute}
|
||||
client := &http.Client{Timeout: 20 * time.Minute}
|
||||
resp, e := client.Do(req)
|
||||
if e != nil {
|
||||
return nil, errors.New("SYNC_OFFLINE")
|
||||
|
||||
Reference in New Issue
Block a user