Teammasik

pythonDev/lr12

Dec 17th, 2025
77
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 14.83 KB | None | 0 0
  1. # main.py
  2. from flask import Flask
  3. from views import api
  4. from db import get_db_connection
  5.  
  6. def create_app():
  7.     app = Flask(__name__)
  8.     app.register_blueprint(api, url_prefix='/')
  9.     get_db_connection()
  10.     return app
  11.  
  12. if __name__ == '__main__':
  13.     app = create_app()
  14.     app.run(host='127.0.0.1', port=5000, debug=True)
  15.  
  16. #--------------------------------------------------------------------------------------------------
  17. #db.py
  18. import pymysql
  19. import os
  20. from werkzeug.security import generate_password_hash
  21.  
  22. DB_HOST = '127.0.0.1'
  23. DB_USER = 'root'
  24. DB_PASSWORD = ''
  25. DB_NAME = 'taskdb'
  26. SECRET_KEY = os.getenv('SECRET_KEY', 'super-secret-dev-key-change-in-prod')
  27. TOKEN_EXPIRATION_HOURS = 1
  28. _connection = None
  29.  
  30. def get_db_connection():
  31.     global _connection
  32.     if _connection is None or not _connection.open:
  33.         _connection = pymysql.connect(
  34.             host=DB_HOST,
  35.             user=DB_USER,
  36.             password=DB_PASSWORD,
  37.             charset='utf8mb4',
  38.             cursorclass=pymysql.cursors.DictCursor,
  39.             autocommit=False
  40.         )
  41.         _init_database()
  42.     return _connection
  43.  
  44.  
  45. def _init_database():
  46.     """Создаёт базу и таблицы, если не существуют."""
  47.     conn = _connection
  48.     try:
  49.         with conn.cursor() as cursor:
  50.             cursor.execute(f"CREATE DATABASE IF NOT EXISTS `{DB_NAME}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
  51.             print(f"База данных '{DB_NAME}' готова.")
  52.             conn.select_db(DB_NAME)
  53.  
  54.             cursor.execute("""
  55.                CREATE TABLE IF NOT EXISTS users (
  56.                    id INT AUTO_INCREMENT PRIMARY KEY,
  57.                    username VARCHAR(50) NOT NULL UNIQUE,
  58.                    password_hash VARCHAR(255) NOT NULL,
  59.                    is_admin TINYINT(1) NOT NULL DEFAULT 0,
  60.                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  61.                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
  62.            """)
  63.             print("Таблица 'users' готова.")
  64.  
  65.             cursor.execute("""
  66.                CREATE TABLE IF NOT EXISTS tasks (
  67.                    id INT AUTO_INCREMENT PRIMARY KEY,
  68.                    user_id INT NOT NULL,
  69.                    title VARCHAR(255) NOT NULL,
  70.                    description TEXT,
  71.                    status ENUM('pending', 'done') NOT NULL DEFAULT 'pending',
  72.                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  73.                    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
  74.                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
  75.            """)
  76.             print("Таблица 'tasks' готова.")
  77.  
  78.             conn.commit()
  79.     except Exception as e:
  80.         print(f"Ошибка инициализации БД: {e}")
  81.         conn.rollback()
  82.         raise
  83.  
  84. _blacklisted_tokens = set()
  85.  
  86. def blacklist_token(token):
  87.     _blacklisted_tokens.add(token)
  88.  
  89. def is_token_blacklisted(token):
  90.     return token in _blacklisted_tokens
  91.  
  92. def create_user(username, password, is_admin=False):
  93.     conn = get_db_connection()
  94.     with conn.cursor() as cursor:
  95.         cursor.execute("SELECT id FROM users WHERE username = %s", (username,))
  96.         if cursor.fetchone():
  97.             return None
  98.         password_hash = generate_password_hash(password)
  99.         cursor.execute(
  100.             "INSERT INTO users (username, password_hash, is_admin) VALUES (%s, %s, %s)",
  101.             (username, password_hash, int(is_admin))
  102.         )
  103.         conn.commit()
  104.         return cursor.lastrowid
  105.  
  106.  
  107. def get_user_by_username(username):
  108.     conn = get_db_connection()
  109.     with conn.cursor() as cursor:
  110.         cursor.execute("SELECT id, username, password_hash, is_admin FROM users WHERE username = %s", (username,))
  111.         return cursor.fetchone()
  112.  
  113.  
  114. def get_user_by_id(user_id):
  115.     conn = get_db_connection()
  116.     with conn.cursor() as cursor:
  117.         cursor.execute("SELECT id, username, password_hash, is_admin FROM users WHERE id = %s", (user_id,))
  118.         return cursor.fetchone()
  119.  
  120.  
  121. def update_password(user_id, new_password):
  122.     conn = get_db_connection()
  123.     with conn.cursor() as cursor:
  124.         password_hash = generate_password_hash(new_password)
  125.         cursor.execute("UPDATE users SET password_hash = %s WHERE id = %s", (password_hash, user_id))
  126.         conn.commit()
  127.         return cursor.rowcount > 0
  128.  
  129.  
  130. def create_task(user_id, title, description=None, status='pending'):
  131.     conn = get_db_connection()
  132.     with conn.cursor() as cursor:
  133.         cursor.execute(
  134.             "INSERT INTO tasks (user_id, title, description, status) VALUES (%s, %s, %s, %s)",
  135.             (user_id, title.strip(), description.strip() if description else None, status)
  136.         )
  137.         conn.commit()
  138.         return cursor.lastrowid
  139.  
  140.  
  141. def get_tasks_for_user(user_id, is_admin=False):
  142.     conn = get_db_connection()
  143.     with conn.cursor() as cursor:
  144.         if is_admin:
  145.             cursor.execute("SELECT * FROM tasks")
  146.         else:
  147.             cursor.execute("SELECT * FROM tasks WHERE user_id = %s", (user_id,))
  148.         return cursor.fetchall()
  149.  
  150.  
  151. def get_task_by_id(task_id, user_id, is_admin=False):
  152.     conn = get_db_connection()
  153.     with conn.cursor() as cursor:
  154.         if is_admin:
  155.             cursor.execute("SELECT * FROM tasks WHERE id = %s", (task_id,))
  156.         else:
  157.             cursor.execute("SELECT * FROM tasks WHERE id = %s AND user_id = %s", (task_id, user_id))
  158.         return cursor.fetchone()
  159.  
  160.  
  161. def update_task(task_id, user_id, is_admin, title=None, description=None, status=None):
  162.     conn = get_db_connection()
  163.     with conn.cursor() as cursor:
  164.         task = get_task_by_id(task_id, user_id, is_admin)
  165.         if not task:
  166.             return None
  167.  
  168.         updates = []
  169.         params = []
  170.         if title is not None:
  171.             updates.append("title = %s")
  172.             params.append(title.strip())
  173.         if description is not None:
  174.             updates.append("description = %s")
  175.             params.append(description.strip() if description else None)
  176.         if status is not None:
  177.             if status not in ('pending', 'done'):
  178.                 return None
  179.             updates.append("status = %s")
  180.             params.append(status)
  181.  
  182.         if not updates:
  183.             return task
  184.  
  185.         params.append(task_id)
  186.         if is_admin:
  187.             sql = f"UPDATE tasks SET {', '.join(updates)} WHERE id = %s"
  188.         else:
  189.             sql = f"UPDATE tasks SET {', '.join(updates)} WHERE id = %s AND user_id = %s"
  190.             params.append(user_id)
  191.  
  192.         cursor.execute(sql, params)
  193.         conn.commit()
  194.         return get_task_by_id(task_id, user_id, is_admin)
  195.  
  196.  
  197. def delete_task(task_id, user_id, is_admin=False):
  198.     conn = get_db_connection()
  199.     with conn.cursor() as cursor:
  200.         if is_admin:
  201.             cursor.execute("DELETE FROM tasks WHERE id = %s", (task_id,))
  202.         else:
  203.             cursor.execute("DELETE FROM tasks WHERE id = %s AND user_id = %s", (task_id, user_id))
  204.         conn.commit()
  205.         return cursor.rowcount > 0
  206.  
  207.  
  208. def get_tasks_by_status(user_id, status, is_admin=False):
  209.     conn = get_db_connection()
  210.     with conn.cursor() as cursor:
  211.         if is_admin:
  212.             cursor.execute("SELECT * FROM tasks WHERE status = %s", (status,))
  213.         else:
  214.             cursor.execute("SELECT * FROM tasks WHERE user_id = %s AND status = %s", (user_id, status))
  215.         return cursor.fetchall()
  216.  
  217. #--------------------------------------------------------------------------------------------------
  218. #auth.py
  219. from functools import wraps
  220. from flask import request, jsonify
  221. import jwt
  222. from db import get_user_by_id, is_token_blacklisted, SECRET_KEY
  223.  
  224. def token_required(f):
  225.     @wraps(f)
  226.     def decorated(*args, **kwargs):
  227.         token = None
  228.  
  229.         if 'Authorization' in request.headers:
  230.             parts = request.headers['Authorization'].split()
  231.             if len(parts) == 2 and parts[0] == "Bearer":
  232.                 token = parts[1]
  233.  
  234.         if not token:
  235.             return jsonify({'error': 'Токен отсутствует'}), 401
  236.  
  237.         if is_token_blacklisted(token):
  238.             return jsonify({'error': 'Токен отозван'}), 401
  239.  
  240.         try:
  241.             data = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
  242.             user_id = data.get('user_id')
  243.             if not user_id:
  244.                 raise jwt.InvalidTokenError("Нет user_id в токене")
  245.  
  246.             current_user = get_user_by_id(user_id)
  247.             if not current_user:
  248.                 raise jwt.InvalidTokenError("Пользователь не найден")
  249.  
  250.         except jwt.ExpiredSignatureError:
  251.             return jsonify({'error': 'Токен истёк'}), 401
  252.         except jwt.InvalidTokenError as e:
  253.             return jsonify({'error': f'Недействительный токен: {str(e)}'}), 401
  254.  
  255.         return f(current_user=current_user, *args, **kwargs)
  256.  
  257.     return decorated
  258.  
  259. #--------------------------------------------------------------------------------------------------
  260. #views.py
  261. from flask import Blueprint, request, jsonify
  262. from werkzeug.security import check_password_hash
  263. from db import (
  264.     create_user, get_user_by_username, get_tasks_for_user, create_task,
  265.     get_task_by_id, update_task, delete_task, get_tasks_by_status,
  266.     update_password, SECRET_KEY, TOKEN_EXPIRATION_HOURS
  267. )
  268. from auth import token_required
  269. import jwt
  270. import re
  271. from datetime import datetime, timedelta
  272.  
  273. api = Blueprint('api', __name__)
  274.  
  275.  
  276. @api.route('/register', methods=['POST'])
  277. def register():
  278.     data = request.get_json()
  279.     if not data or not data.get('username') or not data.get('password'):
  280.         return jsonify({'error': 'Требуются username и password'}), 400
  281.  
  282.     username = data['username'].strip()
  283.     password = data['password']
  284.     is_admin = bool(data.get('is_admin', False))
  285.  
  286.     if not re.match(r"^[a-zA-Z0-9_]{3,20}$", username):
  287.         return jsonify({'error': 'Имя: 3–20 символов, буквы, цифры, _'}), 400
  288.     if get_user_by_username(username):
  289.         return jsonify({'error': 'Пользователь уже существует'}), 409
  290.  
  291.     user_id = create_user(username, password, is_admin)
  292.     if not user_id:
  293.         return jsonify({'error': 'Ошибка создания'}), 500
  294.  
  295.     return jsonify({'message': 'Пользователь создан', 'user_id': user_id}), 201
  296.  
  297.  
  298. @api.route('/login', methods=['POST'])
  299. def login():
  300.     data = request.get_json()
  301.     if not data or not data.get('username') or not data.get('password'):
  302.         return jsonify({'error': 'Требуются username и password'}), 400
  303.  
  304.     user = get_user_by_username(data['username'])
  305.     if not user or not check_password_hash(user['password_hash'], data['password']):
  306.         return jsonify({'error': 'Неверные данные'}), 401
  307.  
  308.     payload = {
  309.         'user_id': user['id'],
  310.         'exp': datetime.now() + timedelta(hours=TOKEN_EXPIRATION_HOURS)
  311.     }
  312.     token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
  313.  
  314.     return jsonify({
  315.         'token': token,
  316.         'user': {
  317.             'id': user['id'],
  318.             'username': user['username'],
  319.             'is_admin': bool(user['is_admin'])
  320.         }
  321.     })
  322.  
  323.  
  324. @api.route('/change-password', methods=['POST'])
  325. @token_required
  326. def change_password(current_user):
  327.     data = request.get_json()
  328.     old = data.get('old_password')
  329.     new = data.get('new_password')
  330.     if not old or not new:
  331.         return jsonify({'error': 'Требуются old_password и new_password'}), 400
  332.     if len(new) < 6:
  333.         return jsonify({'error': 'Новый пароль ≥ 6 символов'}), 400
  334.     if not check_password_hash(current_user['password_hash'], old):
  335.         return jsonify({'error': 'Неверный текущий пароль'}), 401
  336.  
  337.     if update_password(current_user['id'], new):
  338.         return jsonify({'message': 'Пароль изменён'})
  339.     return jsonify({'error': 'Ошибка изменения пароля'}), 500
  340.  
  341.  
  342. @api.route('/logout', methods=['POST'])
  343. @token_required
  344. def logout(current_user):
  345.     auth = request.headers.get('Authorization')
  346.     token = auth.split(' ')[1]
  347.     from db import blacklist_token
  348.     blacklist_token(token)
  349.     return jsonify({'message': 'Вы успешно вышли из системы. Токен отозван.'})
  350.  
  351.  
  352. @api.route('/tasks', methods=['GET'])
  353. @token_required
  354. def list_tasks(current_user):
  355.     status = request.args.get('status')
  356.     if status:
  357.         if status not in ('pending', 'done'):
  358.             return jsonify({'error': 'Недопустимый статус'}), 400
  359.         tasks = get_tasks_by_status(current_user['id'], status, current_user['is_admin'])
  360.     else:
  361.         tasks = get_tasks_for_user(current_user['id'], current_user['is_admin'])
  362.     return jsonify(tasks)
  363.  
  364.  
  365. @api.route('/tasks', methods=['POST'])
  366. @token_required
  367. def create_task_route(current_user):
  368.     data = request.get_json()
  369.     if not data or 'title' not in data or not data['title'].strip():
  370.         return jsonify({'error': 'Поле "title" обязательно'}), 400
  371.  
  372.     title = data['title']
  373.     description = data.get('description')
  374.     status = data.get('status', 'pending')
  375.     if status not in ('pending', 'done'):
  376.         return jsonify({'error': 'Недопустимый статус'}), 400
  377.  
  378.     task_id = create_task(current_user['id'], title, description, status)
  379.     task = get_task_by_id(task_id, current_user['id'], current_user['is_admin'])
  380.     return jsonify(task), 201
  381.  
  382.  
  383. @api.route('/tasks/<int:task_id>', methods=['GET'])
  384. @token_required
  385. def get_task(current_user, task_id):
  386.     task = get_task_by_id(task_id, current_user['id'], current_user['is_admin'])
  387.     if not task:
  388.         return jsonify({'error': 'Задача не найдена'}), 404
  389.     return jsonify(task)
  390.  
  391.  
  392. @api.route('/tasks/<int:task_id>', methods=['PUT'])
  393. @token_required
  394. def update_task_route(current_user, task_id):
  395.     data = request.get_json()
  396.     if not data:
  397.         return jsonify({'error': 'Тело запроса пустое'}), 400
  398.  
  399.     title = data.get('title')
  400.     description = data.get('description')
  401.     status = data.get('status')
  402.  
  403.     updated = update_task(task_id, current_user['id'], current_user['is_admin'], title, description, status)
  404.     if not updated:
  405.         return jsonify({'error': 'Задача не найдена или недоступна'}), 404
  406.     return jsonify(updated)
  407.  
  408.  
  409. @api.route('/tasks/<int:task_id>', methods=['DELETE'])
  410. @token_required
  411. def delete_task_route(current_user, task_id):
  412.     success = delete_task(task_id, current_user['id'], current_user['is_admin'])
  413.     if not success:
  414.         return jsonify({'error': 'Задача не найдена или недоступна'}), 404
  415.     return jsonify({'message': 'Задача удалена'})
  416.  
  417.  
Advertisement
Add Comment
Please, Sign In to add comment