Add: User can change their password
This commit is contained in:
@@ -83,8 +83,11 @@ func NewAPI(config Config) (*API, error) {
|
|||||||
apiMux.HandleFunc("/login", api.HandleLogin)
|
apiMux.HandleFunc("/login", api.HandleLogin)
|
||||||
apiMux.HandleFunc("/register", api.HandleRegister)
|
apiMux.HandleFunc("/register", api.HandleRegister)
|
||||||
apiMux.HandleFunc("/logout", api.LoginAsAnonymous)
|
apiMux.HandleFunc("/logout", api.LoginAsAnonymous)
|
||||||
|
apiMux.HandleFunc("/get_user_info", api.HandleGetUserInfo)
|
||||||
apiMux.HandleFunc("/get_users", api.HandleGetUsers)
|
apiMux.HandleFunc("/get_users", api.HandleGetUsers)
|
||||||
apiMux.HandleFunc("/update_user_active", api.HandleUpdateUserActive)
|
apiMux.HandleFunc("/update_user_active", api.HandleUpdateUserActive)
|
||||||
|
apiMux.HandleFunc("/update_username", api.HandleUpdateUsername)
|
||||||
|
apiMux.HandleFunc("/update_user_password", api.HandleUpdateUserPassword)
|
||||||
// tag
|
// tag
|
||||||
apiMux.HandleFunc("/get_tags", api.HandleGetTags)
|
apiMux.HandleFunc("/get_tags", api.HandleGetTags)
|
||||||
apiMux.HandleFunc("/get_tag_info", api.HandleGetTagInfo)
|
apiMux.HandleFunc("/get_tag_info", api.HandleGetTagInfo)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ var (
|
|||||||
ErrEmpty = errors.New("Empty field detected, please fill in all fields")
|
ErrEmpty = errors.New("Empty field detected, please fill in all fields")
|
||||||
ErrAnonymous = errors.New("Anonymous user detected, please login")
|
ErrAnonymous = errors.New("Anonymous user detected, please login")
|
||||||
ErrNotActive = errors.New("User is not active")
|
ErrNotActive = errors.New("User is not active")
|
||||||
|
ErrWrongPassword = errors.New("Wrong password")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Error struct {
|
type Error struct {
|
||||||
|
|||||||
@@ -245,3 +245,125 @@ func (api *API) HandleUpdateUserActive(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
api.HandleOK(w, r)
|
api.HandleOK(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateUsernameRequest struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleUpdateUsername(w http.ResponseWriter, r *http.Request) {
|
||||||
|
req := &UpdateUsernameRequest{}
|
||||||
|
|
||||||
|
err := json.NewDecoder(r.Body).Decode(req)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := api.Db.GetUserById(req.ID)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, err := api.GetUserID(w, r)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.ID != userID && user.Role != database.RoleAdmin {
|
||||||
|
api.HandleError(w, r, ErrNotAdmin)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.Db.UpdateUsername(req.ID, req.Username)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api.HandleOK(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserInfoRequest struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserInfoResponse struct {
|
||||||
|
User *database.User `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleGetUserInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
|
req := &GetUserInfoRequest{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(req)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := api.Db.GetUserById(req.ID)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := &GetUserInfoResponse{
|
||||||
|
User: user,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.NewEncoder(w).Encode(ret)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateUserPasswordRequest struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
OldPassword string `json:"old_password"`
|
||||||
|
NewPassword string `json:"new_password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleUpdateUserPassword(w http.ResponseWriter, r *http.Request) {
|
||||||
|
req := &UpdateUserPasswordRequest{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(req)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := api.Db.GetUserById(req.ID)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, err := api.GetUserID(w, r)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentUser, err := api.Db.GetUserById(userID)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentUser.Role != database.RoleAdmin {
|
||||||
|
_, err := api.Db.Login(user.Username, req.OldPassword)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, ErrWrongPassword)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.Db.UpdateUserPassword(req.ID, req.NewPassword)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api.HandleOK(w, r)
|
||||||
|
}
|
||||||
|
|||||||
@@ -92,3 +92,19 @@ func (database *Database) UpdateUserActive(id int64, active bool) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (database *Database) UpdateUsername(id int64, username string) error {
|
||||||
|
_, err := database.stmt.updateUsername.Exec(username, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) UpdateUserPassword(id int64, password string) error {
|
||||||
|
_, err := database.stmt.updateUserPassword.Exec(password, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -186,6 +186,10 @@ var getUserByIdQuery = `SELECT id, username, role, active, avatar_id FROM users
|
|||||||
|
|
||||||
var updateUserActiveQuery = `UPDATE users SET active = ? WHERE id = ?;`
|
var updateUserActiveQuery = `UPDATE users SET active = ? WHERE id = ?;`
|
||||||
|
|
||||||
|
var updateUsernameQuery = `UPDATE users SET username = ? WHERE id = ?;`
|
||||||
|
|
||||||
|
var updateUserPasswordQuery = `UPDATE users SET password = ? WHERE id = ?;`
|
||||||
|
|
||||||
var getAnonymousUserQuery = `SELECT id, username, role, avatar_id FROM users WHERE role = 0 LIMIT 1;`
|
var getAnonymousUserQuery = `SELECT id, username, role, avatar_id FROM users WHERE role = 0 LIMIT 1;`
|
||||||
|
|
||||||
var insertTagQuery = `INSERT INTO tags (name, description, created_by_user_id) VALUES (?, ?, ?);`
|
var insertTagQuery = `INSERT INTO tags (name, description, created_by_user_id) VALUES (?, ?, ?);`
|
||||||
@@ -283,6 +287,8 @@ type Stmt struct {
|
|||||||
getUsers *sql.Stmt
|
getUsers *sql.Stmt
|
||||||
getUserById *sql.Stmt
|
getUserById *sql.Stmt
|
||||||
updateUserActive *sql.Stmt
|
updateUserActive *sql.Stmt
|
||||||
|
updateUsername *sql.Stmt
|
||||||
|
updateUserPassword *sql.Stmt
|
||||||
getAnonymousUser *sql.Stmt
|
getAnonymousUser *sql.Stmt
|
||||||
insertTag *sql.Stmt
|
insertTag *sql.Stmt
|
||||||
getTag *sql.Stmt
|
getTag *sql.Stmt
|
||||||
@@ -553,6 +559,18 @@ func NewPreparedStatement(sqlConn *sql.DB) (*Stmt, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// init updateUsername
|
||||||
|
stmt.updateUsername, err = sqlConn.Prepare(updateUsernameQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// init updateUserPassword
|
||||||
|
stmt.updateUserPassword, err = sqlConn.Prepare(updateUserPasswordQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// init getAnonymousUser
|
// init getAnonymousUser
|
||||||
stmt.getAnonymousUser, err = sqlConn.Prepare(getAnonymousUserQuery)
|
stmt.getAnonymousUser, err = sqlConn.Prepare(getAnonymousUserQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -92,3 +92,8 @@ dialog {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
.horizontal {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,15 @@ function Manage(props) {
|
|||||||
Login
|
Login
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{props.user.role !== 0 && (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
navigate(`/manage/users/${props.user.id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{props.user.role !== 0 && (
|
{props.user.role !== 0 && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -5,13 +5,22 @@ import ReviewEntry from "./ReviewEntry";
|
|||||||
function UserProfile(props) {
|
function UserProfile(props) {
|
||||||
let params = useParams();
|
let params = useParams();
|
||||||
const [reviews, setReviews] = useState([]);
|
const [reviews, setReviews] = useState([]);
|
||||||
|
const [oldPassword, setOldPassword] = useState("");
|
||||||
|
const [newPassword, setNewPassword] = useState("");
|
||||||
|
const [newPasswordConfirm, setNewPasswordConfirm] = useState("");
|
||||||
|
const [user, setUser] = useState({
|
||||||
|
id: "",
|
||||||
|
username: "",
|
||||||
|
role: "",
|
||||||
|
active: "",
|
||||||
|
avatar_id: "",
|
||||||
|
});
|
||||||
|
|
||||||
function getReviews() {
|
function getReviews() {
|
||||||
fetch("/api/v1/get_reviews_by_user", {
|
fetch("/api/v1/get_reviews_by_user", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Accept: "application/json",
|
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
id: parseInt(params.id),
|
id: parseInt(params.id),
|
||||||
@@ -27,23 +36,131 @@ function UserProfile(props) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getUserInfo() {
|
||||||
|
fetch("/api/v1/get_user_info", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: parseInt(params.id),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data.error) {
|
||||||
|
alert(data.error);
|
||||||
|
} else {
|
||||||
|
setUser(data.user);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getReviews();
|
getReviews();
|
||||||
|
getUserInfo();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<h3>User Profile</h3>
|
<h3>User Profile</h3>
|
||||||
<div>
|
<div className="horizontal">
|
||||||
<h4>Reviews</h4>
|
<input
|
||||||
{reviews.map((review) => (
|
type="text"
|
||||||
<ReviewEntry
|
value={user.username}
|
||||||
key={review.id}
|
onChange={(e) => {
|
||||||
review={review}
|
setUser({
|
||||||
user={props.user}
|
...user,
|
||||||
/>
|
username: e.target.value,
|
||||||
))}
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
fetch("/api/v1/update_username", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: parseInt(params.id),
|
||||||
|
username: user.username,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data.error) {
|
||||||
|
alert(data.error);
|
||||||
|
} else {
|
||||||
|
props.setUser({
|
||||||
|
...props.user,
|
||||||
|
username: user.username,
|
||||||
|
});
|
||||||
|
alert("Username updated successfully!");
|
||||||
|
getUserInfo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={props.user.id !== user.id || props.user.role === 1}
|
||||||
|
>
|
||||||
|
Save Username
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={oldPassword}
|
||||||
|
placeholder="Old Password"
|
||||||
|
onChange={(e) => setOldPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={newPassword}
|
||||||
|
placeholder="New Password"
|
||||||
|
onChange={(e) => setNewPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={newPasswordConfirm}
|
||||||
|
placeholder="Confirm New Password"
|
||||||
|
onChange={(e) => setNewPasswordConfirm(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
fetch("/api/v1/update_user_password", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: parseInt(params.id),
|
||||||
|
old_password: oldPassword,
|
||||||
|
new_password: newPassword,
|
||||||
|
new_password_confirm: newPasswordConfirm,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data.error) {
|
||||||
|
alert(data.error);
|
||||||
|
} else {
|
||||||
|
alert("Password updated successfully!");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={
|
||||||
|
(props.user.id !== user.id ||
|
||||||
|
props.user.role !== 1) &&
|
||||||
|
newPassword !== newPasswordConfirm
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Change Password
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<h4>Reviews</h4>
|
||||||
|
{reviews.map((review) => (
|
||||||
|
<ReviewEntry key={review.id} review={review} user={props.user} />
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user