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 0000000..c8e0bef Binary files /dev/null and b/src/frontend/public/todo-icon.png differ diff --git a/src/frontend/src/app/components/TodoForm.tsx b/src/frontend/src/app/components/TodoForm.tsx new file mode 100644 index 0000000..817a634 --- /dev/null +++ b/src/frontend/src/app/components/TodoForm.tsx @@ -0,0 +1,100 @@ +'use client'; + +import { useState } from 'react'; +import { motion } from 'framer-motion'; +import { TodoAPI } from '../utils/api'; + +type TodoFormProps = { + onTodoAdded: () => 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 + /> +
+ +
+

重要程度

+
+