diff --git a/backend/app/crud/__init__.py b/backend/app/crud/__init__.py index 1a280d17d74e2db5b9235f927249906d9d737760..69ccf364cfcad47854f4841912cc47f8d4c85a19 100644 --- a/backend/app/crud/__init__.py +++ b/backend/app/crud/__init__.py @@ -12,7 +12,9 @@ from crud.studies import * def get_user(db: Session, user_id: int): - return db.query(models.User).filter(models.User.id == user_id).first() + user = db.query(models.User).filter(models.User.id == user_id).first() + + return user def get_user_by_email(db: Session, email: str): diff --git a/backend/app/crud/studies.py b/backend/app/crud/studies.py index ce4923c55c3566c4a7ad3b2df2b4c87737ad012f..044f84ba26c366dd9c89a44bb8bd629d3e5f8168 100644 --- a/backend/app/crud/studies.py +++ b/backend/app/crud/studies.py @@ -254,3 +254,9 @@ def create_study_info( db.commit() db.refresh(db_study_info) return db_study_info + + +def add_user_to_study(db: Session, study: models.Study, user: models.User): + study.users.append(user) + db.commit() + db.refresh(study) diff --git a/backend/app/main.py b/backend/app/main.py index ac9c7b2918fa444aaa85a7fca0091552beb2b841..8aee55ca3f218e73b1ba5419e992dc095099a514 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -128,6 +128,13 @@ def register( user = crud.create_user(db=db, user=user_data) + if register.study_id: + study = crud.get_study(db, register.study_id) + if study is None: + raise HTTPException(status_code=404, detail="Study not found") + + crud.add_user_to_study(db, study, user) + return user.id diff --git a/backend/app/routes/studies.py b/backend/app/routes/studies.py index d4ce1f466dca0b03771aa8b917440fd2f46b9fc8..5e0b791320b82fe5fd24dae21aa29b9589c2fb5b 100644 --- a/backend/app/routes/studies.py +++ b/backend/app/routes/studies.py @@ -92,3 +92,27 @@ def create_study_info( db: Session = Depends(get_db), ): return crud.create_study_info(db, study_id, study_info) + + +@studiesRouter.post("/{study_id}/users/{user_id}", status_code=status.HTTP_201_CREATED) +def add_user_to_study( + study_id: int, + user_id: int, + current_user: schemas.User, + db: Session = Depends(get_db), +): + if current_user.id != user_id and current_user.type != schemas.UserType.ADMIN: + raise HTTPException( + status_code=403, + detail="You do not have permission to add a user to this study.", + ) + + study = crud.get_study(db, study_id) + if study is None: + raise HTTPException(status_code=404, detail="Study not found") + + user = crud.get_user(db, user_id) + if user is None: + raise HTTPException(status_code=404, detail="User not found") + + crud.add_user_to_study(db, study, user) diff --git a/backend/app/schemas/__init__.py b/backend/app/schemas/__init__.py index f8dbff79fa40cb009ea682bec71c6882d439ab38..cfc6318044d0b78b7fe60a9ea8dc68cc7fdba32f 100644 --- a/backend/app/schemas/__init__.py +++ b/backend/app/schemas/__init__.py @@ -15,6 +15,7 @@ class RegisterData(BaseModel): password: str nickname: str is_tutor: bool + study_id: int | None = None class ContactCreate(BaseModel): diff --git a/backend/app/schemas/users.py b/backend/app/schemas/users.py index ff07d5df38b7d8f29aa0ecbaf068250eb941e728..b48075f7cec48d0a83f6eb594602a510fa35907a 100644 --- a/backend/app/schemas/users.py +++ b/backend/app/schemas/users.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel, NaiveDatetime +from pydantic import BaseModel, NaiveDatetime, model_validator from models import UserType @@ -21,6 +21,16 @@ class User(BaseModel): tutor_list: list[str] | None = [] my_tutor: str | None = None my_slots: list[dict] | None = [] + studies_id: list[int] = [] + + @model_validator(mode="before") + @classmethod + def add_studies_id(cls, data): + if hasattr(data, "__dict__"): + data.studies_id = [] + if hasattr(data, "studies") and data.studies: + data.studies_id = [study.id for study in data.studies] + return data class Config: from_attributes = True @@ -37,6 +47,7 @@ class User(BaseModel): "home_language": self.home_language, "target_language": self.target_language, "birthdate": self.birthdate.isoformat() if self.birthdate else None, + "studies_id": self.studies_id, } diff --git a/frontend/src/lib/types/user.ts b/frontend/src/lib/types/user.ts index b1acf04ec6dda0e0b07676c98a78f9713994b444..906d98667918f45fd8a268b76446186c84d30baf 100644 --- a/frontend/src/lib/types/user.ts +++ b/frontend/src/lib/types/user.ts @@ -32,7 +32,7 @@ export default class User { private _gender: string | null; private _bio: string | null; private _calcom_link: string | null; - private _study_id: number | null; + private _studies_id: number[]; private _last_survey: Date | null; private _tutor_list: string[]; private _my_tutor: string | null; @@ -54,7 +54,7 @@ export default class User { birthdate: Date | null, gender: string | null, calcom_link: string | null, - study_id: number | null, + studies_id: number[] | null, last_survey: Date | null, tutor_list: string[] = [], my_tutor: string | null = null, @@ -73,7 +73,7 @@ export default class User { this._birthdate = birthdate; this._gender = gender; this._calcom_link = calcom_link; - this._study_id = study_id; + this._studies_id = studies_id || []; this._last_survey = last_survey; this._tutor_list = tutor_list; this._my_tutor = my_tutor; @@ -142,8 +142,8 @@ export default class User { return this._calcom_link; } - get study_id(): number | null { - return this._study_id; + get studies_id(): number[] { + return this._studies_id; } get last_survey(): Date | null { @@ -214,7 +214,7 @@ export default class User { birthdate: this.birthdate, gender: this.gender, calcom_link: this.calcom_link, - study_id: this.study_id, + studies_id: this.studies_id, last_survey: this.last_survey, tutor_list: this._tutor_list, my_tutor: this.my_tutor, @@ -237,7 +237,7 @@ export default class User { if (data.birthdate) this._birthdate = data.birthdate; if (data.gender) this._gender = data.gender; if (data.calcum_link) this._calcom_link = data.calcom_link; - if (data.study_id) this._study_id = data.study_id; + if (data.studies_id) this._studies_id = data.studies_id; if (data.last_survey) this._last_survey = data.last_survey; if (data.tutor_list) this._tutor_list = data.tutor_list; if (data.my_tutor) this._my_tutor = data.my_tutor; @@ -355,7 +355,7 @@ export default class User { json.birthdate, json.gender, json.calcom_link, - json.study_id, + json.studies_id, json.last_survey === null ? null : parseToLocalDate(json.last_survey), json.tutor_list || [], json.my_tutor, diff --git a/frontend/src/routes/register/[[studyId]]/+page.server.ts b/frontend/src/routes/register/[[studyId]]/+page.server.ts index 1649973a623d462a8426fabf0d894355a57496e8..5ed5f15614bd107fc36cbea07562e928a45db735 100644 --- a/frontend/src/routes/register/[[studyId]]/+page.server.ts +++ b/frontend/src/routes/register/[[studyId]]/+page.server.ts @@ -1,5 +1,5 @@ import { addUserToStudyAPI } from '$lib/api/studies'; -import { patchUserAPI, getUsersAPI } from '$lib/api/users'; +import { patchUserAPI } from '$lib/api/users'; import { formatToUTCDate } from '$lib/utils/date'; import { validateEmail, validatePassword, validateUsername } from '$lib/utils/security'; import { redirect, type Actions } from '@sveltejs/kit'; @@ -7,8 +7,10 @@ import { redirect, type Actions } from '@sveltejs/kit'; export const actions: Actions = { register: async ({ request, fetch, params }) => { const formData = await request.formData(); - const studyId = params.studyId; - if (!studyId) return { message: 'Invalid request' }; + const study_idStr = params.studyId; + if (!study_idStr) return { message: 'Invalid request' }; + const study_id = parseInt(study_idStr); + if (isNaN(study_id)) return { message: 'Invalid request' }; const email = formData.get('email'); const nickname = formData.get('nickname'); @@ -33,7 +35,7 @@ export const actions: Actions = { 'Content-Type': 'application/json' }, method: 'POST', - body: JSON.stringify({ email, nickname, password, is_tutor }) + body: JSON.stringify({ email, nickname, password, is_tutor, study_id }) }); if (response.status === 400) return { message: 'User already exists' }; @@ -53,7 +55,7 @@ export const actions: Actions = { if (response.status === 422) return { message: 'Invalid request' }; if (!response.ok) return { message: 'Unknown error occurred' }; - return redirect(303, `/register/${studyId}`); + return redirect(303, `/register/${study_id}`); }, data: async ({ request, fetch, locals }) => { if (!locals.user) { @@ -66,7 +68,6 @@ export const actions: Actions = { const targetLanguage = formData.get('targetLanguage'); const birthyear = formData.get('birthyear'); const gender = formData.get('gender'); - const study = formData.get('study'); const bio = formData.get('bio'); let my_tutor = formData.get('myTutor'); @@ -86,7 +87,7 @@ export const actions: Actions = { return { message: 'Invalid request' }; } - const response = await patchUserAPI(fetch, locals.user.id, { + let response = await patchUserAPI(fetch, locals.user.id, { home_language: homeLanguage, target_language: targetLanguage, gender, diff --git a/frontend/src/routes/register/[[studyId]]/+page.svelte b/frontend/src/routes/register/[[studyId]]/+page.svelte index 80c8075332268e97e2c59f2689f0f55d3b7a6383..54af1bc72be7cc3a137506c677501cd31aa76ebb 100644 --- a/frontend/src/routes/register/[[studyId]]/+page.svelte +++ b/frontend/src/routes/register/[[studyId]]/+page.svelte @@ -4,14 +4,13 @@ import { displayDate } from '$lib/utils/date'; import { t } from '$lib/services/i18n'; import { Icon, Envelope, Key, UserCircle } from 'svelte-hero-icons'; - import Typingtest from '$lib/components/tests/typingtest.svelte'; import { browser } from '$app/environment'; import type { PageData } from './$types'; import Consent from '$lib/components/surveys/consent.svelte'; import type Study from '$lib/types/study'; let { data, form }: { data: PageData; form: FormData } = $props(); - let study: Study | undefined = $state(data.study); + let study: Study | undefined | null = $state(data.study); let studies: Study[] | undefined = $state(data.studies); let user = $state(data.user); let message = $state(''); @@ -612,33 +611,15 @@ </div> {:else if current_step == 7} <div class="text-center"> - <p class="text-center"> - {@html $t('register.continue')} - </p> - <button class="button mt-4 w-full" onclick={() => (current_step = 8)}> + <a class="button mt-4 w-full" href="/studies/{study?.id || user?.studies_id[0]}"> {$t('register.continueButton')} - </button> + </a> <button class="button mt-4 w-full" onclick={() => (document.location.href = '/')}> {$t('register.startFastButton')} </button> </div> - {:else if current_step == 8} - <div class="text-center"> - <p class="text-center"> - {@html $t('register.start')} - </p> - <button - class="button mt-4 m-auto" - onclick={() => (document.location.href = `/studies/${study?.id}`)} - > - {$t('register.continueButton')} - </button> - </div> - {:else if current_step == 9} + {:else if current_step == 8}{:else if current_step == 9} <div class="text-center"> - <p class="text-center"> - {@html $t('register.start')} - </p> <button class="button mt-4 m-auto" onclick={() => (document.location.href = '/')}> {$t('register.startButton')} </button> diff --git a/frontend/src/routes/register/[[studyId]]/+page.ts b/frontend/src/routes/register/[[studyId]]/+page.ts index 9f2b5121e4b5fe14bef56b66afbbda8fecd0d225..18f58b7a38438fabd7c347bbaf65c7061a0ea781 100644 --- a/frontend/src/routes/register/[[studyId]]/+page.ts +++ b/frontend/src/routes/register/[[studyId]]/+page.ts @@ -5,6 +5,7 @@ import type { Load } from '@sveltejs/kit'; export const load: Load = async ({ parent, fetch, params }) => { const { user } = await parent(); + console.log(user); const sStudyId: string | undefined = params.studyId; let study = null; @@ -13,12 +14,18 @@ export const load: Load = async ({ parent, fetch, params }) => { if (studyId) { study = Study.parse(await getStudyAPI(fetch, studyId)); } + } else if (user) { + const studyId = user.studies_id[0]; + if (studyId) { + study = Study.parse(await getStudyAPI(fetch, studyId)); + } } const studies = Study.parseAll(await getStudiesAPI(fetch)); const users = await getUsersAPI(fetch); const tutors = users.filter((user) => user.type === 1); + return { studyError: !study, study,