create-flask-react 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- create_flask_react/__init__.py +0 -0
- create_flask_react/main.py +50 -0
- create_flask_react/template/.env.example +10 -0
- create_flask_react/template/.gitignore +23 -0
- create_flask_react/template/Makefile +22 -0
- create_flask_react/template/backend/.dockerignore +3 -0
- create_flask_react/template/backend/Dockerfile +6 -0
- create_flask_react/template/backend/app/__init__.py +23 -0
- create_flask_react/template/backend/app/__pycache__/__init__.cpython-312.pyc +0 -0
- create_flask_react/template/backend/app/__pycache__/config.cpython-312.pyc +0 -0
- create_flask_react/template/backend/app/__pycache__/extensions.cpython-312.pyc +0 -0
- create_flask_react/template/backend/app/config.py +29 -0
- create_flask_react/template/backend/app/extensions.py +19 -0
- create_flask_react/template/backend/app/models/__init__.py +3 -0
- create_flask_react/template/backend/app/models/__pycache__/__init__.cpython-312.pyc +0 -0
- create_flask_react/template/backend/app/models/__pycache__/user.cpython-312.pyc +0 -0
- create_flask_react/template/backend/app/models/user.py +18 -0
- create_flask_react/template/backend/app/routes/__init__.py +16 -0
- create_flask_react/template/backend/app/routes/__pycache__/__init__.cpython-312.pyc +0 -0
- create_flask_react/template/backend/app/routes/__pycache__/auth.cpython-312.pyc +0 -0
- create_flask_react/template/backend/app/routes/__pycache__/main.cpython-312.pyc +0 -0
- create_flask_react/template/backend/app/routes/auth.py +107 -0
- create_flask_react/template/backend/app/routes/main.py +19 -0
- create_flask_react/template/backend/app/static/.gitkeep +0 -0
- create_flask_react/template/backend/migrations/README +1 -0
- create_flask_react/template/backend/migrations/__pycache__/env.cpython-312.pyc +0 -0
- create_flask_react/template/backend/migrations/alembic.ini +40 -0
- create_flask_react/template/backend/migrations/env.py +69 -0
- create_flask_react/template/backend/migrations/script.py.mako +24 -0
- create_flask_react/template/backend/migrations/versions/001_create_user_table.py +32 -0
- create_flask_react/template/backend/migrations/versions/__pycache__/001_create_user_table.cpython-312.pyc +0 -0
- create_flask_react/template/backend/pyproject.toml +13 -0
- create_flask_react/template/backend/tests/.gitkeep +0 -0
- create_flask_react/template/backend/uv.lock +561 -0
- create_flask_react/template/docker-compose.yml +45 -0
- create_flask_react/template/frontend/Dockerfile +5 -0
- create_flask_react/template/frontend/components.json +16 -0
- create_flask_react/template/frontend/index.html +12 -0
- create_flask_react/template/frontend/package.json +33 -0
- create_flask_react/template/frontend/postcss.config.js +6 -0
- create_flask_react/template/frontend/src/App.tsx +23 -0
- create_flask_react/template/frontend/src/api/client.ts +43 -0
- create_flask_react/template/frontend/src/components/ProtectedRoute.tsx +12 -0
- create_flask_react/template/frontend/src/components/ui/alert.tsx +58 -0
- create_flask_react/template/frontend/src/components/ui/button.tsx +55 -0
- create_flask_react/template/frontend/src/components/ui/card.tsx +85 -0
- create_flask_react/template/frontend/src/components/ui/input.tsx +24 -0
- create_flask_react/template/frontend/src/components/ui/label.tsx +23 -0
- create_flask_react/template/frontend/src/context/AuthContext.tsx +64 -0
- create_flask_react/template/frontend/src/index.css +37 -0
- create_flask_react/template/frontend/src/lib/utils.ts +6 -0
- create_flask_react/template/frontend/src/main.tsx +16 -0
- create_flask_react/template/frontend/src/pages/Dashboard.tsx +37 -0
- create_flask_react/template/frontend/src/pages/Login.tsx +88 -0
- create_flask_react/template/frontend/src/pages/Register.tsx +88 -0
- create_flask_react/template/frontend/src/vite-env.d.ts +1 -0
- create_flask_react/template/frontend/tailwind.config.ts +60 -0
- create_flask_react/template/frontend/tsconfig.json +25 -0
- create_flask_react/template/frontend/tsconfig.node.json +10 -0
- create_flask_react/template/frontend/vite.config.ts +24 -0
- create_flask_react-0.1.0.dist-info/METADATA +84 -0
- create_flask_react-0.1.0.dist-info/RECORD +66 -0
- create_flask_react-0.1.0.dist-info/WHEEL +5 -0
- create_flask_react-0.1.0.dist-info/entry_points.txt +2 -0
- create_flask_react-0.1.0.dist-info/licenses/LICENSE +21 -0
- create_flask_react-0.1.0.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""CLI entry point for create-flask-react."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import shutil
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
TEMPLATE_DIR = Path(__file__).parent / "template"
|
|
9
|
+
IGNORE = shutil.ignore_patterns(
|
|
10
|
+
".venv", "node_modules", "__pycache__", "*.pyc", ".env",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main():
|
|
15
|
+
parser = argparse.ArgumentParser(
|
|
16
|
+
description="Create a new Flask + React project"
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"project_name",
|
|
20
|
+
help="Name of the project directory to create (use '.' for current directory)",
|
|
21
|
+
)
|
|
22
|
+
args = parser.parse_args()
|
|
23
|
+
|
|
24
|
+
if args.project_name == ".":
|
|
25
|
+
dest = Path.cwd()
|
|
26
|
+
else:
|
|
27
|
+
dest = Path.cwd() / args.project_name
|
|
28
|
+
|
|
29
|
+
if dest.exists() and args.project_name != ".":
|
|
30
|
+
if any(dest.iterdir()):
|
|
31
|
+
print(f"Error: {dest} already exists and is not empty.", file=sys.stderr)
|
|
32
|
+
sys.exit(1)
|
|
33
|
+
|
|
34
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
|
|
36
|
+
shutil.copytree(TEMPLATE_DIR, dest, dirs_exist_ok=True, ignore=IGNORE)
|
|
37
|
+
|
|
38
|
+
# Create .env from .env.example
|
|
39
|
+
env_example = dest / ".env.example"
|
|
40
|
+
env_file = dest / ".env"
|
|
41
|
+
if env_example.exists() and not env_file.exists():
|
|
42
|
+
shutil.copy2(env_example, env_file)
|
|
43
|
+
|
|
44
|
+
print(f"Created Flask + React project in {dest.resolve()}")
|
|
45
|
+
print()
|
|
46
|
+
print("Next steps:")
|
|
47
|
+
print(f" cd {args.project_name}")
|
|
48
|
+
print(" make dev")
|
|
49
|
+
print()
|
|
50
|
+
print("Then open http://localhost:5173 in your browser.")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.PHONY: dev stop logs db-migrate db-upgrade shell clean
|
|
2
|
+
|
|
3
|
+
dev:
|
|
4
|
+
docker compose up --build
|
|
5
|
+
|
|
6
|
+
stop:
|
|
7
|
+
docker compose down
|
|
8
|
+
|
|
9
|
+
logs:
|
|
10
|
+
docker compose logs -f
|
|
11
|
+
|
|
12
|
+
db-migrate:
|
|
13
|
+
docker compose exec backend uv run flask db migrate
|
|
14
|
+
|
|
15
|
+
db-upgrade:
|
|
16
|
+
docker compose exec backend uv run flask db upgrade
|
|
17
|
+
|
|
18
|
+
shell:
|
|
19
|
+
docker compose exec backend uv run flask shell
|
|
20
|
+
|
|
21
|
+
clean:
|
|
22
|
+
docker compose down -v
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from flask import Flask
|
|
3
|
+
from .config import config
|
|
4
|
+
from .extensions import db, migrate, login_manager
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def create_app():
|
|
8
|
+
app = Flask(__name__, static_folder="static", static_url_path="/")
|
|
9
|
+
|
|
10
|
+
env = os.getenv("FLASK_ENV", "development")
|
|
11
|
+
app.config.from_object(config[env])
|
|
12
|
+
|
|
13
|
+
db.init_app(app)
|
|
14
|
+
migrate.init_app(app, db)
|
|
15
|
+
login_manager.init_app(app)
|
|
16
|
+
|
|
17
|
+
from .routes import api_bp
|
|
18
|
+
from .routes.main import main_bp
|
|
19
|
+
|
|
20
|
+
app.register_blueprint(api_bp)
|
|
21
|
+
app.register_blueprint(main_bp)
|
|
22
|
+
|
|
23
|
+
return app
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BaseConfig:
|
|
5
|
+
SECRET_KEY = os.getenv("SECRET_KEY", "change-this-to-a-random-secret")
|
|
6
|
+
SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL")
|
|
7
|
+
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
|
8
|
+
SESSION_COOKIE_SAMESITE = "Lax"
|
|
9
|
+
SESSION_COOKIE_HTTPONLY = True
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DevelopmentConfig(BaseConfig):
|
|
13
|
+
DEBUG = True
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ProductionConfig(BaseConfig):
|
|
17
|
+
DEBUG = False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestConfig(BaseConfig):
|
|
21
|
+
TESTING = True
|
|
22
|
+
SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
config = {
|
|
26
|
+
"development": DevelopmentConfig,
|
|
27
|
+
"production": ProductionConfig,
|
|
28
|
+
"testing": TestConfig,
|
|
29
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from flask import jsonify
|
|
2
|
+
from flask_login import LoginManager
|
|
3
|
+
from flask_migrate import Migrate
|
|
4
|
+
from flask_sqlalchemy import SQLAlchemy
|
|
5
|
+
|
|
6
|
+
db = SQLAlchemy()
|
|
7
|
+
migrate = Migrate()
|
|
8
|
+
login_manager = LoginManager()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@login_manager.user_loader
|
|
12
|
+
def load_user(user_id):
|
|
13
|
+
from .models.user import User
|
|
14
|
+
return db.session.get(User, int(user_id))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@login_manager.unauthorized_handler
|
|
18
|
+
def unauthorized():
|
|
19
|
+
return jsonify({"error": "Authentication required"}), 401
|
|
Binary file
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
from flask_login import UserMixin
|
|
3
|
+
from werkzeug.security import generate_password_hash, check_password_hash
|
|
4
|
+
from ..extensions import db
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class User(UserMixin, db.Model):
|
|
8
|
+
id = db.Column(db.Integer, primary_key=True)
|
|
9
|
+
email = db.Column(db.String(255), unique=True, nullable=False, index=True)
|
|
10
|
+
password_hash = db.Column(db.String(256), nullable=False)
|
|
11
|
+
is_active = db.Column(db.Boolean, default=True)
|
|
12
|
+
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
|
|
13
|
+
|
|
14
|
+
def set_password(self, password):
|
|
15
|
+
self.password_hash = generate_password_hash(password)
|
|
16
|
+
|
|
17
|
+
def check_password(self, password):
|
|
18
|
+
return check_password_hash(self.password_hash, password)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from flask import Blueprint
|
|
2
|
+
from flask_restx import Api
|
|
3
|
+
|
|
4
|
+
api_bp = Blueprint("api", __name__, url_prefix="/api")
|
|
5
|
+
|
|
6
|
+
api = Api(
|
|
7
|
+
api_bp,
|
|
8
|
+
version="1.0",
|
|
9
|
+
title="API",
|
|
10
|
+
description="Flask + React Starter API",
|
|
11
|
+
doc="/docs",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from .auth import ns as auth_ns # noqa: E402
|
|
15
|
+
|
|
16
|
+
api.add_namespace(auth_ns)
|
|
Binary file
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from flask_login import current_user, login_required, login_user, logout_user
|
|
2
|
+
from flask_restx import Namespace, Resource, fields
|
|
3
|
+
from ..extensions import db
|
|
4
|
+
from ..models.user import User
|
|
5
|
+
|
|
6
|
+
ns = Namespace("auth", description="Authentication")
|
|
7
|
+
|
|
8
|
+
credentials_model = ns.model("Credentials", {
|
|
9
|
+
"email": fields.String(required=True, description="User email"),
|
|
10
|
+
"password": fields.String(required=True, description="User password"),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
user_model = ns.model("User", {
|
|
14
|
+
"id": fields.Integer(description="User ID"),
|
|
15
|
+
"email": fields.String(description="User email"),
|
|
16
|
+
"created_at": fields.DateTime(description="Account creation date"),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
error_model = ns.model("Error", {
|
|
20
|
+
"error": fields.String(description="Error message"),
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
message_model = ns.model("Message", {
|
|
24
|
+
"message": fields.String(description="Status message"),
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@ns.route("/register")
|
|
29
|
+
class Register(Resource):
|
|
30
|
+
@ns.expect(credentials_model)
|
|
31
|
+
@ns.response(201, "User created", user_model)
|
|
32
|
+
@ns.response(400, "Validation error", error_model)
|
|
33
|
+
@ns.response(409, "Email already registered", error_model)
|
|
34
|
+
def post(self):
|
|
35
|
+
"""Register a new user."""
|
|
36
|
+
data = ns.payload
|
|
37
|
+
email = data.get("email")
|
|
38
|
+
password = data.get("password")
|
|
39
|
+
|
|
40
|
+
if not email or not password:
|
|
41
|
+
return {"error": "Email and password are required"}, 400
|
|
42
|
+
|
|
43
|
+
if User.query.filter_by(email=email).first():
|
|
44
|
+
return {"error": "Email already registered"}, 409
|
|
45
|
+
|
|
46
|
+
user = User(email=email)
|
|
47
|
+
user.set_password(password)
|
|
48
|
+
db.session.add(user)
|
|
49
|
+
db.session.commit()
|
|
50
|
+
|
|
51
|
+
login_user(user)
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
"id": user.id,
|
|
55
|
+
"email": user.email,
|
|
56
|
+
"created_at": user.created_at.isoformat(),
|
|
57
|
+
}, 201
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@ns.route("/login")
|
|
61
|
+
class Login(Resource):
|
|
62
|
+
@ns.expect(credentials_model)
|
|
63
|
+
@ns.response(200, "Login successful", user_model)
|
|
64
|
+
@ns.response(401, "Invalid credentials", error_model)
|
|
65
|
+
def post(self):
|
|
66
|
+
"""Log in with email and password."""
|
|
67
|
+
data = ns.payload
|
|
68
|
+
email = data.get("email")
|
|
69
|
+
password = data.get("password")
|
|
70
|
+
|
|
71
|
+
user = User.query.filter_by(email=email).first()
|
|
72
|
+
|
|
73
|
+
if user is None or not user.check_password(password):
|
|
74
|
+
return {"error": "Invalid email or password"}, 401
|
|
75
|
+
|
|
76
|
+
login_user(user)
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
"id": user.id,
|
|
80
|
+
"email": user.email,
|
|
81
|
+
"created_at": user.created_at.isoformat(),
|
|
82
|
+
}, 200
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@ns.route("/logout")
|
|
86
|
+
class Logout(Resource):
|
|
87
|
+
@login_required
|
|
88
|
+
@ns.response(200, "Logged out", message_model)
|
|
89
|
+
@ns.response(401, "Not authenticated", error_model)
|
|
90
|
+
def post(self):
|
|
91
|
+
"""Log out the current user."""
|
|
92
|
+
logout_user()
|
|
93
|
+
return {"message": "Logged out"}, 200
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@ns.route("/me")
|
|
97
|
+
class Me(Resource):
|
|
98
|
+
@login_required
|
|
99
|
+
@ns.response(200, "Current user", user_model)
|
|
100
|
+
@ns.response(401, "Not authenticated", error_model)
|
|
101
|
+
def get(self):
|
|
102
|
+
"""Get the current authenticated user."""
|
|
103
|
+
return {
|
|
104
|
+
"id": current_user.id,
|
|
105
|
+
"email": current_user.email,
|
|
106
|
+
"created_at": current_user.created_at.isoformat(),
|
|
107
|
+
}, 200
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from flask import Blueprint, send_from_directory, current_app
|
|
3
|
+
|
|
4
|
+
main_bp = Blueprint("main", __name__)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@main_bp.route("/", defaults={"path": ""})
|
|
8
|
+
@main_bp.route("/<path:path>")
|
|
9
|
+
def serve(path):
|
|
10
|
+
static_folder = os.path.join(current_app.root_path, "static")
|
|
11
|
+
|
|
12
|
+
if path and os.path.exists(os.path.join(static_folder, path)):
|
|
13
|
+
return send_from_directory(static_folder, path)
|
|
14
|
+
|
|
15
|
+
index_path = os.path.join(static_folder, "index.html")
|
|
16
|
+
if os.path.exists(index_path):
|
|
17
|
+
return send_from_directory(static_folder, "index.html")
|
|
18
|
+
|
|
19
|
+
return "Frontend not built. Run the Vite dev server or build the frontend.", 404
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Single-database configuration for Flask.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[alembic]
|
|
2
|
+
|
|
3
|
+
[loggers]
|
|
4
|
+
keys = root,sqlalchemy,alembic,flask_migrate
|
|
5
|
+
|
|
6
|
+
[handlers]
|
|
7
|
+
keys = console
|
|
8
|
+
|
|
9
|
+
[formatters]
|
|
10
|
+
keys = generic
|
|
11
|
+
|
|
12
|
+
[logger_root]
|
|
13
|
+
level = WARN
|
|
14
|
+
handlers = console
|
|
15
|
+
qualname =
|
|
16
|
+
|
|
17
|
+
[logger_sqlalchemy]
|
|
18
|
+
level = WARN
|
|
19
|
+
handlers =
|
|
20
|
+
qualname = sqlalchemy.engine
|
|
21
|
+
|
|
22
|
+
[logger_alembic]
|
|
23
|
+
level = INFO
|
|
24
|
+
handlers =
|
|
25
|
+
qualname = alembic
|
|
26
|
+
|
|
27
|
+
[logger_flask_migrate]
|
|
28
|
+
level = INFO
|
|
29
|
+
handlers =
|
|
30
|
+
qualname = flask_migrate
|
|
31
|
+
|
|
32
|
+
[handler_console]
|
|
33
|
+
class = StreamHandler
|
|
34
|
+
args = (sys.stderr,)
|
|
35
|
+
level = NOTSET
|
|
36
|
+
formatter = generic
|
|
37
|
+
|
|
38
|
+
[formatter_generic]
|
|
39
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
40
|
+
datefmt = %H:%M:%S
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from logging.config import fileConfig
|
|
3
|
+
|
|
4
|
+
from flask import current_app
|
|
5
|
+
from alembic import context
|
|
6
|
+
|
|
7
|
+
config = context.config
|
|
8
|
+
fileConfig(config.config_file_name)
|
|
9
|
+
logger = logging.getLogger("alembic.env")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_engine():
|
|
13
|
+
try:
|
|
14
|
+
return current_app.extensions["migrate"].db.get_engine()
|
|
15
|
+
except (TypeError, AttributeError):
|
|
16
|
+
return current_app.extensions["migrate"].db.engine
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_engine_url():
|
|
20
|
+
try:
|
|
21
|
+
return (
|
|
22
|
+
get_engine()
|
|
23
|
+
.url.render_as_string(hide_password=False)
|
|
24
|
+
.replace("%", "%%")
|
|
25
|
+
)
|
|
26
|
+
except AttributeError:
|
|
27
|
+
return str(get_engine().url).replace("%", "%%")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
config.set_main_option("sqlalchemy.url", get_engine_url())
|
|
31
|
+
target_db = current_app.extensions["migrate"].db
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_metadata():
|
|
35
|
+
if hasattr(target_db, "metadatas"):
|
|
36
|
+
return target_db.metadatas[None]
|
|
37
|
+
return target_db.metadata
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def run_migrations_offline():
|
|
41
|
+
url = config.get_main_option("sqlalchemy.url")
|
|
42
|
+
context.configure(url=url, target_metadata=get_metadata(), literal_binds=True)
|
|
43
|
+
with context.begin_transaction():
|
|
44
|
+
context.run_migrations()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def run_migrations_online():
|
|
48
|
+
def process_revision_directives(context, revision, directives):
|
|
49
|
+
if getattr(config.cmd_opts, "autogenerate", False):
|
|
50
|
+
script = directives[0]
|
|
51
|
+
if script.upgrade_ops.is_empty():
|
|
52
|
+
directives[:] = []
|
|
53
|
+
logger.info("No changes in schema detected.")
|
|
54
|
+
|
|
55
|
+
connectable = get_engine()
|
|
56
|
+
with connectable.connect() as connection:
|
|
57
|
+
context.configure(
|
|
58
|
+
connection=connection,
|
|
59
|
+
target_metadata=get_metadata(),
|
|
60
|
+
process_revision_directives=process_revision_directives,
|
|
61
|
+
)
|
|
62
|
+
with context.begin_transaction():
|
|
63
|
+
context.run_migrations()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if context.is_offline_mode():
|
|
67
|
+
run_migrations_offline()
|
|
68
|
+
else:
|
|
69
|
+
run_migrations_online()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""${message}
|
|
2
|
+
|
|
3
|
+
Revision ID: ${up_revision}
|
|
4
|
+
Revises: ${down_revision | comma,n}
|
|
5
|
+
Create Date: ${create_date}
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from alembic import op
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
${imports if imports else ""}
|
|
11
|
+
|
|
12
|
+
# revision identifiers, used by Alembic.
|
|
13
|
+
revision = ${repr(up_revision)}
|
|
14
|
+
down_revision = ${repr(down_revision)}
|
|
15
|
+
branch_labels = ${repr(branch_labels)}
|
|
16
|
+
depends_on = ${repr(depends_on)}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade():
|
|
20
|
+
${upgrades if upgrades else "pass"}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def downgrade():
|
|
24
|
+
${downgrades if downgrades else "pass"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""create user table
|
|
2
|
+
|
|
3
|
+
Revision ID: 001
|
|
4
|
+
Revises:
|
|
5
|
+
Create Date: 2024-01-01 00:00:00.000000
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from alembic import op
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
|
|
11
|
+
revision = "001"
|
|
12
|
+
down_revision = None
|
|
13
|
+
branch_labels = None
|
|
14
|
+
depends_on = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def upgrade():
|
|
18
|
+
op.create_table(
|
|
19
|
+
"user",
|
|
20
|
+
sa.Column("id", sa.Integer(), nullable=False),
|
|
21
|
+
sa.Column("email", sa.String(length=255), nullable=False),
|
|
22
|
+
sa.Column("password_hash", sa.String(length=256), nullable=False),
|
|
23
|
+
sa.Column("is_active", sa.Boolean(), nullable=True),
|
|
24
|
+
sa.Column("created_at", sa.DateTime(), nullable=True),
|
|
25
|
+
sa.PrimaryKeyConstraint("id"),
|
|
26
|
+
)
|
|
27
|
+
op.create_index(op.f("ix_user_email"), "user", ["email"], unique=True)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def downgrade():
|
|
31
|
+
op.drop_index(op.f("ix_user_email"), table_name="user")
|
|
32
|
+
op.drop_table("user")
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "backend"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
requires-python = ">=3.12"
|
|
5
|
+
dependencies = [
|
|
6
|
+
"flask>=3.0",
|
|
7
|
+
"flask-login>=0.6",
|
|
8
|
+
"flask-restx>=1.3",
|
|
9
|
+
"flask-sqlalchemy>=3.1",
|
|
10
|
+
"flask-migrate>=4.0",
|
|
11
|
+
"psycopg2-binary>=2.9",
|
|
12
|
+
"werkzeug>=3.0",
|
|
13
|
+
]
|
|
File without changes
|