Merge branch 'main' of https://hackethon.ai/hack/HackXlbef/FreeBug
commit
c603e293da
@ -1,3 +1,48 @@ |
||||
from flask import Blueprint |
||||
from flask import Blueprint, url_for, jsonify, g |
||||
from utils.auth import auth_required |
||||
from db.model import db, Badge, UserBadge |
||||
from sqlalchemy import select |
||||
badge_route = Blueprint('badge', __name__) |
||||
|
||||
badge = Blueprint('badge', __name__) |
||||
@badge_route.route('/listAllBadges') |
||||
def all_badges(): |
||||
badges: list[Badge] = db.session.execute(select(Badge)).scalars() |
||||
data: list = [] |
||||
for bgd in badges: |
||||
data.append({ |
||||
'id': bgd.id, |
||||
'name': bgd.name, |
||||
'description': bgd.description, |
||||
'createDate': bgd.createDate, |
||||
'icon': url_for('send_file', filename=bgd.icon), |
||||
'canClaim': bgd.canClaim |
||||
}) |
||||
return jsonify({ |
||||
'count': len(data), |
||||
'data': data |
||||
}) |
||||
|
||||
@badge_route.route('/myBadges') |
||||
@auth_required() |
||||
def my_badges(): |
||||
user_badges: list[UserBadge] = db.session.execute(select(UserBadge).where( |
||||
UserBadge.userID == g.current_user.id |
||||
)).scalars() |
||||
data: list = [] |
||||
for ub in user_badges: |
||||
bgd = ub.badge |
||||
data.append({ |
||||
'id': ub.id, |
||||
'badgeID': bgd.id, |
||||
'userID': ub.userID, |
||||
'name': bgd.name, |
||||
'description': bgd.description, |
||||
'createDate': bgd.createDate, |
||||
'icon': url_for('send_file', filename=bgd.icon), |
||||
'canClaim': bgd.canClaim, |
||||
'claimedDate': ub.claimedDate, |
||||
}) |
||||
return jsonify({ |
||||
'count': len(data), |
||||
'data': data |
||||
}) |
@ -0,0 +1,226 @@ |
||||
import json |
||||
import os |
||||
import uuid |
||||
import requests |
||||
from flask import Blueprint, request, jsonify, g, url_for |
||||
from uuid import UUID |
||||
from ...db.model import db, User, Course, Enrollment,Chat, Quiz, QuizAttempt |
||||
from utils.auth import auth_required |
||||
import requests |
||||
from config import SPAM_SCORE_THRESHOLD, AI_SPAM_SERVICES_MICROSERVICE, USER_UPLOADS_DIR, AI_QUIZ_SERVICES_MICROSERVICE |
||||
from sqlalchemy import desc, select, and_ |
||||
|
||||
quiz = Blueprint('chat', __name__) |
||||
|
||||
|
||||
@quiz.route('/generate') |
||||
@auth_required() |
||||
def generate_quiz(): |
||||
try: |
||||
course_id: uuid.UUID = uuid.UUID(request.form['course_id']) |
||||
current_page: int = int(request.form['page']) |
||||
except KeyError: |
||||
return jsonify({'message': 'course_id and page must be specified'}), 401 |
||||
enrollment_record: Enrollment = db.session.execute( |
||||
select(Enrollment).where(and_( |
||||
Enrollment.courseID == course_id, |
||||
Enrollment.userID == g.current_user.id) |
||||
) |
||||
).scalar() |
||||
if not enrollment_record: |
||||
return jsonify({"error": "You are not enrolled in this course."}), 403 |
||||
if current_page > enrollment_record.course.totalPages or current_page < 1: |
||||
return jsonify({ |
||||
'message': 'Page range out of bound. No such page' |
||||
}), 404 |
||||
# Everything is alright, now get the text in current page and generate quiz |
||||
current_page_text: str = '' |
||||
with open( |
||||
os.path.join( |
||||
USER_UPLOADS_DIR, |
||||
enrollment_record.course.serverFilename+"_parts", |
||||
f"{current_page}.txt") |
||||
) as f: |
||||
current_page_text = f.read() |
||||
quiz_data_resp = requests.post(AI_QUIZ_SERVICES_MICROSERVICE, json={"string_message": current_page_text}) |
||||
if quiz_data_resp.status_code != 200: |
||||
return jsonify({"error": "Failed to make quiz request."}), 500 |
||||
quiz_data = quiz_data_resp.json() |
||||
# Insert the quiz into table |
||||
rows: list[Quiz] = [] |
||||
for quiz_item in quiz_data['questions']: |
||||
rows.append( |
||||
Quiz( |
||||
creatorUserID=g.current_user.id, |
||||
creatorUser=g.current_user, |
||||
quiz_attempts=[], |
||||
courseID=course_id, |
||||
course=enrollment_record.course, |
||||
quizQuestion=quiz_item['question'], |
||||
quizAnswers=json.dumps(quiz_item['options']), |
||||
quizCorrectAnswer=quiz_item['correct_answer'] |
||||
) |
||||
) |
||||
db.session.add_all(rows) |
||||
db.session.commit() |
||||
return jsonify({'message': 'quizzes were generated for the current page'}) |
||||
|
||||
@quiz.route('/get/personalIncomplete') |
||||
@auth_required() |
||||
def get_incomplete_quiz(): |
||||
try: |
||||
course_id: uuid.UUID = uuid.UUID(request.args['course_id']) |
||||
except KeyError: |
||||
return jsonify({'message': 'course_id must be specified'}), 401 |
||||
quiz_rows: list[Quiz] = db.session.execute(select(Quiz).where( |
||||
and_(Quiz.creatorUserID == g.current_user.id, Quiz.creatorHasAttempted == False) |
||||
)).scalars() |
||||
data: list = [] |
||||
for quiz_row in quiz_rows: |
||||
data.append( |
||||
{ |
||||
'id': quiz_row.id, |
||||
'isActive': quiz_row.isActive, |
||||
'creationDate': quiz_row.creationDate, |
||||
'quizAnswers': quiz_row.quizAnswers, |
||||
'quizQuestion': quiz_row.quizQuestion, |
||||
'course': { |
||||
'id': quiz_row.course.id, |
||||
'name': quiz_row.course.name, |
||||
'description': quiz_row.course.description |
||||
}, |
||||
'creator': { |
||||
'id': quiz_row.creatorUserID, |
||||
'firstName': quiz_row.creatorUser.firstName, |
||||
'lastName': quiz_row.creatorUser.lastName, |
||||
'username': quiz_row.creatorUser.username, |
||||
'pfpFilename': url_for('send_file', filename=quiz_row.creatorUser.pfpFilename) |
||||
} |
||||
} |
||||
) |
||||
return jsonify({ |
||||
'count': len(data), |
||||
'data': data |
||||
}), 200 |
||||
|
||||
|
||||
@quiz.route('/get/allComplete') |
||||
@auth_required() |
||||
def get_incomplete_quiz(): |
||||
try: |
||||
course_id: uuid.UUID = uuid.UUID(request.args['course_id']) |
||||
except KeyError: |
||||
return jsonify({'message': 'course_id must be specified'}), 401 |
||||
quiz_attempts: list[QuizAttempt] = db.session.execute( |
||||
select(QuizAttempt).where(and_( |
||||
QuizAttempt.userID == g.current_user.id, |
||||
Course.id == course_id |
||||
))).scalars() # IF THIS DOES NOT WORK, ADD COURSE IF TO QUIZ_ATTEMPT TABLE ITSELF |
||||
completes: list = [] |
||||
for attempt in quiz_attempts: |
||||
quiz_row: Quiz = attempt.quiz |
||||
completes.append( |
||||
{ |
||||
'id': attempt.id, |
||||
'quizID': quiz_row.id, |
||||
'isActive': quiz_row.isActive, |
||||
'creationDate': quiz_row.creationDate, |
||||
'quizAnswers': quiz_row.quizAnswers, |
||||
'quizQuestion': quiz_row.quizQuestion, |
||||
'userAnswer': attempt.userAnswer, |
||||
'quizCorrectAnswer': quiz_row.quizCorrectAnswer, |
||||
'isCorrect': attempt.isCorrect, |
||||
'course': { |
||||
'id': quiz_row.course.id, |
||||
'name': quiz_row.course.name, |
||||
'description': quiz_row.course.description |
||||
}, |
||||
'creator': { |
||||
'id': quiz_row.creatorUserID, |
||||
'firstName': quiz_row.creatorUser.firstName, |
||||
'lastName': quiz_row.creatorUser.lastName, |
||||
'username': quiz_row.creatorUser.username, |
||||
'pfpFilename': url_for('send_file', filename=quiz_row.creatorUser.pfpFilename) |
||||
} |
||||
} |
||||
) |
||||
return jsonify({ |
||||
'count': len(completes), |
||||
'data': completes |
||||
}), 200 |
||||
|
||||
|
||||
@quiz.route('/submit') |
||||
@auth_required() |
||||
def get_incomplete_quiz(): |
||||
try: |
||||
answer: str = request.form['answer'].strip() |
||||
quiz_id: uuid.UUID = uuid.UUID(request.form['course_id']) |
||||
except KeyError: |
||||
return jsonify({'message': 'course_id and answer must be specified'}), 401 |
||||
quiz_already_attempted: QuizAttempt = db.session.execute(select(QuizAttempt).where( |
||||
and_(QuizAttempt.quizID == quiz_id, QuizAttempt.userID == g.current_user.id ) |
||||
)).scalar() |
||||
if quiz_already_attempted: |
||||
return jsonify({'message': 'Already attempted this quiz'}), 401 |
||||
quiz_row: Quiz = db.session.execute(select(Quiz).where(Quiz.id == quiz_id)).scalar() |
||||
if not quiz_row: |
||||
return jsonify({'message': 'Quiz does not exist'}), 404 |
||||
valid_answers: list = json.loads(quiz_row.quizAnswers) |
||||
is_correct: bool = False |
||||
if answer not in valid_answers: |
||||
return jsonify({'message': 'No such choice of answer given'}), 404 |
||||
if answer == quiz_row.quizCorrectAnswer: |
||||
is_correct = True |
||||
new_attempt: QuizAttempt = QuizAttempt( |
||||
userID=g.current_user.id, |
||||
user=g.current_user, |
||||
quizID=quiz_id, |
||||
quiz=quiz_row, |
||||
userAnswer=answer, |
||||
isCorrect=int(is_correct) |
||||
) |
||||
db.session.add(new_attempt) |
||||
if Quiz.creatorUser.id == g.current_user.id: |
||||
Quiz.creatorHasAttempted = True |
||||
db.session.commit() |
||||
return jsonify({ |
||||
'message': 'Answer submitted', |
||||
'isCorrect': is_correct, |
||||
'attemptID': new_attempt.id, |
||||
'quizID': quiz_row.id, |
||||
'quizAnswers': quiz_row.quizAnswers, |
||||
'quizQuestion': quiz_row.quizQuestion, |
||||
'quizCorrectAnswer': quiz_row.quizCorrectAnswer, |
||||
'userAnswer': answer |
||||
}) |
||||
|
||||
@quiz.route('/quizData') |
||||
@auth_required() |
||||
def get_quiz_info(): |
||||
try: |
||||
quiz_id: uuid.UUID = uuid.UUID(request.args['quiz_id']) |
||||
except KeyError: |
||||
return jsonify({'message': 'quiz_id must be specified'}), 401 |
||||
quiz_row: Quiz = db.session.execute(select(Quiz).where(Quiz.id == quiz_id)).scalar() |
||||
if not quiz_row: |
||||
return jsonify({'message': 'Quiz does not exist'}), 404 |
||||
return jsonify({ |
||||
'id': quiz_row.id, |
||||
'isActive': quiz_row.isActive, |
||||
'creationDate': quiz_row.creationDate, |
||||
'quizAnswers': quiz_row.quizAnswers, |
||||
'quizQuestion': quiz_row.quizQuestion, |
||||
'course': { |
||||
'id': quiz_row.course.id, |
||||
'name': quiz_row.course.name, |
||||
'description': quiz_row.course.description |
||||
}, |
||||
'creator': { |
||||
'id': quiz_row.creatorUserID, |
||||
'firstName': quiz_row.creatorUser.firstName, |
||||
'lastName': quiz_row.creatorUser.lastName, |
||||
'username': quiz_row.creatorUser.username, |
||||
'pfpFilename': url_for('send_file', filename=quiz_row.creatorUser.pfpFilename) |
||||
} |
||||
}), 200 |
@ -0,0 +1,113 @@ |
||||
import DataTable from "@/components/(dashboard)/common/DataTable/DataTable" |
||||
import { Button } from "@/components/(dashboard)/ui/button" |
||||
import { Badge } from "@/components/ui/badge" |
||||
import { routes } from "@/lib/routes" |
||||
import { ColumnDef } from "@tanstack/react-table" |
||||
import { ArrowUpDown } from "lucide-react" |
||||
|
||||
const CategoryTable: React.FC<{ |
||||
mutate: () => void |
||||
Data: Array<any> |
||||
isLoading: boolean |
||||
}> = ({ |
||||
mutate, |
||||
Data, |
||||
isLoading |
||||
}) => { |
||||
|
||||
const columns: ColumnDef<any>[] = [ |
||||
{ |
||||
accessorKey: "sn", |
||||
header: "SN", |
||||
cell: ({ row }) => ( |
||||
<div className="capitalize">{row.index + 1}</div> |
||||
), |
||||
}, |
||||
{ |
||||
id: 'name', |
||||
accessorFn: (row: any) => row.original?.name, |
||||
header: ({ column }) => ( |
||||
<Button |
||||
variant="ghost" |
||||
className="!px-0" |
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} |
||||
> |
||||
Name |
||||
<ArrowUpDown className="ml-2 h-4 w-4" /> |
||||
</Button> |
||||
), |
||||
cell: ({ row }) => ( |
||||
<div className="capitalize"> |
||||
<p>{row.original?.name}</p> |
||||
</div> |
||||
), |
||||
}, |
||||
{ |
||||
id: 'description', |
||||
accessorFn: (row: any) => row.original?.description, |
||||
header: ({ column }) => ( |
||||
<Button |
||||
variant="ghost" |
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} |
||||
className="!px-0" |
||||
> |
||||
Description |
||||
<ArrowUpDown className="ml-2 h-4 w-4" /> |
||||
</Button> |
||||
), |
||||
cell: ({ row }) => ( |
||||
<div className="max-w-[300px] truncate">{row.original?.description ?? '-'}</div> |
||||
), |
||||
}, |
||||
{ |
||||
id: 'creationDate', |
||||
accessorFn: (row: any) => row.original?.creationDate, |
||||
header: ({ column }) => ( |
||||
<Button |
||||
variant="ghost" |
||||
className="!px-0" |
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} |
||||
> |
||||
Created Date |
||||
<ArrowUpDown className="ml-2 h-4 w-4" /> |
||||
</Button> |
||||
), |
||||
cell: ({ row }) => ( |
||||
<div className="whitespace-nowrap"> |
||||
{row.original?.creationDate ? new Date(row.original.creationDate).toLocaleDateString() : '-'} |
||||
</div> |
||||
), |
||||
}, |
||||
{ |
||||
id: 'isActive', |
||||
accessorFn: (row: any) => row.original?.isActive, |
||||
header: ({ column }) => ( |
||||
<Button |
||||
variant="ghost" |
||||
className="!px-0" |
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} |
||||
> |
||||
Status |
||||
<ArrowUpDown className="ml-2 h-4 w-4" /> |
||||
</Button> |
||||
), |
||||
cell: ({ row }) => ( |
||||
<div>{row.original?.isActive ? <Badge variant={'success'}>Active</Badge> : <Badge variant={'destructive'}>Deactive</Badge>}</div> |
||||
), |
||||
}, |
||||
] |
||||
|
||||
return( |
||||
<> |
||||
<DataTable |
||||
data={Data} |
||||
columns={columns} |
||||
mutate={mutate} |
||||
searchKey="name" |
||||
isLoading={isLoading} |
||||
/> |
||||
</> |
||||
) |
||||
} |
||||
|
||||
export default CategoryTable |
@ -0,0 +1,49 @@ |
||||
'use client' |
||||
import BreadCrumbNav from "@/components/(dashboard)/common/BreadCumbNav/BreadCrumbNav" |
||||
import ContentContainer from "@/components/(dashboard)/elements/ContentContainer" |
||||
import { PageHeading } from "@/components/(dashboard)/ui/title" |
||||
import CommonContainer from "@/components/elements/CommonContainer" |
||||
import AppContextProvider from "@/helpers/context/AppContextProvider" |
||||
import { defaultFetcher } from "@/helpers/fetch.helper" |
||||
import { routes } from "@/lib/routes" |
||||
import { APP_BASE_URL } from "@/utils/constants" |
||||
import AdminView from "@/views/AdminView" |
||||
import useSWR from "swr" |
||||
import CategoryTable from "./_partials/CategoryTable" |
||||
|
||||
|
||||
const CategoryIndexPage = () => { |
||||
const CategoryListURL = `${APP_BASE_URL}/api/courses/getCategories` |
||||
const { data, mutate, isLoading } = useSWR(CategoryListURL, defaultFetcher); |
||||
|
||||
return( |
||||
<AppContextProvider> |
||||
<AdminView> |
||||
<CommonContainer> |
||||
<PageHeading>Categories</PageHeading> |
||||
<BreadCrumbNav breadCrumbItems={[ |
||||
{ |
||||
title: 'Dashboard', |
||||
href: routes.DASHBOARD_ROUTE |
||||
}, |
||||
{ |
||||
title: 'Categories', |
||||
href: routes.CATEGORY_INDEX_PAGE |
||||
}, |
||||
]}/> |
||||
<ContentContainer> |
||||
<div> |
||||
<CategoryTable |
||||
mutate={mutate} |
||||
isLoading={isLoading} |
||||
Data={data || []} |
||||
/> |
||||
</div> |
||||
</ContentContainer> |
||||
</CommonContainer> |
||||
</AdminView> |
||||
</AppContextProvider> |
||||
) |
||||
} |
||||
|
||||
export default CategoryIndexPage |
Loading…
Reference in new issue