create-flask-react 0.1.0__tar.gz
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-0.1.0/LICENSE +21 -0
- create_flask_react-0.1.0/PKG-INFO +84 -0
- create_flask_react-0.1.0/README.md +65 -0
- create_flask_react-0.1.0/pyproject.toml +39 -0
- create_flask_react-0.1.0/setup.cfg +4 -0
- create_flask_react-0.1.0/src/create_flask_react/__init__.py +0 -0
- create_flask_react-0.1.0/src/create_flask_react/main.py +50 -0
- create_flask_react-0.1.0/src/create_flask_react/template/.env.example +10 -0
- create_flask_react-0.1.0/src/create_flask_react/template/.gitignore +23 -0
- create_flask_react-0.1.0/src/create_flask_react/template/Makefile +22 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/.dockerignore +3 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/Dockerfile +6 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/__init__.py +23 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/__pycache__/__init__.cpython-312.pyc +0 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/__pycache__/config.cpython-312.pyc +0 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/__pycache__/extensions.cpython-312.pyc +0 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/config.py +29 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/extensions.py +19 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/models/__init__.py +3 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/models/__pycache__/__init__.cpython-312.pyc +0 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/models/__pycache__/user.cpython-312.pyc +0 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/models/user.py +18 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/routes/__init__.py +16 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/routes/__pycache__/__init__.cpython-312.pyc +0 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/routes/__pycache__/auth.cpython-312.pyc +0 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/routes/__pycache__/main.cpython-312.pyc +0 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/routes/auth.py +107 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/routes/main.py +19 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/app/static/.gitkeep +0 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/migrations/README +1 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/migrations/__pycache__/env.cpython-312.pyc +0 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/migrations/alembic.ini +40 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/migrations/env.py +69 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/migrations/script.py.mako +24 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/migrations/versions/001_create_user_table.py +32 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/migrations/versions/__pycache__/001_create_user_table.cpython-312.pyc +0 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/pyproject.toml +13 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/tests/.gitkeep +0 -0
- create_flask_react-0.1.0/src/create_flask_react/template/backend/uv.lock +561 -0
- create_flask_react-0.1.0/src/create_flask_react/template/docker-compose.yml +45 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/Dockerfile +5 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/components.json +16 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/index.html +12 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/package.json +33 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/postcss.config.js +6 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/src/App.tsx +23 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/src/api/client.ts +43 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/src/components/ProtectedRoute.tsx +12 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/src/components/ui/alert.tsx +58 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/src/components/ui/button.tsx +55 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/src/components/ui/card.tsx +85 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/src/components/ui/input.tsx +24 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/src/components/ui/label.tsx +23 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/src/context/AuthContext.tsx +64 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/src/index.css +37 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/src/lib/utils.ts +6 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/src/main.tsx +16 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/src/pages/Dashboard.tsx +37 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/src/pages/Login.tsx +88 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/src/pages/Register.tsx +88 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/src/vite-env.d.ts +1 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/tailwind.config.ts +60 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/tsconfig.json +25 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/tsconfig.node.json +10 -0
- create_flask_react-0.1.0/src/create_flask_react/template/frontend/vite.config.ts +24 -0
- create_flask_react-0.1.0/src/create_flask_react.egg-info/PKG-INFO +84 -0
- create_flask_react-0.1.0/src/create_flask_react.egg-info/SOURCES.txt +68 -0
- create_flask_react-0.1.0/src/create_flask_react.egg-info/dependency_links.txt +1 -0
- create_flask_react-0.1.0/src/create_flask_react.egg-info/entry_points.txt +2 -0
- create_flask_react-0.1.0/src/create_flask_react.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joseph McGill
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: create-flask-react
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Scaffold a full-stack Flask + React starter project with auth, PostgreSQL, and Docker
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/jsphfrntz/create-flask-react
|
|
7
|
+
Project-URL: Repository, https://github.com/jsphfrntz/create-flask-react
|
|
8
|
+
Project-URL: Issues, https://github.com/jsphfrntz/create-flask-react/issues
|
|
9
|
+
Keywords: flask,react,starter,scaffold,docker,fullstack
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
14
|
+
Classifier: Framework :: Flask
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# create-flask-react
|
|
21
|
+
|
|
22
|
+
Scaffold a full-stack Flask + React starter project with auth, PostgreSQL, and Docker.
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uvx create-flask-react my-app
|
|
28
|
+
cd my-app
|
|
29
|
+
make dev
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Then open http://localhost:5173 — you'll see a login page, register an account, and land on an authenticated dashboard.
|
|
33
|
+
|
|
34
|
+
## What You Get
|
|
35
|
+
|
|
36
|
+
- **Backend:** Flask with Flask-RESTX (auto-generated Swagger docs at `/api/docs`)
|
|
37
|
+
- **Auth:** Flask-Login with session-based authentication
|
|
38
|
+
- **Database:** PostgreSQL with Flask-SQLAlchemy + Flask-Migrate
|
|
39
|
+
- **Frontend:** React 18 + TypeScript + Vite
|
|
40
|
+
- **UI:** Tailwind CSS + shadcn/ui components
|
|
41
|
+
- **Docker:** Single `docker compose up` for the full stack
|
|
42
|
+
|
|
43
|
+
## Project Structure
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
my-app/
|
|
47
|
+
├── docker-compose.yml # Dev: Flask + Vite + Postgres
|
|
48
|
+
├── Makefile # Convenience commands
|
|
49
|
+
├── backend/
|
|
50
|
+
│ ├── app/
|
|
51
|
+
│ │ ├── __init__.py # App factory
|
|
52
|
+
│ │ ├── config.py # Config from env vars
|
|
53
|
+
│ │ ├── extensions.py # SQLAlchemy, Migrate, LoginManager
|
|
54
|
+
│ │ ├── models/user.py # User model
|
|
55
|
+
│ │ └── routes/auth.py # Auth API (Flask-RESTX)
|
|
56
|
+
│ ├── migrations/ # Alembic migrations
|
|
57
|
+
│ └── pyproject.toml # Python deps (managed by uv)
|
|
58
|
+
└── frontend/
|
|
59
|
+
└── src/
|
|
60
|
+
├── api/client.ts # Typed API client
|
|
61
|
+
├── context/AuthContext # Auth state management
|
|
62
|
+
├── components/ui/ # shadcn/ui components
|
|
63
|
+
└── pages/ # Login, Register, Dashboard
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Make Targets
|
|
67
|
+
|
|
68
|
+
| Command | Description |
|
|
69
|
+
|---------|-------------|
|
|
70
|
+
| `make dev` | Start dev environment |
|
|
71
|
+
| `make stop` | Stop containers |
|
|
72
|
+
| `make logs` | Tail container logs |
|
|
73
|
+
| `make db-migrate` | Generate a new migration |
|
|
74
|
+
| `make db-upgrade` | Apply migrations |
|
|
75
|
+
| `make shell` | Flask shell |
|
|
76
|
+
| `make clean` | Stop containers and remove volumes |
|
|
77
|
+
|
|
78
|
+
## API Docs
|
|
79
|
+
|
|
80
|
+
Swagger UI is available at http://localhost:5000/api/docs when the backend is running.
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# create-flask-react
|
|
2
|
+
|
|
3
|
+
Scaffold a full-stack Flask + React starter project with auth, PostgreSQL, and Docker.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uvx create-flask-react my-app
|
|
9
|
+
cd my-app
|
|
10
|
+
make dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then open http://localhost:5173 — you'll see a login page, register an account, and land on an authenticated dashboard.
|
|
14
|
+
|
|
15
|
+
## What You Get
|
|
16
|
+
|
|
17
|
+
- **Backend:** Flask with Flask-RESTX (auto-generated Swagger docs at `/api/docs`)
|
|
18
|
+
- **Auth:** Flask-Login with session-based authentication
|
|
19
|
+
- **Database:** PostgreSQL with Flask-SQLAlchemy + Flask-Migrate
|
|
20
|
+
- **Frontend:** React 18 + TypeScript + Vite
|
|
21
|
+
- **UI:** Tailwind CSS + shadcn/ui components
|
|
22
|
+
- **Docker:** Single `docker compose up` for the full stack
|
|
23
|
+
|
|
24
|
+
## Project Structure
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
my-app/
|
|
28
|
+
├── docker-compose.yml # Dev: Flask + Vite + Postgres
|
|
29
|
+
├── Makefile # Convenience commands
|
|
30
|
+
├── backend/
|
|
31
|
+
│ ├── app/
|
|
32
|
+
│ │ ├── __init__.py # App factory
|
|
33
|
+
│ │ ├── config.py # Config from env vars
|
|
34
|
+
│ │ ├── extensions.py # SQLAlchemy, Migrate, LoginManager
|
|
35
|
+
│ │ ├── models/user.py # User model
|
|
36
|
+
│ │ └── routes/auth.py # Auth API (Flask-RESTX)
|
|
37
|
+
│ ├── migrations/ # Alembic migrations
|
|
38
|
+
│ └── pyproject.toml # Python deps (managed by uv)
|
|
39
|
+
└── frontend/
|
|
40
|
+
└── src/
|
|
41
|
+
├── api/client.ts # Typed API client
|
|
42
|
+
├── context/AuthContext # Auth state management
|
|
43
|
+
├── components/ui/ # shadcn/ui components
|
|
44
|
+
└── pages/ # Login, Register, Dashboard
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Make Targets
|
|
48
|
+
|
|
49
|
+
| Command | Description |
|
|
50
|
+
|---------|-------------|
|
|
51
|
+
| `make dev` | Start dev environment |
|
|
52
|
+
| `make stop` | Stop containers |
|
|
53
|
+
| `make logs` | Tail container logs |
|
|
54
|
+
| `make db-migrate` | Generate a new migration |
|
|
55
|
+
| `make db-upgrade` | Apply migrations |
|
|
56
|
+
| `make shell` | Flask shell |
|
|
57
|
+
| `make clean` | Stop containers and remove volumes |
|
|
58
|
+
|
|
59
|
+
## API Docs
|
|
60
|
+
|
|
61
|
+
Swagger UI is available at http://localhost:5000/api/docs when the backend is running.
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
MIT
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "create-flask-react"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Scaffold a full-stack Flask + React starter project with auth, PostgreSQL, and Docker"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
keywords = ["flask", "react", "starter", "scaffold", "docker", "fullstack"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Topic :: Software Development :: Code Generators",
|
|
18
|
+
"Framework :: Flask",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/jsphfrntz/create-flask-react"
|
|
23
|
+
Repository = "https://github.com/jsphfrntz/create-flask-react"
|
|
24
|
+
Issues = "https://github.com/jsphfrntz/create-flask-react/issues"
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
create-flask-react = "create_flask_react.main:main"
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.packages.find]
|
|
30
|
+
where = ["src"]
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.package-data]
|
|
33
|
+
create_flask_react = [
|
|
34
|
+
"template/**/*",
|
|
35
|
+
"template/**/.gitkeep",
|
|
36
|
+
"template/**/.gitignore",
|
|
37
|
+
"template/**/.dockerignore",
|
|
38
|
+
"template/.env.example",
|
|
39
|
+
]
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
|
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
|
|
Binary file
|
|
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.
|
|
Binary file
|
|
@@ -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
|