diff --git a/frontend/src/lib/admin.svelte.ts b/frontend/src/lib/admin.svelte.ts index 4f555ff6..38e7a092 100644 --- a/frontend/src/lib/admin.svelte.ts +++ b/frontend/src/lib/admin.svelte.ts @@ -3,12 +3,16 @@ import { authCookieLogin, getMilestoneGroupsAdmin, getUserQuestionsAdmin, - usersCurrentUser + getMilestoneAgeGroups, + createMilestoneAgeGroup, + deleteMilestoneAgeGroup, + usersCurrentUser, + updateMilestoneAgeGroup } from '$lib/client/services.gen'; import type { - GetLanguagesResponse, + MilestoneAgeGroupPublic, MilestoneGroupAdmin, - UserQuestionAdmin, + MilestoneAgeGroupCreate, UserRead, Body_auth_cookie_login_auth_login_post } from '$lib/client/types.gen'; @@ -47,12 +51,24 @@ export const adminUser = AdminUser(); export async function refreshMilestoneGroups() { console.log('refreshMilestoneGroups...'); - const { data, error } = await getMilestoneGroupsAdmin(); - if (error || data == undefined) { - console.log('Failed to get MilestoneGroups'); - milestoneGroups.set([]); - } else { - milestoneGroups.set(data); + await milestoneAgeGroups.refresh(); + if (!milestoneAgeGroups.value) { + return; + } + const groups = {} as Record>; + for (const ageGroup of milestoneAgeGroups.value) { + const { data, error } = await getMilestoneGroupsAdmin({ + query: { + milestone_age_group_id: ageGroup.id + } + }); + if (error || data == undefined) { + console.log(`Failed to get MilestoneGroups for ${ageGroup}`); + } else { + groups[`${ageGroup.id}`] = data; + } + milestoneGroups.set(groups); + console.log(groups); } } @@ -70,3 +86,51 @@ export async function refreshUserQuestions() { userQuestions.set(data); } } + +function MilestoneAgeGroups() { + let milestoneAgeGroups = $state(null as Array | null); + return { + get value(): Array | null { + return milestoneAgeGroups; + }, + create: async function (ageGroup: MilestoneAgeGroupCreate) { + const { data, error } = await createMilestoneAgeGroup({ body: ageGroup }); + if (error || data === undefined) { + console.log(`Failed to create MilestoneAgeGroup ${ageGroup}`); + } else { + await this.refresh(); + } + }, + save: async function (ageGroup: MilestoneAgeGroupPublic) { + const { data, error } = await updateMilestoneAgeGroup({ body: ageGroup }); + if (error || data === undefined) { + console.log(`Failed to save MilestoneAgeGroup ${ageGroup}`); + } else { + await this.refresh(); + } + }, + delete: async function (id: number) { + const { data, error } = await deleteMilestoneAgeGroup({ + path: { + milestone_age_group_id: id + } + }); + if (error || data === undefined) { + console.log(`Failed to delete MilestoneAgeGroup ${id}`); + } else { + await this.refresh(); + } + }, + refresh: async function () { + const { data, error } = await getMilestoneAgeGroups(); + if (error || data === undefined) { + console.log('Failed to get MilestoneAgeGroups'); + milestoneAgeGroups = null; + } else { + milestoneAgeGroups = data; + } + } + }; +} + +export const milestoneAgeGroups = MilestoneAgeGroups(); diff --git a/frontend/src/lib/client/schemas.gen.ts b/frontend/src/lib/client/schemas.gen.ts index 31d4036e..438d5f43 100644 --- a/frontend/src/lib/client/schemas.gen.ts +++ b/frontend/src/lib/client/schemas.gen.ts @@ -308,6 +308,42 @@ export const MilestoneAdminSchema = { title: 'MilestoneAdmin' } as const; +export const MilestoneAgeGroupCreateSchema = { + properties: { + months_min: { + type: 'integer', + title: 'Months Min' + }, + months_max: { + type: 'integer', + title: 'Months Max' + } + }, + type: 'object', + required: ['months_min', 'months_max'], + title: 'MilestoneAgeGroupCreate' +} as const; + +export const MilestoneAgeGroupPublicSchema = { + properties: { + months_min: { + type: 'integer', + title: 'Months Min' + }, + months_max: { + type: 'integer', + title: 'Months Max' + }, + id: { + type: 'integer', + title: 'Id' + } + }, + type: 'object', + required: ['months_min', 'months_max', 'id'], + title: 'MilestoneAgeGroupPublic' +} as const; + export const MilestoneAnswerPublicSchema = { properties: { milestone_id: { @@ -330,6 +366,14 @@ export const MilestoneAnswerSessionPublicSchema = { type: 'integer', title: 'Id' }, + child_id: { + type: 'integer', + title: 'Child Id' + }, + age_group_id: { + type: 'integer', + title: 'Age Group Id' + }, created_at: { type: 'string', format: 'date-time', @@ -344,7 +388,7 @@ export const MilestoneAnswerSessionPublicSchema = { } }, type: 'object', - required: ['id', 'created_at', 'answers'], + required: ['id', 'child_id', 'age_group_id', 'created_at', 'answers'], title: 'MilestoneAnswerSessionPublic' } as const; @@ -354,6 +398,10 @@ export const MilestoneGroupAdminSchema = { type: 'integer', title: 'Id' }, + age_group_id: { + type: 'integer', + title: 'Age Group Id' + }, order: { type: 'integer', title: 'Order' @@ -376,7 +424,7 @@ export const MilestoneGroupAdminSchema = { } }, type: 'object', - required: ['id', 'order'], + required: ['id', 'age_group_id', 'order'], title: 'MilestoneGroupAdmin' } as const; diff --git a/frontend/src/lib/client/services.gen.ts b/frontend/src/lib/client/services.gen.ts index b307ca6c..58180382 100644 --- a/frontend/src/lib/client/services.gen.ts +++ b/frontend/src/lib/client/services.gen.ts @@ -15,11 +15,14 @@ import type { GetMilestoneData, GetMilestoneError, GetMilestoneResponse, + GetMilestoneGroupsData, GetMilestoneGroupsError, GetMilestoneGroupsResponse, GetMilestoneGroupData, GetMilestoneGroupError, GetMilestoneGroupResponse, + GetMilestoneAgeGroupsError, + GetMilestoneAgeGroupsResponse, GetUserQuestionsError, GetUserQuestionsResponse, CreateLanguageData, @@ -31,8 +34,19 @@ import type { UpdateI18NData, UpdateI18NError, UpdateI18NResponse, + UpdateMilestoneAgeGroupData, + UpdateMilestoneAgeGroupError, + UpdateMilestoneAgeGroupResponse, + CreateMilestoneAgeGroupData, + CreateMilestoneAgeGroupError, + CreateMilestoneAgeGroupResponse, + DeleteMilestoneAgeGroupData, + DeleteMilestoneAgeGroupError, + DeleteMilestoneAgeGroupResponse, + GetMilestoneGroupsAdminData, GetMilestoneGroupsAdminError, GetMilestoneGroupsAdminResponse, + CreateMilestoneGroupAdminData, CreateMilestoneGroupAdminError, CreateMilestoneGroupAdminResponse, UpdateMilestoneGroupAdminData, @@ -169,7 +183,7 @@ export const getMilestone = ( * Get Milestone Groups */ export const getMilestoneGroups = ( - options?: Options + options: Options ) => { return (options?.client ?? client).get< GetMilestoneGroupsResponse, @@ -197,6 +211,22 @@ export const getMilestoneGroup = ( }); }; +/** + * Get Milestone Age Groups + */ +export const getMilestoneAgeGroups = ( + options?: Options +) => { + return (options?.client ?? client).get< + GetMilestoneAgeGroupsResponse, + GetMilestoneAgeGroupsError, + ThrowOnError + >({ + ...options, + url: '/milestone-age-groups/' + }); +}; + /** * Get User Questions */ @@ -257,11 +287,59 @@ export const updateI18N = ( }); }; +/** + * Update Milestone Age Group + */ +export const updateMilestoneAgeGroup = ( + options: Options +) => { + return (options?.client ?? client).put< + UpdateMilestoneAgeGroupResponse, + UpdateMilestoneAgeGroupError, + ThrowOnError + >({ + ...options, + url: '/admin/milestone-age-groups/' + }); +}; + +/** + * Create Milestone Age Group + */ +export const createMilestoneAgeGroup = ( + options: Options +) => { + return (options?.client ?? client).post< + CreateMilestoneAgeGroupResponse, + CreateMilestoneAgeGroupError, + ThrowOnError + >({ + ...options, + url: '/admin/milestone-age-groups/' + }); +}; + +/** + * Delete Milestone Age Group + */ +export const deleteMilestoneAgeGroup = ( + options: Options +) => { + return (options?.client ?? client).delete< + DeleteMilestoneAgeGroupResponse, + DeleteMilestoneAgeGroupError, + ThrowOnError + >({ + ...options, + url: '/admin/milestone-age-groups/{milestone_age_group_id}' + }); +}; + /** * Get Milestone Groups Admin */ export const getMilestoneGroupsAdmin = ( - options?: Options + options: Options ) => { return (options?.client ?? client).get< GetMilestoneGroupsAdminResponse, @@ -277,7 +355,7 @@ export const getMilestoneGroupsAdmin = ( * Create Milestone Group Admin */ export const createMilestoneGroupAdmin = ( - options?: Options + options: Options ) => { return (options?.client ?? client).post< CreateMilestoneGroupAdminResponse, @@ -285,7 +363,7 @@ export const createMilestoneGroupAdmin = ( ThrowOnError >({ ...options, - url: '/admin/milestone-groups/' + url: '/admin/milestone-groups/{milestone_age_group_id}' }); }; diff --git a/frontend/src/lib/client/types.gen.ts b/frontend/src/lib/client/types.gen.ts index 5982f122..2b02b353 100644 --- a/frontend/src/lib/client/types.gen.ts +++ b/frontend/src/lib/client/types.gen.ts @@ -84,6 +84,17 @@ export type MilestoneAdmin = { images?: Array; }; +export type MilestoneAgeGroupCreate = { + months_min: number; + months_max: number; +}; + +export type MilestoneAgeGroupPublic = { + months_min: number; + months_max: number; + id: number; +}; + export type MilestoneAnswerPublic = { milestone_id: number; answer: number; @@ -91,6 +102,8 @@ export type MilestoneAnswerPublic = { export type MilestoneAnswerSessionPublic = { id: number; + child_id: number; + age_group_id: number; created_at: string; answers: { [key: string]: MilestoneAnswerPublic; @@ -99,6 +112,7 @@ export type MilestoneAnswerSessionPublic = { export type MilestoneGroupAdmin = { id: number; + age_group_id: number; order: number; text?: { [key: string]: MilestoneGroupText; @@ -246,9 +260,15 @@ export type GetMilestoneResponse = MilestonePublic; export type GetMilestoneError = HTTPValidationError; +export type GetMilestoneGroupsData = { + query: { + milestone_age_group_id: number; + }; +}; + export type GetMilestoneGroupsResponse = Array; -export type GetMilestoneGroupsError = unknown; +export type GetMilestoneGroupsError = HTTPValidationError; export type GetMilestoneGroupData = { path: { @@ -260,6 +280,10 @@ export type GetMilestoneGroupResponse = MilestoneGroupPublic; export type GetMilestoneGroupError = HTTPValidationError; +export type GetMilestoneAgeGroupsResponse = Array; + +export type GetMilestoneAgeGroupsError = unknown; + export type GetUserQuestionsResponse = Array; export type GetUserQuestionsError = unknown; @@ -297,13 +321,51 @@ export type UpdateI18NResponse = unknown; export type UpdateI18NError = HTTPValidationError; +export type UpdateMilestoneAgeGroupData = { + body: MilestoneAgeGroupPublic; +}; + +export type UpdateMilestoneAgeGroupResponse = MilestoneAgeGroupPublic; + +export type UpdateMilestoneAgeGroupError = HTTPValidationError; + +export type CreateMilestoneAgeGroupData = { + body: MilestoneAgeGroupCreate; +}; + +export type CreateMilestoneAgeGroupResponse = MilestoneAgeGroupPublic; + +export type CreateMilestoneAgeGroupError = HTTPValidationError; + +export type DeleteMilestoneAgeGroupData = { + path: { + milestone_age_group_id: number; + }; +}; + +export type DeleteMilestoneAgeGroupResponse = unknown; + +export type DeleteMilestoneAgeGroupError = HTTPValidationError; + +export type GetMilestoneGroupsAdminData = { + query: { + milestone_age_group_id: number; + }; +}; + export type GetMilestoneGroupsAdminResponse = Array; -export type GetMilestoneGroupsAdminError = unknown; +export type GetMilestoneGroupsAdminError = HTTPValidationError; + +export type CreateMilestoneGroupAdminData = { + path: { + milestone_age_group_id: number; + }; +}; export type CreateMilestoneGroupAdminResponse = MilestoneGroupAdmin; -export type CreateMilestoneGroupAdminError = unknown; +export type CreateMilestoneGroupAdminError = HTTPValidationError; export type UpdateMilestoneGroupAdminData = { body: MilestoneGroupAdmin; diff --git a/frontend/src/lib/components/Admin/MilestoneAgeGroups.svelte b/frontend/src/lib/components/Admin/MilestoneAgeGroups.svelte new file mode 100644 index 00000000..9cbee600 --- /dev/null +++ b/frontend/src/lib/components/Admin/MilestoneAgeGroups.svelte @@ -0,0 +1,96 @@ + + + + + + {#if milestoneAgeGroups.value} + + + ID + {$_('admin.min-age-months')} + {$_('admin.max-age-months')} + {$_('admin.actions')} + + + {#each milestoneAgeGroups.value as milestoneAgeGroup (milestoneAgeGroup.id)} + + + {milestoneAgeGroup.id} + + + + + + + + + { + milestoneAgeGroups.save(milestoneAgeGroup); + }} + /> + { + currentMilestoneAgeGroupId = milestoneAgeGroup.id; + showDeleteModal = true; + }} + /> + + + {/each} + + + + + + + + + + { + milestoneAgeGroups.create(newMilestoneAgeGroup); + }} + /> + + + +
+ {/if} +
+ + { + if (currentMilestoneAgeGroupId) { + milestoneAgeGroups.delete(currentMilestoneAgeGroupId); + } + }} +> diff --git a/frontend/src/lib/components/Admin/MilestoneGroups.svelte b/frontend/src/lib/components/Admin/MilestoneGroups.svelte index b0bafdc0..182599b7 100644 --- a/frontend/src/lib/components/Admin/MilestoneGroups.svelte +++ b/frontend/src/lib/components/Admin/MilestoneGroups.svelte @@ -31,6 +31,8 @@ import type { MilestoneAdmin, MilestoneGroupAdmin } from '$lib/client/types.gen'; import { onMount } from 'svelte'; + let { milestone_age_group_id }: { milestone_age_group_id: number } = $props(); + let currentMilestoneGroup = $state(null as MilestoneGroupAdmin | null); let openMilestoneGroupIndex = $state(null as number | null); let showEditMilestoneGroupModal = $state(false); @@ -49,7 +51,9 @@ } async function addMilestoneGroup() { - const { data, error } = await createMilestoneGroupAdmin(); + const { data, error } = await createMilestoneGroupAdmin({ + path: { milestone_age_group_id: milestone_age_group_id } + }); if (error || data === undefined) { console.log(error); currentMilestoneGroup = null; @@ -118,13 +122,13 @@ - {#if milestoneGroups} + {#if `${milestone_age_group_id}` in $milestoneGroups} {$_('admin.milestone-groups')} - {#each $milestoneGroups as milestoneGroup, groupIndex (milestoneGroup.id)} + {#each $milestoneGroups[`${milestone_age_group_id}`] as milestoneGroup, groupIndex (milestoneGroup.id)} {@const groupTitle = milestoneGroup.text[$lang_id]?.title} { diff --git a/frontend/src/lib/stores/adminStore.ts b/frontend/src/lib/stores/adminStore.ts index c14515ef..bae4dbcb 100644 --- a/frontend/src/lib/stores/adminStore.ts +++ b/frontend/src/lib/stores/adminStore.ts @@ -1,6 +1,6 @@ import { writable, type Writable } from 'svelte/store'; import type { MilestoneGroupAdmin, UserQuestionAdmin } from '$lib/client/types.gen'; -export const milestoneGroups: Writable> = writable([]); +export const milestoneGroups: Writable>> = writable({}); export const userQuestions: Writable> = writable([]); diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 533c522e..0c7e6009 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -19,9 +19,11 @@ "languages": "Sprachen", "milestones": "Meilensteine", "milestone-groups": "Meilensteingruppen", + "milestone-age-groups": "Meilenstein-Altersgruppen", "translations": "Übersetzungen", "user-questions": "Benutzerfragen", "question": "Frage", + "actions": "Aktionen", "add": "Hinzufügen", "edit": "Bearbeiten", "delete": "Löschen", @@ -35,7 +37,9 @@ "obs": "Beobachtungshinweise", "help": "Förderhilfen", "image": "Bild", - "images": "Bilder" + "images": "Bilder", + "min-age-months": "Mindestalter in Monaten", + "max-age-months": "Höchstalter in Monaten" }, "registration": { "heading": "Als neuer Benutzer registrieren", diff --git a/frontend/src/routes/admin/+page.svelte b/frontend/src/routes/admin/+page.svelte index a0d56a8d..4091718f 100644 --- a/frontend/src/routes/admin/+page.svelte +++ b/frontend/src/routes/admin/+page.svelte @@ -1,19 +1,26 @@ @@ -23,13 +30,17 @@ {:else} - -
- - {$_('admin.milestones')} -
- -
+ {#if milestoneAgeGroups.value} + {#each milestoneAgeGroups.value as milestoneAgeGroup (milestoneAgeGroup.id)} + +
+ + {`${$_('admin.milestones')} (${milestoneAgeGroup.months_min}m-${milestoneAgeGroup.months_max}m)`} +
+ +
+ {/each} + {/if}
@@ -37,6 +48,13 @@
+ +
+ + {$_('admin.milestone-age-groups')} +
+ +
diff --git a/frontend/src/routes/milestone/+page.svelte b/frontend/src/routes/milestone/+page.svelte index 39c63ee7..4ad4dc40 100644 --- a/frontend/src/routes/milestone/+page.svelte +++ b/frontend/src/routes/milestone/+page.svelte @@ -15,7 +15,14 @@ let childId = $state(undefined as number | undefined); async function updateMilestoneGroups() { - const { data, error } = await getMilestoneGroups(); + if (!milestoneAnswerSession) { + return; + } + const { data, error } = await getMilestoneGroups({ + query: { + milestone_age_group_id: milestoneAnswerSession.age_group_id + } + }); if (error || data === undefined) { console.log(error); } else { @@ -50,8 +57,8 @@ } onMount(async () => { - await updateMilestoneGroups(); await updateMilestoneAnswerSession(); + await updateMilestoneGroups(); }); diff --git a/mondey_backend/openapi.json b/mondey_backend/openapi.json index 42b7d05c..62d097c9 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": []}]}, "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 +{"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", "parameters": [{"name": "milestone_age_group_id", "in": "query", "required": true, "schema": {"type": "integer", "title": "Milestone Age Group Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/MilestoneGroupPublic"}, "title": "Response Get Milestone Groups Milestone Groups Get"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/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"}}}}}}}, "/milestone-age-groups/": {"get": {"tags": ["milestones"], "summary": "Get Milestone Age Groups", "operationId": "get_milestone_age_groups", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/MilestoneAgeGroupPublic"}, "type": "array", "title": "Response Get Milestone Age Groups Milestone Age Groups Get"}}}}}}}, "/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-age-groups/": {"put": {"tags": ["admin"], "summary": "Update Milestone Age Group", "operationId": "update_milestone_age_group", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneAgeGroupPublic"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneAgeGroupPublic"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}, "post": {"tags": ["admin"], "summary": "Create Milestone Age Group", "operationId": "create_milestone_age_group", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneAgeGroupCreate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneAgeGroupPublic"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/admin/milestone-age-groups/{milestone_age_group_id}": {"delete": {"tags": ["admin"], "summary": "Delete Milestone Age Group", "operationId": "delete_milestone_age_group", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_age_group_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Age 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-groups/": {"get": {"tags": ["admin"], "summary": "Get Milestone Groups Admin", "operationId": "get_milestone_groups_admin", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_age_group_id", "in": "query", "required": true, "schema": {"type": "integer", "title": "Milestone Age Group Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/MilestoneGroupAdmin"}, "title": "Response Get Milestone Groups Admin Admin Milestone Groups Get"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/milestone-groups/{milestone_age_group_id}": {"post": {"tags": ["admin"], "summary": "Create Milestone Group Admin", "operationId": "create_milestone_group_admin", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_age_group_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Age Group Id"}}], "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"}}}}}}}, "/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"}, "MilestoneAgeGroupCreate": {"properties": {"months_min": {"type": "integer", "title": "Months Min"}, "months_max": {"type": "integer", "title": "Months Max"}}, "type": "object", "required": ["months_min", "months_max"], "title": "MilestoneAgeGroupCreate"}, "MilestoneAgeGroupPublic": {"properties": {"months_min": {"type": "integer", "title": "Months Min"}, "months_max": {"type": "integer", "title": "Months Max"}, "id": {"type": "integer", "title": "Id"}}, "type": "object", "required": ["months_min", "months_max", "id"], "title": "MilestoneAgeGroupPublic"}, "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"}, "child_id": {"type": "integer", "title": "Child Id"}, "age_group_id": {"type": "integer", "title": "Age Group 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", "child_id", "age_group_id", "created_at", "answers"], "title": "MilestoneAnswerSessionPublic"}, "MilestoneGroupAdmin": {"properties": {"id": {"type": "integer", "title": "Id"}, "age_group_id": {"type": "integer", "title": "Age Group 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", "age_group_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/milestones.py b/mondey_backend/src/mondey_backend/models/milestones.py index aa1aeff5..ad9ea109 100644 --- a/mondey_backend/src/mondey_backend/models/milestones.py +++ b/mondey_backend/src/mondey_backend/models/milestones.py @@ -3,14 +3,12 @@ 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 dict_relationship from .utils import fixed_length_string_field # Note: models with relationships are defined in the same file to @@ -26,6 +24,26 @@ class LanguageCreate(SQLModel): lang: str = fixed_length_string_field(2, index=True) +## MilestoneAgeGroup + + +class MilestoneAgeGroupBase(SQLModel): + months_min: int + months_max: int + + +class MilestoneAgeGroupCreate(MilestoneAgeGroupBase): + pass + + +class MilestoneAgeGroup(MilestoneAgeGroupBase, table=True): + id: int | None = Field(default=None, primary_key=True) + + +class MilestoneAgeGroupPublic(MilestoneAgeGroupBase): + id: int + + ## MilestoneGroupText @@ -57,13 +75,9 @@ class MilestoneGroupTextPublic(MilestoneGroupTextBase): class MilestoneGroup(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) + age_group_id: int = Field(foreign_key="milestoneagegroup.id") order: int = 0 - text: Mapped[dict[int, MilestoneGroupText]] = Relationship( - sa_relationship=relationship( - collection_class=attribute_keyed_dict("lang_id"), - cascade="all, delete-orphan", - ) - ) + text: Mapped[dict[int, MilestoneGroupText]] = dict_relationship(key="lang_id") milestones: Mapped[list[Milestone]] = back_populates("group") @@ -75,6 +89,7 @@ class MilestoneGroupPublic(SQLModel): class MilestoneGroupAdmin(SQLModel): id: int + age_group_id: int order: int text: dict[int, MilestoneGroupText] = {} milestones: list[MilestoneAdmin] = [] @@ -111,12 +126,7 @@ class Milestone(SQLModel, table=True): group_id: int | None = Field(default=None, foreign_key="milestonegroup.id") order: int = 0 group: MilestoneGroup | None = back_populates("milestones") - text: Mapped[dict[int, MilestoneText]] = Relationship( - sa_relationship=relationship( - collection_class=attribute_keyed_dict("lang_id"), - cascade="all, delete-orphan", - ) - ) + text: Mapped[dict[int, MilestoneText]] = dict_relationship(key="lang_id") images: Mapped[list[MilestoneImage]] = back_populates("milestone") @@ -170,22 +180,20 @@ class MilestoneAnswer(SQLModel, table=True): class MilestoneAnswerSession(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) - child_id: int + child_id: int = Field(foreign_key="child.id") + age_group_id: int = Field(foreign_key="milestoneagegroup.id") 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", - ) - ) + answers: Mapped[dict[int, MilestoneAnswer]] = dict_relationship(key="milestone_id") class MilestoneAnswerSessionPublic(SQLModel): id: int + child_id: int + age_group_id: int created_at: datetime.datetime answers: dict[int, MilestoneAnswerPublic] diff --git a/mondey_backend/src/mondey_backend/models/utils.py b/mondey_backend/src/mondey_backend/models/utils.py index b82aae5d..d9069422 100644 --- a/mondey_backend/src/mondey_backend/models/utils.py +++ b/mondey_backend/src/mondey_backend/models/utils.py @@ -1,6 +1,7 @@ from __future__ import annotations from sqlalchemy import String +from sqlalchemy.orm import attribute_keyed_dict from sqlalchemy.orm import relationship from sqlmodel import Field from sqlmodel import Relationship @@ -12,6 +13,15 @@ def back_populates(name: str): return Relationship(sa_relationship=relationship(back_populates=name)) +def dict_relationship(key: str): + return Relationship( + sa_relationship=relationship( + collection_class=attribute_keyed_dict(key), + cascade="all, delete-orphan", + ) + ) + + def fixed_length_string_field(max_length: int, **kwargs): # bug in sqlmodel - max_length doesn't propagate to actual sqlalchemy column, so we explicitly set sa_type # https://github.com/fastapi/sqlmodel/discussions/746 diff --git a/mondey_backend/src/mondey_backend/routers/admin.py b/mondey_backend/src/mondey_backend/routers/admin.py index 22c8113d..c4b4b58b 100644 --- a/mondey_backend/src/mondey_backend/routers/admin.py +++ b/mondey_backend/src/mondey_backend/routers/admin.py @@ -14,6 +14,9 @@ from ..models.milestones import LanguageCreate from ..models.milestones import Milestone from ..models.milestones import MilestoneAdmin +from ..models.milestones import MilestoneAgeGroup +from ..models.milestones import MilestoneAgeGroupCreate +from ..models.milestones import MilestoneAgeGroupPublic from ..models.milestones import MilestoneGroup from ..models.milestones import MilestoneGroupAdmin from ..models.milestones import MilestoneGroupText @@ -59,16 +62,46 @@ async def update_i18n(language_id: int, i18dict: dict[str, dict[str, str]]): json.dump(i18dict, i18json_file, separators=(",", ":"), ensure_ascii=False) return {"ok": True} + @router.post("/milestone-age-groups/", response_model=MilestoneAgeGroupPublic) + def create_milestone_age_group( + session: SessionDep, milestone_age_group: MilestoneAgeGroupCreate + ): + db_milestone_age_group = MilestoneAgeGroup.model_validate(milestone_age_group) + add(session, db_milestone_age_group) + return db_milestone_age_group + + @router.put("/milestone-age-groups/", response_model=MilestoneAgeGroupPublic) + def update_milestone_age_group( + session: SessionDep, + milestone_age_group: MilestoneAgeGroupPublic, + ): + db_milestone_age_group = get(session, MilestoneAgeGroup, milestone_age_group.id) + for key, value in milestone_age_group.model_dump().items(): + setattr(db_milestone_age_group, key, value) + add(session, db_milestone_age_group) + return db_milestone_age_group + + @router.delete("/milestone-age-groups/{milestone_age_group_id}") + def delete_milestone_age_group(session: SessionDep, milestone_age_group_id: int): + milestone_age_group = get(session, MilestoneAgeGroup, milestone_age_group_id) + session.delete(milestone_age_group) + session.commit() + return {"ok": True} + @router.get("/milestone-groups/", response_model=list[MilestoneGroupAdmin]) - def get_milestone_groups_admin(session: SessionDep): + def get_milestone_groups_admin(session: SessionDep, milestone_age_group_id: int): milestone_groups = session.exec( - select(MilestoneGroup).order_by(col(MilestoneGroup.order)) + select(MilestoneGroup) + .where(col(MilestoneGroup.age_group_id) == milestone_age_group_id) + .order_by(col(MilestoneGroup.order)) ).all() return milestone_groups - @router.post("/milestone-groups/", response_model=MilestoneGroupAdmin) - def create_milestone_group_admin(session: SessionDep): - db_milestone_group = MilestoneGroup() + @router.post( + "/milestone-groups/{milestone_age_group_id}", response_model=MilestoneGroupAdmin + ) + def create_milestone_group_admin(session: SessionDep, milestone_age_group_id: int): + db_milestone_group = MilestoneGroup(age_group_id=milestone_age_group_id) add(session, db_milestone_group) for language in session.exec(select(Language)).all(): session.add( diff --git a/mondey_backend/src/mondey_backend/routers/milestones.py b/mondey_backend/src/mondey_backend/routers/milestones.py index f4b1cc99..3957691a 100644 --- a/mondey_backend/src/mondey_backend/routers/milestones.py +++ b/mondey_backend/src/mondey_backend/routers/milestones.py @@ -8,6 +8,8 @@ from ..dependencies import SessionDep from ..models.milestones import Language from ..models.milestones import Milestone +from ..models.milestones import MilestoneAgeGroup +from ..models.milestones import MilestoneAgeGroupPublic from ..models.milestones import MilestoneGroup from ..models.milestones import MilestoneGroupPublic from ..models.milestones import MilestonePublic @@ -44,9 +46,11 @@ def get_milestone(session: SessionDep, milestone_id: int): return milestone @router.get("/milestone-groups/", response_model=list[MilestoneGroupPublic]) - def get_milestone_groups(session: SessionDep): + def get_milestone_groups(session: SessionDep, milestone_age_group_id: int): milestone_groups = session.exec( - select(MilestoneGroup).order_by(col(MilestoneGroup.order)) + select(MilestoneGroup) + .where(col(MilestoneGroup.age_group_id) == milestone_age_group_id) + .order_by(col(MilestoneGroup.order)) ).all() return milestone_groups @@ -59,4 +63,11 @@ def get_milestone_group(session: SessionDep, milestone_group_id: int): raise HTTPException(status_code=404, detail="milestone_group not found") return milestone_group + @router.get("/milestone-age-groups/", response_model=list[MilestoneAgeGroupPublic]) + def get_milestone_age_groups(session: SessionDep): + milestone_age_groups = session.exec( + select(MilestoneAgeGroup).order_by(col(MilestoneAgeGroup.months_min)) + ).all() + return milestone_age_groups + return router diff --git a/mondey_backend/src/mondey_backend/routers/users.py b/mondey_backend/src/mondey_backend/routers/users.py index 6a9872dd..3da7690b 100644 --- a/mondey_backend/src/mondey_backend/routers/users.py +++ b/mondey_backend/src/mondey_backend/routers/users.py @@ -131,13 +131,10 @@ def update_milestone_answer( milestone_answer_session_id: int, answer: MilestoneAnswerPublic, ): - milestone_answer_session = session.get( - MilestoneAnswerSession, milestone_answer_session_id + milestone_answer_session = get( + session, MilestoneAnswerSession, milestone_answer_session_id ) - if ( - milestone_answer_session is None - or milestone_answer_session.user_id != current_active_user.id - ): + if 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: diff --git a/mondey_backend/src/mondey_backend/routers/utils.py b/mondey_backend/src/mondey_backend/routers/utils.py index 3c0bf615..4824fbd5 100644 --- a/mondey_backend/src/mondey_backend/routers/utils.py +++ b/mondey_backend/src/mondey_backend/routers/utils.py @@ -14,6 +14,7 @@ from ..dependencies import SessionDep from ..models.children import Child from ..models.milestones import MilestoneAdmin +from ..models.milestones import MilestoneAgeGroup from ..models.milestones import MilestoneAnswerSession from ..models.milestones import MilestoneGroupAdmin from ..models.milestones import MilestoneGroupText @@ -93,17 +94,36 @@ def _session_has_expired(milestone_answer_session: MilestoneAnswerSession) -> bo ) +def get_child_age_in_months(child: Child) -> int: + today = datetime.date.today() + return (today.year - child.birth_year) * 12 + (today.month - child.birth_month) + + +def get_milestone_age_group_id(session: SessionDep, child: Child) -> int: + child_age_in_months = get_child_age_in_months(child) + milestone_age_group = session.exec( + select(MilestoneAgeGroup).where( + (col(MilestoneAgeGroup.months_min) <= child_age_in_months) + & (child_age_in_months < col(MilestoneAgeGroup.months_max)) + ) + ).one_or_none() + if milestone_age_group is None or milestone_age_group.id is None: + raise HTTPException(404, "No milestone age group found for this child") + return milestone_age_group.id + + 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_age_group_id = get_milestone_age_group_id(session, child) milestone_answer_session = session.exec( select(MilestoneAnswerSession) .where( - col(MilestoneAnswerSession.user_id) == current_active_user.id - and col(MilestoneAnswerSession.child_id) == child_id + (col(MilestoneAnswerSession.user_id) == current_active_user.id) + & (col(MilestoneAnswerSession.child_id) == child_id) ) .order_by(col(MilestoneAnswerSession.created_at).desc()) ).first() @@ -112,6 +132,7 @@ def get_or_create_current_milestone_answer_session( ): milestone_answer_session = MilestoneAnswerSession( child_id=child_id, + age_group_id=milestone_age_group_id, user_id=current_active_user.id, created_at=datetime.datetime.now(), ) diff --git a/mondey_backend/tests/conftest.py b/mondey_backend/tests/conftest.py index c0e68c49..1a23729a 100644 --- a/mondey_backend/tests/conftest.py +++ b/mondey_backend/tests/conftest.py @@ -20,6 +20,7 @@ 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 MilestoneAgeGroup from mondey_backend.models.milestones import MilestoneAnswer from mondey_backend.models.milestones import MilestoneAnswerSession from mondey_backend.models.milestones import MilestoneGroup @@ -69,8 +70,11 @@ def session(): for lang in ["de", "en", "fr"]: session.add(Language(lang=lang)) lang_ids = [1, 2, 3] + # add two milestone age groups + session.add(MilestoneAgeGroup(months_min=0, months_max=36, years=lang_ids)) + session.add(MilestoneAgeGroup(months_min=36, months_max=72, years=lang_ids)) # add a milestone group with 3 milestones - session.add(MilestoneGroup(order=2)) + session.add(MilestoneGroup(order=2, age_group_id=1)) for lang_id in lang_ids: lbl = f"g1_l{lang_id}" session.add( @@ -93,7 +97,7 @@ def session(): ) ) # add a second milestone group with 2 milestones - session.add(MilestoneGroup(order=1)) + session.add(MilestoneGroup(order=1, age_group_id=1)) for lang_id in lang_ids: lbl = f"g1_l{lang_id}" session.add( @@ -120,41 +124,50 @@ def session(): session.add(MilestoneImage(milestone_id=1, filename="m2.jpg", approved=True)) session.add(MilestoneImage(milestone_id=2, filename="m3.jpg", approved=True)) session.commit() - # add 2 children for user 1 + # add a ~1 yr old child for user 1 + today = datetime.datetime.today() session.add( Child( user_id=1, name="child1", - birth_year=2022, - birth_month=3, + birth_year=today.year - 1, + birth_month=today.month, has_image=False, ) ) + # add a ~4 yr old child for user 2 session.add( Child( user_id=1, name="child2", - birth_year=2024, + birth_year=today.year - 4, birth_month=12, has_image=True, ) ) - # add a child for user 3 + # add a ~5 year old child for user 3 session.add( Child( - user_id=3, name="child3", birth_year=2021, birth_month=1, has_image=True + user_id=3, + name="child3", + birth_year=today.year - 5, + birth_month=7, + 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) + child_id=1, + user_id=1, + age_group_id=1, + created_at=datetime.datetime(today.year - 1, 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() + child_id=1, user_id=1, age_group_id=1, created_at=today ) ) # add two milestone answers @@ -163,7 +176,10 @@ def session(): # 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) + child_id=3, + user_id=3, + age_group_id=2, + created_at=datetime.datetime(today.year - 1, 1, 1), ) ) session.add(MilestoneAnswer(answer_session_id=3, milestone_id=7, answer=2)) @@ -268,6 +284,34 @@ def jpg_file(tmp_path: pathlib.Path): return jpg_path +@pytest.fixture +def children(): + today = datetime.datetime.today() + return [ + { + "birth_month": today.month, + "birth_year": today.year - 1, + "id": 1, + "name": "child1", + "has_image": False, + }, + { + "birth_month": 12, + "birth_year": today.year - 4, + "id": 2, + "name": "child2", + "has_image": True, + }, + { + "birth_month": 7, + "birth_year": today.year - 5, + "id": 3, + "name": "child3", + "has_image": True, + }, + ] + + @pytest.fixture def milestone_group1(): return { @@ -361,6 +405,7 @@ def milestone_group1(): def milestone_group_admin1(): return { "id": 1, + "age_group_id": 1, "order": 2, "text": { "1": {"group_id": 1, "desc": "g1_l1_d", "title": "g1_l1_t", "lang_id": 1}, @@ -554,6 +599,7 @@ def milestone_group2(): def milestone_group_admin2(): return { "id": 2, + "age_group_id": 1, "order": 1, "text": { "1": {"group_id": 2, "desc": "g1_l1_d", "title": "g1_l1_t", "lang_id": 1}, diff --git a/mondey_backend/tests/routers/test_admin.py b/mondey_backend/tests/routers/test_admin.py index 50cdabd4..61405618 100644 --- a/mondey_backend/tests/routers/test_admin.py +++ b/mondey_backend/tests/routers/test_admin.py @@ -45,20 +45,49 @@ def test_update_i18n_invalid_json(admin_client: TestClient, static_dir: pathlib. assert response.status_code == 422 -def test_get_milestone_groups( +def test_get_milestone_groups_age_group_1( admin_client: TestClient, milestone_group_admin1: dict, milestone_group_admin2: dict ): - response = admin_client.get("/admin/milestone-groups/") + response = admin_client.get("/admin/milestone-groups/?milestone_age_group_id=1") assert response.status_code == 200 assert len(response.json()) == 2 assert response.json() == [milestone_group_admin2, milestone_group_admin1] +def test_get_milestone_groups_age_group_2( + admin_client: TestClient, milestone_group_admin1: dict, milestone_group_admin2: dict +): + response = admin_client.get("/admin/milestone-groups/?milestone_age_group_id=2") + assert response.status_code == 200 + assert response.json() == [] + + +def test_create_update_and_delete_milestone_age_group(admin_client: TestClient): + assert admin_client.get("/milestone-age-groups/").json()[-1]["id"] == 2 + response = admin_client.post( + "/admin/milestone-age-groups/", json={"months_min": 73, "months_max": 75} + ) + assert response.status_code == 200 + assert response.json() == {"id": 3, "months_min": 73, "months_max": 75} + assert admin_client.get("/milestone-age-groups/").json()[-1] == response.json() + response = admin_client.put( + "/admin/milestone-age-groups/", + json={"id": 3, "months_min": 97, "months_max": 111}, + ) + assert response.status_code == 200 + assert response.json() == {"id": 3, "months_min": 97, "months_max": 111} + assert admin_client.get("/milestone-age-groups/").json()[-1] == response.json() + response = admin_client.delete("/admin/milestone-age-groups/3") + assert response.status_code == 200 + assert admin_client.get("/milestone-age-groups/").json()[-1]["id"] == 2 + + def test_post_milestone_group(admin_client: TestClient): - response = admin_client.post("/admin/milestone-groups/") + response = admin_client.post("/admin/milestone-groups/1") assert response.status_code == 200 assert response.json() == { "id": 3, + "age_group_id": 1, "order": 0, "text": { "1": { diff --git a/mondey_backend/tests/routers/test_milestones.py b/mondey_backend/tests/routers/test_milestones.py index 9c9c8813..09b2739d 100644 --- a/mondey_backend/tests/routers/test_milestones.py +++ b/mondey_backend/tests/routers/test_milestones.py @@ -11,7 +11,7 @@ def test_get_languages(self, client_type: str, request: pytest.FixtureRequest): assert response.status_code == 200 assert response.json() == {"de": 1, "en": 2, "fr": 3} - def test_get_milestone_groups( + def test_get_milestone_groups_age_group_1( self, client_type: str, milestone_group1: dict, @@ -19,11 +19,31 @@ def test_get_milestone_groups( request: pytest.FixtureRequest, ): client = request.getfixturevalue(client_type) - response = client.get("/milestone-groups/") + response = client.get("/milestone-groups/?milestone_age_group_id=1") assert response.status_code == 200 assert len(response.json()) == 2 assert response.json() == [milestone_group2, milestone_group1] + def test_get_milestone_groups_age_group_2( + self, + client_type: str, + request: pytest.FixtureRequest, + ): + client = request.getfixturevalue(client_type) + response = client.get("/milestone-groups/?milestone_age_group_id=2") + assert response.status_code == 200 + assert response.json() == [] + + def test_get_milestone_groups_invalid_age_group( + self, + client_type: str, + request: pytest.FixtureRequest, + ): + client = request.getfixturevalue(client_type) + response = client.get("/milestone-groups/?milestone_age_group_id=67") + assert response.status_code == 200 + assert response.json() == [] + def test_get_milestone_group( self, client_type: str, @@ -45,3 +65,19 @@ def test_get_milestone_group_invalid_id( client = request.getfixturevalue(client_type) response = client.get("/milestone-groups/99") assert response.status_code == 404 + + def test_get_milestone_age_groups( + self, + client_type: str, + milestone_group1: dict, + milestone_group2: dict, + request: pytest.FixtureRequest, + ): + client = request.getfixturevalue(client_type) + response = client.get("/milestone-age-groups/") + assert response.status_code == 200 + assert len(response.json()) == 2 + assert response.json() == [ + {"id": 1, "months_min": 0, "months_max": 36}, + {"id": 2, "months_min": 36, "months_max": 72}, + ] diff --git a/mondey_backend/tests/routers/test_users.py b/mondey_backend/tests/routers/test_users.py index fa865ecb..aa466b93 100644 --- a/mondey_backend/tests/routers/test_users.py +++ b/mondey_backend/tests/routers/test_users.py @@ -11,25 +11,12 @@ def _is_approx_now(iso_date_string: str, delta=datetime.timedelta(hours=1)) -> b ) -def test_get_children(user_client: TestClient): +def test_get_children( + user_client: TestClient, children: list[dict[str, str | bool | int]] +): response = user_client.get("/users/children/") assert response.status_code == 200 - assert response.json() == [ - { - "birth_month": 3, - "birth_year": 2022, - "id": 1, - "name": "child1", - "has_image": False, - }, - { - "birth_month": 12, - "birth_year": 2024, - "id": 2, - "name": "child2", - "has_image": True, - }, - ] + assert response.json() == [children[0], children[1]] def test_get_children_no_children(research_client: TestClient): @@ -38,18 +25,12 @@ def test_get_children_no_children(research_client: TestClient): assert response.json() == [] -def test_get_children_admin_user_1_child(admin_client: TestClient): +def test_get_children_admin_user_1_child( + admin_client: TestClient, children: list[dict[str, str | bool | int]] +): response = admin_client.get("/users/children/") assert response.status_code == 200 - assert response.json() == [ - { - "birth_month": 1, - "birth_year": 2021, - "id": 3, - "name": "child3", - "has_image": True, - } - ] + assert response.json() == [children[2]] def test_get_children_invalid_user(public_client: TestClient): @@ -128,28 +109,34 @@ def test_upload_child_image( (children_dir / "1.jpg").unlink() -def test_get_milestone_answers_user_does_not_own_child(admin_client: TestClient): +def test_get_milestone_answers_child1_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): +def test_get_milestone_answers_child8_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): +def test_get_milestone_answers_child3_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 response.json()["child_id"] == 3 + assert response.json()["age_group_id"] == 2 assert _is_approx_now(response.json()["created_at"]) assert response.json()["answers"] == {} -def test_get_milestone_answers_current_answer_session(user_client: TestClient): +def test_get_milestone_answers_child1_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()["child_id"] == 1 + assert response.json()["age_group_id"] == 1 assert response.json()["answers"] == { "1": {"milestone_id": 1, "answer": 0}, "2": {"milestone_id": 2, "answer": 3}, @@ -157,10 +144,12 @@ def test_get_milestone_answers_current_answer_session(user_client: TestClient): assert _is_approx_now(response.json()["created_at"]) -def test_update_milestone_answer_current_answer_session_no_existing_answer( +def test_update_milestone_answer_current_answer_session_no_answer_session( user_client: TestClient, ): current_answer_session = user_client.get("/users/milestone-answers/1").json() + assert current_answer_session["child_id"] == 1 + assert current_answer_session["age_group_id"] == 1 assert "6" not in current_answer_session["answers"] new_answer = {"milestone_id": 6, "answer": 2} response = user_client.put( @@ -200,4 +189,4 @@ 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 + assert response.status_code == 404