Hp137 commited on
Commit
66536b4
·
1 Parent(s): b2870f8

feat:Added leave/assets

Browse files
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10
2
+
3
+ WORKDIR /app
4
+
5
+ COPY . .
6
+
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ EXPOSE 7860
10
+
11
+ CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1 +1,10 @@
1
- # YB's Wellness App FastAPI Backend
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Yuvabe Backend App
3
+ emoji: 👁
4
+ colorFrom: pink
5
+ colorTo: red
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
alembic/versions/b33e3b5b7af9_added_roles.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Added roles
2
+
3
+ Revision ID: b33e3b5b7af9
4
+ Revises: e8066533b622
5
+ Create Date: 2025-11-16 21:10:02.038255
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ import sqlmodel.sql.sqltypes
13
+
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = 'b33e3b5b7af9'
17
+ down_revision: Union[str, Sequence[str], None] = 'e8066533b622'
18
+ branch_labels: Union[str, Sequence[str], None] = None
19
+ depends_on: Union[str, Sequence[str], None] = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ """Upgrade schema."""
24
+ # ### commands auto generated by Alembic - please adjust! ###
25
+ pass
26
+ # ### end Alembic commands ###
27
+
28
+
29
+ def downgrade() -> None:
30
+ """Downgrade schema."""
31
+ # ### commands auto generated by Alembic - please adjust! ###
32
+ pass
33
+ # ### end Alembic commands ###
src/assets/feed_db_script.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from src.auth.utils import hash_password
2
+ from datetime import date
3
+ from sqlmodel import Session
4
+
5
+ from src.core.database import engine
6
+ from src.core.models import Users, Teams, Roles, UserTeamsRole
7
+
8
+
9
+ # ------------------------
10
+ # 1. Seed Users
11
+ # ------------------------
12
+ def seed_users(session: Session):
13
+ users = [
14
+ Users(
15
+ email_id="[email protected]",
16
+ password=hash_password("Yuvabe"),
17
+ user_name="ragul",
18
+ dob=date(2001, 5, 21),
19
+ address="Chennai",
20
+ profile_picture="ragul.png",
21
+ ),
22
+ Users(
23
+ email_id="[email protected]",
24
+ password=hash_password("Yuvabe"),
25
+ user_name="Shri",
26
+ dob=date(1999, 3, 14),
27
+ address="Chennai",
28
+ profile_picture="shri.png",
29
+ ),
30
+ Users(
31
+ email_id="[email protected]",
32
+ password=hash_password("Yuvabe"),
33
+ user_name="Sathish",
34
+ dob=date(1998, 7, 10),
35
+ address="Chennai",
36
+ profile_picture="Sathish.png",
37
+ ),
38
+ Users(
39
+ email_id="[email protected]",
40
+ password=hash_password("Yuvabe"),
41
+ user_name="Deepika",
42
+ dob=date(1997, 2, 5),
43
+ address="Chennai",
44
+ profile_picture="deepika.png",
45
+ ),
46
+ ]
47
+
48
+ session.add_all(users)
49
+ session.commit()
50
+ print("Users added.")
51
+ return users
52
+
53
+
54
+ # ------------------------
55
+ # 2. Seed Teams
56
+ # ------------------------
57
+ def seed_teams(session: Session):
58
+ teams = [
59
+ Teams(name="Tech Team"),
60
+ Teams(name="HR Team"),
61
+ ]
62
+ session.add_all(teams)
63
+ session.commit()
64
+ print("Teams added.")
65
+ return teams
66
+
67
+
68
+ # ------------------------
69
+ # 3. Seed Roles
70
+ # ------------------------
71
+ def seed_roles(session: Session):
72
+ roles = [
73
+ Roles(name="Developer"),
74
+ Roles(name="Team Lead"),
75
+ Roles(name="HR Manager"),
76
+ ]
77
+ session.add_all(roles)
78
+ session.commit()
79
+ print("Roles added.")
80
+ return roles
81
+
82
+
83
+ # ------------------------
84
+ # 4. Map Users → Teams → Roles
85
+ # ------------------------
86
+ def seed_user_teams_roles(session: Session, users, teams, roles):
87
+ mappings = [
88
+ # Hari → Tech Team → Developer
89
+ UserTeamsRole(
90
+ user_id=users[0].id, # Hari
91
+ team_id=teams[0].id, # Tech Team
92
+ role_id=roles[0].id, # Developer
93
+ ),
94
+ # Shri → Tech Team → Team Lead
95
+ UserTeamsRole(
96
+ user_id=users[1].id, # Shri
97
+ team_id=teams[0].id, # Tech Team
98
+ role_id=roles[1].id, # Team Lead
99
+ ),
100
+ # HR Keerthana
101
+ UserTeamsRole(
102
+ user_id=users[2].id, # Keerthana
103
+ team_id=teams[1].id, # HR Team
104
+ role_id=roles[2].id, # HR Manager
105
+ ),
106
+ # HR Deepika
107
+ UserTeamsRole(
108
+ user_id=users[3].id, # Deepika
109
+ team_id=teams[1].id, # HR Team
110
+ role_id=roles[2].id, # HR Manager
111
+ ),
112
+ ]
113
+
114
+ session.add_all(mappings)
115
+ session.commit()
116
+ print("User-Team-Role mappings added.")
117
+
118
+
119
+ # ------------------------
120
+ # 5. Master Runner
121
+ # ------------------------
122
+ def run_all_seeds():
123
+ with Session(engine) as session:
124
+ users = seed_users(session)
125
+ teams = seed_teams(session)
126
+ roles = seed_roles(session)
127
+ seed_user_teams_roles(session, users, teams, roles)
128
+ print("All data seeded successfully!")
129
+
130
+
131
+ if __name__ == "__main__":
132
+ run_all_seeds()
src/assets/router.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
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/auth/router.py CHANGED
@@ -8,7 +8,6 @@ from sqlmodel.ext.asyncio.session import AsyncSession
8
  from src.auth.service import (
9
  create_user,
10
  verify_email,
11
- send_verification_link,
12
  login_user,
13
  )
14
  from src.auth.utils import get_current_user
@@ -37,26 +36,26 @@ async def signup(
37
  raise HTTPException(status_code=400, detail=str(e))
38
 
39
 
40
- @router.post("/send-verification", response_model=BaseResponse)
41
- async def send_verification(
42
- payload: SendVerificationRequest, session: AsyncSession = Depends(get_async_session)
43
- ):
44
- if not payload.email:
45
- raise HTTPException(status_code=400, detail="Email is required")
46
 
47
- response = await send_verification_link(session, payload.email)
48
- return {"code": 200, "data": response}
49
 
50
 
51
- @router.get("/verify-email", response_model=BaseResponse)
52
- async def verify_email_route(
53
- token: str, session: AsyncSession = Depends(get_async_session)
54
- ):
55
- response = await verify_email(session, token)
56
- access_token = response["access_token"]
57
- redirect_url = f"yuvabe://verified?token={access_token}"
58
 
59
- return RedirectResponse(url=redirect_url)
60
 
61
 
62
  @router.post("/login", response_model=BaseResponse)
@@ -115,6 +114,8 @@ async def get_home(
115
  "name": user.user_name,
116
  "email": user.email_id,
117
  "is_verified": user.is_verified,
 
 
118
  },
119
  "home_data": {
120
  "announcements": ["Welcome!", "New protocol released"],
 
8
  from src.auth.service import (
9
  create_user,
10
  verify_email,
 
11
  login_user,
12
  )
13
  from src.auth.utils import get_current_user
 
36
  raise HTTPException(status_code=400, detail=str(e))
37
 
38
 
39
+ # @router.post("/send-verification", response_model=BaseResponse)
40
+ # async def send_verification(
41
+ # payload: SendVerificationRequest, session: AsyncSession = Depends(get_async_session)
42
+ # ):
43
+ # if not payload.email:
44
+ # raise HTTPException(status_code=400, detail="Email is required")
45
 
46
+ # response = await send_verification_link(session, payload.email)
47
+ # return {"code": 200, "data": response}
48
 
49
 
50
+ # @router.get("/verify-email", response_model=BaseResponse)
51
+ # async def verify_email_route(
52
+ # token: str, session: AsyncSession = Depends(get_async_session)
53
+ # ):
54
+ # response = await verify_email(session, token)
55
+ # access_token = response["access_token"]
56
+ # redirect_url = f"yuvabe://verified?token={access_token}"
57
 
58
+ # return RedirectResponse(url=redirect_url)
59
 
60
 
61
  @router.post("/login", response_model=BaseResponse)
 
114
  "name": user.user_name,
115
  "email": user.email_id,
116
  "is_verified": user.is_verified,
117
+ "dob": user.dob.isoformat() if user.dob else None,
118
+ "profile_picture": user.profile_picture
119
  },
120
  "home_data": {
121
  "announcements": ["Welcome!", "New protocol released"],
src/auth/service.py CHANGED
@@ -6,7 +6,6 @@ from src.auth.utils import (
6
  verify_verification_token,
7
  create_access_token,
8
  hash_password,
9
- send_verification_email,
10
  create_verification_token,
11
  )
12
  from src.core.models import Users
@@ -17,6 +16,10 @@ from sqlmodel.ext.asyncio.session import AsyncSession
17
 
18
  async def create_user(session: AsyncSession, name: str, email: str, password: str):
19
  """Create user without sending email"""
 
 
 
 
20
  user = await session.exec(select(Users).where(Users.email_id == email))
21
  existing_user = user.first()
22
  if existing_user:
@@ -26,7 +29,7 @@ async def create_user(session: AsyncSession, name: str, email: str, password: st
26
  user_name=name,
27
  email_id=email,
28
  password=hash_password(password),
29
- is_verified=False,
30
  )
31
 
32
  session.add(new_user)
@@ -57,32 +60,32 @@ async def create_user(session: AsyncSession, name: str, email: str, password: st
57
  }
58
 
59
 
60
- async def send_verification_link(session: Session, email: str):
61
- """Send verification email for an existing user."""
62
- result = await session.exec(select(Users).where(Users.email_id == email))
63
- user = result.first()
64
 
65
- if not user:
66
- raise HTTPException(status_code=404, detail="User not found")
67
 
68
- if user.is_verified:
69
- raise HTTPException(status_code=400, detail="User is already verified")
70
 
71
- # Create a token using existing user ID (opaque token)
72
- token = create_verification_token(str(user.id))
73
 
74
- try:
75
- send_verification_email(email, token)
76
- except Exception as e:
77
- raise HTTPException(
78
- status_code=500, detail=f"Failed to send verification email: {str(e)}"
79
- )
80
 
81
- return {
82
- "message": "Verification link sent successfully",
83
- "user_id": str(user.id),
84
- "email": user.email_id,
85
- }
86
 
87
 
88
  async def verify_email(session: Session, token: str):
@@ -116,6 +119,10 @@ async def verify_email(session: Session, token: str):
116
 
117
 
118
  async def login_user(session: Session, email: str, password: str):
 
 
 
 
119
  users = await session.exec(select(Users).where(Users.email_id == email))
120
  user = users.first()
121
 
 
6
  verify_verification_token,
7
  create_access_token,
8
  hash_password,
 
9
  create_verification_token,
10
  )
11
  from src.core.models import Users
 
16
 
17
  async def create_user(session: AsyncSession, name: str, email: str, password: str):
18
  """Create user without sending email"""
19
+
20
+ if not email.lower().endswith("@yuvabe.com"):
21
+ raise HTTPException(status_code=400, detail="Enter you're Yuvabe email ID")
22
+
23
  user = await session.exec(select(Users).where(Users.email_id == email))
24
  existing_user = user.first()
25
  if existing_user:
 
29
  user_name=name,
30
  email_id=email,
31
  password=hash_password(password),
32
+ is_verified=True,
33
  )
34
 
35
  session.add(new_user)
 
60
  }
61
 
62
 
63
+ # async def send_verification_link(session: Session, email: str):
64
+ # """Send verification email for an existing user."""
65
+ # result = await session.exec(select(Users).where(Users.email_id == email))
66
+ # user = result.first()
67
 
68
+ # if not user:
69
+ # raise HTTPException(status_code=404, detail="User not found")
70
 
71
+ # if user.is_verified:
72
+ # raise HTTPException(status_code=400, detail="User is already verified")
73
 
74
+ # # Create a token using existing user ID (opaque token)
75
+ # token = create_verification_token(str(user.id))
76
 
77
+ # try:
78
+ # send_verification_email(email, token)
79
+ # except Exception as e:
80
+ # raise HTTPException(
81
+ # status_code=500, detail=f"Failed to send verification email: {str(e)}"
82
+ # )
83
 
84
+ # return {
85
+ # "message": "Verification link sent successfully",
86
+ # "user_id": str(user.id),
87
+ # "email": user.email_id,
88
+ # }
89
 
90
 
91
  async def verify_email(session: Session, token: str):
 
119
 
120
 
121
  async def login_user(session: Session, email: str, password: str):
122
+
123
+ if not email.lower().endswith("@yuvabe.com"):
124
+ raise HTTPException(status_code=400, detail="Enter you're Yuvabe email ID")
125
+
126
  users = await session.exec(select(Users).where(Users.email_id == email))
127
  user = users.first()
128
 
src/auth/utils.py CHANGED
@@ -3,6 +3,8 @@ import smtplib
3
  import os
4
  import uuid
5
  from email.mime.text import MIMEText
 
 
6
  from passlib.context import CryptContext
7
  from src.core.database import get_async_session
8
  from sqlmodel.ext.asyncio.session import AsyncSession
@@ -18,6 +20,7 @@ from src.core.config import settings
18
  SECRET_KEY = settings.SECRET_KEY
19
  ALGORITHM = settings.JWT_ALGORITHM
20
  ACCESS_TOKEN_EXPIRE_MINUTES = settings.JWT_EXPIRE
 
21
 
22
  SMTP_SERVER = settings.EMAIL_SERVER
23
  SMTP_PORT = settings.EMAIL_PORT
@@ -50,28 +53,75 @@ def create_access_token(data: dict):
50
  return encoded_jwt
51
 
52
 
53
- def send_verification_email(to_email: str, token: str):
54
- """Send email with verification link"""
55
- subject = f"Verify your {settings.APP_NAME} Account"
56
- verification_link = f"{VERIFICATION_BASE_URL}/auth/verify-email?token={token}"
57
- body = f"""
58
- Hi,
59
-
60
- Please verify your {settings.APP_NAME} account by clicking the link below:
61
- {verification_link}
62
-
63
- This link will expire in 24 hours.
64
- """
65
-
66
- msg = MIMEText(body)
67
- msg["Subject"] = subject
68
- msg["From"] = SMTP_EMAIL
69
- msg["To"] = to_email
70
-
71
- with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
72
- server.starttls()
73
- server.login(SMTP_EMAIL, SMTP_PASSWORD)
74
- server.send_message(msg)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
 
77
  fernet = Fernet(FERNET_KEY.encode())
 
3
  import os
4
  import uuid
5
  from email.mime.text import MIMEText
6
+ import logging
7
+ import traceback
8
  from passlib.context import CryptContext
9
  from src.core.database import get_async_session
10
  from sqlmodel.ext.asyncio.session import AsyncSession
 
20
  SECRET_KEY = settings.SECRET_KEY
21
  ALGORITHM = settings.JWT_ALGORITHM
22
  ACCESS_TOKEN_EXPIRE_MINUTES = settings.JWT_EXPIRE
23
+ logger = logging.getLogger(__name__)
24
 
25
  SMTP_SERVER = settings.EMAIL_SERVER
26
  SMTP_PORT = settings.EMAIL_PORT
 
53
  return encoded_jwt
54
 
55
 
56
+ # def send_verification_email(to_email: str, token: str):
57
+ # """Send verification email using smtplib with detailed debug logs."""
58
+ # subject = f"Verify your {settings.APP_NAME} Account"
59
+ # verification_link = f"{VERIFICATION_BASE_URL}/auth/verify-email?token={token}"
60
+
61
+ # body = f"""
62
+ # Hi,
63
+
64
+ # Please verify your {settings.APP_NAME} account by clicking the link below:
65
+ # {verification_link}
66
+
67
+ # This link will expire in 24 hours.
68
+
69
+ # Regards,
70
+ # {settings.APP_NAME} Team
71
+ # """
72
+
73
+ # msg = MIMEText(body)
74
+ # msg["Subject"] = subject
75
+ # msg["From"] = SMTP_EMAIL
76
+ # msg["To"] = to_email
77
+
78
+ # logger.info("🟢 Starting send_verification_email()")
79
+ # logger.info(f"📨 To: {to_email}")
80
+ # logger.info(f"📤 SMTP Server: {SMTP_SERVER}:{SMTP_PORT}")
81
+
82
+ # try:
83
+ # logger.info("🔌 Connecting to SMTP server...")
84
+ # with smtplib.SMTP(SMTP_SERVER, SMTP_PORT, timeout=30) as server:
85
+ # logger.info("✅ Connected successfully.")
86
+
87
+ # logger.info("🔒 Starting TLS...")
88
+ # server.starttls()
89
+ # logger.info("✅ TLS secured.")
90
+
91
+ # logger.info("🔑 Logging in to SMTP server...")
92
+ # server.login(SMTP_EMAIL, SMTP_PASSWORD)
93
+ # logger.info("✅ Logged in successfully.")
94
+
95
+ # # Send email
96
+ # logger.info("📧 Sending email message...")
97
+ # server.send_message(msg)
98
+ # logger.info(f"✅ Email successfully sent to {to_email}")
99
+
100
+ # except smtplib.SMTPAuthenticationError as e:
101
+ # logger.error("❌ Authentication failed — check email or app password.")
102
+ # logger.error(f"Error details: {e}")
103
+ # logger.error(traceback.format_exc())
104
+ # raise
105
+ # except smtplib.SMTPConnectError as e:
106
+ # logger.error("❌ Could not connect to SMTP server.")
107
+ # logger.error(f"Error details: {e}")
108
+ # logger.error(traceback.format_exc())
109
+ # raise
110
+ # except smtplib.SMTPRecipientsRefused as e:
111
+ # logger.error("❌ Recipient address refused.")
112
+ # logger.error(f"Error details: {e}")
113
+ # logger.error(traceback.format_exc())
114
+ # raise
115
+ # except smtplib.SMTPException as e:
116
+ # logger.error("❌ General SMTP error occurred.")
117
+ # logger.error(f"Error details: {e}")
118
+ # logger.error(traceback.format_exc())
119
+ # raise
120
+ # except Exception as e:
121
+ # logger.error("❌ Unknown error occurred while sending verification email.")
122
+ # logger.error(f"Error details: {e}")
123
+ # logger.error(traceback.format_exc())
124
+ # raise
125
 
126
 
127
  fernet = Fernet(FERNET_KEY.encode())
src/leave/router.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
File without changes
src/leave/utils.py ADDED
@@ -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)}")
src/main.py CHANGED
@@ -1,8 +1,11 @@
 
 
1
  from fastapi import FastAPI
2
 
3
  from src.core.database import init_db
4
  from src.home.router import router as home_router
5
  from src.auth.router import router as auth_router
 
6
 
7
  app = FastAPI(title="Yuvabe App API")
8
 
@@ -12,6 +15,12 @@ init_db()
12
 
13
  app.include_router(auth_router)
14
 
 
 
 
 
 
 
15
 
16
  @app.get("/")
17
  def root():
 
1
+ from src.assets.router import router as assets
2
+ from src.profile.router import router as profile
3
  from fastapi import FastAPI
4
 
5
  from src.core.database import init_db
6
  from src.home.router import router as home_router
7
  from src.auth.router import router as auth_router
8
+ from src.leave.router import router as leave
9
 
10
  app = FastAPI(title="Yuvabe App API")
11
 
 
15
 
16
  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("/")
26
  def root():
src/profile/router.py CHANGED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi.routing import APIRouter
2
+ from src.core.database import get_async_session
3
+ from src.auth.utils import get_current_user
4
+ from src.auth.schemas import BaseResponse
5
+ 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,
16
+ user_id: str = Depends(get_current_user),
17
+ session: AsyncSession = Depends(get_async_session),
18
+ ):
19
+ result = await update_user_profile(session, user_id, payload)
20
+ return {"code": 200, "data": result}
src/profile/schemas.py CHANGED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, EmailStr
2
+ from typing import Optional
3
+
4
+ class UpdateProfileRequest(BaseModel):
5
+ name: Optional[str] = None
6
+ email: Optional[EmailStr] = None
7
+ dob: Optional[str] = None
8
+ address: Optional[str] = None
9
+
10
+ current_password: Optional[str] = None
11
+ new_password: Optional[str] = None
src/profile/service.py CHANGED
@@ -1,2 +1,65 @@
1
- from typing import List
2
- from uuid import UUID
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
9
+
10
+ async def update_user_profile(session, user_id: str, data):
11
+ user = await session.get(Users, uuid.UUID(user_id))
12
+
13
+ if not user:
14
+ raise HTTPException(status_code=404, detail="User not found")
15
+
16
+ # --- Update Name ---
17
+ if data.name:
18
+ user.user_name = data.name
19
+
20
+ # --- Update Email ---
21
+ if data.email:
22
+ user.email_id = data.email
23
+
24
+ # --- Update DOB ---
25
+ if data.dob:
26
+ try:
27
+ # Convert DD.MM.YYYY → Python date
28
+ parsed_date = datetime.strptime(data.dob, "%d.%m.%Y").date()
29
+ user.dob = parsed_date
30
+ except:
31
+ raise HTTPException(
32
+ status_code=400, detail="DOB must be in DD.MM.YYYY format"
33
+ )
34
+
35
+ # --- Update Address ---
36
+ if data.address:
37
+ user.address = data.address
38
+
39
+ # --- Change Password ---
40
+ if data.new_password:
41
+ if not data.current_password:
42
+ raise HTTPException(status_code=400, detail="Current password required")
43
+
44
+ # Verify old password
45
+ if not pwd_context.verify(data.current_password, user.password):
46
+ raise HTTPException(status_code=400, detail="Incorrect current password")
47
+
48
+ # Set new password
49
+ user.password = pwd_context.hash(data.new_password)
50
+
51
+ # Commit changes
52
+ await session.commit()
53
+ await session.refresh(user)
54
+
55
+ return {
56
+ "message": "Profile updated successfully",
57
+ "user": {
58
+ "id": str(user.id),
59
+ "name": user.user_name,
60
+ "email": user.email_id,
61
+ "dob": user.dob.isoformat() if user.dob else None,
62
+ "address": user.address,
63
+ "is_verified": user.is_verified,
64
+ },
65
+ }