chore: upgrade Echo v4 to v5.0.3

This commit is contained in:
Steven 2026-02-10 09:15:27 +08:00
parent cdadead6c9
commit 71e8a06463
10 changed files with 85 additions and 95 deletions

View File

@ -15,7 +15,7 @@ concurrency:
cancel-in-progress: true
env:
GO_VERSION: "1.25"
GO_VERSION: "1.25.7"
jobs:
static-checks:

View File

@ -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

22
go.mod
View File

@ -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
)

48
go.sum
View File

@ -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=

View File

@ -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

View File

@ -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{"*"},

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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.