server.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. package server
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "strings"
  7. "time"
  8. "github.com/google/uuid"
  9. "github.com/labstack/echo/v4"
  10. "github.com/labstack/echo/v4/middleware"
  11. "github.com/pkg/errors"
  12. apiv1 "github.com/usememos/memos/api/v1"
  13. apiv2 "github.com/usememos/memos/api/v2"
  14. "github.com/usememos/memos/plugin/telegram"
  15. "github.com/usememos/memos/server/frontend"
  16. "github.com/usememos/memos/server/integration"
  17. "github.com/usememos/memos/server/profile"
  18. versionchecker "github.com/usememos/memos/server/service/version_checker"
  19. "github.com/usememos/memos/store"
  20. )
  21. type Server struct {
  22. e *echo.Echo
  23. ID string
  24. Secret string
  25. Profile *profile.Profile
  26. Store *store.Store
  27. // Asynchronous runners.
  28. telegramBot *telegram.Bot
  29. }
  30. func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store) (*Server, error) {
  31. e := echo.New()
  32. e.Debug = true
  33. e.HideBanner = true
  34. e.HidePort = true
  35. s := &Server{
  36. e: e,
  37. Store: store,
  38. Profile: profile,
  39. // Asynchronous runners.
  40. telegramBot: telegram.NewBotWithHandler(integration.NewTelegramHandler(store)),
  41. }
  42. e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
  43. Format: `{"time":"${time_rfc3339}","latency":"${latency_human}",` +
  44. `"method":"${method}","uri":"${uri}",` +
  45. `"status":${status},"error":"${error}"}` + "\n",
  46. }))
  47. e.Use(CORSMiddleware())
  48. e.Use(middleware.TimeoutWithConfig(middleware.TimeoutConfig{
  49. Skipper: grpcRequestSkipper,
  50. Timeout: 30 * time.Second,
  51. }))
  52. serverID, err := s.getSystemServerID(ctx)
  53. if err != nil {
  54. return nil, errors.Wrap(err, "failed to retrieve system server ID")
  55. }
  56. s.ID = serverID
  57. // Only serve frontend when it's enabled.
  58. if profile.Frontend {
  59. frontendService := frontend.NewFrontendService(profile, store)
  60. frontendService.Serve(ctx, e)
  61. }
  62. secret := "usememos"
  63. if profile.Mode == "prod" {
  64. secret, err = s.getSystemSecretSessionName(ctx)
  65. if err != nil {
  66. return nil, errors.Wrap(err, "failed to retrieve system secret session name")
  67. }
  68. }
  69. s.Secret = secret
  70. // Register healthz endpoint.
  71. e.GET("/healthz", func(c echo.Context) error {
  72. return c.String(http.StatusOK, "Service ready.")
  73. })
  74. // Register API v1 endpoints.
  75. rootGroup := e.Group("")
  76. apiV1Service := apiv1.NewAPIV1Service(s.Secret, profile, store, s.telegramBot)
  77. apiV1Service.Register(rootGroup)
  78. apiV2Service := apiv2.NewAPIV2Service(s.Secret, profile, store, s.Profile.Port+1)
  79. // Register gRPC gateway as api v2.
  80. if err := apiV2Service.RegisterGateway(ctx, e); err != nil {
  81. return nil, errors.Wrap(err, "failed to register gRPC gateway")
  82. }
  83. return s, nil
  84. }
  85. func (s *Server) Start(ctx context.Context) error {
  86. go versionchecker.NewVersionChecker(s.Store, s.Profile).Start(ctx)
  87. go s.telegramBot.Start(ctx)
  88. return s.e.Start(fmt.Sprintf("%s:%d", s.Profile.Addr, s.Profile.Port))
  89. }
  90. func (s *Server) Shutdown(ctx context.Context) {
  91. ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
  92. defer cancel()
  93. // Shutdown echo server
  94. if err := s.e.Shutdown(ctx); err != nil {
  95. fmt.Printf("failed to shutdown server, error: %v\n", err)
  96. }
  97. // Close database connection
  98. if err := s.Store.Close(); err != nil {
  99. fmt.Printf("failed to close database, error: %v\n", err)
  100. }
  101. fmt.Printf("memos stopped properly\n")
  102. }
  103. func (s *Server) GetEcho() *echo.Echo {
  104. return s.e
  105. }
  106. func (s *Server) getSystemServerID(ctx context.Context) (string, error) {
  107. serverIDSetting, err := s.Store.GetWorkspaceSetting(ctx, &store.FindWorkspaceSetting{
  108. Name: apiv1.SystemSettingServerIDName.String(),
  109. })
  110. if err != nil {
  111. return "", err
  112. }
  113. if serverIDSetting == nil || serverIDSetting.Value == "" {
  114. serverIDSetting, err = s.Store.UpsertWorkspaceSetting(ctx, &store.WorkspaceSetting{
  115. Name: apiv1.SystemSettingServerIDName.String(),
  116. Value: uuid.NewString(),
  117. })
  118. if err != nil {
  119. return "", err
  120. }
  121. }
  122. return serverIDSetting.Value, nil
  123. }
  124. func (s *Server) getSystemSecretSessionName(ctx context.Context) (string, error) {
  125. secretSessionNameValue, err := s.Store.GetWorkspaceSetting(ctx, &store.FindWorkspaceSetting{
  126. Name: apiv1.SystemSettingSecretSessionName.String(),
  127. })
  128. if err != nil {
  129. return "", err
  130. }
  131. if secretSessionNameValue == nil || secretSessionNameValue.Value == "" {
  132. secretSessionNameValue, err = s.Store.UpsertWorkspaceSetting(ctx, &store.WorkspaceSetting{
  133. Name: apiv1.SystemSettingSecretSessionName.String(),
  134. Value: uuid.NewString(),
  135. })
  136. if err != nil {
  137. return "", err
  138. }
  139. }
  140. return secretSessionNameValue.Value, nil
  141. }
  142. func grpcRequestSkipper(c echo.Context) bool {
  143. return strings.HasPrefix(c.Request().URL.Path, "/memos.api.v2.")
  144. }
  145. func CORSMiddleware() echo.MiddlewareFunc {
  146. return func(next echo.HandlerFunc) echo.HandlerFunc {
  147. return func(c echo.Context) error {
  148. if grpcRequestSkipper(c) {
  149. return next(c)
  150. }
  151. r := c.Request()
  152. w := c.Response().Writer
  153. w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
  154. w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
  155. w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
  156. w.Header().Set("Access-Control-Allow-Credentials", "true")
  157. // If it's preflight request, return immediately.
  158. if r.Method == "OPTIONS" {
  159. w.WriteHeader(http.StatusOK)
  160. return nil
  161. }
  162. return next(c)
  163. }
  164. }
  165. }