mirror of
				https://github.com/usememos/memos.git
				synced 2025-11-01 01:06:04 +08:00 
			
		
		
		
	chore: rename user role (#108)
* chore: rename user role to `host` * chore: related frontend changes * chore: fix migration file * chore: use tricky sql
This commit is contained in:
		
							parent
							
								
									6f32643d7c
								
							
						
					
					
						commit
						bdc9632b5b
					
				
					 15 changed files with 105 additions and 45 deletions
				
			
		|  | @ -3,6 +3,6 @@ package api | |||
| import "github.com/usememos/memos/server/profile" | ||||
| 
 | ||||
| type SystemStatus struct { | ||||
| 	Owner   *User            `json:"owner"` | ||||
| 	Host    *User            `json:"host"` | ||||
| 	Profile *profile.Profile `json:"profile"` | ||||
| } | ||||
|  |  | |||
|  | @ -4,16 +4,16 @@ package api | |||
| type Role string | ||||
| 
 | ||||
| const ( | ||||
| 	// Owner is the OWNER role. | ||||
| 	Owner Role = "OWNER" | ||||
| 	// Host is the HOST role. | ||||
| 	Host Role = "HOST" | ||||
| 	// NormalUser is the USER role. | ||||
| 	NormalUser Role = "USER" | ||||
| ) | ||||
| 
 | ||||
| func (e Role) String() string { | ||||
| 	switch e { | ||||
| 	case Owner: | ||||
| 		return "OWNER" | ||||
| 	case Host: | ||||
| 		return "HOST" | ||||
| 	case NormalUser: | ||||
| 		return "USER" | ||||
| 	} | ||||
|  |  | |||
|  | @ -60,17 +60,17 @@ func (s *Server) registerAuthRoutes(g *echo.Group) { | |||
| 	}) | ||||
| 
 | ||||
| 	g.POST("/auth/signup", func(c echo.Context) error { | ||||
| 		// Don't allow to signup by this api if site owner existed. | ||||
| 		ownerUserType := api.Owner | ||||
| 		ownerUserFind := api.UserFind{ | ||||
| 			Role: &ownerUserType, | ||||
| 		// Don't allow to signup by this api if site host existed. | ||||
| 		hostUserType := api.Host | ||||
| 		hostUserFind := api.UserFind{ | ||||
| 			Role: &hostUserType, | ||||
| 		} | ||||
| 		ownerUser, err := s.Store.FindUser(&ownerUserFind) | ||||
| 		hostUser, err := s.Store.FindUser(&hostUserFind) | ||||
| 		if err != nil { | ||||
| 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find owner user").SetInternal(err) | ||||
| 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find host user").SetInternal(err) | ||||
| 		} | ||||
| 		if ownerUser != nil { | ||||
| 			return echo.NewHTTPError(http.StatusUnauthorized, "Site Owner existed, please contact the site owner to signin account firstly.").SetInternal(err) | ||||
| 		if hostUser != nil { | ||||
| 			return echo.NewHTTPError(http.StatusUnauthorized, "Site Host existed, please contact the site host to signin account firstly.").SetInternal(err) | ||||
| 		} | ||||
| 
 | ||||
| 		signup := &api.Signup{} | ||||
|  |  | |||
|  | @ -21,22 +21,22 @@ func (s *Server) registerSystemRoutes(g *echo.Group) { | |||
| 	}) | ||||
| 
 | ||||
| 	g.GET("/status", func(c echo.Context) error { | ||||
| 		ownerUserType := api.Owner | ||||
| 		ownerUserFind := api.UserFind{ | ||||
| 			Role: &ownerUserType, | ||||
| 		hostUserType := api.Host | ||||
| 		hostUserFind := api.UserFind{ | ||||
| 			Role: &hostUserType, | ||||
| 		} | ||||
| 		ownerUser, err := s.Store.FindUser(&ownerUserFind) | ||||
| 		hostUser, err := s.Store.FindUser(&hostUserFind) | ||||
| 		if err != nil { | ||||
| 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find owner user").SetInternal(err) | ||||
| 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find host user").SetInternal(err) | ||||
| 		} | ||||
| 
 | ||||
| 		if ownerUser != nil { | ||||
| 		if hostUser != nil { | ||||
| 			// data desensitize | ||||
| 			ownerUser.OpenID = "" | ||||
| 			hostUser.OpenID = "" | ||||
| 		} | ||||
| 
 | ||||
| 		systemStatus := api.SystemStatus{ | ||||
| 			Owner:   ownerUser, | ||||
| 			Host:   hostUser, | ||||
| 			Profile: s.Profile, | ||||
| 		} | ||||
| 
 | ||||
|  |  | |||
|  | @ -143,7 +143,7 @@ func (s *Server) registerUserRoutes(g *echo.Group) { | |||
| 		} | ||||
| 		if currentUser == nil { | ||||
| 			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Current session user not found with ID: %d", currentUserID)).SetInternal(err) | ||||
| 		} else if currentUser.Role != api.Owner { | ||||
| 		} else if currentUser.Role != api.Host { | ||||
| 			return echo.NewHTTPError(http.StatusForbidden, "Access forbidden for current session user").SetInternal(err) | ||||
| 		} | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										56
									
								
								store/db/migration/0.2/00__user_role.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								store/db/migration/0.2/00__user_role.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| -- change user role field from "OWNER"/"USER" to "HOST"/"USER". | ||||
| 
 | ||||
| PRAGMA foreign_keys = off; | ||||
| BEGIN TRANSACTION; | ||||
| 
 | ||||
| DROP TABLE IF EXISTS _user_old; | ||||
| 
 | ||||
| ALTER TABLE  | ||||
|   user RENAME TO _user_old; | ||||
| 
 | ||||
| CREATE TABLE user ( | ||||
|   id INTEGER PRIMARY KEY AUTOINCREMENT,  | ||||
|   created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),  | ||||
|   updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),  | ||||
|   row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',  | ||||
|   email TEXT NOT NULL UNIQUE,  | ||||
|   role TEXT NOT NULL CHECK (role IN ('HOST', 'USER')) DEFAULT 'USER',  | ||||
|   name TEXT NOT NULL,  | ||||
|   password_hash TEXT NOT NULL,  | ||||
|   open_id TEXT NOT NULL UNIQUE | ||||
| ); | ||||
| 
 | ||||
| INSERT INTO user ( | ||||
|   id, created_ts, updated_ts, row_status,  | ||||
|   email, name, password_hash, open_id | ||||
| )  | ||||
| SELECT  | ||||
|   id,  | ||||
|   created_ts,  | ||||
|   updated_ts,  | ||||
|   row_status,  | ||||
|   email,  | ||||
|   name,  | ||||
|   password_hash,  | ||||
|   open_id  | ||||
| FROM  | ||||
|   _user_old; | ||||
| 
 | ||||
| UPDATE  | ||||
|   user  | ||||
| SET  | ||||
|   role = 'HOST'  | ||||
| WHERE  | ||||
|   id IN ( | ||||
|     SELECT  | ||||
|       id  | ||||
|     FROM  | ||||
|       _user_old  | ||||
|     WHERE  | ||||
|       role = 'OWNER' | ||||
|   ); | ||||
| 
 | ||||
| DROP TABLE IF EXISTS _user_old; | ||||
| 
 | ||||
| COMMIT; | ||||
| PRAGMA foreign_keys = on; | ||||
|  | @ -13,7 +13,7 @@ CREATE TABLE user ( | |||
|   -- allowed row status are 'NORMAL', 'ARCHIVED'. | ||||
|   row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL', | ||||
|   email TEXT NOT NULL UNIQUE, | ||||
|   role TEXT NOT NULL CHECK (role IN ('OWNER', 'USER')) DEFAULT 'USER', | ||||
|   role TEXT NOT NULL CHECK (role IN ('HOST', 'USER')) DEFAULT 'USER', | ||||
|   name TEXT NOT NULL, | ||||
|   password_hash TEXT NOT NULL, | ||||
|   open_id TEXT NOT NULL UNIQUE | ||||
|  |  | |||
|  | @ -11,8 +11,8 @@ VALUES | |||
|   ( | ||||
|     101,  | ||||
|     'demo@usememos.com', | ||||
|     'OWNER', | ||||
|     'Demo Owner', | ||||
|     'HOST', | ||||
|     'Demo Host', | ||||
|     'demo_open_id', | ||||
|     -- raw password: secret | ||||
|     '$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK' | ||||
|  |  | |||
|  | @ -106,7 +106,7 @@ func createUser(db *sql.DB, create *api.UserCreate) (*userRaw, error) { | |||
| 			open_id | ||||
| 		) | ||||
| 		VALUES (?, ?, ?, ?, ?) | ||||
| 		RETURNING id, email, role, name, password_hash, open_id, created_ts, updated_ts | ||||
| 		RETURNING id, email, role, name, password_hash, open_id, created_ts, updated_ts, row_status | ||||
| 	`, | ||||
| 		create.Email, | ||||
| 		create.Role, | ||||
|  | @ -130,6 +130,7 @@ func createUser(db *sql.DB, create *api.UserCreate) (*userRaw, error) { | |||
| 		&userRaw.OpenID, | ||||
| 		&userRaw.CreatedTs, | ||||
| 		&userRaw.UpdatedTs, | ||||
| 		&userRaw.RowStatus, | ||||
| 	); err != nil { | ||||
| 		return nil, FormatError(err) | ||||
| 	} | ||||
|  | @ -162,7 +163,7 @@ func patchUser(db *sql.DB, patch *api.UserPatch) (*userRaw, error) { | |||
| 		UPDATE user | ||||
| 		SET `+strings.Join(set, ", ")+` | ||||
| 		WHERE id = ? | ||||
| 		RETURNING id, email, role, name, password_hash, open_id, created_ts, updated_ts | ||||
| 		RETURNING id, email, role, name, password_hash, open_id, created_ts, updated_ts, row_status | ||||
| 	`, args...) | ||||
| 	if err != nil { | ||||
| 		return nil, FormatError(err) | ||||
|  | @ -180,6 +181,7 @@ func patchUser(db *sql.DB, patch *api.UserPatch) (*userRaw, error) { | |||
| 			&userRaw.OpenID, | ||||
| 			&userRaw.CreatedTs, | ||||
| 			&userRaw.UpdatedTs, | ||||
| 			&userRaw.RowStatus, | ||||
| 		); err != nil { | ||||
| 			return nil, FormatError(err) | ||||
| 		} | ||||
|  | @ -218,7 +220,8 @@ func findUserList(db *sql.DB, find *api.UserFind) ([]*userRaw, error) { | |||
| 			password_hash, | ||||
| 			open_id, | ||||
| 			created_ts, | ||||
| 			updated_ts | ||||
| 			updated_ts, | ||||
| 			row_status | ||||
| 		FROM user | ||||
| 		WHERE `+strings.Join(where, " AND ")+` | ||||
| 		ORDER BY created_ts DESC`, | ||||
|  | @ -241,6 +244,7 @@ func findUserList(db *sql.DB, find *api.UserFind) ([]*userRaw, error) { | |||
| 			&userRaw.OpenID, | ||||
| 			&userRaw.CreatedTs, | ||||
| 			&userRaw.UpdatedTs, | ||||
| 			&userRaw.RowStatus, | ||||
| 		); err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			return nil, FormatError(err) | ||||
|  |  | |||
|  | @ -48,7 +48,7 @@ const SettingDialog: React.FC<Props> = (props: Props) => { | |||
|             <span className="icon-text">🏟</span> Preferences | ||||
|           </span> | ||||
|         </div> | ||||
|         {user?.role === "OWNER" ? ( | ||||
|         {user?.role === "HOST" ? ( | ||||
|           <> | ||||
|             <span className="section-title">Admin</span> | ||||
|             <div className="section-items-container"> | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ const UserBanner: React.FC<Props> = () => { | |||
|       if (locationService.getState().pathname === "/") { | ||||
|         api.getSystemStatus().then(({ data }) => { | ||||
|           const { data: status } = data; | ||||
|           setUsername(status.owner.name); | ||||
|           setUsername(status.host.name); | ||||
|         }); | ||||
|       } else { | ||||
|         const currentUserId = userService.getCurrentUserId(); | ||||
|  | @ -51,7 +51,7 @@ const UserBanner: React.FC<Props> = () => { | |||
|     <div className="user-banner-container"> | ||||
|       <div className="username-container" onClick={handleUsernameClick}> | ||||
|         <span className="username-text">{username}</span> | ||||
|         {user?.role === "OWNER" ? <span className="tag">MOD</span> : null} | ||||
|         {user?.role === "HOST" ? <span className="tag">MOD</span> : null} | ||||
|       </div> | ||||
|       <span className="action-btn menu-popup-btn" onClick={handlePopupBtnClick}> | ||||
|         <img src="/icons/more.svg" className="icon-img" /> | ||||
|  |  | |||
|  | @ -69,7 +69,7 @@ | |||
|     > .tip-text { | ||||
|       @apply w-auto inline-block float-right text-sm mt-4 text-gray-500 text-right whitespace-pre-wrap; | ||||
| 
 | ||||
|       &.owner-tip { | ||||
|       &.host-tip { | ||||
|         @apply bg-blue-500 text-white px-2 py-1 rounded; | ||||
|       } | ||||
|     } | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ const validateConfig: ValidatorConfig = { | |||
| 
 | ||||
| const Signin: React.FC<Props> = () => { | ||||
|   const pageLoadingState = useLoading(true); | ||||
|   const [siteOwner, setSiteOwner] = useState<User>(); | ||||
|   const [siteHost, setSiteHost] = useState<User>(); | ||||
|   const [email, setEmail] = useState(""); | ||||
|   const [password, setPassword] = useState(""); | ||||
|   const actionBtnLoadingState = useLoading(false); | ||||
|  | @ -25,7 +25,7 @@ const Signin: React.FC<Props> = () => { | |||
|   useEffect(() => { | ||||
|     api.getSystemStatus().then(({ data }) => { | ||||
|       const { data: status } = data; | ||||
|       setSiteOwner(status.owner); | ||||
|       setSiteHost(status.host); | ||||
|       if (status.profile.mode === "dev") { | ||||
|         setEmail("demo@usememos.com"); | ||||
|         setPassword("secret"); | ||||
|  | @ -77,7 +77,7 @@ const Signin: React.FC<Props> = () => { | |||
|     actionBtnLoadingState.setFinish(); | ||||
|   }; | ||||
| 
 | ||||
|   const handleSignUpAsOwnerBtnsClick = async () => { | ||||
|   const handleSignUpAsHostBtnsClick = async () => { | ||||
|     if (actionBtnLoadingState.isLoading) { | ||||
|       return; | ||||
|     } | ||||
|  | @ -96,7 +96,7 @@ const Signin: React.FC<Props> = () => { | |||
| 
 | ||||
|     try { | ||||
|       actionBtnLoadingState.setLoading(); | ||||
|       await api.signup(email, password, "OWNER"); | ||||
|       await api.signup(email, password, "HOST"); | ||||
|       const user = await userService.doSignIn(); | ||||
|       if (user) { | ||||
|         locationService.replaceHistory("/"); | ||||
|  | @ -132,7 +132,7 @@ const Signin: React.FC<Props> = () => { | |||
|           </div> | ||||
|         </div> | ||||
|         <div className="action-btns-container"> | ||||
|           {siteOwner || pageLoadingState.isLoading ? ( | ||||
|           {siteHost || pageLoadingState.isLoading ? ( | ||||
|             <button | ||||
|               className={`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`} | ||||
|               onClick={() => handleSigninBtnsClick()} | ||||
|  | @ -142,16 +142,16 @@ const Signin: React.FC<Props> = () => { | |||
|           ) : ( | ||||
|             <button | ||||
|               className={`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`} | ||||
|               onClick={() => handleSignUpAsOwnerBtnsClick()} | ||||
|               onClick={() => handleSignUpAsHostBtnsClick()} | ||||
|             > | ||||
|               Sign up as Owner | ||||
|               Sign up as Host | ||||
|             </button> | ||||
|           )} | ||||
|         </div> | ||||
|         <p className={`tip-text ${siteOwner || pageLoadingState.isLoading ? "" : "owner-tip"}`}> | ||||
|           {siteOwner || pageLoadingState.isLoading | ||||
|             ? "If you don't have an account, please\ncontact the site owner." | ||||
|             : "You are registering as the Site Owner."} | ||||
|         <p className={`tip-text ${siteHost || pageLoadingState.isLoading ? "" : "host-tip"}`}> | ||||
|           {siteHost || pageLoadingState.isLoading | ||||
|             ? "If you don't have an account, please\ncontact the site host." | ||||
|             : "You are registering as the Site Host."} | ||||
|         </p> | ||||
|       </div> | ||||
|     </div> | ||||
|  |  | |||
							
								
								
									
										2
									
								
								web/src/types/modules/system.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								web/src/types/modules/system.d.ts
									
										
									
									
										vendored
									
									
								
							|  | @ -4,6 +4,6 @@ interface Profile { | |||
| } | ||||
| 
 | ||||
| interface SystemStatus { | ||||
|   owner: User; | ||||
|   host: User; | ||||
|   profile: Profile; | ||||
| } | ||||
|  |  | |||
							
								
								
									
										2
									
								
								web/src/types/modules/user.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								web/src/types/modules/user.d.ts
									
										
									
									
										vendored
									
									
								
							|  | @ -1,5 +1,5 @@ | |||
| type UserId = number; | ||||
| type UserRole = "OWNER" | "USER"; | ||||
| type UserRole = "HOST" | "USER"; | ||||
| 
 | ||||
| interface User { | ||||
|   id: UserId; | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue