Project structure:

LitExam/
├── auth/
│   ├── __init__.py
│   └── auth.py
├── database/
│   ├── __init__.py
│   ├── db.py
│   └── models.py
├── routes/
│   ├── __init__.py
│   ├── exams.py
│   └── users.py
├── app.py
├── database.db
├── main.py
└── requirements.txt


Project files:

app.py
```py
# app.py

import asyncio
from flask import Flask, jsonify, send_from_directory, render_template
from flask import session
from routes import register_routes
from database import db
from auth import Auth
import os

app = Flask(__name__)
app.secret_key = os.urandom(24)  # Для управления сессиями

@app.route('/')
def index():
    return render_template('start.html')

# Регистрация маршрутов
register_routes(app)

# Асинхронные обработчики запросов
@app.before_request
def initialize_database():
    asyncio.run(db.initialize())

# Запуск приложения
if __name__ == '__main__':
    app.run(debug=True)
```


main.py
```py
# This is a sample Python script.

# Press Shift+F10 to execute it or replace it with your code.
# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings.


def print_hi(name):
    # Use a breakpoint in the code line below to debug your script.
    print(f'Hi, {name}')  # Press Ctrl+F8 to toggle the breakpoint.


# Press the green button in the gutter to run the script.
if __name__ == '__main__':
    print_hi('PyCharm')

# See PyCharm help at https://www.jetbrains.com/help/pycharm/

```


requirements.txt
```txt
Flask
aiosqlite
Flask-Login
Werkzeug
```


auth/auth.py
```py
# auth/auth.py

from werkzeug.security import generate_password_hash, check_password_hash
from flask import session
from database import db

class Auth:
    SESSION_KEY = 'user_id'

    @staticmethod
    async def register(name, password, id=None):
        existing_user = await db.get_user_by_name(name)
        if existing_user:
            raise Exception("User already exists")
        password_hash = generate_password_hash(password)
        user_id = await db.add_user(name, password_hash, id)
        return user_id

    @staticmethod
    async def login(name, password):
        user = await db.get_user_by_name(name)
        if not user:
            raise Exception("User does not exist")
        if not check_password_hash(user.password_hash, password):
            raise Exception("Incorrect password")
        if user.banned:
            raise Exception("User is banned")
        session['user_id'] = user.id
        return user.id

    @staticmethod
    def logout():
        session.pop('user_id', None)

    @staticmethod
    async def get_current_user():
        user_id = session.get('user_id')
        if user_id:
            return await db.get_user_by_id(user_id)
        return None

```


auth/__init__.py
```py
# auth/__init__.py

from .auth import Auth
```


database/db.py
```py
# database/db.py

import aiosqlite
import os
import json
from .models import User, Exam
from datetime import datetime

DATABASE_PATH = "database.db"

class Database:
    def __init__(self, db_path=DATABASE_PATH):
        self.db_path = db_path

    async def initialize(self):
        async with aiosqlite.connect(self.db_path) as db:
            await db.execute("""
                CREATE TABLE IF NOT EXISTS Users (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    name TEXT NOT NULL UNIQUE,
                    password_hash TEXT NOT NULL,
                    created_exams TEXT,
                    viewed_exams TEXT,
                    verify INTEGER DEFAULT 0,
                    admin INTEGER DEFAULT 0,
                    avatar BLOB,
                    description TEXT,
                    banned INTEGER DEFAULT 0,
                    registration_date TEXT DEFAULT CURRENT_TIMESTAMP
                )
            """)
            await db.execute("""
                CREATE TABLE IF NOT EXISTS Exams (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    name TEXT NOT NULL,
                    description TEXT,
                    author_id INTEGER NOT NULL,
                    tags TEXT,
                    questions TEXT DEFAULT '[]',
                    creation_date TEXT DEFAULT CURRENT_TIMESTAMP,
                    FOREIGN KEY(author_id) REFERENCES Users(id)
                )
            """)
            await db.commit()

    # User-related methods

    async def add_user(self, name, password_hash, id=None):
        async with aiosqlite.connect(self.db_path) as db:
            db.row_factory = aiosqlite.Row
            if id:
                try:
                    await db.execute("""
                        INSERT INTO Users (id, name, password_hash, created_exams, viewed_exams)
                        VALUES (?, ?, ?, ?, ?)
                    """, (id, name, password_hash, json.dumps([]), json.dumps([])))
                except aiosqlite.IntegrityError:
                    # ID or name already exists, assign automatically
                    cursor = await db.execute("""
                        INSERT INTO Users (name, password_hash, created_exams, viewed_exams)
                        VALUES (?, ?, ?, ?)
                    """, (name, password_hash, json.dumps([]), json.dumps([])))
            else:
                cursor = await db.execute("""
                    INSERT INTO Users (name, password_hash, created_exams, viewed_exams)
                    VALUES (?, ?, ?, ?)
                """, (name, password_hash, json.dumps([]), json.dumps([])))
            await db.commit()
            user_id = cursor.lastrowid
            return user_id

    async def get_user_by_id(self, user_id):
        async with aiosqlite.connect(self.db_path) as db:
            db.row_factory = aiosqlite.Row
            cursor = await db.execute("SELECT * FROM Users WHERE id = ?", (user_id,))
            row = await cursor.fetchone()
            if row:
                return User.from_row(row)
            return None

    async def get_user_by_name(self, name):
        async with aiosqlite.connect(self.db_path) as db:
            db.row_factory = aiosqlite.Row
            cursor = await db.execute("SELECT * FROM Users WHERE name = ?", (name,))
            row = await cursor.fetchone()
            if row:
                return User.from_row(row)
            return None

    async def delete_user(self, user_id):
        async with aiosqlite.connect(self.db_path) as db:
            await db.execute("DELETE FROM Users WHERE id = ?", (user_id,))
            await db.commit()

    async def update_user(self, user: User):
        async with aiosqlite.connect(self.db_path) as db:
            await db.execute("""
                UPDATE Users SET
                    name = ?,
                    password_hash = ?,
                    created_exams = ?,
                    viewed_exams = ?,
                    verify = ?,
                    admin = ?,
                    avatar = ?,
                    description = ?,
                    banned = ?
                WHERE id = ?
            """, (
                user.name,
                user.password_hash,
                json.dumps(user.created_exams),
                json.dumps(user.viewed_exams),
                int(user.verify),
                int(user.admin),
                user.avatar,
                user.description,
                int(user.banned),
                user.id
            ))
            await db.commit()

    # Exam-related methods

    async def add_exam(self, name, description, author_id, tags):
        async with aiosqlite.connect(self.db_path) as db:
            cursor = await db.execute("""
                INSERT INTO Exams (name, description, author_id, tags)
                VALUES (?, ?, ?, ?)
            """, (name, description, author_id, json.dumps(tags)))
            await db.commit()
            exam_id = cursor.lastrowid
            return exam_id

    async def get_exam_by_id(self, exam_id):
        async with aiosqlite.connect(self.db_path) as db:
            db.row_factory = aiosqlite.Row
            cursor = await db.execute("SELECT * FROM Exams WHERE id = ?", (exam_id,))
            row = await cursor.fetchone()
            if row:
                return Exam.from_row(row)
            return None

    async def get_exams_by_author(self, author_id, limit, offset, search=None, tags=None, sort='new'):
        async with aiosqlite.connect(self.db_path) as db:
            db.row_factory = aiosqlite.Row
            query = "SELECT * FROM Exams WHERE author_id = ?"
            params = [author_id]

            if search:
                query += " AND name LIKE ?"
                params.append(f"%{search}%")

            if tags:
                for tag in tags:
                    query += " AND tags LIKE ?"
                    params.append(f"%{tag}%")

            if sort == 'new':
                query += " ORDER BY datetime(creation_date) DESC"
            else:
                query += " ORDER BY datetime(creation_date) ASC"

            query += " LIMIT ? OFFSET ?"
            params.extend([limit, offset])

            cursor = await db.execute(query, tuple(params))
            rows = await cursor.fetchall()
            return [Exam.from_row(row) for row in rows]

    async def get_all_exams(self, limit, offset, search=None, tags=None, sort='new'):
        async with aiosqlite.connect(self.db_path) as db:
            db.row_factory = aiosqlite.Row
            query = "SELECT * FROM Exams WHERE 1=1"
            params = []

            if search:
                query += " AND name LIKE ?"
                params.append(f"%{search}%")

            if tags:
                for tag in tags:
                    query += " AND tags LIKE ?"
                    params.append(f"%{tag}%")

            if sort == 'new':
                query += " ORDER BY datetime(creation_date) DESC"
            else:
                query += " ORDER BY datetime(creation_date) ASC"

            query += " LIMIT ? OFFSET ?"
            params.extend([limit, offset])

            cursor = await db.execute(query, tuple(params))
            rows = await cursor.fetchall()
            return [Exam.from_row(row) for row in rows]

    async def update_exam(self, exam: Exam):
        async with aiosqlite.connect(self.db_path) as db:
            await db.execute("""
                UPDATE Exams SET
                    name = ?,
                    description = ?,
                    tags = ?,
                    questions = ?
                WHERE id = ?
            """, (
                exam.name,
                exam.description,
                json.dumps(exam.tags),
                json.dumps(exam.questions),
                exam.id
            ))
            await db.commit()

    async def update_exam_questions(self, exam_id, questions):
        async with aiosqlite.connect(self.db_path) as db:
            await db.execute("""
                UPDATE Exams SET questions = ?
                WHERE id = ?
            """, (json.dumps(questions), exam_id))
            await db.commit()

    async def delete_exam(self, exam_id):
        async with aiosqlite.connect(self.db_path) as db:
            await db.execute("DELETE FROM Exams WHERE id = ?", (exam_id,))
            await db.commit()

```


database/models.py
```py
# database/models.py

import json
from datetime import datetime

class User:
    def __init__(self, id, name, password_hash, created_exams, viewed_exams,
                 verify=False, admin=False, avatar=None, description=None,
                 banned=False, registration_date=None):
        self.id = id
        self.name = name
        self.password_hash = password_hash
        self.created_exams = created_exams or []
        self.viewed_exams = viewed_exams or []
        self.verify = verify
        self.admin = admin
        self.avatar = avatar
        self.description = description
        self.banned = banned
        self.registration_date = registration_date or datetime.utcnow()

    @classmethod
    def from_row(cls, row):
        return cls(
            id=row['id'],
            name=row['name'],
            password_hash=row['password_hash'],
            created_exams=json.loads(row['created_exams']),
            viewed_exams=json.loads(row['viewed_exams']),
            verify=bool(row['verify']),
            admin=bool(row['admin']),
            avatar=row['avatar'],
            description=row['description'],
            banned=bool(row['banned']),
            registration_date=row['registration_date']
        )

class Exam:
    def __init__(self, id, name, description, author_id, tags,
                 questions, creation_date=None):
        self.id = id
        self.name = name
        self.description = description
        self.author_id = author_id
        self.tags = tags or []
        self.questions = questions or []
        self.creation_date = creation_date or datetime.utcnow()

    @classmethod
    def from_row(cls, row):
        return cls(
            id=row['id'],
            name=row['name'],
            description=row['description'],
            author_id=row['author_id'],
            tags=json.loads(row['tags']),
            questions=json.loads(row['questions']),
            creation_date=row['creation_date']
        )

```


database/__init__.py
```py
# database/__init__.py

from .db import Database

db = Database()
```


routes/exams.py
```py
# routes/exams.py

from flask import Blueprint, request, jsonify
from auth import Auth
from database import db
from database.models import Exam
import json

exams_bp = Blueprint('exams', __name__, url_prefix='/exams')

@exams_bp.route('/', methods=['POST'])
async def create_exam():
    current_user = await Auth.get_current_user()
    if not current_user:
        return jsonify({"error": "Authentication required"}), 401
    data = request.json
    name = data.get('name')
    description = data.get('description')
    tags = data.get('tags', [])
    if not name:
        return jsonify({"error": "Name is required"}), 400
    exam_id = await db.add_exam(name, description, current_user.id, tags)
    # Update user's created_exams
    current_user.created_exams.append(exam_id)
    await db.update_user(current_user)
    return jsonify({"exam_id": exam_id}), 201

@exams_bp.route('/<int:exam_id>', methods=['DELETE'])
async def delete_exam(exam_id):
    current_user = await Auth.get_current_user()
    if not current_user:
        return jsonify({"error": "Authentication required"}), 401
    exam = await db.get_exam_by_id(exam_id)
    if not exam:
        return jsonify({"error": "Exam not found"}), 404
    if exam.author_id != current_user.id and not current_user.admin:
        return jsonify({"error": "Permission denied"}), 403
    await db.delete_exam(exam_id)
    return jsonify({"message": "Exam deleted"}), 200

@exams_bp.route('/<int:exam_id>', methods=['PUT'])
async def edit_exam(exam_id):
    current_user = await Auth.get_current_user()
    if not current_user:
        return jsonify({"error": "Authentication required"}), 401
    exam = await db.get_exam_by_id(exam_id)
    if not exam:
        return jsonify({"error": "Exam not found"}), 404
    if exam.author_id != current_user.id and not current_user.admin:
        return jsonify({"error": "Permission denied"}), 403
    data = request.json
    exam.name = data.get('name', exam.name)
    exam.description = data.get('description', exam.description)
    exam.tags = data.get('tags', exam.tags)
    await db.update_exam(exam)
    return jsonify({"message": "Exam updated"}), 200

@exams_bp.route('/<int:exam_id>/questions', methods=['PUT'])
async def edit_exam_questions(exam_id):
    current_user = await Auth.get_current_user()
    if not current_user:
        return jsonify({"error": "Authentication required"}), 401
    exam = await db.get_exam_by_id(exam_id)
    if not exam:
        return jsonify({"error": "Exam not found"}), 404
    if exam.author_id != current_user.id and not current_user.admin:
        return jsonify({"error": "Permission denied"}), 403
    data = request.json
    questions = data.get('questions')
    if questions is None:
        return jsonify({"error": "Questions required"}), 400
    exam.questions = questions
    await db.update_exam_questions(exam_id, questions)
    return jsonify({"message": "Questions updated"}), 200

@exams_bp.route('/author/<int:author_id>', methods=['GET'])
async def get_exams_by_author(author_id):
    limit = int(request.args.get('limit', 10))
    offset = int(request.args.get('offset', 0))
    search = request.args.get('search')
    tags = request.args.getlist('tags')
    sort = request.args.get('sort', 'new')
    exams = await db.get_exams_by_author(author_id, limit, offset, search, tags, sort)
    exams_data = []
    for exam in exams:
        author = await db.get_user_by_id(exam.author_id)
        exams_data.append({
            "id": exam.id,
            "name": exam.name,
            "description": exam.description,
            "author": {
                "id": author.id,
                "name": author.name,
                "verify": author.verify,
                "admin": author.admin
            },
            "tags": exam.tags,
            "creation_date": exam.creation_date
        })
    return jsonify(exams_data), 200

@exams_bp.route('/', methods=['GET'])
async def get_all_exams():
    limit = int(request.args.get('limit', 10))
    offset = int(request.args.get('offset', 0))
    search = request.args.get('search')
    tags = request.args.getlist('tags')
    sort = request.args.get('sort', 'new')
    exams = await db.get_all_exams(limit, offset, search, tags, sort)
    exams_data = []
    for exam in exams:
        author = await db.get_user_by_id(exam.author_id)
        exams_data.append({
            "id": exam.id,
            "name": exam.name,
            "description": exam.description,
            "author": {
                "id": author.id,
                "name": author.name,
                "verify": author.verify,
                "admin": author.admin
            },
            "tags": exam.tags,
            "creation_date": exam.creation_date
        })
    return jsonify(exams_data), 200

@exams_bp.route('/<int:exam_id>/questions', methods=['GET'])
async def get_exam_questions(exam_id):
    exam = await db.get_exam_by_id(exam_id)
    if not exam:
        return jsonify({"error": "Exam not found"}), 404
    return jsonify({"questions": exam.questions}), 200

```


routes/users.py
```py
# routes/users.py

from flask import Blueprint, request, jsonify, session
from auth import Auth
from database import db
import json

users_bp = Blueprint('users', __name__, url_prefix='/users')

@users_bp.route('/register', methods=['POST'])
async def register():
    data = request.json
    name = data.get('name')
    password = data.get('password')
    id = data.get('id')  # опционально
    if not name or not password:
        return jsonify({"error": "Name and password required"}), 400
    try:
        user_id = await Auth.register(name, password, id)
        return jsonify({"user_id": user_id}), 201
    except Exception as e:
        return jsonify({"error": str(e)}), 400

@users_bp.route('/login', methods=['POST'])
async def login():
    data = request.json
    name = data.get('name')
    password = data.get('password')
    if not name or not password:
        return jsonify({"error": "Name and password required"}), 400
    try:
        user_id = await Auth.login(name, password)
        return jsonify({"user_id": user_id}), 200
    except Exception as e:
        return jsonify({"error": str(e)}), 400

@users_bp.route('/logout', methods=['POST'])
def logout():
    Auth.logout()
    return jsonify({"message": "Logged out"}), 200

@users_bp.route('/<int:user_id>', methods=['GET'])
async def get_user_info(user_id):
    user = await db.get_user_by_id(user_id)
    if not user:
        return jsonify({"error": "User not found"}), 404
    user_info = {
        "id": user.id,
        "name": user.name,
        "created_exams": user.created_exams,
        "viewed_exams": user.viewed_exams,
        "verify": user.verify,
        "admin": user.admin,
        "avatar": user.avatar.decode('latin1') if user.avatar else None,
        "description": user.description,
        "banned": user.banned,
        "registration_date": user.registration_date
    }
    return jsonify(user_info), 200

@users_bp.route('/summary/<int:user_id>', methods=['GET'])
async def get_user_summary(user_id):
    user = await db.get_user_by_id(user_id)
    if not user:
        return jsonify({"error": "User not found"}), 404
    summary = {
        "name": user.name,
        "verify": user.verify,
        "admin": user.admin
    }
    return jsonify(summary), 200

@users_bp.route('/<int:user_id>', methods=['DELETE'])
async def delete_user(user_id):
    current_user = await Auth.get_current_user()
    if not current_user:
        return jsonify({"error": "Authentication required"}), 401
    if current_user.id != user_id and not current_user.admin:
        return jsonify({"error": "Permission denied"}), 403
    await db.delete_user(user_id)
    return jsonify({"message": "User deleted"}), 200

@users_bp.route('/ban/<int:user_id>', methods=['POST'])
async def ban_user(user_id):
    current_user = await Auth.get_current_user()
    if not current_user or not current_user.admin:
        return jsonify({"error": "Admin privileges required"}), 403
    user = await db.get_user_by_id(user_id)
    if not user:
        return jsonify({"error": "User not found"}), 404
    user.banned = True
    await db.update_user(user)
    return jsonify({"message": "User banned"}), 200

@users_bp.route('/unban/<int:user_id>', methods=['POST'])
async def unban_user(user_id):
    current_user = await Auth.get_current_user()
    if not current_user or not current_user.admin:
        return jsonify({"error": "Admin privileges required"}), 403
    user = await db.get_user_by_id(user_id)
    if not user:
        return jsonify({"error": "User not found"}), 404
    user.banned = False
    await db.update_user(user)
    return jsonify({"message": "User unbanned"}), 200

```


routes/__init__.py
```py
# routes/__init__.py

from .users import users_bp
from .exams import exams_bp

def register_routes(app):
    app.register_blueprint(users_bp)
    app.register_blueprint(exams_bp)
```


Напиши мне код js(jqeury), который бы содержал в себе функции логина и регистрации, и сохранения токена в coockie, чтобы в дальнейшем была возможность его использовать для действий с API