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:
boojack 2022-07-08 22:16:18 +08:00 committed by GitHub
parent 6f32643d7c
commit bdc9632b5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 105 additions and 45 deletions

View File

@ -3,6 +3,6 @@ package api
import "github.com/usememos/memos/server/profile" import "github.com/usememos/memos/server/profile"
type SystemStatus struct { type SystemStatus struct {
Owner *User `json:"owner"` Host *User `json:"host"`
Profile *profile.Profile `json:"profile"` Profile *profile.Profile `json:"profile"`
} }

View File

@ -4,16 +4,16 @@ package api
type Role string type Role string
const ( const (
// Owner is the OWNER role. // Host is the HOST role.
Owner Role = "OWNER" Host Role = "HOST"
// NormalUser is the USER role. // NormalUser is the USER role.
NormalUser Role = "USER" NormalUser Role = "USER"
) )
func (e Role) String() string { func (e Role) String() string {
switch e { switch e {
case Owner: case Host:
return "OWNER" return "HOST"
case NormalUser: case NormalUser:
return "USER" return "USER"
} }

View File

@ -60,17 +60,17 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
}) })
g.POST("/auth/signup", func(c echo.Context) error { g.POST("/auth/signup", func(c echo.Context) error {
// Don't allow to signup by this api if site owner existed. // Don't allow to signup by this api if site host existed.
ownerUserType := api.Owner hostUserType := api.Host
ownerUserFind := api.UserFind{ hostUserFind := api.UserFind{
Role: &ownerUserType, Role: &hostUserType,
} }
ownerUser, err := s.Store.FindUser(&ownerUserFind) hostUser, err := s.Store.FindUser(&hostUserFind)
if err != nil { 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 {
return echo.NewHTTPError(http.StatusUnauthorized, "Site Owner existed, please contact the site owner to signin account firstly.").SetInternal(err) return echo.NewHTTPError(http.StatusUnauthorized, "Site Host existed, please contact the site host to signin account firstly.").SetInternal(err)
} }
signup := &api.Signup{} signup := &api.Signup{}

View File

@ -21,22 +21,22 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
}) })
g.GET("/status", func(c echo.Context) error { g.GET("/status", func(c echo.Context) error {
ownerUserType := api.Owner hostUserType := api.Host
ownerUserFind := api.UserFind{ hostUserFind := api.UserFind{
Role: &ownerUserType, Role: &hostUserType,
} }
ownerUser, err := s.Store.FindUser(&ownerUserFind) hostUser, err := s.Store.FindUser(&hostUserFind)
if err != nil { 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 // data desensitize
ownerUser.OpenID = "" hostUser.OpenID = ""
} }
systemStatus := api.SystemStatus{ systemStatus := api.SystemStatus{
Owner: ownerUser, Host: hostUser,
Profile: s.Profile, Profile: s.Profile,
} }

View File

@ -143,7 +143,7 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
} }
if currentUser == nil { if currentUser == nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Current session user not found with ID: %d", currentUserID)).SetInternal(err) 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) return echo.NewHTTPError(http.StatusForbidden, "Access forbidden for current session user").SetInternal(err)
} }

View 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;

View File

@ -13,7 +13,7 @@ CREATE TABLE user (
-- allowed row status are 'NORMAL', 'ARCHIVED'. -- allowed row status are 'NORMAL', 'ARCHIVED'.
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL', row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
email TEXT NOT NULL UNIQUE, 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, name TEXT NOT NULL,
password_hash TEXT NOT NULL, password_hash TEXT NOT NULL,
open_id TEXT NOT NULL UNIQUE open_id TEXT NOT NULL UNIQUE

View File

@ -11,8 +11,8 @@ VALUES
( (
101, 101,
'demo@usememos.com', 'demo@usememos.com',
'OWNER', 'HOST',
'Demo Owner', 'Demo Host',
'demo_open_id', 'demo_open_id',
-- raw password: secret -- raw password: secret
'$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK' '$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK'

View File

@ -106,7 +106,7 @@ func createUser(db *sql.DB, create *api.UserCreate) (*userRaw, error) {
open_id open_id
) )
VALUES (?, ?, ?, ?, ?) 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.Email,
create.Role, create.Role,
@ -130,6 +130,7 @@ func createUser(db *sql.DB, create *api.UserCreate) (*userRaw, error) {
&userRaw.OpenID, &userRaw.OpenID,
&userRaw.CreatedTs, &userRaw.CreatedTs,
&userRaw.UpdatedTs, &userRaw.UpdatedTs,
&userRaw.RowStatus,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
@ -162,7 +163,7 @@ func patchUser(db *sql.DB, patch *api.UserPatch) (*userRaw, error) {
UPDATE user UPDATE user
SET `+strings.Join(set, ", ")+` SET `+strings.Join(set, ", ")+`
WHERE id = ? 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...) `, args...)
if err != nil { if err != nil {
return nil, FormatError(err) return nil, FormatError(err)
@ -180,6 +181,7 @@ func patchUser(db *sql.DB, patch *api.UserPatch) (*userRaw, error) {
&userRaw.OpenID, &userRaw.OpenID,
&userRaw.CreatedTs, &userRaw.CreatedTs,
&userRaw.UpdatedTs, &userRaw.UpdatedTs,
&userRaw.RowStatus,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
@ -218,7 +220,8 @@ func findUserList(db *sql.DB, find *api.UserFind) ([]*userRaw, error) {
password_hash, password_hash,
open_id, open_id,
created_ts, created_ts,
updated_ts updated_ts,
row_status
FROM user FROM user
WHERE `+strings.Join(where, " AND ")+` WHERE `+strings.Join(where, " AND ")+`
ORDER BY created_ts DESC`, ORDER BY created_ts DESC`,
@ -241,6 +244,7 @@ func findUserList(db *sql.DB, find *api.UserFind) ([]*userRaw, error) {
&userRaw.OpenID, &userRaw.OpenID,
&userRaw.CreatedTs, &userRaw.CreatedTs,
&userRaw.UpdatedTs, &userRaw.UpdatedTs,
&userRaw.RowStatus,
); err != nil { ); err != nil {
fmt.Println(err) fmt.Println(err)
return nil, FormatError(err) return nil, FormatError(err)

View File

@ -48,7 +48,7 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
<span className="icon-text">🏟</span> Preferences <span className="icon-text">🏟</span> Preferences
</span> </span>
</div> </div>
{user?.role === "OWNER" ? ( {user?.role === "HOST" ? (
<> <>
<span className="section-title">Admin</span> <span className="section-title">Admin</span>
<div className="section-items-container"> <div className="section-items-container">

View File

@ -28,7 +28,7 @@ const UserBanner: React.FC<Props> = () => {
if (locationService.getState().pathname === "/") { if (locationService.getState().pathname === "/") {
api.getSystemStatus().then(({ data }) => { api.getSystemStatus().then(({ data }) => {
const { data: status } = data; const { data: status } = data;
setUsername(status.owner.name); setUsername(status.host.name);
}); });
} else { } else {
const currentUserId = userService.getCurrentUserId(); const currentUserId = userService.getCurrentUserId();
@ -51,7 +51,7 @@ const UserBanner: React.FC<Props> = () => {
<div className="user-banner-container"> <div className="user-banner-container">
<div className="username-container" onClick={handleUsernameClick}> <div className="username-container" onClick={handleUsernameClick}>
<span className="username-text">{username}</span> <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> </div>
<span className="action-btn menu-popup-btn" onClick={handlePopupBtnClick}> <span className="action-btn menu-popup-btn" onClick={handlePopupBtnClick}>
<img src="/icons/more.svg" className="icon-img" /> <img src="/icons/more.svg" className="icon-img" />

View File

@ -69,7 +69,7 @@
> .tip-text { > .tip-text {
@apply w-auto inline-block float-right text-sm mt-4 text-gray-500 text-right whitespace-pre-wrap; @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; @apply bg-blue-500 text-white px-2 py-1 rounded;
} }
} }

View File

@ -17,7 +17,7 @@ const validateConfig: ValidatorConfig = {
const Signin: React.FC<Props> = () => { const Signin: React.FC<Props> = () => {
const pageLoadingState = useLoading(true); const pageLoadingState = useLoading(true);
const [siteOwner, setSiteOwner] = useState<User>(); const [siteHost, setSiteHost] = useState<User>();
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const actionBtnLoadingState = useLoading(false); const actionBtnLoadingState = useLoading(false);
@ -25,7 +25,7 @@ const Signin: React.FC<Props> = () => {
useEffect(() => { useEffect(() => {
api.getSystemStatus().then(({ data }) => { api.getSystemStatus().then(({ data }) => {
const { data: status } = data; const { data: status } = data;
setSiteOwner(status.owner); setSiteHost(status.host);
if (status.profile.mode === "dev") { if (status.profile.mode === "dev") {
setEmail("demo@usememos.com"); setEmail("demo@usememos.com");
setPassword("secret"); setPassword("secret");
@ -77,7 +77,7 @@ const Signin: React.FC<Props> = () => {
actionBtnLoadingState.setFinish(); actionBtnLoadingState.setFinish();
}; };
const handleSignUpAsOwnerBtnsClick = async () => { const handleSignUpAsHostBtnsClick = async () => {
if (actionBtnLoadingState.isLoading) { if (actionBtnLoadingState.isLoading) {
return; return;
} }
@ -96,7 +96,7 @@ const Signin: React.FC<Props> = () => {
try { try {
actionBtnLoadingState.setLoading(); actionBtnLoadingState.setLoading();
await api.signup(email, password, "OWNER"); await api.signup(email, password, "HOST");
const user = await userService.doSignIn(); const user = await userService.doSignIn();
if (user) { if (user) {
locationService.replaceHistory("/"); locationService.replaceHistory("/");
@ -132,7 +132,7 @@ const Signin: React.FC<Props> = () => {
</div> </div>
</div> </div>
<div className="action-btns-container"> <div className="action-btns-container">
{siteOwner || pageLoadingState.isLoading ? ( {siteHost || pageLoadingState.isLoading ? (
<button <button
className={`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`} className={`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`}
onClick={() => handleSigninBtnsClick()} onClick={() => handleSigninBtnsClick()}
@ -142,16 +142,16 @@ const Signin: React.FC<Props> = () => {
) : ( ) : (
<button <button
className={`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`} className={`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`}
onClick={() => handleSignUpAsOwnerBtnsClick()} onClick={() => handleSignUpAsHostBtnsClick()}
> >
Sign up as Owner Sign up as Host
</button> </button>
)} )}
</div> </div>
<p className={`tip-text ${siteOwner || pageLoadingState.isLoading ? "" : "owner-tip"}`}> <p className={`tip-text ${siteHost || pageLoadingState.isLoading ? "" : "host-tip"}`}>
{siteOwner || pageLoadingState.isLoading {siteHost || pageLoadingState.isLoading
? "If you don't have an account, please\ncontact the site owner." ? "If you don't have an account, please\ncontact the site host."
: "You are registering as the Site Owner."} : "You are registering as the Site Host."}
</p> </p>
</div> </div>
</div> </div>

View File

@ -4,6 +4,6 @@ interface Profile {
} }
interface SystemStatus { interface SystemStatus {
owner: User; host: User;
profile: Profile; profile: Profile;
} }

View File

@ -1,5 +1,5 @@
type UserId = number; type UserId = number;
type UserRole = "OWNER" | "USER"; type UserRole = "HOST" | "USER";
interface User { interface User {
id: UserId; id: UserId;