Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
9bec29a0
Commit
9bec29a0
authored
Jan 09, 2022
by
email
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: support open api with webhooks
parent
aed1004f
Changes
17
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
233 additions
and
325 deletions
+233
-325
auth.go
api/auth.go
+1
-128
user.go
api/user.go
+8
-20
webhooks.go
api/webhooks.go
+55
-0
initial_db.sql
resources/initial_db.sql
+4
-5
memos.db
resources/memos.db
+0
-0
main.go
server/main.go
+1
-0
user.go
store/user.go
+30
-54
MenuBtnsPopup.tsx
web/src/components/MenuBtnsPopup.tsx
+1
-0
MyAccountSection.tsx
web/src/components/MyAccountSection.tsx
+31
-45
api.ts
web/src/helpers/api.ts
+8
-1
mixin.less
web/src/less/mixin.less
+1
-0
my-account-section.less
web/src/less/my-account-section.less
+41
-32
preferences-section.less
web/src/less/preferences-section.less
+1
-1
Signin.tsx
web/src/pages/Signin.tsx
+23
-31
userService.ts
web/src/services/userService.ts
+9
-6
userStore.ts
web/src/stores/userStore.ts
+18
-1
models.d.ts
web/src/types/models.d.ts
+1
-1
No files found.
api/auth.go
View file @
9bec29a0
package
api
import
(
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"memos/api/e"
"memos/config"
"memos/store"
"memos/utils"
"net/http"
"github.com/gorilla/mux"
...
...
@@ -38,7 +33,7 @@ func handleUserSignUp(w http.ResponseWriter, r *http.Request) {
return
}
user
,
err
:=
store
.
CreateNewUser
(
userSignup
.
Username
,
userSignup
.
Password
,
""
)
user
,
err
:=
store
.
CreateNewUser
(
userSignup
.
Username
,
userSignup
.
Password
)
if
err
!=
nil
{
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
)
{
authRouter
:=
r
.
PathPrefix
(
"/api/auth"
)
.
Subrouter
()
...
...
@@ -236,5 +110,4 @@ func RegisterAuthRoutes(r *mux.Router) {
authRouter
.
HandleFunc
(
"/signup"
,
handleUserSignUp
)
.
Methods
(
"POST"
)
authRouter
.
HandleFunc
(
"/signin"
,
handleUserSignIn
)
.
Methods
(
"POST"
)
authRouter
.
HandleFunc
(
"/signout"
,
handleUserSignOut
)
.
Methods
(
"POST"
)
authRouter
.
HandleFunc
(
"/github"
,
handleGithubAuthCallback
)
.
Methods
(
"GET"
)
}
api/user.go
View file @
9bec29a0
...
...
@@ -29,16 +29,16 @@ func handleGetMyUserInfo(w http.ResponseWriter, r *http.Request) {
func
handleUpdateMyUserInfo
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
userId
,
_
:=
GetUserIdInSession
(
r
)
u
serPatch
:=
store
.
UserPatch
{}
err
:=
json
.
NewDecoder
(
r
.
Body
)
.
Decode
(
&
userPatch
)
u
pdateUserPatch
:=
store
.
Update
UserPatch
{}
err
:=
json
.
NewDecoder
(
r
.
Body
)
.
Decode
(
&
u
pdateU
serPatch
)
if
err
!=
nil
{
e
.
ErrorHandler
(
w
,
"REQUEST_BODY_ERROR"
,
"Bad request"
)
return
}
if
userPatch
.
Username
!=
nil
{
usernameUsable
,
_
:=
store
.
CheckUsernameUsable
(
*
userPatch
.
Username
)
if
u
pdateU
serPatch
.
Username
!=
nil
{
usernameUsable
,
_
:=
store
.
CheckUsernameUsable
(
*
u
pdateU
serPatch
.
Username
)
if
!
usernameUsable
{
json
.
NewEncoder
(
w
)
.
Encode
(
Response
{
Succeed
:
false
,
...
...
@@ -49,19 +49,7 @@ func handleUpdateMyUserInfo(w http.ResponseWriter, r *http.Request) {
}
}
if
userPatch
.
GithubName
!=
nil
{
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
)
user
,
err
:=
store
.
UpdateUser
(
userId
,
&
updateUserPatch
)
if
err
!=
nil
{
e
.
ErrorHandler
(
w
,
"DATABASE_ERROR"
,
err
.
Error
())
...
...
@@ -75,10 +63,10 @@ func handleUpdateMyUserInfo(w http.ResponseWriter, r *http.Request) {
})
}
func
handleRe
fresh
UserOpenId
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
func
handleRe
set
UserOpenId
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
userId
,
_
:=
GetUserIdInSession
(
r
)
openId
,
err
:=
store
.
Update
UserOpenId
(
userId
)
openId
,
err
:=
store
.
Reset
UserOpenId
(
userId
)
if
err
!=
nil
{
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"
,
handleUpdateMyUserInfo
)
.
Methods
(
"PATCH"
)
userRouter
.
HandleFunc
(
"/open_id/new"
,
handleRe
fresh
UserOpenId
)
.
Methods
(
"POST"
)
userRouter
.
HandleFunc
(
"/open_id/new"
,
handleRe
set
UserOpenId
)
.
Methods
(
"POST"
)
userRouter
.
HandleFunc
(
"/checkusername"
,
handleCheckUsername
)
.
Methods
(
"POST"
)
userRouter
.
HandleFunc
(
"/validpassword"
,
handleValidPassword
)
.
Methods
(
"POST"
)
}
api/webhooks.go
0 → 100644
View file @
9bec29a0
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"
)
}
resources/initial_db.sql
View file @
9bec29a0
...
...
@@ -8,10 +8,9 @@ CREATE TABLE `users` (
`username`
TEXT
NOT
NULL
,
`password`
TEXT
NOT
NULL
,
`open_id`
TEXT
NOT
NULL
DEFAULT
''
,
`github_name`
TEXT
NOT
NULL
DEFAULT
''
,
`created_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`
(
...
...
@@ -48,10 +47,10 @@ CREATE TABLE `resources` (
INSERT
INTO
`users`
(
`id`
,
`username`
,
`password`
)
(
`id`
,
`username`
,
`password`
,
`open_id`
)
VALUES
(
'1'
,
'guest'
,
'123456'
),
(
'2'
,
'
test'
,
'123456
'
);
(
'1'
,
'guest'
,
'123456'
,
'guest_open_id'
),
(
'2'
,
'
mine'
,
'123456'
,
'mine_open_id
'
);
INSERT
INTO
`memos`
(
`id`
,
`content`
,
`user_id`
)
...
...
resources/memos.db
View file @
9bec29a0
No preview for this file type
server/main.go
View file @
9bec29a0
...
...
@@ -18,6 +18,7 @@ func main() {
api
.
RegisterMemoRoutes
(
r
)
api
.
RegisterQueryRoutes
(
r
)
api
.
RegisterResourceRoutes
(
r
)
api
.
RegisterWebHooksRoutes
(
r
)
webServe
:=
api
.
SPAHandler
{
StaticPath
:
"./web/dist"
,
...
...
store/user.go
View file @
9bec29a0
...
...
@@ -8,40 +8,37 @@ import (
)
type
User
struct
{
Id
string
`json:"id"`
Username
string
`json:"username"`
Password
string
`json:"password"`
OpenId
string
`json:"openId"`
GithubName
string
`json:"githubName"`
CreatedAt
string
`json:"createdAt"`
UpdatedAt
string
`json:"updatedAt"`
Id
string
`json:"id"`
Username
string
`json:"username"`
Password
string
`json:"password"`
OpenId
string
`json:"openId"`
CreatedAt
string
`json:"createdAt"`
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
()
newUser
:=
User
{
Id
:
utils
.
GenUUID
(),
Username
:
username
,
Password
:
password
,
OpenId
:
utils
.
GenUUID
(),
GithubName
:
githubName
,
CreatedAt
:
nowDateTimeStr
,
UpdatedAt
:
nowDateTimeStr
,
Id
:
utils
.
GenUUID
(),
Username
:
username
,
Password
:
password
,
OpenId
:
utils
.
GenUUID
(),
CreatedAt
:
nowDateTimeStr
,
UpdatedAt
:
nowDateTimeStr
,
}
query
:=
`INSERT INTO users (id, username, password, open_id,
github_name, created_at, updated_at) VALUES (?,
?, ?, ?, ?, ?, ?)`
_
,
err
:=
DB
.
Exec
(
query
,
newUser
.
Id
,
newUser
.
Username
,
newUser
.
Password
,
newUser
.
OpenId
,
newUser
.
GithubName
,
newUser
.
CreatedAt
,
newUser
.
UpdatedAt
)
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
.
CreatedAt
,
newUser
.
UpdatedAt
)
return
newUser
,
FormatDBError
(
err
)
}
type
UserPatch
struct
{
Username
*
string
Password
*
string
GithubName
*
string
type
UpdateUserPatch
struct
{
Username
*
string
Password
*
string
}
func
UpdateUser
(
id
string
,
u
serPatch
*
UserPatch
)
(
User
,
error
)
{
func
UpdateUser
(
id
string
,
u
pdateUserPatch
*
Update
UserPatch
)
(
User
,
error
)
{
user
:=
User
{}
user
,
err
:=
GetUserById
(
id
)
...
...
@@ -51,18 +48,14 @@ func UpdateUser(id string, userPatch *UserPatch) (User, error) {
set
,
args
:=
[]
string
{},
[]
interface
{}{}
if
v
:=
userPatch
.
Username
;
v
!=
nil
{
if
v
:=
u
pdateU
serPatch
.
Username
;
v
!=
nil
{
user
.
Username
=
*
v
set
,
args
=
append
(
set
,
"username=?"
),
append
(
args
,
*
v
)
}
if
v
:=
userPatch
.
Password
;
v
!=
nil
{
if
v
:=
u
pdateU
serPatch
.
Password
;
v
!=
nil
{
user
.
Password
=
*
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
())
args
=
append
(
args
,
id
)
...
...
@@ -72,7 +65,7 @@ func UpdateUser(id string, userPatch *UserPatch) (User, error) {
return
user
,
FormatDBError
(
err
)
}
func
Update
UserOpenId
(
userId
string
)
(
string
,
error
)
{
func
Reset
UserOpenId
(
userId
string
)
(
string
,
error
)
{
openId
:=
utils
.
GenUUID
()
query
:=
`UPDATE users SET open_id=? WHERE id=?`
_
,
err
:=
DB
.
Exec
(
query
,
openId
,
userId
)
...
...
@@ -80,23 +73,23 @@ func UpdateUserOpenId(userId string) (string, 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
{}
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
GetUserBy
UsernameAndPassword
(
username
string
,
passwor
d
string
)
(
User
,
error
)
{
query
:=
`SELECT id, username, password, open_id,
github_name, created_at, updated_at FROM users WHERE username=? AND passwor
d=?`
func
GetUserBy
OpenId
(
openI
d
string
)
(
User
,
error
)
{
query
:=
`SELECT id, username, password, open_id,
created_at, updated_at FROM users WHERE open_i
d=?`
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
,
openId
)
.
Scan
(
&
user
.
Id
,
&
user
.
Username
,
&
user
.
Password
,
&
user
.
OpenId
,
&
user
.
CreatedAt
,
&
user
.
UpdatedAt
)
return
user
,
FormatDBError
(
err
)
}
func
GetUserBy
GithubName
(
githubName
string
)
(
User
,
error
)
{
query
:=
`SELECT id, username, password, open_id,
github_name, created_at, updated_at FROM users WHERE github_name
=?`
func
GetUserBy
UsernameAndPassword
(
username
string
,
password
string
)
(
User
,
error
)
{
query
:=
`SELECT id, username, password, open_id,
created_at, updated_at FROM users WHERE username=? AND password
=?`
user
:=
User
{}
err
:=
DB
.
QueryRow
(
query
,
githubName
)
.
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
)
}
...
...
@@ -118,23 +111,6 @@ func CheckUsernameUsable(username string) (bool, error) {
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
)
{
query
:=
`SELECT * FROM users WHERE id=? AND password=?`
query
=
fmt
.
Sprintf
(
"SELECT COUNT(*) FROM (%s)"
,
query
)
...
...
web/src/components/MenuBtnsPopup.tsx
View file @
9bec29a0
...
...
@@ -43,6 +43,7 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
const
handleSignOutBtnClick
=
async
()
=>
{
await
userService
.
doSignOut
();
locationService
.
replaceHistory
(
"/signin"
);
window
.
location
.
reload
();
};
return
(
...
...
web/src/components/MyAccountSection.tsx
View file @
9bec29a0
...
...
@@ -3,7 +3,8 @@ import appContext from "../stores/appContext";
import
{
userService
}
from
"../services"
;
import
utils
from
"../helpers/utils"
;
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
showChangePasswordDialog
from
"./ChangePasswordDialog"
;
import
"../less/my-account-section.less"
;
...
...
@@ -21,7 +22,9 @@ const MyAccountSection: React.FC<Props> = () => {
const
{
userState
}
=
useContext
(
appContext
);
const
user
=
userState
.
user
as
Model
.
User
;
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
nextUsername
=
e
.
target
.
value
as
string
;
...
...
@@ -69,18 +72,23 @@ const MyAccountSection: React.FC<Props> = () => {
showChangePasswordDialog
();
};
const
handleUnbindGithubBtnClick
=
async
()
=>
{
if
(
showConfirmUnbindGithubBtn
)
{
try
{
await
userService
.
removeGithubName
();
await
userService
.
doSignIn
();
}
catch
(
error
:
any
)
{
toastHelper
.
error
(
error
.
message
);
}
setShowConfirmUnbindGithubBtn
(
false
);
}
else
{
setShowConfirmUnbindGithubBtn
(
true
);
const
handleResetOpenIdBtnClick
=
async
()
=>
{
if
(
!
showConfirmResetAPIBtn
)
{
toggleConfirmResetAPIBtn
(
true
);
return
;
}
if
(
resetBtnClickLoadingState
.
isLoading
)
{
return
;
}
resetBtnClickLoadingState
.
setLoading
();
try
{
await
userService
.
resetOpenId
();
}
catch
(
error
)
{
// do nth
}
resetBtnClickLoadingState
.
setFinish
();
toggleConfirmResetAPIBtn
(
false
);
};
const
handlePreventDefault
=
(
e
:
React
.
MouseEvent
)
=>
{
...
...
@@ -124,39 +132,17 @@ const MyAccountSection: React.FC<Props> = () => {
</
span
>
</
label
>
</
div
>
{
/* Account Binding Settings: only can use for domain: memos.justsven.top */
}
<
Only
when=
{
window
.
location
.
origin
.
includes
(
"justsven.top"
)
}
>
<
div
className=
"section-container connect-section-container"
>
<
p
className=
"title-text"
>
关联账号
</
p
>
<
label
className=
"form-label input-form-label"
>
<
span
className=
"normal-text"
>
GitHub:
</
span
>
{
user
.
githubName
?
(
<>
<
a
className=
"value-text"
href=
{
"https://github.com/"
+
user
.
githubName
}
>
{
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
className=
"section-container openapi-section-container"
>
<
p
className=
"title-text"
>
Open API(实验性功能)
</
p
>
<
p
className=
"value-text"
>
{
openAPIRoute
}
</
p
>
<
span
className=
{
`reset-btn ${resetBtnClickLoadingState.isLoading ? "loading" : ""}`
}
onClick=
{
handleResetOpenIdBtnClick
}
>
{
showConfirmResetAPIBtn
?
"⚠️ 确定重置 API"
:
"重置 API"
}
</
span
>
<
div
className=
"usage-guide-container"
>
<
p
className=
"title-text"
>
使用方法:
</
p
>
<
pre
>
{
`POST ${openAPIRoute}\nContent-type: application/json\n{\n "content": "Hello, #memos ${window.location.origin}"\n}`
}
</
pre
>
</
div
>
</
Only
>
</
div
>
</>
);
};
...
...
web/src/helpers/api.ts
View file @
9bec29a0
...
...
@@ -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
({
method
:
"PATCH"
,
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
()
{
return
request
<
Model
.
Memo
[]
>
({
method
:
"GET"
,
...
...
web/src/less/mixin.less
View file @
9bec29a0
...
...
@@ -11,6 +11,7 @@
@bg-lightgray: #eaeaea;
@bg-blue: #1337a3;
@bg-yellow: yellow;
@bg-red: #fcf0f0;
@bg-light-blue: #eef3fe;
@bg-paper-yellow: #fbf4de;
...
...
web/src/less/my-account-section.less
View file @
9bec29a0
...
...
@@ -2,7 +2,7 @@
.account-section-container {
> .form-label {
height: 28px;
min-
height: 28px;
&.username-label {
> input {
...
...
@@ -62,43 +62,52 @@
}
}
.connect-section-container {
> .form-label {
height: 28px;
.openapi-section-container {
> .value-text {
width: 100%;
border: 1px solid lightgray;
padding: 4px 6px;
border-radius: 4px;
line-height: 1.6;
}
> .value-text {
max-width: 128px;
min-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
> .reset-btn {
margin-top: 4px;
padding: 4px 8px;
background-color: @bg-red;
border: 1px solid red;
color: red;
border-radius: 4px;
line-height: 1.6;
cursor: pointer;
user-select: none;
&:hover {
opacity: 0.8;
}
> .btn-text {
padding: 0 8px;
margin-left: 12px;
border-radius: 4px;
font-size: 12px;
line-height: 28px;
cursor: pointer;
&:hover {
opacity: 0.8;
}
&.loading {
opacity: 0.6;
cursor: wait;
}
}
&.bind-btn {
color: white;
background-color: @text-green;
text-decoration: none;
}
> .usage-guide-container {
.flex(column, flex-start, flex-start);
margin-top: 8px;
&.unbind-btn
{
color: #d28653
;
background-color: @bg-lightgray;
> .title-text
{
line-height: 2
;
}
&.final-confirm {
font-weight: bold;
}
}
> pre {
background-color: @bg-whitegray;
padding: 8px 12px;
border-radius: 4px;
line-height: 1.4;
word-break: break-all;
word-wrap: break-word;
white-space: pre-wrap;
}
}
}
web/src/less/preferences-section.less
View file @
9bec29a0
...
...
@@ -9,7 +9,7 @@
}
> .form-label {
height: 28px;
min-
height: 28px;
cursor: pointer;
> .icon-img {
...
...
web/src/pages/Signin.tsx
View file @
9bec29a0
...
...
@@ -3,7 +3,6 @@ import api from "../helpers/api";
import
{
validate
,
ValidatorConfig
}
from
"../helpers/validator"
;
import
useLoading
from
"../hooks/useLoading"
;
import
{
locationService
,
userService
}
from
"../services"
;
import
Only
from
"../components/common/OnlyWhen"
;
import
toastHelper
from
"../components/Toast"
;
import
"../less/signin.less"
;
...
...
@@ -20,7 +19,7 @@ const Signin: React.FC<Props> = () => {
const
[
username
,
setUsername
]
=
useState
(
""
);
const
[
password
,
setPassword
]
=
useState
(
""
);
const
[
showAutoSigninAsGuest
,
setShowAutoSigninAsGuest
]
=
useState
(
true
);
const
signinBtnClickLoadingState
=
useLoading
(
false
);
const
signinBtn
s
ClickLoadingState
=
useLoading
(
false
);
const
autoSigninAsGuestBtn
=
useRef
<
HTMLDivElement
>
(
null
);
const
signinBtn
=
useRef
<
HTMLButtonElement
>
(
null
);
...
...
@@ -49,12 +48,8 @@ const Signin: React.FC<Props> = () => {
setPassword
(
text
);
};
const
handleSignUpBtnClick
=
async
()
=>
{
toastHelper
.
info
(
"注册已关闭"
);
};
const
handleSignInBtnClick
=
async
()
=>
{
if
(
signinBtnClickLoadingState
.
isLoading
)
{
const
handleSigninBtnsClick
=
async
(
action
:
"signin"
|
"signup"
=
"signin"
)
=>
{
if
(
signinBtnsClickLoadingState
.
isLoading
)
{
return
;
}
...
...
@@ -71,8 +66,11 @@ const Signin: React.FC<Props> = () => {
}
try
{
signinBtnClickLoadingState
.
setLoading
();
const
actionFunc
=
api
.
signin
;
signinBtnsClickLoadingState
.
setLoading
();
let
actionFunc
=
api
.
signin
;
if
(
action
===
"signup"
)
{
actionFunc
=
api
.
signup
;
}
const
{
succeed
,
message
}
=
await
actionFunc
(
username
,
password
);
if
(
!
succeed
&&
message
)
{
...
...
@@ -90,11 +88,11 @@ const Signin: React.FC<Props> = () => {
console
.
error
(
error
);
toastHelper
.
error
(
"😟 "
+
error
.
message
);
}
signinBtnClickLoadingState
.
setFinish
();
signinBtn
s
ClickLoadingState
.
setFinish
();
};
const
handleSwitchAccountSigninBtnClick
=
()
=>
{
if
(
signinBtnClickLoadingState
.
isLoading
)
{
if
(
signinBtn
s
ClickLoadingState
.
isLoading
)
{
return
;
}
...
...
@@ -102,12 +100,12 @@ const Signin: React.FC<Props> = () => {
};
const
handleAutoSigninAsGuestBtnClick
=
async
()
=>
{
if
(
signinBtnClickLoadingState
.
isLoading
)
{
if
(
signinBtn
s
ClickLoadingState
.
isLoading
)
{
return
;
}
try
{
signinBtnClickLoadingState
.
setLoading
();
signinBtn
s
ClickLoadingState
.
setLoading
();
const
{
succeed
,
message
}
=
await
api
.
signin
(
"guest"
,
"123456"
);
if
(
!
succeed
&&
message
)
{
...
...
@@ -125,7 +123,7 @@ const Signin: React.FC<Props> = () => {
console
.
error
(
error
);
toastHelper
.
error
(
"😟 "
+
error
.
message
);
}
signinBtnClickLoadingState
.
setFinish
();
signinBtn
s
ClickLoadingState
.
setFinish
();
};
return
(
...
...
@@ -141,13 +139,13 @@ const Signin: React.FC<Props> = () => {
<
div
className=
"quickly-btns-container"
>
<
div
ref=
{
autoSigninAsGuestBtn
}
className=
{
`btn guest-signin ${signinBtnClickLoadingState.isLoading ? "requesting" : ""}`
}
className=
{
`btn guest-signin ${signinBtn
s
ClickLoadingState.isLoading ? "requesting" : ""}`
}
onClick=
{
handleAutoSigninAsGuestBtnClick
}
>
👉 快速登录进行体验
</
div
>
<
div
className=
{
`btn ${signinBtnClickLoadingState.isLoading ? "requesting" : ""}`
}
className=
{
`btn ${signinBtn
s
ClickLoadingState.isLoading ? "requesting" : ""}`
}
onClick=
{
handleSwitchAccountSigninBtnClick
}
>
已有账号,我要自己登录
...
...
@@ -167,32 +165,26 @@ const Signin: React.FC<Props> = () => {
</
div
>
</
div
>
<
div
className=
"page-footer-container"
>
<
div
className=
"btns-container"
>
<
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"
>
{
/* nth */
}
</
div
>
<
div
className=
"btns-container"
>
<
button
className=
{
`btn ${signinBtnClickLoadingState.isLoading ? "requesting" : ""}`
}
className=
{
`btn ${signinBtn
s
ClickLoadingState.isLoading ? "requesting" : ""}`
}
onClick=
{
handleAutoSigninAsGuestBtnClick
}
>
体验一下
</
button
>
<
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
>
<
span
className=
"split-text"
>
/
</
span
>
<
button
className=
{
`btn signin-btn ${signinBtnClickLoadingState.isLoading ? "requesting" : ""}`
}
ref=
{
signinBtn
}
onClick=
{
handleSignInBtnClick
}
className=
{
`btn signin-btn ${signinBtnsClickLoadingState.isLoading ? "requesting" : ""}`
}
onClick=
{
()
=>
handleSigninBtnsClick
(
"signin"
)
}
>
登录
</
button
>
...
...
web/src/services/userService.ts
View file @
9bec29a0
...
...
@@ -40,12 +40,6 @@ class UserService {
});
}
public
async
removeGithubName
():
Promise
<
void
>
{
await
api
.
updateUserinfo
({
githubName
:
""
,
});
}
public
async
checkPasswordValid
(
password
:
string
):
Promise
<
boolean
>
{
const
{
data
:
isValid
}
=
await
api
.
checkPasswordValid
(
password
);
return
isValid
;
...
...
@@ -56,6 +50,15 @@ class UserService {
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
();
...
...
web/src/stores/userStore.ts
View file @
9bec29a0
...
...
@@ -12,7 +12,12 @@ interface SignOutAction {
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
{
switch
(
action
.
type
)
{
...
...
@@ -26,6 +31,18 @@ export function reducer(state: State, action: Actions): State {
user
:
null
,
};
}
case
"RESET_OPENID"
:
{
if
(
!
state
.
user
)
{
return
state
;
}
return
{
user
:
{
...
state
.
user
,
openId
:
action
.
payload
,
},
};
}
default
:
{
return
state
;
}
...
...
web/src/types/models.d.ts
View file @
9bec29a0
...
...
@@ -7,7 +7,7 @@ declare namespace Model {
interface
User
extends
BaseModel
{
username
:
string
;
githubName
:
string
;
openId
:
string
;
}
interface
Memo
extends
BaseModel
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment