mirror of
				https://github.com/aykhans/portfolio-blog.git
				synced 2025-10-31 14:09:58 +00:00 
			
		
		
		
	Added create_user command & edited imports
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -2,3 +2,4 @@ | |||||||
| __pycache__ | __pycache__ | ||||||
| media | media | ||||||
| *.env | *.env | ||||||
|  | test.py | ||||||
| @@ -1,4 +1,3 @@ | |||||||
| from typing import Optional |  | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
|  |  | ||||||
| from pydantic_settings import BaseSettings | from pydantic_settings import BaseSettings | ||||||
|   | |||||||
| @@ -1,5 +1,11 @@ | |||||||
| from datetime import datetime, timedelta | from datetime import ( | ||||||
| from typing import Any, Union |     datetime, | ||||||
|  |     timedelta | ||||||
|  | ) | ||||||
|  | from typing import ( | ||||||
|  |     Any, | ||||||
|  |     Union | ||||||
|  | ) | ||||||
|  |  | ||||||
| from jose import jwt | from jose import jwt | ||||||
| from passlib.context import CryptContext | from passlib.context import CryptContext | ||||||
|   | |||||||
| @@ -1,7 +1,18 @@ | |||||||
| from typing import Any, Dict, Generic, List, Optional, Type, TypeVar, Union | from typing import ( | ||||||
|  |     Any, | ||||||
|  |     Dict, | ||||||
|  |     Generic, | ||||||
|  |     List, | ||||||
|  |     Optional, | ||||||
|  |     Type, | ||||||
|  |     TypeVar, | ||||||
|  |     Union | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | from pydantic import BaseModel | ||||||
|  |  | ||||||
| from fastapi.encoders import jsonable_encoder | from fastapi.encoders import jsonable_encoder | ||||||
| from pydantic import BaseModel |  | ||||||
| from sqlalchemy.future import select | from sqlalchemy.future import select | ||||||
| from sqlalchemy.orm import Session | from sqlalchemy.orm import Session | ||||||
|  |  | ||||||
| @@ -37,6 +48,14 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): | |||||||
|         obj = await db.execute(q) |         obj = await db.execute(q) | ||||||
|         return obj.scalars() |         return obj.scalars() | ||||||
|  |  | ||||||
|  |     def sync_get_multi( | ||||||
|  |         self, db: Session, *, skip: int = 0, limit: int = 100 | ||||||
|  |     ) -> List[ModelType]: | ||||||
|  |  | ||||||
|  |         q = select(self.model).offset(skip).limit(limit).order_by(self.model.id.desc()) | ||||||
|  |         obj = db.execute(q) | ||||||
|  |         return obj.scalars() | ||||||
|  |  | ||||||
|     async def create(self, db: Session, *, obj_in: CreateSchemaType) -> ModelType: |     async def create(self, db: Session, *, obj_in: CreateSchemaType) -> ModelType: | ||||||
|         obj_in_data = jsonable_encoder(obj_in) |         obj_in_data = jsonable_encoder(obj_in) | ||||||
|         db_obj = self.model(**obj_in_data)  # type: ignore |         db_obj = self.model(**obj_in_data)  # type: ignore | ||||||
|   | |||||||
| @@ -15,6 +15,11 @@ class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]): | |||||||
|         obj = await db.execute(q) |         obj = await db.execute(q) | ||||||
|         return obj.scalar_one_or_none() |         return obj.scalar_one_or_none() | ||||||
|  |  | ||||||
|  |     def sync_get_by_email(self, db: Session, *, email: str) -> Optional[User]: | ||||||
|  |         q = select(self.model).where(self.model.email == email) | ||||||
|  |         obj = db.execute(q) | ||||||
|  |         return obj.scalar_one_or_none() | ||||||
|  |  | ||||||
|     async def create(self, db: Session, *, obj_in: UserCreate) -> User: |     async def create(self, db: Session, *, obj_in: UserCreate) -> User: | ||||||
|         db_obj = User( |         db_obj = User( | ||||||
|             email=obj_in.email, |             email=obj_in.email, | ||||||
| @@ -29,6 +34,20 @@ class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]): | |||||||
|  |  | ||||||
|         return db_obj |         return db_obj | ||||||
|  |  | ||||||
|  |     def sync_create(self, db: Session, *, obj_in: UserCreate) -> User: | ||||||
|  |         db_obj = User( | ||||||
|  |             email=obj_in.email, | ||||||
|  |             hashed_password=get_password_hash(obj_in.password), | ||||||
|  |             username=obj_in.username, | ||||||
|  |             is_superuser=obj_in.is_superuser, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         db.add(db_obj) | ||||||
|  |         db.commit() | ||||||
|  |         db.refresh(db_obj) | ||||||
|  |  | ||||||
|  |         return db_obj | ||||||
|  |  | ||||||
|     async def update( |     async def update( | ||||||
|         self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[str, Any]] |         self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[str, Any]] | ||||||
|     ) -> User: |     ) -> User: | ||||||
|   | |||||||
| @@ -1,7 +1,10 @@ | |||||||
| from typing import Any | from typing import Any | ||||||
|  |  | ||||||
| from sqlalchemy.ext.declarative import as_declarative, declared_attr | from sqlalchemy.ext.declarative import as_declarative, declared_attr | ||||||
| from sqlalchemy import DateTime, Column | from sqlalchemy import ( | ||||||
|  |     DateTime, | ||||||
|  |     Column | ||||||
|  | ) | ||||||
| from sqlalchemy.sql import func | from sqlalchemy.sql import func | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ from app.schemas.login import LoginForm | |||||||
| from app.schemas.post import Post, PostCreate, PostUpdate | from app.schemas.post import Post, PostCreate, PostUpdate | ||||||
| from sqlalchemy.ext.asyncio import AsyncSession | from sqlalchemy.ext.asyncio import AsyncSession | ||||||
| from app.schemas.user import User, UserCreate | from app.schemas.user import User, UserCreate | ||||||
| from fastapi.responses import HTMLResponse | from fastapi.responses import FileResponse, HTMLResponse | ||||||
| from app.views.depends import get_async_db, handle_image | from app.views.depends import get_async_db, handle_image | ||||||
|  |  | ||||||
| from typing import Annotated, Any | from typing import Annotated, Any | ||||||
| @@ -46,6 +46,11 @@ async def validation_exception_handler(request, exc): | |||||||
|     return await request_validation_exception_handler(request, exc) |     return await request_validation_exception_handler(request, exc) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @app.exception_handler(404) | ||||||
|  | async def custom_404_handler(_, __): | ||||||
|  |     return FileResponse(settings.STATIC_FOLDER / '404.jpg') | ||||||
|  |  | ||||||
|  |  | ||||||
| # @app.post("/login", response_model=JWTToken) | # @app.post("/login", response_model=JWTToken) | ||||||
| # async def login( | # async def login( | ||||||
| #     db: AsyncSession = Depends(get_async_db), | #     db: AsyncSession = Depends(get_async_db), | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | from contextlib import contextmanager | ||||||
| from slugify import slugify | from slugify import slugify | ||||||
|  |  | ||||||
| from sqlalchemy.orm.base import NO_VALUE | from sqlalchemy.orm.base import NO_VALUE | ||||||
| @@ -34,8 +35,7 @@ from app.core.config import settings | |||||||
| def generate_slug(target, value, oldvalue, initiator): | def generate_slug(target, value, oldvalue, initiator): | ||||||
|     slug = slugify(value) |     slug = slugify(value) | ||||||
|  |  | ||||||
|     db = next(get_db()) |     with contextmanager(get_db)() as db: | ||||||
|  |  | ||||||
|         number = 1 |         number = 1 | ||||||
|         temp_slug = slug |         temp_slug = slug | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,7 +10,8 @@ from .user import ( | |||||||
|     User, |     User, | ||||||
|     UserCreate, |     UserCreate, | ||||||
|     UserInDBBase, |     UserInDBBase, | ||||||
|     UserUpdate |     UserUpdate, | ||||||
|  |     UserBase | ||||||
| ) | ) | ||||||
| from .login import ( | from .login import ( | ||||||
|     JWTToken, |     JWTToken, | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| from dataclasses import dataclass | from dataclasses import dataclass | ||||||
| from typing import Optional | from typing import Optional | ||||||
|  |  | ||||||
| from fastapi import Form | from fastapi import Form | ||||||
|  |  | ||||||
| from pydantic import ( | from pydantic import ( | ||||||
|   | |||||||
| @@ -1,6 +1,9 @@ | |||||||
| from typing import Optional | from typing import Optional | ||||||
|  |  | ||||||
| from pydantic import BaseModel, EmailStr | from pydantic import ( | ||||||
|  |     BaseModel, | ||||||
|  |     EmailStr | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserBase(BaseModel): | class UserBase(BaseModel): | ||||||
|   | |||||||
| @@ -13,28 +13,53 @@ | |||||||
|     <div class="container" style="margin-top: 10rem;"> |     <div class="container" style="margin-top: 10rem;"> | ||||||
|         <div class="row"> |         <div class="row"> | ||||||
|             <div class="col-md-10 col-lg-4 mx-auto"> |             <div class="col-md-10 col-lg-4 mx-auto"> | ||||||
|                 <form> |                 <form id="loginForm"> | ||||||
|                     <div class="form-outline mb-4"> |                     <div class="form-outline mb-4"> | ||||||
|                         <input type="email" id="form2Example1" class="form-control" /> |                         <input type="email" id="form2Example1" name="email" class="form-control" /> | ||||||
|                         <label class="form-label" for="form2Example1">Email address</label> |                         <label class="form-label" for="form2Example1">Email address</label> | ||||||
|                     </div> |                     </div> | ||||||
|  |  | ||||||
|                     <div class="form-outline mb-4"> |                     <div class="form-outline mb-4"> | ||||||
|                         <input type="password" id="form2Example2" class="form-control" /> |                         <input type="password" id="form2Example2" name="password" class="form-control" /> | ||||||
|                         <label class="form-label" for="form2Example2">Password</label> |                         <label class="form-label" for="form2Example2">Password</label> | ||||||
|                     </div> |                     </div> | ||||||
|  |  | ||||||
|                     <button type="button" class="btn btn-primary btn-block mb-4">Sign in</button> |                     <button type="sumbit" class="btn btn-primary btn-block mb-4">Sign in</button> | ||||||
|                 </form> |                 </form> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     <script src="static/bootstrap/js/bootstrap.min.js"></script> |     <script src="static/bootstrap/js/bootstrap.min.js"></script> | ||||||
|  |     <script> | ||||||
|  |         function handleSubmit(event) { | ||||||
|  |             event.preventDefault(); | ||||||
|  |             const form = document.getElementById('loginForm'); | ||||||
|  |             const formData = new FormData(form); | ||||||
|  |             console.log(formData); | ||||||
|  |  | ||||||
|  |             fetch('{{login_url}}', { | ||||||
|  |                 method: 'POST', | ||||||
|  |                 body: formData, | ||||||
|  |             }) | ||||||
|  |             .then(response => response.json()) | ||||||
|  |             .then(data => { | ||||||
|  |                 const accessToken = data.access_token; | ||||||
|  |                 localStorage.setItem('accessToken', accessToken) | ||||||
|  |             }) | ||||||
|  |             .catch(error => { | ||||||
|  |                 console.error('Login error:', error); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const loginForm = document.getElementById('loginForm'); | ||||||
|  |         loginForm.addEventListener('submit', handleSubmit); | ||||||
|  |     </script> | ||||||
| </body> | </body> | ||||||
|  |  | ||||||
| </html> | </html> | ||||||
|  |  | ||||||
|  |  | ||||||
| <!-- <!DOCTYPE html> | <!-- <!DOCTYPE html> | ||||||
| <html> | <html> | ||||||
| <head> | <head> | ||||||
| @@ -46,32 +71,5 @@ | |||||||
|         <input type="password" name="password" placeholder="Password"> |         <input type="password" name="password" placeholder="Password"> | ||||||
|         <button type="submit">Login</button> |         <button type="submit">Login</button> | ||||||
|     </form> |     </form> | ||||||
|  |  | ||||||
|     <script> |  | ||||||
|         // Function to handle the form submission |  | ||||||
|         function handleSubmit(event) { |  | ||||||
|             event.preventDefault(); |  | ||||||
|             const form = document.getElementById('loginForm'); |  | ||||||
|             const formData = new FormData(form); |  | ||||||
|  |  | ||||||
|             fetch('/login', { |  | ||||||
|                 method: 'POST', // Adjust the HTTP method if needed |  | ||||||
|                 body: formData, |  | ||||||
|             }) |  | ||||||
|             .then(response => response.json()) |  | ||||||
|             .then(data => { |  | ||||||
|                 const accessToken = data.access_token; |  | ||||||
|                 console.log(accessToken); |  | ||||||
|                 document.cookie = `access_token=${accessToken}`; |  | ||||||
|             }) |  | ||||||
|             .catch(error => { |  | ||||||
|                 console.error('Login error:', error); |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Add a submit event listener to the form |  | ||||||
|         const loginForm = document.getElementById('loginForm'); |  | ||||||
|         loginForm.addEventListener('submit', handleSubmit); |  | ||||||
|     </script> |  | ||||||
| </body> | </body> | ||||||
| </html> --> | </html> --> | ||||||
|   | |||||||
| @@ -1 +0,0 @@ | |||||||
| <h1>Navbar</h1> |  | ||||||
| @@ -0,0 +1,119 @@ | |||||||
|  | from sys import path | ||||||
|  | path.append('/src') | ||||||
|  |  | ||||||
|  | from contextlib import contextmanager | ||||||
|  | from typing import Optional | ||||||
|  |  | ||||||
|  | from pydantic import ( | ||||||
|  |     EmailStr, | ||||||
|  |     ValidationError, | ||||||
|  |     ConfigDict | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | from app import crud | ||||||
|  | from app.schemas import UserCreate | ||||||
|  | from app.views.depends import get_db | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserCreateCommand(UserCreate): | ||||||
|  |     model_config = ConfigDict(validate_assignment=True) | ||||||
|  |  | ||||||
|  |     email: Optional[EmailStr] = None | ||||||
|  |     password: Optional[str] = None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     user_in = UserCreateCommand() | ||||||
|  |  | ||||||
|  |     while 1: | ||||||
|  |         email = input('*Email: ') | ||||||
|  |  | ||||||
|  |         if not email: | ||||||
|  |             print('Email is required\n') | ||||||
|  |             continue | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             user_in.email = email | ||||||
|  |  | ||||||
|  |         except ValidationError as e: | ||||||
|  |             print('\n', e, end='\n\n') | ||||||
|  |             continue | ||||||
|  |  | ||||||
|  |         with contextmanager(get_db)() as db: | ||||||
|  |             user = crud.user.sync_get_by_email( | ||||||
|  |                 db, | ||||||
|  |                 email=user_in.email | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         if user: | ||||||
|  |             print('User already exists\n') | ||||||
|  |             continue | ||||||
|  |  | ||||||
|  |         break | ||||||
|  |  | ||||||
|  |     while 1: | ||||||
|  |         username = input('Username: ') | ||||||
|  |  | ||||||
|  |         if username: | ||||||
|  |             try: | ||||||
|  |                 user_in.username = username | ||||||
|  |  | ||||||
|  |             except ValidationError as e: | ||||||
|  |                 print('\n', e, end='\n\n') | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |         break | ||||||
|  |  | ||||||
|  |     while 1: | ||||||
|  |         password = input('*Password: ') | ||||||
|  |  | ||||||
|  |         if not password: | ||||||
|  |             print('Password is required\n') | ||||||
|  |             continue | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             user_in.password = password | ||||||
|  |  | ||||||
|  |         except ValidationError as e: | ||||||
|  |             print('\n', e, end='\n\n') | ||||||
|  |             continue | ||||||
|  |  | ||||||
|  |         break | ||||||
|  |  | ||||||
|  |     while 1: | ||||||
|  |         is_active = input('Is active? y/n (y): ') or 'y' | ||||||
|  |  | ||||||
|  |         if is_active == 'y': | ||||||
|  |             user_in.is_active = True | ||||||
|  |  | ||||||
|  |         elif is_active == 'n': | ||||||
|  |             user_in.is_active = False | ||||||
|  |  | ||||||
|  |         else: | ||||||
|  |             print('Invalid input\n') | ||||||
|  |             continue | ||||||
|  |  | ||||||
|  |         break | ||||||
|  |  | ||||||
|  |     while 1: | ||||||
|  |         is_superuser = input('Is superuser? y/n (n): ') or 'n' | ||||||
|  |  | ||||||
|  |         if is_superuser == 'y': | ||||||
|  |             user_in.is_superuser = True | ||||||
|  |  | ||||||
|  |         elif is_superuser == 'n': | ||||||
|  |             user_in.is_superuser = False | ||||||
|  |  | ||||||
|  |         else: | ||||||
|  |             print('Invalid input\n') | ||||||
|  |             continue | ||||||
|  |  | ||||||
|  |         break | ||||||
|  |  | ||||||
|  |     with contextmanager(get_db)() as db: | ||||||
|  |         user = crud.user.sync_create( | ||||||
|  |                 db, | ||||||
|  |                 obj_in=user_in | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |     print(f'\nUser created:\n{user_in}\n') | ||||||
| @@ -1,8 +1,12 @@ | |||||||
| import io | import io | ||||||
| from pathlib import Path | from typing import ( | ||||||
| from typing import Annotated, Generator |     Annotated, | ||||||
|  |     Generator | ||||||
|  | ) | ||||||
|  |  | ||||||
| from PIL import Image | from PIL import Image | ||||||
| from jose import jwt | from jose import jwt | ||||||
|  |  | ||||||
| from pydantic import ValidationError | from pydantic import ValidationError | ||||||
|  |  | ||||||
| from sqlalchemy.ext.asyncio import AsyncSession | from sqlalchemy.ext.asyncio import AsyncSession | ||||||
| @@ -20,7 +24,10 @@ from fastapi.security import OAuth2PasswordBearer | |||||||
| from app.models.user import User as UserModel | from app.models.user import User as UserModel | ||||||
| from app.core import security | from app.core import security | ||||||
| from app.core.config import settings | from app.core.config import settings | ||||||
| from app.db.session import SessionLocal, AsyncSessionLocal | from app.db.session import ( | ||||||
|  |     SessionLocal, | ||||||
|  |     AsyncSessionLocal | ||||||
|  | ) | ||||||
| from app import crud | from app import crud | ||||||
| from app import schemas | from app import schemas | ||||||
| from app.utils.image_operations import ( | from app.utils.image_operations import ( | ||||||
| @@ -31,22 +38,15 @@ from app.utils.image_operations import ( | |||||||
|  |  | ||||||
| reusable_oauth2 = OAuth2PasswordBearer(tokenUrl='/login') | reusable_oauth2 = OAuth2PasswordBearer(tokenUrl='/login') | ||||||
|  |  | ||||||
| def get_db() -> Generator: |  | ||||||
|     try: |  | ||||||
|         db = SessionLocal() |  | ||||||
|         yield db |  | ||||||
|  |  | ||||||
|     finally: | def get_db() -> Generator: | ||||||
|         db.close() |     with SessionLocal() as db: | ||||||
|  |         yield db | ||||||
|  |  | ||||||
|  |  | ||||||
| async def get_async_db() -> Generator: | async def get_async_db() -> Generator: | ||||||
|     try: |  | ||||||
|     async with AsyncSessionLocal() as async_db: |     async with AsyncSessionLocal() as async_db: | ||||||
|             yield async_db |         yield await async_db | ||||||
|  |  | ||||||
|     finally: |  | ||||||
|         await async_db.close() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| async def get_access_token_from_cookie(access_token: Annotated[str, Cookie()]): | async def get_access_token_from_cookie(access_token: Annotated[str, Cookie()]): | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ from app import crud | |||||||
| from app.core.config import settings | from app.core.config import settings | ||||||
| from app.schemas import ListPostInTemplate | from app.schemas import ListPostInTemplate | ||||||
| from app.schemas.main import SendEmail | from app.schemas.main import SendEmail | ||||||
| from app.utils.email import send_email_notification | from app.utils.email_utils import send_email_notification | ||||||
| from app.views.depends import get_async_db | from app.views.depends import get_async_db | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,10 +1,12 @@ | |||||||
| from typing import Any |  | ||||||
| from datetime import timedelta | from datetime import timedelta | ||||||
|  |  | ||||||
| from sqlalchemy.ext.asyncio import AsyncSession | from sqlalchemy.ext.asyncio import AsyncSession | ||||||
|  |  | ||||||
| from fastapi.templating import Jinja2Templates | from fastapi.templating import Jinja2Templates | ||||||
| from fastapi.responses import FileResponse, HTMLResponse | from fastapi.responses import ( | ||||||
|  |     FileResponse, | ||||||
|  |     HTMLResponse | ||||||
|  | ) | ||||||
| from fastapi import ( | from fastapi import ( | ||||||
|     APIRouter, |     APIRouter, | ||||||
|     HTTPException, |     HTTPException, | ||||||
| @@ -36,7 +38,8 @@ async def login( | |||||||
|     return templates.TemplateResponse( |     return templates.TemplateResponse( | ||||||
|                 'admin/login.html', |                 'admin/login.html', | ||||||
|                 { |                 { | ||||||
|                     'request': request |                     'request': request, | ||||||
|  |                     'login_url': f'/{settings.SECRET_KEY[-10:]}' | ||||||
|                 } |                 } | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								src/static/404.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/static/404.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 48 KiB | 
		Reference in New Issue
	
	Block a user