1- from typing import Optional
2- from app .schemas .submission import SubmissionOut
3- from sqlalchemy .orm import Session ,joinedload
4- from app .db import models
1+ from typing import Optional , List
52from datetime import date , timedelta
6- from sqlalchemy import func
7- from app . db . db import SessionLocal
3+ import os , json
4+
85import gspread
6+ from gspread .exceptions import CellNotFound
97from oauth2client .service_account import ServiceAccountCredentials
10- import os
8+ from sqlalchemy .orm import Session
9+ from sqlalchemy import func
10+
11+ from app .schemas .submission import SubmissionOut
12+ from app .db import models
13+ from app .db .db import SessionLocal
1114
12- SCOPE = ["https://spreadsheets.google.com/feeds" , "https://www.googleapis.com/auth/drive" ]
13- CREDS_FILE = "credentials.json"
15+ SCOPE = [
16+ "https://www.googleapis.com/auth/spreadsheets" ,
17+ "https://www.googleapis.com/auth/drive" ,
18+ ]
19+
20+ def _gspread_client ():
21+ raw = os .getenv ("GOOGLE_CREDENTIALS_JSON" )
22+ if not raw :
23+ raise RuntimeError ("Missing GOOGLE_CREDENTIALS_JSON in environment" )
24+ raw = raw .strip ().strip ("'" ).strip ('"' )
25+ creds_dict = json .loads (raw )
26+ credentials = ServiceAccountCredentials .from_json_keyfile_dict (creds_dict , scopes = SCOPE )
27+ return gspread .authorize (credentials )
28+
29+ def _to_date (d ):
30+ if d is None :
31+ return None
32+ if isinstance (d , date ):
33+ return d
34+ return getattr (d , "date" , lambda : None )()
1435
1536def get_user_by_email (db : Session , email : str ):
1637 return db .query (models .User ).filter (models .User .email == email ).first ()
@@ -21,91 +42,66 @@ def get_task(db: Session, track_id: int, task_no: int):
2142def submit_task (db : Session , mentee_id : int , task_id : int , start_date : date , commit_hash : str ):
2243 existing = db .query (models .Submission ).filter_by (mentee_id = mentee_id , task_id = task_id ).first ()
2344 if existing :
24- return None # Already submitted
25-
45+ return None
2646 mentee = db .query (models .User ).filter (models .User .id == mentee_id ).first ()
27-
2847 task = db .query (models .Task ).filter (models .Task .id == task_id ).first ()
2948 if not task :
3049 raise Exception ("Task not found" )
31-
32- # convert start_date into a datetime object
33- start_date = start_date
34- deadline = start_date + timedelta (days = task .deadline_days )
3550 submitted_at = date .today ()
36-
37- # Check if the submission is late
38- if deadline >= submitted_at :
39- submission = models .Submission (
40- mentee_id = mentee_id ,
41- task_id = task .id ,
42- task_name = task .title ,
43- task_no = task .task_no ,
44- submitted_at = date .today (),
45- status = "submitted" ,
46- start_date = start_date ,
47- commit_hash = commit_hash
51+ deadline = start_date + timedelta (days = task .deadline_days or 0 ) if task .deadline_days else None
52+ if deadline and submitted_at > deadline :
53+ return "late submission not allowed"
54+ submission = models .Submission (
55+ mentee_id = mentee_id ,
56+ task_id = task .id ,
57+ task_name = task .title ,
58+ task_no = task .task_no ,
59+ submitted_at = submitted_at ,
60+ status = "submitted" ,
61+ start_date = start_date ,
62+ commit_hash = commit_hash ,
63+ )
64+ client = _gspread_client ()
65+ if task .track_id in (1 , 2 ):
66+ sheet = client .open ("Copy of Praveshan 2025 Master DB" ).worksheet (
67+ "S1 Submissions" if task .track_id == 1 else "S2 Submissions"
4868 )
49-
50- credentials = ServiceAccountCredentials .from_json_keyfile_name (CREDS_FILE , SCOPE )
51- client = gspread .authorize (credentials )
52- if (task .track_id == 1 ):
53- sheet = client .open ("Copy of Praveshan 2025 Master DB" ).worksheet ("S1 Submissions" )
54- cell = sheet .find (mentee .name )
55- if not cell :
56- name_column = sheet .col_values (1 )
57- row = len (name_column ) + 1
58- sheet .update_cell (row , 1 , mentee .name )
59- sheet .update_cell (row , task .task_no + 2 , commit_hash )
60- else :
61- row = cell .row
62- sheet .update_cell (row , task .task_no + 2 , commit_hash )
63- elif (task .track_id == 2 ):
64- sheet = client .open ("Copy of Praveshan 2025 Master DB" ).worksheet ("S2 Submissions" )
69+ try :
6570 cell = sheet .find (mentee .name )
66- if not cell :
67- name_column = sheet .col_values (1 )
68- row = len (name_column ) + 1
69- sheet .update_cell (row , 1 , mentee .name )
70- sheet .update_cell (row , task .task_no + 2 , commit_hash )
71- else :
72- row = cell .row
73- sheet .update_cell (row , task .task_no + 2 , commit_hash )
74-
75-
76-
77- db .add (submission )
78- db .commit ()
79- db .refresh (submission )
80-
81- return submission
82- else :
83- return "late submission not allowed"
71+ row = cell .row
72+ except CellNotFound :
73+ name_column = sheet .col_values (1 )
74+ row = len (name_column ) + 1 if name_column else 1
75+ sheet .update_cell (row , 1 , mentee .name )
76+ sheet .update_cell (row , task .task_no + 2 , commit_hash )
77+ db .add (submission )
78+ db .commit ()
79+ db .refresh (submission )
80+ return submission
8481
8582def approve_submission (db : Session , submission_id : int , mentor_feedback : str , status : str ):
8683 sub = db .query (models .Submission ).filter_by (id = submission_id ).first ()
8784 if not sub :
8885 return None
89-
9086 sub .status = status
9187 sub .mentor_feedback = mentor_feedback
9288 if status == "approved" :
9389 sub .approved_at = date .today ()
94-
9590 db .commit ()
9691 db .refresh (sub )
9792 return sub
9893
9994def is_mentor_of (db : Session , mentor_id : int , mentee_id : int ):
100- return db .query (models .MentorMenteeMap ).filter_by (mentor_id = mentor_id , mentee_id = mentee_id ).first () is not None
95+ return db .query (models .MentorMenteeMap ).filter_by (
96+ mentor_id = mentor_id , mentee_id = mentee_id
97+ ).first () is not None
10198
10299def get_leaderboard_data (db : Session , track_id : int ):
103-
104100 return (
105101 db .query (
106102 models .User .name ,
107103 func .sum (models .Task .points ).label ("total_points" ),
108- func .count (models .Submission .id ).label ("tasks_completed" )
104+ func .count (models .Submission .id ).label ("tasks_completed" ),
109105 )
110106 .join (models .Submission , models .Submission .mentee_id == models .User .id )
111107 .join (models .Task , models .Submission .task_id == models .Task .id )
@@ -115,6 +111,7 @@ def get_leaderboard_data(db: Session, track_id: int):
115111 .order_by (func .sum (models .Task .points ).desc ())
116112 .all ()
117113 )
114+
118115def get_otp_by_email (db , email ):
119116 return db .query (models .OTP ).filter (models .OTP .email == email ).first ()
120117
@@ -128,21 +125,14 @@ def create_or_update_otp(db, email, otp, expires_at):
128125 db .add (entry )
129126 db .commit ()
130127
131-
132- def get_submissions_for_user (db : Session , email : str , track_id : Optional [int ] = None ) -> list [SubmissionOut ]:
128+ def get_submissions_for_user (db : Session , email : str , track_id : Optional [int ] = None ) -> List [SubmissionOut ]:
133129 user = db .query (models .User ).filter (models .User .email == email ).first ()
134130 if not user :
135131 return []
136-
137- query = db .query (models .Submission ).filter (
138- models .Submission .mentee_id == user .id
139- )
140-
132+ query = db .query (models .Submission ).filter (models .Submission .mentee_id == user .id )
141133 if track_id is not None :
142134 query = query .join (models .Task ).filter (models .Task .track_id == track_id )
143-
144135 submissions = query .all ()
145-
146136 return [
147137 SubmissionOut (
148138 id = sub .id ,
@@ -151,39 +141,35 @@ def get_submissions_for_user(db: Session, email: str, track_id: Optional[int] =
151141 task_name = sub .task_name ,
152142 task_no = sub .task_no ,
153143 status = sub .status ,
154- submitted_at = sub . submitted_at . date () if sub .submitted_at else None ,
155- approved_at = sub . approved_at . date () if sub .approved_at else None ,
144+ submitted_at = _to_date ( sub .submitted_at ) ,
145+ approved_at = _to_date ( sub .approved_at ) ,
156146 mentor_feedback = sub .mentor_feedback ,
157- start_date = sub . start_date . date () if sub .start_date else None
147+ start_date = _to_date ( sub .start_date ),
158148 )
159149 for sub in submissions
160150 ]
161-
151+
162152def get_sheet_data ():
163- creds = ServiceAccountCredentials .from_json_keyfile_name (CREDS_FILE , SCOPE )
164- client = gspread .authorize (creds )
165- worksheet = client .open_by_key (os .getenv ("GOOGLE_SHEET_ID" )).worksheet ("Praveshan Phase 3" ) # Change sheet name
153+ client = _gspread_client ()
154+ worksheet = client .open_by_key (os .getenv ("GOOGLE_SHEET_ID" )).worksheet ("Praveshan Phase 3" )
166155 expected_headers = ["Name" , "Email Address" ]
167- data = worksheet .get_all_records (expected_headers = expected_headers )
168- return data
156+ return worksheet .get_all_records (expected_headers = expected_headers )
169157
170158def sync_users_from_sheet ():
171159 db : Session = SessionLocal ()
172160 try :
173- rows = get_sheet_data ()
174- print (f"Loaded { len (rows )} rows from sheet." )
161+ rows = get_sheet_data ()
175162 inserted_count = 0
176163 for row in rows :
177164 email = row .get ("Email Address" , "" ).strip ()
178165 name = row .get ("Name" , "" ).strip ()
179166 if not email or not name :
180167 continue
181168 if get_user_by_email (db , email ):
182- continue
169+ continue
183170 user = models .User (name = name , email = email , role = "mentee" )
184171 db .add (user )
185172 inserted_count += 1
186-
187173 db .commit ()
188174 print (f"Inserted { inserted_count } new users." )
189175 except Exception as e :
0 commit comments