Separate dev deployment, main validation and GitHub Git mirroring #1

Merged
eezd merged 4 commits from dev into main 2026-10-29 21:33:48 +08:00
4 changed files with 26 additions and 2 deletions
Showing only changes of commit 4607da1a04 - Show all commits

No files matched your search

+1
View File
@@ -160,6 +160,7 @@ The Bearer authentication upgrade requires the frontend and backend to be releas
- Resetting settings retains the account, resources, plugin storage, and local tabs.
- Logs live in `logs/`, with three rotated files retained, each up to 10 MiB. Settings can export logs and a version summary; this does not reconstruct historical logs.
- If the interface breaks after an update, first check that the frontend and backend come from the same build, then inspect browser network requests and application logs. See the [user guide](guide.md#plugins-and-interface-recovery) for plugin and appearance recovery entry points.
- Note reads send `Cache-Control: no-store, no-transform` so proxies preserve the strong revision ETag used by conditional saves. A proxy rewriting it to a weak ETag can cause otherwise valid edits to return HTTP 412; preserve this response policy through the public ingress.
## Managed development and production
+1
View File
@@ -160,6 +160,7 @@ Bearer 认证升级需要前后端一起发布。旧 Cookie 页面不能继续
- 重置设置保留账户、资源、插件存储和本机标签页。
- 日志位于 `logs/`,轮转保留三份,每份至多 10 MiB;可在设置中导出日志及版本摘要。该功能不补录历史日志。
- 更新后界面异常先核对前后端是否来自同一构建,再查看浏览器网络请求和应用日志;插件与外观恢复入口见[使用指南](guide.zh-CN.md#插件与界面恢复)。
- 笔记读取返回 `Cache-Control: no-store, no-transform`,防止代理改写条件保存使用的强版本 ETag。代理将其改为弱 ETag 可能导致正常编辑返回 HTTP 412;公网入口须保留该响应策略。
## 开发与生产托管
+2 -1
View File
@@ -327,7 +327,8 @@ func (s *Server) readFile(w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("Content-Type", "text/markdown; charset=utf-8")
w.Header().Set("ETag", `"`+rev+`"`)
w.Header().Set("Cache-Control", "no-store")
// Preserve the strong revision ETag through compression proxies for If-Match writes.
w.Header().Set("Cache-Control", "no-store, no-transform")
w.Write(data)
}
+22 -1
View File
@@ -37,6 +37,23 @@ type fixture struct {
admin *http.Client
}
func TestNoteRevisionSurvivesProxyBoundary(t *testing.T) {
f := newFixture(t)
f.write("proxy.md", "original")
res := f.do(f.admin, "GET", "/api/v1/files/proxy.md", "", nil)
defer res.Body.Close()
etag := res.Header.Get("ETag")
if res.StatusCode != http.StatusOK || res.Header.Get("Cache-Control") != "no-store, no-transform" || !strings.HasPrefix(etag, `"`) {
t.Fatalf("笔记响应必须保留强版本标识并禁止代理转换: %d %v", res.StatusCode, res.Header)
}
if code := f.status(f.admin, "PUT", "/api/v1/files/proxy.md", "updated", map[string]string{"If-Match": etag}); code != http.StatusOK {
t.Fatalf("读取的版本标识应允许条件保存: %d", code)
}
if code := f.status(f.admin, "PUT", "/api/v1/files/proxy.md", "stale", map[string]string{"If-Match": etag}); code != http.StatusPreconditionFailed {
t.Fatalf("过期版本必须仍拒绝保存: %d", code)
}
}
func TestMoveBroadcastsWhenAssetRewriteReplacesFileIdentity(t *testing.T) {
f := newFixture(t)
f.write("from.md", "![](from.assets/x.png)")
@@ -274,7 +291,11 @@ func TestAPIResponsesAreNotCached(t *testing.T) {
// Firefox once cached identity and tree responses offline, incorrectly opening an unloadable workspace.
for _, path := range []string{"/api/v1/auth/state", "/api/v1/tree", "/api/v1/files/笔记.md", "/api/v1/raw/笔记.md"} {
res := f.do(f.admin, "GET", path, "", nil)
if res.StatusCode != http.StatusOK || res.Header.Get("Cache-Control") != "no-store" {
want := "no-store"
if strings.HasPrefix(path, "/api/v1/files/") {
want = "no-store, no-transform"
}
if res.StatusCode != http.StatusOK || res.Header.Get("Cache-Control") != want {
t.Errorf("%s:状态 %d,缓存策略 %q", path, res.StatusCode, res.Header.Get("Cache-Control"))
}
}