Commit 9ca6adf4 by JustSong

Able to manage token now

parent 466cdbf1
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"time"
) )
func OpenBrowser(url string) { func OpenBrowser(url string) {
...@@ -132,6 +133,10 @@ func GetUUID() string { ...@@ -132,6 +133,10 @@ func GetUUID() string {
return code return code
} }
func GetTimestamp() int64 {
return time.Now().Unix()
}
func Max(a int, b int) int { func Max(a int, b int) int {
if a >= b { if a >= b {
return a return a
......
...@@ -51,6 +51,7 @@ func SearchTokens(c *gin.Context) { ...@@ -51,6 +51,7 @@ func SearchTokens(c *gin.Context) {
func GetToken(c *gin.Context) { func GetToken(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id")) id, err := strconv.Atoi(c.Param("id"))
userId := c.GetInt("id")
if err != nil { if err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": false, "success": false,
...@@ -58,7 +59,7 @@ func GetToken(c *gin.Context) { ...@@ -58,7 +59,7 @@ func GetToken(c *gin.Context) {
}) })
return return
} }
token, err := model.GetTokenById(id) token, err := model.GetTokenByIds(id, userId)
if err != nil { if err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": false, "success": false,
...@@ -84,7 +85,21 @@ func AddToken(c *gin.Context) { ...@@ -84,7 +85,21 @@ func AddToken(c *gin.Context) {
}) })
return return
} }
err = token.Insert() if len(token.Name) == 0 || len(token.Name) > 20 {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "令牌名称长度必须在1-20之间",
})
return
}
cleanToken := model.Token{
UserId: c.GetInt("id"),
Name: token.Name,
Key: common.GetUUID(),
CreatedTime: common.GetTimestamp(),
AccessedTime: common.GetTimestamp(),
}
err = cleanToken.Insert()
if err != nil { if err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": false, "success": false,
...@@ -101,8 +116,8 @@ func AddToken(c *gin.Context) { ...@@ -101,8 +116,8 @@ func AddToken(c *gin.Context) {
func DeleteToken(c *gin.Context) { func DeleteToken(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id")) id, _ := strconv.Atoi(c.Param("id"))
token := model.Token{Id: id} userId := c.GetInt("id")
err := token.Delete() err := model.DeleteTokenById(id, userId)
if err != nil { if err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": false, "success": false,
...@@ -118,6 +133,7 @@ func DeleteToken(c *gin.Context) { ...@@ -118,6 +133,7 @@ func DeleteToken(c *gin.Context) {
} }
func UpdateToken(c *gin.Context) { func UpdateToken(c *gin.Context) {
userId := c.GetInt("id")
token := model.Token{} token := model.Token{}
err := c.ShouldBindJSON(&token) err := c.ShouldBindJSON(&token)
if err != nil { if err != nil {
...@@ -127,7 +143,17 @@ func UpdateToken(c *gin.Context) { ...@@ -127,7 +143,17 @@ func UpdateToken(c *gin.Context) {
}) })
return return
} }
err = token.Update() cleanToken, err := model.GetTokenByIds(token.Id, userId)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
cleanToken.Name = token.Name
cleanToken.Status = token.Status
err = cleanToken.Update()
if err != nil { if err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": false, "success": false,
...@@ -138,6 +164,7 @@ func UpdateToken(c *gin.Context) { ...@@ -138,6 +164,7 @@ func UpdateToken(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "", "message": "",
"data": cleanToken,
}) })
return return
} }
...@@ -56,6 +56,10 @@ func InitDB() (err error) { ...@@ -56,6 +56,10 @@ func InitDB() (err error) {
if err != nil { if err != nil {
return err return err
} }
err = db.AutoMigrate(&Token{})
if err != nil {
return err
}
err = db.AutoMigrate(&User{}) err = db.AutoMigrate(&User{})
if err != nil { if err != nil {
return err return err
......
package model package model
import ( import (
"errors"
_ "gorm.io/driver/sqlite" _ "gorm.io/driver/sqlite"
) )
...@@ -9,7 +10,7 @@ type Token struct { ...@@ -9,7 +10,7 @@ type Token struct {
UserId int `json:"user_id"` UserId int `json:"user_id"`
Key string `json:"key"` Key string `json:"key"`
Status int `json:"status" gorm:"default:1"` Status int `json:"status" gorm:"default:1"`
Name string `json:"name" gorm:"unique;index"` Name string `json:"name" gorm:"index" `
CreatedTime int64 `json:"created_time" gorm:"bigint"` CreatedTime int64 `json:"created_time" gorm:"bigint"`
AccessedTime int64 `json:"accessed_time" gorm:"bigint"` AccessedTime int64 `json:"accessed_time" gorm:"bigint"`
} }
...@@ -17,19 +18,22 @@ type Token struct { ...@@ -17,19 +18,22 @@ type Token struct {
func GetAllUserTokens(userId int, startIdx int, num int) ([]*Token, error) { func GetAllUserTokens(userId int, startIdx int, num int) ([]*Token, error) {
var tokens []*Token var tokens []*Token
var err error var err error
err = DB.Where("userId = ?", userId).Order("id desc").Limit(num).Offset(startIdx).Omit("key").Find(&tokens).Error err = DB.Where("user_id = ?", userId).Order("id desc").Limit(num).Offset(startIdx).Find(&tokens).Error
return tokens, err return tokens, err
} }
func SearchUserTokens(userId int, keyword string) (tokens []*Token, err error) { func SearchUserTokens(userId int, keyword string) (tokens []*Token, err error) {
err = DB.Where("userId = ?", userId).Omit("key").Where("id = ? or name LIKE ?", keyword, keyword+"%").Find(&tokens).Error err = DB.Where("user_id = ?", userId).Where("id = ? or name LIKE ?", keyword, keyword+"%").Find(&tokens).Error
return tokens, err return tokens, err
} }
func GetTokenById(id int) (*Token, error) { func GetTokenByIds(id int, userId int) (*Token, error) {
token := Token{Id: id} if id == 0 || userId == 0 {
return nil, errors.New("id 或 userId 为空!")
}
token := Token{Id: id, UserId: userId}
var err error = nil var err error = nil
err = DB.Omit("key").Select([]string{"id", "type"}).First(&token, "id = ?", id).Error err = DB.First(&token, "id = ? and user_id = ?", id, userId).Error
return &token, err return &token, err
} }
...@@ -50,3 +54,16 @@ func (token *Token) Delete() error { ...@@ -50,3 +54,16 @@ func (token *Token) Delete() error {
err = DB.Delete(token).Error err = DB.Delete(token).Error
return err return err
} }
func DeleteTokenById(id int, userId int) (err error) {
// Why we need userId here? In case user want to delete other's token.
if id == 0 || userId == 0 {
return errors.New("id 或 userId 为空!")
}
token := Token{Id: id, UserId: userId}
err = DB.Where(token).First(&token).Error
if err != nil {
return err
}
return token.Delete()
}
...@@ -16,6 +16,10 @@ import PasswordResetConfirm from './components/PasswordResetConfirm'; ...@@ -16,6 +16,10 @@ import PasswordResetConfirm from './components/PasswordResetConfirm';
import { UserContext } from './context/User'; import { UserContext } from './context/User';
import Channel from './pages/Channel'; import Channel from './pages/Channel';
import Token from './pages/Token'; import Token from './pages/Token';
import EditToken from './pages/Token/EditToken';
import AddToken from './pages/Token/AddToken';
import EditChannel from './pages/Channel/EditChannel';
import AddChannel from './pages/Channel/AddChannel';
const Home = lazy(() => import('./pages/Home')); const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About')); const About = lazy(() => import('./pages/About'));
...@@ -74,12 +78,44 @@ function App() { ...@@ -74,12 +78,44 @@ function App() {
} }
/> />
<Route <Route
path='/channel/edit/:id'
element={
<Suspense fallback={<Loading></Loading>}>
<EditChannel />
</Suspense>
}
/>
<Route
path='/channel/add'
element={
<Suspense fallback={<Loading></Loading>}>
<AddChannel />
</Suspense>
}
/>
<Route
path='/token' path='/token'
element={ element={
<Token /> <Token />
} }
/> />
<Route <Route
path='/token/edit/:id'
element={
<Suspense fallback={<Loading></Loading>}>
<EditToken />
</Suspense>
}
/>
<Route
path='/token/add'
element={
<Suspense fallback={<Loading></Loading>}>
<AddToken />
</Suspense>
}
/>
<Route
path='/user' path='/user'
element={ element={
<PrivateRoute> <PrivateRoute>
......
...@@ -97,3 +97,41 @@ export function removeTrailingSlash(url) { ...@@ -97,3 +97,41 @@ export function removeTrailingSlash(url) {
return url; return url;
} }
} }
export function timestamp2string(timestamp) {
let date = new Date(timestamp * 1000);
let year = date.getFullYear().toString();
let month = (date.getMonth() + 1).toString();
let day = date.getDate().toString();
let hour = date.getHours().toString();
let minute = date.getMinutes().toString();
let second = date.getSeconds().toString();
if (month.length === 1) {
month = '0' + month;
}
if (day.length === 1) {
day = '0' + day;
}
if (hour.length === 1) {
hour = '0' + hour;
}
if (minute.length === 1) {
minute = '0' + minute;
}
if (second.length === 1) {
second = '0' + second;
}
return (
year +
'-' +
month +
'-' +
day +
' ' +
hour +
':' +
minute +
':' +
second
);
}
\ No newline at end of file
import React, { useState } from 'react';
import { Button, Form, Header, Segment } from 'semantic-ui-react';
import { API, showError, showSuccess } from '../../helpers';
const AddChannel = () => {
const originInputs = {
username: '',
display_name: '',
password: '',
};
const [inputs, setInputs] = useState(originInputs);
const { username, display_name, password } = inputs;
const handleInputChange = (e, { name, value }) => {
setInputs((inputs) => ({ ...inputs, [name]: value }));
};
const submit = async () => {
if (inputs.username === '' || inputs.password === '') return;
const res = await API.post(`/api/user/`, inputs);
const { success, message } = res.data;
if (success) {
showSuccess('用户账户创建成功!');
setInputs(originInputs);
} else {
showError(message);
}
};
return (
<>
<Segment>
<Header as="h3">创建新用户账户</Header>
<Form autoComplete="off">
<Form.Field>
<Form.Input
label="用户名"
name="username"
placeholder={'请输入用户名'}
onChange={handleInputChange}
value={username}
autoComplete="off"
required
/>
</Form.Field>
<Form.Field>
<Form.Input
label="显示名称"
name="display_name"
placeholder={'请输入显示名称'}
onChange={handleInputChange}
value={display_name}
autoComplete="off"
/>
</Form.Field>
<Form.Field>
<Form.Input
label="密码"
name="password"
type={'password'}
placeholder={'请输入密码'}
onChange={handleInputChange}
value={password}
autoComplete="off"
required
/>
</Form.Field>
<Button type={'submit'} onClick={submit}>
提交
</Button>
</Form>
</Segment>
</>
);
};
export default AddChannel;
import React, { useEffect, useState } from 'react';
import { Button, Form, Header, Segment } from 'semantic-ui-react';
import { useParams } from 'react-router-dom';
import { API, showError, showSuccess } from '../../helpers';
const EditChannel = () => {
const params = useParams();
const userId = params.id;
const [loading, setLoading] = useState(true);
const [inputs, setInputs] = useState({
username: '',
display_name: '',
password: '',
github_id: '',
wechat_id: '',
email: '',
});
const { username, display_name, password, github_id, wechat_id, email } =
inputs;
const handleInputChange = (e, { name, value }) => {
setInputs((inputs) => ({ ...inputs, [name]: value }));
};
const loadUser = async () => {
let res = undefined;
if (userId) {
res = await API.get(`/api/user/${userId}`);
} else {
res = await API.get(`/api/user/self`);
}
const { success, message, data } = res.data;
if (success) {
data.password = '';
setInputs(data);
} else {
showError(message);
}
setLoading(false);
};
useEffect(() => {
loadUser().then();
}, []);
const submit = async () => {
let res = undefined;
if (userId) {
res = await API.put(`/api/user/`, { ...inputs, id: parseInt(userId) });
} else {
res = await API.put(`/api/user/self`, inputs);
}
const { success, message } = res.data;
if (success) {
showSuccess('用户信息更新成功!');
} else {
showError(message);
}
};
return (
<>
<Segment loading={loading}>
<Header as='h3'>更新用户信息</Header>
<Form autoComplete='off'>
<Form.Field>
<Form.Input
label='用户名'
name='username'
placeholder={'请输入新的用户名'}
onChange={handleInputChange}
value={username}
autoComplete='off'
/>
</Form.Field>
<Form.Field>
<Form.Input
label='密码'
name='password'
type={'password'}
placeholder={'请输入新的密码'}
onChange={handleInputChange}
value={password}
autoComplete='off'
/>
</Form.Field>
<Form.Field>
<Form.Input
label='显示名称'
name='display_name'
placeholder={'请输入新的显示名称'}
onChange={handleInputChange}
value={display_name}
autoComplete='off'
/>
</Form.Field>
<Form.Field>
<Form.Input
label='已绑定的 GitHub 账户'
name='github_id'
value={github_id}
autoComplete='off'
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
readOnly
/>
</Form.Field>
<Form.Field>
<Form.Input
label='已绑定的微信账户'
name='wechat_id'
value={wechat_id}
autoComplete='off'
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
readOnly
/>
</Form.Field>
<Form.Field>
<Form.Input
label='已绑定的邮箱账户'
name='email'
value={email}
autoComplete='off'
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
readOnly
/>
</Form.Field>
<Button onClick={submit}>提交</Button>
</Form>
</Segment>
</>
);
};
export default EditChannel;
import React, { useState } from 'react';
import { Button, Form, Header, Segment } from 'semantic-ui-react';
import { API, showError, showSuccess } from '../../helpers';
const AddToken = () => {
const originInputs = {
name: '',
};
const [inputs, setInputs] = useState(originInputs);
const { name, display_name, password } = inputs;
const handleInputChange = (e, { name, value }) => {
setInputs((inputs) => ({ ...inputs, [name]: value }));
};
const submit = async () => {
if (inputs.name === '') return;
const res = await API.post(`/api/token/`, inputs);
const { success, message } = res.data;
if (success) {
showSuccess('令牌创建成功!');
setInputs(originInputs);
} else {
showError(message);
}
};
return (
<>
<Segment>
<Header as="h3">创建新的令牌</Header>
<Form autoComplete="off">
<Form.Field>
<Form.Input
label="名称"
name="name"
placeholder={'请输入名称'}
onChange={handleInputChange}
value={name}
autoComplete="off"
required
/>
</Form.Field>
<Button type={'submit'} onClick={submit}>
提交
</Button>
</Form>
</Segment>
</>
);
};
export default AddToken;
import React, { useEffect, useState } from 'react';
import { Button, Form, Header, Segment } from 'semantic-ui-react';
import { useParams } from 'react-router-dom';
import { API, showError, showSuccess } from '../../helpers';
const EditToken = () => {
const params = useParams();
const tokenId = params.id;
const [loading, setLoading] = useState(true);
const [inputs, setInputs] = useState({
name: ''
});
const { name } = inputs;
const handleInputChange = (e, { name, value }) => {
setInputs((inputs) => ({ ...inputs, [name]: value }));
};
const loadToken = async () => {
let res = await API.get(`/api/token/${tokenId}`);
const { success, message, data } = res.data;
if (success) {
data.password = '';
setInputs(data);
} else {
showError(message);
}
setLoading(false);
};
useEffect(() => {
loadToken().then();
}, []);
const submit = async () => {
let res = await API.put(`/api/token/`, { ...inputs, id: parseInt(tokenId) });
const { success, message } = res.data;
if (success) {
showSuccess('令牌更新成功!');
} else {
showError(message);
}
};
return (
<>
<Segment loading={loading}>
<Header as='h3'>更新令牌信息</Header>
<Form autoComplete='off'>
<Form.Field>
<Form.Input
label='名称'
name='name'
placeholder={'请输入新的名称'}
onChange={handleInputChange}
value={name}
autoComplete='off'
/>
</Form.Field>
<Button onClick={submit}>提交</Button>
</Form>
</Segment>
</>
);
};
export default EditToken;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment