From a03c5229f34a2a24598ae3cc3054aa2e85d69fa9 Mon Sep 17 00:00:00 2001 From: isekaijoucyo Date: Sat, 8 Feb 2025 08:33:13 +0800 Subject: [PATCH] layout v0.0.1 --- db_scripts/test.py | 8 ++ src/backend/app.py | 35 +++++- .../initialize_tools/jwt_secret_gen.py | 5 + src/backend/initialize_tools/jwt_token_gen.py | 35 ++++++ src/frontend/package-lock.json | 43 +++++++ src/frontend/package.json | 11 +- src/frontend/public/todo-icon.png | Bin 0 -> 5117 bytes src/frontend/src/app/components/TodoForm.tsx | 100 +++++++++++++++++ src/frontend/src/app/components/TodoItem.tsx | 55 +++++++++ src/frontend/src/app/components/TodoList.tsx | 45 ++++++++ src/frontend/src/app/page.tsx | 106 +++++------------- src/frontend/src/app/types/todo.ts | 13 +++ src/frontend/src/app/utils/api.ts | 46 ++++++++ 13 files changed, 415 insertions(+), 87 deletions(-) create mode 100644 db_scripts/test.py create mode 100644 src/backend/initialize_tools/jwt_secret_gen.py create mode 100644 src/backend/initialize_tools/jwt_token_gen.py create mode 100644 src/frontend/public/todo-icon.png create mode 100644 src/frontend/src/app/components/TodoForm.tsx create mode 100644 src/frontend/src/app/components/TodoItem.tsx create mode 100644 src/frontend/src/app/components/TodoList.tsx create mode 100644 src/frontend/src/app/types/todo.ts create mode 100644 src/frontend/src/app/utils/api.ts diff --git a/db_scripts/test.py b/db_scripts/test.py new file mode 100644 index 0000000..0696c2d --- /dev/null +++ b/db_scripts/test.py @@ -0,0 +1,8 @@ +import os +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() + +backup_dir = os.getenv('BACKUP_DIR', '/default/backup/path') +print(backup_dir) \ No newline at end of file diff --git a/src/backend/app.py b/src/backend/app.py index f9ff30c..a543983 100644 --- a/src/backend/app.py +++ b/src/backend/app.py @@ -5,19 +5,30 @@ from bson.objectid import ObjectId from bson.json_util import dumps from datetime import datetime from bson import errors as bson_errors +import os import uuid +import jwt +from dotenv import load_dotenv + +# Load environment variables before initializing app +load_dotenv() app = Flask(__name__) cors = CORS(app, resources={ r"/api/*": { "origins": ["http://localhost:3000", "http://localhost:3001"], "methods": ["GET", "POST", "DELETE", "OPTIONS"], - "allow_headers": ["Content-Type"] + "allow_headers": ["Content-Type", "Authorization"] # Allow Authorization header } }) app.config['CORS_HEADERS'] = 'Content-Type' +# Load JWT secret key from environment variable +SECRET_KEY = os.getenv('JWT_SECRET_KEY', 'default_secret_key') +TOKEN_LV1 = os.getenv('JWT_TOKEN_LV1', 'default_token_lv1') +TOKEN_LV2 = os.getenv('JWT_TOKEN_LV2', 'default_token_lv2') + client = MongoClient('localhost', 27017) db = client.flask_db @@ -26,6 +37,20 @@ todos = db.todos # 确保UUID字段的唯一性 todos.create_index('uuid', unique=True) +def check_permission(token, required_level): + print(f"SECRET: {SECRET_KEY}") + print(f"TOKEN1: {TOKEN_LV1}") + print(f"TOKEN2: {TOKEN_LV2}") + print(f"TOKENIN: {token}") + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256']) + print(f"PAYLOAD: {payload}") + return payload.get('level', 0) >= required_level + except jwt.ExpiredSignatureError: + return False + except jwt.InvalidTokenError: + return False + @app.route('/api/todos', methods=['GET']) @cross_origin() def get_todos(): @@ -35,6 +60,10 @@ def get_todos(): @app.route('/api/todos', methods=['POST']) @cross_origin() def create_todo(): + token = request.headers.get('Authorization', '').split(' ')[-1] + if not check_permission(token, 1): + return jsonify({'error': 'Unauthorized'}), 403 + content = request.json.get('content') degree = request.json.get('degree') timestamp = datetime.now() @@ -50,6 +79,10 @@ def create_todo(): @app.route('/api/todos/', methods=['DELETE']) @cross_origin() def delete_todo(uuid): + token = request.headers.get('Authorization', '').split(' ')[-1] + if not check_permission(token, 2): + return jsonify({'error': 'Unauthorized'}), 403 + result = todos.delete_one({"uuid": uuid}) if result.deleted_count > 0: return '', 204 diff --git a/src/backend/initialize_tools/jwt_secret_gen.py b/src/backend/initialize_tools/jwt_secret_gen.py new file mode 100644 index 0000000..3900a77 --- /dev/null +++ b/src/backend/initialize_tools/jwt_secret_gen.py @@ -0,0 +1,5 @@ +import secrets + +# Generate a random secret key for JWT +jwt_secret_key = secrets.token_urlsafe(32) +print(jwt_secret_key) \ No newline at end of file diff --git a/src/backend/initialize_tools/jwt_token_gen.py b/src/backend/initialize_tools/jwt_token_gen.py new file mode 100644 index 0000000..347cb7a --- /dev/null +++ b/src/backend/initialize_tools/jwt_token_gen.py @@ -0,0 +1,35 @@ +import jwt +import os +from datetime import datetime, timedelta +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() + +# 加载密钥 +jwt_secret_key = os.getenv('JWT_SECRET_KEY', '') + +# 生成 JWT 的函数 +def generate_token(level): + payload = { + 'level': level, # 权限等级 + 'exp': datetime.utcnow() + timedelta(days=365 * 10 + 3) # 令牌过期时间 + } + token = jwt.encode(payload, jwt_secret_key, algorithm='HS256') + return token + +def generate_static_token(): + payload = { + 'level': 1, # 权限等级 + 'exp': 1024 # 令牌过期时间 + } + token = jwt.encode(payload, jwt_secret_key, algorithm='HS256') + return token + +# 生成不同权限等级的令牌 +token_level_1 = generate_token(1) +token_level_2 = generate_token(2) + +print("Token for level 1:", token_level_1) +print("Token for level 2:", token_level_2) +print("Static Token:", generate_static_token()) \ No newline at end of file diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index f2fa478..97fec4c 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "axios": "^1.6.2", + "framer-motion": "^12.4.0", "next": "14.0.4", "react": "^18.2.0", "react-dom": "^18.2.0" @@ -872,6 +873,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.4.0.tgz", + "integrity": "sha512-QX92ThRniev3YTkjXOV/7m9fXBRQ5xDDRWDinuU//Xkjh+q9ppg3Nb0b95xgJYd2AE0rgJ7eoihY+Z+jjSv22w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.0.0", + "motion-utils": "^12.0.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1177,6 +1205,21 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/motion-dom": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.0.0.tgz", + "integrity": "sha512-CvYd15OeIR6kHgMdonCc1ihsaUG4MYh/wrkz8gZ3hBX/uamyZCXN9S9qJoYF03GqfTt7thTV/dxnHYX4+55vDg==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.0.0" + } + }, + "node_modules/motion-utils": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.0.0.tgz", + "integrity": "sha512-MNFiBKbbqnmvOjkPyOKgHUp3Q6oiokLkI1bEwm5QA28cxMZrv0CbbBGDNmhF6DIXsi1pCQBSs0dX8xjeER1tmA==", + "license": "MIT" + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index c39fd7a..d520923 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -10,18 +10,19 @@ "lint": "next lint" }, "dependencies": { + "axios": "^1.6.2", + "framer-motion": "^12.4.0", "next": "14.0.4", "react": "^18.2.0", - "react-dom": "^18.2.0", - "axios": "^1.6.2" + "react-dom": "^18.2.0" }, "devDependencies": { "@types/node": "^20.10.4", "@types/react": "^18.2.45", "@types/react-dom": "^18.2.17", - "typescript": "^5.3.3", "autoprefixer": "^10.4.16", "postcss": "^8.4.32", - "tailwindcss": "^3.3.6" + "tailwindcss": "^3.3.6", + "typescript": "^5.3.3" } -} \ No newline at end of file +} diff --git a/src/frontend/public/todo-icon.png b/src/frontend/public/todo-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c8e0beff3da99d2c5e43a4398d575e12e095c86e GIT binary patch literal 5117 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 void; +}; + +export default function TodoForm({ onTodoAdded }: TodoFormProps) { + const [content, setContent] = useState(''); + const [degree, setDegree] = useState<'Important' | 'Unimportant'>('Important'); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!content.trim()) return; + + try { + await TodoAPI.create({ content, degree }); + setContent(''); + setDegree('Important'); + onTodoAdded(); + } catch (error) { + console.error('Error creating todo:', error); + } + }; + + return ( + +
+ + setContent(e.target.value)} + placeholder="输入待办事项..." + className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" + required + /> +
+ +
+

重要程度

+
+