From 71e8a06463deb40fb140c78f2fc59206f45318e7 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Feb 2026 09:15:27 +0800 Subject: [PATCH] chore: upgrade Echo v4 to v5.0.3 --- .github/workflows/backend-tests.yml | 2 +- .github/workflows/build-binaries.yml | 2 +- go.mod | 22 +++++------- go.sum | 48 +++++++++++--------------- scripts/Dockerfile | 2 +- server/router/api/v1/v1.go | 12 ++++--- server/router/fileserver/fileserver.go | 39 +++++++++++---------- server/router/frontend/frontend.go | 13 ++++--- server/router/rss/rss.go | 18 +++++----- server/server.go | 22 ++++++------ 10 files changed, 85 insertions(+), 95 deletions(-) diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml index c1b1b8909..a3e31637e 100644 --- a/.github/workflows/backend-tests.yml +++ b/.github/workflows/backend-tests.yml @@ -15,7 +15,7 @@ concurrency: cancel-in-progress: true env: - GO_VERSION: "1.25" + GO_VERSION: "1.25.7" jobs: static-checks: diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 0d35b8757..a075ce5e8 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -7,7 +7,7 @@ on: # Environment variables for build configuration env: - GO_VERSION: "1.25" + GO_VERSION: "1.25.7" NODE_VERSION: "22" PNPM_VERSION: "10" ARTIFACT_RETENTION_DAYS: 60 diff --git a/go.mod b/go.mod index b9a86cab6..1205ad88f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/usememos/memos -go 1.25 +go 1.25.7 require ( connectrpc.com/connect v1.19.1 @@ -16,7 +16,7 @@ require ( github.com/gorilla/feeds v1.2.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 github.com/joho/godotenv v1.5.1 - github.com/labstack/echo/v4 v4.13.4 + github.com/labstack/echo/v5 v5.0.3 github.com/lib/pq v1.10.9 github.com/lithammer/shortuuid/v4 v4.2.0 github.com/pkg/errors v0.9.1 @@ -27,11 +27,11 @@ require ( github.com/testcontainers/testcontainers-go/modules/mysql v0.40.0 github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 github.com/yuin/goldmark v1.7.13 - golang.org/x/crypto v0.43.0 - golang.org/x/mod v0.28.0 - golang.org/x/net v0.45.0 + golang.org/x/crypto v0.47.0 + golang.org/x/mod v0.31.0 + golang.org/x/net v0.49.0 golang.org/x/oauth2 v0.30.0 - golang.org/x/sync v0.17.0 + golang.org/x/sync v0.19.0 google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 google.golang.org/grpc v1.75.1 modernc.org/sqlite v1.38.2 @@ -124,15 +124,11 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/disintegration/imaging v1.6.2 github.com/golang-jwt/jwt/v5 v5.3.0 - github.com/labstack/gommon v0.4.2 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect - golang.org/x/sys v0.37.0 // indirect - golang.org/x/text v0.30.0 - golang.org/x/time v0.12.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 + golang.org/x/time v0.14.0 // indirect google.golang.org/protobuf v1.36.9 gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9398efe12..527ee36bc 100644 --- a/go.sum +++ b/go.sum @@ -137,10 +137,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA= -github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ= -github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= -github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/labstack/echo/v5 v5.0.3 h1:Jql8sDtCYXrhh2Mbs6jKwjR6r7X8FSQQmch+w6QS7kc= +github.com/labstack/echo/v5 v5.0.3/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c= @@ -149,8 +147,6 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI= @@ -237,10 +233,6 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= @@ -269,21 +261,21 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0= golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4= golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c= -golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= -golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= -golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= -golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -291,17 +283,17 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= -golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= -golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= diff --git a/scripts/Dockerfile b/scripts/Dockerfile index ed2894c2b..2842e4ce1 100644 --- a/scripts/Dockerfile +++ b/scripts/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS backend +FROM --platform=$BUILDPLATFORM golang:1.25.7-alpine AS backend WORKDIR /backend-build # Install build dependencies diff --git a/server/router/api/v1/v1.go b/server/router/api/v1/v1.go index 694b5bbc3..2fa17ec90 100644 --- a/server/router/api/v1/v1.go +++ b/server/router/api/v1/v1.go @@ -6,8 +6,8 @@ import ( "connectrpc.com/connect" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" - "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" + "github.com/labstack/echo/v5" + "github.com/labstack/echo/v5/middleware" "golang.org/x/sync/semaphore" "github.com/usememos/memos/internal/profile" @@ -119,7 +119,9 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech return err } gwGroup := echoServer.Group("") - gwGroup.Use(middleware.CORS()) + gwGroup.Use(middleware.CORSWithConfig(middleware.CORSConfig{ + AllowOrigins: []string{"*"}, + })) handler := echo.WrapHandler(gwMux) gwGroup.Any("/api/v1/*", handler) @@ -139,8 +141,8 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech // Wrap with CORS for browser access corsHandler := middleware.CORSWithConfig(middleware.CORSConfig{ - AllowOriginFunc: func(_ string) (bool, error) { - return true, nil + UnsafeAllowOriginFunc: func(_ *echo.Context, origin string) (string, bool, error) { + return origin, true, nil }, AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodOptions}, AllowHeaders: []string{"*"}, diff --git a/server/router/fileserver/fileserver.go b/server/router/fileserver/fileserver.go index 0aaffd42f..3fe30b1de 100644 --- a/server/router/fileserver/fileserver.go +++ b/server/router/fileserver/fileserver.go @@ -6,6 +6,7 @@ import ( "encoding/base64" "fmt" "io" + "log/slog" "net/http" "os" "path/filepath" @@ -14,7 +15,7 @@ import ( "time" "github.com/disintegration/imaging" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/pkg/errors" "golang.org/x/sync/semaphore" @@ -118,7 +119,7 @@ func (s *FileServerService) RegisterRoutes(echoServer *echo.Echo) { // ============================================================================= // serveAttachmentFile serves attachment binary content using native HTTP. -func (s *FileServerService) serveAttachmentFile(c echo.Context) error { +func (s *FileServerService) serveAttachmentFile(c *echo.Context) error { ctx := c.Request().Context() uid := c.Param("uid") wantThumbnail := c.QueryParam("thumbnail") == "true" @@ -128,7 +129,7 @@ func (s *FileServerService) serveAttachmentFile(c echo.Context) error { GetBlob: true, }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "failed to get attachment").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to get attachment").Wrap(err) } if attachment == nil { return echo.NewHTTPError(http.StatusNotFound, "attachment not found") @@ -149,13 +150,13 @@ func (s *FileServerService) serveAttachmentFile(c echo.Context) error { } // serveUserAvatar serves user avatar images. -func (s *FileServerService) serveUserAvatar(c echo.Context) error { +func (s *FileServerService) serveUserAvatar(c *echo.Context) error { ctx := c.Request().Context() identifier := c.Param("identifier") user, err := s.getUserByIdentifier(ctx, identifier) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "failed to get user").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to get user").Wrap(err) } if user == nil { return echo.NewHTTPError(http.StatusNotFound, "user not found") @@ -166,7 +167,7 @@ func (s *FileServerService) serveUserAvatar(c echo.Context) error { imageType, imageData, err := s.parseDataURI(user.AvatarURL) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "failed to parse avatar data").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to parse avatar data").Wrap(err) } if !avatarAllowedTypes[imageType] { @@ -185,7 +186,7 @@ func (s *FileServerService) serveUserAvatar(c echo.Context) error { // ============================================================================= // serveMediaStream serves video/audio files using streaming to avoid memory exhaustion. -func (s *FileServerService) serveMediaStream(c echo.Context, attachment *store.Attachment, contentType string) error { +func (s *FileServerService) serveMediaStream(c *echo.Context, attachment *store.Attachment, contentType string) error { setSecurityHeaders(c) setMediaHeaders(c, contentType, attachment.Type) @@ -193,7 +194,7 @@ func (s *FileServerService) serveMediaStream(c echo.Context, attachment *store.A case storepb.AttachmentStorageType_LOCAL: filePath, err := s.resolveLocalPath(attachment.Reference) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "failed to resolve file path").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to resolve file path").Wrap(err) } http.ServeFile(c.Response(), c.Request(), filePath) return nil @@ -201,7 +202,7 @@ func (s *FileServerService) serveMediaStream(c echo.Context, attachment *store.A case storepb.AttachmentStorageType_S3: presignURL, err := s.getS3PresignedURL(c.Request().Context(), attachment) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "failed to generate presigned URL").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to generate presigned URL").Wrap(err) } return c.Redirect(http.StatusTemporaryRedirect, presignURL) @@ -214,16 +215,16 @@ func (s *FileServerService) serveMediaStream(c echo.Context, attachment *store.A } // serveStaticFile serves non-streaming files (images, documents, etc.). -func (s *FileServerService) serveStaticFile(c echo.Context, attachment *store.Attachment, contentType string, wantThumbnail bool) error { +func (s *FileServerService) serveStaticFile(c *echo.Context, attachment *store.Attachment, contentType string, wantThumbnail bool) error { blob, err := s.getAttachmentBlob(attachment) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "failed to get attachment blob").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to get attachment blob").Wrap(err) } // Generate thumbnail for supported image types. if wantThumbnail && thumbnailSupportedTypes[attachment.Type] { if thumbnailBlob, err := s.getOrGenerateThumbnail(c.Request().Context(), attachment); err != nil { - c.Logger().Warnf("failed to get thumbnail: %v", err) + slog.Warn("failed to get thumbnail", "error", err) } else { blob = thumbnailBlob } @@ -468,12 +469,12 @@ func calculateThumbnailDimensions(width, height int) (int, int) { // ============================================================================= // checkAttachmentPermission verifies the user has permission to access the attachment. -func (s *FileServerService) checkAttachmentPermission(ctx context.Context, c echo.Context, attachment *store.Attachment) error { +func (s *FileServerService) checkAttachmentPermission(ctx context.Context, c *echo.Context, attachment *store.Attachment) error { // For unlinked attachments, only the creator can access. if attachment.MemoID == nil { user, err := s.getCurrentUser(ctx, c) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "failed to get current user").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to get current user").Wrap(err) } if user == nil { return echo.NewHTTPError(http.StatusUnauthorized, "unauthorized access") @@ -486,7 +487,7 @@ func (s *FileServerService) checkAttachmentPermission(ctx context.Context, c ech memo, err := s.Store.GetMemo(ctx, &store.FindMemo{ID: attachment.MemoID}) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "failed to find memo").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to find memo").Wrap(err) } if memo == nil { return echo.NewHTTPError(http.StatusNotFound, "memo not found") @@ -498,7 +499,7 @@ func (s *FileServerService) checkAttachmentPermission(ctx context.Context, c ech user, err := s.getCurrentUser(ctx, c) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "failed to get current user").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to get current user").Wrap(err) } if user == nil { return echo.NewHTTPError(http.StatusUnauthorized, "unauthorized access") @@ -513,7 +514,7 @@ func (s *FileServerService) checkAttachmentPermission(ctx context.Context, c ech // getCurrentUser retrieves the current authenticated user from the request. // Authentication priority: Bearer token (Access Token V2 or PAT) > Refresh token cookie. -func (s *FileServerService) getCurrentUser(ctx context.Context, c echo.Context) (*store.User, error) { +func (s *FileServerService) getCurrentUser(ctx context.Context, c *echo.Context) (*store.User, error) { // Try Bearer token authentication. if authHeader := c.Request().Header.Get(echo.HeaderAuthorization); authHeader != "" { if user, err := s.authenticateByBearerToken(ctx, authHeader); err == nil && user != nil { @@ -615,7 +616,7 @@ func isMediaType(mimeType string) bool { } // setSecurityHeaders sets common security headers for all responses. -func setSecurityHeaders(c echo.Context) { +func setSecurityHeaders(c *echo.Context) { h := c.Response().Header() h.Set("X-Content-Type-Options", "nosniff") h.Set("X-Frame-Options", "DENY") @@ -623,7 +624,7 @@ func setSecurityHeaders(c echo.Context) { } // setMediaHeaders sets headers for media file responses. -func setMediaHeaders(c echo.Context, contentType, originalType string) { +func setMediaHeaders(c *echo.Context, contentType, originalType string) { h := c.Response().Header() h.Set(echo.HeaderContentType, contentType) h.Set(echo.HeaderCacheControl, cacheMaxAge) diff --git a/server/router/frontend/frontend.go b/server/router/frontend/frontend.go index 1e54ffb6f..dedee6390 100644 --- a/server/router/frontend/frontend.go +++ b/server/router/frontend/frontend.go @@ -4,10 +4,9 @@ import ( "context" "embed" "io/fs" - "net/http" - "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" + "github.com/labstack/echo/v5" + "github.com/labstack/echo/v5/middleware" "github.com/usememos/memos/internal/profile" "github.com/usememos/memos/internal/util" @@ -30,7 +29,7 @@ func NewFrontendService(profile *profile.Profile, store *store.Store) *FrontendS } func (*FrontendService) Serve(_ context.Context, e *echo.Echo) { - skipper := func(c echo.Context) bool { + skipper := func(c *echo.Context) bool { // Skip API routes. if util.HasPrefixes(c.Path(), "/api", "/memos.api.v1") { return true @@ -60,10 +59,10 @@ func (*FrontendService) Serve(_ context.Context, e *echo.Echo) { })) } -func getFileSystem(path string) http.FileSystem { - fs, err := fs.Sub(embeddedFiles, path) +func getFileSystem(path string) fs.FS { + sub, err := fs.Sub(embeddedFiles, path) if err != nil { panic(err) } - return http.FS(fs) + return sub } diff --git a/server/router/rss/rss.go b/server/router/rss/rss.go index a21a59c07..53ea86a20 100644 --- a/server/router/rss/rss.go +++ b/server/router/rss/rss.go @@ -12,7 +12,7 @@ import ( "time" "github.com/gorilla/feeds" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/usememos/memos/internal/profile" "github.com/usememos/memos/plugin/markdown" @@ -69,7 +69,7 @@ func (s *RSSService) RegisterRoutes(g *echo.Group) { g.GET("/u/:username/rss.xml", s.GetUserRSS) } -func (s *RSSService) GetExploreRSS(c echo.Context) error { +func (s *RSSService) GetExploreRSS(c *echo.Context) error { ctx := c.Request().Context() cacheKey := "explore" @@ -92,13 +92,13 @@ func (s *RSSService) GetExploreRSS(c echo.Context) error { } memoList, err := s.Store.ListMemos(ctx, &memoFind) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo list").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo list").Wrap(err) } baseURL := c.Scheme() + "://" + c.Request().Host rss, lastModified, err := s.generateRSSFromMemoList(ctx, memoList, baseURL, nil) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate rss").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate rss").Wrap(err) } // Cache the result @@ -107,7 +107,7 @@ func (s *RSSService) GetExploreRSS(c echo.Context) error { return c.String(http.StatusOK, rss) } -func (s *RSSService) GetUserRSS(c echo.Context) error { +func (s *RSSService) GetUserRSS(c *echo.Context) error { ctx := c.Request().Context() username := c.Param("username") cacheKey := "user:" + username @@ -126,7 +126,7 @@ func (s *RSSService) GetUserRSS(c echo.Context) error { Username: &username, }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").Wrap(err) } if user == nil { return echo.NewHTTPError(http.StatusNotFound, "User not found") @@ -142,13 +142,13 @@ func (s *RSSService) GetUserRSS(c echo.Context) error { } memoList, err := s.Store.ListMemos(ctx, &memoFind) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo list").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo list").Wrap(err) } baseURL := c.Scheme() + "://" + c.Request().Host rss, lastModified, err := s.generateRSSFromMemoList(ctx, memoList, baseURL, user) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate rss").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate rss").Wrap(err) } // Cache the result @@ -392,7 +392,7 @@ func (s *RSSService) putInCache(key, content string, lastModified time.Time) str } // setRSSHeaders sets appropriate HTTP headers for RSS responses. -func (*RSSService) setRSSHeaders(c echo.Context, etag string, lastModified time.Time) { +func (*RSSService) setRSSHeaders(c *echo.Context, etag string, lastModified time.Time) { c.Response().Header().Set(echo.HeaderContentType, "application/rss+xml; charset=utf-8") c.Response().Header().Set(echo.HeaderCacheControl, fmt.Sprintf("public, max-age=%d", int(defaultCacheDuration.Seconds()))) c.Response().Header().Set("ETag", etag) diff --git a/server/server.go b/server/server.go index af09c4bcd..af3056764 100644 --- a/server/server.go +++ b/server/server.go @@ -10,8 +10,8 @@ import ( "time" "github.com/google/uuid" - "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" + "github.com/labstack/echo/v5" + "github.com/labstack/echo/v5/middleware" "github.com/pkg/errors" "github.com/usememos/memos/internal/profile" @@ -30,6 +30,7 @@ type Server struct { Store *store.Store echoServer *echo.Echo + httpServer *http.Server runnerCancelFuncs []context.CancelFunc } @@ -40,9 +41,6 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store } echoServer := echo.New() - echoServer.Debug = true - echoServer.HideBanner = true - echoServer.HidePort = true echoServer.Use(middleware.Recover()) s.echoServer = echoServer @@ -58,7 +56,7 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store s.Secret = secret // Register healthz endpoint. - echoServer.GET("/healthz", func(c echo.Context) error { + echoServer.GET("/healthz", func(c *echo.Context) error { return c.String(http.StatusOK, "Service ready.") }) @@ -99,9 +97,9 @@ func (s *Server) Start(ctx context.Context) error { } // Start Echo server directly (no cmux needed - all traffic is HTTP). - s.echoServer.Listener = listener + s.httpServer = &http.Server{Handler: s.echoServer} go func() { - if err := s.echoServer.Start(address); err != nil && err != http.ErrServerClosed { + if err := s.httpServer.Serve(listener); err != nil && err != http.ErrServerClosed { slog.Error("failed to start echo server", "error", err) } }() @@ -123,9 +121,11 @@ func (s *Server) Shutdown(ctx context.Context) { } } - // Shutdown echo server. - if err := s.echoServer.Shutdown(ctx); err != nil { - slog.Error("failed to shutdown server", slog.String("error", err.Error())) + // Shutdown HTTP server. + if s.httpServer != nil { + if err := s.httpServer.Shutdown(ctx); err != nil { + slog.Error("failed to shutdown server", slog.String("error", err.Error())) + } } // Close database connection.