diff --git a/frontend/src/lib/client/schemas.gen.ts b/frontend/src/lib/client/schemas.gen.ts index 3e89f2b6..31d4036e 100644 --- a/frontend/src/lib/client/schemas.gen.ts +++ b/frontend/src/lib/client/schemas.gen.ts @@ -163,14 +163,10 @@ export const ChildCreateSchema = { birth_month: { type: 'integer', title: 'Birth Month' - }, - has_image: { - type: 'boolean', - title: 'Has Image' } }, type: 'object', - required: ['birth_year', 'birth_month', 'has_image'], + required: ['birth_year', 'birth_month'], title: 'ChildCreate' } as const; @@ -189,17 +185,17 @@ export const ChildPublicSchema = { type: 'integer', title: 'Birth Month' }, - has_image: { - type: 'boolean', - title: 'Has Image' - }, id: { type: 'integer', title: 'Id' + }, + has_image: { + type: 'boolean', + title: 'Has Image' } }, type: 'object', - required: ['birth_year', 'birth_month', 'has_image', 'id'], + required: ['birth_year', 'birth_month', 'id', 'has_image'], title: 'ChildPublic' } as const; @@ -312,6 +308,46 @@ export const MilestoneAdminSchema = { title: 'MilestoneAdmin' } as const; +export const MilestoneAnswerPublicSchema = { + properties: { + milestone_id: { + type: 'integer', + title: 'Milestone Id' + }, + answer: { + type: 'integer', + title: 'Answer' + } + }, + type: 'object', + required: ['milestone_id', 'answer'], + title: 'MilestoneAnswerPublic' +} as const; + +export const MilestoneAnswerSessionPublicSchema = { + properties: { + id: { + type: 'integer', + title: 'Id' + }, + created_at: { + type: 'string', + format: 'date-time', + title: 'Created At' + }, + answers: { + additionalProperties: { + $ref: '#/components/schemas/MilestoneAnswerPublic' + }, + type: 'object', + title: 'Answers' + } + }, + type: 'object', + required: ['id', 'created_at', 'answers'], + title: 'MilestoneAnswerSessionPublic' +} as const; + export const MilestoneGroupAdminSchema = { properties: { id: { diff --git a/frontend/src/lib/client/services.gen.ts b/frontend/src/lib/client/services.gen.ts index 69301e78..b307ca6c 100644 --- a/frontend/src/lib/client/services.gen.ts +++ b/frontend/src/lib/client/services.gen.ts @@ -82,12 +82,12 @@ import type { UsersDeleteUserResponse, GetChildrenError, GetChildrenResponse, - CreateChildData, - CreateChildError, - CreateChildResponse, UpdateChildData, UpdateChildError, UpdateChildResponse, + CreateChildData, + CreateChildError, + CreateChildResponse, DeleteChildData, DeleteChildError, DeleteChildResponse, @@ -97,6 +97,12 @@ import type { UploadChildImageData, UploadChildImageError, UploadChildImageResponse, + GetCurrentMilestoneAnswerSessionData, + GetCurrentMilestoneAnswerSessionError, + GetCurrentMilestoneAnswerSessionResponse, + UpdateMilestoneAnswerData, + UpdateMilestoneAnswerError, + UpdateMilestoneAnswerResponse, AuthCookieLoginData, AuthCookieLoginError, AuthCookieLoginResponse, @@ -558,26 +564,26 @@ export const getChildren = ( }; /** - * Create Child + * Update Child */ -export const createChild = ( - options: Options +export const updateChild = ( + options: Options ) => { - return (options?.client ?? client).post({ + return (options?.client ?? client).put({ ...options, url: '/users/children/' }); }; /** - * Update Child + * Create Child */ -export const updateChild = ( - options: Options +export const createChild = ( + options: Options ) => { - return (options?.client ?? client).put({ + return (options?.client ?? client).post({ ...options, - url: '/users/children' + url: '/users/children/' }); }; @@ -626,6 +632,38 @@ export const uploadChildImage = ( }); }; +/** + * Get Current Milestone Answer Session + */ +export const getCurrentMilestoneAnswerSession = ( + options: Options +) => { + return (options?.client ?? client).get< + GetCurrentMilestoneAnswerSessionResponse, + GetCurrentMilestoneAnswerSessionError, + ThrowOnError + >({ + ...options, + url: '/users/milestone-answers/{child_id}' + }); +}; + +/** + * Update Milestone Answer + */ +export const updateMilestoneAnswer = ( + options: Options +) => { + return (options?.client ?? client).put< + UpdateMilestoneAnswerResponse, + UpdateMilestoneAnswerError, + ThrowOnError + >({ + ...options, + url: '/users/milestone-answers/{milestone_answer_session_id}' + }); +}; + /** * Auth:Cookie.Login */ diff --git a/frontend/src/lib/client/types.gen.ts b/frontend/src/lib/client/types.gen.ts index 73e82cf3..5982f122 100644 --- a/frontend/src/lib/client/types.gen.ts +++ b/frontend/src/lib/client/types.gen.ts @@ -43,15 +43,14 @@ export type ChildCreate = { name?: string; birth_year: number; birth_month: number; - has_image: boolean; }; export type ChildPublic = { name?: string; birth_year: number; birth_month: number; - has_image: boolean; id: number; + has_image: boolean; }; export type ErrorModel = { @@ -85,6 +84,19 @@ export type MilestoneAdmin = { images?: Array; }; +export type MilestoneAnswerPublic = { + milestone_id: number; + answer: number; +}; + +export type MilestoneAnswerSessionPublic = { + id: number; + created_at: string; + answers: { + [key: string]: MilestoneAnswerPublic; + }; +}; + export type MilestoneGroupAdmin = { id: number; order: number; @@ -434,14 +446,6 @@ export type GetChildrenResponse = Array; export type GetChildrenError = unknown; -export type CreateChildData = { - body: ChildCreate; -}; - -export type CreateChildResponse = ChildPublic; - -export type CreateChildError = HTTPValidationError; - export type UpdateChildData = { body: ChildPublic; }; @@ -450,6 +454,14 @@ export type UpdateChildResponse = ChildPublic; export type UpdateChildError = HTTPValidationError; +export type CreateChildData = { + body: ChildCreate; +}; + +export type CreateChildResponse = ChildPublic; + +export type CreateChildError = HTTPValidationError; + export type DeleteChildData = { path: { child_id: number; @@ -481,6 +493,27 @@ export type UploadChildImageResponse = unknown; export type UploadChildImageError = HTTPValidationError; +export type GetCurrentMilestoneAnswerSessionData = { + path: { + child_id: number; + }; +}; + +export type GetCurrentMilestoneAnswerSessionResponse = MilestoneAnswerSessionPublic; + +export type GetCurrentMilestoneAnswerSessionError = HTTPValidationError; + +export type UpdateMilestoneAnswerData = { + body: MilestoneAnswerPublic; + path: { + milestone_answer_session_id: number; + }; +}; + +export type UpdateMilestoneAnswerResponse = MilestoneAnswerPublic; + +export type UpdateMilestoneAnswerError = HTTPValidationError; + export type AuthCookieLoginData = { body: Body_auth_cookie_login_auth_login_post; }; diff --git a/frontend/src/lib/components/Milestone.svelte b/frontend/src/lib/components/Milestone.svelte index 843ec0ff..4dd32ab5 100644 --- a/frontend/src/lib/components/Milestone.svelte +++ b/frontend/src/lib/components/Milestone.svelte @@ -17,20 +17,29 @@ ArrowRightOutline, ArrowLeftOutline } from 'flowbite-svelte-icons'; - import type { MilestoneGroupPublic, MilestonePublic } from '$lib/client/types.gen'; + import type { + MilestoneGroupPublic, + MilestonePublic, + MilestoneAnswerSessionPublic + } from '$lib/client/types.gen'; + import { updateMilestoneAnswer } from '$lib/client/services.gen'; import MilestoneButton from '$lib/components/MilestoneButton.svelte'; import { lang_id } from '$lib/stores/langStore'; import { _ } from 'svelte-i18n'; let { milestoneGroup = undefined, - milestoneAnswers = {} - }: { milestoneGroup?: MilestoneGroupPublic; milestoneAnswers?: Record } = - $props(); + milestoneAnswerSession = undefined + }: { + milestoneGroup?: MilestoneGroupPublic; + milestoneAnswerSession?: MilestoneAnswerSessionPublic; + } = $props(); let currentMilestoneIndex = $state(0); let currentMilestone = $state(undefined as MilestonePublic | undefined); - let selectedAnswer = $state(undefined as number | undefined); + let selectedAnswer = $derived( + milestoneAnswerSession?.answers?.[`${currentMilestone?.id}`]?.answer + ); let autoGoToNextMilestone = $state(false); let currentImageIndex = $state(0); @@ -38,13 +47,12 @@ console.log(milestoneGroup); if (milestoneGroup && milestoneGroup.milestones) { currentMilestone = milestoneGroup.milestones[currentMilestoneIndex]; - selectedAnswer = milestoneAnswers[currentMilestone.id]; } }); const imageInterval = 5000; setInterval(() => { - if (currentMilestone && currentMilestone.images) { + if (currentMilestone && currentMilestone.images && currentMilestone.images.length > 0) { currentImageIndex = (currentImageIndex + 1) % currentMilestone.images.length; } }, imageInterval); @@ -54,34 +62,54 @@ return; } currentMilestoneIndex -= 1; + currentImageIndex = 0; currentMilestone = milestoneGroup.milestones[currentMilestoneIndex]; - selectedAnswer = milestoneAnswers[currentMilestone.id]; } - function nextMilestone() { + async function nextMilestone() { if ( !milestoneGroup || !milestoneGroup.milestones || !currentMilestone || - selectedAnswer === undefined + selectedAnswer === undefined || + !milestoneAnswerSession ) { return; } - milestoneAnswers[currentMilestone.id] = selectedAnswer; - // todo: API call to submit answer - console.log(`TODO: submit answer ${selectedAnswer} for milestone ${currentMilestoneIndex}`); + const { data, error } = await updateMilestoneAnswer({ + body: { milestone_id: currentMilestone.id, answer: selectedAnswer }, + path: { + milestone_answer_session_id: milestoneAnswerSession.id + } + }); + if (error) { + console.log(error); + return; + } + milestoneAnswerSession.answers[`${currentMilestone.id}`] = data; if (currentMilestoneIndex + 1 == milestoneGroup.milestones.length) { console.log(`TODO: redirect to next milestone group or back to group overview`); // todo: redirect to bereichuebersicht? or go to next set of milestones? return; } currentMilestoneIndex += 1; + currentImageIndex = 0; currentMilestone = milestoneGroup.milestones[currentMilestoneIndex]; - selectedAnswer = milestoneAnswers[currentMilestone.id]; } function selectAnswer(answer: number | undefined) { - selectedAnswer = answer; + if (!currentMilestone) { + console.log('selectAnswer: missing currentMilestone'); + return; + } + if (!milestoneAnswerSession) { + console.log('selectAnswer: missing milestoneAnswerSession'); + return; + } + milestoneAnswerSession.answers[`${currentMilestone.id}`] = { + milestone_id: currentMilestone.id, + answer: answer + }; if (selectedAnswer !== undefined && autoGoToNextMilestone) { nextMilestone(); } diff --git a/frontend/src/routes/milestone/+page.svelte b/frontend/src/routes/milestone/+page.svelte index 0d774fd7..39c63ee7 100644 --- a/frontend/src/routes/milestone/+page.svelte +++ b/frontend/src/routes/milestone/+page.svelte @@ -2,23 +2,63 @@
{#key milestoneGroup} - + {#key milestoneAnswerSession} + + {/key} {/key}
diff --git a/mondey_backend/openapi.json b/mondey_backend/openapi.json index dc4df1ec..42b7d05c 100644 --- a/mondey_backend/openapi.json +++ b/mondey_backend/openapi.json @@ -1 +1 @@ -{"openapi": "3.1.0", "info": {"title": "MONDEY API", "version": "0.1.0"}, "paths": {"/languages/": {"get": {"tags": ["milestones"], "summary": "Get Languages", "operationId": "get_languages", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"additionalProperties": {"type": "integer"}, "type": "object", "title": "Response Get Languages Languages Get"}}}}}}}, "/milestones/": {"get": {"tags": ["milestones"], "summary": "Get Milestones", "operationId": "get_milestones", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/MilestonePublic"}, "type": "array", "title": "Response Get Milestones Milestones Get"}}}}}}}, "/milestones/{milestone_id}": {"get": {"tags": ["milestones"], "summary": "Get Milestone", "operationId": "get_milestone", "parameters": [{"name": "milestone_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestonePublic"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/milestone-groups/": {"get": {"tags": ["milestones"], "summary": "Get Milestone Groups", "operationId": "get_milestone_groups", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/MilestoneGroupPublic"}, "type": "array", "title": "Response Get Milestone Groups Milestone Groups Get"}}}}}}}, "/milestone-groups/{milestone_group_id}": {"get": {"tags": ["milestones"], "summary": "Get Milestone Group", "operationId": "get_milestone_group", "parameters": [{"name": "milestone_group_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Group Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneGroupPublic"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/user-questions/": {"get": {"tags": ["questions"], "summary": "Get User Questions", "operationId": "get_user_questions", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/UserQuestionPublic"}, "type": "array", "title": "Response Get User Questions User Questions Get"}}}}}}}, "/admin/languages/": {"post": {"tags": ["admin"], "summary": "Create Language", "operationId": "create_language", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LanguageCreate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Language"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/admin/languages/{language_id}": {"delete": {"tags": ["admin"], "summary": "Delete Language", "operationId": "delete_language", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "language_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Language Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/i18n/{language_id}": {"put": {"tags": ["admin"], "summary": "Update I18N", "operationId": "update_i18n", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "language_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Language Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": {"type": "object", "additionalProperties": {"type": "string"}}, "title": "I18Dict"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/milestone-groups/": {"get": {"tags": ["admin"], "summary": "Get Milestone Groups Admin", "operationId": "get_milestone_groups_admin", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/MilestoneGroupAdmin"}, "type": "array", "title": "Response Get Milestone Groups Admin Admin Milestone Groups Get"}}}}}, "security": [{"APIKeyCookie": []}]}, "post": {"tags": ["admin"], "summary": "Create Milestone Group Admin", "operationId": "create_milestone_group_admin", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneGroupAdmin"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/admin/milestone-groups": {"put": {"tags": ["admin"], "summary": "Update Milestone Group Admin", "operationId": "update_milestone_group_admin", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneGroupAdmin"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneGroupAdmin"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/admin/milestone-groups/{milestone_group_id}": {"delete": {"tags": ["admin"], "summary": "Delete Milestone Group Admin", "operationId": "delete_milestone_group_admin", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_group_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Group Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/milestone-group-images/{milestone_group_id}": {"put": {"tags": ["admin"], "summary": "Upload Milestone Group Image", "operationId": "upload_milestone_group_image", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_group_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Group Id"}}], "requestBody": {"required": true, "content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_upload_milestone_group_image_admin_milestone_group_images__milestone_group_id__put"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/milestones/{milestone_group_id}": {"post": {"tags": ["admin"], "summary": "Create Milestone", "operationId": "create_milestone", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_group_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Group Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneAdmin"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/milestones/": {"put": {"tags": ["admin"], "summary": "Update Milestone", "operationId": "update_milestone", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneAdmin"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneAdmin"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/admin/milestones/{milestone_id}": {"delete": {"tags": ["admin"], "summary": "Delete Milestone", "operationId": "delete_milestone", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/milestone-images/{milestone_id}": {"post": {"tags": ["admin"], "summary": "Upload Milestone Image", "operationId": "upload_milestone_image", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Id"}}], "requestBody": {"required": true, "content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_upload_milestone_image_admin_milestone_images__milestone_id__post"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneImage"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/user-questions/": {"get": {"tags": ["admin"], "summary": "Get User Questions Admin", "operationId": "get_user_questions_admin", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/UserQuestionAdmin"}, "type": "array", "title": "Response Get User Questions Admin Admin User Questions Get"}}}}}, "security": [{"APIKeyCookie": []}]}, "put": {"tags": ["admin"], "summary": "Update User Question", "operationId": "update_user_question", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserQuestionAdmin"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserQuestionAdmin"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}, "post": {"tags": ["admin"], "summary": "Create User Question", "operationId": "create_user_question", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserQuestionAdmin"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/admin/user-questions/{user_question_id}": {"delete": {"tags": ["admin"], "summary": "Delete User Question", "operationId": "delete_user_question", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "user_question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "User Question Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/users/me": {"get": {"tags": ["users"], "summary": "Users:Current User", "operationId": "users:current_user", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "401": {"description": "Missing token or inactive user."}}, "security": [{"APIKeyCookie": []}]}, "patch": {"tags": ["users"], "summary": "Users:Patch Current User", "operationId": "users:patch_current_user", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserUpdate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "401": {"description": "Missing token or inactive user."}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorModel"}, "examples": {"UPDATE_USER_EMAIL_ALREADY_EXISTS": {"summary": "A user with this email already exists.", "value": {"detail": "UPDATE_USER_EMAIL_ALREADY_EXISTS"}}, "UPDATE_USER_INVALID_PASSWORD": {"summary": "Password validation failed.", "value": {"detail": {"code": "UPDATE_USER_INVALID_PASSWORD", "reason": "Password should beat least 3 characters"}}}}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/users/{id}": {"get": {"tags": ["users"], "summary": "Users:User", "operationId": "users:user", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string", "title": "Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "401": {"description": "Missing token or inactive user."}, "403": {"description": "Not a superuser."}, "404": {"description": "The user does not exist."}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "patch": {"tags": ["users"], "summary": "Users:Patch User", "operationId": "users:patch_user", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string", "title": "Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserUpdate"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "401": {"description": "Missing token or inactive user."}, "403": {"description": "Not a superuser."}, "404": {"description": "The user does not exist."}, "400": {"content": {"application/json": {"examples": {"UPDATE_USER_EMAIL_ALREADY_EXISTS": {"summary": "A user with this email already exists.", "value": {"detail": "UPDATE_USER_EMAIL_ALREADY_EXISTS"}}, "UPDATE_USER_INVALID_PASSWORD": {"summary": "Password validation failed.", "value": {"detail": {"code": "UPDATE_USER_INVALID_PASSWORD", "reason": "Password should beat least 3 characters"}}}}, "schema": {"$ref": "#/components/schemas/ErrorModel"}}}, "description": "Bad Request"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["users"], "summary": "Users:Delete User", "operationId": "users:delete_user", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string", "title": "Id"}}], "responses": {"204": {"description": "Successful Response"}, "401": {"description": "Missing token or inactive user."}, "403": {"description": "Not a superuser."}, "404": {"description": "The user does not exist."}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/users/children/": {"get": {"tags": ["users"], "summary": "Get Children", "operationId": "get_children", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/ChildPublic"}, "type": "array", "title": "Response Get Children Users Children Get"}}}}}, "security": [{"APIKeyCookie": []}]}, "post": {"tags": ["users"], "summary": "Create Child", "operationId": "create_child", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChildCreate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChildPublic"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/users/children": {"put": {"tags": ["users"], "summary": "Update Child", "operationId": "update_child", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChildPublic"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChildPublic"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/users/children/{child_id}": {"delete": {"tags": ["users"], "summary": "Delete Child", "operationId": "delete_child", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "child_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Child Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/users/children-images/{child_id}": {"get": {"tags": ["users"], "summary": "Get Child Image", "operationId": "get_child_image", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "child_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Child Id"}}], "responses": {"200": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "put": {"tags": ["users"], "summary": "Upload Child Image", "operationId": "upload_child_image", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "child_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Child Id"}}], "requestBody": {"required": true, "content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_upload_child_image_users_children_images__child_id__put"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/login": {"post": {"tags": ["auth"], "summary": "Auth:Cookie.Login", "operationId": "auth:cookie.login", "requestBody": {"content": {"application/x-www-form-urlencoded": {"schema": {"$ref": "#/components/schemas/Body_auth_cookie_login_auth_login_post"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorModel"}, "examples": {"LOGIN_BAD_CREDENTIALS": {"summary": "Bad credentials or the user is inactive.", "value": {"detail": "LOGIN_BAD_CREDENTIALS"}}, "LOGIN_USER_NOT_VERIFIED": {"summary": "The user is not verified.", "value": {"detail": "LOGIN_USER_NOT_VERIFIED"}}}}}}, "204": {"description": "No Content"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/logout": {"post": {"tags": ["auth"], "summary": "Auth:Cookie.Logout", "operationId": "auth:cookie.logout", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "401": {"description": "Missing token or inactive user."}, "204": {"description": "No Content"}}, "security": [{"APIKeyCookie": []}]}}, "/auth/register": {"post": {"tags": ["auth"], "summary": "Register:Register", "operationId": "register:register", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserCreate"}}}, "required": true}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorModel"}, "examples": {"REGISTER_USER_ALREADY_EXISTS": {"summary": "A user with this email already exists.", "value": {"detail": "REGISTER_USER_ALREADY_EXISTS"}}, "REGISTER_INVALID_PASSWORD": {"summary": "Password validation failed.", "value": {"detail": {"code": "REGISTER_INVALID_PASSWORD", "reason": "Password should beat least 3 characters"}}}}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/forgot-password": {"post": {"tags": ["auth"], "summary": "Reset:Forgot Password", "operationId": "reset:forgot_password", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Body_reset_forgot_password_auth_forgot_password_post"}}}, "required": true}, "responses": {"202": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/reset-password": {"post": {"tags": ["auth"], "summary": "Reset:Reset Password", "operationId": "reset:reset_password", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Body_reset_reset_password_auth_reset_password_post"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorModel"}, "examples": {"RESET_PASSWORD_BAD_TOKEN": {"summary": "Bad or expired token.", "value": {"detail": "RESET_PASSWORD_BAD_TOKEN"}}, "RESET_PASSWORD_INVALID_PASSWORD": {"summary": "Password validation failed.", "value": {"detail": {"code": "RESET_PASSWORD_INVALID_PASSWORD", "reason": "Password should be at least 3 characters"}}}}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/request-verify-token": {"post": {"tags": ["auth"], "summary": "Verify:Request-Token", "operationId": "verify:request-token", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Body_verify_request_token_auth_request_verify_token_post"}}}, "required": true}, "responses": {"202": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/verify": {"post": {"tags": ["auth"], "summary": "Verify:Verify", "operationId": "verify:verify", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Body_verify_verify_auth_verify_post"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorModel"}, "examples": {"VERIFY_USER_BAD_TOKEN": {"summary": "Bad token, not existing user ornot the e-mail currently set for the user.", "value": {"detail": "VERIFY_USER_BAD_TOKEN"}}, "VERIFY_USER_ALREADY_VERIFIED": {"summary": "The user is already verified.", "value": {"detail": "VERIFY_USER_ALREADY_VERIFIED"}}}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/research/auth/": {"get": {"tags": ["research"], "summary": "Auth", "operationId": "auth", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"APIKeyCookie": []}]}}}, "components": {"schemas": {"Body_auth_cookie_login_auth_login_post": {"properties": {"grant_type": {"anyOf": [{"type": "string", "pattern": "password"}, {"type": "null"}], "title": "Grant Type"}, "username": {"type": "string", "title": "Username"}, "password": {"type": "string", "title": "Password"}, "scope": {"type": "string", "title": "Scope", "default": ""}, "client_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Client Id"}, "client_secret": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Client Secret"}}, "type": "object", "required": ["username", "password"], "title": "Body_auth_cookie_login_auth_login_post"}, "Body_reset_forgot_password_auth_forgot_password_post": {"properties": {"email": {"type": "string", "format": "email", "title": "Email"}}, "type": "object", "required": ["email"], "title": "Body_reset_forgot_password_auth_forgot_password_post"}, "Body_reset_reset_password_auth_reset_password_post": {"properties": {"token": {"type": "string", "title": "Token"}, "password": {"type": "string", "title": "Password"}}, "type": "object", "required": ["token", "password"], "title": "Body_reset_reset_password_auth_reset_password_post"}, "Body_upload_child_image_users_children_images__child_id__put": {"properties": {"file": {"type": "string", "format": "binary", "title": "File"}}, "type": "object", "required": ["file"], "title": "Body_upload_child_image_users_children_images__child_id__put"}, "Body_upload_milestone_group_image_admin_milestone_group_images__milestone_group_id__put": {"properties": {"file": {"type": "string", "format": "binary", "title": "File"}}, "type": "object", "required": ["file"], "title": "Body_upload_milestone_group_image_admin_milestone_group_images__milestone_group_id__put"}, "Body_upload_milestone_image_admin_milestone_images__milestone_id__post": {"properties": {"file": {"type": "string", "format": "binary", "title": "File"}}, "type": "object", "required": ["file"], "title": "Body_upload_milestone_image_admin_milestone_images__milestone_id__post"}, "Body_verify_request_token_auth_request_verify_token_post": {"properties": {"email": {"type": "string", "format": "email", "title": "Email"}}, "type": "object", "required": ["email"], "title": "Body_verify_request_token_auth_request_verify_token_post"}, "Body_verify_verify_auth_verify_post": {"properties": {"token": {"type": "string", "title": "Token"}}, "type": "object", "required": ["token"], "title": "Body_verify_verify_auth_verify_post"}, "ChildCreate": {"properties": {"name": {"type": "string", "title": "Name", "default": ""}, "birth_year": {"type": "integer", "title": "Birth Year"}, "birth_month": {"type": "integer", "title": "Birth Month"}, "has_image": {"type": "boolean", "title": "Has Image"}}, "type": "object", "required": ["birth_year", "birth_month", "has_image"], "title": "ChildCreate"}, "ChildPublic": {"properties": {"name": {"type": "string", "title": "Name", "default": ""}, "birth_year": {"type": "integer", "title": "Birth Year"}, "birth_month": {"type": "integer", "title": "Birth Month"}, "has_image": {"type": "boolean", "title": "Has Image"}, "id": {"type": "integer", "title": "Id"}}, "type": "object", "required": ["birth_year", "birth_month", "has_image", "id"], "title": "ChildPublic"}, "ErrorModel": {"properties": {"detail": {"anyOf": [{"type": "string"}, {"additionalProperties": {"type": "string"}, "type": "object"}], "title": "Detail"}}, "type": "object", "required": ["detail"], "title": "ErrorModel"}, "HTTPValidationError": {"properties": {"detail": {"items": {"$ref": "#/components/schemas/ValidationError"}, "type": "array", "title": "Detail"}}, "type": "object", "title": "HTTPValidationError"}, "Language": {"properties": {"id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Id"}, "lang": {"type": "string", "maxLength": 2, "title": "Lang"}}, "type": "object", "required": ["lang"], "title": "Language"}, "LanguageCreate": {"properties": {"lang": {"type": "string", "maxLength": 2, "title": "Lang"}}, "type": "object", "required": ["lang"], "title": "LanguageCreate"}, "MilestoneAdmin": {"properties": {"id": {"type": "integer", "title": "Id"}, "group_id": {"type": "integer", "title": "Group Id"}, "order": {"type": "integer", "title": "Order"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneText"}, "type": "object", "title": "Text", "default": {}}, "images": {"items": {"$ref": "#/components/schemas/MilestoneImage"}, "type": "array", "title": "Images", "default": []}}, "type": "object", "required": ["id", "group_id", "order"], "title": "MilestoneAdmin"}, "MilestoneGroupAdmin": {"properties": {"id": {"type": "integer", "title": "Id"}, "order": {"type": "integer", "title": "Order"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneGroupText"}, "type": "object", "title": "Text", "default": {}}, "milestones": {"items": {"$ref": "#/components/schemas/MilestoneAdmin"}, "type": "array", "title": "Milestones", "default": []}}, "type": "object", "required": ["id", "order"], "title": "MilestoneGroupAdmin"}, "MilestoneGroupPublic": {"properties": {"id": {"type": "integer", "title": "Id"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneGroupTextPublic"}, "type": "object", "title": "Text", "default": {}}, "milestones": {"items": {"$ref": "#/components/schemas/MilestonePublic"}, "type": "array", "title": "Milestones", "default": []}}, "type": "object", "required": ["id"], "title": "MilestoneGroupPublic"}, "MilestoneGroupText": {"properties": {"title": {"type": "string", "title": "Title", "default": ""}, "desc": {"type": "string", "title": "Desc", "default": ""}, "group_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Group Id"}, "lang_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Lang Id"}}, "type": "object", "title": "MilestoneGroupText"}, "MilestoneGroupTextPublic": {"properties": {"title": {"type": "string", "title": "Title", "default": ""}, "desc": {"type": "string", "title": "Desc", "default": ""}}, "type": "object", "title": "MilestoneGroupTextPublic"}, "MilestoneImage": {"properties": {"id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Id"}, "milestone_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Milestone Id"}, "filename": {"type": "string", "title": "Filename", "default": ""}, "approved": {"type": "boolean", "title": "Approved", "default": false}}, "type": "object", "title": "MilestoneImage"}, "MilestoneImagePublic": {"properties": {"filename": {"type": "string", "title": "Filename"}, "approved": {"type": "boolean", "title": "Approved"}}, "type": "object", "required": ["filename", "approved"], "title": "MilestoneImagePublic"}, "MilestonePublic": {"properties": {"id": {"type": "integer", "title": "Id"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneTextPublic"}, "type": "object", "title": "Text", "default": {}}, "images": {"items": {"$ref": "#/components/schemas/MilestoneImagePublic"}, "type": "array", "title": "Images", "default": []}}, "type": "object", "required": ["id"], "title": "MilestonePublic"}, "MilestoneText": {"properties": {"title": {"type": "string", "title": "Title", "default": ""}, "desc": {"type": "string", "title": "Desc", "default": ""}, "obs": {"type": "string", "title": "Obs", "default": ""}, "help": {"type": "string", "title": "Help", "default": ""}, "milestone_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Milestone Id"}, "lang_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Lang Id"}}, "type": "object", "title": "MilestoneText"}, "MilestoneTextPublic": {"properties": {"title": {"type": "string", "title": "Title", "default": ""}, "desc": {"type": "string", "title": "Desc", "default": ""}, "obs": {"type": "string", "title": "Obs", "default": ""}, "help": {"type": "string", "title": "Help", "default": ""}}, "type": "object", "title": "MilestoneTextPublic"}, "UserCreate": {"properties": {"email": {"type": "string", "format": "email", "title": "Email"}, "password": {"type": "string", "title": "Password"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active", "default": true}, "is_superuser": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Superuser", "default": false}, "is_verified": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Verified", "default": false}, "is_researcher": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Researcher", "default": false}}, "type": "object", "required": ["email", "password"], "title": "UserCreate"}, "UserQuestionAdmin": {"properties": {"id": {"type": "integer", "title": "Id"}, "order": {"type": "integer", "title": "Order"}, "input": {"type": "string", "title": "Input"}, "options": {"type": "string", "title": "Options"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/UserQuestionText"}, "type": "object", "title": "Text", "default": {}}}, "type": "object", "required": ["id", "order", "input", "options"], "title": "UserQuestionAdmin"}, "UserQuestionPublic": {"properties": {"id": {"type": "integer", "title": "Id"}, "input": {"type": "string", "title": "Input"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/UserQuestionTextPublic"}, "type": "object", "title": "Text", "default": {}}}, "type": "object", "required": ["id", "input"], "title": "UserQuestionPublic"}, "UserQuestionText": {"properties": {"question": {"type": "string", "title": "Question", "default": ""}, "options_json": {"type": "string", "title": "Options Json", "default": ""}, "user_question_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "User Question Id"}, "lang_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Lang Id"}, "options": {"type": "string", "title": "Options", "default": ""}}, "type": "object", "title": "UserQuestionText"}, "UserQuestionTextPublic": {"properties": {"question": {"type": "string", "title": "Question", "default": ""}, "options_json": {"type": "string", "title": "Options Json", "default": ""}}, "type": "object", "title": "UserQuestionTextPublic"}, "UserRead": {"properties": {"id": {"type": "integer", "title": "Id"}, "email": {"type": "string", "format": "email", "title": "Email"}, "is_active": {"type": "boolean", "title": "Is Active", "default": true}, "is_superuser": {"type": "boolean", "title": "Is Superuser", "default": false}, "is_verified": {"type": "boolean", "title": "Is Verified", "default": false}, "is_researcher": {"type": "boolean", "title": "Is Researcher"}}, "type": "object", "required": ["id", "email", "is_researcher"], "title": "UserRead"}, "UserUpdate": {"properties": {"password": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Password"}, "email": {"anyOf": [{"type": "string", "format": "email"}, {"type": "null"}], "title": "Email"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}, "is_superuser": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Superuser"}, "is_verified": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Verified"}, "is_researcher": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Researcher"}}, "type": "object", "title": "UserUpdate"}, "ValidationError": {"properties": {"loc": {"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, "type": "array", "title": "Location"}, "msg": {"type": "string", "title": "Message"}, "type": {"type": "string", "title": "Error Type"}}, "type": "object", "required": ["loc", "msg", "type"], "title": "ValidationError"}}, "securitySchemes": {"APIKeyCookie": {"type": "apiKey", "in": "cookie", "name": "fastapiusersauth"}}}} \ No newline at end of file +{"openapi": "3.1.0", "info": {"title": "MONDEY API", "version": "0.1.0"}, "paths": {"/languages/": {"get": {"tags": ["milestones"], "summary": "Get Languages", "operationId": "get_languages", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"additionalProperties": {"type": "integer"}, "type": "object", "title": "Response Get Languages Languages Get"}}}}}}}, "/milestones/": {"get": {"tags": ["milestones"], "summary": "Get Milestones", "operationId": "get_milestones", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/MilestonePublic"}, "type": "array", "title": "Response Get Milestones Milestones Get"}}}}}}}, "/milestones/{milestone_id}": {"get": {"tags": ["milestones"], "summary": "Get Milestone", "operationId": "get_milestone", "parameters": [{"name": "milestone_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestonePublic"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/milestone-groups/": {"get": {"tags": ["milestones"], "summary": "Get Milestone Groups", "operationId": "get_milestone_groups", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/MilestoneGroupPublic"}, "type": "array", "title": "Response Get Milestone Groups Milestone Groups Get"}}}}}}}, "/milestone-groups/{milestone_group_id}": {"get": {"tags": ["milestones"], "summary": "Get Milestone Group", "operationId": "get_milestone_group", "parameters": [{"name": "milestone_group_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Group Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneGroupPublic"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/user-questions/": {"get": {"tags": ["questions"], "summary": "Get User Questions", "operationId": "get_user_questions", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/UserQuestionPublic"}, "type": "array", "title": "Response Get User Questions User Questions Get"}}}}}}}, "/admin/languages/": {"post": {"tags": ["admin"], "summary": "Create Language", "operationId": "create_language", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LanguageCreate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Language"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/admin/languages/{language_id}": {"delete": {"tags": ["admin"], "summary": "Delete Language", "operationId": "delete_language", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "language_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Language Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/i18n/{language_id}": {"put": {"tags": ["admin"], "summary": "Update I18N", "operationId": "update_i18n", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "language_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Language Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": {"type": "object", "additionalProperties": {"type": "string"}}, "title": "I18Dict"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/milestone-groups/": {"get": {"tags": ["admin"], "summary": "Get Milestone Groups Admin", "operationId": "get_milestone_groups_admin", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/MilestoneGroupAdmin"}, "type": "array", "title": "Response Get Milestone Groups Admin Admin Milestone Groups Get"}}}}}, "security": [{"APIKeyCookie": []}]}, "post": {"tags": ["admin"], "summary": "Create Milestone Group Admin", "operationId": "create_milestone_group_admin", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneGroupAdmin"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/admin/milestone-groups": {"put": {"tags": ["admin"], "summary": "Update Milestone Group Admin", "operationId": "update_milestone_group_admin", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneGroupAdmin"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneGroupAdmin"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/admin/milestone-groups/{milestone_group_id}": {"delete": {"tags": ["admin"], "summary": "Delete Milestone Group Admin", "operationId": "delete_milestone_group_admin", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_group_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Group Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/milestone-group-images/{milestone_group_id}": {"put": {"tags": ["admin"], "summary": "Upload Milestone Group Image", "operationId": "upload_milestone_group_image", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_group_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Group Id"}}], "requestBody": {"required": true, "content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_upload_milestone_group_image_admin_milestone_group_images__milestone_group_id__put"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/milestones/{milestone_group_id}": {"post": {"tags": ["admin"], "summary": "Create Milestone", "operationId": "create_milestone", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_group_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Group Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneAdmin"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/milestones/": {"put": {"tags": ["admin"], "summary": "Update Milestone", "operationId": "update_milestone", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneAdmin"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneAdmin"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/admin/milestones/{milestone_id}": {"delete": {"tags": ["admin"], "summary": "Delete Milestone", "operationId": "delete_milestone", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/milestone-images/{milestone_id}": {"post": {"tags": ["admin"], "summary": "Upload Milestone Image", "operationId": "upload_milestone_image", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Id"}}], "requestBody": {"required": true, "content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_upload_milestone_image_admin_milestone_images__milestone_id__post"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneImage"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/user-questions/": {"get": {"tags": ["admin"], "summary": "Get User Questions Admin", "operationId": "get_user_questions_admin", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/UserQuestionAdmin"}, "type": "array", "title": "Response Get User Questions Admin Admin User Questions Get"}}}}}, "security": [{"APIKeyCookie": []}]}, "put": {"tags": ["admin"], "summary": "Update User Question", "operationId": "update_user_question", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserQuestionAdmin"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserQuestionAdmin"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}, "post": {"tags": ["admin"], "summary": "Create User Question", "operationId": "create_user_question", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserQuestionAdmin"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/admin/user-questions/{user_question_id}": {"delete": {"tags": ["admin"], "summary": "Delete User Question", "operationId": "delete_user_question", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "user_question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "User Question Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/users/me": {"get": {"tags": ["users"], "summary": "Users:Current User", "operationId": "users:current_user", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "401": {"description": "Missing token or inactive user."}}, "security": [{"APIKeyCookie": []}]}, "patch": {"tags": ["users"], "summary": "Users:Patch Current User", "operationId": "users:patch_current_user", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserUpdate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "401": {"description": "Missing token or inactive user."}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorModel"}, "examples": {"UPDATE_USER_EMAIL_ALREADY_EXISTS": {"summary": "A user with this email already exists.", "value": {"detail": "UPDATE_USER_EMAIL_ALREADY_EXISTS"}}, "UPDATE_USER_INVALID_PASSWORD": {"summary": "Password validation failed.", "value": {"detail": {"code": "UPDATE_USER_INVALID_PASSWORD", "reason": "Password should beat least 3 characters"}}}}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/users/{id}": {"get": {"tags": ["users"], "summary": "Users:User", "operationId": "users:user", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string", "title": "Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "401": {"description": "Missing token or inactive user."}, "403": {"description": "Not a superuser."}, "404": {"description": "The user does not exist."}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "patch": {"tags": ["users"], "summary": "Users:Patch User", "operationId": "users:patch_user", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string", "title": "Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserUpdate"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "401": {"description": "Missing token or inactive user."}, "403": {"description": "Not a superuser."}, "404": {"description": "The user does not exist."}, "400": {"content": {"application/json": {"examples": {"UPDATE_USER_EMAIL_ALREADY_EXISTS": {"summary": "A user with this email already exists.", "value": {"detail": "UPDATE_USER_EMAIL_ALREADY_EXISTS"}}, "UPDATE_USER_INVALID_PASSWORD": {"summary": "Password validation failed.", "value": {"detail": {"code": "UPDATE_USER_INVALID_PASSWORD", "reason": "Password should beat least 3 characters"}}}}, "schema": {"$ref": "#/components/schemas/ErrorModel"}}}, "description": "Bad Request"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["users"], "summary": "Users:Delete User", "operationId": "users:delete_user", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string", "title": "Id"}}], "responses": {"204": {"description": "Successful Response"}, "401": {"description": "Missing token or inactive user."}, "403": {"description": "Not a superuser."}, "404": {"description": "The user does not exist."}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/users/children/": {"get": {"tags": ["users"], "summary": "Get Children", "operationId": "get_children", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/ChildPublic"}, "type": "array", "title": "Response Get Children Users Children Get"}}}}}, "security": [{"APIKeyCookie": []}]}, "put": {"tags": ["users"], "summary": "Update Child", "operationId": "update_child", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChildPublic"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChildPublic"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}, "post": {"tags": ["users"], "summary": "Create Child", "operationId": "create_child", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChildCreate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChildPublic"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/users/children/{child_id}": {"delete": {"tags": ["users"], "summary": "Delete Child", "operationId": "delete_child", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "child_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Child Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/users/children-images/{child_id}": {"get": {"tags": ["users"], "summary": "Get Child Image", "operationId": "get_child_image", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "child_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Child Id"}}], "responses": {"200": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "put": {"tags": ["users"], "summary": "Upload Child Image", "operationId": "upload_child_image", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "child_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Child Id"}}], "requestBody": {"required": true, "content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_upload_child_image_users_children_images__child_id__put"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/users/milestone-answers/{child_id}": {"get": {"tags": ["users"], "summary": "Get Current Milestone Answer Session", "operationId": "get_current_milestone_answer_session", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "child_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Child Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneAnswerSessionPublic"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/users/milestone-answers/{milestone_answer_session_id}": {"put": {"tags": ["users"], "summary": "Update Milestone Answer", "operationId": "update_milestone_answer", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_answer_session_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Answer Session Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneAnswerPublic"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneAnswerPublic"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/login": {"post": {"tags": ["auth"], "summary": "Auth:Cookie.Login", "operationId": "auth:cookie.login", "requestBody": {"content": {"application/x-www-form-urlencoded": {"schema": {"$ref": "#/components/schemas/Body_auth_cookie_login_auth_login_post"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorModel"}, "examples": {"LOGIN_BAD_CREDENTIALS": {"summary": "Bad credentials or the user is inactive.", "value": {"detail": "LOGIN_BAD_CREDENTIALS"}}, "LOGIN_USER_NOT_VERIFIED": {"summary": "The user is not verified.", "value": {"detail": "LOGIN_USER_NOT_VERIFIED"}}}}}}, "204": {"description": "No Content"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/logout": {"post": {"tags": ["auth"], "summary": "Auth:Cookie.Logout", "operationId": "auth:cookie.logout", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "401": {"description": "Missing token or inactive user."}, "204": {"description": "No Content"}}, "security": [{"APIKeyCookie": []}]}}, "/auth/register": {"post": {"tags": ["auth"], "summary": "Register:Register", "operationId": "register:register", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserCreate"}}}, "required": true}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorModel"}, "examples": {"REGISTER_USER_ALREADY_EXISTS": {"summary": "A user with this email already exists.", "value": {"detail": "REGISTER_USER_ALREADY_EXISTS"}}, "REGISTER_INVALID_PASSWORD": {"summary": "Password validation failed.", "value": {"detail": {"code": "REGISTER_INVALID_PASSWORD", "reason": "Password should beat least 3 characters"}}}}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/forgot-password": {"post": {"tags": ["auth"], "summary": "Reset:Forgot Password", "operationId": "reset:forgot_password", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Body_reset_forgot_password_auth_forgot_password_post"}}}, "required": true}, "responses": {"202": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/reset-password": {"post": {"tags": ["auth"], "summary": "Reset:Reset Password", "operationId": "reset:reset_password", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Body_reset_reset_password_auth_reset_password_post"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorModel"}, "examples": {"RESET_PASSWORD_BAD_TOKEN": {"summary": "Bad or expired token.", "value": {"detail": "RESET_PASSWORD_BAD_TOKEN"}}, "RESET_PASSWORD_INVALID_PASSWORD": {"summary": "Password validation failed.", "value": {"detail": {"code": "RESET_PASSWORD_INVALID_PASSWORD", "reason": "Password should be at least 3 characters"}}}}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/request-verify-token": {"post": {"tags": ["auth"], "summary": "Verify:Request-Token", "operationId": "verify:request-token", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Body_verify_request_token_auth_request_verify_token_post"}}}, "required": true}, "responses": {"202": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/verify": {"post": {"tags": ["auth"], "summary": "Verify:Verify", "operationId": "verify:verify", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Body_verify_verify_auth_verify_post"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorModel"}, "examples": {"VERIFY_USER_BAD_TOKEN": {"summary": "Bad token, not existing user ornot the e-mail currently set for the user.", "value": {"detail": "VERIFY_USER_BAD_TOKEN"}}, "VERIFY_USER_ALREADY_VERIFIED": {"summary": "The user is already verified.", "value": {"detail": "VERIFY_USER_ALREADY_VERIFIED"}}}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/research/auth/": {"get": {"tags": ["research"], "summary": "Auth", "operationId": "auth", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"APIKeyCookie": []}]}}}, "components": {"schemas": {"Body_auth_cookie_login_auth_login_post": {"properties": {"grant_type": {"anyOf": [{"type": "string", "pattern": "password"}, {"type": "null"}], "title": "Grant Type"}, "username": {"type": "string", "title": "Username"}, "password": {"type": "string", "title": "Password"}, "scope": {"type": "string", "title": "Scope", "default": ""}, "client_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Client Id"}, "client_secret": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Client Secret"}}, "type": "object", "required": ["username", "password"], "title": "Body_auth_cookie_login_auth_login_post"}, "Body_reset_forgot_password_auth_forgot_password_post": {"properties": {"email": {"type": "string", "format": "email", "title": "Email"}}, "type": "object", "required": ["email"], "title": "Body_reset_forgot_password_auth_forgot_password_post"}, "Body_reset_reset_password_auth_reset_password_post": {"properties": {"token": {"type": "string", "title": "Token"}, "password": {"type": "string", "title": "Password"}}, "type": "object", "required": ["token", "password"], "title": "Body_reset_reset_password_auth_reset_password_post"}, "Body_upload_child_image_users_children_images__child_id__put": {"properties": {"file": {"type": "string", "format": "binary", "title": "File"}}, "type": "object", "required": ["file"], "title": "Body_upload_child_image_users_children_images__child_id__put"}, "Body_upload_milestone_group_image_admin_milestone_group_images__milestone_group_id__put": {"properties": {"file": {"type": "string", "format": "binary", "title": "File"}}, "type": "object", "required": ["file"], "title": "Body_upload_milestone_group_image_admin_milestone_group_images__milestone_group_id__put"}, "Body_upload_milestone_image_admin_milestone_images__milestone_id__post": {"properties": {"file": {"type": "string", "format": "binary", "title": "File"}}, "type": "object", "required": ["file"], "title": "Body_upload_milestone_image_admin_milestone_images__milestone_id__post"}, "Body_verify_request_token_auth_request_verify_token_post": {"properties": {"email": {"type": "string", "format": "email", "title": "Email"}}, "type": "object", "required": ["email"], "title": "Body_verify_request_token_auth_request_verify_token_post"}, "Body_verify_verify_auth_verify_post": {"properties": {"token": {"type": "string", "title": "Token"}}, "type": "object", "required": ["token"], "title": "Body_verify_verify_auth_verify_post"}, "ChildCreate": {"properties": {"name": {"type": "string", "title": "Name", "default": ""}, "birth_year": {"type": "integer", "title": "Birth Year"}, "birth_month": {"type": "integer", "title": "Birth Month"}}, "type": "object", "required": ["birth_year", "birth_month"], "title": "ChildCreate"}, "ChildPublic": {"properties": {"name": {"type": "string", "title": "Name", "default": ""}, "birth_year": {"type": "integer", "title": "Birth Year"}, "birth_month": {"type": "integer", "title": "Birth Month"}, "id": {"type": "integer", "title": "Id"}, "has_image": {"type": "boolean", "title": "Has Image"}}, "type": "object", "required": ["birth_year", "birth_month", "id", "has_image"], "title": "ChildPublic"}, "ErrorModel": {"properties": {"detail": {"anyOf": [{"type": "string"}, {"additionalProperties": {"type": "string"}, "type": "object"}], "title": "Detail"}}, "type": "object", "required": ["detail"], "title": "ErrorModel"}, "HTTPValidationError": {"properties": {"detail": {"items": {"$ref": "#/components/schemas/ValidationError"}, "type": "array", "title": "Detail"}}, "type": "object", "title": "HTTPValidationError"}, "Language": {"properties": {"id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Id"}, "lang": {"type": "string", "maxLength": 2, "title": "Lang"}}, "type": "object", "required": ["lang"], "title": "Language"}, "LanguageCreate": {"properties": {"lang": {"type": "string", "maxLength": 2, "title": "Lang"}}, "type": "object", "required": ["lang"], "title": "LanguageCreate"}, "MilestoneAdmin": {"properties": {"id": {"type": "integer", "title": "Id"}, "group_id": {"type": "integer", "title": "Group Id"}, "order": {"type": "integer", "title": "Order"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneText"}, "type": "object", "title": "Text", "default": {}}, "images": {"items": {"$ref": "#/components/schemas/MilestoneImage"}, "type": "array", "title": "Images", "default": []}}, "type": "object", "required": ["id", "group_id", "order"], "title": "MilestoneAdmin"}, "MilestoneAnswerPublic": {"properties": {"milestone_id": {"type": "integer", "title": "Milestone Id"}, "answer": {"type": "integer", "title": "Answer"}}, "type": "object", "required": ["milestone_id", "answer"], "title": "MilestoneAnswerPublic"}, "MilestoneAnswerSessionPublic": {"properties": {"id": {"type": "integer", "title": "Id"}, "created_at": {"type": "string", "format": "date-time", "title": "Created At"}, "answers": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneAnswerPublic"}, "type": "object", "title": "Answers"}}, "type": "object", "required": ["id", "created_at", "answers"], "title": "MilestoneAnswerSessionPublic"}, "MilestoneGroupAdmin": {"properties": {"id": {"type": "integer", "title": "Id"}, "order": {"type": "integer", "title": "Order"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneGroupText"}, "type": "object", "title": "Text", "default": {}}, "milestones": {"items": {"$ref": "#/components/schemas/MilestoneAdmin"}, "type": "array", "title": "Milestones", "default": []}}, "type": "object", "required": ["id", "order"], "title": "MilestoneGroupAdmin"}, "MilestoneGroupPublic": {"properties": {"id": {"type": "integer", "title": "Id"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneGroupTextPublic"}, "type": "object", "title": "Text", "default": {}}, "milestones": {"items": {"$ref": "#/components/schemas/MilestonePublic"}, "type": "array", "title": "Milestones", "default": []}}, "type": "object", "required": ["id"], "title": "MilestoneGroupPublic"}, "MilestoneGroupText": {"properties": {"title": {"type": "string", "title": "Title", "default": ""}, "desc": {"type": "string", "title": "Desc", "default": ""}, "group_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Group Id"}, "lang_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Lang Id"}}, "type": "object", "title": "MilestoneGroupText"}, "MilestoneGroupTextPublic": {"properties": {"title": {"type": "string", "title": "Title", "default": ""}, "desc": {"type": "string", "title": "Desc", "default": ""}}, "type": "object", "title": "MilestoneGroupTextPublic"}, "MilestoneImage": {"properties": {"id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Id"}, "milestone_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Milestone Id"}, "filename": {"type": "string", "title": "Filename", "default": ""}, "approved": {"type": "boolean", "title": "Approved", "default": false}}, "type": "object", "title": "MilestoneImage"}, "MilestoneImagePublic": {"properties": {"filename": {"type": "string", "title": "Filename"}, "approved": {"type": "boolean", "title": "Approved"}}, "type": "object", "required": ["filename", "approved"], "title": "MilestoneImagePublic"}, "MilestonePublic": {"properties": {"id": {"type": "integer", "title": "Id"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneTextPublic"}, "type": "object", "title": "Text", "default": {}}, "images": {"items": {"$ref": "#/components/schemas/MilestoneImagePublic"}, "type": "array", "title": "Images", "default": []}}, "type": "object", "required": ["id"], "title": "MilestonePublic"}, "MilestoneText": {"properties": {"title": {"type": "string", "title": "Title", "default": ""}, "desc": {"type": "string", "title": "Desc", "default": ""}, "obs": {"type": "string", "title": "Obs", "default": ""}, "help": {"type": "string", "title": "Help", "default": ""}, "milestone_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Milestone Id"}, "lang_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Lang Id"}}, "type": "object", "title": "MilestoneText"}, "MilestoneTextPublic": {"properties": {"title": {"type": "string", "title": "Title", "default": ""}, "desc": {"type": "string", "title": "Desc", "default": ""}, "obs": {"type": "string", "title": "Obs", "default": ""}, "help": {"type": "string", "title": "Help", "default": ""}}, "type": "object", "title": "MilestoneTextPublic"}, "UserCreate": {"properties": {"email": {"type": "string", "format": "email", "title": "Email"}, "password": {"type": "string", "title": "Password"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active", "default": true}, "is_superuser": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Superuser", "default": false}, "is_verified": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Verified", "default": false}, "is_researcher": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Researcher", "default": false}}, "type": "object", "required": ["email", "password"], "title": "UserCreate"}, "UserQuestionAdmin": {"properties": {"id": {"type": "integer", "title": "Id"}, "order": {"type": "integer", "title": "Order"}, "input": {"type": "string", "title": "Input"}, "options": {"type": "string", "title": "Options"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/UserQuestionText"}, "type": "object", "title": "Text", "default": {}}}, "type": "object", "required": ["id", "order", "input", "options"], "title": "UserQuestionAdmin"}, "UserQuestionPublic": {"properties": {"id": {"type": "integer", "title": "Id"}, "input": {"type": "string", "title": "Input"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/UserQuestionTextPublic"}, "type": "object", "title": "Text", "default": {}}}, "type": "object", "required": ["id", "input"], "title": "UserQuestionPublic"}, "UserQuestionText": {"properties": {"question": {"type": "string", "title": "Question", "default": ""}, "options_json": {"type": "string", "title": "Options Json", "default": ""}, "user_question_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "User Question Id"}, "lang_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Lang Id"}, "options": {"type": "string", "title": "Options", "default": ""}}, "type": "object", "title": "UserQuestionText"}, "UserQuestionTextPublic": {"properties": {"question": {"type": "string", "title": "Question", "default": ""}, "options_json": {"type": "string", "title": "Options Json", "default": ""}}, "type": "object", "title": "UserQuestionTextPublic"}, "UserRead": {"properties": {"id": {"type": "integer", "title": "Id"}, "email": {"type": "string", "format": "email", "title": "Email"}, "is_active": {"type": "boolean", "title": "Is Active", "default": true}, "is_superuser": {"type": "boolean", "title": "Is Superuser", "default": false}, "is_verified": {"type": "boolean", "title": "Is Verified", "default": false}, "is_researcher": {"type": "boolean", "title": "Is Researcher"}}, "type": "object", "required": ["id", "email", "is_researcher"], "title": "UserRead"}, "UserUpdate": {"properties": {"password": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Password"}, "email": {"anyOf": [{"type": "string", "format": "email"}, {"type": "null"}], "title": "Email"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}, "is_superuser": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Superuser"}, "is_verified": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Verified"}, "is_researcher": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Researcher"}}, "type": "object", "title": "UserUpdate"}, "ValidationError": {"properties": {"loc": {"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, "type": "array", "title": "Location"}, "msg": {"type": "string", "title": "Message"}, "type": {"type": "string", "title": "Error Type"}}, "type": "object", "required": ["loc", "msg", "type"], "title": "ValidationError"}}, "securitySchemes": {"APIKeyCookie": {"type": "apiKey", "in": "cookie", "name": "fastapiusersauth"}}}} \ No newline at end of file diff --git a/mondey_backend/src/mondey_backend/models/children.py b/mondey_backend/src/mondey_backend/models/children.py index 2781a46b..7aa382bf 100644 --- a/mondey_backend/src/mondey_backend/models/children.py +++ b/mondey_backend/src/mondey_backend/models/children.py @@ -8,12 +8,12 @@ class ChildBase(SQLModel): name: str = "" birth_year: int birth_month: int - has_image: bool class Child(ChildBase, table=True): id: int | None = Field(default=None, primary_key=True) user_id: int + has_image: bool class ChildCreate(ChildBase): @@ -22,3 +22,4 @@ class ChildCreate(ChildBase): class ChildPublic(ChildBase): id: int + has_image: bool diff --git a/mondey_backend/src/mondey_backend/models/milestones.py b/mondey_backend/src/mondey_backend/models/milestones.py index b5c7ec96..aa1aeff5 100644 --- a/mondey_backend/src/mondey_backend/models/milestones.py +++ b/mondey_backend/src/mondey_backend/models/milestones.py @@ -1,11 +1,14 @@ from __future__ import annotations +import datetime + from sqlalchemy.orm import Mapped from sqlalchemy.orm import attribute_keyed_dict from sqlalchemy.orm import relationship from sqlmodel import Field from sqlmodel import Relationship from sqlmodel import SQLModel +from sqlmodel import text from .utils import back_populates from .utils import fixed_length_string_field @@ -145,3 +148,44 @@ class MilestoneImage(SQLModel, table=True): class MilestoneImagePublic(SQLModel): filename: str approved: bool + + +## MilestoneAnswer + + +class MilestoneAnswerPublic(SQLModel): + milestone_id: int + answer: int + + +class MilestoneAnswer(SQLModel, table=True): + answer_session_id: int | None = Field( + default=None, foreign_key="milestoneanswersession.id", primary_key=True + ) + milestone_id: int | None = Field( + default=None, foreign_key="milestone.id", primary_key=True + ) + answer: int + + +class MilestoneAnswerSession(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + child_id: int + user_id: int + created_at: datetime.datetime = Field( + sa_column_kwargs={ + "server_default": text("CURRENT_TIMESTAMP"), + } + ) + answers: Mapped[dict[int, MilestoneAnswer]] = Relationship( + sa_relationship=relationship( + collection_class=attribute_keyed_dict("milestone_id"), + cascade="all, delete-orphan", + ) + ) + + +class MilestoneAnswerSessionPublic(SQLModel): + id: int + created_at: datetime.datetime + answers: dict[int, MilestoneAnswerPublic] diff --git a/mondey_backend/src/mondey_backend/routers/users.py b/mondey_backend/src/mondey_backend/routers/users.py index d6c5afd1..6a9872dd 100644 --- a/mondey_backend/src/mondey_backend/routers/users.py +++ b/mondey_backend/src/mondey_backend/routers/users.py @@ -14,12 +14,17 @@ from ..models.children import Child from ..models.children import ChildCreate from ..models.children import ChildPublic +from ..models.milestones import MilestoneAnswer +from ..models.milestones import MilestoneAnswerPublic +from ..models.milestones import MilestoneAnswerSession +from ..models.milestones import MilestoneAnswerSessionPublic from ..models.users import UserRead from ..models.users import UserUpdate from ..settings import app_settings from ..users import fastapi_users from .utils import add from .utils import get +from .utils import get_or_create_current_milestone_answer_session from .utils import write_file @@ -42,19 +47,20 @@ def create_child( current_active_user: CurrentActiveUserDep, child: ChildCreate, ): - child.user_id = current_active_user.id - db_child = Child.model_validate(child) + db_child = Child.model_validate( + child, update={"user_id": current_active_user.id, "has_image": False} + ) add(session, db_child) return db_child - @router.put("/children", response_model=ChildPublic) + @router.put("/children/", response_model=ChildPublic) def update_child( session: SessionDep, current_active_user: CurrentActiveUserDep, child: ChildPublic, ): db_child = get(session, Child, child.id) - if db_child.user_id != current_active_user.id: + if db_child is None or db_child.user_id != current_active_user.id: raise HTTPException(401) for key, value in child.model_dump().items(): setattr(db_child, key, value) @@ -104,4 +110,46 @@ async def upload_child_image( write_file(file, filename) return {"ok": True} + @router.get( + "/milestone-answers/{child_id}", response_model=MilestoneAnswerSessionPublic + ) + def get_current_milestone_answer_session( + session: SessionDep, current_active_user: CurrentActiveUserDep, child_id: int + ): + milestone_answer_session = get_or_create_current_milestone_answer_session( + session, current_active_user, child_id + ) + return milestone_answer_session + + @router.put( + "/milestone-answers/{milestone_answer_session_id}", + response_model=MilestoneAnswerPublic, + ) + def update_milestone_answer( + session: SessionDep, + current_active_user: CurrentActiveUserDep, + milestone_answer_session_id: int, + answer: MilestoneAnswerPublic, + ): + milestone_answer_session = session.get( + MilestoneAnswerSession, milestone_answer_session_id + ) + if ( + milestone_answer_session is None + or milestone_answer_session.user_id != current_active_user.id + ): + raise HTTPException(401) + milestone_answer = milestone_answer_session.answers.get(answer.milestone_id) + if milestone_answer is None: + milestone_answer = MilestoneAnswer( + answer_session_id=milestone_answer_session.id, + milestone_id=answer.milestone_id, + answer=answer.answer, + ) + add(session, milestone_answer) + else: + milestone_answer.answer = answer.answer + session.commit() + return milestone_answer + return router diff --git a/mondey_backend/src/mondey_backend/routers/utils.py b/mondey_backend/src/mondey_backend/routers/utils.py index e684ae5a..3c0bf615 100644 --- a/mondey_backend/src/mondey_backend/routers/utils.py +++ b/mondey_backend/src/mondey_backend/routers/utils.py @@ -1,5 +1,6 @@ from __future__ import annotations +import datetime import logging import pathlib from collections.abc import Iterable @@ -7,14 +8,19 @@ from fastapi import HTTPException from fastapi import UploadFile from sqlmodel import SQLModel +from sqlmodel import col +from sqlmodel import select from ..dependencies import SessionDep +from ..models.children import Child from ..models.milestones import MilestoneAdmin +from ..models.milestones import MilestoneAnswerSession from ..models.milestones import MilestoneGroupAdmin from ..models.milestones import MilestoneGroupText from ..models.milestones import MilestoneText from ..models.questions import UserQuestionAdmin from ..models.questions import UserQuestionText +from ..users import User Text = MilestoneText | MilestoneGroupText | UserQuestionText @@ -77,3 +83,37 @@ def update_user_question_text(session: SessionDep, user_question: UserQuestionAd _update_text( session, UserQuestionText, user_question.text.values(), user_question.id ) + + +def _session_has_expired(milestone_answer_session: MilestoneAnswerSession) -> bool: + session_lifetime_days = 7 + return ( + datetime.datetime.now() - milestone_answer_session.created_at + > datetime.timedelta(days=session_lifetime_days) + ) + + +def get_or_create_current_milestone_answer_session( + session: SessionDep, current_active_user: User, child_id: int +): + child = session.get(Child, child_id) + if child is None or child.user_id != current_active_user.id: + raise HTTPException(401) + milestone_answer_session = session.exec( + select(MilestoneAnswerSession) + .where( + col(MilestoneAnswerSession.user_id) == current_active_user.id + and col(MilestoneAnswerSession.child_id) == child_id + ) + .order_by(col(MilestoneAnswerSession.created_at).desc()) + ).first() + if milestone_answer_session is None or _session_has_expired( + milestone_answer_session + ): + milestone_answer_session = MilestoneAnswerSession( + child_id=child_id, + user_id=current_active_user.id, + created_at=datetime.datetime.now(), + ) + add(session, milestone_answer_session) + return milestone_answer_session diff --git a/mondey_backend/tests/conftest.py b/mondey_backend/tests/conftest.py index a801eeab..c0e68c49 100644 --- a/mondey_backend/tests/conftest.py +++ b/mondey_backend/tests/conftest.py @@ -1,5 +1,6 @@ from __future__ import annotations +import datetime import pathlib import pytest @@ -19,6 +20,8 @@ from mondey_backend.models.children import Child from mondey_backend.models.milestones import Language from mondey_backend.models.milestones import Milestone +from mondey_backend.models.milestones import MilestoneAnswer +from mondey_backend.models.milestones import MilestoneAnswerSession from mondey_backend.models.milestones import MilestoneGroup from mondey_backend.models.milestones import MilestoneGroupText from mondey_backend.models.milestones import MilestoneImage @@ -142,6 +145,29 @@ def session(): user_id=3, name="child3", birth_year=2021, birth_month=1, has_image=True ) ) + # add an (expired) milestone answer session for child 1 / user 1 with no answers + session.add( + MilestoneAnswerSession( + child_id=1, user_id=1, created_at=datetime.datetime(2024, 1, 1) + ) + ) + # add another (current) milestone answer session for child 1 / user 1 with 2 answers + session.add( + MilestoneAnswerSession( + child_id=1, user_id=1, created_at=datetime.datetime.now() + ) + ) + # add two milestone answers + session.add(MilestoneAnswer(answer_session_id=2, milestone_id=1, answer=0)) + session.add(MilestoneAnswer(answer_session_id=2, milestone_id=2, answer=3)) + # add an (expired) milestone answer session for child 3 / user 3 with 1 answer + session.add( + MilestoneAnswerSession( + child_id=3, user_id=3, created_at=datetime.datetime(2024, 1, 1) + ) + ) + session.add(MilestoneAnswer(answer_session_id=3, milestone_id=7, answer=2)) + yield session diff --git a/mondey_backend/tests/routers/test_users.py b/mondey_backend/tests/routers/test_users.py index fb763199..fa865ecb 100644 --- a/mondey_backend/tests/routers/test_users.py +++ b/mondey_backend/tests/routers/test_users.py @@ -1,8 +1,16 @@ +import datetime import pathlib from fastapi.testclient import TestClient +def _is_approx_now(iso_date_string: str, delta=datetime.timedelta(hours=1)) -> bool: + return ( + datetime.datetime.now() - datetime.datetime.fromisoformat(iso_date_string) + < delta + ) + + def test_get_children(user_client: TestClient): response = user_client.get("/users/children/") assert response.status_code == 200 @@ -60,6 +68,45 @@ def test_get_child_image_no_image(user_client: TestClient): assert response.status_code == 404 +def test_create_update_and_delete_child(user_client: TestClient): + assert len(user_client.get("/users/children/").json()) == 2 + response_create = user_client.post( + "/users/children/", + json={"name": "child1", "birth_year": 2021, "birth_month": 3}, + ) + assert response_create.status_code == 200 + assert response_create.json() == { + "id": 4, + "name": "child1", + "birth_year": 2021, + "birth_month": 3, + "has_image": False, + } + assert len(user_client.get("/users/children/").json()) == 3 + response_update = user_client.put( + "/users/children/", + json={ + "id": 4, + "name": "c", + "birth_year": 2020, + "birth_month": 9, + "has_image": False, + }, + ) + assert response_update.status_code == 200 + assert response_update.json() == { + "id": 4, + "name": "c", + "birth_year": 2020, + "birth_month": 9, + "has_image": False, + } + assert len(user_client.get("/users/children/").json()) == 3 + response_delete = user_client.delete("/users/children/4") + assert response_delete.status_code == 200 + assert len(user_client.get("/users/children/").json()) == 2 + + def test_upload_child_image( user_client: TestClient, private_dir: pathlib.Path, jpg_file: pathlib.Path ): @@ -79,3 +126,78 @@ def test_upload_child_image( assert user_client.get("/users/children/").json()[0]["has_image"] is True assert user_client.get("/users/children-images/1").status_code == 200 (children_dir / "1.jpg").unlink() + + +def test_get_milestone_answers_user_does_not_own_child(admin_client: TestClient): + response = admin_client.get("/users/milestone-answers/1") + assert response.status_code == 401 + + +def test_get_milestone_answers_child_does_not_exist(admin_client: TestClient): + response = admin_client.get("/users/milestone-answers/8") + assert response.status_code == 401 + + +def test_get_milestone_answers_no_current_answer_session(admin_client: TestClient): + response = admin_client.get("/users/milestone-answers/3") + assert response.status_code == 200 + assert response.json()["id"] == 4 + assert _is_approx_now(response.json()["created_at"]) + assert response.json()["answers"] == {} + + +def test_get_milestone_answers_current_answer_session(user_client: TestClient): + response = user_client.get("/users/milestone-answers/1") + assert response.status_code == 200 + assert response.json()["id"] == 2 + assert response.json()["answers"] == { + "1": {"milestone_id": 1, "answer": 0}, + "2": {"milestone_id": 2, "answer": 3}, + } + assert _is_approx_now(response.json()["created_at"]) + + +def test_update_milestone_answer_current_answer_session_no_existing_answer( + user_client: TestClient, +): + current_answer_session = user_client.get("/users/milestone-answers/1").json() + assert "6" not in current_answer_session["answers"] + new_answer = {"milestone_id": 6, "answer": 2} + response = user_client.put( + f"/users/milestone-answers/{current_answer_session['id']}", json=new_answer + ) + assert response.status_code == 200 + assert response.json() == new_answer + assert ( + user_client.get("/users/milestone-answers/1").json()["answers"]["6"] + == new_answer + ) + + +def test_update_milestone_answer_update_existing_answer(user_client: TestClient): + current_answer_session = user_client.get("/users/milestone-answers/1").json() + assert current_answer_session["answers"]["1"] == {"milestone_id": 1, "answer": 0} + new_answer = {"milestone_id": 1, "answer": 2} + response = user_client.put( + f"/users/milestone-answers/{current_answer_session['id']}", json=new_answer + ) + assert response.status_code == 200 + assert response.json() == new_answer + assert ( + user_client.get("/users/milestone-answers/1").json()["answers"]["1"] + == new_answer + ) + + +def test_update_milestone_answer_invalid_user(research_client: TestClient): + response = research_client.put( + "/users/milestone-answers/1", json={"milestone_id": 1, "answer": 2} + ) + assert response.status_code == 401 + + +def test_update_milestone_answer_invalid_answer_session(user_client: TestClient): + response = user_client.put( + "/users/milestone-answers/16", json={"milestone_id": 1, "answer": 2} + ) + assert response.status_code == 401