From 53ee23da0ac1373b823cd732007591e80d4f3a36 Mon Sep 17 00:00:00 2001 From: HanNayeoniee Date: Fri, 19 Dec 2025 00:14:10 +0900 Subject: [PATCH 01/66] =?UTF-8?q?UserChallengeStatus=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- getcloser/backend/app/models/challenges.py | 5 +++-- .../backend/app/services/challenge_service.py | 22 ++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/getcloser/backend/app/models/challenges.py b/getcloser/backend/app/models/challenges.py index a607e2a..5570462 100644 --- a/getcloser/backend/app/models/challenges.py +++ b/getcloser/backend/app/models/challenges.py @@ -6,8 +6,9 @@ class UserChallengeStatus(Base): __tablename__ = "user_challenge_status" - user_id = Column(Integer, primary_key=True, index=True) - challenge_id = Column(Integer, ForeignKey("challenge_questions.id"), index=True) + id = Column(Integer, primary_key=True, index=True) + user_id = Column(Integer, index=True) + challenge_id = Column(Integer, ForeignKey("challenge_questions.id"), nullable=True) is_correct = Column(Boolean, default=False) submitted_at = Column(DateTime(timezone=True)) diff --git a/getcloser/backend/app/services/challenge_service.py b/getcloser/backend/app/services/challenge_service.py index 8a870d5..365c31a 100644 --- a/getcloser/backend/app/services/challenge_service.py +++ b/getcloser/backend/app/services/challenge_service.py @@ -36,6 +36,7 @@ def assign_challenges_logic(my_id: str, members: list, db: Session) -> list: available_ids = members.copy() random.shuffle(available_ids) + members = list(set(members)) for user_id in members: possible_ids = [uid for uid in available_ids if uid != user_id] if not possible_ids: @@ -43,9 +44,26 @@ def assign_challenges_logic(my_id: str, members: list, db: Session) -> list: assigned_user_id = random.choice(possible_ids) assigned_question = random.choice([q for q in team_questions if q.user_id == assigned_user_id]) - + available_ids.remove(assigned_user_id) + # ✅ UserChallengeStatus 업데이트 + user_status = ( + db.query(UserChallengeStatus) + .filter(UserChallengeStatus.user_id == user_id) + .first() + ) + + if not user_status: + raise HTTPException(status_code=404, detail="UserChallengeStatus 없음") + + user_status.challenge_id = assigned_question.id + user_status.submitted_at = None + user_status.is_correct = False + user_status.is_redeemed = False + + db.add(user_status) + assigned_list.append(AssignedChallenge( user_id=user_id, assigned_challenge_id=assigned_question.id, @@ -54,6 +72,8 @@ def assign_challenges_logic(my_id: str, members: list, db: Session) -> list: answer=assigned_question.answer )) + db.commit() + return assigned_list[0] From ce085c92ae0e9d5858e70ac5bc547b3dd97a7df0 Mon Sep 17 00:00:00 2001 From: HanNayeoniee Date: Mon, 29 Dec 2025 23:15:19 +0900 Subject: [PATCH 02/66] =?UTF-8?q?fix:=20from=5Fuser=5Fid=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- getcloser/backend/app/schemas/challenge_schema.py | 1 - getcloser/backend/app/scripts/challenge_question.py | 4 ---- 2 files changed, 5 deletions(-) diff --git a/getcloser/backend/app/schemas/challenge_schema.py b/getcloser/backend/app/schemas/challenge_schema.py index fa32c2e..c38c868 100644 --- a/getcloser/backend/app/schemas/challenge_schema.py +++ b/getcloser/backend/app/schemas/challenge_schema.py @@ -21,7 +21,6 @@ class Config: class AssignedChallenge(BaseModel): user_id: int assigned_challenge_id: int - from_user_id: int category: str answer: str diff --git a/getcloser/backend/app/scripts/challenge_question.py b/getcloser/backend/app/scripts/challenge_question.py index c45b515..51289c8 100644 --- a/getcloser/backend/app/scripts/challenge_question.py +++ b/getcloser/backend/app/scripts/challenge_question.py @@ -31,7 +31,3 @@ def seed_challenge_questions_from_csv(file_path: str): finally: db.close() - - -# if __name__ == "__main__": -# seed_challenge_questions_from_csv("./scripts/challenge_question.csv") From 97bfa79d2c49bda8fdbcb7ad6d7e7b7db66c09d3 Mon Sep 17 00:00:00 2001 From: HanNayeoniee Date: Mon, 29 Dec 2025 23:15:49 +0900 Subject: [PATCH 03/66] =?UTF-8?q?fix:=20=EB=AC=B8=EC=A0=9C=20=ED=95=A0?= =?UTF-8?q?=EB=8B=B9=20=EB=B0=8F=20=ED=8C=90=EB=B3=84=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/app/services/challenge_service.py | 112 ++++++++---------- 1 file changed, 51 insertions(+), 61 deletions(-) diff --git a/getcloser/backend/app/services/challenge_service.py b/getcloser/backend/app/services/challenge_service.py index 365c31a..336856b 100644 --- a/getcloser/backend/app/services/challenge_service.py +++ b/getcloser/backend/app/services/challenge_service.py @@ -12,7 +12,7 @@ def assign_challenges_logic(my_id: str, members: list, db: Session) -> list: # 현재 사용자 retry_count 조회 status = db.query(UserChallengeStatus).filter(UserChallengeStatus.user_id == my_id).first() - # ✅ 없으면 생성 + # status 없으면 생성 if not status: status = UserChallengeStatus( user_id=my_id, @@ -28,85 +28,75 @@ def assign_challenges_logic(my_id: str, members: list, db: Session) -> list: if status.retry_count >= 2: return {"message": "retry_count가 2 이상입니다. 팀을 다시 구성해주세요."} + # 팀원들이 만든 문제 조회 team_questions = db.query(ChallengeQuestion).filter(ChallengeQuestion.user_id.in_(members)).all() - if len(team_questions) < len(members): - raise ValueError("팀원 문제가 충분하지 않습니다.") - - assigned_list = [] - available_ids = members.copy() - random.shuffle(available_ids) - - members = list(set(members)) - for user_id in members: - possible_ids = [uid for uid in available_ids if uid != user_id] - if not possible_ids: - raise ValueError(f"{user_id}에게 할당할 문제 부족") - - assigned_user_id = random.choice(possible_ids) - assigned_question = random.choice([q for q in team_questions if q.user_id == assigned_user_id]) - - available_ids.remove(assigned_user_id) - - # ✅ UserChallengeStatus 업데이트 - user_status = ( - db.query(UserChallengeStatus) - .filter(UserChallengeStatus.user_id == user_id) - .first() - ) - - if not user_status: - raise HTTPException(status_code=404, detail="UserChallengeStatus 없음") - - user_status.challenge_id = assigned_question.id - user_status.submitted_at = None - user_status.is_correct = False - user_status.is_redeemed = False - - db.add(user_status) - - assigned_list.append(AssignedChallenge( - user_id=user_id, - assigned_challenge_id=assigned_question.id, - from_user_id=assigned_question.user_id, - category=assigned_question.category, - answer=assigned_question.answer - )) - - db.commit() - - return assigned_list[0] - - -def submit_challenges_logic(user_id: str, challenge_id: int, submitted_answer: str, db: Session) -> bool: - # 1. 사용자가 푼 문제 찾기 - challenge = db.query(ChallengeQuestion).filter( - ChallengeQuestion.user_id == user_id, - ChallengeQuestion.id == challenge_id - ).first() + if not team_questions: + raise ValueError("배정할 문제가 없습니다.") + + # 셔플 후 1개 선택 + selected_question = random.choice(team_questions) + + # AssignedChallenge로 변환 + assigned_challenge = AssignedChallenge( + assigned_challenge_id=selected_question.id, + user_id=selected_question.user_id, # 문제 출제자 + category=selected_question.category, + answer=selected_question.answer, + ) + + # ✅ UserChallengeStatus 업데이트 + user_status = ( + db.query(UserChallengeStatus) + .filter(UserChallengeStatus.user_id == my_id) + .first() + ) + + if not user_status: + raise HTTPException(status_code=404, detail="UserChallengeStatus 없음") + + user_status.challenge_id = selected_question.id + user_status.submitted_at = None + user_status.is_correct = False + user_status.is_redeemed = False + + db.add(user_status) + db.commit() + + return assigned_challenge + + +def submit_challenges_logic(my_id: str, challenge_id: int, submitted_answer: str, db: Session) -> bool: + # # 1. 사용자가 푼 문제 찾기 + # challenge = db.query(ChallengeQuestion).filter( + # ChallengeQuestion.user_id == user_id, + # ChallengeQuestion.id == challenge_id + # ).first() - if not challenge: - raise ValueError("해당 사용자의 할당된 문제가 없습니다.") + # if not challenge: + # raise ValueError("해당 사용자의 할당된 문제가 없습니다.") # 2. 원본 문제에서 정답 확인 question = db.query(ChallengeQuestion).filter( - ChallengeQuestion.id == challenge.id + ChallengeQuestion.id == challenge_id ).first() if not question: raise ValueError("문제 정보를 찾을 수 없습니다.") # 3. 정답 판별 - is_correct = (submitted_answer.strip().lower() == question.answer.strip().lower()) + answer_keyword = max(question.answer.lower().split(), key=len) + submitted = submitted_answer.strip().lower() + is_correct = answer_keyword in submitted # 4. UserChallengeStatus 조회 또는 생성 status = db.query(UserChallengeStatus).filter( - UserChallengeStatus.user_id == user_id, + UserChallengeStatus.user_id == my_id, UserChallengeStatus.challenge_id == challenge_id ).first() if not status: status = UserChallengeStatus( - user_id=user_id, + user_id=my_id, challenge_id=challenge_id, retry_count=0 ) From 5c0ffdc3d5c9d52b658be673005d96015e71442f Mon Sep 17 00:00:00 2001 From: daclouds Date: Fri, 2 Jan 2026 23:20:13 +0900 Subject: [PATCH 04/66] fix(getcloser): redirect to create view if myId is not exist in ready_members --- getcloser/frontend/src/app/pages/Page2.tsx | 24 ++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/getcloser/frontend/src/app/pages/Page2.tsx b/getcloser/frontend/src/app/pages/Page2.tsx index 0c0bfb2..b5c0b7e 100644 --- a/getcloser/frontend/src/app/pages/Page2.tsx +++ b/getcloser/frontend/src/app/pages/Page2.tsx @@ -224,10 +224,28 @@ export default function Page2() { const interval = setInterval(async () => { try { const response = await authenticatedFetch(`/api/v1/teams/${String(teamId)}/status`); - if (!response.ok) throw new Error('Failed to fetch team status'); + + if (!response.ok) { + if (response.status === 404 || response.status === 403) { + console.error('Team not found or user not authorized. Returning to create view.'); + setView('create'); + clearInterval(interval); + return; + } + throw new Error(`Failed to fetch team status: ${response.status}`); + } + const memberStatuses: { team_id: number, status: string, members_ready: number[] } = await response.json(); const readyMemberIds = new Set(memberStatuses.members_ready); + // Per user request, if our ID is not in the list from the server, return to create view. + if (myId && !readyMemberIds.has(Number(myId))) { + console.log('User ID not in members_ready list. Returning to create view.', myId, readyMemberIds); + setView('create'); + clearInterval(interval); + return; + } + setTeamMembers(prevTeamMembers => prevTeamMembers.map(member => ({ ...member, @@ -236,11 +254,13 @@ export default function Page2() { ); } catch (error) { console.error('Error polling team status:', error); + setView('create'); + clearInterval(interval); } }, 2000); return () => clearInterval(interval); - }, [view, teamId]); + }, [view, teamId, myId, setView]); useEffect(() => { if (view === 'waiting' && teamMembers.length > 0 && teamMembers.every(m => m.is_ready)) { From 5e9986eaa4a738e128f7e707e34cb738af4e0c72 Mon Sep 17 00:00:00 2001 From: daclouds Date: Fri, 2 Jan 2026 23:21:49 +0900 Subject: [PATCH 05/66] feat(getcloser): change image when redeemed --- getcloser/frontend/public/redeemed-gift.svg | 15 +++++++++++++++ getcloser/frontend/src/app/pages/Page4.tsx | 13 ++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 getcloser/frontend/public/redeemed-gift.svg diff --git a/getcloser/frontend/public/redeemed-gift.svg b/getcloser/frontend/public/redeemed-gift.svg new file mode 100644 index 0000000..92f2e46 --- /dev/null +++ b/getcloser/frontend/public/redeemed-gift.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + 수령 완료 + diff --git a/getcloser/frontend/src/app/pages/Page4.tsx b/getcloser/frontend/src/app/pages/Page4.tsx index 1c4e740..461947f 100644 --- a/getcloser/frontend/src/app/pages/Page4.tsx +++ b/getcloser/frontend/src/app/pages/Page4.tsx @@ -43,11 +43,16 @@ export default function Page4() { const [lastClickTime, setLastClickTime] = useState(0); const [selectedMemberChallenge, setSelectedMemberChallenge] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); + const [isRedeemed, setIsRedeemed] = useState(false); // This useEffect will set the initial success/failure based on progressStatus from the store useEffect(() => { - if (progressStatus === 'CHALLENGE_SUCCESS' || progressStatus === 'REDEEMED') { + if (progressStatus === 'CHALLENGE_SUCCESS') { setResult('성공'); + setIsRedeemed(false); + } else if (progressStatus === 'REDEEMED') { + setResult('성공'); + setIsRedeemed(true); } else if (progressStatus === 'CHALLENGE_FAILED') { setResult('실패'); } else { @@ -90,6 +95,8 @@ export default function Page4() { }; const handleSuccessClick = async () => { + if (isRedeemed) return; + const currentTime = new Date().getTime(); if (currentTime - lastClickTime < 2000) { // 2 seconds window setClickCount(prevCount => prevCount + 1); @@ -115,7 +122,7 @@ export default function Page4() { } alert('수령 완료!'); - + setIsRedeemed(true); } }; @@ -154,7 +161,7 @@ export default function Page4() { <>

성공!

Success Date: Mon, 5 Jan 2026 21:33:21 +0900 Subject: [PATCH 06/66] refactor(getcloser): unconfirmed members from receiving TEAM_WAITING status --- getcloser/backend/app/scripts/user_challenge_status.py | 3 ++- getcloser/backend/app/services/user_service.py | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/getcloser/backend/app/scripts/user_challenge_status.py b/getcloser/backend/app/scripts/user_challenge_status.py index 65a8d9e..0d542d1 100644 --- a/getcloser/backend/app/scripts/user_challenge_status.py +++ b/getcloser/backend/app/scripts/user_challenge_status.py @@ -12,7 +12,8 @@ def seed_users_from_csv(file_path: str): reader = csv.DictReader(csvfile) for row in reader: user_challenge_status = UserChallengeStatus( - user_id=row["user_id"], + id=int(row["id"]), + user_id=int(row["user_id"]), is_correct=bool(row["is_correct"]), submitted_at=row["submitted_at"], is_redeemed=bool(row["is_redeemed"]), diff --git a/getcloser/backend/app/services/user_service.py b/getcloser/backend/app/services/user_service.py index ed0b59a..ade6959 100644 --- a/getcloser/backend/app/services/user_service.py +++ b/getcloser/backend/app/services/user_service.py @@ -49,9 +49,15 @@ def progress_status( if not team_member: return None, None, ProgressStatus.NONE_TEAM + + if not team_member.confirmed: + return None, None, ProgressStatus.NONE_TEAM team = db.query(Team).get(team_member.team_id) + if not team: + return None, None, ProgressStatus.NONE_TEAM + # 2. 팀 상태 if team.status == TeamStatus.PENDING: return team.id, None, ProgressStatus.TEAM_WAITING From d5cbc75271553f997d2317d3effd53389581ed99 Mon Sep 17 00:00:00 2001 From: HanNayeoniee Date: Mon, 5 Jan 2026 21:58:02 +0900 Subject: [PATCH 07/66] refactor(getcloser): add dummy data --- getcloser/backend/app/scripts/seed.py | 2 ++ getcloser/backend/app/scripts/user_challenge_status.py | 5 +---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/getcloser/backend/app/scripts/seed.py b/getcloser/backend/app/scripts/seed.py index acc5d2c..4511fb7 100644 --- a/getcloser/backend/app/scripts/seed.py +++ b/getcloser/backend/app/scripts/seed.py @@ -2,6 +2,7 @@ from scripts.users import seed_users_from_csv from scripts.challenge_question import seed_challenge_questions_from_csv +from scripts.user_challenge_status import seed_challenges_from_csv BASE_DIR = Path(__file__).resolve().parent @@ -12,3 +13,4 @@ def seed_initial_data(): """ seed_users_from_csv(str(BASE_DIR / "user_data.csv")) seed_challenge_questions_from_csv(str(BASE_DIR / "challenge_question.csv")) + seed_challenges_from_csv(str(BASE_DIR / "challenge_data.csv")) diff --git a/getcloser/backend/app/scripts/user_challenge_status.py b/getcloser/backend/app/scripts/user_challenge_status.py index 0d542d1..477490c 100644 --- a/getcloser/backend/app/scripts/user_challenge_status.py +++ b/getcloser/backend/app/scripts/user_challenge_status.py @@ -3,7 +3,7 @@ from core.database import SessionLocal, engine, Base from models.challenges import UserChallengeStatus -def seed_users_from_csv(file_path: str): +def seed_challenges_from_csv(file_path: str): Base.metadata.create_all(bind=engine) db: Session = SessionLocal() @@ -28,6 +28,3 @@ def seed_users_from_csv(file_path: str): print("❌ Error while seeding data:", e) finally: db.close() - -if __name__ == "__main__": - seed_users_from_csv("challenge_data.csv") From 6624cb878b611d3e0c5dc81c7cf4dea115378204 Mon Sep 17 00:00:00 2001 From: daclouds Date: Mon, 5 Jan 2026 22:07:11 +0900 Subject: [PATCH 08/66] feat(getcloser): implement try again --- getcloser/frontend/src/app/pages/Page4.tsx | 53 +++++++++++++++++++--- getcloser/frontend/src/store/formStore.ts | 20 ++++++-- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/getcloser/frontend/src/app/pages/Page4.tsx b/getcloser/frontend/src/app/pages/Page4.tsx index 461947f..3bbd98d 100644 --- a/getcloser/frontend/src/app/pages/Page4.tsx +++ b/getcloser/frontend/src/app/pages/Page4.tsx @@ -33,8 +33,20 @@ interface ChallengeResult { is_correct: boolean; } +const getJsonFromResponse = async (response: Response) => { + try { + const text = await response.text(); + if (text) { + return JSON.parse(text); + } + } catch (e) { + console.error('Failed to parse response JSON', e); + } + return { detail: response.statusText || 'An unknown error occurred' }; +}; + export default function Page4() { - const { id, progressStatus } = useFormStore(); // Use progressStatus + const { id, progressStatus, teamId, memberIds, reset, setQuestion, setChallengeId } = useFormStore(); // Use progressStatus const { setCurrentPage } = useNavigationStore(); const [result, setResult] = useState(''); @@ -90,8 +102,38 @@ export default function Page4() { } }, [result]); // Depend on result - const handleTryAgain = () => { - setCurrentPage('page2'); + const handleTryAgain = async () => { + try { + // First, reset the form store to clear previous challenge data. + // This preserves the user session while clearing challenge-specific state. + reset(); + + // Call assign API to get a new challenge + const assignResponse = await authenticatedFetch('/api/v1/challenges/assign', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ team_id: teamId, my_id: id, members_ids: memberIds.filter((memberId) => memberId !== Number(id)) }), + }); + + if (!assignResponse.ok) { + const errorData = await getJsonFromResponse(assignResponse); + throw new Error(`Failed to assign new challenge: ${errorData.detail}`); + } + + const newChallengeData = await assignResponse.json(); + console.log('Successfully assigned new challenge:', newChallengeData); + + // Navigate to page3 to start the new challenge. Page3's useEffect will handle populating the question. + setCurrentPage('page3'); + } catch (error: unknown) { + console.error('Error trying again:', error); + let errorMessage = '새로운 챌린지를 할당하는 데 실패했습니다.'; + if (error instanceof Error) { + errorMessage = error.message; + } + alert(`재도전 실패: ${errorMessage}`); + setCurrentPage('page2'); // Fallback to page2 if something goes wrong + } }; const handleSuccessClick = async () => { @@ -176,11 +218,10 @@ export default function Page4() {

우리 팀원들

{teamData.members - .filter(member => member.id !== id) // Filter out current user's info + .filter(member => member.user_id !== id) // Filter out current user's info, and use correct id .map((member) => ( -
+
handleMemberClick(member.user_id)}>

{member.name}

-

Email: {member.email}

{member.github_url && (

GitHub: {member.github_url} diff --git a/getcloser/frontend/src/store/formStore.ts b/getcloser/frontend/src/store/formStore.ts index ad8a461..f822206 100644 --- a/getcloser/frontend/src/store/formStore.ts +++ b/getcloser/frontend/src/store/formStore.ts @@ -40,7 +40,7 @@ const initialState = { export const useFormStore = create()( persist( - (set) => ({ + (set, get) => ({ ...initialState, setEmail: (email) => set({ email }), setId: (id) => set({ id }), @@ -50,9 +50,23 @@ export const useFormStore = create()( setAnswer: (answer) => set({ answer }), setTeamId: (teamId) => set({ teamId }), setMemberIds: (memberIds) => set({ memberIds }), - setIsCorrect: (isCorrect) => set({ isCorrect }), + setIsCorrect: (isCorrect) => + set({ + isCorrect, + progressStatus: isCorrect ? 'CHALLENGE_SUCCESS' : 'CHALLENGE_FAILED', + }), setProgressStatus: (progressStatus) => set({ progressStatus }), - reset: () => set(initialState), + reset: () => { + const { email, id, accessToken, teamId, memberIds } = get(); + set({ + ...initialState, + email, + id, + accessToken, + teamId, + memberIds, + }); + }, }), { name: 'form-storage', // name of the item in the storage (must be unique) From ca1a8d51594df3c1ffe2e2137d6df3f9a75e6f5e Mon Sep 17 00:00:00 2001 From: HanNayeoniee Date: Mon, 5 Jan 2026 22:32:30 +0900 Subject: [PATCH 09/66] fix(getcloser): autoincrement id --- getcloser/backend/app/models/challenges.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/getcloser/backend/app/models/challenges.py b/getcloser/backend/app/models/challenges.py index 5570462..0ef02f1 100644 --- a/getcloser/backend/app/models/challenges.py +++ b/getcloser/backend/app/models/challenges.py @@ -6,7 +6,7 @@ class UserChallengeStatus(Base): __tablename__ = "user_challenge_status" - id = Column(Integer, primary_key=True, index=True) + id = Column(Integer, primary_key=True, index=True, autoincrement=True) user_id = Column(Integer, index=True) challenge_id = Column(Integer, ForeignKey("challenge_questions.id"), nullable=True) From 88e7f7eef89bda7bb22dcd4261419118b19aa9a3 Mon Sep 17 00:00:00 2001 From: hu6r1s Date: Mon, 5 Jan 2026 22:45:40 +0900 Subject: [PATCH 10/66] refactor(getcloser): get team member challenge answer info --- getcloser/backend/app/services/team_service.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/getcloser/backend/app/services/team_service.py b/getcloser/backend/app/services/team_service.py index 0d956a1..64afd53 100644 --- a/getcloser/backend/app/services/team_service.py +++ b/getcloser/backend/app/services/team_service.py @@ -266,8 +266,11 @@ def get_team_member_challenge( record = ( db.query(UserChallengeStatus) .join(ChallengeQuestion, ChallengeQuestion.id == UserChallengeStatus.challenge_id) - .filter(UserChallengeStatus.user_id == user_id) - .order_by(UserChallengeStatus.created_at.desc()) + .filter( + UserChallengeStatus.user_id == user_id, + UserChallengeStatus.is_correct.is_(True) + ) + .order_by(UserChallengeStatus.id.desc()) .first() ) @@ -276,8 +279,7 @@ def get_team_member_challenge( return MemberChallengeResponse( user_id=user_id, - question=record.challenge.question, - user_answer=record.answer, + question=record.challenge.category, correct_answer=record.challenge.answer, is_correct=record.is_correct ) From 8558e73f6ec92e3818ed3dcb39320c3a46468483 Mon Sep 17 00:00:00 2001 From: HanNayeoniee Date: Mon, 5 Jan 2026 23:13:11 +0900 Subject: [PATCH 11/66] fix(getcloser): disable challenge dummy data --- getcloser/backend/app/scripts/seed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/getcloser/backend/app/scripts/seed.py b/getcloser/backend/app/scripts/seed.py index 4511fb7..323f727 100644 --- a/getcloser/backend/app/scripts/seed.py +++ b/getcloser/backend/app/scripts/seed.py @@ -13,4 +13,4 @@ def seed_initial_data(): """ seed_users_from_csv(str(BASE_DIR / "user_data.csv")) seed_challenge_questions_from_csv(str(BASE_DIR / "challenge_question.csv")) - seed_challenges_from_csv(str(BASE_DIR / "challenge_data.csv")) + # seed_challenges_from_csv(str(BASE_DIR / "challenge_data.csv")) From b412f6049e09a38aaa9f085b88d9edc30c0aa99c Mon Sep 17 00:00:00 2001 From: daclouds Date: Mon, 5 Jan 2026 23:40:46 +0900 Subject: [PATCH 12/66] feat(getcloser): short answers --- getcloser/frontend/src/app/pages/Page3.tsx | 70 ++++++++++++++-------- getcloser/frontend/src/app/pages/Page4.tsx | 16 ++++- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/getcloser/frontend/src/app/pages/Page3.tsx b/getcloser/frontend/src/app/pages/Page3.tsx index 9a86f38..0ed4802 100644 --- a/getcloser/frontend/src/app/pages/Page3.tsx +++ b/getcloser/frontend/src/app/pages/Page3.tsx @@ -1,9 +1,7 @@ 'use client'; import { Button } from '@/components/ui/button'; -import { Label } from '@/components/ui/label'; -import { Textarea } from '@/components/ui/textarea'; -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { useFormStore } from '../../store/formStore'; import { authenticatedFetch } from '../../lib/api'; import { useNavigationStore } from '../../store/navigationStore'; @@ -18,11 +16,18 @@ interface TeamMember { } const questions = [ - { category: '1', keyword: '관심사', problem: '사용자의 관심사를 맞춰주세요. 예: 기술, 예술, 환경 등' }, - { category: '2', keyword: '취미', problem: '사용자의 취미를 맞춰주세요. 예: 등산, 독서, 요리 등' }, - { category: '3', keyword: 'MBTI', problem: '사용자의 MBTI 유형을 맞춰주세요. 예: INFP, ESTJ 등' }, + { category: '1', keyword: '관심사', problem: '님의 관심사를 맞춰주세요.', options: [ 'Agentic AI', 'AI Ethics', 'AI Security', 'Causal Inference', 'Computer Graphics', 'Computer Vision', 'Efficient AI', 'LLM/Multimodal', 'Physical AI', 'XAI' ]}, + { category: '2', keyword: '계절', problem: '님이 좋아하는 계절을 맞춰주세요.', options: ['봄', '여름', '가을', '겨울'] }, + { category: '3', keyword: 'MBTI', problem: '님의 MBTI 유형을 맞춰주세요.', options: ['INFP', 'ENFP', 'INFJ', 'ENFJ', 'INTJ', 'ENTJ', 'INTP', 'ENTP', 'ISFP', 'ESFP', 'ISTP', 'ESTP', 'ISFJ', 'ESFJ', 'ISTJ', 'ESTJ'] }, ]; +interface QuestionInfo { + category: string; + keyword: string; + problem: string; + options: string[]; +} + const getJsonFromResponse = async (response: Response) => { try { const text = await response.text(); @@ -36,13 +41,21 @@ const getJsonFromResponse = async (response: Response) => { }; export default function Page3() { - const { question, answer, challengeId, setAnswer, id, teamId, memberIds, setQuestion, setChallengeId, setIsCorrect, reset } = useFormStore(); // Destructure new state + const { question, answer, challengeId, setAnswer, id, teamId, memberIds, setQuestion, setChallengeId, setProgressStatus, reset } = useFormStore(); // Destructure new state const { setCurrentPage } = useNavigationStore(); + const [currentQuestionInfo, setCurrentQuestionInfo] = useState(null); useEffect(() => { const initializeChallenge = async () => { // If a question is already loaded, do nothing. if (question) { + // Still need to set the question info for rendering options + if (!currentQuestionInfo) { + const loadedQuestionCategory = questions.find(q => question.includes(q.problem)); + if(loadedQuestionCategory) { + setCurrentQuestionInfo(loadedQuestionCategory as QuestionInfo); // Cast to QuestionInfo + } + } console.log('Question already exists, skipping initialization.'); return; } @@ -102,6 +115,7 @@ export default function Page3() { const memberName = findMemberName(userId); setQuestion([memberName, questionInfo.problem].join(' ')); setChallengeId(assigned_challenge_id); + setCurrentQuestionInfo(questionInfo as QuestionInfo); // Cast to QuestionInfo } else { throw new Error(`Could not find question for category: ${category}`); } @@ -120,15 +134,13 @@ export default function Page3() { initializeChallenge(); // Added challengeId to dependency array to react to changes if needed, // though the main trigger is the absence of `question`. - }, [id, teamId, question, memberIds, setQuestion, setChallengeId, reset, setCurrentPage, challengeId]); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); + }, [id, teamId, question, memberIds, setQuestion, setChallengeId, reset, setCurrentPage, challengeId, currentQuestionInfo]); + const submitAnswer = async (submittedAnswer: string) => { const requestBody = { user_id: id, challenge_id: challengeId, - submitted_answer: answer, + submitted_answer: submittedAnswer, }; try { @@ -147,7 +159,7 @@ export default function Page3() { const responseData = await response.json(); console.log('Challenge submission successful:', responseData); - setIsCorrect(responseData.is_correct); + setProgressStatus(responseData.is_correct); setCurrentPage('page4'); } catch (error: unknown) { console.error('Error submitting challenge:', error); @@ -159,25 +171,31 @@ export default function Page3() { } }; + const handleOptionClick = async (option: string) => { + setAnswer(option); // Update the store + await submitAnswer(option); // Submit the answer immediately + }; + return (

질문:

{question || '질문이 입력되지 않았습니다.'}

-
-
- -