Skip to content

Solving all High-Priority issues #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 64 additions & 6 deletions app/db/crud.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Optional
from sqlalchemy.orm import Session
from app.db import models
from datetime import datetime, date
from datetime import datetime, date, timedelta
from sqlalchemy import func

def get_user_by_email(db: Session, email: str):
Expand All @@ -10,6 +10,7 @@ def get_user_by_email(db: Session, email: str):
def get_task(db: Session, track_id: int, task_no: int):
return db.query(models.Task).filter_by(track_id=track_id, task_no=task_no).first()


def submit_task(db: Session, mentee_id: int, task_id: int, reference_link: str,start_date: date):
existing = db.query(models.Submission).filter_by(mentee_id=mentee_id, task_id=task_id).first()
if existing:
Expand All @@ -30,15 +31,16 @@ def submit_task(db: Session, mentee_id: int, task_id: int, reference_link: str,s
db.refresh(submission)
return submission

def approve_submission(db: Session, submission_id: int, mentor_feedback: str, status: str):
def approve_submission(db: Session, submission_id: int, mentor_feedback: str, points_awarded: int):
sub = db.query(models.Submission).filter_by(id=submission_id).first()
if not sub:
return None

sub.status = status
sub.mentor_feedback = mentor_feedback
if status == "approved":
sub.approved_at = datetime.utcnow()
sub.approved_at = datetime.now()
sub.points_awarded += points_awarded
sub.total_paused_time = (datetime.now() - sub.submitted_at).days
sub.status = "approved"

db.commit()
db.refresh(sub)
Expand Down Expand Up @@ -76,6 +78,46 @@ def create_or_update_otp(db, email, otp, expires_at):
db.add(entry)
db.commit()

def pause_task(db: Session, submission: int):
pause_row=db.query(models.Submission).filter_by(id=submission).first()
pause_row.pause_start=datetime.now()
pause_row.status = "paused"
db.commit()
db.refresh(pause_row)
return pause_row

def end_pause(db: Session, submission: int):
pause_row=db.query(models.Submission).filter_by(id=submission).first()
pause_row.total_paused_time+= (datetime.now().date() - pause_row.pause_start.date()).days
pause_row.status = "ongoing"
pause_row.pause_start = None
db.commit()
db.refresh(pause_row)
return pause_row

def start_task(db: Session, task_id: int, mentee_id: int):
task_start = models.Submission(
mentee_id = mentee_id,
task_id = task_id,
start_date = datetime.now(),
status = "ongoing"
)
db.add(task_start)
db.commit()
db.refresh(task_start)
return task_start

def find_time_spent_on_task(db: Session, submission_id: int):
submission = db.query(models.Submission).filter_by(id=submission_id).first()
start = datetime.fromisoformat(submission.start_date) if isinstance(submission.start_date, str) else submission.start_date
end = submission.submitted_at or datetime.now()
time_spent = (end - start - timedelta(days=submission.total_paused_time)).days
return time_spent

def get_submission(db: Session, mentee_email: str, track_id: int, task_no: int):
mentee = get_user_by_email(db, email=mentee_email)
task = get_task(db, track_id=track_id, task_no=task_no)
return db.query(models.Submission).filter_by(mentee_id=mentee.id, task_id=task.id).first()

def get_submissions(db: Session, email: str, track_id: Optional[int] = None):
user = db.query(models.User).filter(models.User.email == email).first()
Expand All @@ -85,4 +127,20 @@ def get_submissions(db: Session, email: str, track_id: Optional[int] = None):
if track_id is not None:
query = query.join(models.Task, models.Submission.task_id == models.Task.id)
query = query.filter(models.Task.track_id == track_id)
return query.all()
return query.all()

def reject_submission(db: Session, submission_id: int, mentor_feedback: str):
sub = db.query(models.Submission).filter_by(id=submission_id).first()
if not sub:
return None

sub.reference_link = None
sub.mentor_feedback = mentor_feedback
sub.total_paused_time = (datetime.now() - sub.submitted_at).days
sub.submitted_at = None
sub.status = "ongoing"
sub.start_date = None

db.commit()
db.refresh(sub)
return sub
15 changes: 9 additions & 6 deletions app/db/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, UniqueConstraint
from sqlalchemy.orm import relationship
from datetime import datetime
from app.db.db import Base

class User(Base):
Expand Down Expand Up @@ -37,12 +36,16 @@ class Submission(Base):
id = Column(Integer, primary_key=True, index=True)
mentee_id = Column(Integer, ForeignKey("users.id"), nullable=False)
task_id = Column(Integer, ForeignKey("tasks.id"), nullable=False)
reference_link = Column(Text, nullable=False)
status = Column(String, default="submitted") # submitted / approved / paused / rejected
submitted_at = Column(DateTime, default=datetime.utcnow)
start_date = Column(DateTime, nullable=False)

reference_link = Column(Text, nullable=True)
status = Column(String, default="ongoing") # submitted / approved / paused / ongoing / not started
start_date = Column(String, nullable=False)
submitted_at = Column(DateTime, nullable=True)
approved_at = Column(DateTime, nullable=True)
mentor_feedback = Column(Text, nullable=True)
pause_start = Column(DateTime, nullable=True)
total_paused_time = Column(Integer, nullable=False, default=0)

mentee = relationship("User")
task = relationship("Task")

Expand All @@ -63,7 +66,7 @@ class LeaderboardEntry(Base):
tasks_completed = Column(Integer, default=0)
class OTP(Base):
__tablename__ = "otp"

email = Column(String, primary_key=True, index=True)
otp = Column(String, nullable=False)
expires_at = Column(DateTime, nullable=False)
81 changes: 68 additions & 13 deletions app/routes/progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
from sqlalchemy.orm import Session
from app.db import crud, models
from app.db.db import get_db
from app.schemas.submission import SubmissionCreate, SubmissionOut, SubmissionApproval
from app.schemas.submission import SubmissionCreate, SubmissionOut, SubmissionApproval, PauseTask, StartTask

router = APIRouter()

@router.post("/submit-task", response_model=SubmissionOut)
@router.post("/submit-task", response_model=SubmissionOut) # check if it has already been submitted,
def submit_task(data: SubmissionCreate, db: Session = Depends(get_db)):
# 1. Validate mentee
mentee = crud.get_user_by_email(db, data.mentee_email)
mentee = crud.get_user_by_email(db, email=data.mentee_email)
if not mentee or mentee.role != "mentee":
raise HTTPException(status_code=403, detail="Invalid or missing mentee")

Expand All @@ -18,34 +18,89 @@ def submit_task(data: SubmissionCreate, db: Session = Depends(get_db)):
if not task:
raise HTTPException(status_code=404, detail="Task not found")


# 3. Submit
submission = crud.submit_task(db, mentee_id=mentee.id, task_id=task.id, reference_link=data.reference_link, start_date=data.start_date)
if not submission:
raise HTTPException(status_code=400, detail="Task already submitted")
# 4. Submit task
submission = crud.submit_task(db, mentee_id=mentee.id, task_id=task.id, reference_link=data.reference_link, status = "submitted")

return submission

@router.patch("/approve-task", response_model=SubmissionOut)
@router.patch("/review-task", response_model=SubmissionOut)
def approve_task(data: SubmissionApproval, db: Session = Depends(get_db)):
# 1. Validate mentor
mentor = crud.get_user_by_email(db, data.mentor_email)
mentor = crud.get_user_by_email(db, email=data.mentor_email)
if not mentor or mentor.role != "mentor":
raise HTTPException(status_code=403, detail="Invalid or missing mentor")

# 2. Validate submission
sub = db.query(models.Submission).filter_by(id=data.submission_id).first()
if not sub:
raise HTTPException(status_code=404, detail="Submission not found")
task = db.query(models.Tasks).filter_by(id=sub.task_id).first()

# 3. Confirm mentor is assigned to this mentee
if not crud.is_mentor_of(db, mentor.id, sub.mentee_id):
raise HTTPException(status_code=403, detail="Mentor not authorized for this mentee")

# 4. See if accepted or rejected
if data.accepted:
if data.points_awarded > task.points:
raise HTTPException(status_code=422, detail='Awarded points exceed maximum points of the task')
else:
crud.approve_submission(
db,
submission_id=sub.id,
mentor_feedback=data.mentor_feedback,
points_awarded=data.points_awarded
)
else:
crud.reject_submission(
db,
submission_id=sub.id,
mentor_feedback=data.mentor_feedback,
)
return SubmissionOut(mentee_id=sub.mentee_id,
task_id=sub.task_id,
reference_link=sub.reference_link or None,
status=sub.status,
submitted_at=sub.submitted_at or None
)

@router.post("/pause-task", response_model=PauseTask)
def pause_task(data: PauseTask, db: Session = Depends(get_db)):
mentor = crud.get_user_by_email(db, email=data.mentor_email)
mentee = crud.get_user_by_email(db, email=data.mentee_email)
if not crud.is_mentor_of(db, mentor.id, mentee.id):
raise HTTPException(status_code=403, detail="Mentor not authorized for this mentee")
task = crud.get_task(db, task_no=data.task_no, track_id=data.track_id)
mentee_task_submission = crud.get_submission(db, mentee.email, task.track_id, task.task_no)
if mentee_task_submission.pause_start:
raise HTTPException(status_code=400, detail="This task is already paused")
crud.pause_task(db, mentee_task_submission.id)
return PauseTask(task_no=task.task_no, track_id=task.track_id, mentee_email=mentee.email, mentor_email=mentor.email)

# 4. Approve or pause
updated = crud.approve_submission(
db,
submission_id=sub.id,
mentor_feedback=data.mentor_feedback,
status=data.status
)
return updated
@router.post("/pause-end", response_model=PauseTask)
def end_pause(data: PauseTask, db: Session = Depends(get_db)):
mentor = crud.get_user_by_email(db, email=data.mentor_email)
mentee = crud.get_user_by_email(db, email=data.mentee_email)
if not crud.is_mentor_of(db, mentor.id, mentee.id):
raise HTTPException(status_code=403, detail="Mentor not authorized for this mentee")
task = crud.get_task(db, task_no=data.task_no, track_id=data.track_id)
mentee_task_submission = db.query(models.Submission).filter_by(mentee_id=mentee.id, task_id=task.id).first()
if not mentee_task_submission.pause_start:
raise HTTPException(status_code=400, detail="This task is not paused")
crud.end_pause(db, mentee_task_submission.id)
return PauseTask(task_no=task.task_no, track_id=task.track_id, mentee_email=mentee.email, mentor_email=mentor.email)

@router.post("/start-task", response_model=StartTask)
def start_task(data: StartTask, db: Session = Depends(get_db)):
mentee = crud.get_user_by_email(db, email=data.mentee_email)
task = crud.get_task(db, track_id=data.track_id, task_no=data.task_no)
existing = crud.get_submission(db, mentee.email, task.track_id, task.task_no)
if existing:
raise HTTPException(status_code=403, detail="Already Started")
crud.start_task(db, task_id=task.id, mentee_id=mentee.id)
return mentee.email, task.task_no, task.track.id
39 changes: 32 additions & 7 deletions app/routes/tracks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
from sqlalchemy.orm import Session
from app.db import models
from app.db.db import get_db
from app.schemas.track import TrackOut
from app.schemas.track import TrackOut, MenteeTasks
from app.schemas.task import TaskOut
from app.db import crud

router = APIRouter()

Expand All @@ -12,9 +13,33 @@ def list_tracks(db: Session = Depends(get_db)):
return db.query(models.Track).all()

@router.get("/{track_id}/tasks", response_model=list[TaskOut])
def list_tasks_for_track(track_id: int, db: Session = Depends(get_db)):
track = db.query(models.Track).filter_by(id=track_id).first()
if not track:
raise HTTPException(status_code=404, detail="Track not found")

return db.query(models.Task).filter_by(track_id=track_id).order_by(models.Task.task_no).all()
def mentee_specific_status(track_id: int, mentee_email: str, db: Session = Depends(get_db)):
tasks = db.query(models.Task).filter_by(track_id=track_id).order_by(models.Task.task_no).all()
if not tasks:
raise HTTPException(status_code=404, detail='Track not found')
mentee = crud.get_user_by_email(db, mentee_email)
if not mentee:
raise HTTPException(status_code=404, detail="Mentee not found")
tasks_with_status=[]
for task in tasks:
submission_of_task = db.query(models.Submission).filter_by(task_id=task.id, mentee_id=mentee.id).first()
if submission_of_task:
status = submission_of_task.status
time_spent = crud.find_time_spent_on_task(db, submission_of_task.id)
else:
status = "Not started"
time_spent = 0
progress_bar = int((time_spent/task.deadline_days) * 100)
tasks_with_status.append(
{
"task_no": task.task_no,
"title": task.title,
"points": task.points,
"deadline": task.deadline_days,
"status": status,
"progress_bar": progress_bar,
"description": task.description,
"track_id": task.track_id
}
)
return tasks_with_status
23 changes: 20 additions & 3 deletions app/schemas/submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class SubmissionCreate(SubmissionBase):


class SubmissionOut(BaseModel):
id: int
id: int
mentee_id: int
task_id: int
reference_link: str
Expand All @@ -27,5 +27,22 @@ class Config:
class SubmissionApproval(BaseModel):
submission_id: int
mentor_email: str
status: str # approved, paused, rejected
mentor_feedback: Optional[str] = None
status: str
mentor_feedback: Optional[str] = None
accepted: bool
points_awarded: int

class PauseTask(BaseModel):
task_no: int
track_id: int
mentee_email: str
mentor_email: str

class TasksList(BaseModel):
track_id: int
mentee_id: str

class StartTask(BaseModel):
mentee_email: str
task_no: int
track_id: int
5 changes: 3 additions & 2 deletions app/schemas/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ class TaskBase(BaseModel):
title: str
description: Optional[str] = None
points: int = 10
deadline: Optional[datetime] = None
deadline: Optional[int] = None

class TaskCreate(TaskBase):
pass

class TaskOut(TaskBase):
id: int
progress_bar: int
status: str

class Config:
orm_mode = True
17 changes: 16 additions & 1 deletion app/schemas/track.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,19 @@ class TrackOut(TrackBase):
id: int

class Config:
orm_mode = True
orm_mode = True

class TaskOut(BaseModel):
task_no: int
title: str
points: int
deadline: int
status: str
time_spent: int
description: str
track: str

class MenteeTasks(BaseModel):
mentee_email: str
task_no: int
track_id: int