Shri Jayaram commited on
Commit
d60aba7
·
2 Parent(s): 3ce5685679dfd8

Merge pull request #23 from yuvabe-ai-labs/feat/chatbot

Browse files
alembic/versions/d9bb355538fd_add_is_read_to_leave_table.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """add is_read to leave table
2
+
3
+ Revision ID: d9bb355538fd
4
+ Revises: e95f62f91348
5
+ Create Date: 2025-11-25 16:43:45.584602
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 = 'd9bb355538fd'
17
+ down_revision: Union[str, Sequence[str], None] = 'e95f62f91348'
18
+ branch_labels: Union[str, Sequence[str], None] = None
19
+ depends_on: Union[str, Sequence[str], None] = None
20
+
21
+
22
+ def upgrade():
23
+ op.add_column(
24
+ "leave",
25
+ sa.Column(
26
+ "is_read",
27
+ sa.Boolean(),
28
+ nullable=False,
29
+ server_default=sa.false()
30
+ )
31
+ )
32
+
33
+ def downgrade():
34
+ op.drop_column("leave", "is_read")
src/main.py CHANGED
@@ -1,13 +1,12 @@
1
- from src.profile.router import router as profile
2
  from fastapi import FastAPI
3
 
4
  import os
5
  from src.auth.router import router as auth_router
6
- from src.chatbot.router import router as chatbot
7
  from src.core.database import init_db
8
  from src.home.router import router as home_router
9
  from src.notifications.router import router as notifications_router
10
-
11
  from fastapi.staticfiles import StaticFiles
12
 
13
 
@@ -18,16 +17,11 @@ app.include_router(home_router, prefix="/home", tags=["Home"])
18
 
19
  # init_db()
20
 
21
- BASE_DIR = os.path.dirname(os.path.abspath(__file__))
22
- AUDIO_DIR = os.path.join(BASE_DIR, "static", "audio")
23
-
24
  app.include_router(profile)
25
- app.mount("/static/audio", StaticFiles(directory=AUDIO_DIR), name="audio")
26
- print("Serving audio from:", AUDIO_DIR)
27
 
28
  app.include_router(auth_router)
29
 
30
- app.include_router(chatbot)
31
 
32
  app.include_router(notifications_router)
33
 
 
 
1
  from fastapi import FastAPI
2
 
3
  import os
4
  from src.auth.router import router as auth_router
5
+ from src.chatbot.router import router as chatbot_router
6
  from src.core.database import init_db
7
  from src.home.router import router as home_router
8
  from src.notifications.router import router as notifications_router
9
+ from src.profile.router import router as profile
10
  from fastapi.staticfiles import StaticFiles
11
 
12
 
 
17
 
18
  # init_db()
19
 
 
 
 
20
  app.include_router(profile)
 
 
21
 
22
  app.include_router(auth_router)
23
 
24
+ app.include_router(chatbot_router)
25
 
26
  app.include_router(notifications_router)
27
 
src/profile/models.py CHANGED
@@ -15,6 +15,7 @@ class LeaveType(str, Enum):
15
  class LeaveStatus(str, Enum):
16
  APPROVED = "Approved"
17
  REJECTED = "Rejected"
 
18
  PENDING = "Pending"
19
 
20
  class Leave(SQLModel, table=True):
@@ -30,6 +31,7 @@ class Leave(SQLModel, table=True):
30
  reason: str = Field(nullable=True)
31
  status: LeaveStatus = Field(default=LeaveStatus.PENDING)
32
  is_delivered: bool = Field(default= False)
 
33
  requested_at: date = Field(default_factory=date.today)
34
  updated_at: date = Field(default_factory=date.today)
35
  reject_reason: Optional[str] = None
 
15
  class LeaveStatus(str, Enum):
16
  APPROVED = "Approved"
17
  REJECTED = "Rejected"
18
+ CANCELLED = "Cancelled"
19
  PENDING = "Pending"
20
 
21
  class Leave(SQLModel, table=True):
 
31
  reason: str = Field(nullable=True)
32
  status: LeaveStatus = Field(default=LeaveStatus.PENDING)
33
  is_delivered: bool = Field(default= False)
34
+ is_read: bool = Field(default=False)
35
  requested_at: date = Field(default_factory=date.today)
36
  updated_at: date = Field(default_factory=date.today)
37
  reject_reason: Optional[str] = None
src/profile/service.py CHANGED
@@ -21,13 +21,13 @@ from src.profile.schemas import CreateLeaveRequest, LeaveStatus, ApproveRejectRe
21
  from src.notifications.fcm import send_fcm
22
 
23
 
24
- pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
25
 
26
- # src/profile/service.py
27
 
28
- # Leave limits (you can move to config)
29
- SICK_LIMIT = getattr(settings, "SICK_LEAVE_LIMIT", 10)
30
- CASUAL_LIMIT = getattr(settings, "CASUAL_LEAVE_LIMIT", 10)
31
 
32
 
33
  async def _get_team_roles(session: AsyncSession, user_id: uuid.UUID):
@@ -135,7 +135,7 @@ async def create_leave(session, user_id, body):
135
  leave.lead_id,
136
  )
137
 
138
- return leave
139
 
140
 
141
  async def mentor_decide_leave(session, mentor_id, leave_id, body):
@@ -172,7 +172,7 @@ async def mentor_decide_leave(session, mentor_id, leave_id, body):
172
  {"type": "leave_status"},
173
  )
174
 
175
- return leave
176
 
177
 
178
  # async def apply_leave(session: AsyncSession, user_id, payload: ApplyLeaveRequest):
@@ -377,25 +377,25 @@ async def mentor_decide_leave(session, mentor_id, leave_id, body):
377
  # return leave
378
 
379
 
380
- async def add_device_token(session: AsyncSession, user_id, device_token: str):
381
- """
382
- Add FCM token to Users.device_tokens ARRAY.
383
- Avoid duplicates.
384
- """
385
 
386
- # 1) Fetch user
387
- user = await session.get(Users, user_id)
388
- if not user:
389
- raise HTTPException(404, "User not found")
390
 
391
- # 2) If token not present -> add it
392
- if device_token not in user.device_tokens:
393
- user.device_tokens.append(device_token)
394
- session.add(user)
395
- await session.commit()
396
- await session.refresh(user)
397
 
398
- return user.device_tokens
399
 
400
 
401
  # async def get_leave_balance(session: AsyncSession, user_id) -> List[dict]:
@@ -421,106 +421,106 @@ async def add_device_token(session: AsyncSession, user_id, device_token: str):
421
  # ]
422
 
423
 
424
- # In production, replace with DB storage
425
- USER_TOKEN_STORE = {} # {google_user_id: {tokens}}
426
 
427
 
428
- async def send_email_service(req: SendMailRequest):
429
- record = USER_TOKEN_STORE.get(req.user_id)
430
- if not record:
431
- raise HTTPException(404, "User not logged in with Google OAuth")
432
 
433
- access_token = record["access_token"]
434
- refresh_token = record.get("refresh_token")
435
 
436
- if not access_token and refresh_token:
437
- new_tokens = await refresh_access_token(refresh_token)
438
- access_token = new_tokens["access_token"]
439
- record["access_token"] = access_token
440
 
441
- if not access_token:
442
- raise HTTPException(400, "Re-auth required")
443
 
444
- raw = build_raw_message(
445
- to_email=req.to,
446
- subject=req.subject,
447
- body=req.body,
448
- from_name=req.from_name,
449
- from_email=record["email"],
450
- )
451
 
452
- url = "https://gmail.googleapis.com/gmail/v1/users/me/messages/send"
453
- payload = {"raw": raw}
454
 
455
- async with httpx.AsyncClient() as client:
456
- r = await client.post(
457
- url, json=payload, headers={"Authorization": f"Bearer {access_token}"}
458
- )
459
 
460
- if r.status_code >= 400:
461
- raise HTTPException(500, f"Gmail error: {r.text}")
462
 
463
- return r.json()
464
 
465
 
466
- async def update_user_profile(session, user_id: str, data):
467
- user = await session.get(Users, uuid.UUID(user_id))
468
 
469
- if not user:
470
- raise HTTPException(status_code=404, detail="User not found")
471
 
472
- # --- Update Name ---
473
- if data.name:
474
- user.user_name = data.name
475
 
476
- # --- Update Email ---
477
- if data.email:
478
- user.email_id = data.email
479
 
480
- # --- Update DOB ---
481
- if data.dob:
482
- try:
483
- # Convert DD.MM.YYYY → Python date
484
- parsed_date = datetime.strptime(data.dob, "%d.%m.%Y").date()
485
- user.dob = parsed_date
486
- except:
487
- raise HTTPException(
488
- status_code=400, detail="DOB must be in DD.MM.YYYY format"
489
- )
490
 
491
- # --- Update Address ---
492
- if data.address:
493
- user.address = data.address
494
 
495
- # --- Change Password ---
496
- if data.new_password:
497
- if not data.current_password:
498
- raise HTTPException(status_code=400, detail="Current password required")
499
 
500
- # Verify old password
501
- if not pwd_context.verify(data.current_password, user.password):
502
- raise HTTPException(status_code=400, detail="Incorrect current password")
503
 
504
- # Set new password
505
- user.password = pwd_context.hash(data.new_password)
506
 
507
- # Commit changes
508
- await session.commit()
509
- await session.refresh(user)
510
-
511
- return {
512
- "message": "Profile updated successfully",
513
- "user": {
514
- "id": str(user.id),
515
- "name": user.user_name,
516
- "email": user.email_id,
517
- "dob": user.dob.isoformat() if user.dob else None,
518
- "address": user.address,
519
- "is_verified": user.is_verified,
520
- },
521
- }
522
-
523
-
524
- async def list_user_assets(session: AsyncSession, user_id: str) -> List[Assets]:
525
- q = await session.exec(select(Assets).where(Assets.user_id == uuid.UUID(user_id)))
526
- return q.all()
 
21
  from src.notifications.fcm import send_fcm
22
 
23
 
24
+ # pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
25
 
26
+ # # src/profile/service.py
27
 
28
+ # # Leave limits (you can move to config)
29
+ # SICK_LIMIT = getattr(settings, "SICK_LEAVE_LIMIT", 10)
30
+ # CASUAL_LIMIT = getattr(settings, "CASUAL_LEAVE_LIMIT", 10)
31
 
32
 
33
  async def _get_team_roles(session: AsyncSession, user_id: uuid.UUID):
 
135
  leave.lead_id,
136
  )
137
 
138
+ # return leave
139
 
140
 
141
  async def mentor_decide_leave(session, mentor_id, leave_id, body):
 
172
  {"type": "leave_status"},
173
  )
174
 
175
+ # return leave
176
 
177
 
178
  # async def apply_leave(session: AsyncSession, user_id, payload: ApplyLeaveRequest):
 
377
  # return leave
378
 
379
 
380
+ # async def add_device_token(session: AsyncSession, user_id, device_token: str):
381
+ # """
382
+ # Add FCM token to Users.device_tokens ARRAY.
383
+ # Avoid duplicates.
384
+ # """
385
 
386
+ # # 1) Fetch user
387
+ # user = await session.get(Users, user_id)
388
+ # if not user:
389
+ # raise HTTPException(404, "User not found")
390
 
391
+ # # 2) If token not present -> add it
392
+ # if device_token not in user.device_tokens:
393
+ # user.device_tokens.append(device_token)
394
+ # session.add(user)
395
+ # await session.commit()
396
+ # await session.refresh(user)
397
 
398
+ # return user.device_tokens
399
 
400
 
401
  # async def get_leave_balance(session: AsyncSession, user_id) -> List[dict]:
 
421
  # ]
422
 
423
 
424
+ # # In production, replace with DB storage
425
+ # USER_TOKEN_STORE = {} # {google_user_id: {tokens}}
426
 
427
 
428
+ # async def send_email_service(req: SendMailRequest):
429
+ # record = USER_TOKEN_STORE.get(req.user_id)
430
+ # if not record:
431
+ # raise HTTPException(404, "User not logged in with Google OAuth")
432
 
433
+ # access_token = record["access_token"]
434
+ # refresh_token = record.get("refresh_token")
435
 
436
+ # if not access_token and refresh_token:
437
+ # new_tokens = await refresh_access_token(refresh_token)
438
+ # access_token = new_tokens["access_token"]
439
+ # record["access_token"] = access_token
440
 
441
+ # if not access_token:
442
+ # raise HTTPException(400, "Re-auth required")
443
 
444
+ # raw = build_raw_message(
445
+ # to_email=req.to,
446
+ # subject=req.subject,
447
+ # body=req.body,
448
+ # from_name=req.from_name,
449
+ # from_email=record["email"],
450
+ # )
451
 
452
+ # url = "https://gmail.googleapis.com/gmail/v1/users/me/messages/send"
453
+ # payload = {"raw": raw}
454
 
455
+ # async with httpx.AsyncClient() as client:
456
+ # r = await client.post(
457
+ # url, json=payload, headers={"Authorization": f"Bearer {access_token}"}
458
+ # )
459
 
460
+ # if r.status_code >= 400:
461
+ # raise HTTPException(500, f"Gmail error: {r.text}")
462
 
463
+ # return r.json()
464
 
465
 
466
+ # async def update_user_profile(session, user_id: str, data):
467
+ # user = await session.get(Users, uuid.UUID(user_id))
468
 
469
+ # if not user:
470
+ # raise HTTPException(status_code=404, detail="User not found")
471
 
472
+ # # --- Update Name ---
473
+ # if data.name:
474
+ # user.user_name = data.name
475
 
476
+ # # --- Update Email ---
477
+ # if data.email:
478
+ # user.email_id = data.email
479
 
480
+ # # --- Update DOB ---
481
+ # if data.dob:
482
+ # try:
483
+ # # Convert DD.MM.YYYY → Python date
484
+ # parsed_date = datetime.strptime(data.dob, "%d.%m.%Y").date()
485
+ # user.dob = parsed_date
486
+ # except:
487
+ # raise HTTPException(
488
+ # status_code=400, detail="DOB must be in DD.MM.YYYY format"
489
+ # )
490
 
491
+ # # --- Update Address ---
492
+ # if data.address:
493
+ # user.address = data.address
494
 
495
+ # # --- Change Password ---
496
+ # if data.new_password:
497
+ # if not data.current_password:
498
+ # raise HTTPException(status_code=400, detail="Current password required")
499
 
500
+ # # Verify old password
501
+ # if not pwd_context.verify(data.current_password, user.password):
502
+ # raise HTTPException(status_code=400, detail="Incorrect current password")
503
 
504
+ # # Set new password
505
+ # user.password = pwd_context.hash(data.new_password)
506
 
507
+ # # Commit changes
508
+ # await session.commit()
509
+ # await session.refresh(user)
510
+
511
+ # return {
512
+ # "message": "Profile updated successfully",
513
+ # "user": {
514
+ # "id": str(user.id),
515
+ # "name": user.user_name,
516
+ # "email": user.email_id,
517
+ # "dob": user.dob.isoformat() if user.dob else None,
518
+ # "address": user.address,
519
+ # "is_verified": user.is_verified,
520
+ # },
521
+ # }
522
+
523
+
524
+ # async def list_user_assets(session: AsyncSession, user_id: str) -> List[Assets]:
525
+ # q = await session.exec(select(Assets).where(Assets.user_id == uuid.UUID(user_id)))
526
+ # return q.all()