File size: 20,720 Bytes
c073c9d
a7c1c74
 
 
04226dd
66536b4
 
6a958f4
 
fa5d0ef
66536b4
fa5d0ef
66536b4
 
20d48af
a84fe28
1ad9865
 
 
 
fa5d0ef
 
 
 
 
1ad9865
fa5d0ef
1ad9865
fa5d0ef
1ad9865
a7c1c74
 
6a958f4
fa5d0ef
20d48af
66536b4
 
 
1ad9865
fa5d0ef
 
 
1ad9865
fa5d0ef
1ad9865
fa5d0ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1ad9865
 
fa5d0ef
 
 
 
1ad9865
fa5d0ef
1ad9865
fa5d0ef
 
 
 
 
 
 
 
 
 
 
 
 
1ad9865
fa5d0ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1ad9865
 
fa5d0ef
 
1ad9865
 
fa5d0ef
 
1ad9865
fa5d0ef
1ad9865
fa5d0ef
 
1ad9865
fa5d0ef
1ad9865
fa5d0ef
 
1ad9865
fa5d0ef
 
1ad9865
fa5d0ef
 
 
 
 
 
 
 
1ad9865
 
fa5d0ef
 
 
1ad9865
 
fa5d0ef
 
 
 
5078e89
fa5d0ef
 
5078e89
fa5d0ef
 
5078e89
fa5d0ef
 
 
 
 
 
 
 
5078e89
 
fa5d0ef
 
 
 
 
 
 
 
 
 
 
 
5078e89
fa5d0ef
1ad9865
5078e89
fa5d0ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6a958f4
fa5d0ef
 
5078e89
fa5d0ef
5078e89
 
04226dd
fa5d0ef
 
20d48af
fa5d0ef
20d48af
04226dd
 
 
 
 
 
fa5d0ef
04226dd
 
 
fa5d0ef
 
04226dd
 
fa5d0ef
 
 
 
 
04226dd
fa5d0ef
 
 
04226dd
 
fa5d0ef
 
 
 
04226dd
fa5d0ef
20d48af
 
 
fa5d0ef
 
66536b4
fa5d0ef
66536b4
a7c1c74
 
04226dd
 
a7c1c74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
04226dd
 
 
 
735e535
04226dd
 
735e535
fa5d0ef
 
 
 
04226dd
 
 
 
fa5d0ef
04226dd
 
 
 
 
 
 
 
 
 
 
 
 
 
a7c1c74
fa5d0ef
04226dd
 
a7c1c74
20d48af
 
04226dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20d48af
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a84fe28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310c607
a84fe28
 
6a958f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a7c1c74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c073c9d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
from src.profile.schemas import UpdateProfileRequest
from uuid import UUID
from src.core.models import Assets
from src.profile.schemas import AssetResponse
from src.profile.schemas import LeaveDetailResponse
from src.core.database import get_async_session
from src.auth.utils import get_current_user
from src.notifications.service import get_user_device_tokens
from src.notifications.fcm import send_fcm
from src.profile.models import Leave, LeaveType, LeaveStatus
from src.auth.schemas import BaseResponse
from sqlalchemy import desc
from sqlalchemy.ext.asyncio.session import AsyncSession
from fastapi.params import Depends
from src.core.models import Users, Teams, Roles, UserTeamsRole
from fastapi import APIRouter, Query, HTTPException, BackgroundTasks
from fastapi.responses import RedirectResponse, JSONResponse
from fastapi import APIRouter, Depends, HTTPException, status
from sqlmodel.ext.asyncio.session import AsyncSession
from src.auth.utils import get_current_user  # adjust path
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import select
import uuid
from src.core.models import Users
from src.profile.schemas import (
    CreateLeaveRequest,
    LeaveResponse,
    ApproveRejectRequest,
)
from sqlalchemy import desc

from datetime import datetime
from src.profile.service import create_leave, mentor_decide_leave


router = APIRouter(prefix="/profile", tags=["Profile"])


@router.post("/request", response_model=LeaveResponse)
async def request_leave_route(
    body: CreateLeaveRequest,
    session: AsyncSession = Depends(get_async_session),
    user_id: str = Depends(get_current_user),
):
    # convert user_id string -> UUID if needed
    try:
        user_uuid = uuid.UUID(user_id)
    except ValueError:
        raise HTTPException(status_code=400, detail="Invalid user id format")

    leave = await create_leave(session, user_uuid, body)

    return LeaveResponse(
        id=str(leave.id),
        leave_type=leave.leave_type,
        from_date=leave.from_date,
        to_date=leave.to_date,
        days=leave.days,
        reason=leave.reason,
        status=leave.status,
        mentor_id=str(leave.mentor_id),
        lead_id=str(leave.lead_id),
    )


@router.post("/{leave_id}/mentor-decision", response_model=LeaveResponse)
async def mentor_decision_route(
    leave_id: str,
    body: ApproveRejectRequest,
    session: AsyncSession = Depends(get_async_session),
    user_id: str = Depends(get_current_user),
):
    # validate leave_id + user_id UUIDs
    try:
        leave_uuid = uuid.UUID(leave_id)
        mentor_uuid = uuid.UUID(user_id)
    except ValueError:
        raise HTTPException(status_code=400, detail="Invalid UUID")

    # If rejected, comment must be provided
    if body.status == LeaveStatus.REJECTED and not body.comment:
        raise HTTPException(
            status_code=400,
            detail="Comment is required when rejecting leave",
        )

    try:
        leave = await mentor_decide_leave(session, mentor_uuid, leave_uuid, body)
    except PermissionError as e:
        raise HTTPException(status_code=403, detail=str(e))
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

    return LeaveResponse(
        id=str(leave.id),
        leave_type=leave.leave_type,
        from_date=leave.from_date,
        to_date=leave.to_date,
        days=leave.days,
        reason=leave.reason,
        status=leave.status,
        mentor_id=str(leave.mentor_id),
        lead_id=str(leave.lead_id),
    )


SICK_LIMIT = 10
CASUAL_LIMIT = 10


@router.get("/balance")
async def get_leave_balance(
    session: AsyncSession = Depends(get_async_session),
    user_id: str = Depends(get_current_user),
):
    stmt = select(Leave).where(
        Leave.user_id == user_id, Leave.status == LeaveStatus.APPROVED
    )
    results = (await session.exec(stmt)).all()

    sick_used = sum(1 for l in results if l.leave_type == LeaveType.SICK)
    casual_used = sum(1 for l in results if l.leave_type == LeaveType.CASUAL)

    sick_remaining = SICK_LIMIT - sick_used
    casual_remaining = CASUAL_LIMIT - casual_used

    return {
        "code": 200,
        "message": "success",
        "data": {
            "sick_remaining": sick_remaining,
            "casual_remaining": casual_remaining,
        },
    }


@router.get("/balance/{user_id}")
async def get_leave_balance_for_user(
    user_id: str,
    session: AsyncSession = Depends(get_async_session),
):
    stmt = select(Leave).where(
        Leave.user_id == user_id, Leave.status == LeaveStatus.APPROVED
    )
    results = (await session.exec(stmt)).all()

    sick_used = sum(1 for l in results if l.leave_type == LeaveType.SICK)
    casual_used = sum(1 for l in results if l.leave_type == LeaveType.CASUAL)

    sick_remaining = SICK_LIMIT - sick_used
    casual_remaining = CASUAL_LIMIT - casual_used

    return {
        "code": 200,
        "message": "success",
        "data": {
            "sick_remaining": sick_remaining,
            "casual_remaining": casual_remaining,
        },
    }


@router.get("/notifications")
async def list_notifications(
    session: AsyncSession = Depends(get_async_session),
    user_id: str = Depends(get_current_user),
):

    stmt = (
        select(Leave)
        .where(
            (Leave.user_id == user_id)
            | (Leave.mentor_id == user_id)
            | (Leave.lead_id == user_id)
        )
        .order_by(desc(Leave.updated_at))
    )

    results = (await session.exec(stmt)).all()

    notifications = []

    for leave in results:
        if leave.user_id == user_id:
            # user = leave owner
            title = f"Your leave was {leave.status}"
            body = f"{leave.leave_type} from {leave.from_date} to {leave.to_date}"
        elif leave.mentor_id == user_id:
            # mentor receives new leave request
            title = "New Leave Request"
            body = f"{leave.leave_type} requested by user"
        elif leave.lead_id == user_id:
            # lead receives updates
            title = f"Leave {leave.status}"
            body = f"{leave.leave_type} for user updated"
        else:
            title = "Leave Update"
            body = leave.reason or ""

        notifications.append(
            {
                "id": str(leave.id),
                "mentor_id": str(leave.mentor_id),
                "lead_id": str(leave.lead_id),
                "title": title,
                "body": body,
                "type": leave.status,
                "updated_at": leave.updated_at.isoformat(),
                "leave_type": leave.leave_type,
                "from_date": str(leave.from_date),
                "to_date": str(leave.to_date),
                "reject_reason": leave.reject_reason,
                "reason": leave.reason,
                "is_read": leave.is_read,
            }
        )

    return {"code": 200, "data": notifications}


@router.get("/leave/{leave_id}", response_model=LeaveDetailResponse)
async def get_leave_details(
    leave_id: str,
    session: AsyncSession = Depends(get_async_session),
    user_id: str = Depends(get_current_user),
):
    # Join Leave + Users table to get user_name
    stmt = (
        select(Leave, Users.user_name)
        .join(Users, Users.id == Leave.user_id)
        .where(Leave.id == uuid.UUID(leave_id))
    )

    row = (await session.exec(stmt)).first()

    if not row:
        raise HTTPException(status_code=404, detail="Leave not found")

    leave, user_name = row

    return {
        "code": 200,
        "data": {
            "id": str(leave.id),
            "user_id": str(leave.user_id),
            "user_name": user_name,
            "mentor_id": str(leave.mentor_id),
            "lead_id": str(leave.lead_id),
            "leave_type": leave.leave_type,
            "from_date": leave.from_date.isoformat(),
            "to_date": leave.to_date.isoformat(),
            "days": leave.days,
            "reason": leave.reason,
            "status": leave.status,
            "reject_reason": leave.reject_reason,
            "updated_at": leave.updated_at.isoformat() if leave.updated_at else None,
        },
    }


@router.get("/mentor/pending")
async def mentor_pending_leaves(
    session: AsyncSession = Depends(get_async_session),
    mentor_id: str = Depends(get_current_user),
):
    mentor_uuid = uuid.UUID(mentor_id)

    print("🔥 mentor pending called for:", mentor_id)

    mentor_team = (
        await session.exec(
            select(UserTeamsRole).where(UserTeamsRole.user_id == mentor_uuid)
        )
    ).first()

    if not mentor_team:
        raise HTTPException(404, "Mentor has no team")

    team_users = (
        await session.exec(
            select(UserTeamsRole.user_id).where(
                UserTeamsRole.team_id == mentor_team.team_id
            )
        )
    ).all()

    team_user_ids = [u for u in team_users]

    stmt = (
        select(Leave, Users.user_name)
        .join(Users, Users.id == Leave.user_id)
        .where(
            Leave.user_id.in_(team_user_ids),
            Leave.status == LeaveStatus.PENDING,
        )
        .order_by(desc(Leave.updated_at))
    )

    rows = (await session.exec(stmt)).all()

    result = []

    for leave, user_name in rows:
        result.append(
            LeaveResponse(
                id=str(leave.id),
                leave_type=leave.leave_type,
                from_date=leave.from_date.isoformat(),
                to_date=leave.to_date.isoformat(),
                days=leave.days,
                reason=leave.reason,
                status=(
                    leave.status.value
                    if hasattr(leave.status, "value")
                    else leave.status
                ),
                mentor_id=str(leave.mentor_id),
                lead_id=str(leave.lead_id),
                user_name=user_name,
                updated_at=(leave.updated_at.isoformat() if leave.updated_at else None),
            )
        )

    return {"code": 200, "data": result}


@router.get("/my-leaves")
async def my_leave_history(
    session: AsyncSession = Depends(get_async_session),
    user_id: str = Depends(get_current_user),
):
    stmt = (
        select(Leave, Users.user_name, Users.email_id)
        .join(Users, Users.id == Leave.mentor_id, isouter=True)
        .where(Leave.user_id == uuid.UUID(user_id))
        .order_by(desc(Leave.updated_at))
    )

    rows = (await session.exec(stmt)).all()

    result = []
    for leave, mentor_name, mentor_email in rows:
        result.append(
            {
                "id": str(leave.id),
                "leave_type": leave.leave_type,
                "from_date": leave.from_date.isoformat(),
                "to_date": leave.to_date.isoformat(),
                "days": leave.days,
                "reason": leave.reason,
                "status": leave.status,
                "mentor_name": mentor_name,
                "updated_at": (
                    leave.updated_at.isoformat() if leave.updated_at else None
                ),
            }
        )

    return {"code": 200, "data": result}


@router.get("/mentor/team-leaves")
async def team_leave_history(
    session: AsyncSession = Depends(get_async_session),
    mentor_id: str = Depends(get_current_user),
):
    stmt = (
        select(Leave, Users.user_name)
        .join(Users, Users.id == Leave.user_id)
        .where(Leave.mentor_id == uuid.UUID(mentor_id))
        .order_by(desc(Leave.updated_at))
    )

    rows = (await session.exec(stmt)).all()

    result = []
    for leave, username in rows:
        result.append(
            {
                "id": str(leave.id),
                "user_name": username,
                "leave_type": leave.leave_type,
                "from_date": leave.from_date.isoformat(),
                "to_date": leave.to_date.isoformat(),
                "days": leave.days,
                "reason": leave.reason,
                "status": leave.status,
                "updated_at": (
                    leave.updated_at.isoformat() if leave.updated_at else None
                ),
            }
        )

    return {"code": 200, "data": result}


@router.get("/contacts", response_model=BaseResponse)
async def get_leave_contacts(
    current_user=Depends(get_current_user),
    session: AsyncSession = Depends(get_async_session),
):
    # get_current_user returns a STRING user_id
    user_id = current_user

    if not user_id:
        raise HTTPException(status_code=400, detail="Invalid user token")

    # 1) Get user's team
    stmt = select(UserTeamsRole).where(UserTeamsRole.user_id == user_id)
    ut = (await session.exec(stmt)).first()

    if not ut:
        raise HTTPException(status_code=404, detail="User-Team mapping not found")

    # 2) Get Team Lead role
    lead_role = (
        await session.exec(select(Roles).where(Roles.name == "Team Lead"))
    ).first()

    if not lead_role:
        raise HTTPException(status_code=500, detail="Team Lead role not found")

    # 3) Find Team Lead user in same team
    lead_user = (
        await session.exec(
            select(Users)
            .join(UserTeamsRole)
            .where(UserTeamsRole.team_id == ut.team_id)
            .where(UserTeamsRole.role_id == lead_role.id)
        )
    ).all()

    if not lead_user:
        raise HTTPException(status_code=404, detail="Team lead not found")

    to_email = ", ".join([u.email_id for u in lead_user])

    # 4) HR CC emails
    hr_team = (await session.exec(select(Teams).where(Teams.name == "HR Team"))).first()

    cc = []
    if hr_team:
        hr_users = (
            await session.exec(
                select(Users)
                .join(UserTeamsRole)
                .where(UserTeamsRole.team_id == hr_team.id)
            )
        ).all()

        cc = [str(row.email_id) for row in hr_users]

    return BaseResponse(code=200, message="success", data={"to": to_email, "cc": cc})


@router.get("/details", response_model=BaseResponse)
async def get_profile_details(
    current_user=Depends(get_current_user),
    session: AsyncSession = Depends(get_async_session),
):
    user_id = current_user

    # 1) Get the user
    user = await session.get(Users, user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    # 2) Get user's team mapping
    user_team = (
        await session.exec(
            select(UserTeamsRole).where(UserTeamsRole.user_id == user_id)
        )
    ).first()

    if not user_team:
        raise HTTPException(status_code=404, detail="User does not belong to any team")

    # 3) Get team name
    team = await session.get(Teams, user_team.team_id)

    # 4) Find mentor (team lead)
    lead_role = (
        await session.exec(select(Roles).where(Roles.name == "Mentor"))
    ).first()

    mentor_users = (
        await session.exec(
            select(Users)
            .join(UserTeamsRole)
            .where(UserTeamsRole.team_id == user_team.team_id)
            .where(UserTeamsRole.role_id == lead_role.id)
        )
    ).all()

    mentor_names = [u.user_name for u in mentor_users]
    mentor_emails = [u.email_id for u in mentor_users]

    return BaseResponse(
        code=200,
        message="success",
        data={
            "name": user.user_name,
            "email": user.email_id,
            "team_name": team.name,
            "mentor_name": ", ".join(mentor_names),
            "mentor_email": ", ".join(mentor_emails),
            "join_date": user.join_date,
        },
    )


@router.post("/leave/{leave_id}/cancel")
async def cancel_leave(
    leave_id: str,
    session: AsyncSession = Depends(get_async_session),
    user_id: str = Depends(get_current_user),
):
    leave = await session.get(Leave, uuid.UUID(leave_id))

    if not leave:
        raise HTTPException(status_code=404, detail="Leave not found")

    # User must own this leave
    if str(leave.user_id) != user_id:
        raise HTTPException(status_code=403, detail="Not your leave")

    # Only approved leaves can be cancelled
    if leave.status not in [LeaveStatus.APPROVED, LeaveStatus.PENDING]:
        raise HTTPException(
            status_code=400,
            detail="Only pending or approved leaves can be cancelled",
        )

    # Check if leave date is future
    from datetime import date

    if leave.from_date <= date.today():
        raise HTTPException(
            status_code=400,
            detail="Past or current day leaves cannot be cancelled",
        )

    # Update leave status
    leave.status = LeaveStatus.CANCELLED
    leave.updated_at = datetime.utcnow()
    await session.commit()

    user = await session.get(Users, leave.user_id)

    # Notify mentor
    mentor_tokens = await get_user_device_tokens(session, leave.mentor_id)
    await send_fcm(
        mentor_tokens,
        "Leave Cancelled",
        f"{user.user_name} cancelled their approved leave.",
        {"type": "leave_cancel", "leave_id": str(leave.id)},
    )

    # Notify Team Lead
    lead_tokens = await get_user_device_tokens(session, leave.lead_id)
    await send_fcm(
        lead_tokens,
        "Leave Cancelled",
        f"User {user.user_name} cancelled their approved leave.",
        {"type": "leave_cancel", "leave_id": str(leave.id)},
    )

    return {
        "code": 200,
        "message": "Leave cancelled successfully",
        "data": {
            "id": str(leave.id),
            "status": leave.status,
        },
    }


@router.get("/assets", response_model=BaseResponse)
async def get_profile(
    session: AsyncSession = Depends(get_async_session),
    user_id: str = Depends(get_current_user),
):
    # Fetch User
    result = await session.exec(select(Users).where(Users.id == UUID(user_id)))
    user = result.first()

    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    # Fetch Assets for logged-in user
    assets_result = await session.exec(select(Assets).where(Assets.user_id == user.id))
    assets = assets_result.all()

    # Convert SQL rows → Pydantic
    assets_dto = [
        AssetResponse(
            id=a.id,
            user_id=a.user_id,
            name=a.name,
            type=a.type,
            status=a.status,
        )
        for a in assets
    ]

    return BaseResponse(
        code=200,
        data={
            "user_id": str(user.id),
            "email": user.email_id,
            "name": user.user_name,
            "assets": assets_dto,
        },
    )


@router.put("/update-profile", response_model=BaseResponse)
async def update_profile(
    body: UpdateProfileRequest,
    session: AsyncSession = Depends(get_async_session),
    user_id: str = Depends(get_current_user),
):
    # Get logged-in user
    user = await session.get(Users, user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    # --- Update basic profile fields ---
    if body.name is not None:
        user.user_name = body.name

    if body.email is not None:
        user.email_id = body.email

    if body.dob is not None:
        dob_str = body.dob.replace(".", "-")  # convert dots to dashes
        try:
            user.dob = datetime.strptime(dob_str, "%Y-%m-%d").date()
        except ValueError:
            raise HTTPException(
                status_code=400, detail="DOB must be YYYY-MM-DD or YYYY.MM.DD"
            )

    if body.address is not None:
        user.address = body.address  # ensure DB has this column

    # --- Handle password update ---
    if body.current_password and body.new_password:
        # Check current password matches
        from src.auth.utils import verify_password, hash_password

        if not verify_password(body.current_password, user.password):
            raise HTTPException(
                status_code=400,
                detail="Current password is incorrect",
            )

        # update password
        user.password = hash_password(body.new_password)

    elif body.new_password and not body.current_password:
        raise HTTPException(
            status_code=400,
            detail="Current password is required to set new password",
        )

    # Save
    await session.commit()
    await session.refresh(user)

    # Response matches frontend expectation
    return BaseResponse(
        code=200,
        data={
            "user": {
                "id": str(user.id),
                "name": user.user_name,
                "email": user.email_id,
                "dob": getattr(user, "dob", None),
                "address": getattr(user, "address", None),
            }
        },
    )