Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
71e8a064
Commit
71e8a064
authored
Feb 10, 2026
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: upgrade Echo v4 to v5.0.3
parent
cdadead6
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
85 additions
and
95 deletions
+85
-95
backend-tests.yml
.github/workflows/backend-tests.yml
+1
-1
build-binaries.yml
.github/workflows/build-binaries.yml
+1
-1
go.mod
go.mod
+9
-13
go.sum
go.sum
+20
-28
Dockerfile
scripts/Dockerfile
+1
-1
v1.go
server/router/api/v1/v1.go
+7
-5
fileserver.go
server/router/fileserver/fileserver.go
+20
-19
frontend.go
server/router/frontend/frontend.go
+6
-7
rss.go
server/router/rss/rss.go
+9
-9
server.go
server/server.go
+11
-11
No files found.
.github/workflows/backend-tests.yml
View file @
71e8a064
...
...
@@ -15,7 +15,7 @@ concurrency:
cancel-in-progress
:
true
env
:
GO_VERSION
:
"
1.25"
GO_VERSION
:
"
1.25
.7
"
jobs
:
static-checks
:
...
...
.github/workflows/build-binaries.yml
View file @
71e8a064
...
...
@@ -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
...
...
go.mod
View file @
71e8a064
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/v
4 v4.13.4
github.com/labstack/echo/v
5 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.4
3
.0
golang.org/x/mod v0.
28
.0
golang.org/x/net v0.4
5
.0
golang.org/x/crypto v0.4
7
.0
golang.org/x/mod v0.
31
.0
golang.org/x/net v0.4
9
.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.1
7
.0
golang.org/x/sync v0.1
9
.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
)
go.sum
View file @
71e8a064
...
...
@@ -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.4
3.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04
=
golang.org/x/crypto v0.4
3.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0
=
golang.org/x/crypto v0.4
7.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8
=
golang.org/x/crypto v0.4
7.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.4
5.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM
=
golang.org/x/net v0.4
5.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.4
9.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o
=
golang.org/x/net v0.4
9.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.1
7.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug
=
golang.org/x/sync v0.1
7
.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.1
9.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4
=
golang.org/x/sync v0.1
9
.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:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9k
Q=
golang.org/x/sys v0.
37
.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.3
6.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q
=
golang.org/x/term v0.3
6.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss
=
golang.org/x/sys v0.
40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEW
Q=
golang.org/x/sys v0.
40
.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.3
9.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY
=
golang.org/x/term v0.3
9.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.3
0.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k
=
golang.org/x/text v0.3
0.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM
=
golang.org/x/time v0.1
2.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE
=
golang.org/x/time v0.1
2.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.3
3.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE
=
golang.org/x/text v0.3
3.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8
=
golang.org/x/time v0.1
4.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI
=
golang.org/x/time v0.1
4.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=
...
...
scripts/Dockerfile
View file @
71e8a064
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
...
...
server/router/api/v1/v1.go
View file @
71e8a064
...
...
@@ -6,8 +6,8 @@ import (
"connectrpc.com/connect"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/labstack/echo/v
4
"
"github.com/labstack/echo/v
4
/middleware"
"github.com/labstack/echo/v
5
"
"github.com/labstack/echo/v
5
/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
{
"*"
},
...
...
server/router/fileserver/fileserver.go
View file @
71e8a064
...
...
@@ -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/v
4
"
"github.com/labstack/echo/v
5
"
"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
)
...
...
server/router/frontend/frontend.go
View file @
71e8a064
...
...
@@ -4,10 +4,9 @@ import (
"context"
"embed"
"io/fs"
"net/http"
"github.com/labstack/echo/v
4
"
"github.com/labstack/echo/v
4
/middleware"
"github.com/labstack/echo/v
5
"
"github.com/labstack/echo/v
5
/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
}
server/router/rss/rss.go
View file @
71e8a064
...
...
@@ -12,7 +12,7 @@ import (
"time"
"github.com/gorilla/feeds"
"github.com/labstack/echo/v
4
"
"github.com/labstack/echo/v
5
"
"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
)
...
...
server/server.go
View file @
71e8a064
...
...
@@ -10,8 +10,8 @@ import (
"time"
"github.com/google/uuid"
"github.com/labstack/echo/v
4
"
"github.com/labstack/echo/v
4
/middleware"
"github.com/labstack/echo/v
5
"
"github.com/labstack/echo/v
5
/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.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment