mirror of
https://github.com/usememos/memos.git
synced 2024-11-10 08:52:22 +08:00
chore: migrate user setting to api v1 package (#1855)
* chore: migrate to api v1 package * chore: update
This commit is contained in:
parent
07e82c3f4a
commit
b44f2b5ffb
20 changed files with 764 additions and 173 deletions
137
api/v1/activity.go
Normal file
137
api/v1/activity.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
package v1
|
||||
|
||||
import "github.com/usememos/memos/server/profile"
|
||||
|
||||
// ActivityType is the type for an activity.
|
||||
type ActivityType string
|
||||
|
||||
const (
|
||||
// User related.
|
||||
|
||||
// ActivityUserCreate is the type for creating users.
|
||||
ActivityUserCreate ActivityType = "user.create"
|
||||
// ActivityUserUpdate is the type for updating users.
|
||||
ActivityUserUpdate ActivityType = "user.update"
|
||||
// ActivityUserDelete is the type for deleting users.
|
||||
ActivityUserDelete ActivityType = "user.delete"
|
||||
// ActivityUserAuthSignIn is the type for user signin.
|
||||
ActivityUserAuthSignIn ActivityType = "user.auth.signin"
|
||||
// ActivityUserAuthSignUp is the type for user signup.
|
||||
ActivityUserAuthSignUp ActivityType = "user.auth.signup"
|
||||
// ActivityUserSettingUpdate is the type for updating user settings.
|
||||
ActivityUserSettingUpdate ActivityType = "user.setting.update"
|
||||
|
||||
// Memo related.
|
||||
|
||||
// ActivityMemoCreate is the type for creating memos.
|
||||
ActivityMemoCreate ActivityType = "memo.create"
|
||||
// ActivityMemoUpdate is the type for updating memos.
|
||||
ActivityMemoUpdate ActivityType = "memo.update"
|
||||
// ActivityMemoDelete is the type for deleting memos.
|
||||
ActivityMemoDelete ActivityType = "memo.delete"
|
||||
|
||||
// Shortcut related.
|
||||
|
||||
// ActivityShortcutCreate is the type for creating shortcuts.
|
||||
ActivityShortcutCreate ActivityType = "shortcut.create"
|
||||
// ActivityShortcutUpdate is the type for updating shortcuts.
|
||||
ActivityShortcutUpdate ActivityType = "shortcut.update"
|
||||
// ActivityShortcutDelete is the type for deleting shortcuts.
|
||||
ActivityShortcutDelete ActivityType = "shortcut.delete"
|
||||
|
||||
// Resource related.
|
||||
|
||||
// ActivityResourceCreate is the type for creating resources.
|
||||
ActivityResourceCreate ActivityType = "resource.create"
|
||||
// ActivityResourceDelete is the type for deleting resources.
|
||||
ActivityResourceDelete ActivityType = "resource.delete"
|
||||
|
||||
// Tag related.
|
||||
|
||||
// ActivityTagCreate is the type for creating tags.
|
||||
ActivityTagCreate ActivityType = "tag.create"
|
||||
// ActivityTagDelete is the type for deleting tags.
|
||||
ActivityTagDelete ActivityType = "tag.delete"
|
||||
|
||||
// Server related.
|
||||
|
||||
// ActivityServerStart is the type for starting server.
|
||||
ActivityServerStart ActivityType = "server.start"
|
||||
)
|
||||
|
||||
// ActivityLevel is the level of activities.
|
||||
type ActivityLevel string
|
||||
|
||||
const (
|
||||
// ActivityInfo is the INFO level of activities.
|
||||
ActivityInfo ActivityLevel = "INFO"
|
||||
// ActivityWarn is the WARN level of activities.
|
||||
ActivityWarn ActivityLevel = "WARN"
|
||||
// ActivityError is the ERROR level of activities.
|
||||
ActivityError ActivityLevel = "ERROR"
|
||||
)
|
||||
|
||||
type ActivityUserCreatePayload struct {
|
||||
UserID int `json:"userId"`
|
||||
Username string `json:"username"`
|
||||
Role Role `json:"role"`
|
||||
}
|
||||
|
||||
type ActivityUserAuthSignInPayload struct {
|
||||
UserID int `json:"userId"`
|
||||
IP string `json:"ip"`
|
||||
}
|
||||
|
||||
type ActivityUserAuthSignUpPayload struct {
|
||||
Username string `json:"username"`
|
||||
IP string `json:"ip"`
|
||||
}
|
||||
|
||||
type ActivityMemoCreatePayload struct {
|
||||
Content string `json:"content"`
|
||||
Visibility string `json:"visibility"`
|
||||
}
|
||||
|
||||
type ActivityShortcutCreatePayload struct {
|
||||
Title string `json:"title"`
|
||||
Payload string `json:"payload"`
|
||||
}
|
||||
|
||||
type ActivityResourceCreatePayload struct {
|
||||
Filename string `json:"filename"`
|
||||
Type string `json:"type"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
type ActivityTagCreatePayload struct {
|
||||
TagName string `json:"tagName"`
|
||||
}
|
||||
|
||||
type ActivityServerStartPayload struct {
|
||||
ServerID string `json:"serverId"`
|
||||
Profile *profile.Profile `json:"profile"`
|
||||
}
|
||||
|
||||
type Activity struct {
|
||||
ID int `json:"id"`
|
||||
|
||||
// Standard fields
|
||||
CreatorID int `json:"creatorId"`
|
||||
CreatedTs int64 `json:"createdTs"`
|
||||
|
||||
// Domain specific fields
|
||||
Type ActivityType `json:"type"`
|
||||
Level ActivityLevel `json:"level"`
|
||||
Payload string `json:"payload"`
|
||||
}
|
||||
|
||||
// ActivityCreate is the API message for creating an activity.
|
||||
type ActivityCreate struct {
|
||||
// Standard fields
|
||||
CreatorID int
|
||||
|
||||
// Domain specific fields
|
||||
Type ActivityType `json:"type"`
|
||||
Level ActivityLevel
|
||||
Payload string `json:"payload"`
|
||||
}
|
|
@ -8,7 +8,6 @@ import (
|
|||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/usememos/memos/api"
|
||||
"github.com/usememos/memos/common"
|
||||
"github.com/usememos/memos/plugin/idp"
|
||||
"github.com/usememos/memos/plugin/idp/oauth2"
|
||||
|
@ -41,16 +40,15 @@ func (s *APIV1Service) registerAuthRoutes(g *echo.Group, secret string) {
|
|||
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted signin request").SetInternal(err)
|
||||
}
|
||||
|
||||
userFind := &api.UserFind{
|
||||
user, err := s.Store.GetUser(ctx, &store.FindUserMessage{
|
||||
Username: &signin.Username,
|
||||
}
|
||||
user, err := s.Store.FindUser(ctx, userFind)
|
||||
})
|
||||
if err != nil && common.ErrorCode(err) != common.NotFound {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Incorrect login credentials, please try again")
|
||||
}
|
||||
if user == nil {
|
||||
return echo.NewHTTPError(http.StatusUnauthorized, "Incorrect login credentials, please try again")
|
||||
} else if user.RowStatus == api.Archived {
|
||||
} else if user.RowStatus == store.Archived {
|
||||
return echo.NewHTTPError(http.StatusForbidden, fmt.Sprintf("User has been archived with username %s", signin.Username))
|
||||
}
|
||||
|
||||
|
@ -110,20 +108,19 @@ func (s *APIV1Service) registerAuthRoutes(g *echo.Group, secret string) {
|
|||
}
|
||||
}
|
||||
|
||||
user, err := s.Store.FindUser(ctx, &api.UserFind{
|
||||
user, err := s.Store.GetUser(ctx, &store.FindUserMessage{
|
||||
Username: &userInfo.Identifier,
|
||||
})
|
||||
if err != nil && common.ErrorCode(err) != common.NotFound {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Incorrect login credentials, please try again")
|
||||
}
|
||||
if user == nil {
|
||||
userCreate := &api.UserCreate{
|
||||
userCreate := &store.UserMessage{
|
||||
Username: userInfo.Identifier,
|
||||
// The new signup user should be normal user by default.
|
||||
Role: api.NormalUser,
|
||||
Role: store.NormalUser,
|
||||
Nickname: userInfo.DisplayName,
|
||||
Email: userInfo.Email,
|
||||
Password: userInfo.Email,
|
||||
OpenID: common.GenUUID(),
|
||||
}
|
||||
password, err := common.RandomString(20)
|
||||
|
@ -135,12 +132,12 @@ func (s *APIV1Service) registerAuthRoutes(g *echo.Group, secret string) {
|
|||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
|
||||
}
|
||||
userCreate.PasswordHash = string(passwordHash)
|
||||
user, err = s.Store.CreateUser(ctx, userCreate)
|
||||
user, err = s.Store.CreateUserV1(ctx, userCreate)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create user").SetInternal(err)
|
||||
}
|
||||
}
|
||||
if user.RowStatus == api.Archived {
|
||||
if user.RowStatus == store.Archived {
|
||||
return echo.NewHTTPError(http.StatusForbidden, fmt.Sprintf("User has been archived with username %s", userInfo.Identifier))
|
||||
}
|
||||
|
||||
|
@ -160,27 +157,27 @@ func (s *APIV1Service) registerAuthRoutes(g *echo.Group, secret string) {
|
|||
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted signup request").SetInternal(err)
|
||||
}
|
||||
|
||||
userCreate := &api.UserCreate{
|
||||
Username: signup.Username,
|
||||
// The new signup user should be normal user by default.
|
||||
Role: api.NormalUser,
|
||||
Nickname: signup.Username,
|
||||
Password: signup.Password,
|
||||
OpenID: common.GenUUID(),
|
||||
}
|
||||
hostUserType := api.Host
|
||||
existedHostUsers, err := s.Store.FindUserList(ctx, &api.UserFind{
|
||||
hostUserType := store.Host
|
||||
existedHostUsers, err := s.Store.ListUsers(ctx, &store.FindUserMessage{
|
||||
Role: &hostUserType,
|
||||
})
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Failed to find users").SetInternal(err)
|
||||
}
|
||||
|
||||
userCreate := &store.UserMessage{
|
||||
Username: signup.Username,
|
||||
// The new signup user should be normal user by default.
|
||||
Role: store.NormalUser,
|
||||
Nickname: signup.Username,
|
||||
OpenID: common.GenUUID(),
|
||||
}
|
||||
if len(existedHostUsers) == 0 {
|
||||
// Change the default role to host if there is no host user.
|
||||
userCreate.Role = api.Host
|
||||
userCreate.Role = store.Host
|
||||
} else {
|
||||
allowSignUpSetting, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{
|
||||
Name: api.SystemSettingAllowSignUpName,
|
||||
allowSignUpSetting, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSettingMessage{
|
||||
Name: SystemSettingAllowSignUpName.String(),
|
||||
})
|
||||
if err != nil && common.ErrorCode(err) != common.NotFound {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting").SetInternal(err)
|
||||
|
@ -198,17 +195,13 @@ func (s *APIV1Service) registerAuthRoutes(g *echo.Group, secret string) {
|
|||
}
|
||||
}
|
||||
|
||||
if err := userCreate.Validate(); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format").SetInternal(err)
|
||||
}
|
||||
|
||||
passwordHash, err := bcrypt.GenerateFromPassword([]byte(signup.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
|
||||
}
|
||||
|
||||
userCreate.PasswordHash = string(passwordHash)
|
||||
user, err := s.Store.CreateUser(ctx, userCreate)
|
||||
user, err := s.Store.CreateUserV1(ctx, userCreate)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create user").SetInternal(err)
|
||||
}
|
||||
|
@ -228,9 +221,9 @@ func (s *APIV1Service) registerAuthRoutes(g *echo.Group, secret string) {
|
|||
})
|
||||
}
|
||||
|
||||
func (s *APIV1Service) createAuthSignInActivity(c echo.Context, user *api.User) error {
|
||||
func (s *APIV1Service) createAuthSignInActivity(c echo.Context, user *store.UserMessage) error {
|
||||
ctx := c.Request().Context()
|
||||
payload := api.ActivityUserAuthSignInPayload{
|
||||
payload := ActivityUserAuthSignInPayload{
|
||||
UserID: user.ID,
|
||||
IP: echo.ExtractIPFromRealIPHeader()(c.Request()),
|
||||
}
|
||||
|
@ -238,10 +231,10 @@ func (s *APIV1Service) createAuthSignInActivity(c echo.Context, user *api.User)
|
|||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal activity payload")
|
||||
}
|
||||
activity, err := s.Store.CreateActivity(ctx, &api.ActivityCreate{
|
||||
activity, err := s.Store.CreateActivityV1(ctx, &store.ActivityMessage{
|
||||
CreatorID: user.ID,
|
||||
Type: api.ActivityUserAuthSignIn,
|
||||
Level: api.ActivityInfo,
|
||||
Type: string(ActivityUserAuthSignIn),
|
||||
Level: string(ActivityInfo),
|
||||
Payload: string(payloadBytes),
|
||||
})
|
||||
if err != nil || activity == nil {
|
||||
|
@ -250,9 +243,9 @@ func (s *APIV1Service) createAuthSignInActivity(c echo.Context, user *api.User)
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *APIV1Service) createAuthSignUpActivity(c echo.Context, user *api.User) error {
|
||||
func (s *APIV1Service) createAuthSignUpActivity(c echo.Context, user *store.UserMessage) error {
|
||||
ctx := c.Request().Context()
|
||||
payload := api.ActivityUserAuthSignUpPayload{
|
||||
payload := ActivityUserAuthSignUpPayload{
|
||||
Username: user.Username,
|
||||
IP: echo.ExtractIPFromRealIPHeader()(c.Request()),
|
||||
}
|
||||
|
@ -260,10 +253,10 @@ func (s *APIV1Service) createAuthSignUpActivity(c echo.Context, user *api.User)
|
|||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal activity payload")
|
||||
}
|
||||
activity, err := s.Store.CreateActivity(ctx, &api.ActivityCreate{
|
||||
activity, err := s.Store.CreateActivityV1(ctx, &store.ActivityMessage{
|
||||
CreatorID: user.ID,
|
||||
Type: api.ActivityUserAuthSignUp,
|
||||
Level: api.ActivityInfo,
|
||||
Type: string(ActivityUserAuthSignUp),
|
||||
Level: string(ActivityInfo),
|
||||
Payload: string(payloadBytes),
|
||||
})
|
||||
if err != nil || activity == nil {
|
||||
|
|
25
api/v1/memo.go
Normal file
25
api/v1/memo.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package v1
|
||||
|
||||
// Visibility is the type of a visibility.
|
||||
type Visibility string
|
||||
|
||||
const (
|
||||
// Public is the PUBLIC visibility.
|
||||
Public Visibility = "PUBLIC"
|
||||
// Protected is the PROTECTED visibility.
|
||||
Protected Visibility = "PROTECTED"
|
||||
// Private is the PRIVATE visibility.
|
||||
Private Visibility = "PRIVATE"
|
||||
)
|
||||
|
||||
func (v Visibility) String() string {
|
||||
switch v {
|
||||
case Public:
|
||||
return "PUBLIC"
|
||||
case Protected:
|
||||
return "PROTECTED"
|
||||
case Private:
|
||||
return "PRIVATE"
|
||||
}
|
||||
return "PRIVATE"
|
||||
}
|
194
api/v1/system_setting.go
Normal file
194
api/v1/system_setting.go
Normal file
|
@ -0,0 +1,194 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SystemSettingName string
|
||||
|
||||
const (
|
||||
// SystemSettingServerIDName is the name of server id.
|
||||
SystemSettingServerIDName SystemSettingName = "server-id"
|
||||
// SystemSettingSecretSessionName is the name of secret session.
|
||||
SystemSettingSecretSessionName SystemSettingName = "secret-session"
|
||||
// SystemSettingAllowSignUpName is the name of allow signup setting.
|
||||
SystemSettingAllowSignUpName SystemSettingName = "allow-signup"
|
||||
// SystemSettingDisablePublicMemosName is the name of disable public memos setting.
|
||||
SystemSettingDisablePublicMemosName SystemSettingName = "disable-public-memos"
|
||||
// SystemSettingMaxUploadSizeMiBName is the name of max upload size setting.
|
||||
SystemSettingMaxUploadSizeMiBName SystemSettingName = "max-upload-size-mib"
|
||||
// SystemSettingAdditionalStyleName is the name of additional style.
|
||||
SystemSettingAdditionalStyleName SystemSettingName = "additional-style"
|
||||
// SystemSettingAdditionalScriptName is the name of additional script.
|
||||
SystemSettingAdditionalScriptName SystemSettingName = "additional-script"
|
||||
// SystemSettingCustomizedProfileName is the name of customized server profile.
|
||||
SystemSettingCustomizedProfileName SystemSettingName = "customized-profile"
|
||||
// SystemSettingStorageServiceIDName is the name of storage service ID.
|
||||
SystemSettingStorageServiceIDName SystemSettingName = "storage-service-id"
|
||||
// SystemSettingLocalStoragePathName is the name of local storage path.
|
||||
SystemSettingLocalStoragePathName SystemSettingName = "local-storage-path"
|
||||
// SystemSettingOpenAIConfigName is the name of OpenAI config.
|
||||
SystemSettingOpenAIConfigName SystemSettingName = "openai-config"
|
||||
// SystemSettingTelegramBotToken is the name of Telegram Bot Token.
|
||||
SystemSettingTelegramBotTokenName SystemSettingName = "telegram-bot-token"
|
||||
SystemSettingMemoDisplayWithUpdatedTsName SystemSettingName = "memo-display-with-updated-ts"
|
||||
)
|
||||
|
||||
// CustomizedProfile is the struct definition for SystemSettingCustomizedProfileName system setting item.
|
||||
type CustomizedProfile struct {
|
||||
// Name is the server name, default is `memos`
|
||||
Name string `json:"name"`
|
||||
// LogoURL is the url of logo image.
|
||||
LogoURL string `json:"logoUrl"`
|
||||
// Description is the server description.
|
||||
Description string `json:"description"`
|
||||
// Locale is the server default locale.
|
||||
Locale string `json:"locale"`
|
||||
// Appearance is the server default appearance.
|
||||
Appearance string `json:"appearance"`
|
||||
// ExternalURL is the external url of server. e.g. https://usermemos.com
|
||||
ExternalURL string `json:"externalUrl"`
|
||||
}
|
||||
|
||||
type OpenAIConfig struct {
|
||||
Key string `json:"key"`
|
||||
Host string `json:"host"`
|
||||
}
|
||||
|
||||
func (key SystemSettingName) String() string {
|
||||
switch key {
|
||||
case SystemSettingServerIDName:
|
||||
return "server-id"
|
||||
case SystemSettingSecretSessionName:
|
||||
return "secret-session"
|
||||
case SystemSettingAllowSignUpName:
|
||||
return "allow-signup"
|
||||
case SystemSettingDisablePublicMemosName:
|
||||
return "disable-public-memos"
|
||||
case SystemSettingMaxUploadSizeMiBName:
|
||||
return "max-upload-size-mib"
|
||||
case SystemSettingAdditionalStyleName:
|
||||
return "additional-style"
|
||||
case SystemSettingAdditionalScriptName:
|
||||
return "additional-script"
|
||||
case SystemSettingCustomizedProfileName:
|
||||
return "customized-profile"
|
||||
case SystemSettingStorageServiceIDName:
|
||||
return "storage-service-id"
|
||||
case SystemSettingLocalStoragePathName:
|
||||
return "local-storage-path"
|
||||
case SystemSettingOpenAIConfigName:
|
||||
return "openai-config"
|
||||
case SystemSettingTelegramBotTokenName:
|
||||
return "telegram-bot-token"
|
||||
case SystemSettingMemoDisplayWithUpdatedTsName:
|
||||
return "memo-display-with-updated-ts"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type SystemSetting struct {
|
||||
Name SystemSettingName `json:"name"`
|
||||
// Value is a JSON string with basic value.
|
||||
Value string `json:"value"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type SystemSettingUpsert struct {
|
||||
Name SystemSettingName `json:"name"`
|
||||
Value string `json:"value"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
const systemSettingUnmarshalError = `failed to unmarshal value from system setting "%v"`
|
||||
|
||||
func (upsert SystemSettingUpsert) Validate() error {
|
||||
switch settingName := upsert.Name; settingName {
|
||||
case SystemSettingServerIDName:
|
||||
return fmt.Errorf("updating %v is not allowed", settingName)
|
||||
case SystemSettingAllowSignUpName:
|
||||
var value bool
|
||||
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
|
||||
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
||||
}
|
||||
case SystemSettingDisablePublicMemosName:
|
||||
var value bool
|
||||
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
|
||||
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
||||
}
|
||||
case SystemSettingMaxUploadSizeMiBName:
|
||||
var value int
|
||||
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
|
||||
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
||||
}
|
||||
case SystemSettingAdditionalStyleName:
|
||||
var value string
|
||||
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
|
||||
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
||||
}
|
||||
case SystemSettingAdditionalScriptName:
|
||||
var value string
|
||||
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
|
||||
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
||||
}
|
||||
case SystemSettingCustomizedProfileName:
|
||||
customizedProfile := CustomizedProfile{
|
||||
Name: "memos",
|
||||
LogoURL: "",
|
||||
Description: "",
|
||||
Locale: "en",
|
||||
Appearance: "system",
|
||||
ExternalURL: "",
|
||||
}
|
||||
if err := json.Unmarshal([]byte(upsert.Value), &customizedProfile); err != nil {
|
||||
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
||||
}
|
||||
case SystemSettingStorageServiceIDName:
|
||||
// Note: 0 is the default value(database) for storage service ID.
|
||||
value := 0
|
||||
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
|
||||
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
||||
}
|
||||
return nil
|
||||
case SystemSettingLocalStoragePathName:
|
||||
value := ""
|
||||
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
|
||||
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
||||
}
|
||||
case SystemSettingOpenAIConfigName:
|
||||
value := OpenAIConfig{}
|
||||
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
|
||||
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
||||
}
|
||||
case SystemSettingTelegramBotTokenName:
|
||||
if upsert.Value == "" {
|
||||
return nil
|
||||
}
|
||||
// Bot Token with Reverse Proxy shoule like `http.../bot<token>`
|
||||
if strings.HasPrefix(upsert.Value, "http") {
|
||||
slashIndex := strings.LastIndexAny(upsert.Value, "/")
|
||||
if strings.HasPrefix(upsert.Value[slashIndex:], "/bot") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("token start with `http` must end with `/bot<token>`")
|
||||
}
|
||||
fragments := strings.Split(upsert.Value, ":")
|
||||
if len(fragments) != 2 {
|
||||
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
||||
}
|
||||
case SystemSettingMemoDisplayWithUpdatedTsName:
|
||||
var value bool
|
||||
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
|
||||
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("invalid system setting name")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SystemSettingFind struct {
|
||||
Name SystemSettingName `json:"name"`
|
||||
}
|
134
api/v1/user_setting.go
Normal file
134
api/v1/user_setting.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type UserSettingKey string
|
||||
|
||||
const (
|
||||
// UserSettingLocaleKey is the key type for user locale.
|
||||
UserSettingLocaleKey UserSettingKey = "locale"
|
||||
// UserSettingAppearanceKey is the key type for user appearance.
|
||||
UserSettingAppearanceKey UserSettingKey = "appearance"
|
||||
// UserSettingMemoVisibilityKey is the key type for user preference memo default visibility.
|
||||
UserSettingMemoVisibilityKey UserSettingKey = "memo-visibility"
|
||||
// UserSettingTelegramUserID is the key type for telegram UserID of memos user.
|
||||
UserSettingTelegramUserIDKey UserSettingKey = "telegram-user-id"
|
||||
)
|
||||
|
||||
// String returns the string format of UserSettingKey type.
|
||||
func (key UserSettingKey) String() string {
|
||||
switch key {
|
||||
case UserSettingLocaleKey:
|
||||
return "locale"
|
||||
case UserSettingAppearanceKey:
|
||||
return "appearance"
|
||||
case UserSettingMemoVisibilityKey:
|
||||
return "memo-visibility"
|
||||
case UserSettingTelegramUserIDKey:
|
||||
return "telegram-user-id"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var (
|
||||
UserSettingLocaleValue = []string{
|
||||
"de",
|
||||
"en",
|
||||
"es",
|
||||
"fr",
|
||||
"hr",
|
||||
"it",
|
||||
"ja",
|
||||
"ko",
|
||||
"nl",
|
||||
"pl",
|
||||
"pt-BR",
|
||||
"ru",
|
||||
"sl",
|
||||
"sv",
|
||||
"tr",
|
||||
"uk",
|
||||
"vi",
|
||||
"zh-Hans",
|
||||
"zh-Hant",
|
||||
}
|
||||
UserSettingAppearanceValue = []string{"system", "light", "dark"}
|
||||
UserSettingMemoVisibilityValue = []Visibility{Private, Protected, Public}
|
||||
)
|
||||
|
||||
type UserSetting struct {
|
||||
UserID int
|
||||
Key UserSettingKey `json:"key"`
|
||||
// Value is a JSON string with basic value
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type UserSettingUpsert struct {
|
||||
UserID int `json:"-"`
|
||||
Key UserSettingKey `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func (upsert UserSettingUpsert) Validate() error {
|
||||
if upsert.Key == UserSettingLocaleKey {
|
||||
localeValue := "en"
|
||||
err := json.Unmarshal([]byte(upsert.Value), &localeValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal user setting locale value")
|
||||
}
|
||||
if !slices.Contains(UserSettingLocaleValue, localeValue) {
|
||||
return fmt.Errorf("invalid user setting locale value")
|
||||
}
|
||||
} else if upsert.Key == UserSettingAppearanceKey {
|
||||
appearanceValue := "system"
|
||||
err := json.Unmarshal([]byte(upsert.Value), &appearanceValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal user setting appearance value")
|
||||
}
|
||||
if !slices.Contains(UserSettingAppearanceValue, appearanceValue) {
|
||||
return fmt.Errorf("invalid user setting appearance value")
|
||||
}
|
||||
} else if upsert.Key == UserSettingMemoVisibilityKey {
|
||||
memoVisibilityValue := Private
|
||||
err := json.Unmarshal([]byte(upsert.Value), &memoVisibilityValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal user setting memo visibility value")
|
||||
}
|
||||
if !slices.Contains(UserSettingMemoVisibilityValue, memoVisibilityValue) {
|
||||
return fmt.Errorf("invalid user setting memo visibility value")
|
||||
}
|
||||
} else if upsert.Key == UserSettingTelegramUserIDKey {
|
||||
var s string
|
||||
err := json.Unmarshal([]byte(upsert.Value), &s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid user setting telegram user id value")
|
||||
}
|
||||
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
if _, err := strconv.Atoi(s); err != nil {
|
||||
return fmt.Errorf("invalid user setting telegram user id value")
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("invalid user setting key")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type UserSettingFind struct {
|
||||
UserID *int
|
||||
|
||||
Key UserSettingKey `json:"key"`
|
||||
}
|
||||
|
||||
type UserSettingDelete struct {
|
||||
UserID int
|
||||
}
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/usememos/memos/api"
|
||||
"github.com/usememos/memos/store"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -64,7 +64,7 @@ func GenerateRefreshToken(userName string, userID int, secret string) (string, e
|
|||
}
|
||||
|
||||
// GenerateTokensAndSetCookies generates jwt token and saves it to the http-only cookie.
|
||||
func GenerateTokensAndSetCookies(c echo.Context, user *api.User, secret string) error {
|
||||
func GenerateTokensAndSetCookies(c echo.Context, user *store.UserMessage, secret string) error {
|
||||
accessToken, err := GenerateAccessToken(user.Username, user.ID, secret)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to generate access token")
|
||||
|
|
|
@ -10,9 +10,9 @@ import (
|
|||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/usememos/memos/api"
|
||||
"github.com/usememos/memos/common"
|
||||
"github.com/usememos/memos/server/auth"
|
||||
"github.com/usememos/memos/store"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -136,7 +136,7 @@ func JWTMiddleware(server *Server, next echo.HandlerFunc, secret string) echo.Ha
|
|||
}
|
||||
|
||||
// Even if there is no error, we still need to make sure the user still exists.
|
||||
user, err := server.Store.FindUser(ctx, &api.UserFind{
|
||||
user, err := server.Store.GetUser(ctx, &store.FindUserMessage{
|
||||
ID: &userID,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/usememos/memos/api"
|
||||
apiv1 "github.com/usememos/memos/api/v1"
|
||||
"github.com/usememos/memos/common"
|
||||
"github.com/usememos/memos/store"
|
||||
|
||||
|
@ -37,9 +38,9 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
|
|||
}
|
||||
|
||||
if createMemoRequest.Visibility == "" {
|
||||
userMemoVisibilitySetting, err := s.Store.FindUserSetting(ctx, &api.UserSettingFind{
|
||||
userMemoVisibilitySetting, err := s.Store.GetUserSetting(ctx, &store.FindUserSettingMessage{
|
||||
UserID: &userID,
|
||||
Key: api.UserSettingMemoVisibilityKey,
|
||||
Key: apiv1.UserSettingMemoVisibilityKey.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user setting").SetInternal(err)
|
||||
|
|
|
@ -7,7 +7,9 @@ import (
|
|||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/usememos/memos/api"
|
||||
apiv1 "github.com/usememos/memos/api/v1"
|
||||
"github.com/usememos/memos/common"
|
||||
"github.com/usememos/memos/plugin/telegram"
|
||||
"github.com/usememos/memos/store"
|
||||
|
@ -37,14 +39,13 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot,
|
|||
}
|
||||
|
||||
var creatorID int
|
||||
userSettingList, err := t.store.FindUserSettingList(ctx, &api.UserSettingFind{
|
||||
Key: api.UserSettingTelegramUserIDKey,
|
||||
userSettingMessageList, err := t.store.ListUserSettings(ctx, &store.FindUserSettingMessage{
|
||||
Key: apiv1.UserSettingTelegramUserIDKey.String(),
|
||||
})
|
||||
if err != nil {
|
||||
_, err := bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("Fail to find memo user: %s", err), nil)
|
||||
return err
|
||||
return errors.Wrap(err, "Failed to find userSettingList")
|
||||
}
|
||||
for _, userSetting := range userSettingList {
|
||||
for _, userSetting := range userSettingMessageList {
|
||||
var value string
|
||||
if err := json.Unmarshal([]byte(userSetting.Value), &value); err != nil {
|
||||
continue
|
||||
|
|
|
@ -9,7 +9,9 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/usememos/memos/api"
|
||||
apiv1 "github.com/usememos/memos/api/v1"
|
||||
"github.com/usememos/memos/common"
|
||||
"github.com/usememos/memos/store"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
@ -83,7 +85,7 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
|
|||
return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
|
||||
}
|
||||
|
||||
userSettingUpsert := &api.UserSettingUpsert{}
|
||||
userSettingUpsert := &apiv1.UserSettingUpsert{}
|
||||
if err := json.NewDecoder(c.Request().Body).Decode(userSettingUpsert); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post user setting upsert request").SetInternal(err)
|
||||
}
|
||||
|
@ -92,10 +94,15 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
|
|||
}
|
||||
|
||||
userSettingUpsert.UserID = userID
|
||||
userSetting, err := s.Store.UpsertUserSetting(ctx, userSettingUpsert)
|
||||
userSettingMessage, err := s.Store.UpsertUserSettingV1(ctx, &store.UserSettingMessage{
|
||||
UserID: userID,
|
||||
Key: userSettingUpsert.Key.String(),
|
||||
Value: userSettingUpsert.Value,
|
||||
})
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert user setting").SetInternal(err)
|
||||
}
|
||||
userSetting := convertUserSettingFromStore(userSettingMessage)
|
||||
return c.JSON(http.StatusOK, composeResponse(userSetting))
|
||||
})
|
||||
|
||||
|
@ -115,12 +122,21 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
|
|||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
|
||||
}
|
||||
|
||||
userSettingList, err := s.Store.FindUserSettingList(ctx, &api.UserSettingFind{
|
||||
userSettingMessageList, err := s.Store.ListUserSettings(ctx, &store.FindUserSettingMessage{
|
||||
UserID: &userID,
|
||||
})
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find userSettingList").SetInternal(err)
|
||||
}
|
||||
userSettingList := []*api.UserSetting{}
|
||||
for _, userSettingMessage := range userSettingMessageList {
|
||||
userSettingV1 := convertUserSettingFromStore(userSettingMessage)
|
||||
userSettingList = append(userSettingList, &api.UserSetting{
|
||||
UserID: userSettingV1.UserID,
|
||||
Key: api.UserSettingKey(userSettingV1.Key),
|
||||
Value: userSettingV1.Value,
|
||||
})
|
||||
}
|
||||
user.UserSettingList = userSettingList
|
||||
return c.JSON(http.StatusOK, composeResponse(user))
|
||||
})
|
||||
|
@ -202,12 +218,21 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
|
|||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch user").SetInternal(err)
|
||||
}
|
||||
|
||||
userSettingList, err := s.Store.FindUserSettingList(ctx, &api.UserSettingFind{
|
||||
userSettingMessageList, err := s.Store.ListUserSettings(ctx, &store.FindUserSettingMessage{
|
||||
UserID: &userID,
|
||||
})
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find userSettingList").SetInternal(err)
|
||||
}
|
||||
userSettingList := []*api.UserSetting{}
|
||||
for _, userSettingMessage := range userSettingMessageList {
|
||||
userSettingV1 := convertUserSettingFromStore(userSettingMessage)
|
||||
userSettingList = append(userSettingList, &api.UserSetting{
|
||||
UserID: userSettingV1.UserID,
|
||||
Key: api.UserSettingKey(userSettingV1.Key),
|
||||
Value: userSettingV1.Value,
|
||||
})
|
||||
}
|
||||
user.UserSettingList = userSettingList
|
||||
return c.JSON(http.StatusOK, composeResponse(user))
|
||||
})
|
||||
|
@ -271,3 +296,11 @@ func (s *Server) createUserCreateActivity(c echo.Context, user *api.User) error
|
|||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func convertUserSettingFromStore(userSetting *store.UserSettingMessage) *apiv1.UserSetting {
|
||||
return &apiv1.UserSetting{
|
||||
UserID: userSetting.UserID,
|
||||
Key: apiv1.UserSettingKey(userSetting.Key),
|
||||
Value: userSetting.Value,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,51 @@ import (
|
|||
"github.com/usememos/memos/api"
|
||||
)
|
||||
|
||||
type ActivityMessage struct {
|
||||
ID int
|
||||
|
||||
// Standard fields
|
||||
CreatorID int
|
||||
CreatedTs int64
|
||||
|
||||
// Domain specific fields
|
||||
Type string
|
||||
Level string
|
||||
Payload string
|
||||
}
|
||||
|
||||
// CreateActivityV1 creates an instance of Activity.
|
||||
func (s *Store) CreateActivityV1(ctx context.Context, create *ActivityMessage) (*ActivityMessage, error) {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
query := `
|
||||
INSERT INTO activity (
|
||||
creator_id,
|
||||
type,
|
||||
level,
|
||||
payload
|
||||
)
|
||||
VALUES (?, ?, ?, ?)
|
||||
RETURNING id, created_ts
|
||||
`
|
||||
if err := tx.QueryRowContext(ctx, query, create.CreatorID, create.Type, create.Level, create.Payload).Scan(
|
||||
&create.ID,
|
||||
&create.CreatedTs,
|
||||
); err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
activityMessage := create
|
||||
return activityMessage, nil
|
||||
}
|
||||
|
||||
// activityRaw is the store model for an Activity.
|
||||
// Fields have exactly the same meanings as Activity.
|
||||
type activityRaw struct {
|
||||
|
@ -38,10 +83,6 @@ func (raw *activityRaw) toActivity() *api.Activity {
|
|||
|
||||
// CreateActivity creates an instance of Activity.
|
||||
func (s *Store) CreateActivity(ctx context.Context, create *api.ActivityCreate) (*api.Activity, error) {
|
||||
if s.Profile.Mode == "prod" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, FormatError(err)
|
||||
|
|
|
@ -2,14 +2,8 @@ package store
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/usememos/memos/api"
|
||||
)
|
||||
|
||||
func getUserSettingCacheKey(userSetting userSettingRaw) string {
|
||||
return fmt.Sprintf("%d-%s", userSetting.UserID, userSetting.Key.String())
|
||||
}
|
||||
|
||||
func getUserSettingFindCacheKey(userSettingFind *api.UserSettingFind) string {
|
||||
return fmt.Sprintf("%d-%s", userSettingFind.UserID, userSettingFind.Key.String())
|
||||
func getUserSettingCacheKeyV1(userID int, key string) string {
|
||||
return fmt.Sprintf("%d-%s", userID, key)
|
||||
}
|
||||
|
|
|
@ -291,7 +291,7 @@ func listIdentityProviders(ctx context.Context, tx *sql.Tx, find *FindIdentityPr
|
|||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, FormatError(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return identityProviderMessages, nil
|
||||
|
|
|
@ -14,9 +14,9 @@ type Store struct {
|
|||
db *sql.DB
|
||||
systemSettingCache sync.Map // map[string]*systemSettingRaw
|
||||
userCache sync.Map // map[int]*userRaw
|
||||
userSettingCache sync.Map // map[string]*userSettingRaw
|
||||
userSettingCache sync.Map // map[string]*UserSettingMessage
|
||||
shortcutCache sync.Map // map[int]*shortcutRaw
|
||||
idpCache sync.Map // map[int]*identityProviderMessage
|
||||
idpCache sync.Map // map[int]*IdentityProviderMessage
|
||||
resourceCache sync.Map // map[int]*resourceRaw
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,101 @@ import (
|
|||
"github.com/usememos/memos/common"
|
||||
)
|
||||
|
||||
type SystemSettingMessage struct {
|
||||
Name string
|
||||
Value string
|
||||
Description string
|
||||
}
|
||||
|
||||
type FindSystemSettingMessage struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (s *Store) ListSystemSettings(ctx context.Context, find *FindSystemSettingMessage) ([]*SystemSettingMessage, error) {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
list, err := listSystemSettings(ctx, tx, find)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, systemSettingMessage := range list {
|
||||
s.systemSettingCache.Store(systemSettingMessage.Name, systemSettingMessage)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetSystemSetting(ctx context.Context, find *FindSystemSettingMessage) (*SystemSettingMessage, error) {
|
||||
if find.Name != "" {
|
||||
if cache, ok := s.systemSettingCache.Load(find.Name); ok {
|
||||
return cache.(*SystemSettingMessage), nil
|
||||
}
|
||||
}
|
||||
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
list, err := listSystemSettings(ctx, tx, find)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(list) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
systemSettingMessage := list[0]
|
||||
s.systemSettingCache.Store(systemSettingMessage.Name, systemSettingMessage)
|
||||
return systemSettingMessage, nil
|
||||
}
|
||||
|
||||
func listSystemSettings(ctx context.Context, tx *sql.Tx, find *FindSystemSettingMessage) ([]*SystemSettingMessage, error) {
|
||||
where, args := []string{"1 = 1"}, []any{}
|
||||
if find.Name != "" {
|
||||
where, args = append(where, "name = ?"), append(args, find.Name)
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
name,
|
||||
value,
|
||||
description
|
||||
FROM system_setting
|
||||
WHERE ` + strings.Join(where, " AND ")
|
||||
|
||||
rows, err := tx.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
list := []*SystemSettingMessage{}
|
||||
for rows.Next() {
|
||||
systemSettingMessage := &SystemSettingMessage{}
|
||||
if err := rows.Scan(
|
||||
&systemSettingMessage.Name,
|
||||
&systemSettingMessage.Value,
|
||||
&systemSettingMessage.Description,
|
||||
); err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
list = append(list, systemSettingMessage)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
type systemSettingRaw struct {
|
||||
Name api.SystemSettingName
|
||||
Value string
|
||||
|
|
|
@ -10,23 +10,6 @@ import (
|
|||
"github.com/usememos/memos/common"
|
||||
)
|
||||
|
||||
func (s *Store) SeedDataForNewUser(ctx context.Context, user *api.User) error {
|
||||
// Create a memo for the user.
|
||||
_, err := s.CreateMemo(ctx, &MemoMessage{
|
||||
CreatorID: user.ID,
|
||||
Content: "#inbox Welcome to Memos!",
|
||||
Visibility: Private,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.UpsertTag(ctx, &api.TagUpsert{
|
||||
CreatorID: user.ID,
|
||||
Name: "inbox",
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Role is the type of a role.
|
||||
type Role string
|
||||
|
||||
|
@ -289,10 +272,6 @@ func (s *Store) CreateUser(ctx context.Context, create *api.UserCreate) (*api.Us
|
|||
s.userCache.Store(userRaw.ID, userRaw)
|
||||
user := userRaw.toUser()
|
||||
|
||||
if err := s.SeedDataForNewUser(ctx, user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -4,33 +4,35 @@ import (
|
|||
"context"
|
||||
"database/sql"
|
||||
"strings"
|
||||
|
||||
"github.com/usememos/memos/api"
|
||||
)
|
||||
|
||||
type userSettingRaw struct {
|
||||
type UserSettingMessage struct {
|
||||
UserID int
|
||||
Key api.UserSettingKey
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (raw *userSettingRaw) toUserSetting() *api.UserSetting {
|
||||
return &api.UserSetting{
|
||||
UserID: raw.UserID,
|
||||
Key: raw.Key,
|
||||
Value: raw.Value,
|
||||
}
|
||||
type FindUserSettingMessage struct {
|
||||
UserID *int
|
||||
Key string
|
||||
}
|
||||
|
||||
func (s *Store) UpsertUserSetting(ctx context.Context, upsert *api.UserSettingUpsert) (*api.UserSetting, error) {
|
||||
func (s *Store) UpsertUserSettingV1(ctx context.Context, upsert *UserSettingMessage) (*UserSettingMessage, error) {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
userSettingRaw, err := upsertUserSetting(ctx, tx, upsert)
|
||||
if err != nil {
|
||||
query := `
|
||||
INSERT INTO user_setting (
|
||||
user_id, key, value
|
||||
)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(user_id, key) DO UPDATE
|
||||
SET value = EXCLUDED.value
|
||||
`
|
||||
if _, err := tx.ExecContext(ctx, query, upsert.UserID, upsert.Key, upsert.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -38,39 +40,34 @@ func (s *Store) UpsertUserSetting(ctx context.Context, upsert *api.UserSettingUp
|
|||
return nil, err
|
||||
}
|
||||
|
||||
s.userSettingCache.Store(getUserSettingCacheKey(*userSettingRaw), userSettingRaw)
|
||||
userSetting := userSettingRaw.toUserSetting()
|
||||
|
||||
return userSetting, nil
|
||||
userSettingMessage := upsert
|
||||
s.userSettingCache.Store(getUserSettingCacheKeyV1(userSettingMessage.UserID, userSettingMessage.Key), userSettingMessage)
|
||||
return userSettingMessage, nil
|
||||
}
|
||||
|
||||
func (s *Store) FindUserSettingList(ctx context.Context, find *api.UserSettingFind) ([]*api.UserSetting, error) {
|
||||
func (s *Store) ListUserSettings(ctx context.Context, find *FindUserSettingMessage) ([]*UserSettingMessage, error) {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
userSettingRawList, err := findUserSettingList(ctx, tx, find)
|
||||
userSettingList, err := listUserSettings(ctx, tx, find)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := []*api.UserSetting{}
|
||||
for _, raw := range userSettingRawList {
|
||||
s.userSettingCache.Store(getUserSettingCacheKey(*raw), raw)
|
||||
list = append(list, raw.toUserSetting())
|
||||
for _, userSetting := range userSettingList {
|
||||
s.userSettingCache.Store(getUserSettingCacheKeyV1(userSetting.UserID, userSetting.Key), userSetting)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
return userSettingList, nil
|
||||
}
|
||||
|
||||
func (s *Store) FindUserSetting(ctx context.Context, find *api.UserSettingFind) (*api.UserSetting, error) {
|
||||
if userSetting, ok := s.userSettingCache.Load(getUserSettingFindCacheKey(find)); ok {
|
||||
if userSetting == nil {
|
||||
return nil, nil
|
||||
func (s *Store) GetUserSetting(ctx context.Context, find *FindUserSettingMessage) (*UserSettingMessage, error) {
|
||||
if find.UserID != nil {
|
||||
if cache, ok := s.userSettingCache.Load(getUserSettingCacheKeyV1(*find.UserID, find.Key)); ok {
|
||||
return cache.(*UserSettingMessage), nil
|
||||
}
|
||||
return userSetting.(*userSettingRaw).toUserSetting(), nil
|
||||
}
|
||||
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
|
@ -79,51 +76,25 @@ func (s *Store) FindUserSetting(ctx context.Context, find *api.UserSettingFind)
|
|||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
list, err := findUserSettingList(ctx, tx, find)
|
||||
list, err := listUserSettings(ctx, tx, find)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(list) == 0 {
|
||||
s.userSettingCache.Store(getUserSettingFindCacheKey(find), nil)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
userSettingRaw := list[0]
|
||||
s.userSettingCache.Store(getUserSettingCacheKey(*userSettingRaw), userSettingRaw)
|
||||
return userSettingRaw.toUserSetting(), nil
|
||||
userSettingMessage := list[0]
|
||||
s.userSettingCache.Store(getUserSettingCacheKeyV1(userSettingMessage.UserID, userSettingMessage.Key), userSettingMessage)
|
||||
return userSettingMessage, nil
|
||||
}
|
||||
|
||||
func upsertUserSetting(ctx context.Context, tx *sql.Tx, upsert *api.UserSettingUpsert) (*userSettingRaw, error) {
|
||||
query := `
|
||||
INSERT INTO user_setting (
|
||||
user_id, key, value
|
||||
)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(user_id, key) DO UPDATE
|
||||
SET
|
||||
value = EXCLUDED.value
|
||||
RETURNING user_id, key, value
|
||||
`
|
||||
var userSettingRaw userSettingRaw
|
||||
if err := tx.QueryRowContext(ctx, query, upsert.UserID, upsert.Key, upsert.Value).Scan(
|
||||
&userSettingRaw.UserID,
|
||||
&userSettingRaw.Key,
|
||||
&userSettingRaw.Value,
|
||||
); err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
|
||||
return &userSettingRaw, nil
|
||||
}
|
||||
|
||||
func findUserSettingList(ctx context.Context, tx *sql.Tx, find *api.UserSettingFind) ([]*userSettingRaw, error) {
|
||||
func listUserSettings(ctx context.Context, tx *sql.Tx, find *FindUserSettingMessage) ([]*UserSettingMessage, error) {
|
||||
where, args := []string{"1 = 1"}, []any{}
|
||||
|
||||
if v := find.Key.String(); v != "" {
|
||||
if v := find.Key; v != "" {
|
||||
where, args = append(where, "key = ?"), append(args, v)
|
||||
}
|
||||
|
||||
if v := find.UserID; v != nil {
|
||||
where, args = append(where, "user_id = ?"), append(args, *find.UserID)
|
||||
}
|
||||
|
@ -141,25 +112,24 @@ func findUserSettingList(ctx context.Context, tx *sql.Tx, find *api.UserSettingF
|
|||
}
|
||||
defer rows.Close()
|
||||
|
||||
userSettingRawList := make([]*userSettingRaw, 0)
|
||||
userSettingMessageList := make([]*UserSettingMessage, 0)
|
||||
for rows.Next() {
|
||||
var userSettingRaw userSettingRaw
|
||||
var userSettingMessage UserSettingMessage
|
||||
if err := rows.Scan(
|
||||
&userSettingRaw.UserID,
|
||||
&userSettingRaw.Key,
|
||||
&userSettingRaw.Value,
|
||||
&userSettingMessage.UserID,
|
||||
&userSettingMessage.Key,
|
||||
&userSettingMessage.Value,
|
||||
); err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
|
||||
userSettingRawList = append(userSettingRawList, &userSettingRaw)
|
||||
userSettingMessageList = append(userSettingMessageList, &userSettingMessage)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
|
||||
return userSettingRawList, nil
|
||||
return userSettingMessageList, nil
|
||||
}
|
||||
|
||||
func vacuumUserSetting(ctx context.Context, tx *sql.Tx) error {
|
||||
|
|
|
@ -26,9 +26,6 @@ func TestMemoRelationServer(t *testing.T) {
|
|||
user, err := s.postAuthSignup(signup)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, signup.Username, user.Username)
|
||||
memoList, err := s.getMemoList()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, memoList, 1)
|
||||
memo, err := s.postMemoCreate(&api.CreateMemoRequest{
|
||||
Content: "test memo",
|
||||
})
|
||||
|
@ -45,9 +42,9 @@ func TestMemoRelationServer(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test memo2", memo2.Content)
|
||||
memoList, err = s.getMemoList()
|
||||
memoList, err := s.getMemoList()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, memoList, 3)
|
||||
require.Len(t, memoList, 2)
|
||||
require.Len(t, memo2.RelationList, 1)
|
||||
err = s.deleteMemoRelation(memo2.ID, memo.ID, api.MemoRelationReference)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -26,17 +26,14 @@ func TestMemoServer(t *testing.T) {
|
|||
user, err := s.postAuthSignup(signup)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, signup.Username, user.Username)
|
||||
memoList, err := s.getMemoList()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, memoList, 1)
|
||||
memo, err := s.postMemoCreate(&api.CreateMemoRequest{
|
||||
Content: "test memo",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test memo", memo.Content)
|
||||
memoList, err = s.getMemoList()
|
||||
memoList, err := s.getMemoList()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, memoList, 2)
|
||||
require.Len(t, memoList, 1)
|
||||
updatedContent := "updated memo"
|
||||
memo, err = s.patchMemo(&api.PatchMemoRequest{
|
||||
ID: memo.ID,
|
||||
|
@ -62,7 +59,7 @@ func TestMemoServer(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
memoList, err = s.getMemoList()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, memoList, 1)
|
||||
require.Len(t, memoList, 0)
|
||||
}
|
||||
|
||||
func (s *TestingServer) getMemo(memoID int) (*api.MemoResponse, error) {
|
||||
|
|
|
@ -36,8 +36,8 @@ func TestMemoStore(t *testing.T) {
|
|||
CreatorID: &user.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(memoList))
|
||||
require.Equal(t, memo, memoList[1])
|
||||
require.Equal(t, 1, len(memoList))
|
||||
require.Equal(t, memo, memoList[0])
|
||||
err = ts.DeleteMemo(ctx, &store.DeleteMemoMessage{
|
||||
ID: memo.ID,
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue