Spaces:
Sleeping
Sleeping
chore:merge leave and assets in profile
Browse files- src/assets/router.py +0 -30
- src/assets/schemas.py +0 -31
- src/assets/service.py +0 -11
- src/{assets → auth}/feed_db_script.py +0 -0
- src/leave/router.py +0 -91
- src/leave/schemas.py +0 -0
- src/leave/utils.py +0 -50
- src/main.py +2 -2
- src/profile/router.py +118 -0
- src/profile/schemas.py +30 -0
- src/profile/service.py +13 -0
- src/profile/utils.py +50 -0
src/assets/router.py
DELETED
|
@@ -1,30 +0,0 @@
|
|
| 1 |
-
from fastapi import APIRouter, Depends
|
| 2 |
-
from sqlmodel.ext.asyncio.session import AsyncSession
|
| 3 |
-
from src.core.database import get_async_session
|
| 4 |
-
from src.auth.utils import get_current_user
|
| 5 |
-
from src.assets.schemas import BaseResponse
|
| 6 |
-
from src.assets.service import list_user_assets
|
| 7 |
-
|
| 8 |
-
router = APIRouter(prefix="/assets", tags=["Assets"])
|
| 9 |
-
|
| 10 |
-
@router.get("/", response_model=BaseResponse)
|
| 11 |
-
async def get_assets(
|
| 12 |
-
user_id: str = Depends(get_current_user),
|
| 13 |
-
session: AsyncSession = Depends(get_async_session)
|
| 14 |
-
):
|
| 15 |
-
assets = await list_user_assets(session, user_id)
|
| 16 |
-
|
| 17 |
-
data = {
|
| 18 |
-
"assets": [
|
| 19 |
-
{
|
| 20 |
-
"id": a.id,
|
| 21 |
-
"name": a.name,
|
| 22 |
-
"type": a.type,
|
| 23 |
-
"status": a.status,
|
| 24 |
-
}
|
| 25 |
-
for a in assets
|
| 26 |
-
]
|
| 27 |
-
}
|
| 28 |
-
|
| 29 |
-
return {"code": 200, "data": data}
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/assets/schemas.py
DELETED
|
@@ -1,31 +0,0 @@
|
|
| 1 |
-
from typing import Optional
|
| 2 |
-
from pydantic import BaseModel
|
| 3 |
-
import uuid
|
| 4 |
-
from enum import Enum
|
| 5 |
-
|
| 6 |
-
class AssetStatus(str, Enum):
|
| 7 |
-
ACTIVE = "Active"
|
| 8 |
-
UNAVAILABLE = "Unavailable"
|
| 9 |
-
ON_REQUEST = "On Request"
|
| 10 |
-
IN_SERVICE = "In Service"
|
| 11 |
-
|
| 12 |
-
class AssetCreateRequest(BaseModel):
|
| 13 |
-
name: str
|
| 14 |
-
type: str
|
| 15 |
-
status: Optional[AssetStatus] = AssetStatus.UNAVAILABLE
|
| 16 |
-
|
| 17 |
-
class AssetUpdateRequest(BaseModel):
|
| 18 |
-
name: Optional[str] = None
|
| 19 |
-
type: Optional[str] = None
|
| 20 |
-
status: Optional[AssetStatus] = None
|
| 21 |
-
|
| 22 |
-
class AssetResponse(BaseModel):
|
| 23 |
-
id: uuid.UUID
|
| 24 |
-
user_id: uuid.UUID
|
| 25 |
-
name: str
|
| 26 |
-
type: str
|
| 27 |
-
status: AssetStatus
|
| 28 |
-
|
| 29 |
-
class BaseResponse(BaseModel):
|
| 30 |
-
code: int
|
| 31 |
-
data: dict
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/assets/service.py
DELETED
|
@@ -1,11 +0,0 @@
|
|
| 1 |
-
import uuid
|
| 2 |
-
from typing import List
|
| 3 |
-
from sqlmodel import select
|
| 4 |
-
from sqlmodel.ext.asyncio.session import AsyncSession
|
| 5 |
-
from src.core.models import Assets
|
| 6 |
-
|
| 7 |
-
async def list_user_assets(session: AsyncSession, user_id: str) -> List[Assets]:
|
| 8 |
-
q = await session.exec(
|
| 9 |
-
select(Assets).where(Assets.user_id == uuid.UUID(user_id))
|
| 10 |
-
)
|
| 11 |
-
return q.all()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/{assets → auth}/feed_db_script.py
RENAMED
|
File without changes
|
src/leave/router.py
DELETED
|
@@ -1,91 +0,0 @@
|
|
| 1 |
-
from src.leave.utils import send_email
|
| 2 |
-
from fastapi import APIRouter, Depends, HTTPException
|
| 3 |
-
from sqlmodel import select
|
| 4 |
-
from sqlmodel.ext.asyncio.session import AsyncSession
|
| 5 |
-
from src.auth.utils import get_current_user
|
| 6 |
-
from src.core.database import get_async_session
|
| 7 |
-
from src.core.models import Users, Teams, Roles, UserTeamsRole
|
| 8 |
-
from src.auth.schemas import BaseResponse
|
| 9 |
-
from fastapi import BackgroundTasks
|
| 10 |
-
|
| 11 |
-
router = APIRouter(prefix="/leave", tags=["leave"])
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
@router.get("/contacts", response_model=BaseResponse)
|
| 15 |
-
async def get_leave_contacts(
|
| 16 |
-
current_user=Depends(get_current_user),
|
| 17 |
-
session: AsyncSession = Depends(get_async_session),
|
| 18 |
-
):
|
| 19 |
-
# get_current_user returns a STRING user_id
|
| 20 |
-
user_id = current_user
|
| 21 |
-
|
| 22 |
-
if not user_id:
|
| 23 |
-
raise HTTPException(status_code=400, detail="Invalid user token")
|
| 24 |
-
|
| 25 |
-
# 1) Get user's team
|
| 26 |
-
stmt = select(UserTeamsRole).where(UserTeamsRole.user_id == user_id)
|
| 27 |
-
ut = (await session.exec(stmt)).first()
|
| 28 |
-
|
| 29 |
-
if not ut:
|
| 30 |
-
raise HTTPException(status_code=404, detail="User-Team mapping not found")
|
| 31 |
-
|
| 32 |
-
# 2) Get Team Lead role
|
| 33 |
-
lead_role = (
|
| 34 |
-
await session.exec(select(Roles).where(Roles.name == "Team Lead"))
|
| 35 |
-
).first()
|
| 36 |
-
|
| 37 |
-
if not lead_role:
|
| 38 |
-
raise HTTPException(status_code=500, detail="Team Lead role not found")
|
| 39 |
-
|
| 40 |
-
# 3) Find Team Lead user in same team
|
| 41 |
-
lead_user = (
|
| 42 |
-
await session.exec(
|
| 43 |
-
select(Users)
|
| 44 |
-
.join(UserTeamsRole)
|
| 45 |
-
.where(UserTeamsRole.team_id == ut.team_id)
|
| 46 |
-
.where(UserTeamsRole.role_id == lead_role.id)
|
| 47 |
-
)
|
| 48 |
-
).all()
|
| 49 |
-
|
| 50 |
-
if not lead_user:
|
| 51 |
-
raise HTTPException(status_code=404, detail="Team lead not found")
|
| 52 |
-
|
| 53 |
-
to_email = ", ".join([u.email_id for u in lead_user])
|
| 54 |
-
|
| 55 |
-
# 4) HR CC emails
|
| 56 |
-
hr_team = (await session.exec(select(Teams).where(Teams.name == "HR Team"))).first()
|
| 57 |
-
|
| 58 |
-
cc = []
|
| 59 |
-
if hr_team:
|
| 60 |
-
hr_users = (
|
| 61 |
-
await session.exec(
|
| 62 |
-
select(Users)
|
| 63 |
-
.join(UserTeamsRole)
|
| 64 |
-
.where(UserTeamsRole.team_id == hr_team.id)
|
| 65 |
-
)
|
| 66 |
-
).all()
|
| 67 |
-
|
| 68 |
-
cc = [str(row.email_id) for row in hr_users]
|
| 69 |
-
|
| 70 |
-
return BaseResponse(code=200, message="success", data={"to": to_email, "cc": cc})
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
@router.post("/send", response_model=BaseResponse)
|
| 74 |
-
async def send_leave_email(
|
| 75 |
-
payload: dict,
|
| 76 |
-
background: BackgroundTasks,
|
| 77 |
-
current_user=Depends(get_current_user),
|
| 78 |
-
):
|
| 79 |
-
from_email = payload.get("from_email")
|
| 80 |
-
to_email = payload.get("to")
|
| 81 |
-
cc = payload.get("cc", [])
|
| 82 |
-
subject = payload.get("subject")
|
| 83 |
-
body = payload.get("body")
|
| 84 |
-
|
| 85 |
-
if not subject or not body:
|
| 86 |
-
raise HTTPException(status_code=400, detail="Subject and body required")
|
| 87 |
-
|
| 88 |
-
# send in background so API returns fast
|
| 89 |
-
background.add_task(send_email, to_email, subject, body, cc, from_email)
|
| 90 |
-
|
| 91 |
-
return BaseResponse(code=200, message="Leave request sent", data=None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/leave/schemas.py
DELETED
|
File without changes
|
src/leave/utils.py
DELETED
|
@@ -1,50 +0,0 @@
|
|
| 1 |
-
# src/core/email_utils.py
|
| 2 |
-
import smtplib
|
| 3 |
-
from email.message import EmailMessage
|
| 4 |
-
from src.core.config import settings
|
| 5 |
-
from typing import List
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
SMTP_HOST = settings.EMAIL_SERVER
|
| 9 |
-
SMTP_PORT = settings.EMAIL_PORT
|
| 10 |
-
SMTP_USER = settings.EMAIL_USERNAME
|
| 11 |
-
SMTP_PASS = settings.EMAIL_PASSWORD
|
| 12 |
-
FROM_DEFAULT = settings.EMAIL_USERNAME
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
def send_email(
|
| 16 |
-
to_email: str, subject: str, body: str, cc: list[str] = None, from_email: str = None
|
| 17 |
-
):
|
| 18 |
-
"""
|
| 19 |
-
Gmail cannot send as another user.
|
| 20 |
-
So we set 'From' = your Gmail, but 'Reply-To' = user email.
|
| 21 |
-
"""
|
| 22 |
-
|
| 23 |
-
cc = cc or []
|
| 24 |
-
|
| 25 |
-
msg = EmailMessage()
|
| 26 |
-
msg["Subject"] = subject
|
| 27 |
-
|
| 28 |
-
# Always send FROM your SMTP account
|
| 29 |
-
msg["From"] = settings.EMAIL_USERNAME
|
| 30 |
-
|
| 31 |
-
# Show this as reply address
|
| 32 |
-
if from_email:
|
| 33 |
-
msg["Reply-To"] = from_email
|
| 34 |
-
|
| 35 |
-
msg["To"] = to_email
|
| 36 |
-
|
| 37 |
-
if cc:
|
| 38 |
-
msg["Cc"] = ", ".join(cc)
|
| 39 |
-
|
| 40 |
-
msg.set_content(body)
|
| 41 |
-
|
| 42 |
-
try:
|
| 43 |
-
with smtplib.SMTP(settings.EMAIL_SERVER, settings.EMAIL_PORT) as server:
|
| 44 |
-
server.starttls()
|
| 45 |
-
server.login(settings.EMAIL_USERNAME, settings.EMAIL_PASSWORD)
|
| 46 |
-
|
| 47 |
-
server.send_message(msg)
|
| 48 |
-
|
| 49 |
-
except Exception as e:
|
| 50 |
-
raise Exception(f"Email sending failed: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/main.py
CHANGED
|
@@ -17,9 +17,9 @@ app.include_router(auth_router)
|
|
| 17 |
|
| 18 |
app.include_router(profile)
|
| 19 |
|
| 20 |
-
app.include_router(assets)
|
| 21 |
|
| 22 |
-
app.include_router(leave)
|
| 23 |
|
| 24 |
|
| 25 |
@app.get("/")
|
|
|
|
| 17 |
|
| 18 |
app.include_router(profile)
|
| 19 |
|
| 20 |
+
# app.include_router(assets)
|
| 21 |
|
| 22 |
+
# app.include_router(leave)
|
| 23 |
|
| 24 |
|
| 25 |
@app.get("/")
|
src/profile/router.py
CHANGED
|
@@ -6,10 +6,46 @@ from sqlalchemy.ext.asyncio.session import AsyncSession
|
|
| 6 |
from fastapi.params import Depends
|
| 7 |
from .schemas import UpdateProfileRequest
|
| 8 |
from src.profile.service import update_user_profile
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
router = APIRouter(prefix="/profile", tags=["Profile"])
|
| 11 |
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
@router.put("/update-profile", response_model=BaseResponse)
|
| 14 |
async def update_profile(
|
| 15 |
payload: UpdateProfileRequest,
|
|
@@ -18,3 +54,85 @@ async def update_profile(
|
|
| 18 |
):
|
| 19 |
result = await update_user_profile(session, user_id, payload)
|
| 20 |
return {"code": 200, "data": result}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
from fastapi.params import Depends
|
| 7 |
from .schemas import UpdateProfileRequest
|
| 8 |
from src.profile.service import update_user_profile
|
| 9 |
+
from fastapi import APIRouter, Depends
|
| 10 |
+
from sqlmodel.ext.asyncio.session import AsyncSession
|
| 11 |
+
from src.core.database import get_async_session
|
| 12 |
+
from src.auth.utils import get_current_user
|
| 13 |
+
from src.assets.schemas import BaseResponse
|
| 14 |
+
from src.assets.service import list_user_assets
|
| 15 |
+
from src.leave.utils import send_email
|
| 16 |
+
from fastapi import APIRouter, Depends, HTTPException
|
| 17 |
+
from sqlmodel import select
|
| 18 |
+
from sqlmodel.ext.asyncio.session import AsyncSession
|
| 19 |
+
from src.auth.utils import get_current_user
|
| 20 |
+
from src.core.models import Users, Teams, Roles, UserTeamsRole
|
| 21 |
+
from fastapi import BackgroundTasks
|
| 22 |
+
|
| 23 |
|
| 24 |
router = APIRouter(prefix="/profile", tags=["Profile"])
|
| 25 |
|
| 26 |
|
| 27 |
+
@router.get("/", response_model=BaseResponse)
|
| 28 |
+
async def get_assets(
|
| 29 |
+
user_id: str = Depends(get_current_user),
|
| 30 |
+
session: AsyncSession = Depends(get_async_session),
|
| 31 |
+
):
|
| 32 |
+
assets = await list_user_assets(session, user_id)
|
| 33 |
+
|
| 34 |
+
data = {
|
| 35 |
+
"assets": [
|
| 36 |
+
{
|
| 37 |
+
"id": a.id,
|
| 38 |
+
"name": a.name,
|
| 39 |
+
"type": a.type,
|
| 40 |
+
"status": a.status,
|
| 41 |
+
}
|
| 42 |
+
for a in assets
|
| 43 |
+
]
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
return {"code": 200, "data": data}
|
| 47 |
+
|
| 48 |
+
|
| 49 |
@router.put("/update-profile", response_model=BaseResponse)
|
| 50 |
async def update_profile(
|
| 51 |
payload: UpdateProfileRequest,
|
|
|
|
| 54 |
):
|
| 55 |
result = await update_user_profile(session, user_id, payload)
|
| 56 |
return {"code": 200, "data": result}
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
@router.get("/contacts", response_model=BaseResponse)
|
| 61 |
+
async def get_leave_contacts(
|
| 62 |
+
current_user=Depends(get_current_user),
|
| 63 |
+
session: AsyncSession = Depends(get_async_session),
|
| 64 |
+
):
|
| 65 |
+
# get_current_user returns a STRING user_id
|
| 66 |
+
user_id = current_user
|
| 67 |
+
|
| 68 |
+
if not user_id:
|
| 69 |
+
raise HTTPException(status_code=400, detail="Invalid user token")
|
| 70 |
+
|
| 71 |
+
# 1) Get user's team
|
| 72 |
+
stmt = select(UserTeamsRole).where(UserTeamsRole.user_id == user_id)
|
| 73 |
+
ut = (await session.exec(stmt)).first()
|
| 74 |
+
|
| 75 |
+
if not ut:
|
| 76 |
+
raise HTTPException(status_code=404, detail="User-Team mapping not found")
|
| 77 |
+
|
| 78 |
+
# 2) Get Team Lead role
|
| 79 |
+
lead_role = (
|
| 80 |
+
await session.exec(select(Roles).where(Roles.name == "Team Lead"))
|
| 81 |
+
).first()
|
| 82 |
+
|
| 83 |
+
if not lead_role:
|
| 84 |
+
raise HTTPException(status_code=500, detail="Team Lead role not found")
|
| 85 |
+
|
| 86 |
+
# 3) Find Team Lead user in same team
|
| 87 |
+
lead_user = (
|
| 88 |
+
await session.exec(
|
| 89 |
+
select(Users)
|
| 90 |
+
.join(UserTeamsRole)
|
| 91 |
+
.where(UserTeamsRole.team_id == ut.team_id)
|
| 92 |
+
.where(UserTeamsRole.role_id == lead_role.id)
|
| 93 |
+
)
|
| 94 |
+
).all()
|
| 95 |
+
|
| 96 |
+
if not lead_user:
|
| 97 |
+
raise HTTPException(status_code=404, detail="Team lead not found")
|
| 98 |
+
|
| 99 |
+
to_email = ", ".join([u.email_id for u in lead_user])
|
| 100 |
+
|
| 101 |
+
# 4) HR CC emails
|
| 102 |
+
hr_team = (await session.exec(select(Teams).where(Teams.name == "HR Team"))).first()
|
| 103 |
+
|
| 104 |
+
cc = []
|
| 105 |
+
if hr_team:
|
| 106 |
+
hr_users = (
|
| 107 |
+
await session.exec(
|
| 108 |
+
select(Users)
|
| 109 |
+
.join(UserTeamsRole)
|
| 110 |
+
.where(UserTeamsRole.team_id == hr_team.id)
|
| 111 |
+
)
|
| 112 |
+
).all()
|
| 113 |
+
|
| 114 |
+
cc = [str(row.email_id) for row in hr_users]
|
| 115 |
+
|
| 116 |
+
return BaseResponse(code=200, message="success", data={"to": to_email, "cc": cc})
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
@router.post("/send", response_model=BaseResponse)
|
| 120 |
+
async def send_leave_email(
|
| 121 |
+
payload: dict,
|
| 122 |
+
background: BackgroundTasks,
|
| 123 |
+
current_user=Depends(get_current_user),
|
| 124 |
+
):
|
| 125 |
+
from_email = payload.get("from_email")
|
| 126 |
+
to_email = payload.get("to")
|
| 127 |
+
cc = payload.get("cc", [])
|
| 128 |
+
subject = payload.get("subject")
|
| 129 |
+
body = payload.get("body")
|
| 130 |
+
|
| 131 |
+
if not subject or not body:
|
| 132 |
+
raise HTTPException(status_code=400, detail="Subject and body required")
|
| 133 |
+
|
| 134 |
+
# send in background so API returns fast
|
| 135 |
+
background.add_task(send_email, to_email, subject, body, cc, from_email)
|
| 136 |
+
|
| 137 |
+
return BaseResponse(code=200, message="Leave request sent", data=None)
|
| 138 |
+
|
src/profile/schemas.py
CHANGED
|
@@ -1,5 +1,35 @@
|
|
| 1 |
from pydantic import BaseModel, EmailStr
|
| 2 |
from typing import Optional
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
class UpdateProfileRequest(BaseModel):
|
| 5 |
name: Optional[str] = None
|
|
|
|
| 1 |
from pydantic import BaseModel, EmailStr
|
| 2 |
from typing import Optional
|
| 3 |
+
import uuid
|
| 4 |
+
from enum import Enum
|
| 5 |
+
|
| 6 |
+
class AssetStatus(str, Enum):
|
| 7 |
+
ACTIVE = "Active"
|
| 8 |
+
UNAVAILABLE = "Unavailable"
|
| 9 |
+
ON_REQUEST = "On Request"
|
| 10 |
+
IN_SERVICE = "In Service"
|
| 11 |
+
|
| 12 |
+
class AssetCreateRequest(BaseModel):
|
| 13 |
+
name: str
|
| 14 |
+
type: str
|
| 15 |
+
status: Optional[AssetStatus] = AssetStatus.UNAVAILABLE
|
| 16 |
+
|
| 17 |
+
class AssetUpdateRequest(BaseModel):
|
| 18 |
+
name: Optional[str] = None
|
| 19 |
+
type: Optional[str] = None
|
| 20 |
+
status: Optional[AssetStatus] = None
|
| 21 |
+
|
| 22 |
+
class AssetResponse(BaseModel):
|
| 23 |
+
id: uuid.UUID
|
| 24 |
+
user_id: uuid.UUID
|
| 25 |
+
name: str
|
| 26 |
+
type: str
|
| 27 |
+
status: AssetStatus
|
| 28 |
+
|
| 29 |
+
class BaseResponse(BaseModel):
|
| 30 |
+
code: int
|
| 31 |
+
data: dict
|
| 32 |
+
|
| 33 |
|
| 34 |
class UpdateProfileRequest(BaseModel):
|
| 35 |
name: Optional[str] = None
|
src/profile/service.py
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
|
|
|
|
|
| 1 |
from datetime import datetime
|
| 2 |
import uuid
|
| 3 |
from fastapi import HTTPException
|
| 4 |
from passlib.context import CryptContext
|
| 5 |
from src.core.models import Users
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
| 8 |
|
|
@@ -63,3 +70,9 @@ async def update_user_profile(session, user_id: str, data):
|
|
| 63 |
"is_verified": user.is_verified,
|
| 64 |
},
|
| 65 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from src.core.models import Assets
|
| 2 |
+
from ast import List
|
| 3 |
from datetime import datetime
|
| 4 |
import uuid
|
| 5 |
from fastapi import HTTPException
|
| 6 |
from passlib.context import CryptContext
|
| 7 |
from src.core.models import Users
|
| 8 |
+
import uuid
|
| 9 |
+
from typing import List
|
| 10 |
+
from sqlmodel import select
|
| 11 |
+
from sqlmodel.ext.asyncio.session import AsyncSession
|
| 12 |
+
from src.core.models import Assets
|
| 13 |
|
| 14 |
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
| 15 |
|
|
|
|
| 70 |
"is_verified": user.is_verified,
|
| 71 |
},
|
| 72 |
}
|
| 73 |
+
|
| 74 |
+
async def list_user_assets(session: AsyncSession, user_id: str) -> List[Assets]:
|
| 75 |
+
q = await session.exec(
|
| 76 |
+
select(Assets).where(Assets.user_id == uuid.UUID(user_id))
|
| 77 |
+
)
|
| 78 |
+
return q.all()
|
src/profile/utils.py
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# src/core/email_utils.py
|
| 2 |
+
import smtplib
|
| 3 |
+
from email.message import EmailMessage
|
| 4 |
+
from src.core.config import settings
|
| 5 |
+
from typing import List
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
SMTP_HOST = settings.EMAIL_SERVER
|
| 9 |
+
SMTP_PORT = settings.EMAIL_PORT
|
| 10 |
+
SMTP_USER = settings.EMAIL_USERNAME
|
| 11 |
+
SMTP_PASS = settings.EMAIL_PASSWORD
|
| 12 |
+
FROM_DEFAULT = settings.EMAIL_USERNAME
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def send_email(
|
| 16 |
+
to_email: str, subject: str, body: str, cc: list[str] = None, from_email: str = None
|
| 17 |
+
):
|
| 18 |
+
"""
|
| 19 |
+
Gmail cannot send as another user.
|
| 20 |
+
So we set 'From' = your Gmail, but 'Reply-To' = user email.
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
cc = cc or []
|
| 24 |
+
|
| 25 |
+
msg = EmailMessage()
|
| 26 |
+
msg["Subject"] = subject
|
| 27 |
+
|
| 28 |
+
# Always send FROM your SMTP account
|
| 29 |
+
msg["From"] = settings.EMAIL_USERNAME
|
| 30 |
+
|
| 31 |
+
# Show this as reply address
|
| 32 |
+
if from_email:
|
| 33 |
+
msg["Reply-To"] = from_email
|
| 34 |
+
|
| 35 |
+
msg["To"] = to_email
|
| 36 |
+
|
| 37 |
+
if cc:
|
| 38 |
+
msg["Cc"] = ", ".join(cc)
|
| 39 |
+
|
| 40 |
+
msg.set_content(body)
|
| 41 |
+
|
| 42 |
+
try:
|
| 43 |
+
with smtplib.SMTP(settings.EMAIL_SERVER, settings.EMAIL_PORT) as server:
|
| 44 |
+
server.starttls()
|
| 45 |
+
server.login(settings.EMAIL_USERNAME, settings.EMAIL_PASSWORD)
|
| 46 |
+
|
| 47 |
+
server.send_message(msg)
|
| 48 |
+
|
| 49 |
+
except Exception as e:
|
| 50 |
+
raise Exception(f"Email sending failed: {str(e)}")
|