mirror of
https://github.com/usememos/memos.git
synced 2025-04-01 03:20:30 +02:00
feat: support open api with webhooks
This commit is contained in:
parent
aed1004f39
commit
9bec29a03e
api
resources
server
store
web/src
components
helpers
less
pages
services
stores
types
129
api/auth.go
129
api/auth.go
@ -1,14 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"memos/api/e"
|
"memos/api/e"
|
||||||
"memos/config"
|
|
||||||
"memos/store"
|
"memos/store"
|
||||||
"memos/utils"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
@ -38,7 +33,7 @@ func handleUserSignUp(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := store.CreateNewUser(userSignup.Username, userSignup.Password, "")
|
user, err := store.CreateNewUser(userSignup.Username, userSignup.Password)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e.ErrorHandler(w, "DATABASE_ERROR", err.Error())
|
e.ErrorHandler(w, "DATABASE_ERROR", err.Error())
|
||||||
@ -107,127 +102,6 @@ func handleUserSignOut(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGithubAuthCallback(w http.ResponseWriter, r *http.Request) {
|
|
||||||
code := r.URL.Query().Get("code")
|
|
||||||
|
|
||||||
requestBody := map[string]string{
|
|
||||||
"client_id": config.GITHUB_CLIENTID,
|
|
||||||
"client_secret": config.GITHUB_SECRET,
|
|
||||||
"code": code,
|
|
||||||
}
|
|
||||||
|
|
||||||
requestJSON, _ := json.Marshal(requestBody)
|
|
||||||
|
|
||||||
// POST request to get access_token
|
|
||||||
req, err := http.NewRequest(
|
|
||||||
"POST",
|
|
||||||
"https://github.com/login/oauth/access_token",
|
|
||||||
bytes.NewBuffer(requestJSON),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
e.ErrorHandler(w, "REQUEST_BODY_ERROR", "Error in request github api")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
req.Header.Set("Accept", "application/json")
|
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
e.ErrorHandler(w, "REQUEST_BODY_ERROR", "Error in request github api")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Response body converted to stringified JSON
|
|
||||||
respBody, _ := ioutil.ReadAll(resp.Body)
|
|
||||||
|
|
||||||
// Represents the response received from Github
|
|
||||||
type GithubAccessTokenResponse struct {
|
|
||||||
AccessToken string `json:"access_token"`
|
|
||||||
TokenType string `json:"token_type"`
|
|
||||||
Scope string `json:"scope"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ghResp := GithubAccessTokenResponse{}
|
|
||||||
json.Unmarshal(respBody, &ghResp)
|
|
||||||
|
|
||||||
githubAccessToken := ghResp.AccessToken
|
|
||||||
|
|
||||||
// Get request to a set URL
|
|
||||||
req, err = http.NewRequest(
|
|
||||||
"GET",
|
|
||||||
"https://api.github.com/user",
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
e.ErrorHandler(w, "REQUEST_BODY_ERROR", "Error in request github api")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
authorizationHeaderValue := fmt.Sprintf("token %s", githubAccessToken)
|
|
||||||
req.Header.Set("Authorization", authorizationHeaderValue)
|
|
||||||
|
|
||||||
resp, err = http.DefaultClient.Do(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
e.ErrorHandler(w, "REQUEST_BODY_ERROR", "Error in request github api")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
respBody, _ = ioutil.ReadAll(resp.Body)
|
|
||||||
|
|
||||||
githubData := string(respBody)
|
|
||||||
|
|
||||||
type GithubUser struct {
|
|
||||||
Login string `json:"login"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
githubUser := GithubUser{}
|
|
||||||
json.Unmarshal([]byte(githubData), &githubUser)
|
|
||||||
|
|
||||||
session, _ := SessionStore.Get(r, "session")
|
|
||||||
userId := fmt.Sprintf("%v", session.Values["user_id"])
|
|
||||||
|
|
||||||
if userId != "" {
|
|
||||||
githubNameUsable, err := store.CheckGithubNameUsable(githubUser.Login)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
e.ErrorHandler(w, "DATABASE_ERROR", "Error in CheckGithubNameUsable")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !githubNameUsable {
|
|
||||||
e.ErrorHandler(w, "DATABASE_ERROR", "Error in CheckGithubNameUsable")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userPatch := store.UserPatch{
|
|
||||||
GithubName: &githubUser.Login,
|
|
||||||
}
|
|
||||||
|
|
||||||
store.UpdateUser(userId, &userPatch)
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := store.GetUserByGithubName(githubUser.Login)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
username := githubUser.Name
|
|
||||||
usernameUsable, _ := store.CheckUsernameUsable(username)
|
|
||||||
for !usernameUsable {
|
|
||||||
username = githubUser.Name + utils.GenUUID()
|
|
||||||
usernameUsable, _ = store.CheckUsernameUsable(username)
|
|
||||||
}
|
|
||||||
user, _ = store.CreateNewUser(username, username, githubUser.Login)
|
|
||||||
}
|
|
||||||
|
|
||||||
session.Values["user_id"] = user.Id
|
|
||||||
session.Save(r, w)
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterAuthRoutes(r *mux.Router) {
|
func RegisterAuthRoutes(r *mux.Router) {
|
||||||
authRouter := r.PathPrefix("/api/auth").Subrouter()
|
authRouter := r.PathPrefix("/api/auth").Subrouter()
|
||||||
|
|
||||||
@ -236,5 +110,4 @@ func RegisterAuthRoutes(r *mux.Router) {
|
|||||||
authRouter.HandleFunc("/signup", handleUserSignUp).Methods("POST")
|
authRouter.HandleFunc("/signup", handleUserSignUp).Methods("POST")
|
||||||
authRouter.HandleFunc("/signin", handleUserSignIn).Methods("POST")
|
authRouter.HandleFunc("/signin", handleUserSignIn).Methods("POST")
|
||||||
authRouter.HandleFunc("/signout", handleUserSignOut).Methods("POST")
|
authRouter.HandleFunc("/signout", handleUserSignOut).Methods("POST")
|
||||||
authRouter.HandleFunc("/github", handleGithubAuthCallback).Methods("GET")
|
|
||||||
}
|
}
|
||||||
|
28
api/user.go
28
api/user.go
@ -29,16 +29,16 @@ func handleGetMyUserInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
func handleUpdateMyUserInfo(w http.ResponseWriter, r *http.Request) {
|
func handleUpdateMyUserInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
userId, _ := GetUserIdInSession(r)
|
userId, _ := GetUserIdInSession(r)
|
||||||
|
|
||||||
userPatch := store.UserPatch{}
|
updateUserPatch := store.UpdateUserPatch{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&userPatch)
|
err := json.NewDecoder(r.Body).Decode(&updateUserPatch)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e.ErrorHandler(w, "REQUEST_BODY_ERROR", "Bad request")
|
e.ErrorHandler(w, "REQUEST_BODY_ERROR", "Bad request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if userPatch.Username != nil {
|
if updateUserPatch.Username != nil {
|
||||||
usernameUsable, _ := store.CheckUsernameUsable(*userPatch.Username)
|
usernameUsable, _ := store.CheckUsernameUsable(*updateUserPatch.Username)
|
||||||
if !usernameUsable {
|
if !usernameUsable {
|
||||||
json.NewEncoder(w).Encode(Response{
|
json.NewEncoder(w).Encode(Response{
|
||||||
Succeed: false,
|
Succeed: false,
|
||||||
@ -49,19 +49,7 @@ func handleUpdateMyUserInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if userPatch.GithubName != nil {
|
user, err := store.UpdateUser(userId, &updateUserPatch)
|
||||||
githubNameUsable, _ := store.CheckGithubNameUsable(*userPatch.GithubName)
|
|
||||||
if !githubNameUsable {
|
|
||||||
json.NewEncoder(w).Encode(Response{
|
|
||||||
Succeed: false,
|
|
||||||
Message: "GitHub name is existed",
|
|
||||||
Data: nil,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := store.UpdateUser(userId, &userPatch)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e.ErrorHandler(w, "DATABASE_ERROR", err.Error())
|
e.ErrorHandler(w, "DATABASE_ERROR", err.Error())
|
||||||
@ -75,10 +63,10 @@ func handleUpdateMyUserInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRefreshUserOpenId(w http.ResponseWriter, r *http.Request) {
|
func handleResetUserOpenId(w http.ResponseWriter, r *http.Request) {
|
||||||
userId, _ := GetUserIdInSession(r)
|
userId, _ := GetUserIdInSession(r)
|
||||||
|
|
||||||
openId, err := store.UpdateUserOpenId(userId)
|
openId, err := store.ResetUserOpenId(userId)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e.ErrorHandler(w, "DATABASE_ERROR", err.Error())
|
e.ErrorHandler(w, "DATABASE_ERROR", err.Error())
|
||||||
@ -155,7 +143,7 @@ func RegisterUserRoutes(r *mux.Router) {
|
|||||||
|
|
||||||
userRouter.HandleFunc("/me", handleGetMyUserInfo).Methods("GET")
|
userRouter.HandleFunc("/me", handleGetMyUserInfo).Methods("GET")
|
||||||
userRouter.HandleFunc("/me", handleUpdateMyUserInfo).Methods("PATCH")
|
userRouter.HandleFunc("/me", handleUpdateMyUserInfo).Methods("PATCH")
|
||||||
userRouter.HandleFunc("/open_id/new", handleRefreshUserOpenId).Methods("POST")
|
userRouter.HandleFunc("/open_id/new", handleResetUserOpenId).Methods("POST")
|
||||||
userRouter.HandleFunc("/checkusername", handleCheckUsername).Methods("POST")
|
userRouter.HandleFunc("/checkusername", handleCheckUsername).Methods("POST")
|
||||||
userRouter.HandleFunc("/validpassword", handleValidPassword).Methods("POST")
|
userRouter.HandleFunc("/validpassword", handleValidPassword).Methods("POST")
|
||||||
}
|
}
|
||||||
|
55
api/webhooks.go
Normal file
55
api/webhooks.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"memos/api/e"
|
||||||
|
"memos/store"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleCreateMemoByWH(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
openId := vars["openId"]
|
||||||
|
|
||||||
|
type CreateMemoDataBody struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
createMemo := CreateMemoDataBody{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&createMemo)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
e.ErrorHandler(w, "REQUEST_BODY_ERROR", "Bad request")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := store.GetUserByOpenId(openId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
e.ErrorHandler(w, "DATABASE_ERROR", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
memo, err := store.CreateNewMemo(createMemo.Content, user.Id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
e.ErrorHandler(w, "DATABASE_ERROR", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(Response{
|
||||||
|
Succeed: true,
|
||||||
|
Message: "",
|
||||||
|
Data: memo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterWebHooksRoutes(r *mux.Router) {
|
||||||
|
memoRouter := r.PathPrefix("/api/whs").Subrouter()
|
||||||
|
|
||||||
|
memoRouter.Use(JSONResponseMiddleWare)
|
||||||
|
|
||||||
|
memoRouter.HandleFunc("/memo/{openId}", handleCreateMemoByWH).Methods("POST")
|
||||||
|
}
|
@ -8,10 +8,9 @@ CREATE TABLE `users` (
|
|||||||
`username` TEXT NOT NULL,
|
`username` TEXT NOT NULL,
|
||||||
`password` TEXT NOT NULL,
|
`password` TEXT NOT NULL,
|
||||||
`open_id` TEXT NOT NULL DEFAULT '',
|
`open_id` TEXT NOT NULL DEFAULT '',
|
||||||
`github_name` TEXT NOT NULL DEFAULT '',
|
|
||||||
`created_at` TEXT NOT NULL DEFAULT (DATETIME('now', 'localtime')),
|
`created_at` TEXT NOT NULL DEFAULT (DATETIME('now', 'localtime')),
|
||||||
`updated_at` TEXT NOT NULL DEFAULT (DATETIME('now', 'localtime')),
|
`updated_at` TEXT NOT NULL DEFAULT (DATETIME('now', 'localtime')),
|
||||||
UNIQUE(`username`, `github_name`)
|
UNIQUE(`username`, `open_id`)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE `queries` (
|
CREATE TABLE `queries` (
|
||||||
@ -48,10 +47,10 @@ CREATE TABLE `resources` (
|
|||||||
|
|
||||||
|
|
||||||
INSERT INTO `users`
|
INSERT INTO `users`
|
||||||
(`id`, `username`, `password`)
|
(`id`, `username`, `password`, `open_id`)
|
||||||
VALUES
|
VALUES
|
||||||
('1', 'guest', '123456'),
|
('1', 'guest', '123456', 'guest_open_id'),
|
||||||
('2', 'test', '123456');
|
('2', 'mine', '123456', 'mine_open_id');
|
||||||
|
|
||||||
INSERT INTO `memos`
|
INSERT INTO `memos`
|
||||||
(`id`, `content`, `user_id`)
|
(`id`, `content`, `user_id`)
|
||||||
|
Binary file not shown.
@ -18,6 +18,7 @@ func main() {
|
|||||||
api.RegisterMemoRoutes(r)
|
api.RegisterMemoRoutes(r)
|
||||||
api.RegisterQueryRoutes(r)
|
api.RegisterQueryRoutes(r)
|
||||||
api.RegisterResourceRoutes(r)
|
api.RegisterResourceRoutes(r)
|
||||||
|
api.RegisterWebHooksRoutes(r)
|
||||||
|
|
||||||
webServe := api.SPAHandler{
|
webServe := api.SPAHandler{
|
||||||
StaticPath: "./web/dist",
|
StaticPath: "./web/dist",
|
||||||
|
@ -8,40 +8,37 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
OpenId string `json:"openId"`
|
OpenId string `json:"openId"`
|
||||||
GithubName string `json:"githubName"`
|
CreatedAt string `json:"createdAt"`
|
||||||
CreatedAt string `json:"createdAt"`
|
UpdatedAt string `json:"updatedAt"`
|
||||||
UpdatedAt string `json:"updatedAt"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateNewUser(username string, password string, githubName string) (User, error) {
|
func CreateNewUser(username string, password string) (User, error) {
|
||||||
nowDateTimeStr := utils.GetNowDateTimeStr()
|
nowDateTimeStr := utils.GetNowDateTimeStr()
|
||||||
newUser := User{
|
newUser := User{
|
||||||
Id: utils.GenUUID(),
|
Id: utils.GenUUID(),
|
||||||
Username: username,
|
Username: username,
|
||||||
Password: password,
|
Password: password,
|
||||||
OpenId: utils.GenUUID(),
|
OpenId: utils.GenUUID(),
|
||||||
GithubName: githubName,
|
CreatedAt: nowDateTimeStr,
|
||||||
CreatedAt: nowDateTimeStr,
|
UpdatedAt: nowDateTimeStr,
|
||||||
UpdatedAt: nowDateTimeStr,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
query := `INSERT INTO users (id, username, password, open_id, github_name, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)`
|
query := `INSERT INTO users (id, username, password, open_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`
|
||||||
_, err := DB.Exec(query, newUser.Id, newUser.Username, newUser.Password, newUser.OpenId, newUser.GithubName, newUser.CreatedAt, newUser.UpdatedAt)
|
_, err := DB.Exec(query, newUser.Id, newUser.Username, newUser.Password, newUser.OpenId, newUser.CreatedAt, newUser.UpdatedAt)
|
||||||
|
|
||||||
return newUser, FormatDBError(err)
|
return newUser, FormatDBError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserPatch struct {
|
type UpdateUserPatch struct {
|
||||||
Username *string
|
Username *string
|
||||||
Password *string
|
Password *string
|
||||||
GithubName *string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateUser(id string, userPatch *UserPatch) (User, error) {
|
func UpdateUser(id string, updateUserPatch *UpdateUserPatch) (User, error) {
|
||||||
user := User{}
|
user := User{}
|
||||||
user, err := GetUserById(id)
|
user, err := GetUserById(id)
|
||||||
|
|
||||||
@ -51,18 +48,14 @@ func UpdateUser(id string, userPatch *UserPatch) (User, error) {
|
|||||||
|
|
||||||
set, args := []string{}, []interface{}{}
|
set, args := []string{}, []interface{}{}
|
||||||
|
|
||||||
if v := userPatch.Username; v != nil {
|
if v := updateUserPatch.Username; v != nil {
|
||||||
user.Username = *v
|
user.Username = *v
|
||||||
set, args = append(set, "username=?"), append(args, *v)
|
set, args = append(set, "username=?"), append(args, *v)
|
||||||
}
|
}
|
||||||
if v := userPatch.Password; v != nil {
|
if v := updateUserPatch.Password; v != nil {
|
||||||
user.Password = *v
|
user.Password = *v
|
||||||
set, args = append(set, "password=?"), append(args, *v)
|
set, args = append(set, "password=?"), append(args, *v)
|
||||||
}
|
}
|
||||||
if v := userPatch.GithubName; v != nil {
|
|
||||||
user.GithubName = *v
|
|
||||||
set, args = append(set, "github_name=?"), append(args, *v)
|
|
||||||
}
|
|
||||||
set, args = append(set, "updated_at=?"), append(args, utils.GetNowDateTimeStr())
|
set, args = append(set, "updated_at=?"), append(args, utils.GetNowDateTimeStr())
|
||||||
args = append(args, id)
|
args = append(args, id)
|
||||||
|
|
||||||
@ -72,7 +65,7 @@ func UpdateUser(id string, userPatch *UserPatch) (User, error) {
|
|||||||
return user, FormatDBError(err)
|
return user, FormatDBError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateUserOpenId(userId string) (string, error) {
|
func ResetUserOpenId(userId string) (string, error) {
|
||||||
openId := utils.GenUUID()
|
openId := utils.GenUUID()
|
||||||
query := `UPDATE users SET open_id=? WHERE id=?`
|
query := `UPDATE users SET open_id=? WHERE id=?`
|
||||||
_, err := DB.Exec(query, openId, userId)
|
_, err := DB.Exec(query, openId, userId)
|
||||||
@ -80,23 +73,23 @@ func UpdateUserOpenId(userId string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetUserById(id string) (User, error) {
|
func GetUserById(id string) (User, error) {
|
||||||
query := `SELECT id, username, password, open_id, github_name, created_at, updated_at FROM users WHERE id=?`
|
query := `SELECT id, username, password, open_id, created_at, updated_at FROM users WHERE id=?`
|
||||||
user := User{}
|
user := User{}
|
||||||
err := DB.QueryRow(query, id).Scan(&user.Id, &user.Username, &user.Password, &user.OpenId, &user.GithubName, &user.CreatedAt, &user.UpdatedAt)
|
err := DB.QueryRow(query, id).Scan(&user.Id, &user.Username, &user.Password, &user.OpenId, &user.CreatedAt, &user.UpdatedAt)
|
||||||
|
return user, FormatDBError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserByOpenId(openId string) (User, error) {
|
||||||
|
query := `SELECT id, username, password, open_id, created_at, updated_at FROM users WHERE open_id=?`
|
||||||
|
user := User{}
|
||||||
|
err := DB.QueryRow(query, openId).Scan(&user.Id, &user.Username, &user.Password, &user.OpenId, &user.CreatedAt, &user.UpdatedAt)
|
||||||
return user, FormatDBError(err)
|
return user, FormatDBError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserByUsernameAndPassword(username string, password string) (User, error) {
|
func GetUserByUsernameAndPassword(username string, password string) (User, error) {
|
||||||
query := `SELECT id, username, password, open_id, github_name, created_at, updated_at FROM users WHERE username=? AND password=?`
|
query := `SELECT id, username, password, open_id, created_at, updated_at FROM users WHERE username=? AND password=?`
|
||||||
user := User{}
|
user := User{}
|
||||||
err := DB.QueryRow(query, username, password).Scan(&user.Id, &user.Username, &user.Password, &user.OpenId, &user.GithubName, &user.CreatedAt, &user.UpdatedAt)
|
err := DB.QueryRow(query, username, password).Scan(&user.Id, &user.Username, &user.Password, &user.OpenId, &user.CreatedAt, &user.UpdatedAt)
|
||||||
return user, FormatDBError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUserByGithubName(githubName string) (User, error) {
|
|
||||||
query := `SELECT id, username, password, open_id, github_name, created_at, updated_at FROM users WHERE github_name=?`
|
|
||||||
user := User{}
|
|
||||||
err := DB.QueryRow(query, githubName).Scan(&user.Id, &user.Username, &user.Password, &user.OpenId, &user.GithubName, &user.CreatedAt, &user.UpdatedAt)
|
|
||||||
return user, FormatDBError(err)
|
return user, FormatDBError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,23 +111,6 @@ func CheckUsernameUsable(username string) (bool, error) {
|
|||||||
return usable, nil
|
return usable, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckGithubNameUsable(githubName string) (bool, error) {
|
|
||||||
query := `SELECT * FROM users WHERE github_name=?`
|
|
||||||
query = fmt.Sprintf("SELECT COUNT(*) FROM (%s)", query)
|
|
||||||
|
|
||||||
var count uint
|
|
||||||
err := DB.QueryRow(query, githubName).Scan(&count)
|
|
||||||
if err != nil && err != sql.ErrNoRows {
|
|
||||||
return false, FormatDBError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if count > 0 {
|
|
||||||
return false, nil
|
|
||||||
} else {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckPasswordValid(id string, password string) (bool, error) {
|
func CheckPasswordValid(id string, password string) (bool, error) {
|
||||||
query := `SELECT * FROM users WHERE id=? AND password=?`
|
query := `SELECT * FROM users WHERE id=? AND password=?`
|
||||||
query = fmt.Sprintf("SELECT COUNT(*) FROM (%s)", query)
|
query = fmt.Sprintf("SELECT COUNT(*) FROM (%s)", query)
|
||||||
|
@ -43,6 +43,7 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
|||||||
const handleSignOutBtnClick = async () => {
|
const handleSignOutBtnClick = async () => {
|
||||||
await userService.doSignOut();
|
await userService.doSignOut();
|
||||||
locationService.replaceHistory("/signin");
|
locationService.replaceHistory("/signin");
|
||||||
|
window.location.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -3,7 +3,8 @@ import appContext from "../stores/appContext";
|
|||||||
import { userService } from "../services";
|
import { userService } from "../services";
|
||||||
import utils from "../helpers/utils";
|
import utils from "../helpers/utils";
|
||||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||||
import Only from "./common/OnlyWhen";
|
import useLoading from "../hooks/useLoading";
|
||||||
|
import useToggle from "../hooks/useToggle";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
import showChangePasswordDialog from "./ChangePasswordDialog";
|
import showChangePasswordDialog from "./ChangePasswordDialog";
|
||||||
import "../less/my-account-section.less";
|
import "../less/my-account-section.less";
|
||||||
@ -21,7 +22,9 @@ const MyAccountSection: React.FC<Props> = () => {
|
|||||||
const { userState } = useContext(appContext);
|
const { userState } = useContext(appContext);
|
||||||
const user = userState.user as Model.User;
|
const user = userState.user as Model.User;
|
||||||
const [username, setUsername] = useState<string>(user.username);
|
const [username, setUsername] = useState<string>(user.username);
|
||||||
const [showConfirmUnbindGithubBtn, setShowConfirmUnbindGithubBtn] = useState(false);
|
const resetBtnClickLoadingState = useLoading(false);
|
||||||
|
const [showConfirmResetAPIBtn, toggleConfirmResetAPIBtn] = useToggle(false);
|
||||||
|
const openAPIRoute = `${window.location.origin}/api/whs/memo/${user.openId}`;
|
||||||
|
|
||||||
const handleUsernameChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleUsernameChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const nextUsername = e.target.value as string;
|
const nextUsername = e.target.value as string;
|
||||||
@ -69,18 +72,23 @@ const MyAccountSection: React.FC<Props> = () => {
|
|||||||
showChangePasswordDialog();
|
showChangePasswordDialog();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUnbindGithubBtnClick = async () => {
|
const handleResetOpenIdBtnClick = async () => {
|
||||||
if (showConfirmUnbindGithubBtn) {
|
if (!showConfirmResetAPIBtn) {
|
||||||
try {
|
toggleConfirmResetAPIBtn(true);
|
||||||
await userService.removeGithubName();
|
return;
|
||||||
await userService.doSignIn();
|
|
||||||
} catch (error: any) {
|
|
||||||
toastHelper.error(error.message);
|
|
||||||
}
|
|
||||||
setShowConfirmUnbindGithubBtn(false);
|
|
||||||
} else {
|
|
||||||
setShowConfirmUnbindGithubBtn(true);
|
|
||||||
}
|
}
|
||||||
|
if (resetBtnClickLoadingState.isLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetBtnClickLoadingState.setLoading();
|
||||||
|
try {
|
||||||
|
await userService.resetOpenId();
|
||||||
|
} catch (error) {
|
||||||
|
// do nth
|
||||||
|
}
|
||||||
|
resetBtnClickLoadingState.setFinish();
|
||||||
|
toggleConfirmResetAPIBtn(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePreventDefault = (e: React.MouseEvent) => {
|
const handlePreventDefault = (e: React.MouseEvent) => {
|
||||||
@ -124,39 +132,17 @@ const MyAccountSection: React.FC<Props> = () => {
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{/* Account Binding Settings: only can use for domain: memos.justsven.top */}
|
<div className="section-container openapi-section-container">
|
||||||
<Only when={window.location.origin.includes("justsven.top")}>
|
<p className="title-text">Open API(实验性功能)</p>
|
||||||
<div className="section-container connect-section-container">
|
<p className="value-text">{openAPIRoute}</p>
|
||||||
<p className="title-text">关联账号</p>
|
<span className={`reset-btn ${resetBtnClickLoadingState.isLoading ? "loading" : ""}`} onClick={handleResetOpenIdBtnClick}>
|
||||||
<label className="form-label input-form-label">
|
{showConfirmResetAPIBtn ? "⚠️ 确定重置 API" : "重置 API"}
|
||||||
<span className="normal-text">GitHub:</span>
|
</span>
|
||||||
{user.githubName ? (
|
<div className="usage-guide-container">
|
||||||
<>
|
<p className="title-text">使用方法:</p>
|
||||||
<a className="value-text" href={"https://github.com/" + user.githubName}>
|
<pre>{`POST ${openAPIRoute}\nContent-type: application/json\n{\n "content": "Hello, #memos ${window.location.origin}"\n}`}</pre>
|
||||||
{user.githubName}
|
|
||||||
</a>
|
|
||||||
<span
|
|
||||||
className={`btn-text unbind-btn ${showConfirmUnbindGithubBtn ? "final-confirm" : ""}`}
|
|
||||||
onMouseLeave={() => setShowConfirmUnbindGithubBtn(false)}
|
|
||||||
onClick={handleUnbindGithubBtnClick}
|
|
||||||
>
|
|
||||||
{showConfirmUnbindGithubBtn ? "确定取消绑定!" : "取消绑定"}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span className="value-text">空</span>
|
|
||||||
<a
|
|
||||||
className="btn-text link-btn"
|
|
||||||
href="https://github.com/login/oauth/authorize?client_id=187ba36888f152b06612&scope=read:user,gist"
|
|
||||||
>
|
|
||||||
前往绑定
|
|
||||||
</a>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</Only>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -87,7 +87,7 @@ namespace api {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateUserinfo(userinfo: Partial<{ username: string; password: string; githubName: string }>) {
|
export function updateUserinfo(userinfo: Partial<{ username: string; password: string }>) {
|
||||||
return request({
|
return request({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/api/user/me",
|
url: "/api/user/me",
|
||||||
@ -95,6 +95,13 @@ namespace api {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resetOpenId() {
|
||||||
|
return request<string>({
|
||||||
|
method: "POST",
|
||||||
|
url: "/api/user/open_id/new",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function getMyMemos() {
|
export function getMyMemos() {
|
||||||
return request<Model.Memo[]>({
|
return request<Model.Memo[]>({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
@bg-lightgray: #eaeaea;
|
@bg-lightgray: #eaeaea;
|
||||||
@bg-blue: #1337a3;
|
@bg-blue: #1337a3;
|
||||||
@bg-yellow: yellow;
|
@bg-yellow: yellow;
|
||||||
|
@bg-red: #fcf0f0;
|
||||||
@bg-light-blue: #eef3fe;
|
@bg-light-blue: #eef3fe;
|
||||||
@bg-paper-yellow: #fbf4de;
|
@bg-paper-yellow: #fbf4de;
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
.account-section-container {
|
.account-section-container {
|
||||||
> .form-label {
|
> .form-label {
|
||||||
height: 28px;
|
min-height: 28px;
|
||||||
|
|
||||||
&.username-label {
|
&.username-label {
|
||||||
> input {
|
> input {
|
||||||
@ -62,43 +62,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.connect-section-container {
|
.openapi-section-container {
|
||||||
> .form-label {
|
> .value-text {
|
||||||
height: 28px;
|
width: 100%;
|
||||||
|
border: 1px solid lightgray;
|
||||||
|
padding: 4px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
> .value-text {
|
> .reset-btn {
|
||||||
max-width: 128px;
|
margin-top: 4px;
|
||||||
min-height: 20px;
|
padding: 4px 8px;
|
||||||
overflow: hidden;
|
background-color: @bg-red;
|
||||||
text-overflow: ellipsis;
|
border: 1px solid red;
|
||||||
|
color: red;
|
||||||
|
border-radius: 4px;
|
||||||
|
line-height: 1.6;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .btn-text {
|
&.loading {
|
||||||
padding: 0 8px;
|
opacity: 0.6;
|
||||||
margin-left: 12px;
|
cursor: wait;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .usage-guide-container {
|
||||||
|
.flex(column, flex-start, flex-start);
|
||||||
|
margin-top: 8px;
|
||||||
|
|
||||||
|
> .title-text {
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
> pre {
|
||||||
|
background-color: @bg-whitegray;
|
||||||
|
padding: 8px 12px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 12px;
|
line-height: 1.4;
|
||||||
line-height: 28px;
|
word-break: break-all;
|
||||||
cursor: pointer;
|
word-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
&:hover {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.bind-btn {
|
|
||||||
color: white;
|
|
||||||
background-color: @text-green;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.unbind-btn {
|
|
||||||
color: #d28653;
|
|
||||||
background-color: @bg-lightgray;
|
|
||||||
|
|
||||||
&.final-confirm {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
> .form-label {
|
> .form-label {
|
||||||
height: 28px;
|
min-height: 28px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
> .icon-img {
|
> .icon-img {
|
||||||
|
@ -3,7 +3,6 @@ import api from "../helpers/api";
|
|||||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import { locationService, userService } from "../services";
|
import { locationService, userService } from "../services";
|
||||||
import Only from "../components/common/OnlyWhen";
|
|
||||||
import toastHelper from "../components/Toast";
|
import toastHelper from "../components/Toast";
|
||||||
import "../less/signin.less";
|
import "../less/signin.less";
|
||||||
|
|
||||||
@ -20,7 +19,7 @@ const Signin: React.FC<Props> = () => {
|
|||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [showAutoSigninAsGuest, setShowAutoSigninAsGuest] = useState(true);
|
const [showAutoSigninAsGuest, setShowAutoSigninAsGuest] = useState(true);
|
||||||
const signinBtnClickLoadingState = useLoading(false);
|
const signinBtnsClickLoadingState = useLoading(false);
|
||||||
const autoSigninAsGuestBtn = useRef<HTMLDivElement>(null);
|
const autoSigninAsGuestBtn = useRef<HTMLDivElement>(null);
|
||||||
const signinBtn = useRef<HTMLButtonElement>(null);
|
const signinBtn = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
@ -49,12 +48,8 @@ const Signin: React.FC<Props> = () => {
|
|||||||
setPassword(text);
|
setPassword(text);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSignUpBtnClick = async () => {
|
const handleSigninBtnsClick = async (action: "signin" | "signup" = "signin") => {
|
||||||
toastHelper.info("注册已关闭");
|
if (signinBtnsClickLoadingState.isLoading) {
|
||||||
};
|
|
||||||
|
|
||||||
const handleSignInBtnClick = async () => {
|
|
||||||
if (signinBtnClickLoadingState.isLoading) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,8 +66,11 @@ const Signin: React.FC<Props> = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
signinBtnClickLoadingState.setLoading();
|
signinBtnsClickLoadingState.setLoading();
|
||||||
const actionFunc = api.signin;
|
let actionFunc = api.signin;
|
||||||
|
if (action === "signup") {
|
||||||
|
actionFunc = api.signup;
|
||||||
|
}
|
||||||
const { succeed, message } = await actionFunc(username, password);
|
const { succeed, message } = await actionFunc(username, password);
|
||||||
|
|
||||||
if (!succeed && message) {
|
if (!succeed && message) {
|
||||||
@ -90,11 +88,11 @@ const Signin: React.FC<Props> = () => {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
toastHelper.error("😟 " + error.message);
|
toastHelper.error("😟 " + error.message);
|
||||||
}
|
}
|
||||||
signinBtnClickLoadingState.setFinish();
|
signinBtnsClickLoadingState.setFinish();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSwitchAccountSigninBtnClick = () => {
|
const handleSwitchAccountSigninBtnClick = () => {
|
||||||
if (signinBtnClickLoadingState.isLoading) {
|
if (signinBtnsClickLoadingState.isLoading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,12 +100,12 @@ const Signin: React.FC<Props> = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAutoSigninAsGuestBtnClick = async () => {
|
const handleAutoSigninAsGuestBtnClick = async () => {
|
||||||
if (signinBtnClickLoadingState.isLoading) {
|
if (signinBtnsClickLoadingState.isLoading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
signinBtnClickLoadingState.setLoading();
|
signinBtnsClickLoadingState.setLoading();
|
||||||
const { succeed, message } = await api.signin("guest", "123456");
|
const { succeed, message } = await api.signin("guest", "123456");
|
||||||
|
|
||||||
if (!succeed && message) {
|
if (!succeed && message) {
|
||||||
@ -125,7 +123,7 @@ const Signin: React.FC<Props> = () => {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
toastHelper.error("😟 " + error.message);
|
toastHelper.error("😟 " + error.message);
|
||||||
}
|
}
|
||||||
signinBtnClickLoadingState.setFinish();
|
signinBtnsClickLoadingState.setFinish();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -141,13 +139,13 @@ const Signin: React.FC<Props> = () => {
|
|||||||
<div className="quickly-btns-container">
|
<div className="quickly-btns-container">
|
||||||
<div
|
<div
|
||||||
ref={autoSigninAsGuestBtn}
|
ref={autoSigninAsGuestBtn}
|
||||||
className={`btn guest-signin ${signinBtnClickLoadingState.isLoading ? "requesting" : ""}`}
|
className={`btn guest-signin ${signinBtnsClickLoadingState.isLoading ? "requesting" : ""}`}
|
||||||
onClick={handleAutoSigninAsGuestBtnClick}
|
onClick={handleAutoSigninAsGuestBtnClick}
|
||||||
>
|
>
|
||||||
👉 快速登录进行体验
|
👉 快速登录进行体验
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`btn ${signinBtnClickLoadingState.isLoading ? "requesting" : ""}`}
|
className={`btn ${signinBtnsClickLoadingState.isLoading ? "requesting" : ""}`}
|
||||||
onClick={handleSwitchAccountSigninBtnClick}
|
onClick={handleSwitchAccountSigninBtnClick}
|
||||||
>
|
>
|
||||||
已有账号,我要自己登录
|
已有账号,我要自己登录
|
||||||
@ -167,32 +165,26 @@ const Signin: React.FC<Props> = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="page-footer-container">
|
<div className="page-footer-container">
|
||||||
<div className="btns-container">
|
<div className="btns-container">{/* nth */}</div>
|
||||||
<Only when={window.location.origin.includes("justsven.top")}>
|
|
||||||
<a
|
|
||||||
className="btn-text"
|
|
||||||
href="https://github.com/login/oauth/authorize?client_id=187ba36888f152b06612&scope=read:user,gist"
|
|
||||||
>
|
|
||||||
Sign In with GitHub
|
|
||||||
</a>
|
|
||||||
</Only>
|
|
||||||
</div>
|
|
||||||
<div className="btns-container">
|
<div className="btns-container">
|
||||||
<button
|
<button
|
||||||
className={`btn ${signinBtnClickLoadingState.isLoading ? "requesting" : ""}`}
|
className={`btn ${signinBtnsClickLoadingState.isLoading ? "requesting" : ""}`}
|
||||||
onClick={handleAutoSigninAsGuestBtnClick}
|
onClick={handleAutoSigninAsGuestBtnClick}
|
||||||
>
|
>
|
||||||
体验一下
|
体验一下
|
||||||
</button>
|
</button>
|
||||||
<span className="split-text">/</span>
|
<span className="split-text">/</span>
|
||||||
<button className="btn signup-btn disabled" onClick={handleSignUpBtnClick}>
|
<button
|
||||||
|
className={`btn signin-btn ${signinBtnsClickLoadingState.isLoading ? "requesting" : ""}`}
|
||||||
|
onClick={() => handleSigninBtnsClick("signup")}
|
||||||
|
>
|
||||||
注册
|
注册
|
||||||
</button>
|
</button>
|
||||||
<span className="split-text">/</span>
|
<span className="split-text">/</span>
|
||||||
<button
|
<button
|
||||||
className={`btn signin-btn ${signinBtnClickLoadingState.isLoading ? "requesting" : ""}`}
|
|
||||||
ref={signinBtn}
|
ref={signinBtn}
|
||||||
onClick={handleSignInBtnClick}
|
className={`btn signin-btn ${signinBtnsClickLoadingState.isLoading ? "requesting" : ""}`}
|
||||||
|
onClick={() => handleSigninBtnsClick("signin")}
|
||||||
>
|
>
|
||||||
登录
|
登录
|
||||||
</button>
|
</button>
|
||||||
|
@ -40,12 +40,6 @@ class UserService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async removeGithubName(): Promise<void> {
|
|
||||||
await api.updateUserinfo({
|
|
||||||
githubName: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async checkPasswordValid(password: string): Promise<boolean> {
|
public async checkPasswordValid(password: string): Promise<boolean> {
|
||||||
const { data: isValid } = await api.checkPasswordValid(password);
|
const { data: isValid } = await api.checkPasswordValid(password);
|
||||||
return isValid;
|
return isValid;
|
||||||
@ -56,6 +50,15 @@ class UserService {
|
|||||||
password,
|
password,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async resetOpenId(): Promise<string> {
|
||||||
|
const { data: openId } = await api.resetOpenId();
|
||||||
|
appStore.dispatch({
|
||||||
|
type: "RESET_OPENID",
|
||||||
|
payload: openId,
|
||||||
|
});
|
||||||
|
return openId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const userService = new UserService();
|
const userService = new UserService();
|
||||||
|
@ -12,7 +12,12 @@ interface SignOutAction {
|
|||||||
payload: null;
|
payload: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Actions = SignInAction | SignOutAction;
|
interface ResetOpenIdAction {
|
||||||
|
type: "RESET_OPENID";
|
||||||
|
payload: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Actions = SignInAction | SignOutAction | ResetOpenIdAction;
|
||||||
|
|
||||||
export function reducer(state: State, action: Actions): State {
|
export function reducer(state: State, action: Actions): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
@ -26,6 +31,18 @@ export function reducer(state: State, action: Actions): State {
|
|||||||
user: null,
|
user: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case "RESET_OPENID": {
|
||||||
|
if (!state.user) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
...state.user,
|
||||||
|
openId: action.payload,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
2
web/src/types/models.d.ts
vendored
2
web/src/types/models.d.ts
vendored
@ -7,7 +7,7 @@ declare namespace Model {
|
|||||||
|
|
||||||
interface User extends BaseModel {
|
interface User extends BaseModel {
|
||||||
username: string;
|
username: string;
|
||||||
githubName: string;
|
openId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Memo extends BaseModel {
|
interface Memo extends BaseModel {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user