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

chore:merge leave and assets in profile

Browse files
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)}")