Kaynağa Gözat

Update makefile and user model, add Telegram integration

Ehco1996 1 yıl önce
ebeveyn
işleme
194ff1ac57

+ 1 - 7
makefile

@@ -1,7 +1,7 @@
 FRONTEND_DIR = ./web
 BACKEND_DIR = .
 
-.PHONY: all start-frontend start-backend
+.PHONY: all build-frontend start-backend
 
 all: start-frontend start-backend
 
@@ -9,12 +9,6 @@ build-frontend:
 	@echo "Building frontend..."
 	@cd $(FRONTEND_DIR) && npm install && DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat VERSION) npm run build npm run build
 
-# 启动前端开发服务器
-start-frontend:
-	@echo "Starting frontend dev server..."
-	@cd $(FRONTEND_DIR) && npm start &
-
-# 启动后端开发服务器
 start-backend:
 	@echo "Starting backend dev server..."
 	@cd $(BACKEND_DIR) && go run main.go &

+ 3 - 1
model/user.go

@@ -3,10 +3,11 @@ package model
 import (
 	"errors"
 	"fmt"
-	"gorm.io/gorm"
 	"one-api/common"
 	"strings"
 	"time"
+
+	"gorm.io/gorm"
 )
 
 // User if you add sensitive fields, don't forget to clean them in setupLogin function.
@@ -21,6 +22,7 @@ type User struct {
 	Email            string         `json:"email" gorm:"index" validate:"max=50"`
 	GitHubId         string         `json:"github_id" gorm:"column:github_id;index"`
 	WeChatId         string         `json:"wechat_id" gorm:"column:wechat_id;index"`
+	TelegramId       string         `json:"telegram_id" gorm:"column:telegram_id;index"`
 	VerificationCode string         `json:"verification_code" gorm:"-:all"`                                    // this field is only for Email verification, don't save it to database!
 	AccessToken      string         `json:"access_token" gorm:"type:char(32);column:access_token;uniqueIndex"` // this token is for system management
 	Quota            int            `json:"quota" gorm:"type:int;default:0"`

+ 43 - 24
web/src/components/LoginForm.js

@@ -1,14 +1,14 @@
-import React, {useContext, useEffect, useState} from 'react';
-import {Link, useNavigate, useSearchParams} from 'react-router-dom';
-import {UserContext} from '../context/User';
-import {API, getLogo, isMobile, showError, showInfo, showSuccess, showWarning} from '../helpers';
-import {onGitHubOAuthClicked} from './utils';
+import React, { useContext, useEffect, useState } from 'react';
+import { Link, useNavigate, useSearchParams } from 'react-router-dom';
+import { UserContext } from '../context/User';
+import { API, getLogo, isMobile, showError, showInfo, showSuccess, showWarning } from '../helpers';
+import { onGitHubOAuthClicked } from './utils';
 import Turnstile from "react-turnstile";
-import {Layout, Card, Image, Form, Button, Divider, Modal} from "@douyinfe/semi-ui";
+import { Layout, Card, Image, Form, Button, Divider, Modal } from "@douyinfe/semi-ui";
 import Title from "@douyinfe/semi-ui/lib/es/typography/title";
 import Text from "@douyinfe/semi-ui/lib/es/typography/text";
 
-import {IconGithubLogo} from '@douyinfe/semi-icons';
+import { IconGithubLogo } from '@douyinfe/semi-icons';
 
 const LoginForm = () => {
     const [inputs, setInputs] = useState({
@@ -18,7 +18,7 @@ const LoginForm = () => {
     });
     const [searchParams, setSearchParams] = useSearchParams();
     const [submitted, setSubmitted] = useState(false);
-    const {username, password} = inputs;
+    const { username, password } = inputs;
     const [userState, userDispatch] = useContext(UserContext);
     const [turnstileEnabled, setTurnstileEnabled] = useState(false);
     const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
@@ -56,9 +56,9 @@ const LoginForm = () => {
         const res = await API.get(
             `/api/oauth/wechat?code=${inputs.wechat_verification_code}`
         );
-        const {success, message, data} = res.data;
+        const { success, message, data } = res.data;
         if (success) {
-            userDispatch({type: 'login', payload: data});
+            userDispatch({ type: 'login', payload: data });
             localStorage.setItem('user', JSON.stringify(data));
             navigate('/');
             showSuccess('登录成功!');
@@ -69,7 +69,7 @@ const LoginForm = () => {
     };
 
     function handleChange(name, value) {
-        setInputs((inputs) => ({...inputs, [name]: value}));
+        setInputs((inputs) => ({ ...inputs, [name]: value }));
     }
 
     async function handleSubmit(e) {
@@ -83,13 +83,13 @@ const LoginForm = () => {
                 username,
                 password
             });
-            const {success, message, data} = res.data;
+            const { success, message, data } = res.data;
             if (success) {
-                userDispatch({type: 'login', payload: data});
+                userDispatch({ type: 'login', payload: data });
                 localStorage.setItem('user', JSON.stringify(data));
                 showSuccess('登录成功!');
                 if (username === 'root' && password === '123456') {
-                    Modal.error({title: '您正在使用默认密码!', content: '请立刻修改默认密码!', centered: true});
+                    Modal.error({ title: '您正在使用默认密码!', content: '请立刻修改默认密码!', centered: true });
                 }
                 navigate('/token');
             } else {
@@ -100,16 +100,23 @@ const LoginForm = () => {
         }
     }
 
+    // 添加Telegram登录处理函数
+    const onTelegramLoginClicked = async () => {
+        // 这里调用后端API进行Telegram登录
+        // 例如: const res = await API.get(`/api/oauth/telegram`);
+        // 根据响应处理登录逻辑
+    };
+
     return (
         <div>
             <Layout>
                 <Layout.Header>
                 </Layout.Header>
                 <Layout.Content>
-                    <div style={{justifyContent: 'center', display: "flex", marginTop: 120}}>
-                        <div style={{width: 500}}>
+                    <div style={{ justifyContent: 'center', display: "flex", marginTop: 120 }}>
+                        <div style={{ width: 500 }}>
                             <Card>
-                                <Title heading={2} style={{textAlign: 'center'}}>
+                                <Title heading={2} style={{ textAlign: 'center' }}>
                                     用户登录
                                 </Title>
                                 <Form>
@@ -129,12 +136,12 @@ const LoginForm = () => {
                                         onChange={(value) => handleChange('password', value)}
                                     />
 
-                                    <Button theme='solid' style={{width: '100%'}} type={'primary'} size='large'
-                                            htmlType={'submit'} onClick={handleSubmit}>
+                                    <Button theme='solid' style={{ width: '100%' }} type={'primary'} size='large'
+                                        htmlType={'submit'} onClick={handleSubmit}>
                                         登录
                                     </Button>
                                 </Form>
-                                <div style={{display: 'flex', justifyContent: 'space-between', marginTop: 20}}>
+                                <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 20 }}>
                                     <Text>
                                         没有账号请先 <Link to='/register'>注册账号</Link>
                                     </Text>
@@ -142,16 +149,16 @@ const LoginForm = () => {
                                         忘记密码 <Link to='/reset'>点击重置</Link>
                                     </Text>
                                 </div>
-                                {status.github_oauth || status.wechat_login ? (
+                                {status.github_oauth || status.wechat_login || status.telegram_oauth ? (
                                     <>
                                         <Divider margin='12px' align='center'>
                                             第三方登录
                                         </Divider>
-                                        <div style={{display: 'flex', justifyContent: 'center', marginTop: 20}}>
+                                        <div style={{ display: 'flex', justifyContent: 'center', marginTop: 20 }}>
                                             {status.github_oauth ? (
                                                 <Button
                                                     type='primary'
-                                                    icon={<IconGithubLogo/>}
+                                                    icon={<IconGithubLogo />}
                                                     onClick={() => onGitHubOAuthClicked(status.github_client_id)}
                                                 />
                                             ) : (
@@ -167,6 +174,18 @@ const LoginForm = () => {
                                             {/*) : (*/}
                                             {/*    <></>*/}
                                             {/*)}*/}
+
+                                            {status.telegram_oauth ? (
+                                                <Button
+                                                    type='primary'
+                                                    // icon={<IconTelegram/>} // 假设您有Telegram的图标
+                                                    onClick={onTelegramLoginClicked}
+                                                >
+                                                    Telegram登录
+                                                </Button>
+                                            ) : (
+                                                <></>
+                                            )}
                                         </div>
                                     </>
                                 ) : (
@@ -208,7 +227,7 @@ const LoginForm = () => {
                                 {/*</Modal>*/}
                             </Card>
                             {turnstileEnabled ? (
-                                <div style={{display: 'flex', justifyContent: 'center', marginTop: 20}}>
+                                <div style={{ display: 'flex', justifyContent: 'center', marginTop: 20 }}>
                                     <Turnstile
                                         sitekey={turnstileSiteKey}
                                         onVerify={(token) => {

+ 21 - 0
web/src/components/PersonalSetting.js

@@ -443,6 +443,27 @@ const PersonalSetting = () => {
                                 </div>
                             </div>
 
+                            <div style={{marginTop: 10}}>
+                                <Typography.Text strong>Telegram</Typography.Text>
+                                <div style={{display: 'flex', justifyContent: 'space-between'}}>
+                                    <div>
+                                        <Input
+                                            value={userState.user && userState.user.telegram_id !== ''?userState.user.telegram_id:'未绑定'}
+                                            readonly={true}
+                                        ></Input>
+                                    </div>
+                                    <div>
+                                        <Button
+                                            disabled={(userState.user && userState.user.telegram_id !== '') || !status.telegram_oauth}
+                                        >
+                                            {
+                                                status.github_oauth?'绑定':'未启用'
+                                            }
+                                        </Button>
+                                    </div>
+                                </div>
+                            </div>
+
                             <div style={{marginTop: 10}}>
                                 <Space>
                                     <Button onClick={generateAccessToken}>生成系统访问令牌</Button>

+ 9 - 11
web/src/components/SystemSetting.js

@@ -34,7 +34,7 @@ const SystemSetting = () => {
         EmailDomainRestrictionEnabled: '',
         EmailDomainWhitelist: '',
         // telegram login
-        TelegramLoginEnabled: '',
+        TelegramOAuthEnabled: '',
         TelegramBotToken: '',
         TelegramBotName: '',
     });
@@ -81,7 +81,7 @@ const SystemSetting = () => {
             case 'EmailVerificationEnabled':
             case 'GitHubOAuthEnabled':
             case 'WeChatAuthEnabled':
-            case 'TelegramLoginEnabled':
+            case 'TelegramOAuthEnabled':
             case 'TurnstileCheckEnabled':
             case 'EmailDomainRestrictionEnabled':
             case 'RegisterEnabled':
@@ -240,7 +240,7 @@ const SystemSetting = () => {
     };
 
     const submitTelegramSettings = async () => {
-        await updateOption('TelegramLoginEnabled', inputs.TelegramLoginEnabled);
+        await updateOption('TelegramOAuthEnabled', inputs.TelegramOAuthEnabled);
         await updateOption('TelegramBotToken', inputs.TelegramBotToken);
         await updateOption('TelegramBotName', inputs.TelegramBotName);
     };
@@ -397,6 +397,12 @@ const SystemSetting = () => {
                             name='WeChatAuthEnabled'
                             onChange={handleInputChange}
                         />
+                        <Form.Checkbox
+                            checked={inputs.TelegramOAuthEnabled === 'true'}
+                            label='允许通过 Telegram 进行登录'
+                            name='TelegramOAuthEnabled'
+                            onChange={handleInputChange}
+                        />
                     </Form.Group>
                     <Form.Group inline>
                         <Form.Checkbox
@@ -596,25 +602,17 @@ const SystemSetting = () => {
                     <Divider />
                     <Header as='h3'>配置 Telegram 登录</Header>
                     <Form.Group inline>
-                        <Form.Checkbox
-                            checked={inputs.TelegramLoginEnabled === 'true'}
-                            label='允许通过 Telegram 进行登录'
-                            name='TelegramLoginEnabled'
-                            onChange={handleInputChange}
-                        />
                         <Form.Input
                             label='Telegram Bot Token'
                             name='TelegramBotToken'
                             value={inputs.TelegramBotToken}
                             placeholder='输入你的 Telegram Bot Token'
-                            onChange={handleInputChange}
                         />
                         <Form.Input
                             label='Telegram Bot 名称'
                             name='TelegramBotName'
                             value={inputs.TelegramBotName}
                             placeholder='输入你的 Telegram Bot 名称'
-                            onChange={handleInputChange}
                         />
                     </Form.Group>
                     <Form.Button onClick={submitTelegramSettings}>

+ 127 - 121
web/src/pages/User/EditUser.js

@@ -1,9 +1,9 @@
 import React, { useEffect, useState } from 'react';
 import { useParams, useNavigate } from 'react-router-dom';
-import {API, isMobile, showError, showSuccess} from '../../helpers';
+import { API, isMobile, showError, showSuccess } from '../../helpers';
 import { renderQuota, renderQuotaWithPrompt } from '../../helpers/render';
 import Title from "@douyinfe/semi-ui/lib/es/typography/title";
-import {SideSheet, Space, Button, Spin, Input, Typography, Select, Divider} from "@douyinfe/semi-ui";
+import { SideSheet, Space, Button, Spin, Input, Typography, Select, Divider } from "@douyinfe/semi-ui";
 
 const EditUser = (props) => {
   const userId = props.editingUser.id;
@@ -19,8 +19,8 @@ const EditUser = (props) => {
     group: 'default'
   });
   const [groupOptions, setGroupOptions] = useState([]);
-  const { username, display_name, password, github_id, wechat_id, email, quota, group } =
-      inputs;
+  const { username, display_name, password, github_id, wechat_id, telegram_id, email, quota, group } =
+    inputs;
   const handleInputChange = (name, value) => {
     setInputs((inputs) => ({ ...inputs, [name]: value }));
   };
@@ -88,126 +88,132 @@ const EditUser = (props) => {
   };
 
   return (
-      <>
-        <SideSheet
-            placement={'right'}
-            title={<Title level={3}>{'编辑用户'}</Title>}
-            headerStyle={{borderBottom: '1px solid var(--semi-color-border)'}}
-            bodyStyle={{borderBottom: '1px solid var(--semi-color-border)'}}
-            visible={props.visible}
-            footer={
-              <div style={{display: 'flex', justifyContent: 'flex-end'}}>
-                <Space>
-                  <Button theme='solid' size={'large'} onClick={submit}>提交</Button>
-                  <Button theme='solid' size={'large'} type={'tertiary'} onClick={handleCancel}>取消</Button>
-                </Space>
+    <>
+      <SideSheet
+        placement={'right'}
+        title={<Title level={3}>{'编辑用户'}</Title>}
+        headerStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
+        bodyStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
+        visible={props.visible}
+        footer={
+          <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
+            <Space>
+              <Button theme='solid' size={'large'} onClick={submit}>提交</Button>
+              <Button theme='solid' size={'large'} type={'tertiary'} onClick={handleCancel}>取消</Button>
+            </Space>
+          </div>
+        }
+        closeIcon={null}
+        onCancel={() => handleCancel()}
+        width={isMobile() ? '100%' : 600}
+      >
+        <Spin spinning={loading}>
+          <div style={{ marginTop: 20 }}>
+            <Typography.Text>用户名</Typography.Text>
+          </div>
+          <Input
+            label='用户名'
+            name='username'
+            placeholder={'请输入新的用户名'}
+            onChange={value => handleInputChange('username', value)}
+            value={username}
+            autoComplete='new-password'
+          />
+          <div style={{ marginTop: 20 }}>
+            <Typography.Text>密码</Typography.Text>
+          </div>
+          <Input
+            label='密码'
+            name='password'
+            type={'password'}
+            placeholder={'请输入新的密码,最短 8 位'}
+            onChange={value => handleInputChange('password', value)}
+            value={password}
+            autoComplete='new-password'
+          />
+          <div style={{ marginTop: 20 }}>
+            <Typography.Text>显示名称</Typography.Text>
+          </div>
+          <Input
+            label='显示名称'
+            name='display_name'
+            placeholder={'请输入新的显示名称'}
+            onChange={value => handleInputChange('display_name', value)}
+            value={display_name}
+            autoComplete='new-password'
+          />
+          {
+            userId && <>
+              <div style={{ marginTop: 20 }}>
+                <Typography.Text>分组</Typography.Text>
               </div>
-            }
-            closeIcon={null}
-            onCancel={() => handleCancel()}
-            width={isMobile() ? '100%' : 600}
-        >
-          <Spin spinning={loading}>
-            <div style={{marginTop: 20}}>
-              <Typography.Text>用户名</Typography.Text>
-            </div>
-            <Input
-                label='用户名'
-                name='username'
-                placeholder={'请输入新的用户名'}
-                onChange={value => handleInputChange('username', value)}
-                value={username}
+              <Select
+                placeholder={'请选择分组'}
+                name='group'
+                fluid
+                search
+                selection
+                allowAdditions
+                additionLabel={'请在系统设置页面编辑分组倍率以添加新的分组:'}
+                onChange={value => handleInputChange('group', value)}
+                value={inputs.group}
                 autoComplete='new-password'
-            />
-            <div style={{marginTop: 20}}>
-              <Typography.Text>密码</Typography.Text>
-            </div>
-            <Input
-                label='密码'
-                name='password'
-                type={'password'}
-                placeholder={'请输入新的密码,最短 8 位'}
-                onChange={value => handleInputChange('password', value)}
-                value={password}
-                autoComplete='new-password'
-            />
-            <div style={{marginTop: 20}}>
-              <Typography.Text>显示名称</Typography.Text>
-            </div>
-            <Input
-                label='显示名称'
-                name='display_name'
-                placeholder={'请输入新的显示名称'}
-                onChange={value => handleInputChange('display_name', value)}
-                value={display_name}
-                autoComplete='new-password'
-            />
-            {
-                userId && <>
-                  <div style={{marginTop: 20}}>
-                    <Typography.Text>分组</Typography.Text>
-                  </div>
-                  <Select
-                      placeholder={'请选择分组'}
-                      name='group'
-                      fluid
-                      search
-                      selection
-                      allowAdditions
-                      additionLabel={'请在系统设置页面编辑分组倍率以添加新的分组:'}
-                      onChange={value => handleInputChange('group', value)}
-                      value={inputs.group}
-                      autoComplete='new-password'
-                      optionList={groupOptions}
-                  />
-                  <div style={{marginTop: 20}}>
-                    <Typography.Text>{`剩余额度${renderQuotaWithPrompt(quota)}`}</Typography.Text>
-                  </div>
-                  <Input
-                      name='quota'
-                      placeholder={'请输入新的剩余额度'}
-                      onChange={value => handleInputChange('quota', value)}
-                      value={quota}
-                      type={'number'}
-                      autoComplete='new-password'
-                  />
-                </>
-            }
-            <Divider style={{marginTop: 20}}>以下信息不可修改</Divider>
-            <div style={{marginTop: 20}}>
-              <Typography.Text>已绑定的 GitHub 账户</Typography.Text>
-            </div>
-            <Input
-                name='github_id'
-                value={github_id}
-                autoComplete='new-password'
-                placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
-                readonly
-            />
-            <div style={{marginTop: 20}}>
-              <Typography.Text>已绑定的微信账户</Typography.Text>
-            </div>
-            <Input
-                name='wechat_id'
-                value={wechat_id}
-                autoComplete='new-password'
-                placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
-                readonly
-            />
-            <div style={{marginTop: 20}}>
-              <Typography.Text>已绑定的邮箱账户</Typography.Text>
-            </div>
-            <Input
-                name='email'
-                value={email}
+                optionList={groupOptions}
+              />
+              <div style={{ marginTop: 20 }}>
+                <Typography.Text>{`剩余额度${renderQuotaWithPrompt(quota)}`}</Typography.Text>
+              </div>
+              <Input
+                name='quota'
+                placeholder={'请输入新的剩余额度'}
+                onChange={value => handleInputChange('quota', value)}
+                value={quota}
+                type={'number'}
                 autoComplete='new-password'
-                placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
-                readonly
-            />
-          </Spin>
-
-        </SideSheet>
-      </>
+              />
+            </>
+          }
+          <Divider style={{ marginTop: 20 }}>以下信息不可修改</Divider>
+          <div style={{ marginTop: 20 }}>
+            <Typography.Text>已绑定的 GitHub 账户</Typography.Text>
+          </div>
+          <Input
+            name='github_id'
+            value={github_id}
+            autoComplete='new-password'
+            placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
+            readonly
+          />
+          <div style={{ marginTop: 20 }}>
+            <Typography.Text>已绑定的微信账户</Typography.Text>
+          </div>
+          <Input
+            name='wechat_id'
+            value={wechat_id}
+            autoComplete='new-password'
+            placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
+            readonly
+          />
+          <Input
+            name='telegram_id'
+            value={telegram_id}
+            autoComplete='new-password'
+            placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
+            readonly
+          />
+          <div style={{ marginTop: 20 }}>
+            <Typography.Text>已绑定的邮箱账户</Typography.Text>
+          </div>
+          <Input
+            name='email'
+            value={email}
+            autoComplete='new-password'
+            placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
+            readonly
+          />
+        </Spin>
+      </SideSheet>
+    </>
   );
 };