mirror of
https://github.com/aykhans/portfolio-blog.git
synced 2025-04-16 19:03:11 +00:00
Added create_user command & edited imports
This commit is contained in:
parent
77e4722576
commit
006f2af15b
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
.venv
|
.venv
|
||||||
__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,16 +35,15 @@ 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
|
||||||
|
temp_slug = slug
|
||||||
|
|
||||||
number = 1
|
while db.query(Post).filter(Post.slug == temp_slug).first() is not None:
|
||||||
temp_slug = slug
|
temp_slug = f'{slug}-{number}'
|
||||||
|
number += 1
|
||||||
|
|
||||||
while db.query(Post).filter(Post.slug == temp_slug).first() is not None:
|
target.slug = temp_slug
|
||||||
temp_slug = f'{slug}-{number}'
|
|
||||||
number += 1
|
|
||||||
|
|
||||||
target.slug = temp_slug
|
|
||||||
|
|
||||||
listen(Post.title, 'set', generate_slug)
|
listen(Post.title, 'set', generate_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 await async_db
|
||||||
yield 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 |
Loading…
x
Reference in New Issue
Block a user