shafikul-cli 2.1.6__tar.gz → 2.2.6__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.
- {shafikul_cli-2.1.6/shafikul_cli.egg-info → shafikul_cli-2.2.6}/PKG-INFO +2 -2
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/README.md +1 -1
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/pyproject.toml +1 -1
- shafikul_cli-2.2.6/shafikul_cli/asset/icon.png +0 -0
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli/main.py +69 -5
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli/utility/create_file_content.py +13 -8
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli/utility/helper_function.py +172 -41
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli/utility/install_dependicy.py +3 -9
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6/shafikul_cli.egg-info}/PKG-INFO +2 -2
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli.egg-info/SOURCES.txt +1 -0
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/LICENSE +0 -0
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/setup.cfg +0 -0
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli/__init__.py +0 -0
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli/asset/__init__.py +0 -0
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli/asset/logo.png +0 -0
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli/utility/__init__.py +0 -0
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli/utility/create_file_structure.py +0 -0
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli/utility/create_html_file.py +0 -0
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli/utility/ensure_db.py +0 -0
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli/utility/ensure_main.py +0 -0
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli/utility/env_generate.py +0 -0
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli.egg-info/dependency_links.txt +0 -0
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli.egg-info/entry_points.txt +0 -0
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli.egg-info/requires.txt +0 -0
- {shafikul_cli-2.1.6 → shafikul_cli-2.2.6}/shafikul_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shafikul_cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.6
|
|
4
4
|
Summary: CLI tool for FastAPI scaffolding with router, models, database, and templates.
|
|
5
5
|
Author-email: Md Shafikul Islam <buildwithshafikul@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/build-with-shafikul/shafikul_cli
|
|
@@ -175,7 +175,7 @@ your-project/
|
|
|
175
175
|
│
|
|
176
176
|
├── 📂 static/ # Served static files
|
|
177
177
|
│ └── images/
|
|
178
|
-
│ └──
|
|
178
|
+
│ └── icon.png
|
|
179
179
|
│
|
|
180
180
|
├── 📂 utility/ # Helper functions
|
|
181
181
|
│ └── helper_function.py
|
|
Binary file
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import os, subprocess
|
|
2
2
|
import typer
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from typing import Optional
|
|
5
|
+
import questionary
|
|
4
6
|
from rich.console import Console
|
|
5
7
|
from rich.panel import Panel
|
|
6
8
|
from rich.text import Text
|
|
@@ -34,6 +36,60 @@ def show_banner():
|
|
|
34
36
|
|
|
35
37
|
console.print(panel)
|
|
36
38
|
|
|
39
|
+
# =========================================================
|
|
40
|
+
# Select Folder DIR
|
|
41
|
+
# =========================================================
|
|
42
|
+
def select_project_directory() -> Path:
|
|
43
|
+
current_dir = Path.cwd()
|
|
44
|
+
|
|
45
|
+
choice = questionary.select(
|
|
46
|
+
"Where do you want to create the project?",
|
|
47
|
+
choices=[
|
|
48
|
+
"New folder",
|
|
49
|
+
"Current directory",
|
|
50
|
+
"Default (demo_folder)",
|
|
51
|
+
],
|
|
52
|
+
default="Default (demo_folder)",
|
|
53
|
+
).ask()
|
|
54
|
+
|
|
55
|
+
if choice is None:
|
|
56
|
+
raise typer.Abort()
|
|
57
|
+
|
|
58
|
+
if choice == "Current directory":
|
|
59
|
+
return current_dir
|
|
60
|
+
|
|
61
|
+
if choice == "Default (demo_folder)":
|
|
62
|
+
project_dir = current_dir / "demo_folder"
|
|
63
|
+
|
|
64
|
+
else:
|
|
65
|
+
folder_name = questionary.text(
|
|
66
|
+
"Enter your project folder name:"
|
|
67
|
+
).ask()
|
|
68
|
+
|
|
69
|
+
if not folder_name or not folder_name.strip():
|
|
70
|
+
raise typer.Abort()
|
|
71
|
+
|
|
72
|
+
folder_name = folder_name.strip()
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
folder_name in {".", ".."}
|
|
76
|
+
or Path(folder_name).is_absolute()
|
|
77
|
+
or len(Path(folder_name).parts) != 1
|
|
78
|
+
):
|
|
79
|
+
typer.echo("Invalid folder name.")
|
|
80
|
+
raise typer.Exit(code=1)
|
|
81
|
+
|
|
82
|
+
project_dir = current_dir / folder_name
|
|
83
|
+
|
|
84
|
+
if project_dir.exists() and (
|
|
85
|
+
not project_dir.is_dir() or any(project_dir.iterdir())
|
|
86
|
+
):
|
|
87
|
+
typer.echo(f"Folder already exists and is not empty: {project_dir}")
|
|
88
|
+
raise typer.Exit(code=1)
|
|
89
|
+
|
|
90
|
+
project_dir.mkdir(parents=True, exist_ok=True)
|
|
91
|
+
|
|
92
|
+
return project_dir
|
|
37
93
|
|
|
38
94
|
# =========================================================
|
|
39
95
|
# 📘 ABOUT & (--version) COMMAND
|
|
@@ -78,9 +134,15 @@ def create_app():
|
|
|
78
134
|
# target: Optional[str] = typer.Argument(None, help="structure")
|
|
79
135
|
|
|
80
136
|
show_banner()
|
|
137
|
+
project_dir = select_project_directory()
|
|
138
|
+
# All relative paths will now point inside the selected folder
|
|
139
|
+
os.chdir(project_dir)
|
|
140
|
+
console.print(f"[bold cyan]Project location:[/] {project_dir.resolve()}")
|
|
81
141
|
|
|
82
142
|
folders = [
|
|
83
143
|
"app",
|
|
144
|
+
"app/models",
|
|
145
|
+
"app/database",
|
|
84
146
|
"config",
|
|
85
147
|
"asset/css",
|
|
86
148
|
"asset/images",
|
|
@@ -104,11 +166,12 @@ def create_app():
|
|
|
104
166
|
"README.md",
|
|
105
167
|
"main.py",
|
|
106
168
|
"config/app.py",
|
|
107
|
-
"app/database.py",
|
|
108
|
-
"app/models.py",
|
|
169
|
+
"app/database/database.py",
|
|
170
|
+
"app/models/__init__.py",
|
|
171
|
+
"app/models/users.py",
|
|
109
172
|
"asset/css/style.css",
|
|
110
|
-
"
|
|
111
|
-
"static/images/
|
|
173
|
+
"static/images/icon.png",
|
|
174
|
+
"static/images/favicon.ico",
|
|
112
175
|
"asset/js/script.js",
|
|
113
176
|
"router/api/users.py",
|
|
114
177
|
"router/web/auth.py",
|
|
@@ -129,7 +192,7 @@ def create_app():
|
|
|
129
192
|
os.makedirs(folder, exist_ok=True)
|
|
130
193
|
|
|
131
194
|
for file in files:
|
|
132
|
-
file_inside_content(file)
|
|
195
|
+
file_inside_content(file, project_version('shafikul-cli'))
|
|
133
196
|
|
|
134
197
|
console.print("\n[green]Project structure created successfully![/]")
|
|
135
198
|
|
|
@@ -147,6 +210,7 @@ def create_app():
|
|
|
147
210
|
"argon2-cffi",
|
|
148
211
|
"python-multipart",
|
|
149
212
|
"alembic",
|
|
213
|
+
"pymysql"
|
|
150
214
|
])
|
|
151
215
|
|
|
152
216
|
# ==========================================
|
|
@@ -3,7 +3,7 @@ from . import helper_function as Hfn
|
|
|
3
3
|
|
|
4
4
|
console = Console()
|
|
5
5
|
|
|
6
|
-
def file_inside_content(file_name: str):
|
|
6
|
+
def file_inside_content(file_name: str, project_version):
|
|
7
7
|
console.print("[bold magenta]Creating File: [/] " + file_name )
|
|
8
8
|
if file_name == '.env':
|
|
9
9
|
Hfn.env(file_name)
|
|
@@ -26,11 +26,14 @@ def file_inside_content(file_name: str):
|
|
|
26
26
|
if file_name == 'config/app.py':
|
|
27
27
|
Hfn.config_app(file_name)
|
|
28
28
|
|
|
29
|
-
if file_name == 'app/database.py':
|
|
29
|
+
if file_name == 'app/database/database.py':
|
|
30
30
|
Hfn.database(file_name)
|
|
31
31
|
|
|
32
|
-
if file_name == 'app/models.py':
|
|
33
|
-
Hfn.
|
|
32
|
+
if file_name == 'app/models/__init__.py':
|
|
33
|
+
Hfn.models_init(file_name)
|
|
34
|
+
|
|
35
|
+
if file_name == 'app/models/users.py':
|
|
36
|
+
Hfn.models_user(file_name)
|
|
34
37
|
|
|
35
38
|
if file_name == 'router/api/users.py':
|
|
36
39
|
Hfn.api_user_route(file_name)
|
|
@@ -48,10 +51,10 @@ def file_inside_content(file_name: str):
|
|
|
48
51
|
Hfn.helper_function(file_name)
|
|
49
52
|
|
|
50
53
|
if file_name == 'templates/layouts/main.html':
|
|
51
|
-
Hfn.main_layout(file_name)
|
|
54
|
+
Hfn.main_layout(file_name, project_version)
|
|
52
55
|
|
|
53
56
|
if file_name == 'templates/layouts/auth.html':
|
|
54
|
-
Hfn.auth_layout(file_name)
|
|
57
|
+
Hfn.auth_layout(file_name, project_version)
|
|
55
58
|
|
|
56
59
|
if file_name == 'templates/pages/public/index.html':
|
|
57
60
|
Hfn.index(file_name)
|
|
@@ -67,11 +70,13 @@ def file_inside_content(file_name: str):
|
|
|
67
70
|
|
|
68
71
|
if file_name == 'templates/pages/auth/registater.html':
|
|
69
72
|
Hfn.registater(file_name)
|
|
70
|
-
|
|
71
73
|
|
|
72
|
-
if file_name == 'static/images/
|
|
74
|
+
if file_name == 'static/images/favicon.ico':
|
|
73
75
|
Hfn.static_logo(file_name)
|
|
74
76
|
|
|
77
|
+
if file_name == 'static/images/icon.png':
|
|
78
|
+
Hfn.static_icon(file_name)
|
|
79
|
+
|
|
75
80
|
# if file_name == 'main':
|
|
76
81
|
# pass
|
|
77
82
|
|
|
@@ -25,8 +25,20 @@ DATABASE_URL="sqlite:///./test.db"
|
|
|
25
25
|
ALLOW_ORIGIN=http://localhost:3000,http://localhost:8000
|
|
26
26
|
ALLOW_METHODS=GET,POST,PUT,DELETE,PATCH,OPTIONS
|
|
27
27
|
ALLOW_HEADERS=*
|
|
28
|
-
SESSION_SECRET="
|
|
28
|
+
SESSION_SECRET="aa0d-00e09_8032b8c"
|
|
29
29
|
|
|
30
|
+
# Database Selector ('mysql' OR 'sqlite')
|
|
31
|
+
CONNECTOR_TYPE="sqlite"
|
|
32
|
+
|
|
33
|
+
# SQLite Configuration (if CONNECTOR_TYPE='sqlite')
|
|
34
|
+
SQLITE_DB_PATH="./test.db"
|
|
35
|
+
|
|
36
|
+
# MySQL Configuration (if CONNECTOR_TYPE='mysql')
|
|
37
|
+
DB_HOST="localhost"
|
|
38
|
+
DB_PORT=4000
|
|
39
|
+
DB_USERNAME=""
|
|
40
|
+
DB_PASSWORD=""
|
|
41
|
+
DB_DATABASE="test"
|
|
30
42
|
""")
|
|
31
43
|
|
|
32
44
|
# ==========================================
|
|
@@ -43,6 +55,18 @@ ALLOW_METHODS=GET,POST,PUT,DELETE,PATCH,OPTIONS
|
|
|
43
55
|
ALLOW_HEADERS=*
|
|
44
56
|
SESSION_SECRET=""
|
|
45
57
|
|
|
58
|
+
# Database Selector ('mysql' OR 'sqlite')
|
|
59
|
+
CONNECTOR_TYPE="mysql"
|
|
60
|
+
|
|
61
|
+
# SQLite Configuration (if CONNECTOR_TYPE='sqlite')
|
|
62
|
+
SQLITE_DB_PATH="./test.db"
|
|
63
|
+
|
|
64
|
+
# MySQL Configuration (if CONNECTOR_TYPE='mysql')
|
|
65
|
+
DB_HOST="localhost"
|
|
66
|
+
DB_PORT=4000
|
|
67
|
+
DB_USERNAME=""
|
|
68
|
+
DB_PASSWORD=""
|
|
69
|
+
DB_DATABASE="test"
|
|
46
70
|
""")
|
|
47
71
|
|
|
48
72
|
|
|
@@ -76,6 +100,7 @@ python-multipart
|
|
|
76
100
|
argon2-cffi
|
|
77
101
|
passlib[bcrypt]
|
|
78
102
|
alembic
|
|
103
|
+
pymysql
|
|
79
104
|
""")
|
|
80
105
|
|
|
81
106
|
# ==========================================
|
|
@@ -95,7 +120,8 @@ def mainfile(file_nm: str):
|
|
|
95
120
|
with open(file_nm, "w" ,encoding="utf-8") as f:
|
|
96
121
|
f.write("""
|
|
97
122
|
import uvicorn
|
|
98
|
-
from config.app import app,
|
|
123
|
+
from config.app import app, Request, BASE_DIR
|
|
124
|
+
from fastapi.responses import FileResponse
|
|
99
125
|
|
|
100
126
|
# =================================
|
|
101
127
|
# ADD API route
|
|
@@ -112,7 +138,23 @@ from router.web import auth
|
|
|
112
138
|
from router.web import error
|
|
113
139
|
|
|
114
140
|
# ======================
|
|
115
|
-
#
|
|
141
|
+
# Global Favicon Icon
|
|
142
|
+
# ======================
|
|
143
|
+
FAVICON_PATH = BASE_DIR / "static" / "images" / "favicon.ico"
|
|
144
|
+
@app.get("/favicon.ico", include_in_schema=False)
|
|
145
|
+
async def favicon():
|
|
146
|
+
return FileResponse(FAVICON_PATH, media_type="image/x-icon")
|
|
147
|
+
|
|
148
|
+
# ======================
|
|
149
|
+
# Global Png Icon
|
|
150
|
+
# ======================
|
|
151
|
+
ICON_PATH = BASE_DIR / "static" / "images" / "icon.png"
|
|
152
|
+
@app.get("/icon.png", include_in_schema=False)
|
|
153
|
+
async def icon():
|
|
154
|
+
return FileResponse(ICON_PATH, media_type="image/png")
|
|
155
|
+
|
|
156
|
+
# ======================
|
|
157
|
+
# session or authention debug check
|
|
116
158
|
# ======================
|
|
117
159
|
@app.get("/debug")
|
|
118
160
|
async def debug(request: Request):
|
|
@@ -122,7 +164,7 @@ async def debug(request: Request):
|
|
|
122
164
|
}
|
|
123
165
|
|
|
124
166
|
if __name__ == "__main__":
|
|
125
|
-
uvicorn.run("main:app", host="
|
|
167
|
+
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|
|
126
168
|
""")
|
|
127
169
|
|
|
128
170
|
# ==========================================
|
|
@@ -132,8 +174,8 @@ def config_app(file_nm: str):
|
|
|
132
174
|
with open(file_nm, "w" ,encoding="utf-8") as f:
|
|
133
175
|
f.write("""
|
|
134
176
|
import os
|
|
135
|
-
from fastapi import FastAPI,Request
|
|
136
|
-
from fastapi.responses import RedirectResponse
|
|
177
|
+
from fastapi import FastAPI, Request
|
|
178
|
+
from fastapi.responses import RedirectResponse
|
|
137
179
|
from fastapi.templating import Jinja2Templates
|
|
138
180
|
from fastapi.staticfiles import StaticFiles
|
|
139
181
|
from dotenv import load_dotenv
|
|
@@ -214,14 +256,30 @@ from dotenv import load_dotenv
|
|
|
214
256
|
|
|
215
257
|
load_dotenv()
|
|
216
258
|
|
|
217
|
-
|
|
259
|
+
CONNECTOR_TYPE = os.getenv("CONNECTOR_TYPE", "sqlite").lower()
|
|
218
260
|
|
|
261
|
+
if CONNECTOR_TYPE == "mysql":
|
|
262
|
+
db_user = os.getenv("DB_USERNAME")
|
|
263
|
+
db_pass = os.getenv("DB_PASSWORD")
|
|
264
|
+
db_host = os.getenv("DB_HOST")
|
|
265
|
+
db_port = os.getenv("DB_PORT", "4000")
|
|
266
|
+
db_name = os.getenv("DB_DATABASE")
|
|
267
|
+
|
|
268
|
+
DATABASE_URL = f"mysql+pymysql://{db_user}:{db_pass}@{db_host}:{db_port}/{db_name}?ssl_verify_cert=true&ssl_verify_identity=true"
|
|
269
|
+
connect_args = {} # MySQL extra parameters & args, like SSL
|
|
270
|
+
|
|
271
|
+
else:
|
|
272
|
+
sqlite_path = os.getenv("SQLITE_DB_PATH", "./test.db")
|
|
273
|
+
DATABASE_URL = f"sqlite:///{sqlite_path}"
|
|
274
|
+
connect_args = {"check_same_thread": False} # only SQLite
|
|
275
|
+
|
|
276
|
+
# SQLAlchemy Engine
|
|
219
277
|
engine = create_engine(
|
|
220
278
|
DATABASE_URL,
|
|
221
279
|
pool_size=10,
|
|
222
280
|
max_overflow=20,
|
|
223
281
|
pool_pre_ping=True,
|
|
224
|
-
connect_args=
|
|
282
|
+
connect_args=connect_args
|
|
225
283
|
)
|
|
226
284
|
|
|
227
285
|
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
|
|
@@ -238,37 +296,54 @@ def get_db():
|
|
|
238
296
|
""")
|
|
239
297
|
|
|
240
298
|
# ==========================================
|
|
241
|
-
# app/
|
|
299
|
+
# app/__init__.py file create
|
|
300
|
+
# ==========================================
|
|
301
|
+
def models_init(file_nm: str):
|
|
302
|
+
with open(file_nm, "w" ,encoding="utf-8") as f:
|
|
303
|
+
f.write("""
|
|
304
|
+
from app.models.users import User
|
|
305
|
+
|
|
306
|
+
# Add More Models
|
|
307
|
+
""")
|
|
308
|
+
|
|
309
|
+
# ==========================================
|
|
310
|
+
# app/models/users.py file create
|
|
242
311
|
# ==========================================
|
|
243
|
-
def
|
|
312
|
+
def models_user(file_nm: str):
|
|
244
313
|
with open(file_nm, "w" ,encoding="utf-8") as f:
|
|
245
314
|
f.write("""
|
|
246
|
-
from sqlalchemy import Column, Integer, String, Boolean, DateTime
|
|
315
|
+
from sqlalchemy import Column, Integer, String, Boolean, DateTime, Enum
|
|
247
316
|
from datetime import datetime
|
|
248
|
-
from .database import Base
|
|
317
|
+
from app.database.database import Base
|
|
249
318
|
|
|
250
319
|
class User(Base):
|
|
251
320
|
__tablename__ = "users"
|
|
252
321
|
|
|
253
322
|
id = Column(Integer, primary_key=True, index=True)
|
|
254
323
|
username = Column(String(50), unique=True, nullable=False)
|
|
255
|
-
name = Column(String, index=True)
|
|
256
|
-
email = Column(String, unique=True, index=True)
|
|
257
|
-
|
|
324
|
+
name = Column(String(200), index=True)
|
|
325
|
+
email = Column(String(255), unique=True, index=True)
|
|
326
|
+
role = Column(Enum('users', 'admin', 'moderator', name='user_role'),nullable=False,server_default='users')
|
|
327
|
+
password = Column(String(255), nullable=False)
|
|
258
328
|
is_active = Column(Boolean, default=True)
|
|
259
329
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
260
330
|
|
|
261
|
-
# Add More Table ....
|
|
262
|
-
|
|
263
331
|
""")
|
|
264
332
|
|
|
265
333
|
# ==========================================
|
|
266
|
-
# static/images/
|
|
334
|
+
# static/images/favicon.ico file create
|
|
267
335
|
# ==========================================
|
|
268
336
|
def static_logo(file_nm: str):
|
|
269
|
-
logo_path = files("shafikul_cli.asset").joinpath("
|
|
337
|
+
logo_path = files("shafikul_cli.asset").joinpath("favicon.ico")
|
|
270
338
|
shutil.copy(logo_path, file_nm)
|
|
271
339
|
|
|
340
|
+
# ==========================================
|
|
341
|
+
# static/images/icon.png file create
|
|
342
|
+
# ==========================================
|
|
343
|
+
def static_icon(file_nm: str):
|
|
344
|
+
icon_path = files("shafikul_cli.asset").joinpath("icon.png")
|
|
345
|
+
shutil.copy(icon_path, file_nm)
|
|
346
|
+
|
|
272
347
|
# ==========================================
|
|
273
348
|
# router/api/users.py file create
|
|
274
349
|
# ==========================================
|
|
@@ -294,10 +369,11 @@ def index():
|
|
|
294
369
|
def web_auth_route(file_nm: str):
|
|
295
370
|
with open(file_nm, "w" ,encoding="utf-8") as f:
|
|
296
371
|
f.write("""
|
|
297
|
-
from config.app import app, templates, os, Request
|
|
372
|
+
from config.app import app, templates, os, Request
|
|
373
|
+
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
298
374
|
from fastapi import Form,Depends
|
|
299
375
|
from sqlalchemy.orm import Session
|
|
300
|
-
from app.database import get_db
|
|
376
|
+
from app.database.database import get_db
|
|
301
377
|
from app.models import User
|
|
302
378
|
from utility.helper_function import hash_password, verify_password
|
|
303
379
|
import random, uuid
|
|
@@ -433,7 +509,8 @@ def dashboard(request: Request,db: Session = Depends(get_db)):
|
|
|
433
509
|
def web_error_route(file_nm: str):
|
|
434
510
|
with open(file_nm, "w" ,encoding="utf-8") as f:
|
|
435
511
|
f.write("""
|
|
436
|
-
from config.app import app, templates, Request
|
|
512
|
+
from config.app import app, templates, Request
|
|
513
|
+
from fastapi.responses import JSONResponse
|
|
437
514
|
from starlette.exceptions import HTTPException as NotFoundException
|
|
438
515
|
|
|
439
516
|
@app.exception_handler(NotFoundException)
|
|
@@ -458,7 +535,8 @@ async def http_exception_handler(request: Request, exc: NotFoundException):
|
|
|
458
535
|
def web_public_route(file_nm: str):
|
|
459
536
|
with open(file_nm, "w" ,encoding="utf-8") as f:
|
|
460
537
|
f.write("""
|
|
461
|
-
from config.app import app, templates, os,
|
|
538
|
+
from config.app import app, templates, os, Request
|
|
539
|
+
from fastapi.responses import HTMLResponse
|
|
462
540
|
|
|
463
541
|
@app.get('/', response_class=HTMLResponse)
|
|
464
542
|
def index(request: Request):
|
|
@@ -530,12 +608,8 @@ def index(file_nm: str):
|
|
|
530
608
|
text-center transform transition-all hover:shadow-2xl">
|
|
531
609
|
|
|
532
610
|
<!-- Icon And Logo -->
|
|
533
|
-
<div class="inline-flex p-3 rounded-2xl
|
|
534
|
-
<
|
|
535
|
-
viewBox="0 0 24 24">
|
|
536
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
537
|
-
d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
|
538
|
-
</svg>
|
|
611
|
+
<div class="inline-flex p-3 rounded-2xl mb-6">
|
|
612
|
+
<img src="/icon.png" alt="Icon" class="w-[5rem] h-[5rem] rounded-full animate-float">
|
|
539
613
|
</div>
|
|
540
614
|
|
|
541
615
|
<h1
|
|
@@ -551,12 +625,12 @@ def index(file_nm: str):
|
|
|
551
625
|
<div class="space-y-4">
|
|
552
626
|
<a href="/signup"
|
|
553
627
|
class="block w-full py-3 px-6 bg-indigo-600 hover:bg-indigo-700 text-white font-semibold rounded-xl shadow-lg shadow-indigo-500/30 transition-all hover:-translate-y-1">
|
|
554
|
-
|
|
628
|
+
Sign Up
|
|
555
629
|
</a>
|
|
556
630
|
|
|
557
631
|
<a href="/login"
|
|
558
632
|
class="block w-full py-3 px-6 bg-white/50 dark:bg-white/10 hover:bg-white/80 dark:hover:bg-white/20 text-slate-800 dark:text-white font-semibold rounded-xl border border-white/20 transition-all">
|
|
559
|
-
|
|
633
|
+
Login
|
|
560
634
|
</a>
|
|
561
635
|
</div>
|
|
562
636
|
|
|
@@ -1003,7 +1077,7 @@ Not Found
|
|
|
1003
1077
|
# ==========================================
|
|
1004
1078
|
# templates/layouts/main.html file create
|
|
1005
1079
|
# ==========================================
|
|
1006
|
-
def main_layout(file_nm: str):
|
|
1080
|
+
def main_layout(file_nm: str, project_version):
|
|
1007
1081
|
with open(file_nm, "w" ,encoding="utf-8") as f:
|
|
1008
1082
|
f.write("""
|
|
1009
1083
|
<!DOCTYPE html>
|
|
@@ -1012,11 +1086,37 @@ def main_layout(file_nm: str):
|
|
|
1012
1086
|
<head>
|
|
1013
1087
|
<meta charset="UTF-8">
|
|
1014
1088
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1015
|
-
<title>Shafikul FastAPI App {% block title %}
|
|
1016
|
-
<
|
|
1089
|
+
<title>Shafikul FastAPI App {% block title %}v""" + project_version + """{% endblock %}</title>
|
|
1090
|
+
<meta name="description" content="Generate production-ready boilerplate with user authentication, database models, and a complete folder structure in seconds.">
|
|
1091
|
+
<meta name="keywords" content="FastAPI, Python, Web Development">
|
|
1092
|
+
<meta name="author" content="Shafikul Islam">
|
|
1093
|
+
<meta name="robots" content="index, follow">
|
|
1094
|
+
<meta name="language" content="English">
|
|
1095
|
+
<meta name="theme-color" content="#0f172a">
|
|
1096
|
+
<meta name="color-scheme" content="dark light">
|
|
1097
|
+
<meta name="application-name" content="Shafikul FastAPI App">
|
|
1098
|
+
<meta name="referrer" content="strict-origin-when-cross-origin">
|
|
1099
|
+
<meta name="mobile-web-app-capable" content="yes">
|
|
1100
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
1101
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
1102
|
+
<meta name="apple-mobile-web-app-title" content="Shafikul FastAPI App">
|
|
1103
|
+
<meta http-equiv="X-Content-Type-Options" content="nosniff">
|
|
1104
|
+
<link rel="icon" href="{{ request.base_url }}favicon.ico">
|
|
1105
|
+
<link rel="apple-touch-icon" href="{{ request.base_url }}icon.png">
|
|
1106
|
+
<link rel="canonical" href="{{ request.base_url }}">
|
|
1107
|
+
<meta property="og:type" content="website">
|
|
1108
|
+
<meta property="og:title" content="Shafikul FastAPI App — A Modern Web Application">
|
|
1109
|
+
<meta property="og:description" content="A modern web application built with FastAPI and Python.">
|
|
1110
|
+
<meta property="og:image" content="{{ request.base_url }}icon.png">
|
|
1111
|
+
<meta property="og:url" content="{{ request.base_url }}">
|
|
1112
|
+
<meta property="og:site_name" content="Shafikul FastAPI App">
|
|
1113
|
+
<meta property="og:locale" content="en_US">
|
|
1114
|
+
<meta name="twitter:card" content="summary_large_image">
|
|
1115
|
+
<meta name="twitter:title" content="Shafikul FastAPI App — A Modern Web Application">
|
|
1116
|
+
<meta name="twitter:description" content="A modern web application built with FastAPI and Python.">
|
|
1117
|
+
<meta name="twitter:image" content="{{ request.base_url }}icon.png">
|
|
1017
1118
|
|
|
1018
1119
|
{% block add_head %}{% endblock %}
|
|
1019
|
-
|
|
1020
1120
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
1021
1121
|
<script>
|
|
1022
1122
|
tailwind.config = {
|
|
@@ -1056,7 +1156,7 @@ def main_layout(file_nm: str):
|
|
|
1056
1156
|
# ==========================================
|
|
1057
1157
|
# templates/layouts/auth.html file create
|
|
1058
1158
|
# ==========================================
|
|
1059
|
-
def auth_layout(file_nm: str):
|
|
1159
|
+
def auth_layout(file_nm: str, project_version):
|
|
1060
1160
|
with open(file_nm, "w" ,encoding="utf-8") as f:
|
|
1061
1161
|
f.write("""
|
|
1062
1162
|
<!DOCTYPE html>
|
|
@@ -1065,9 +1165,21 @@ def auth_layout(file_nm: str):
|
|
|
1065
1165
|
<head>
|
|
1066
1166
|
<meta charset="UTF-8">
|
|
1067
1167
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1068
|
-
<title>Shafikul - {% block title %}
|
|
1069
|
-
<
|
|
1168
|
+
<title>Shafikul Auth Page - {% block title %}v""" + project_version + """{% endblock %}</title>
|
|
1169
|
+
<meta name="description" content="Generate production-ready boilerplate with user authentication, database models, and a complete folder structure in seconds.">
|
|
1170
|
+
<meta name="author" content="Shafikul Islam">
|
|
1171
|
+
<meta name="robots" content="nofollow">
|
|
1172
|
+
<meta name="language" content="English">
|
|
1173
|
+
<meta name="application-name" content="Shafikul FastAPI App Auth Page">
|
|
1174
|
+
<meta name="referrer" content="strict-origin-when-cross-origin">
|
|
1175
|
+
<meta name="mobile-web-app-capable" content="yes">
|
|
1176
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
1177
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
1178
|
+
<meta name="apple-mobile-web-app-title" content="Shafikul FastAPI App">
|
|
1179
|
+
<meta http-equiv="X-Content-Type-Options" content="nosniff">
|
|
1180
|
+
<link rel="shortcut icon" href="{{ request.base_url }}favicon.ico">
|
|
1070
1181
|
|
|
1182
|
+
|
|
1071
1183
|
{% block add_head %}{% endblock %}
|
|
1072
1184
|
|
|
1073
1185
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
@@ -1827,15 +1939,22 @@ def edit_migration_env():
|
|
|
1827
1939
|
import_added = False
|
|
1828
1940
|
load_dotenv_added = False
|
|
1829
1941
|
db_url_added = False
|
|
1942
|
+
skipping_connectable = False
|
|
1830
1943
|
|
|
1831
1944
|
for line in lines:
|
|
1832
1945
|
stripped = line.strip()
|
|
1833
1946
|
|
|
1947
|
+
# engine_from_config(...) block-er baki line-gulo skip kora
|
|
1948
|
+
if skipping_connectable:
|
|
1949
|
+
if stripped == ")":
|
|
1950
|
+
skipping_connectable = False
|
|
1951
|
+
continue
|
|
1952
|
+
|
|
1834
1953
|
# ১. ইম্পোর্ট অ্যাড করা (একবারই হবে)
|
|
1835
1954
|
if not import_added and ("from alembic import context" in stripped or stripped.startswith("import")):
|
|
1836
1955
|
new_lines.append("import os\n")
|
|
1837
|
-
new_lines.append("from app.database import Base\n")
|
|
1838
|
-
new_lines.append("
|
|
1956
|
+
new_lines.append("from app.database.database import Base, engine, DATABASE_URL\n")
|
|
1957
|
+
new_lines.append("import app.models\n")
|
|
1839
1958
|
new_lines.append("from dotenv import load_dotenv\n\n")
|
|
1840
1959
|
import_added = True
|
|
1841
1960
|
|
|
@@ -1849,12 +1968,24 @@ def edit_migration_env():
|
|
|
1849
1968
|
new_lines.append("target_metadata = Base.metadata\n")
|
|
1850
1969
|
continue # এটা দিলে নিচের new_lines.append(line) রান হবে না, ফলে পুরনো লাইনটি বাদ যাবে
|
|
1851
1970
|
|
|
1971
|
+
# 4. connectable block: original line likhar AGE-i replace kora
|
|
1972
|
+
if stripped == "connectable = engine_from_config(":
|
|
1973
|
+
indent = line[: len(line) - len(line.lstrip())]
|
|
1974
|
+
new_lines.append(f"{indent}# connectable = engine_from_config(\n")
|
|
1975
|
+
new_lines.append(f"{indent}# config.get_section(config.config_ini_section, {{}}),\n")
|
|
1976
|
+
new_lines.append(f'{indent}# prefix="sqlalchemy.",\n')
|
|
1977
|
+
new_lines.append(f"{indent}# poolclass=pool.NullPool,\n")
|
|
1978
|
+
new_lines.append(f"{indent}# )\n")
|
|
1979
|
+
new_lines.append(f"{indent}connectable = engine\n")
|
|
1980
|
+
skipping_connectable = True
|
|
1981
|
+
continue
|
|
1982
|
+
|
|
1852
1983
|
# মূল লাইনটি অ্যাড করা (যদি উপরের 'continue' হিট না করে)
|
|
1853
1984
|
new_lines.append(line)
|
|
1854
1985
|
|
|
1855
1986
|
# ৪. config = context.config লাইনের পরে DATABASE_URL সেট করা
|
|
1856
1987
|
if "config = context.config" in stripped and not db_url_added:
|
|
1857
|
-
new_lines.append('config.set_main_option("sqlalchemy.url",
|
|
1988
|
+
new_lines.append('config.set_main_option("sqlalchemy.url", DATABASE_URL)\n\n')
|
|
1858
1989
|
db_url_added = True
|
|
1859
1990
|
|
|
1860
1991
|
with open(file_path, "w", encoding="utf-8") as f:
|
|
@@ -33,15 +33,9 @@ def alembic_init():
|
|
|
33
33
|
|
|
34
34
|
try:
|
|
35
35
|
subprocess.run(
|
|
36
|
-
["alembic", "init", "migrations"],
|
|
36
|
+
[sys.executable, "-m", "alembic", "init", "migrations"],
|
|
37
37
|
check=True
|
|
38
38
|
)
|
|
39
|
-
subprocess.run([
|
|
40
|
-
sys.executable,
|
|
41
|
-
"alembic",
|
|
42
|
-
"init",
|
|
43
|
-
"migrations"
|
|
44
|
-
])
|
|
45
39
|
console.print("[green]Alembic initialized successfully[/]")
|
|
46
40
|
except subprocess.CalledProcessError as e:
|
|
47
41
|
console.print(f"[red]Failed to initialize Alembic: {e}[/]")
|
|
@@ -61,13 +55,13 @@ def user_table_autogenerate():
|
|
|
61
55
|
|
|
62
56
|
try:
|
|
63
57
|
subprocess.run(
|
|
64
|
-
["alembic", "revision", "--autogenerate", "-m", "create users table"],
|
|
58
|
+
[ sys.executable, "-m", "alembic", "revision", "--autogenerate", "-m", "create users table"],
|
|
65
59
|
check=True
|
|
66
60
|
)
|
|
67
61
|
|
|
68
62
|
console.print("[green]Migrate Table In DB[/]")
|
|
69
63
|
subprocess.run(
|
|
70
|
-
["alembic", "upgrade", "head"],
|
|
64
|
+
[sys.executable, "-m", "alembic", "upgrade", "head"],
|
|
71
65
|
check=True
|
|
72
66
|
)
|
|
73
67
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shafikul_cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.6
|
|
4
4
|
Summary: CLI tool for FastAPI scaffolding with router, models, database, and templates.
|
|
5
5
|
Author-email: Md Shafikul Islam <buildwithshafikul@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/build-with-shafikul/shafikul_cli
|
|
@@ -175,7 +175,7 @@ your-project/
|
|
|
175
175
|
│
|
|
176
176
|
├── 📂 static/ # Served static files
|
|
177
177
|
│ └── images/
|
|
178
|
-
│ └──
|
|
178
|
+
│ └── icon.png
|
|
179
179
|
│
|
|
180
180
|
├── 📂 utility/ # Helper functions
|
|
181
181
|
│ └── helper_function.py
|
|
@@ -10,6 +10,7 @@ shafikul_cli.egg-info/entry_points.txt
|
|
|
10
10
|
shafikul_cli.egg-info/requires.txt
|
|
11
11
|
shafikul_cli.egg-info/top_level.txt
|
|
12
12
|
shafikul_cli/asset/__init__.py
|
|
13
|
+
shafikul_cli/asset/icon.png
|
|
13
14
|
shafikul_cli/asset/logo.png
|
|
14
15
|
shafikul_cli/utility/__init__.py
|
|
15
16
|
shafikul_cli/utility/create_file_content.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|