• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gcivil-nyu-org / fall24-monday-team2 / 488

16 Dec 2024 11:56PM UTC coverage: 82.626% (+3.4%) from 79.234%
488

Pull #167

travis-pro

web-flow
Merge 815598c66 into f38dc7d7f
Pull Request #167: User trainer access

264 of 321 new or added lines in 3 files covered. (82.24%)

52 existing lines in 3 files now uncovered.

4028 of 4875 relevant lines covered (82.63%)

0.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

59.89
/FitOn/views.py
1
from django.shortcuts import render, redirect
1✔
2
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
1✔
3

4
# from django.core.serializers import serialize
5
from .models import Exercise, MuscleGroup, User
1✔
6

7
# from django.http import JsonResponse
8

9
from .dynamodb import (
1✔
10
    add_fitness_trainer_application,
11
    # create_post,
12
    post_comment,
13
    create_thread,
14
    create_reply,
15
    create_user,
16
    delete_user_by_username,
17
    delete_post,
18
    fetch_posts_for_thread,
19
    get_fitness_trainer_applications,
20
    get_last_reset_request_time,
21
    get_user,
22
    get_user_by_email,
23
    # get_user_by_uid,
24
    get_user_by_username,
25
    update_reset_request_time,
26
    update_user,
27
    update_user_password,
28
    upload_profile_picture,
29
    fetch_filtered_threads,
30
    fetch_all_users,
31
    get_fitness_data,
32
    dynamodb,
33
    threads_table,
34
    get_fitness_trainers,
35
    make_fitness_trainer,
36
    remove_fitness_trainer,
37
    delete_reply,
38
    fetch_reported_threads_and_comments,
39
    mark_thread_as_reported,
40
    mark_comment_as_reported,
41
    posts_table,
42
    delete_thread_by_id,
43
    get_section_stats,
44
    get_users_without_specific_username,
45
    get_chat_history_from_db,
46
    chat_table,
47
    get_standard_users,
48
    send_data_request_to_user,
49
    add_to_list,
50
    remove_from_list,
51
    cancel_data_request_to_user,
52
    get_mock_user_by_uid,
53
    mark_user_as_warned_thread,
54
    mark_user_as_warned_comment,
55
    set_user_warned_to_false,
56
    get_users_by_username_query,
57
    get_step_user_goals,
58
    get_sleep_user_goals,
59
    get_weight_user_goals,
60
    get_custom_user_goals,
61
    get_activity_user_goals,
62
    get_user_by_uid,
63
    store_custom_plan,
64
    custom_plans_table,
65
)
66
from .rds import rds_main, fetch_user_data
1✔
67

68
from .forms import (
1✔
69
    FitnessTrainerApplicationForm,
70
    LoginForm,
71
    PasswordResetForm,
72
    ProfileForm,
73
    SetNewPasswordForm,
74
    SignUpForm,
75
)
76

77
from .models import GroupChatMember
1✔
78

79
# from .models import PasswordResetRequest
80
import datetime as dt
1✔
81
import re
1✔
82
import ast
1✔
83
from datetime import datetime
1✔
84
import pytz
1✔
85
from datetime import timedelta
1✔
86
from django.contrib.auth.hashers import make_password, check_password
1✔
87
import pandas as pd
1✔
88

89
# from django.contrib.auth.models import User
90
from django.contrib.auth.tokens import default_token_generator
1✔
91
from django.contrib.auth import logout
1✔
92
from django.contrib import messages
1✔
93
from django.conf import settings
1✔
94

95
# from django.core.files.storage import FileSystemStorage
96
from django.core.mail import EmailMessage
1✔
97

98
# from django.core.mail.backends.locmem import EmailBackend
99
# from django.core.mail import EmailMultiAlternatives
100
from django.http import JsonResponse, HttpResponseForbidden
1✔
101
from django.template.loader import render_to_string
1✔
102
from django.urls import reverse
1✔
103
from django.utils import timezone
1✔
104

105
# from django.utils.encoding import force_bytes
106
# from django.utils.html import strip_tags
107
# from django.utils.http import urlsafe_base64_encode
108
# import os
109
from asgiref.sync import sync_to_async
1✔
110
from django.utils.encoding import force_bytes, force_str
1✔
111
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
1✔
112
import uuid
1✔
113
import boto3
1✔
114
import pymysql
1✔
115
from google_auth_oauthlib.flow import Flow
1✔
116
import requests
1✔
117

118
# from django.contrib.auth.decorators import login_required
119
import json
1✔
120

121
# from google import Things
122
from google.oauth2.credentials import Credentials
1✔
123
from googleapiclient.discovery import build
1✔
124
import asyncio
1✔
125
from collections import defaultdict
1✔
126
from boto3.dynamodb.conditions import Key, Attr
1✔
127
from django.views.decorators.csrf import csrf_exempt
1✔
128

129
# Define metric data types
130
dataTypes = {
1✔
131
    "heart_rate": "com.google.heart_rate.bpm",
132
    "resting_heart_rate": "com.google.heart_rate.bpm",
133
    "steps": "com.google.step_count.delta",
134
    "sleep": "com.google.sleep.segment",
135
    "oxygen": "com.google.oxygen_saturation",
136
    "activity": "com.google.activity.segment",
137
    "glucose": "com.google.blood_glucose",
138
    "pressure": "com.google.blood_pressure",
139
}
140

141
df = pd.read_csv("google_fit_activity_types.csv")
1✔
142

143
SCOPES = [
1✔
144
    "https://www.googleapis.com/auth/fitness.activity.read",
145
    "https://www.googleapis.com/auth/fitness.body.read",
146
    "https://www.googleapis.com/auth/fitness.heart_rate.read",
147
    "https://www.googleapis.com/auth/fitness.sleep.read",
148
    "https://www.googleapis.com/auth/fitness.blood_glucose.read",
149
    "https://www.googleapis.com/auth/fitness.blood_pressure.read",
150
    "https://www.googleapis.com/auth/fitness.body_temperature.read",
151
    "https://www.googleapis.com/auth/fitness.location.read",
152
    "https://www.googleapis.com/auth/fitness.nutrition.read",
153
    "https://www.googleapis.com/auth/fitness.oxygen_saturation.read",
154
    "https://www.googleapis.com/auth/fitness.reproductive_health.read",
155
]
156

157

158
def homepage(request):
1✔
159
    username = request.session.get("username", "Guest")
1✔
160
    user_id = request.session.get("user_id")  # Retrieve user_id from session
1✔
161

162
    if not user_id:
1✔
163
        return redirect("login")  # Redirect to login if the user is not authenticated
1✔
164

165
    # Fetch user details from DynamoDB
166
    user = get_user(user_id)  # Replace with your function to fetch the user
1✔
167
    is_warned = user.get("is_warned", False)  # Get the 'is_warned' status
1✔
168

169
    return render(request, "home.html", {"username": username, "is_warned": is_warned})
1✔
170

171

172
def list_metrics(request):
1✔
173
    return render(request, "metric_list.html")
×
174

175

176
@sync_to_async
1✔
177
def add_message(request, level, message):
1✔
178
    messages.add_message(request, level, message)
1✔
179

180

181
@sync_to_async
1✔
182
def perform_redirect(url_name):
1✔
183
    return redirect(url_name)
1✔
184

185

186
def login(request):
1✔
187
    error_message = None
1✔
188

189
    if request.method == "POST":
1✔
190
        form = LoginForm(request.POST)
1✔
191
        if form.is_valid():
1✔
192
            username = form.cleaned_data["username"]
1✔
193
            password = form.cleaned_data["password"]
1✔
194

195
            # Query DynamoDB for the user by username
196
            user = get_user_by_username(username)
1✔
197

198
            if user:
1✔
199
                # Get the hashed password from DynamoDB
200
                stored_password = user["password"]
1✔
201
                user_id = user["user_id"]
1✔
202

203
                # Verify the password using Django's check_password
204
                if check_password(password, stored_password):
1✔
205
                    # Set the session and redirect to the homepage
206
                    request.session["username"] = username
1✔
207
                    request.session["user_id"] = user_id
1✔
208
                    return redirect("homepage")
1✔
209
                else:
210
                    error_message = "Invalid password. Please try again."
1✔
211
            else:
212
                error_message = "User does not exist."
1✔
213

214
    else:
215
        form = LoginForm()
×
216

217
    return render(request, "login.html", {"form": form, "error_message": error_message})
1✔
218

219

220
def custom_logout(request):
1✔
221
    # Log out the user
222
    logout(request)
1✔
223

224
    # Clear the entire session to ensure no data is persisted
225
    request.session.flush()
1✔
226

227
    # Redirect to the homepage or a specific page after logging out
228
    response = redirect("login")
1✔
229
    response["Cache-Control"] = "no-cache, no-store, must-revalidate"  # HTTP 1.1
1✔
230
    response["Pragma"] = "no-cache"  # HTTP 1.0
1✔
231
    response["Expires"] = "0"  # Proxies
1✔
232
    return response
1✔
233

234

235
def signup(request):
1✔
236
    if request.method == "POST":
1✔
237
        form = SignUpForm(request.POST)
1✔
238
        if form.is_valid():
1✔
239
            username = form.cleaned_data["username"]
1✔
240
            email = form.cleaned_data["email"]
1✔
241
            name = form.cleaned_data["name"]
1✔
242
            date_of_birth = form.cleaned_data["date_of_birth"]
1✔
243
            gender = form.cleaned_data["gender"]
1✔
244
            password = form.cleaned_data["password"]
1✔
245
            height = form.cleaned_data["height"]
1✔
246
            weight = form.cleaned_data["weight"]
1✔
247

248
            # Hash the password before saving it
249
            hashed_password = make_password(password)
1✔
250

251
            # Create a unique user ID
252
            user_id = str(uuid.uuid4())
1✔
253

254
            # Sync data with DynamoDB
255
            if create_user(
1✔
256
                user_id,
257
                username,
258
                email,
259
                name,
260
                date_of_birth,
261
                gender,
262
                height,
263
                weight,
264
                hashed_password,
265
            ):
266
                request.session["username"] = username
1✔
267
                request.session["user_id"] = user_id
1✔
268
                return redirect("homepage")
1✔
269
            else:
270
                form.add_error(None, "Error creating user in DynamoDB.")
×
271
    else:
272
        form = SignUpForm()  # Return an empty form for the GET request
×
273

274
    return render(
1✔
275
        request, "signup.html", {"form": form}
276
    )  # Ensure form is passed for both GET and POST
277

278

279
def password_reset_request(request):
1✔
280
    countdown = None
1✔
281
    error_message = None
1✔
282
    form = PasswordResetForm(request.POST or None)
1✔
283
    if request.method == "POST" and form.is_valid():
1✔
284
        email = form.cleaned_data["email"]
1✔
285
        if not email:
1✔
286
            error_message = "The email you entered is not registered with an account."
×
287
            return render(
×
288
                request,
289
                "password_reset_request.html",
290
                {"form": form, "error_message": error_message},
291
            )
292
        user = get_user_by_email(email)
1✔
293
        if user:
1✔
294
            if not user.is_active:
1✔
UNCOV
295
                error_message = (
×
296
                    "The email you entered is not registered with an account."
297
                )
UNCOV
298
                return render(
×
299
                    request,
300
                    "password_reset_request.html",
301
                    {"form": form, "error_message": error_message},
302
                )
303
            last_request_time_str = get_last_reset_request_time(user.user_id)
1✔
304
            if last_request_time_str:
1✔
305
                last_request_time = timezone.datetime.fromisoformat(
1✔
306
                    last_request_time_str
307
                )
308
                time_since_last_request = timezone.now() - last_request_time
1✔
309
                if time_since_last_request < timedelta(minutes=1):
1✔
310
                    countdown = 60 - time_since_last_request.seconds
1✔
311
                    return render(
1✔
312
                        request,
313
                        "password_reset_request.html",
314
                        {"form": form, "countdown": countdown},
315
                    )
316
            update_reset_request_time(user.user_id)
1✔
317
            reset_token = default_token_generator.make_token(user)
1✔
318
            uid = urlsafe_base64_encode(force_bytes(user.user_id))
1✔
319
            reset_url = request.build_absolute_uri(
1✔
320
                reverse("password_reset_confirm", args=[uid, reset_token])
321
            )
322
            message = render_to_string(
1✔
323
                "password_reset_email.html",
324
                {"username": user.username, "reset_url": reset_url},
325
            )
326
            email_message = EmailMessage(
1✔
327
                "Password Reset Requested",
328
                message,
329
                "fiton.notifications@gmail.com",
330
                [email],
331
            )
332
            email_message.content_subtype = "html"
1✔
333
            email_message.send()
1✔
334
            return redirect("password_reset_done")
1✔
335
        error_message = "The email you entered is not registered with an account."
1✔
336
        return render(
1✔
337
            request,
338
            "password_reset_request.html",
339
            {"form": form, "error_message": error_message},
340
        )
341
    return render(
1✔
342
        request,
343
        "password_reset_request.html",
344
        {"form": form, "error_message": error_message, "countdown": countdown},
345
    )
346

347

348
def password_reset_confirm(request, uidb64, token):
1✔
349
    if not uidb64 or not token:
1✔
350
        return render(
×
351
            request,
352
            "password_reset_invalid.html",
353
            {"error_message": "The password reset link is invalid or has expired."},
354
        )
355
    try:
1✔
356
        user_id = force_str(urlsafe_base64_decode(uidb64))
1✔
357
        user = get_mock_user_by_uid(user_id)
1✔
358
        if user and default_token_generator.check_token(user, token):
1✔
359
            form = SetNewPasswordForm(request.POST or None)
1✔
360
            if request.method == "POST" and form.is_valid():
1✔
361
                new_password = form.cleaned_data["new_password"]
1✔
362
                confirm_password = form.cleaned_data["confirm_password"]
1✔
363
                if new_password == confirm_password:
1✔
364
                    update_user_password(user.user_id, new_password)
1✔
365
                    messages.success(
1✔
366
                        request, "Your password has been successfully reset."
367
                    )
368
                    return redirect("password_reset_complete")
1✔
369
                form.add_error("confirm_password", "Passwords do not match.")
×
370
            return render(request, "password_reset_confirm.html", {"form": form})
1✔
371
        return render(
1✔
372
            request,
373
            "password_reset_invalid.html",
374
            {"error_message": "The password reset link is invalid or has expired."},
375
        )
376
    except Exception:
1✔
377
        return render(
1✔
378
            request,
379
            "password_reset_invalid.html",
380
            {"error_message": "The password reset link is invalid or has expired."},
381
        )
382

383

384
def password_reset_complete(request):
1✔
385
    return render(request, "password_reset_complete.html")
1✔
386

387

388
def password_reset_done(request):
1✔
389
    return render(request, "password_reset_done.html")
1✔
390

391

392
def upload_profile_picture_view(request):
1✔
393
    user_id = request.session.get("user_id")  # Get the user ID from the session
×
394

395
    if request.method == "POST" and request.FILES.get("profile_picture"):
×
396
        profile_picture = request.FILES["profile_picture"]
×
397

398
        # Upload to S3 and get the URL
399
        new_image_url = upload_profile_picture(user_id, profile_picture)
×
400

401
        if new_image_url:
×
402
            return JsonResponse({"success": True, "new_image_url": new_image_url})
×
403
        else:
404
            return JsonResponse(
×
405
                {"success": False, "message": "Failed to upload image to S3"}
406
            )
407

408
    return JsonResponse({"success": False, "message": "No file uploaded"})
×
409

410

411
def profile_view(request):
1✔
412
    user_id = request.session.get("user_id")
×
413

414
    # Fetch user details from DynamoDB
415
    user = get_user(user_id)
×
416

417
    if not user:
×
418
        messages.error(request, "User not found.")
×
419
        return redirect("login")
×
420

421
    if request.method == "POST":
×
422
        # Handle profile picture upload
423
        if "profile_picture" in request.FILES:
×
424
            profile_picture = request.FILES["profile_picture"]
×
425
            image_url = upload_profile_picture(user_id, profile_picture)
×
426

427
            if image_url:
×
428
                # Update the user's profile picture URL in DynamoDB
429
                update_user(user_id, {"profile_picture": {"Value": image_url}})
×
430
                messages.success(request, "Profile picture updated successfully!")
×
431
                return redirect("profile")
×
432
            else:
433
                messages.error(request, "Failed to upload profile picture.")
×
434

435
        # Handling other profile updates
436
        form = ProfileForm(request.POST)
×
437
        if form.is_valid():
×
438
            # Prepare data to be updated
439
            update_data = {
×
440
                "name": {"Value": form.cleaned_data["name"]},
441
                "date_of_birth": {"Value": form.cleaned_data["date_of_birth"]},
442
                "gender": {"Value": form.cleaned_data["gender"]},
443
                "height": {"Value": form.cleaned_data["height"]},
444
                "weight": {"Value": form.cleaned_data["weight"]},
445
                "bio": {"Value": form.cleaned_data["bio"]},
446
                "address": {"Value": form.cleaned_data["address"]},
447
            }
448

449
            # Only add phone number and country code if provided
450
            country_code = form.cleaned_data["country_code"]
×
451
            phone_number = form.cleaned_data["phone_number"]
×
452

453
            if country_code:  # If country code is provided, add it to update_data
×
454
                update_data["country_code"] = {"Value": country_code}
×
455
            if phone_number:  # If phone number is provided, add it to update_data
×
456
                update_data["phone_number"] = {"Value": phone_number}
×
457

458
            update_user(user_id, update_data)
×
459
            messages.success(request, "Profile updated successfully!")
×
460
            return redirect("profile")
×
461
        else:
462
            messages.error(request, "Please correct the errors below")
×
463
    else:
464
        form = ProfileForm(
×
465
            initial={
466
                "name": user.get("name", ""),
467
                "date_of_birth": user.get("date_of_birth", ""),
468
                "email": user.get("email", ""),
469
                "gender": user.get("gender", ""),
470
                "phone_number": user.get("phone_number", ""),
471
                "height": user.get("height", ""),
472
                "weight": user.get("weight", ""),
473
                "address": user.get("address", ""),
474
                "bio": user.get("bio", ""),
475
                "country_code": user.get("country_code", ""),  # Default country code
476
            }
477
        )
478

479
    return render(request, "profile.html", {"form": form, "user": user})
×
480

481

482
def deactivate_account(request):
1✔
483
    # This simply shows the confirmation page
484
    return render(request, "deactivate.html")
1✔
485

486

487
def confirm_deactivation(request):
1✔
488
    if request.method == "POST":
1✔
489
        username = request.session.get("username")
1✔
490

491
        if username:
1✔
492
            # Delete the user from DynamoDB
493
            if delete_user_by_username(username):
1✔
494
                # Log the user out and redirect to the homepage
495
                logout(request)
1✔
496
                request.session.flush()
1✔
497
                return redirect("homepage")  # Redirect to homepage after deactivation
1✔
498
            else:
499
                return render(
×
500
                    request,
501
                    "deactivate.html",
502
                    {"error_message": "Error deleting the account."},
503
                )
504
        else:
505
            # Redirect to login if there's no username in session
506
            return redirect("login")
1✔
507
    else:
508
        # Redirect to the deactivate page if the request method is not POST
509
        return redirect("deactivate_account")
×
510

511

512
def authorize_google_fit(request):
1✔
513
    credentials = request.session.get("google_fit_credentials")
1✔
514
    # print("inside auth")
515

516
    if not credentials or credentials.expired:
1✔
517
        # if settings.DEBUG == True:
518
        #     flow = Flow.from_client_secrets_file('credentials.json', SCOPES)
519
        # else:
520
        # print(settings.GOOGLEFIT_CLIENT_CONFIG)
521
        flow = Flow.from_client_config(settings.GOOGLEFIT_CLIENT_CONFIG, SCOPES)
1✔
522
        flow.redirect_uri = request.build_absolute_uri(
1✔
523
            reverse("callback_google_fit")
524
        ).replace("http://", "https://")
525
        # print("Redirected URI: ", flow.redirect_uri)
526
        authorization_url, state = flow.authorization_url(
1✔
527
            access_type="offline", include_granted_scopes="true"
528
        )
529
        # Debugging print statements
530
        # print("Authorization URL:", authorization_url)
531
        # print("State:", state)
532

533
        request.session["google_fit_state"] = state
1✔
534
        return redirect(authorization_url)
1✔
535
    return redirect("profile")
×
536

537

538
def callback_google_fit(request):
1✔
539
    user_id = request.session.get("user_id")
1✔
540
    # print("Inside Callback")
541
    # print("Session: ")
542
    # Fetch user details from DynamoDB
543
    user = get_user(user_id)
1✔
544
    state = request.session.get("google_fit_state")
1✔
545
    if state:
1✔
546
        # if settings.DEBUG:
547
        #     flow = Flow.from_client_secrets_file('credentials.json', SCOPES, state=state)
548
        # else:
549
        # print("inside calback")
550

551
        flow = Flow.from_client_config(
1✔
552
            settings.GOOGLEFIT_CLIENT_CONFIG, SCOPES, state=state
553
        )
554
        # print("flow=", flow)
555
        flow.redirect_uri = request.build_absolute_uri(reverse("callback_google_fit"))
1✔
556
        flow.fetch_token(authorization_response=request.build_absolute_uri())
1✔
557

558
        credentials = flow.credentials
1✔
559
        request.session["credentials"] = {
1✔
560
            "token": credentials.token,
561
            "refresh_token": credentials.refresh_token,
562
            "token_uri": credentials.token_uri,
563
            "client_id": credentials.client_id,
564
            "client_secret": credentials.client_secret,
565
            "scopes": credentials.scopes,
566
        }
567

568
        form = ProfileForm(
1✔
569
            initial={
570
                "name": user.get("name", ""),
571
                "date_of_birth": user.get("date_of_birth", ""),
572
                "email": user.get("email", ""),
573
                "gender": user.get("gender", ""),
574
                "phone_number": user.get("phone_number", ""),
575
                "address": user.get("address", ""),
576
                "bio": user.get("bio", ""),
577
                "country_code": user.get("country_code", ""),  # Default country code
578
            }
579
        )
580

581
        # Set a success message
582
        messages.success(request, "Signed in Successfully")
1✔
583

584
        # Set login_success to True for successful login
585
        login_success = True
1✔
586
        return render(
1✔
587
            request,
588
            "profile.html",
589
            {"login_success": login_success, "form": form, "user": user},
590
        )
591

592
    # In case of failure or missing state, redirect to a fallback page or profile without login_success
593
    # Handle invalid state
594
    messages.error(request, "Sign-in failed. Please try again.")
1✔
595
    return redirect("homepage")
1✔
596

597

598
def delink_google_fit(request):
1✔
599
    if "credentials" in request.session:
1✔
600
        credentials = Credentials(**request.session["credentials"])
1✔
601

602
        # Revoke the token on Google's side (optional but recommended)
603
        revoke_endpoint = "https://accounts.google.com/o/oauth2/revoke"
1✔
604
        token = credentials.token
1✔
605
        revoke_response = requests.post(
1✔
606
            revoke_endpoint,
607
            params={"token": token},
608
            headers={"content-type": "application/x-www-form-urlencoded"},
609
        )
610

611
        if revoke_response.status_code == 200:
1✔
612
            # print("Google account successfully revoked.")
613
            ...
1✔
614
        else:
615
            # print("Failed to revoke Google account.")
616
            ...
×
617

618
        # Remove credentials from the session
619
        del request.session["credentials"]
1✔
620

621
        # Display a message to the user (optional)
622
        messages.success(request, "Your Google account has been successfully delinked.")
1✔
623
    else:
624
        messages.error(request, "No linked Google account found.")
×
625

626
    return redirect("profile")
1✔
627

628

629
# --------------------------------- #
630
# User + Fitness Trainer Functions  #
631
# --------------------------------- #
632
def fitness_trainer_application_view(request):
1✔
633
    user_id = request.session.get("user_id")
1✔
634
    if request.method == "POST":
1✔
635
        form = FitnessTrainerApplicationForm(request.POST, request.FILES)
1✔
636
        if form.is_valid():
1✔
637
            past_experience_trainer = form.cleaned_data.get("past_experience_trainer")
×
638
            past_experience_dietician = form.cleaned_data.get(
×
639
                "past_experience_dietician"
640
            )
641
            resume = request.FILES["resume"]
×
642
            certifications = request.FILES.get("certifications")
×
643
            reference_name = form.cleaned_data.get("reference_name")
×
644
            reference_contact = form.cleaned_data.get("reference_contact")
×
645
            # Call the DynamoDB function, making sure all names match
646
            add_fitness_trainer_application(
×
647
                user_id=user_id,
648
                past_experience_trainer=past_experience_trainer,
649
                past_experience_dietician=past_experience_dietician,
650
                resume=resume,
651
                certifications=certifications,
652
                reference_name=reference_name,
653
                reference_contact=reference_contact,
654
            )
655
            # Notify user and redirect
656
            messages.success(
×
657
                request, "Your application has been submitted successfully!"
658
            )
659
            return redirect("profile")
×
660
    else:
661
        form = FitnessTrainerApplicationForm()
1✔
662
    return render(request, "fitness_trainer_application.html", {"form": form})
1✔
663

664

665
def fitness_trainer_applications_list_view(request):
1✔
666
    # Check if the current user is an admin
667
    user_id = request.session.get("user_id")
1✔
668
    user = get_user(user_id)
1✔
669
    if not user or not user.get("is_admin"):
1✔
670
        return HttpResponseForbidden("You do not have permission to access this page.")
1✔
671
    # Retrieve applications from DynamoDB
672
    applications = get_fitness_trainer_applications()
1✔
673
    # Render the list of applications
674
    return render(
1✔
675
        request,
676
        "fitness_trainer_applications_list.html",
677
        {"applications": applications},
678
    )
679

680

681
def approve_fitness_trainer(request):
1✔
682
    if (
1✔
683
        request.method == "POST"
684
        and request.headers.get("x-requested-with") == "XMLHttpRequest"
685
    ):
686
        data = json.loads(request.body)
1✔
687
        username = data.get("username")
1✔
688
        user = get_user_by_username(username)
1✔
689
        if not user:
1✔
690
            return JsonResponse(
×
691
                {"status": "error", "message": "User not found"}, status=404
692
            )
693
        make_fitness_trainer(user["user_id"])
1✔
694
        subject = "Fitness Trainer Application Approved"
1✔
695
        message = render_to_string(
1✔
696
            "fitness_trainer_email.html",
697
            {"username": username, "approval": True, "reason": ""},
698
        )
699
        senderEmail = "fiton.notifications@gmail.com"
1✔
700
        userEmail = user.get("email")
1✔
701
        email_message = EmailMessage(
1✔
702
            subject,
703
            message,
704
            senderEmail,
705
            [userEmail],
706
        )
707
        email_message.content_subtype = "html"
1✔
708
        email_message.send()
1✔
709
        return JsonResponse(
1✔
710
            {"status": "success", "message": "Fitness Trainer has been approved"}
711
        )
712
    return JsonResponse({"status": "error", "message": "Invalid request"}, status=400)
×
713

714

715
def reject_fitness_trainer(request):
1✔
716
    if (
1✔
717
        request.method == "POST"
718
        and request.headers.get("x-requested-with") == "XMLHttpRequest"
719
    ):
720
        data = json.loads(request.body)
1✔
721
        username = data.get("username")
1✔
722
        user = get_user_by_username(username)
1✔
723
        if not user:
1✔
724
            return JsonResponse(
×
725
                {"status": "error", "message": "User not found"}, status=404
726
            )
727
        remove_fitness_trainer(user["user_id"])
1✔
728
        subject = "Fitness Trainer Application Rejected"
1✔
729
        message = render_to_string(
1✔
730
            "fitness_trainer_email.html",
731
            {
732
                "username": username,
733
                "approval": False,
734
                "reason": "We are not accepting fitness trainers right now, please try again later",
735
            },
736
        )
737
        senderEmail = "fiton.notifications@gmail.com"
1✔
738
        userEmail = user.get("email")
1✔
739
        email_message = EmailMessage(
1✔
740
            subject,
741
            message,
742
            senderEmail,
743
            [userEmail],
744
        )
745
        email_message.content_subtype = "html"
1✔
746
        email_message.send()
1✔
747
        return JsonResponse(
1✔
748
            {"status": "success", "message": "Fitness Trainer application rejected"}
749
        )
750
    return JsonResponse({"status": "error", "message": "Invalid request"}, status=400)
×
751

752

753
def accept_trainer(request):
1✔
754
    if request.method == "POST":
1✔
755
        data = json.loads(request.body)
1✔
756
        trainer_id = data.get("trainer_id")
1✔
757
        user_id = request.session.get("user_id")
1✔
758
        trainer = get_user(trainer_id)
1✔
759
        user = get_user(user_id)
1✔
760
        if not user or not trainer:
1✔
761
            return JsonResponse({"status": "error", "message": "User is not found"})
×
762
        # Update access lists in DynamoDB
763
        add_to_list(user_id, "trainers_with_access", trainer_id)
1✔
764
        add_to_list(trainer_id, "users_with_access", user_id)
1✔
765
        # # Remove trainer from the user's waiting list
766
        remove_from_list(user_id, "waiting_list_of_trainers", trainer_id)
1✔
767
        remove_from_list(trainer_id, "waiting_list_of_users", user_id)
1✔
768
        return JsonResponse({"status": "success"})
1✔
769
    return JsonResponse({"status": "error", "message": "Invalid request"})
×
770

771

772
def deny_trainer(request):
1✔
773
    if request.method == "POST":
1✔
774
        data = json.loads(request.body)
1✔
775
        trainer_id = data.get("trainer_id")
1✔
776
        user_id = request.session.get("user_id")
1✔
777
        trainer = get_user(trainer_id)
1✔
778
        user = get_user(user_id)
1✔
779
        if not user or not trainer:
1✔
780
            return JsonResponse({"status": "error", "message": "User is not found"})
×
781
        # Remove trainer from the user's waiting list
782
        remove_from_list(user_id, "waiting_list_of_trainers", trainer_id)
1✔
783
        remove_from_list(trainer_id, "waiting_list_of_users", user_id)
1✔
784
        return JsonResponse({"status": "success"})
1✔
785
    return JsonResponse({"status": "error", "message": "Invalid request"})
×
786

787

788
def provide_access_to_trainer(request):
1✔
789
    if request.method == "POST":
1✔
790
        data = json.loads(request.body)
1✔
791
        trainer_id = data.get("trainer_id")
1✔
792
        user_id = request.session.get("user_id")
1✔
793
        trainer = get_user(trainer_id)
1✔
794
        user = get_user(user_id)
1✔
795
        if not user or not trainer:
1✔
796
            return JsonResponse({"status": "error", "message": "User is not found"})
×
797
        # Update user and trainer records in DynamoDB
798
        add_to_list(user_id, "trainers_with_access", trainer_id)
1✔
799
        add_to_list(trainer_id, "users_with_access", user_id)
1✔
800
        return JsonResponse({"status": "success"})
1✔
801
    return JsonResponse({"status": "error", "message": "Invalid request"})
×
802

803

804
def revoke_access_to_trainer(request):
1✔
805
    if request.method == "POST":
1✔
806
        data = json.loads(request.body)
1✔
807
        trainer_id = data.get("trainer_id")
1✔
808
        user_id = request.session.get("user_id")
1✔
809
        if not user_id or not trainer_id:
1✔
810
            return JsonResponse({"status": "error", "message": "Invalid data"})
1✔
811
        try:
×
812
            remove_from_list(user_id, "trainers_with_access", trainer_id)
×
813
            remove_from_list(trainer_id, "users_with_access", user_id)
×
814
            return JsonResponse(
×
815
                {
816
                    "status": "success",
817
                    "message": f"Revoked access for Trainer: {trainer_id}",
818
                }
819
            )
820
        except Exception as e:
×
821
            return JsonResponse({"status": "error", "message": str(e)})
×
822
    else:
823
        return JsonResponse({"status": "error", "message": "Invalid request method"})
×
824

825

826
def fitness_trainers_list_view(request):
1✔
827
    # Make sure that the current user is not a fitness trainer
828
    user_id = request.session.get("user_id")
1✔
829
    user = get_user(user_id)
1✔
830
    if not user or user.get("is_fitness_trainer"):
1✔
831
        return HttpResponseForbidden("You do not have permission to access this page")
1✔
832
    # Retrieve list of trainers from DynamoDB
833
    trainers = get_fitness_trainers()
1✔
834
    # Extract lists from the user's data
835
    waiting_list_of_trainers = user.get("waiting_list_of_trainers", [])
1✔
836
    trainers_with_access = user.get("trainers_with_access", [])
1✔
837
    # Separate trainers into categories
838
    trainers_in_waiting_list = [
1✔
839
        trainer
840
        for trainer in trainers
841
        if trainer["user_id"] in waiting_list_of_trainers
842
    ]
843
    my_trainers = []
1✔
844
    for trainer in trainers:
1✔
845
        if trainer["user_id"] in trainers_with_access:
1✔
846
            # Check if the user has an existing custom workout plan with this trainer
NEW
847
            response = custom_plans_table.scan(
×
848
                FilterExpression="user_id = :user_id AND trainer_id = :trainer_id",
849
                ExpressionAttributeValues={
850
                    ":user_id": user_id,
851
                    ":trainer_id": trainer["user_id"],
852
                },
853
            )
NEW
854
            existing_plan = None
×
NEW
855
            if response["Items"]:
×
NEW
856
                existing_plan = response["Items"][
×
857
                    0
858
                ]  # Assuming the first item is the existing plan
859
            # Add the trainer along with the existing plan information
NEW
860
            trainer["existing_plan"] = existing_plan
×
NEW
861
            my_trainers.append(trainer)
×
862

863
    remaining_trainers = [
1✔
864
        trainer
865
        for trainer in trainers
866
        if trainer["user_id"] not in waiting_list_of_trainers
867
        and trainer["user_id"] not in trainers_with_access
868
    ]
869
    # Pass the categorized trainers to the template
870
    context = {
1✔
871
        "trainers_in_waiting_list": trainers_in_waiting_list,
872
        "my_trainers": my_trainers,
873
        "remaining_trainers": remaining_trainers,
874
    }
875
    return render(request, "fitness_trainers_list.html", context)
1✔
876

877

878
def standard_users_list_view(request):
1✔
879
    # Check if the current user is a verified fitness trainer
880
    user_id = request.session.get("user_id")
1✔
881
    user = get_user(user_id)
1✔
882
    if not user or not user.get("is_fitness_trainer"):
1✔
883
        return HttpResponseForbidden("You do not have permission to access this page")
1✔
884
    # Retrieve list of standard users from DynamoDB
885
    standard_users = get_standard_users()
1✔
886
    waiting_list_of_users = user.get("waiting_list_of_users", [])
1✔
887
    users_in_waiting_list = [
1✔
888
        user for user in standard_users if user["user_id"] in waiting_list_of_users
889
    ]
890
    users_with_access = user.get("users_with_access", [])
1✔
891
    my_users = [user for user in standard_users if user["user_id"] in users_with_access]
1✔
892
    remaining_users = [
1✔
893
        user
894
        for user in standard_users
895
        if user["user_id"] not in waiting_list_of_users + users_with_access
896
    ]
897
    return render(
1✔
898
        request,
899
        "standard_users_list.html",
900
        {
901
            "users_in_waiting_list": users_in_waiting_list,
902
            "my_users": my_users,
903
            "remaining_users": remaining_users,
904
        },
905
    )
906

907

908
def send_data_request(request):
1✔
909
    if request.method == "POST":
1✔
910
        data = json.loads(request.body)
1✔
911
        standard_user_id = data.get("user_id")
1✔
912
        fitness_trainer_id = request.session.get("user_id")
1✔
913
        success = send_data_request_to_user(fitness_trainer_id, standard_user_id)
1✔
914
        if success:
1✔
915
            return JsonResponse({"message": "Request sent successfully!"}, status=200)
×
916
        else:
917
            return JsonResponse({"error": "Failed to send request"}, status=400)
1✔
918
    return JsonResponse({"error": "Invalid request"}, status=400)
×
919

920

921
def cancel_data_request(request):
1✔
922
    if request.method == "POST":
1✔
923
        data = json.loads(request.body)
1✔
924
        standard_user_id = data.get("user_id")
1✔
925
        fitness_trainer_id = request.session.get("user_id")
1✔
926
        if not standard_user_id:
1✔
927
            return JsonResponse({"error": "User ID is required"}, status=400)
×
928
        success = cancel_data_request_to_user(fitness_trainer_id, standard_user_id)
1✔
929
        if success:
1✔
930
            return JsonResponse(
×
931
                {"message": "Request cancelled successfully"}, status=200
932
            )
933
        else:
934
            return JsonResponse({"error": "Failed to cancel the request"}, status=400)
1✔
935
    else:
936
        return JsonResponse({"error": "Invalid method"}, status=405)
×
937

938

939
def view_user_data(request, user_id):
1✔
940
    try:
1✔
941
        user_data = request.session.get("user_data")
1✔
942
        if not user_data:
1✔
943
            raise ValueError("User data is not available in the session.")
1✔
NEW
944
        if "user_id" not in user_data:
×
NEW
945
            user_data["user_id"] = user_id
×
946

NEW
947
        exercises = Exercise.objects.all()
×
948

NEW
949
        response = custom_plans_table.scan(
×
950
            FilterExpression="user_id = :user_id AND trainer_id = :trainer_id",
951
            ExpressionAttributeValues={
952
                ":user_id": user_id,
953
                ":trainer_id": request.session.get("user_id"),
954
            },
955
        )
NEW
956
        existing_plan = None
×
NEW
957
        if response["Items"]:
×
NEW
958
            existing_plan = response["Items"][0]
×
959

UNCOV
960
        return render(
×
961
            request,
962
            "view_user_data.html",
963
            {
964
                "user_data": user_data,
965
                "exercises": exercises,
966
                "existing_plan": existing_plan,
967
            },
968
        )
969
    except Exception as e:
1✔
970
        return render(
1✔
971
            request,
972
            "error.html",
973
            {
974
                "error_message": str(e),
975
            },
976
        )
977

978

979
def store_session_data(request, user_data):
1✔
980
    # Store data in the session
981
    request.session["user_data"] = user_data
×
982
    request.session.modified = True  # Ensure the session is marked as modified
×
983
    return
×
984

985

986
def serialize_data(data):
1✔
987
    # Handle dictionaries
988
    if isinstance(data, dict):
×
989
        return {key: serialize_data(value) for key, value in data.items()}
×
990
    # Handle lists and tuples
991
    elif isinstance(data, list) or isinstance(data, tuple):
×
992
        return [serialize_data(item) for item in data]
×
993
    # Handle dates: Convert datetime.date to ISO format string
994
    elif isinstance(data, dt.date):
×
995
        return data.isoformat()
×
996
    # Handle other types that can be directly serialized to JSON
997
    elif isinstance(data, (str, int, float, bool)):
×
998
        return data
×
999
    # If we can't serialize the type, return it as a string
1000
    return str(data)
×
1001

1002

1003
async def async_view_user_data(request, user_id):
1✔
UNCOV
1004
    try:
×
1005
        # Fetch the user data asynchronously
1006
        user = get_user(user_id)
×
1007
        user_email = user.get("email")
×
1008
        user_data = await fetch_user_data(user_email)
×
1009
        # Serialize user data
1010
        serialized_data = serialize_data(user_data)
×
1011
        # Store the data in the session
UNCOV
1012
        store_session_data(request, serialized_data)
×
1013
        # Send serialized user_data in JSON response
1014
        return JsonResponse({"success": True, "user_data": serialized_data})
×
1015
    except Exception as e:
×
1016
        return JsonResponse({"error": str(e)}, status=500)
×
1017

1018

1019
def create_custom_plan(request, user_id):
1✔
1020
    if request.method == "POST":
1✔
1021
        try:
1✔
1022
            data = json.loads(request.body)
1✔
NEW
1023
            exercise_ids = data.get("exercise_ids", [])
×
1024

NEW
1025
            if len(exercise_ids) != 3:
×
NEW
1026
                return JsonResponse(
×
1027
                    {"success": False, "error": "You must select exactly 3 exercises."},
1028
                    status=400,
1029
                )
1030

1031
            # Store the custom plan using the function from dynamodb.py
NEW
1032
            result = store_custom_plan(
×
1033
                user_id, request.session.get("user_id"), exercise_ids
1034
            )
1035

NEW
1036
            if result["success"]:
×
NEW
1037
                return JsonResponse({"success": True, "message": result["message"]})
×
1038
            else:
NEW
1039
                return JsonResponse(
×
1040
                    {"success": False, "error": result["message"]}, status=500
1041
                )
1042

1043
        except Exception as e:
1✔
1044
            return JsonResponse({"success": False, "error": str(e)}, status=500)
1✔
1045

NEW
1046
    return JsonResponse(
×
1047
        {"success": False, "error": "Invalid request method."}, status=405
1048
    )
1049

1050

1051
def view_custom_plan(request, trainer_id):
1✔
1052
    user_id = request.session.get("user_id")
1✔
1053

1054
    try:
1✔
1055
        response = custom_plans_table.scan(
1✔
1056
            FilterExpression="user_id = :user_id AND trainer_id = :trainer_id",
1057
            ExpressionAttributeValues={
1058
                ":user_id": user_id,
1059
                ":trainer_id": trainer_id,
1060
            },
1061
        )
1062

1063
        custom_plan = None
1✔
1064
        if response["Items"]:
1✔
NEW
1065
            custom_plan = response["Items"][0]
×
1066
        if not custom_plan:
1✔
1067
            return render(
1✔
1068
                request,
1069
                "view_custom_plan.html",
1070
                {
1071
                    "message": "No custom plan found for this trainer ",
1072
                },
1073
            )
1074

NEW
1075
        exercise_ids = custom_plan.get("exercise_ids", [])
×
NEW
1076
        exercises = Exercise.objects.all()
×
1077

NEW
1078
        filtered_exercises = exercises.filter(
×
1079
            id__in=[int(exercise_id) for exercise_id in exercise_ids]
1080
        )
NEW
1081
        filtered_exercises_image_urls = []
×
NEW
1082
        for exercise in filtered_exercises:
×
NEW
1083
            name = re.sub(r"[^a-zA-Z0-9-(),']", "_", exercise.name)
×
NEW
1084
            url = {
×
1085
                "url_0": f"https://fiton-static-files.s3.us-west-2.amazonaws.com/exercise_images/{name}_0.jpg",
1086
                "url_1": f"https://fiton-static-files.s3.us-west-2.amazonaws.com/exercise_images/{name}_1.jpg",
1087
            }
NEW
1088
            filtered_exercises_image_urls.append(url)
×
1089

NEW
1090
    except Exception as e:
×
NEW
1091
        return render(
×
1092
            request,
1093
            "view_custom_plan.html",
1094
            {
1095
                "error": str(e),
1096
            },
1097
        )
1098

NEW
1099
    return render(
×
1100
        request,
1101
        "view_custom_plan.html",
1102
        {
1103
            "custom_plan": custom_plan,
1104
            "custom_plan_exercises": zip(
1105
                filtered_exercises, filtered_exercises_image_urls
1106
            ),
1107
        },
1108
    )
1109

1110

1111
def request_custom_plan(request):
1✔
NEW
1112
    if request.method == "POST":
×
NEW
1113
        data = json.loads(request.body)
×
NEW
1114
        trainer_id = data.get("trainer_id")
×
NEW
1115
        trainer = get_user(trainer_id)
×
1116

NEW
1117
        subject = "User Requested Custom Workout Plan"
×
NEW
1118
        message = "User Requested Custom Workout Plan"
×
NEW
1119
        senderEmail = "fiton.notifications@gmail.com"
×
NEW
1120
        userEmail = trainer.get("email")
×
NEW
1121
        email_message = EmailMessage(
×
1122
            subject,
1123
            message,
1124
            senderEmail,
1125
            [userEmail],
1126
        )
NEW
1127
        email_message.content_subtype = "text"
×
NEW
1128
        email_message.send()
×
NEW
1129
        return JsonResponse({"status": "success"})
×
NEW
1130
    return JsonResponse({"status": "error", "message": "Invalid request method."})
×
1131

1132

1133
# -------------------------------
1134
# Forums Functions
1135
# -------------------------------
1136

1137

1138
# View to display a single thread with its posts
1139
def thread_detail_view(request, thread_id):
1✔
1140
    # Fetch thread details from DynamoDB
1141
    thread = threads_table.get_item(Key={"ThreadID": thread_id}).get("Item")
×
1142
    posts = fetch_posts_for_thread(thread_id)  # Fetch replies related to the thread
×
1143
    if not thread:
×
1144
        return JsonResponse(
×
1145
            {"status": "error", "message": "Thread not found"}, status=404
1146
        )
1147

1148
    user_id = request.session.get("user_id")
×
1149

1150
    user = get_user(user_id)
×
1151

1152
    is_banned = user.get("is_banned")
×
1153
    if is_banned:
×
1154
        return render(request, "forums.html", {"is_banned": is_banned})
×
1155

1156
    user_id = request.session.get("username")  # Assuming user is logged in
×
1157

1158
    if request.method == "POST":
×
1159
        if request.headers.get("x-requested-with") == "XMLHttpRequest":
×
1160
            # Parse the AJAX request data
1161
            data = json.loads(request.body.decode("utf-8"))
×
1162
            action = data.get("action")
×
1163
            post_id = data.get("post_id")
×
1164
            # print("Action received:", action)  # Print the action value
1165

1166
            if action == "like_post":
×
1167
                # Handle like/unlike for the main thread post
1168
                liked_by = thread.get("LikedBy", [])
×
1169
                if user_id in liked_by:
×
1170
                    # Unlike logic
1171
                    likes = max(0, thread.get("Likes", 0) - 1)
×
1172
                    liked_by.remove(user_id)
×
1173
                else:
1174
                    # Like logic
1175
                    likes = thread.get("Likes", 0) + 1
×
1176
                    liked_by.append(user_id)
×
1177
                threads_table.update_item(
×
1178
                    Key={"ThreadID": thread_id},
1179
                    UpdateExpression="SET Likes=:l, LikedBy=:lb",
1180
                    ExpressionAttributeValues={":l": likes, ":lb": liked_by},
1181
                )
1182
                return JsonResponse(
×
1183
                    {"status": "success", "likes": likes, "liked": user_id in liked_by}
1184
                )
1185

1186
            elif action == "like_comment":
×
1187
                # Handle like/unlike for a comment
1188
                post = posts_table.get_item(
×
1189
                    Key={"PostID": post_id, "ThreadID": thread_id}
1190
                ).get("Item")
1191
                if not post:
×
1192
                    return JsonResponse(
×
1193
                        {"status": "error", "message": "Comment not found"}, status=404
1194
                    )
1195

1196
                liked_by = post.get("LikedBy", [])
×
1197
                if user_id in liked_by:
×
1198
                    # Unlike logic
1199
                    likes = max(0, post.get("Likes", 0) - 1)
×
1200
                    liked_by.remove(user_id)
×
1201
                else:
1202
                    # Like logic
1203
                    likes = post.get("Likes", 0) + 1
×
1204
                    liked_by.append(user_id)
×
1205
                posts_table.update_item(
×
1206
                    Key={"PostID": post_id, "ThreadID": thread_id},
1207
                    UpdateExpression="SET Likes=:l, LikedBy=:lb",
1208
                    ExpressionAttributeValues={":l": likes, ":lb": liked_by},
1209
                )
1210
                return JsonResponse(
×
1211
                    {"status": "success", "likes": likes, "liked": user_id in liked_by}
1212
                )
1213

1214
            elif action == "report_comment":
×
1215
                # Handle report for a comment (you can define reporting logic here)
1216
                # For simplicity, let's say reporting just returns a success message
1217
                return JsonResponse(
×
1218
                    {"status": "success", "message": "Comment reported successfully!"}
1219
                )
1220

1221
            elif action == "add_reply":
×
1222
                # print("add reply")
1223

1224
                # Handle adding a reply to a comment
1225
                reply_content = data.get("content", "").strip()
×
1226
                if not reply_content:
×
1227
                    return JsonResponse(
×
1228
                        {"status": "error", "message": "Reply content cannot be empty!"}
1229
                    )
1230

1231
                # create reply
1232
                reply_id = create_reply(
×
1233
                    post_id=post_id,
1234
                    thread_id=thread_id,
1235
                    user_id=user_id,
1236
                    content=reply_content,
1237
                )
1238
                # Return success and the reply content with the username
1239
                return JsonResponse(
×
1240
                    {
1241
                        "status": "success",
1242
                        "content": reply_content,
1243
                        "username": user_id,
1244
                        "reply_id": reply_id,
1245
                    }
1246
                )
1247

1248
            # Get the list of users who have liked the thread
1249
            liked_by = thread.get("LikedBy", [])
×
1250

1251
            if user_id in liked_by:
×
1252
                # If user has already liked the post, "unlike" (remove the like)
1253
                likes = max(
×
1254
                    0, thread.get("Likes", 0) - 1
1255
                )  # Ensure likes never go below 0
1256
                liked_by.remove(user_id)  # Remove the user from the LikedBy list
×
1257
            else:
1258
                # If user hasn't liked the post, add a like
1259
                likes = thread.get("Likes", 0) + 1
×
1260
                liked_by.append(user_id)  # Add the current user to the LikedBy list
×
1261

1262
            # Update the thread with the new like count and LikedBy list
1263
            threads_table.update_item(
×
1264
                Key={"ThreadID": thread_id},
1265
                UpdateExpression="set Likes=:l, LikedBy=:lb",
1266
                ExpressionAttributeValues={":l": likes, ":lb": liked_by},
1267
            )
1268
            return JsonResponse(
×
1269
                {"status": "success", "likes": likes, "liked": user_id in liked_by}
1270
            )
1271

1272
        # Handle non-AJAX post submission for creating a new comment
1273
        elif "content" in request.POST:
×
1274
            # Add a new post to the thread
1275
            new_content = request.POST.get("content").strip()
×
1276
            if new_content:
×
1277
                post_comment(thread_id=thread_id, user_id=user_id, content=new_content)
×
1278

1279
            # Redirect after posting to avoid resubmission on refresh
1280
            return redirect("thread_detail", thread_id=thread_id)
×
1281

1282
        else:
1283
            # Handle reply submission (non-AJAX form submission)
1284
            content = request.POST.get("content")
×
1285

1286
            if content and user_id:
×
1287
                post_comment(thread_id=thread_id, user_id=user_id, content=content)
×
1288
                return redirect("thread_detail", thread_id=thread_id)
×
1289

1290
    return render(
×
1291
        request,
1292
        "thread_detail.html",
1293
        {
1294
            "user": user,
1295
            "thread": thread,
1296
            "posts": posts,
1297
            "liked": user_id in thread.get("LikedBy", []),
1298
        },
1299
    )
1300

1301

1302
def new_thread_view(request):
1✔
1303
    user_id = request.session.get("user_id")
×
1304

1305
    # Fetch user details from DynamoDB
1306
    user = get_user(user_id)
×
1307

1308
    if not user:
×
1309
        messages.error(request, "User not found.")
×
1310
        return redirect("login")
×
1311

1312
    if request.method == "POST":
×
1313
        title = request.POST.get("title")
×
1314
        content = request.POST.get("content")
×
1315
        section = request.POST.get("section")
×
1316
        user_id = request.session.get("username")  # Assuming the user is logged in
×
1317

1318
        if title and content and user_id:
×
1319
            # Call your DynamoDB function to create a new thread
1320
            create_thread(
×
1321
                title=title, user_id=user_id, content=content, section=section
1322
            )
1323

1324
            # Redirect to the forums page after successfully creating the thread
1325
            return redirect("forum")
×
1326
        else:
1327
            # If something's missing, return the form with an error message
1328
            return render(
×
1329
                request, "new_thread.html", {"error": "All fields are required."}
1330
            )
1331

1332
    # If the request method is GET, simply show the form
1333
    return render(request, "new_thread.html", {"user": user})
×
1334

1335

1336
def delete_post_view(request):
1✔
1337
    if (
×
1338
        request.method == "POST"
1339
        and request.headers.get("x-requested-with") == "XMLHttpRequest"
1340
    ):
1341
        try:
×
1342
            data = json.loads(request.body.decode("utf-8"))
×
1343
            post_id = data.get("post_id")
×
1344
            thread_id = data.get("thread_id")  # Make sure you're getting thread_id too
×
1345

1346
            if not post_id or not thread_id:
×
1347
                return JsonResponse(
×
1348
                    {"status": "error", "message": "Post or Thread ID missing"},
1349
                    status=400,
1350
                )
1351

1352
            # Call the delete_post function to delete from DynamoDB
1353
            if delete_post(post_id, thread_id):
×
1354
                return JsonResponse({"status": "success"})
×
1355
            else:
1356
                return JsonResponse(
×
1357
                    {"status": "error", "message": "Failed to delete post"}, status=500
1358
                )
1359

1360
        except Exception as e:
×
1361
            return JsonResponse({"status": "error", "message": str(e)}, status=500)
×
1362
    return JsonResponse({"status": "error", "message": "Invalid request"}, status=400)
×
1363

1364

1365
def forum_view(request):
1✔
1366
    user_id = request.session.get("username")
1✔
1367
    user = get_user_by_username(user_id)
1✔
1368
    if not user:
1✔
1369
        messages.error(request, "User not found.")
1✔
1370
        return redirect("login")
1✔
1371
    is_banned = user.get("is_banned")
×
1372
    if is_banned:
×
1373
        return render(request, "forums.html", {"is_banned": is_banned})
×
1374

1375
    # Get filter inputs from the request's GET parameters
1376
    username = request.GET.get("username", "")  # Username filter
×
1377
    thread_type = request.GET.get("type", "all")  # Thread or Reply filter
×
1378
    start_date = request.GET.get("start_date", "")  # Start date filter
×
1379
    end_date = request.GET.get("end_date", "")  # End date filter
×
1380
    search_text = request.GET.get("search", "")  # Search text filter
×
1381

1382
    # Fetch section stats
1383
    sections = ["General", "Workout Suggestions", "Diet Plans", "Other"]
×
1384
    section_stats = {section: get_section_stats(section) for section in sections}
×
1385

1386
    # Fetch filtered threads based on the inputs
1387
    threads = fetch_filtered_threads(
×
1388
        username=username,
1389
        thread_type=thread_type,
1390
        start_date=start_date,
1391
        end_date=end_date,
1392
        search_text=search_text,
1393
    )
1394

1395
    # Fetch all users for the dropdown filter
1396
    users = (
×
1397
        fetch_all_users()
1398
    )  # Assuming you have a function to fetch users who posted threads/replies
1399

1400
    return render(
×
1401
        request,
1402
        "forums.html",
1403
        {
1404
            "threads": threads,
1405
            "users": users,
1406
            "user": user,
1407
            "section_stats": section_stats,
1408
        },
1409
    )
1410

1411

1412
def section_view(request, section_name):
1✔
1413
    user_id = request.session.get("username")
×
1414
    user = get_user_by_username(user_id)
×
1415

1416
    if not user:
×
1417
        messages.error(request, "User not found.")
×
1418
        return redirect("login")
×
1419

1420
    is_banned = user.get("is_banned")
×
1421
    if is_banned:
×
1422
        return render(request, "forums.html", {"is_banned": is_banned})
×
1423

1424
    # Fetch threads only for the specified section
1425
    threads = fetch_filtered_threads(section=section_name)
×
1426
    threads = sorted(threads, key=lambda t: t.get("CreatedAt"), reverse=True)
×
1427

1428
    return render(
×
1429
        request,
1430
        "section_threads.html",
1431
        {"threads": threads, "section_name": section_name, "user": user},
1432
    )
1433

1434

1435
######################################
1436
#       Fetching data using API     #
1437
######################################
1438

1439

1440
async def format_bod_fitness_data(total_data):
1✔
1441
    list1 = total_data["glucose"]["glucose_data_json"]
1✔
1442
    list2 = total_data["pressure"]["pressure_data_json"]
1✔
1443

1444
    def parse_date(date_str):
1✔
1445
        return dt.datetime.strptime(date_str, "%b %d, %I %p")
1✔
1446

1447
    # Extract all unique start dates from both lists
1448
    all_dates = set()
1✔
1449
    for item in list1 + list2:
1✔
1450
        all_dates.add(item["start"])
1✔
1451

1452
    # Update list1
1453
    for date in all_dates:
1✔
1454
        found = False
1✔
1455
        for item in list1:
1✔
1456
            if item["start"] == date:
1✔
1457
                found = True
1✔
1458
                break
1✔
1459
        if not found:
1✔
1460
            list1.append({"start": date, "end": date, "count": 0})
1✔
1461

1462
    # Update list2
1463
    for date in all_dates:
1✔
1464
        found = False
1✔
1465
        for item in list2:
1✔
1466
            if item["start"] == date:
1✔
1467
                found = True
1✔
1468
                break
1✔
1469
        if not found:
1✔
1470
            list2.append({"start": date, "end": date, "count": 0})
1✔
1471

1472
    # Sort lists by start date
1473
    list1.sort(key=lambda x: parse_date(x["start"]))
1✔
1474
    list2.sort(key=lambda x: parse_date(x["start"]))
1✔
1475

1476
    total_data["glucose"]["glucose_data_json"] = list1
1✔
1477
    total_data["pressure"]["pressure_data_json"] = list2
1✔
1478

1479
    return total_data
1✔
1480

1481

1482
def process_dynamo_data(items, frequency):
1✔
1483
    # Dictionary to hold the data grouped by date
1484
    # print("Items in dictionary", items)
1485
    date_groups = defaultdict(list)
1✔
1486

1487
    # Process each item
1488
    for item in items:
1✔
1489
        time = dt.datetime.strptime(item["time"], "%Y-%m-%dT%H:%M")
1✔
1490
        start, end = get_group_key(time, frequency)
1✔
1491
        start_key = start.strftime("%b %d, %I %p")
1✔
1492
        end_key = end.strftime("%b %d, %I %p")
1✔
1493
        value = float(item["value"])
1✔
1494
        date_groups[(start_key, end_key)].append(value)
1✔
1495

1496
    # Prepare the final data structure
1497
    result = []
1✔
1498

1499
    for (start_key, end_key), values in date_groups.items():
1✔
1500
        avg_count = sum(values) / len(values) if values else 0
1✔
1501
        result.append(
1✔
1502
            {
1503
                "start": start_key,
1504
                "end": end_key,
1505
                "count": avg_count,
1506
            }
1507
        )
1508

1509
    return {"Items": result}
1✔
1510

1511

1512
# function to convert miliseconds to Day
1513
def parse_millis(millis):
1✔
1514
    return dt.datetime.fromtimestamp(int(millis) / 1000).strftime("%b %d, %I %p")
1✔
1515

1516

1517
def get_group_key(time, frequency):
1✔
1518
    """Adjusts start and end times based on frequency."""
1519
    if frequency == "hourly":
1✔
1520
        start = time.replace(minute=0, second=0, microsecond=0)
1✔
1521
        end = start + dt.timedelta(hours=1)
1✔
1522
    elif frequency == "daily":
1✔
1523
        start = time.replace(hour=0, minute=0, second=0, microsecond=0)
1✔
1524
        end = start + dt.timedelta(days=1)
1✔
1525
    elif frequency == "weekly":
1✔
1526
        start = time - dt.timedelta(days=time.weekday())
1✔
1527
        start = start.replace(hour=0, minute=0, second=0, microsecond=0)
1✔
1528
        end = start + dt.timedelta(days=7)
1✔
1529
    elif frequency == "monthly":
1✔
1530
        start = time.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
1✔
1531
        end = start + dt.timedelta(
1✔
1532
            days=(time.replace(month=time.month % 12 + 1, day=1) - time).days
1533
        )
1534
    else:
1535
        start = time  # Fallback to the exact time if frequency is unrecognized
1✔
1536
        end = time
1✔
1537

1538
    return start, end
1✔
1539

1540

1541
def merge_data(existing_data, new_data, frequency):
1✔
1542
    """
1543
    Merges new data into existing data based on overlapping time ranges defined by frequency.
1544

1545
    Parameters:
1546
    existing_data (list): The existing list of data points for a metric.
1547
    new_data (list): The new data points to be merged.
1548
    frequency (str): The frequency of data collection ('hourly', 'daily', 'weekly', 'monthly').
1549

1550
    Returns:
1551
    list: Updated list of data points after merging.
1552
    """
1553

1554
    # Helper to parse datetime from string
1555
    def parse_time(time_str):
1✔
1556
        return dt.datetime.strptime(time_str, "%b %d, %I %p")
1✔
1557

1558
    # Create index of existing data by start time for quick access
1559
    data_index = {}
1✔
1560
    for item in existing_data:
1✔
1561
        start, end = get_group_key(parse_time(item["start"]), frequency)
1✔
1562
        data_index[start] = item
1✔
1563
        item["end_range"] = end  # Temporarily store the range end to use in comparisons
1✔
1564

1565
    # Process each new data point
1566
    for new_item in new_data:
1✔
1567
        new_start, new_end = get_group_key(parse_time(new_item["start"]), frequency)
1✔
1568
        if new_start in data_index:
1✔
1569
            # There's an overlap, so update the existing entry
1570
            existing_item = data_index[new_start]
1✔
1571
            # Averaging the counts, updating mins and maxs
1572
            existing_item["count"] = (existing_item["count"] + new_item["count"]) / 2
1✔
1573
            if min in existing_item or min in new_item:
1✔
1574
                existing_item["min"] = (
×
1575
                    new_item["min"]
1576
                    if "min" not in existing_item
1577
                    else min(existing_item["min"], new_item["min"])
1578
                )
1579
                existing_item["max"] = (
×
1580
                    new_item["max"]
1581
                    if "max" not in existing_item
1582
                    else max(existing_item["max"], new_item["max"])
1583
                )
1584
        else:
1585
            # No overlap, append this new item
1586
            new_item["end"] = new_end.strftime(
1✔
1587
                "%b %d, %I %p"
1588
            )  # Format end time for consistency
1589
            existing_data.append(new_item)
1✔
1590

1591
    # Remove temporary 'end_range' from existing items
1592
    for item in existing_data:
1✔
1593
        item.pop("end_range", None)
1✔
1594

1595
    existing_data.sort(key=lambda x: parse_time(x["start"]))
1✔
1596

1597
    combined_data = []
1✔
1598
    for obj in existing_data:
1✔
1599
        if not combined_data or parse_time(combined_data[-1]["start"]) != parse_time(
1✔
1600
            obj["start"]
1601
        ):
1602
            combined_data.append(obj)
1✔
1603
        else:
1604
            combined_data[-1]["count"] += obj["count"]
×
1605

1606
    return combined_data
1✔
1607

1608

1609
def steps_barplot(data):
1✔
1610
    # Your steps data
1611
    # print("inside steps function\n")
1612
    steps_data = []
1✔
1613
    for record in data["bucket"]:
1✔
1614
        if len(record["dataset"][0]["point"]) == 0:
1✔
1615
            continue
1✔
1616
        else:
1617
            d = {}
1✔
1618
            d["start"] = parse_millis(record["startTimeMillis"])
1✔
1619
            d["end"] = parse_millis(record["endTimeMillis"])
1✔
1620
            d["count"] = record["dataset"][0]["point"][0]["value"][0]["intVal"]
1✔
1621
            steps_data.append(d)
1✔
1622

1623
    # Pass the plot path to the template
1624
    # print("Steps Data:", steps_data)
1625
    context = {"steps_data_json": steps_data}
1✔
1626
    return context
1✔
1627

1628

1629
def resting_heartrate_plot(data):
1✔
1630
    # print("inside resting heart function\n")
1631
    resting_heart_data = []
1✔
1632
    for record in data["bucket"]:
1✔
1633
        if len(record["dataset"][0]["point"]) == 0:
1✔
1634
            continue
1✔
1635
        else:
1636
            d = {}
1✔
1637
            d["start"] = parse_millis(record["startTimeMillis"])
1✔
1638
            d["end"] = parse_millis(record["endTimeMillis"])
1✔
1639
            d["count"] = int(record["dataset"][0]["point"][0]["value"][0]["fpVal"])
1✔
1640
            resting_heart_data.append(d)
1✔
1641

1642
    # Pass the plot path to the template
1643
    context = {"resting_heart_data_json": resting_heart_data}
1✔
1644
    return context
1✔
1645

1646

1647
def sleep_plot(data):
1✔
1648
    # print("inside sleep function\n")
1649
    sleep_data = []
×
1650
    for record in data["session"]:
×
1651
        d = {}
×
1652
        d["start"] = parse_millis(record["startTimeMillis"])
×
1653
        d["end"] = parse_millis(record["endTimeMillis"])
×
1654
        d["count"] = (
×
1655
            (int(record["endTimeMillis"]) - int(record["startTimeMillis"]))
1656
            / 1000
1657
            / 60
1658
            / 60
1659
        )
1660
        sleep_data.append(d)
×
1661

1662
    # Pass the plot path to the template
1663
    context = {"sleep_data_json": sleep_data}
×
1664
    print("----------")
×
1665
    print(sleep_data)
×
1666
    return context
×
1667

1668

1669
def heartrate_plot(data):
1✔
1670
    # print("inside heart function\n")
1671
    heart_data = []
1✔
1672
    for record in data["bucket"]:
1✔
1673
        if len(record["dataset"][0]["point"]) == 0:
1✔
1674
            continue
1✔
1675
        else:
1676
            d = {}
1✔
1677
            d["start"] = parse_millis(record["startTimeMillis"])
1✔
1678
            d["end"] = parse_millis(record["endTimeMillis"])
1✔
1679
            d["count"] = float(record["dataset"][0]["point"][0]["value"][0]["fpVal"])
1✔
1680
            d["min"] = int(record["dataset"][0]["point"][0]["value"][1]["fpVal"])
1✔
1681
            d["max"] = int(record["dataset"][0]["point"][0]["value"][2]["fpVal"])
1✔
1682
            heart_data.append(d)
1✔
1683

1684
    # Pass the plot path to the template
1685
    context = {"heart_data_json": heart_data}
1✔
1686
    return context
1✔
1687

1688

1689
def activity_plot(data):
1✔
1690
    # print("inside activity function\n")
1691
    activity_data = {}
1✔
1692
    for record in data["session"]:
1✔
1693
        activity_name = df.loc[df["Integer"] == record["activityType"]][
1✔
1694
            "Activity Type"
1695
        ].values
1696
        if len(activity_name) == 0:
1✔
1697
            continue
1✔
1698
        act = activity_name[0]
1✔
1699
        duration = (
1✔
1700
            (int(record["endTimeMillis"]) - int(record["startTimeMillis"])) / 1000 / 60
1701
        )
1702
        if act in activity_data:
1✔
1703
            activity_data[act] += int(duration)
1✔
1704
        else:
1705
            activity_data[act] = int(duration)
1✔
1706

1707
    activity_data = sorted(activity_data.items(), key=lambda x: x[1], reverse=True)
1✔
1708
    activity_data = activity_data[:10]
1✔
1709

1710
    # Pass the plot path to the template
1711
    context = {"activity_data_json": activity_data}
1✔
1712
    return context
1✔
1713

1714

1715
def oxygen_plot(data):
1✔
1716
    # print("inside oxygen saturation function\n")
1717
    oxygen_data = []
1✔
1718
    for record in data["bucket"]:
1✔
1719
        if len(record["dataset"][0]["point"]) == 0:
1✔
1720
            continue
1✔
1721
        else:
1722
            d = {}
1✔
1723
            d["start"] = parse_millis(record["startTimeMillis"])
1✔
1724
            d["end"] = parse_millis(record["endTimeMillis"])
1✔
1725
            d["count"] = int(record["dataset"][0]["point"][0]["value"][0]["fpVal"])
1✔
1726
            oxygen_data.append(d)
1✔
1727

1728
    # Pass the plot path to the template
1729
    context = {"oxygen_data_json": oxygen_data}
1✔
1730
    return context
1✔
1731

1732

1733
def glucose_plot(data):
1✔
1734
    # print("inside blood glucose function\n")
1735
    oxygen_data = []
1✔
1736
    for record in data["bucket"]:
1✔
1737
        if len(record["dataset"][0]["point"]) == 0:
1✔
1738
            continue
1✔
1739
        else:
1740
            d = {}
1✔
1741
            d["start"] = parse_millis(record["startTimeMillis"])
1✔
1742
            d["end"] = parse_millis(record["endTimeMillis"])
1✔
1743
            d["count"] = int(record["dataset"][0]["point"][0]["value"][0]["fpVal"])
1✔
1744
            oxygen_data.append(d)
1✔
1745

1746
    # Pass the plot path to the template
1747
    context = {"glucose_data_json": oxygen_data}
1✔
1748
    return context
1✔
1749

1750

1751
def pressure_plot(data):
1✔
1752
    # print("inside blood pressure function\n")
1753
    oxygen_data = []
1✔
1754
    for record in data["bucket"]:
1✔
1755
        if len(record["dataset"][0]["point"]) == 0:
1✔
1756
            continue
1✔
1757
        else:
1758
            d = {}
1✔
1759
            d["start"] = parse_millis(record["startTimeMillis"])
1✔
1760
            d["end"] = parse_millis(record["endTimeMillis"])
1✔
1761
            d["count"] = int(record["dataset"][0]["point"][0]["value"][0]["fpVal"])
1✔
1762
            oxygen_data.append(d)
1✔
1763

1764
    # Pass the plot path to the template
1765
    context = {"pressure_data_json": oxygen_data}
1✔
1766
    return context
1✔
1767

1768

1769
async def fetch_metric_data(service, metric, total_data, duration, frequency, email):
1✔
1770
    end_time = dt.datetime.now() - dt.timedelta(minutes=1)
1✔
1771

1772
    if duration == "day":
1✔
1773
        start_time = end_time - dt.timedelta(hours=23, minutes=59)
1✔
1774
    elif duration == "week":
×
1775
        start_time = end_time - dt.timedelta(days=6, hours=23, minutes=59)
×
1776
    elif duration == "month":
×
1777
        start_time = end_time - dt.timedelta(days=29, hours=23, minutes=59)
×
1778
    elif duration == "quarter":
×
1779
        start_time = end_time - dt.timedelta(days=89, hours=23, minutes=59)
×
1780

1781
    if frequency == "hourly":
1✔
1782
        bucket = 3600000
1✔
1783
    elif frequency == "daily":
×
1784
        bucket = 86400000
×
1785
    elif frequency == "weekly":
×
1786
        bucket = 604800000
×
1787
    elif frequency == "monthly":
×
1788
        bucket = 2592000000
×
1789

1790
    start_date = start_time.astimezone(pytz.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
1✔
1791
    end_date = end_time.astimezone(pytz.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
1✔
1792

1793
    if metric == "sleep":
1✔
1794
        data = (
×
1795
            service.users()
1796
            .sessions()
1797
            .list(
1798
                userId="me",
1799
                activityType=72,
1800
                startTime=f"{start_date}",
1801
                endTime=f"{end_date}",
1802
            )
1803
            .execute()
1804
        )
1805
    elif metric == "activity":
1✔
1806
        data = (
×
1807
            service.users()
1808
            .sessions()
1809
            .list(userId="me", startTime=f"{start_date}", endTime=f"{end_date}")
1810
            .execute()
1811
        )
1812
    else:
1813
        data = (
1✔
1814
            service.users()
1815
            .dataset()
1816
            .aggregate(
1817
                userId="me",
1818
                body={
1819
                    "aggregateBy": [{"dataTypeName": dataTypes[metric]}],
1820
                    "bucketByTime": {"durationMillis": bucket},
1821
                    "startTimeMillis": int(start_time.timestamp()) * 1000,
1822
                    "endTimeMillis": int(end_time.timestamp()) * 1000,
1823
                },
1824
            )
1825
            .execute()
1826
        )
1827

1828
    if metric == "heart_rate":
1✔
1829
        context = heartrate_plot(data)
×
1830
        total_data["heartRate"] = context
×
1831
    elif metric == "steps":
1✔
1832
        context = steps_barplot(data)
×
1833
        total_data["steps"] = context
×
1834
    elif metric == "resting_heart_rate":
1✔
1835
        context = resting_heartrate_plot(data)
×
1836
        total_data["restingHeartRate"] = context
×
1837
    elif metric == "sleep":
1✔
1838
        context = sleep_plot(data)
×
1839
        total_data["sleep"] = context
×
1840
    elif metric == "activity":
1✔
1841
        context = activity_plot(data)
×
1842
        total_data["activity"] = context
×
1843
    elif metric == "oxygen":
1✔
1844
        context = oxygen_plot(data)
1✔
1845
        total_data["oxygen"] = context
1✔
1846
    elif metric == "glucose":
×
1847
        context = glucose_plot(data)
×
1848
        total_data["glucose"] = context
×
1849
    elif metric == "pressure":
×
1850
        context = pressure_plot(data)
×
1851
        total_data["pressure"] = context
×
1852
    response = get_fitness_data(metric, email, start_time, end_time)
1✔
1853
    # print(
1854
    #     f"Metric : {metric}\nResponse: {response}\n",
1855
    # )
1856
    # print("printing processed data from DynamoDB--------------------------------")
1857

1858
    processed_data = process_dynamo_data(response["Items"], frequency)
1✔
1859
    # print("processed data", processed_data)
1860

1861
    # Assuming 'processed_data' is structured similarly for each metric
1862
    # and 'frequency' is defined appropriately for the context in which this is run
1863

1864
    if metric == "heart_rate":
1✔
1865
        # print("heart rate")
1866
        total_data["heartRate"]["heart_data_json"] = merge_data(
×
1867
            total_data["heartRate"]["heart_data_json"],
1868
            processed_data["Items"],
1869
            frequency,
1870
        )
1871
    elif metric == "steps":
1✔
1872
        # print("steps")
1873
        total_data["steps"]["steps_data_json"] = merge_data(
×
1874
            total_data["steps"]["steps_data_json"], processed_data["Items"], frequency
1875
        )
1876
    elif metric == "resting_heart_rate":
1✔
1877
        # print("resting heart rate")
1878
        total_data["restingHeartRate"]["resting_heart_data_json"] = merge_data(
×
1879
            total_data["restingHeartRate"]["resting_heart_data_json"],
1880
            processed_data["Items"],
1881
            frequency,
1882
        )
1883
    elif metric == "sleep":
1✔
1884
        # print("sleep")
1885
        total_data["sleep"]["sleep_data_json"] = merge_data(
×
1886
            total_data["sleep"]["sleep_data_json"], processed_data["Items"], frequency
1887
        )
1888
    elif metric == "activity":
1✔
1889
        # print("activity")
1890
        print()
×
1891
        # final = merge_data(total_data['activity']['activity_data_json'], processed_data['Items'], frequency)
1892
        # print(final) #
1893

1894
    elif metric == "oxygen":
1✔
1895
        # print("oxygen")
1896
        total_data["oxygen"]["oxygen_data_json"] = merge_data(
1✔
1897
            total_data["oxygen"]["oxygen_data_json"], processed_data["Items"], frequency
1898
        )
1899
    else:
1900
        print("Unknown metric")
×
1901

1902

1903
async def get_sleep_scores(request, total_data):
1✔
1904
    sleep_body = ""
×
1905
    for sleep_data in total_data["sleep"]["sleep_data_json"]:
×
1906
        duration = sleep_data["count"]
×
1907
        user_id = request.session.get("user_id")
×
1908
        user = get_user(user_id)
×
1909
        age = 26
×
1910
        activity_level = 70
×
1911
        given_date = dt.datetime.strptime(sleep_data["start"], "%b %d, %I %p")
×
1912
        nearest_hr = min(
×
1913
            total_data["restingHeartRate"]["resting_heart_data_json"],
1914
            key=lambda x: abs(
1915
                dt.datetime.strptime(x["start"], "%b %d, %I %p") - given_date
1916
            ),
1917
        )
1918
        heart_rate = nearest_hr["count"]
×
1919

1920
        nearest_steps = min(
×
1921
            total_data["steps"]["steps_data_json"],
1922
            key=lambda x: abs(
1923
                dt.datetime.strptime(x["start"], "%b %d, %I %p") - given_date
1924
            ),
1925
        )
1926
        daily_steps = nearest_steps["count"]
×
1927

1928
        gender_female = user.get("gender") == "F"
×
1929
        gender_male = user.get("gender") == "M"
×
1930

1931
        sleep_body += f"""{age},{duration},{activity_level},{heart_rate},{daily_steps},
×
1932
        {gender_female},{gender_male},{True},{True},{False},{False},{False},{False}\n"""
1933

1934
    if sleep_body and sleep_body[-1] == "\n":
×
1935
        sleep_body = sleep_body[:-1]
×
1936

1937
    url = "https://9pweqg5b1i.execute-api.us-west-2.amazonaws.com/dev/inference"
×
1938

1939
    response = requests.post(url, json=sleep_body)
×
1940
    print("Response:", response)
×
1941
    sleep_score = response.text.split(":")[1][:-1].strip()
×
1942
    try:
×
1943
        sleep_score = ast.literal_eval(sleep_score)
×
1944
        print("Sleep Score:", sleep_score)
×
1945

1946
    except Exception as e:
×
1947
        print(e)
×
1948
    for i, sleep_data in enumerate(total_data["sleep"]["sleep_data_json"]):
×
1949
        sleep_data["count"] = sleep_score[i]
×
1950
    print("Total Data:", total_data)
×
1951
    return total_data
×
1952

1953

1954
@sync_to_async
1✔
1955
def get_credentials(request):
1✔
1956
    print("Request:", request)
×
1957
    if "credentials" in request.session:
×
1958
        credentials = Credentials(**request.session["credentials"])
×
1959
        return credentials, request.user.username
×
1960
    return None, None
×
1961

1962

1963
async def fetch_all_metric_data(request, duration, frequency):
1✔
1964
    total_data = {}
1✔
1965
    credentials, email = await get_credentials(request)
1✔
1966
    print(f"Credentails: {credentials}. Email: {email}")
1✔
1967

1968
    user_id = request.session.get("user_id")
1✔
1969
    user = get_user(user_id)
1✔
1970
    email = user.get("email")
1✔
1971
    if credentials:
1✔
1972
        # try:
1973
        service = build("fitness", "v1", credentials=credentials)
1✔
1974
        tasks = []
1✔
1975
        for metric in dataTypes.keys():
1✔
1976
            tasks.append(
1✔
1977
                fetch_metric_data(
1978
                    service, metric, total_data, duration, frequency, email
1979
                )
1980
            )
1981

1982
        await asyncio.gather(*tasks)
1✔
1983
        print("----- Total Data -----")
1✔
1984
        print(total_data)
1✔
1985
        total_data = await get_sleep_scores(request, total_data)
1✔
1986
        total_data = await format_bod_fitness_data(total_data)
1✔
1987

1988
        # except Exception as e:
1989
        #     print(e)
1990
        #     total_data = {}
1991

1992
    else:
1993
        print("Not Signed in Google")
×
1994
    print("total data: ", total_data)
1✔
1995
    return total_data
1✔
1996

1997

1998
async def get_metric_data(request):
1✔
1999
    credentials = await sync_to_async(lambda: request.session.get("credentials"))()
1✔
2000
    user_id = await sync_to_async(lambda: request.session.get("user_id"))()
1✔
2001
    user = get_user(user_id)
1✔
2002
    user_email = user.get("email")
1✔
2003
    # print("Credentials: \n", credentials)
2004
    # print("User Email: \n", user_email)
2005
    if credentials:
1✔
2006
        duration = "week"
1✔
2007
        frequency = "daily"
1✔
2008

2009
        if request.GET.get("data_drn"):
1✔
2010
            duration = request.GET.get("data_drn")
1✔
2011

2012
        if request.GET.get("data_freq"):
1✔
2013
            frequency = request.GET.get("data_freq")
1✔
2014

2015
        total_data = await fetch_all_metric_data(request, duration, frequency)
1✔
2016
        rds_response = await rds_main(user_email, total_data)
1✔
2017
        print("RDS Response: \n", rds_response)
1✔
2018

2019
        steps = get_step_user_goals(user_id)
1✔
2020
        weight = get_weight_user_goals(user_id)
1✔
2021
        sleep = get_sleep_user_goals(user_id)
1✔
2022
        activity = get_activity_user_goals(user_id)
1✔
2023
        custom = get_custom_user_goals(user_id)
1✔
2024

2025
        context = {
1✔
2026
            "data": total_data,
2027
            "step_goal": steps,
2028
            "weight_goal": weight,
2029
            "sleep_goal": sleep,
2030
            "activity_goal": activity,
2031
            "custom_goal": custom,
2032
        }
2033
        # print("Inside get metric:", context)
2034
        return await sync_to_async(render)(
1✔
2035
            request, "display_metrics_data.html", context
2036
        )
2037
    else:
2038
        await add_message(
1✔
2039
            request,
2040
            messages.ERROR,
2041
            "User not logged in. Please sign in to access your data.",
2042
        )
2043
        return await perform_redirect("profile")
1✔
2044

2045

2046
def health_data_view(request):
1✔
2047
    user_id = request.session.get("user_id")
×
2048
    user = get_user(user_id)
×
2049
    user_email = user.get("email")
×
2050
    dynamodb_res = dynamodb
×
2051
    table = dynamodb_res.Table("UserFitnessData")
×
2052

2053
    if request.method == "POST":
×
2054
        data = request.POST
×
2055
        print("Data:", data)
×
2056
        try:
×
2057
            table.put_item(
×
2058
                Item={
2059
                    "email": user_email,  # Use the default email
2060
                    "metric": data.get("metric"),
2061
                    "time": data.get("time"),
2062
                    "value": data.get("value"),
2063
                },
2064
                ConditionExpression="attribute_not_exists(email) AND attribute_not_exists(#t)",
2065
                ExpressionAttributeNames={"#t": "time"},
2066
            )
2067
        except dynamodb_res.meta.client.exceptions.ConditionalCheckFailedException:
×
2068
            print("Item already exists and was not replaced.")
×
2069
        return redirect("get_metric_data")
×
2070

2071
    # Fetch all the metrics data from DynamoDB
2072
    response = table.scan()
×
2073
    metrics_data = {}
×
2074
    for item in response["Items"]:
×
2075
        metric = item["metric"]
×
2076
        if metric not in metrics_data:
×
2077
            metrics_data[metric] = []
×
2078
        metrics_data[metric].append(item)
×
2079

2080
    for metric in metrics_data:
×
2081
        metrics_data[metric].sort(key=lambda x: x["time"], reverse=True)
×
2082

2083
    step_goal = get_step_user_goals(user_id)
×
2084
    weight_goal = get_weight_user_goals(user_id)
×
2085
    sleep_goal = get_sleep_user_goals(user_id)
×
2086
    custom_goal = get_custom_user_goals(user_id)
×
2087
    return render(
×
2088
        request,
2089
        "display_metric_data.html",
2090
        {
2091
            "metrics_data": metrics_data,
2092
            "step_goal": step_goal,
2093
            "weight_goal": weight_goal,
2094
            "sleep_goal": sleep_goal,
2095
            "custom_goal": custom_goal,
2096
        },
2097
    )
2098
    # return render(
2099
    #     request,
2100
    #     "forums.html",
2101
    #     {
2102
    #         "user": user,
2103
    #         "threads": threads,
2104
    #         "users": users,
2105
    #         "is_banned": is_banned,
2106
    #     },
2107
    # )
2108

2109

2110
def add_reply(request):
1✔
2111
    if (
×
2112
        request.method == "POST"
2113
        and request.headers.get("x-requested-with") == "XMLHttpRequest"
2114
    ):
2115
        try:
×
2116
            data = json.loads(request.body.decode("utf-8"))
×
2117

2118
            post_id = data.get("post_id")
×
2119
            content = data.get("content")
×
2120
            thread_id = data.get("thread_id")
×
2121

2122
            if not post_id or not content:
×
2123
                return JsonResponse(
×
2124
                    {"status": "error", "message": "Post ID and content are required."},
2125
                    status=400,
2126
                )
2127

2128
            user_id = request.session.get("username")
×
2129
            if not user_id:
×
2130
                return JsonResponse(
×
2131
                    {"status": "error", "message": "User not authenticated"}, status=403
2132
                )
2133

2134
            # tz = timezone("EST")
2135
            reply_data = {
×
2136
                "ReplyID": str(uuid.uuid4()),
2137
                "UserID": user_id,
2138
                "Content": content,
2139
                # "CreatedAt": datetime.now(tz).isoformat(),
2140
            }
2141

2142
            # Simulating interaction with a database (DynamoDB, for example)
2143
            # print("Attempting to save reply:", reply_data)  # Debugging statement
2144
            # Assuming 'posts_table' is configured to interact with your database
2145
            posts_table.update_item(
×
2146
                Key={"PostID": post_id, "ThreadID": thread_id},
2147
                UpdateExpression="SET Replies = list_append(if_not_exists(Replies, :empty_list), :reply)",
2148
                ExpressionAttributeValues={":reply": [reply_data], ":empty_list": []},
2149
                ReturnValues="UPDATED_NEW",
2150
            )
2151

2152
            return JsonResponse(
×
2153
                {
2154
                    "status": "success",
2155
                    "reply_id": reply_data["ReplyID"],
2156
                    "content": content,
2157
                    "username": user_id,
2158
                    # "created_at": reply_data["CreatedAt"],
2159
                }
2160
            )
2161

2162
        except Exception as e:
×
2163
            print("Exception occurred:", e)  # Debugging statement
×
2164
            return JsonResponse(
×
2165
                {"status": "error", "message": f"Failed to save reply: {str(e)}"},
2166
                status=500,
2167
            )
2168

2169
    return JsonResponse({"status": "error", "message": "Invalid request"}, status=400)
×
2170

2171

2172
def delete_reply_view(request):
1✔
2173
    # print("ReplitID:")
2174
    if (
×
2175
        request.method == "POST"
2176
        and request.headers.get("x-requested-with") == "XMLHttpRequest"
2177
    ):
2178
        data = json.loads(request.body.decode("utf-8"))
×
2179
        post_id = data.get("post_id")
×
2180
        reply_id = data.get("reply_id")
×
2181
        thread_id = data.get("thread_id")  # Retrieve thread_id from the request data
×
2182

2183
        if not post_id or not reply_id:
×
2184
            return JsonResponse(
×
2185
                {
2186
                    "status": "error",
2187
                    "message": "Post ID, Reply ID and Thread ID are required.",
2188
                },
2189
                status=400,
2190
            )
2191

2192
        # Call the delete_reply function in dynamodb.py
2193
        result = delete_reply(post_id, thread_id, reply_id)
×
2194

2195
        if result.get("status") == "success":
×
2196
            return JsonResponse({"status": "success"})
×
2197
        else:
2198
            error_message = result.get(
×
2199
                "message", "An error occurred while deleting the reply."
2200
            )
2201
            return JsonResponse(
×
2202
                {"status": "error", "message": error_message}, status=500
2203
            )
2204

2205
    return JsonResponse({"status": "error", "message": "Invalid request"}, status=400)
×
2206

2207

2208
def delete_thread(request):
1✔
2209
    if (
×
2210
        request.method == "POST"
2211
        and request.headers.get("x-requested-with") == "XMLHttpRequest"
2212
    ):
2213
        data = json.loads(request.body.decode("utf-8"))
×
2214
        thread_id = data.get("thread_id")
×
2215

2216
        if not thread_id:
×
2217
            return JsonResponse(
×
2218
                {"status": "error", "message": "Thread ID is required."}, status=400
2219
            )
2220

2221
        try:
×
2222
            # Perform the deletion from DynamoDB
2223
            delete_thread_by_id(thread_id)
×
2224
            # threads_table.delete_item(Key={"ThreadID": thread_id})
2225
            return JsonResponse(
×
2226
                {"status": "success", "message": "Thread deleted successfully."}
2227
            )
2228
        except Exception as e:
×
2229
            return JsonResponse({"status": "error", "message": str(e)}, status=500)
×
2230
    return JsonResponse(
×
2231
        {"status": "error", "message": "Invalid request method."}, status=400
2232
    )
2233

2234

2235
def reports_view(request):
1✔
2236
    user = get_user(request.session.get("user_id"))
×
2237
    reporting_user = user.get("user_id")  # Get the user ID from the session
×
2238

2239
    # Handle POST requests (Reporting Threads and Comments) - Available to all users
2240
    if request.method == "POST":
×
2241
        data = json.loads(request.body.decode("utf-8"))
×
2242
        action = data.get("action")
×
2243
        thread_id = data.get("thread_id")
×
2244
        post_id = data.get("post_id")  # Add support for comment IDs
×
2245

2246
        # Debugging input values
2247
        # print(f"Action: {action}, Thread ID: {thread_id}, Post ID: {post_id}")
2248

2249
        # Allow anyone to report a thread
2250
        if action == "report_thread" and thread_id:
×
2251
            # Mark the thread as reported in DynamoDB
2252
            mark_thread_as_reported(thread_id)
×
2253
            return JsonResponse({"status": "success"})
×
2254

2255
        # Allow anyone to report a comment
2256
        elif action == "report_comment" and thread_id and post_id:
×
2257
            # print("Reporting comment...")
2258
            # Pass all three arguments to the function
2259
            mark_comment_as_reported(thread_id, post_id, reporting_user)
×
2260
            return JsonResponse(
×
2261
                {
2262
                    "status": "success",
2263
                    "message": f"Comment {post_id} reported successfully.",
2264
                }
2265
            )
2266

2267
        return JsonResponse(
×
2268
            {"status": "error", "message": "Invalid request"}, status=400
2269
        )
2270

2271
    # Handle GET requests (View reported threads and comments) - Restricted to admins
2272
    if not user.get("is_admin"):
×
2273
        return redirect("forum")  # Redirect non-admins to the main forum page
×
2274

2275
    # Retrieve reported threads and comments (Only for admins)
2276
    reported_data = fetch_reported_threads_and_comments()
×
2277
    return render(request, "reports.html", reported_data)
×
2278

2279

2280
# -----------------
2281
# Ban User Function
2282
# ------------------
2283

2284

2285
# By username
2286
def toggle_ban_user(request):
1✔
2287
    dynamodb = boto3.resource("dynamodb", region_name="us-west-2")
1✔
2288
    users_table = dynamodb.Table("Users")
1✔
2289

2290
    if (
1✔
2291
        request.method == "POST"
2292
        and request.headers.get("x-requested-with") == "XMLHttpRequest"
2293
    ):
2294
        data = json.loads(request.body)
1✔
2295
        username = data.get(
1✔
2296
            "user_id"
2297
        )  # Ensure this matches the 'user_id' field in DynamoDB
2298

2299
        if not username:
1✔
2300
            return JsonResponse(
×
2301
                {"status": "error", "message": "User ID is missing"}, status=400
2302
            )
2303

2304
        # Fetch user to check if they exist
2305
        user = get_user_by_username(username)
1✔
2306
        if not user:
1✔
2307
            return JsonResponse(
×
2308
                {"status": "error", "message": "User not found"}, status=404
2309
            )
2310

2311
        uid = user.get("user_id")
1✔
2312
        # Toggle the 'is_banned' attribute
2313
        is_banned = not user.get("is_banned", False)
1✔
2314

2315
        # Define the update expression and attributes
2316
        update_expression = "set is_banned = :b"
1✔
2317
        expression_values = {":b": is_banned}
1✔
2318

2319
        # If banning the user, set 'punishment_date' to the current time
2320
        if is_banned:
1✔
2321
            est = pytz.timezone("US/Eastern")
1✔
2322
            punishment_date = datetime.now(est).isoformat()
1✔
2323
            update_expression += ", punishment_date = :d"
1✔
2324
            expression_values[":d"] = punishment_date
1✔
2325
        else:
2326
            # If unbanning, remove punishment_date attribute
2327
            update_expression += " remove punishment_date"
×
2328

2329
        # Update the user item in DynamoDB
2330
        users_table.update_item(
1✔
2331
            Key={"user_id": uid},
2332
            UpdateExpression=update_expression,
2333
            ExpressionAttributeValues=expression_values,
2334
        )
2335

2336
        return JsonResponse({"status": "success", "is_banned": is_banned})
1✔
2337

2338
    return JsonResponse({"status": "error", "message": "Invalid request"}, status=400)
×
2339

2340

2341
def toggle_mute_user(request):
1✔
2342
    dynamodb = boto3.resource("dynamodb", region_name="us-west-2")
1✔
2343
    users_table = dynamodb.Table("Users")
1✔
2344

2345
    if (
1✔
2346
        request.method == "POST"
2347
        and request.headers.get("x-requested-with") == "XMLHttpRequest"
2348
    ):
2349
        data = json.loads(request.body)
1✔
2350
        username = data.get(
1✔
2351
            "user_id"
2352
        )  # Ensure this matches the 'user_id' field in DynamoDB
2353

2354
        if not username:
1✔
2355
            return JsonResponse(
×
2356
                {"status": "error", "message": "User ID is missing"}, status=400
2357
            )
2358

2359
        # Fetch user to check if they exist
2360
        user = get_user_by_username(username)
1✔
2361
        if not user:
1✔
2362
            return JsonResponse(
×
2363
                {"status": "error", "message": "User not found"}, status=404
2364
            )
2365

2366
        uid = user.get("user_id")
1✔
2367
        # Toggle the 'is_banned' attribute
2368
        is_muted = not user.get("is_muted", False)
1✔
2369

2370
        # Define the update expression and attributes
2371
        update_expression = "set is_muted = :b"
1✔
2372
        expression_values = {":b": is_muted}
1✔
2373

2374
        # If banning the user, set 'punishment_date' to the current time
2375
        if is_muted:
1✔
2376
            est = pytz.timezone("US/Eastern")
1✔
2377
            punishment_date = datetime.now(est).isoformat()
1✔
2378
            update_expression += ", punishment_date = :d"
1✔
2379
            expression_values[":d"] = punishment_date
1✔
2380
        else:
2381
            # If unbanning, remove punishment_date attribute
2382
            update_expression += " remove punishment_date"
×
2383

2384
        # Update the user item in DynamoDB
2385
        users_table.update_item(
1✔
2386
            Key={"user_id": uid},
2387
            UpdateExpression=update_expression,
2388
            ExpressionAttributeValues=expression_values,
2389
        )
2390

2391
        return JsonResponse({"status": "success", "is_muted": is_muted})
1✔
2392

2393
    return JsonResponse({"status": "error", "message": "Invalid request"}, status=400)
×
2394

2395

2396
def unban_user(request):
1✔
2397
    dynamodb = boto3.resource("dynamodb", region_name="us-west-2")
1✔
2398
    users_table = dynamodb.Table("Users")
1✔
2399

2400
    if (
1✔
2401
        request.method == "POST"
2402
        and request.headers.get("x-requested-with") == "XMLHttpRequest"
2403
    ):
2404
        data = json.loads(request.body)
1✔
2405
        user_id = data.get(
1✔
2406
            "user_id"
2407
        )  # Ensure this matches the 'user_id' field in DynamoDB
2408

2409
        if not user_id:
1✔
2410
            return JsonResponse(
×
2411
                {"status": "error", "message": "User ID is missing"}, status=400
2412
            )
2413

2414
        # Fetch user to check if they exist
2415
        if not user_id:
1✔
2416
            return JsonResponse(
×
2417
                {"status": "error", "message": "User not found"}, status=404
2418
            )
2419

2420
        # Set 'is_banned' to False and remove 'punishment_date'
2421
        users_table.update_item(
1✔
2422
            Key={"user_id": user_id},
2423
            UpdateExpression="set is_banned = :b remove punishment_date",
2424
            ExpressionAttributeValues={":b": False},
2425
        )
2426

2427
        return JsonResponse({"status": "success", "message": "User has been unbanned"})
1✔
2428

2429
    return JsonResponse({"status": "error", "message": "Invalid request"}, status=400)
×
2430

2431

2432
def unmute_user(request):
1✔
2433
    dynamodb = boto3.resource("dynamodb", region_name="us-west-2")
1✔
2434
    users_table = dynamodb.Table("Users")
1✔
2435

2436
    if (
1✔
2437
        request.method == "POST"
2438
        and request.headers.get("x-requested-with") == "XMLHttpRequest"
2439
    ):
2440
        data = json.loads(request.body)
1✔
2441
        user_id = data.get(
1✔
2442
            "user_id"
2443
        )  # Ensure this matches the 'user_id' field in DynamoDB
2444
        if not user_id:
1✔
2445
            return JsonResponse(
×
2446
                {"status": "error", "message": "User ID is missing"}, status=400
2447
            )
2448

2449
        # Fetch user to check if they exist
2450
        if not user_id:
1✔
2451
            return JsonResponse(
×
2452
                {"status": "error", "message": "User not found"}, status=404
2453
            )
2454

2455
        # Set 'is_banned' to False and remove 'punishment_date'
2456
        users_table.update_item(
1✔
2457
            Key={"user_id": user_id},
2458
            UpdateExpression="set is_muted = :b remove punishment_date",
2459
            ExpressionAttributeValues={":b": False},
2460
        )
2461

2462
        return JsonResponse({"status": "success", "message": "User has been unmuted"})
1✔
2463

2464
    return JsonResponse({"status": "error", "message": "Invalid request"}, status=400)
×
2465

2466

2467
def warn_action(request):
1✔
2468
    if request.method == "POST":
1✔
2469
        data = json.loads(request.body.decode("utf-8"))
1✔
2470
        action = data.get("action")
1✔
2471
        thread_id = data.get("thread_id")
1✔
2472
        post_id = data.get("post_id")
1✔
2473
        username = data.get("user_id")  # Here 'user_id' refers to the username.
1✔
2474

2475
        if not username:
1✔
2476
            return JsonResponse(
×
2477
                {"status": "error", "message": "Username is missing."}, status=400
2478
            )
2479

2480
        # Retrieve the user record by username
2481
        user = get_user_by_username(username)
1✔
2482
        if not user:
1✔
2483
            return JsonResponse(
1✔
2484
                {"status": "error", "message": "User not found."}, status=404
2485
            )
2486

2487
        # Extract the actual user_id from the user record
2488
        user_id = user.get("user_id")
1✔
2489

2490
        print(
1✔
2491
            f"Action: {action}, Thread ID: {thread_id}, Post ID: {post_id}, User ID: {user_id}"
2492
        )
2493

2494
        if action == "warn_thread" and thread_id:
1✔
2495
            mark_user_as_warned_thread(thread_id, user_id)
1✔
2496
            return JsonResponse(
1✔
2497
                {"status": "success", "message": "User warned for thread successfully."}
2498
            )
2499

2500
        elif action == "warn_comment" and post_id:
1✔
UNCOV
2501
            mark_user_as_warned_comment(post_id, user_id)
×
UNCOV
2502
            return JsonResponse(
×
2503
                {
2504
                    "status": "success",
2505
                    "message": "User warned for comment successfully.",
2506
                }
2507
            )
2508

2509
        return JsonResponse(
1✔
2510
            {"status": "error", "message": "Invalid action or ID."}, status=400
2511
        )
2512

2513
    return JsonResponse(
×
2514
        {"status": "error", "message": "Invalid request method."}, status=405
2515
    )
2516

2517

2518
def dismiss_warning(request):
1✔
2519
    if request.method == "POST":
1✔
2520
        user_id = request.session.get("user_id")  # Retrieve the logged-in user's ID
1✔
2521
        if not user_id:
1✔
2522
            return JsonResponse(
1✔
2523
                {"status": "error", "message": "User ID is missing."}, status=400
2524
            )
2525

2526
        # Call the function to set is_warned to False
2527
        result = set_user_warned_to_false(user_id)
1✔
2528

2529
        if result["status"] == "success":
1✔
2530
            return JsonResponse({"status": "success", "message": result["message"]})
1✔
2531
        else:
2532
            return JsonResponse(
×
2533
                {"status": "error", "message": result["message"]}, status=500
2534
            )
2535

2536
    return JsonResponse(
×
2537
        {"status": "error", "message": "Invalid request method."}, status=400
2538
    )
2539

2540

2541
# -------------
2542
# Punishmentsx
2543
# -------------
2544

2545

2546
def punishments_view(request):
1✔
2547
    # Check if the current user is an admin
2548
    user_id = request.session.get("username")
×
2549
    user = get_user_by_username(user_id)
×
2550
    if not user or not user.get("is_admin"):
×
2551
        return HttpResponseForbidden("You do not have permission to access this page.")
×
2552

2553
    # Fetch only punished users (banned or muted)
2554
    dynamodb = boto3.resource("dynamodb", region_name="us-west-2")
×
2555
    users_table = dynamodb.Table("Users")
×
2556
    response = users_table.scan(
×
2557
        FilterExpression="is_banned = :true OR is_muted = :true",
2558
        ExpressionAttributeValues={":true": True},
2559
    )
2560
    punished_users = response.get("Items", [])
×
2561

2562
    # Pass the punished users to the template
2563
    return render(request, "punishments.html", {"punished_users": punished_users})
×
2564

2565

2566
# # ---------------------
2567
# #   Sleep Score
2568
# # ---------------------
2569

2570

2571
def store_exercises(request):
1✔
2572
    post_data = json.loads(request.body)
×
2573
    exercise_list = post_data["data_list"]
×
2574
    print("Exercises: ", exercise_list)
×
2575
    connection = pymysql.connect(
×
2576
        host="database-1.chu04k6u0syf.us-east-1.rds.amazonaws.com",
2577
        user="admin",
2578
        password="admin1234",
2579
        database="exercises",
2580
    )
2581
    user = User.objects.get(email=request.user.username)
×
2582
    success = False
×
2583
    try:
×
2584
        with connection.cursor() as cursor:
×
2585
            sql = "INSERT INTO fitness_data (timestamp, user_id, Age, gender, height, weight, heartrate, steps, exercise_id) VALUES ("
×
2586

2587
            ts = datetime.datetime.now()
×
2588
            # Iterate over the list of values and execute the query for each row
2589
            for value in exercise_list:
×
2590
                ts = ts - datetime.timedelta(seconds=1)
×
2591
                time = ts.strftime("%Y-%m-%d %H:%M:%S.%f")
×
2592
                temp_sql = str(sql)
×
2593
                temp_sql += f'"{time}", '
×
2594
                temp_sql += str(user.id)
×
2595
                temp_sql += ", 26, "
×
2596
                gender = 1 if user.sex == "male" else 0
×
2597
                temp_sql += f"{gender}, "
×
2598
                temp_sql += f"{user.height}, "
×
2599
                temp_sql += f"{user.weight}, "
×
2600
                temp_sql += "75, "
×
2601
                temp_sql += "5000, "
×
2602
                temp_sql += f"{value})"
×
2603

2604
                cursor.execute(temp_sql)
×
2605

2606
            # Commit the transaction
2607
            connection.commit()
×
2608
            print("Insertion of exercises successful")
×
2609
            success = True
×
2610

2611
    finally:
2612
        # Close the connection
2613
        connection.close()
×
2614

2615
    if success:
×
2616
        return JsonResponse({"message": "Data received successfully"})
×
2617
    else:
2618
        return JsonResponse({"error": "Insertion failed"}, status=500)
×
2619

2620

2621
def list_exercises(request):
1✔
2622
    print("Request:", request)
×
2623
    name = request.GET.get("exercise_name")
×
2624
    level = request.GET.get("exercise_level")
×
2625
    equipment = request.GET.get("exercise_equipment")
×
2626
    muscle = request.GET.get("exercise_muscle")
×
2627
    category = request.GET.get("exercise_category")
×
2628
    print(
×
2629
        f"Name: {name}, Level:{level}, Equipment:{equipment}, Muscle: {muscle}, Category:{category}"
2630
    )
2631

2632
    # user = User.objects.get(email=request.user.username)
2633
    user_id = request.session.get("username")
×
2634
    user = get_user_by_username(user_id)
×
2635
    print("User:", user)
×
2636

2637
    gender = 1 if (user["gender"]) == "M" else 0
×
2638
    body = f"{gender}, {user['height']}, {user['weight']}, 70, 5000"
×
2639
    url = "https://9pweqg5b1i.execute-api.us-west-2.amazonaws.com/dev/recommend"
×
2640
    response = requests.post(url, json=body).text
×
2641
    print(f"Response:{response}")
×
2642
    start_index = response.index("[")
×
2643
    end_index = response.rindex("]")
×
2644
    list_string = response[start_index : end_index + 1]
×
2645
    print(f"List String: {list_string}")
×
2646
    inference_list = eval(list_string)[0]
×
2647
    print(f"Inference String: {inference_list}")
×
2648
    # if(type(inference_list) == int):
2649
    #     inference_list = [inference_list]
2650
    # inference_list = [max(50+i, i) for i in inference_list]
2651
    print(f"Inference String 2: {inference_list}")
×
2652

2653
    print(f"Request: {request}")
×
2654
    selected_exercises = request.GET.getlist("exercise")
×
2655
    print(f"Selected Exercises: {selected_exercises}")
×
2656

2657
    exercises = Exercise.objects.all()
×
2658
    print(f"Exercises: {exercises}")
×
2659

2660
    if name:
×
2661
        exercises = exercises.filter(name__icontains=name)
×
2662
    if level and level != "none":
×
2663
        exercises = exercises.filter(level__icontains=level)
×
2664
    if equipment and equipment != "none":
×
2665
        exercises = exercises.filter(equipment__icontains=equipment)
×
2666
    if category and category != "none":
×
2667
        exercises = exercises.filter(category__icontains=category)
×
2668
    if muscle and muscle != "none":
×
2669
        if MuscleGroup.objects.filter(name=muscle).exists():
×
2670
            exercises = exercises.filter(
×
2671
                primaryMuscles__name__icontains=muscle
2672
            ) | exercises.filter(secondaryMuscles__name__icontains=muscle)
2673

2674
    filter_dict = {
×
2675
        "name": name if name else "",
2676
        "level": level if level else "none",
2677
        "equipment": equipment if equipment else "none",
2678
        "category": category if category else "none",
2679
        "muscle": muscle if muscle else "none",
2680
    }
2681

2682
    page_number = request.GET.get("page", 1)  # Default to page 1 if not provided
×
2683
    paginator = Paginator(exercises, 10)
×
2684

2685
    try:
×
2686
        exercises = paginator.page(page_number)
×
2687
    except PageNotAnInteger:
×
2688
        exercises = paginator.page(1)
×
2689
    except EmptyPage:
×
2690
        exercises = paginator.page(paginator.num_pages)
×
2691

2692
    current_page_number = exercises.number
×
2693
    page_range = paginator.page_range
×
2694
    num_pages = paginator.num_pages
×
2695

2696
    image_urls = []
×
2697
    for ex in exercises:
×
2698
        name = re.sub(r"[^a-zA-Z0-9-(),']", "_", ex.name)
×
2699
        url = {
×
2700
            "url_0": f"https://fiton-static-files.s3.us-west-2.amazonaws.com/exercise_images/{name}_0.jpg",
2701
            "url_1": f"https://fiton-static-files.s3.us-west-2.amazonaws.com/exercise_images/{name}_1.jpg",
2702
        }
2703
        image_urls.append(url)
×
2704
    print(f"Image URLS: {image_urls}")
×
2705

2706
    if selected_exercises and len(selected_exercises):
×
2707
        selected_exercises = Exercise.objects.filter(id__in=selected_exercises)
×
2708
    else:
2709
        selected_exercises = []
×
2710

2711
    if inference_list and len(inference_list) == 4:
×
2712
        recommended_exercises = Exercise.objects.filter(id__in=inference_list)
×
2713
    else:
2714
        recommended_exercises = []
×
2715

2716
    print(f"Recommended Exercises 1: {recommended_exercises}")
×
2717

2718
    recommended_image_urls = []
×
2719
    for ex in recommended_exercises:
×
2720
        name = re.sub(r"[^a-zA-Z0-9-(),']", "_", ex.name)
×
2721
        url = {
×
2722
            "url_0": f"https://fiton-static-files.s3.us-west-2.amazonaws.com/exercise_images/{name}_0.jpg",
2723
            "url_1": f"https://fiton-static-files.s3.us-west-2.amazonaws.com/exercise_images/{name}_1.jpg",
2724
        }
2725
        recommended_image_urls.append(url)
×
2726

2727
    print(f"Recommended Exercises :{recommended_exercises}")
×
2728

2729
    return render(
×
2730
        request,
2731
        "exercise_list.html",
2732
        {
2733
            "exercises": zip(exercises, image_urls),
2734
            "filter_dict": filter_dict,
2735
            "current_page_number": current_page_number,
2736
            "page_range": page_range,
2737
            "num_pages": num_pages,
2738
            "selected_exercises": selected_exercises,
2739
            "recommended_exercises": zip(recommended_exercises, recommended_image_urls),
2740
        },
2741
    )
2742

2743

2744
# -------------------------------
2745
# Chat Functions
2746
# -------------------------------
2747

2748

2749
def private_chat(request):
1✔
2750
    username = request.session.get("username")
1✔
2751
    user = get_user_by_username(username)
1✔
2752

2753
    users_with_chat_history = []
1✔
2754

2755
    # Get all users except the logged-in user
2756
    users = get_users_without_specific_username(username)
1✔
2757

2758
    for u in users:
1✔
2759
        room_id = create_room_id(user["user_id"], u["user_id"])
1✔
2760
        chat_history = get_chat_history_from_db(room_id)
1✔
2761

2762
        print(chat_history)
1✔
2763
        if chat_history and chat_history.get("Items"):
1✔
2764
            # Check if there are unread messages for this user
2765
            unread = any(
1✔
2766
                msg.get("sender") == u["user_id"] and msg.get("is_read") is False
2767
                for msg in chat_history["Items"]
2768
            )
2769

2770
            latest_message = max(chat_history["Items"], key=lambda x: x["timestamp"])
1✔
2771
            u["last_activity"] = latest_message["timestamp"]
1✔
2772
            u["unread"] = unread  # Add unread status
1✔
2773
            print(u["username"], u["unread"])
1✔
2774
            users_with_chat_history.append(u)
1✔
2775

2776
    # Sort users with chat history by the latest activity timestamp
2777
    users_with_chat_history = sorted(
1✔
2778
        users_with_chat_history, key=lambda x: x["last_activity"], reverse=True
2779
    )
2780

2781
    # Handle search query if provided
2782
    search_query = request.GET.get("search", "").lower()
1✔
2783
    if search_query:
1✔
2784
        users_with_chat_history = [
×
2785
            u for u in users_with_chat_history if search_query in u["username"].lower()
2786
        ]
2787

2788
    # Prepare the context for the template
2789
    dic = {
1✔
2790
        "data": users_with_chat_history,  # Only users with chat history
2791
        "mine": user,
2792
    }
2793
    return render(request, "chat.html", dic)
1✔
2794

2795

2796
def create_room_id(uid_a, uid_b):
1✔
2797
    """Helper function to create consistent room IDs."""
2798
    ids = sorted([uid_a, uid_b])
1✔
2799
    return f"{ids[0]}and{ids[1]}"
1✔
2800

2801

2802
def get_chat_history(request, room_id):
1✔
2803
    # Fetch chat history
2804
    response = get_chat_history_from_db(room_id)
1✔
2805
    items = response.get("Items", [])
1✔
2806

2807
    # Mark unread messages as read
2808
    mark_messages_as_read(request, room_id)
1✔
2809

2810
    return JsonResponse({"messages": items})
1✔
2811

2812

2813
def group_chat(request):
1✔
2814
    username = request.session.get("username")
1✔
2815
    print(f"[DEBUG] Username in group_chat view: {username}")
1✔
2816
    user = get_user_by_username(username)
1✔
2817
    allUser = get_users_without_specific_username(username)
1✔
2818

2819
    data = []
1✔
2820
    for i in GroupChatMember.objects.filter(
1✔
2821
        uid=user.get("user_id"), status=GroupChatMember.AgreementStatus.COMPLETED
2822
    ):
2823
        data.append({"name": i.name})
1✔
2824

2825
    context = {
1✔
2826
        "data": data,
2827
        "mine": user,
2828
        "all_users": allUser,
2829
    }
2830
    return render(request, "chatg.html", context)
1✔
2831

2832

2833
def create_group_chat(request):
1✔
2834
    username = request.session.get("username")
1✔
2835
    user = get_user_by_username(username)
1✔
2836
    payload = json.loads(request.body.decode())
1✔
2837
    allUser = payload.get("allUser")
1✔
2838
    roomName = payload.get("roomName")
1✔
2839
    existing_group = (GroupChatMember.objects.filter)(name=roomName)
1✔
2840

2841
    if (existing_group.exists)():
1✔
2842
        return JsonResponse(
×
2843
            {"code": "400", "message": "Group has already been created!"}
2844
        )
2845

2846
    roomId = (GroupChatMember.objects.create)(
1✔
2847
        name=str(roomName),
2848
        uid=user["user_id"],
2849
        status=GroupChatMember.AgreementStatus.COMPLETED,
2850
    )
2851
    (roomId.save)()
1✔
2852

2853
    for i in allUser:
1✔
2854
        # try:
2855
        roomId = (GroupChatMember.objects.create)(
1✔
2856
            name=str(roomName),
2857
            uid=i,
2858
            status=GroupChatMember.AgreementStatus.COMPLETED,
2859
        )
2860
        (roomId.save)()
1✔
2861
        # except Exception as e:
2862
        #     print(f"Error creating GroupWebSocket for {i}: {e}")
2863
        #     return JsonResponse({"code": "500", "message": "Database error"})
2864
    dic = {"code": "200", "message": "ok"}
1✔
2865
    return JsonResponse(dic, json_dumps_params={"ensure_ascii": False})
1✔
2866

2867

2868
def invite_to_group(request):
1✔
2869
    payload = json.loads(request.body.decode())
1✔
2870
    allUser = payload.get("allUser")
1✔
2871
    roomName = payload.get("roomName")
1✔
2872

2873
    for i in allUser:
1✔
2874
        # try:
2875
        roomId = (GroupChatMember.objects.create)(
1✔
2876
            name=str(roomName),
2877
            uid=i,
2878
            status=GroupChatMember.AgreementStatus.IN_PROGRESS,
2879
        )
2880
        (roomId.save)()
1✔
2881

2882
        # except Exception as e:
2883
        #     print(f"Error creating GroupWebSocket for {i}: {e}")
2884
        #     return JsonResponse({"code": "500", "message": "Database error"})
2885
    dic = {"code": "200", "message": "ok"}
1✔
2886
    return JsonResponse(dic, json_dumps_params={"ensure_ascii": False})
1✔
2887

2888

2889
def join_group_chat(request):
1✔
2890
    payload = json.loads(request.body.decode())
1✔
2891
    userId = payload.get("userId")
1✔
2892
    room = payload.get("room")
1✔
2893

2894
    group = GroupChatMember.objects.get(uid=userId, name=room)
1✔
2895
    group.status = GroupChatMember.AgreementStatus.COMPLETED
1✔
2896
    group.save()
1✔
2897

2898
    dic = {"code": "200", "message": "ok"}
1✔
2899
    return JsonResponse(dic, json_dumps_params={"ensure_ascii": False})
1✔
2900

2901

2902
def leave_group_chat(request):
1✔
2903
    payload = json.loads(request.body.decode())
1✔
2904
    userId = payload.get("userId")
1✔
2905
    room = payload.get("room")
1✔
2906
    GroupChatMember.objects.get(uid=userId, name=room).delete()
1✔
2907

2908
    dic = {"code": "200", "message": "ok"}
1✔
2909
    return JsonResponse(dic, json_dumps_params={"ensure_ascii": False})
1✔
2910

2911

2912
# def get_pending_invitations(request):
2913
#     username = request.session.get("username")
2914

2915
#     # Retrieve the user ID using the `get_user_by_username` function
2916
#     user_id = get_user_by_username(username)["user_id"]
2917

2918
#     # Fetch pending invitations for the user from the database
2919
#     pending_invitations = GroupChatMember.objects.filter(
2920
#         uid=user_id, status=GroupChatMember.AgreementStatus.IN_PROGRESS
2921
#     )
2922

2923
#     # Convert GroupChatMember objects to a list of dictionaries
2924
#     invitation_data = []
2925
#     for invitation in pending_invitations:
2926
#         invitation_data.append(
2927
#             {
2928
#                 "name": invitation.name,
2929
#                 "uid": invitation.uid,
2930
#                 "status": invitation.status,
2931
#             }
2932
#         )
2933

2934
#     # Return the serialized data as JSON response
2935
#     dic = {"code": "200", "message": "ok", "data": invitation_data}
2936
#     return JsonResponse(dic, json_dumps_params={"ensure_ascii": False})
2937

2938

2939
def search_users(request):
1✔
2940
    query = request.GET.get("query", "").lower()
1✔
2941
    print(f"Search query: {query}")  # Debug log for the query
1✔
2942
    try:
1✔
2943
        matching_users = get_users_by_username_query(query)
1✔
2944
        print(f"Matching users: {matching_users}")  # Debug log for results
1✔
2945
        result = [
1✔
2946
            {"username": user["username"], "user_id": user["user_id"]}
2947
            for user in matching_users
2948
        ]
2949
        return JsonResponse(result, safe=False)
1✔
2950
    except Exception as e:
1✔
2951
        print(f"Error in search_users function: {e}")
1✔
2952
        return JsonResponse(
1✔
2953
            {"error": "Error occurred while searching users."}, status=500
2954
        )
2955

2956

2957
# def mark_messages_as_read(request, room_id):
2958
#     user_id = request.session.get("user_id")
2959

2960
#     # Fetch unread messages
2961
#     unread_messages = chat_table.query(
2962
#         KeyConditionExpression=Key("room_name").eq(room_id),
2963
#         FilterExpression=Attr("sender").ne(user_id) & Attr("is_read").eq(False),
2964
#     )
2965

2966
#     # Mark each message as read
2967
#     for msg in unread_messages.get("Items", []):
2968
#         chat_table.update_item(
2969
#             Key={
2970
#                 "room_name": room_id,
2971
#                 "timestamp": msg["timestamp"],
2972
#             },
2973
#             UpdateExpression="SET is_read = :true",
2974
#             ExpressionAttributeValues={":true": True},
2975
#         )
2976

2977

2978
def mark_messages_as_read(request, room_id):
1✔
2979
    user_id = request.session.get("user_id")
1✔
2980
    if not user_id:
1✔
2981
        return JsonResponse({"error": "User not authenticated"}, status=401)
1✔
2982

2983
    # Fetch unread messages
2984
    unread_messages = chat_table.query(
1✔
2985
        KeyConditionExpression=Key("room_name").eq(room_id),
2986
        FilterExpression=Attr("sender").ne(user_id) & Attr("is_read").eq(False),
2987
    )
2988

2989
    # Mark each message as read
2990
    for msg in unread_messages.get("Items", []):
1✔
2991
        chat_table.update_item(
1✔
2992
            Key={
2993
                "room_name": room_id,
2994
                "timestamp": msg["timestamp"],
2995
            },
2996
            UpdateExpression="SET is_read = :true",
2997
            ExpressionAttributeValues={":true": True},
2998
        )
2999

3000
    # Return success response
3001
    return JsonResponse({"code": "200", "message": "Messages marked as read"})
1✔
3002

3003
    # except Exception as e:
3004
    #     print(f"Error in mark_messages_as_read: {e}")
3005
    #     return JsonResponse(
3006
    #         {"error": "An error occurred while processing the request."}, status=500
3007
    #     )
3008

3009

3010
def get_group_members(request, group_name):
1✔
3011
    group_members = GroupChatMember.objects.filter(name=group_name)
1✔
3012
    member_data = []
1✔
3013

3014
    for member in group_members:
1✔
3015
        # try:
3016
        # Fetch the User instance from DynamoDB using get_user_by_uid
3017
        user = get_user_by_uid(member.uid)  # Call the DynamoDB helper function
1✔
3018
        if user:
1✔
3019
            member_data.append(
1✔
3020
                {
3021
                    "username": user[
3022
                        "username"
3023
                    ],  # Replace with the correct key from DynamoDB response
3024
                    "id": user[
3025
                        "user_id"
3026
                    ],  # Replace with the correct key for the unique identifier
3027
                }
3028
            )
3029
        # else:
3030
        #     print(f"User with UID {member.uid} not found in DynamoDB.")
3031
        # except Exception as e:
3032
        #     print(f"Error fetching user with UID {member.uid}: {e}")
3033
        #     continue
3034

3035
    return JsonResponse({"members": member_data}, status=200)
1✔
3036

3037

3038
@csrf_exempt
1✔
3039
def add_users_to_group(request):
1✔
3040
    if request.method == "POST":
1✔
3041
        # try:
3042
        data = json.loads(request.body)
1✔
3043
        room_name = data.get("roomName")
1✔
3044
        user_ids = data.get("allUser", [])
1✔
3045

3046
        if not room_name or not user_ids:
1✔
3047
            return JsonResponse(
1✔
3048
                {"code": "400", "message": "Room name and users are required."}
3049
            )
3050

3051
        # Add users to the group chat
3052
        for user_id in user_ids:
1✔
3053
            GroupChatMember.objects.get_or_create(
1✔
3054
                name=room_name,
3055
                uid=user_id,
3056
                defaults={"status": GroupChatMember.AgreementStatus.COMPLETED},
3057
            )
3058

3059
        return JsonResponse({"code": "200", "message": "Users added successfully."})
1✔
3060
        # except Exception as e:
3061
        #     return JsonResponse(
3062
        #         {"code": "500", "message": f"Error adding users: {str(e)}"}
3063
        #     )
3064
    return JsonResponse({"code": "405", "message": "Method not allowed."})
1✔
3065

3066

3067
# Fitness Goals View
3068
def fitness_goals_view(request):
1✔
3069
    user_id = request.session.get("user_id")
1✔
3070

3071
    user = get_user(user_id)
1✔
3072

3073
    if not user:
1✔
3074
        messages.error(request, "User not found.")
×
3075
        return redirect("login")
×
3076

3077
    dynamodb = boto3.resource("dynamodb", region_name="us-west-2")
1✔
3078
    user_goals_table = dynamodb.Table("UserGoals")
1✔
3079

3080
    if request.method == "POST":
1✔
3081
        # Retrieve form data
3082
        goal_type = request.POST.get("goal_type")  # e.g., "weight", "steps", etc.
1✔
3083
        custom_name = request.POST.get("goal_name", "")  # Only for custom goals
1✔
3084
        goal_value = request.POST.get(
1✔
3085
            "goal_value"
3086
        )  # Goal value, e.g., 150 lbs, 10,000 steps
3087

3088
        restricted_types = ["weight", "steps", "sleep"]
1✔
3089
        if goal_type in restricted_types:
1✔
3090
            try:
1✔
3091
                response = user_goals_table.query(
1✔
3092
                    KeyConditionExpression=boto3.dynamodb.conditions.Key("user_id").eq(
3093
                        user_id
3094
                    )
3095
                )
3096
                existing_goals = response.get("Items", [])
1✔
3097
                if any(goal["Type"] == goal_type for goal in existing_goals):
1✔
3098
                    messages.error(
1✔
3099
                        request,
3100
                        f"You already have a {goal_type} goal. Please edit it instead.",
3101
                    )
3102
                    return redirect(
1✔
3103
                        "fitness_goals"
3104
                    )  # Redirect back to the fitness goals page
3105
            except Exception as e:
×
3106
                messages.error(request, f"Failed to check existing goals: {e}")
×
3107
                return redirect("fitness_goals")
×
3108

3109
        # Create a new GoalID and get the user_id
3110
        goal_id = str(uuid.uuid4())
1✔
3111

3112
        # Prepare the item for DynamoDB
3113
        item = {
1✔
3114
            "GoalID": goal_id,
3115
            "user_id": user_id,
3116
            "Type": goal_type,
3117
            "Name": custom_name if goal_type in ["custom", "activity"] else None,
3118
            "Value": goal_value,
3119
        }
3120

3121
        # Add the goal to DynamoDB
3122
        try:
1✔
3123
            user_goals_table.put_item(Item=item)
1✔
3124
        except Exception as e:
×
3125
            messages.error(request, f"Failed to add goal: {e}")
×
3126

3127
        return redirect("fitness_goals")  # Redirect back to the fitness goals page
1✔
3128

3129
    try:
1✔
3130
        response = user_goals_table.query(
1✔
3131
            KeyConditionExpression=boto3.dynamodb.conditions.Key("user_id").eq(user_id)
3132
        )
3133
        goals = response.get("Items", [])
1✔
3134
    except Exception as e:
×
3135
        goals = []
×
3136
        messages.error(request, f"Failed to fetch goals: {e}")
×
3137

3138
    return render(request, "fitness_goals.html", {"user": user, "goals": goals})
1✔
3139

3140

3141
def edit_goal(request):
1✔
3142
    if request.method == "POST":
1✔
3143
        try:
1✔
3144
            data = json.loads(request.body)
1✔
3145
            goal_id = data.get("goal_id")
1✔
3146
            goal_value = data.get("goal_value")
1✔
3147
            goal_name = data.get("goal_name", None)
1✔
3148

3149
            dynamodb = boto3.resource("dynamodb", region_name="us-west-2")
1✔
3150
            user_goals_table = dynamodb.Table("UserGoals")
1✔
3151

3152
            user_id = request.session.get("user_id")
1✔
3153

3154
            # Update the goal in DynamoDB
3155
            user_goals_table.update_item(
1✔
3156
                Key={
3157
                    "GoalID": goal_id,
3158
                    "user_id": user_id,
3159
                },
3160
                UpdateExpression="SET #val = :val, #name = :name",
3161
                ExpressionAttributeNames={"#val": "Value", "#name": "Name"},
3162
                ExpressionAttributeValues={":val": goal_value, ":name": goal_name},
3163
            )
3164
            return JsonResponse({"message": "Goal updated successfully!"}, status=200)
1✔
3165
        except Exception as e:
×
3166
            return JsonResponse({"error": str(e)}, status=400)
×
3167

3168
    return JsonResponse({"error": "Invalid request"}, status=400)
×
3169

3170

3171
def delete_goal(request):
1✔
3172
    if request.method == "POST":
1✔
3173
        try:
1✔
3174
            data = json.loads(request.body)
1✔
3175
            goal_id = data.get("goal_id")
1✔
3176
            user_id = request.session.get("user_id")
1✔
3177

3178
            if not goal_id or not user_id:
1✔
3179
                return JsonResponse({"error": "Invalid request."}, status=400)
×
3180

3181
            dynamodb = boto3.resource("dynamodb", region_name="us-west-2")
1✔
3182
            user_goals_table = dynamodb.Table("UserGoals")
1✔
3183
            # Delete the goal from DynamoDB
3184
            user_goals_table.delete_item(Key={"GoalID": goal_id, "user_id": user_id})
1✔
3185
            return JsonResponse({"message": "Goal deleted successfully."}, status=200)
1✔
3186
        except Exception as e:
×
3187
            return JsonResponse({"error": str(e)}, status=500)
×
3188

3189
    return JsonResponse({"error": "Invalid request method."}, status=400)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc