From 6963a428c4ee8e6a1c6677a59292feaa6667d0f1 Mon Sep 17 00:00:00 2001 From: Liam Keegan Date: Tue, 15 Oct 2024 11:04:51 +0200 Subject: [PATCH] Add Children to backend - add Child model and routes - only the user who owns the child can access these routes - add STATIC_FILES_PATH to settings - this is where non-public files are stored, e.g. child images - fix user dependency injection in tests - add public client with no user authentification to tests - resolves #111 --- frontend/src/lib/client/schemas.gen.ts | 59 +++++++++++ frontend/src/lib/client/services.gen.ts | 98 +++++++++++++++++++ frontend/src/lib/client/types.gen.ts | 68 +++++++++++++ mondey_backend/openapi.json | 2 +- .../src/mondey_backend/dependencies.py | 2 + mondey_backend/src/mondey_backend/main.py | 2 +- .../src/mondey_backend/models/children.py | 24 +++++ .../src/mondey_backend/models/milestones.py | 12 --- .../src/mondey_backend/routers/research.py | 4 +- .../src/mondey_backend/routers/users.py | 93 ++++++++++++++++++ .../src/mondey_backend/routers/utils.py | 2 + mondey_backend/src/mondey_backend/settings.py | 5 +- mondey_backend/tests/conftest.py | 60 +++++++++++- mondey_backend/tests/routers/test_admin.py | 4 +- .../tests/routers/test_milestones.py | 2 +- mondey_backend/tests/routers/test_users.py | 80 +++++++++++++++ 16 files changed, 491 insertions(+), 26 deletions(-) create mode 100644 mondey_backend/src/mondey_backend/models/children.py create mode 100644 mondey_backend/tests/routers/test_users.py diff --git a/frontend/src/lib/client/schemas.gen.ts b/frontend/src/lib/client/schemas.gen.ts index d620e66f..5eedeeb9 100644 --- a/frontend/src/lib/client/schemas.gen.ts +++ b/frontend/src/lib/client/schemas.gen.ts @@ -84,6 +84,19 @@ export const Body_reset_reset_password_auth_reset_password_postSchema = { title: 'Body_reset_reset_password_auth_reset_password_post' } as const; +export const Body_upload_child_image_users_children_images__child_id__putSchema = { + properties: { + file: { + type: 'string', + format: 'binary', + title: 'File' + } + }, + type: 'object', + required: ['file'], + title: 'Body_upload_child_image_users_children_images__child_id__put' +} as const; + export const Body_upload_milestone_group_image_admin_milestone_group_images__milestone_group_id__putSchema = { properties: { @@ -136,6 +149,52 @@ export const Body_verify_verify_auth_verify_postSchema = { title: 'Body_verify_verify_auth_verify_post' } as const; +export const ChildCreateSchema = { + 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' +} as const; + +export const ChildPublicSchema = { + 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' + } + }, + type: 'object', + required: ['birth_year', 'birth_month', 'id'], + title: 'ChildPublic' +} as const; + export const ErrorModelSchema = { properties: { detail: { diff --git a/frontend/src/lib/client/services.gen.ts b/frontend/src/lib/client/services.gen.ts index 4ff9e6e6..69301e78 100644 --- a/frontend/src/lib/client/services.gen.ts +++ b/frontend/src/lib/client/services.gen.ts @@ -80,6 +80,23 @@ import type { UsersDeleteUserData, UsersDeleteUserError, UsersDeleteUserResponse, + GetChildrenError, + GetChildrenResponse, + CreateChildData, + CreateChildError, + CreateChildResponse, + UpdateChildData, + UpdateChildError, + UpdateChildResponse, + DeleteChildData, + DeleteChildError, + DeleteChildResponse, + GetChildImageData, + GetChildImageError, + GetChildImageResponse, + UploadChildImageData, + UploadChildImageError, + UploadChildImageResponse, AuthCookieLoginData, AuthCookieLoginError, AuthCookieLoginResponse, @@ -528,6 +545,87 @@ export const usersDeleteUser = ( }); }; +/** + * Get Children + */ +export const getChildren = ( + options?: Options +) => { + return (options?.client ?? client).get({ + ...options, + url: '/users/children/' + }); +}; + +/** + * Create Child + */ +export const createChild = ( + options: Options +) => { + return (options?.client ?? client).post({ + ...options, + url: '/users/children/' + }); +}; + +/** + * Update Child + */ +export const updateChild = ( + options: Options +) => { + return (options?.client ?? client).put({ + ...options, + url: '/users/children' + }); +}; + +/** + * Delete Child + */ +export const deleteChild = ( + options: Options +) => { + return (options?.client ?? client).delete({ + ...options, + url: '/users/children/{child_id}' + }); +}; + +/** + * Get Child Image + */ +export const getChildImage = ( + options: Options +) => { + return (options?.client ?? client).get({ + ...options, + url: '/users/children-images/{child_id}' + }); +}; + +/** + * Upload Child Image + */ +export const uploadChildImage = ( + options: Options +) => { + return (options?.client ?? client).put< + UploadChildImageResponse, + UploadChildImageError, + ThrowOnError + >({ + ...options, + ...formDataBodySerializer, + headers: { + 'Content-Type': null, + ...options?.headers + }, + url: '/users/children-images/{child_id}' + }); +}; + /** * Auth:Cookie.Login */ diff --git a/frontend/src/lib/client/types.gen.ts b/frontend/src/lib/client/types.gen.ts index c652c550..f2adf22e 100644 --- a/frontend/src/lib/client/types.gen.ts +++ b/frontend/src/lib/client/types.gen.ts @@ -18,6 +18,10 @@ export type Body_reset_reset_password_auth_reset_password_post = { password: string; }; +export type Body_upload_child_image_users_children_images__child_id__put = { + file: Blob | File; +}; + export type Body_upload_milestone_group_image_admin_milestone_group_images__milestone_group_id__put = { file: Blob | File; @@ -35,6 +39,19 @@ export type Body_verify_verify_auth_verify_post = { token: string; }; +export type ChildCreate = { + name?: string; + birth_year: number; + birth_month: number; +}; + +export type ChildPublic = { + name?: string; + birth_year: number; + birth_month: number; + id: number; +}; + export type ErrorModel = { detail: | string @@ -411,6 +428,57 @@ export type UsersDeleteUserResponse = void; export type UsersDeleteUserError = unknown | HTTPValidationError; +export type GetChildrenResponse = Array; + +export type GetChildrenError = unknown; + +export type CreateChildData = { + body: ChildCreate; +}; + +export type CreateChildResponse = ChildPublic; + +export type CreateChildError = HTTPValidationError; + +export type UpdateChildData = { + body: ChildPublic; +}; + +export type UpdateChildResponse = ChildPublic; + +export type UpdateChildError = HTTPValidationError; + +export type DeleteChildData = { + path: { + child_id: number; + }; +}; + +export type DeleteChildResponse = unknown; + +export type DeleteChildError = HTTPValidationError; + +export type GetChildImageData = { + path: { + child_id: number; + }; +}; + +export type GetChildImageResponse = unknown; + +export type GetChildImageError = HTTPValidationError; + +export type UploadChildImageData = { + body: Body_upload_child_image_users_children_images__child_id__put; + path: { + child_id: number; + }; +}; + +export type UploadChildImageResponse = unknown; + +export type UploadChildImageError = HTTPValidationError; + export type AuthCookieLoginData = { body: Body_auth_cookie_login_auth_login_post; }; diff --git a/mondey_backend/openapi.json b/mondey_backend/openapi.json index 565254d7..e29d43e7 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"}}}}}}}, "/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_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"}, "ErrorModel": {"properties": {"detail": {"anyOf": [{"type": "string"}, {"additionalProperties": {"type": "string"}, "type": "object"}], "title": "Detail"}}, "type": "object", "required": ["detail"], "title": "ErrorModel"}, "HTTPValidationError": {"properties": {"detail": {"items": {"$ref": "#/components/schemas/ValidationError"}, "type": "array", "title": "Detail"}}, "type": "object", "title": "HTTPValidationError"}, "Language": {"properties": {"id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Id"}, "lang": {"type": "string", "maxLength": 2, "title": "Lang"}}, "type": "object", "required": ["lang"], "title": "Language"}, "LanguageCreate": {"properties": {"lang": {"type": "string", "maxLength": 2, "title": "Lang"}}, "type": "object", "required": ["lang"], "title": "LanguageCreate"}, "MilestoneAdmin": {"properties": {"id": {"type": "integer", "title": "Id"}, "group_id": {"type": "integer", "title": "Group Id"}, "order": {"type": "integer", "title": "Order"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneText"}, "type": "object", "title": "Text", "default": {}}, "images": {"items": {"$ref": "#/components/schemas/MilestoneImage"}, "type": "array", "title": "Images", "default": []}}, "type": "object", "required": ["id", "group_id", "order"], "title": "MilestoneAdmin"}, "MilestoneGroupAdmin": {"properties": {"id": {"type": "integer", "title": "Id"}, "order": {"type": "integer", "title": "Order"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneGroupText"}, "type": "object", "title": "Text", "default": {}}, "milestones": {"items": {"$ref": "#/components/schemas/MilestoneAdmin"}, "type": "array", "title": "Milestones", "default": []}}, "type": "object", "required": ["id", "order"], "title": "MilestoneGroupAdmin"}, "MilestoneGroupPublic": {"properties": {"id": {"type": "integer", "title": "Id"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneGroupTextPublic"}, "type": "object", "title": "Text", "default": {}}, "milestones": {"items": {"$ref": "#/components/schemas/MilestonePublic"}, "type": "array", "title": "Milestones", "default": []}}, "type": "object", "required": ["id"], "title": "MilestoneGroupPublic"}, "MilestoneGroupText": {"properties": {"title": {"type": "string", "title": "Title", "default": ""}, "desc": {"type": "string", "title": "Desc", "default": ""}, "group_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Group Id"}, "lang_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Lang Id"}}, "type": "object", "title": "MilestoneGroupText"}, "MilestoneGroupTextPublic": {"properties": {"title": {"type": "string", "title": "Title", "default": ""}, "desc": {"type": "string", "title": "Desc", "default": ""}}, "type": "object", "title": "MilestoneGroupTextPublic"}, "MilestoneImage": {"properties": {"id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Id"}, "milestone_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Milestone Id"}, "filename": {"type": "string", "title": "Filename", "default": ""}, "approved": {"type": "boolean", "title": "Approved", "default": false}}, "type": "object", "title": "MilestoneImage"}, "MilestoneImagePublic": {"properties": {"filename": {"type": "string", "title": "Filename"}, "approved": {"type": "boolean", "title": "Approved"}}, "type": "object", "required": ["filename", "approved"], "title": "MilestoneImagePublic"}, "MilestonePublic": {"properties": {"id": {"type": "integer", "title": "Id"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneTextPublic"}, "type": "object", "title": "Text", "default": {}}, "images": {"items": {"$ref": "#/components/schemas/MilestoneImagePublic"}, "type": "array", "title": "Images", "default": []}}, "type": "object", "required": ["id"], "title": "MilestonePublic"}, "MilestoneText": {"properties": {"title": {"type": "string", "title": "Title", "default": ""}, "desc": {"type": "string", "title": "Desc", "default": ""}, "obs": {"type": "string", "title": "Obs", "default": ""}, "help": {"type": "string", "title": "Help", "default": ""}, "milestone_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Milestone Id"}, "lang_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Lang Id"}}, "type": "object", "title": "MilestoneText"}, "MilestoneTextPublic": {"properties": {"title": {"type": "string", "title": "Title", "default": ""}, "desc": {"type": "string", "title": "Desc", "default": ""}, "obs": {"type": "string", "title": "Obs", "default": ""}, "help": {"type": "string", "title": "Help", "default": ""}}, "type": "object", "title": "MilestoneTextPublic"}, "UserCreate": {"properties": {"email": {"type": "string", "format": "email", "title": "Email"}, "password": {"type": "string", "title": "Password"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active", "default": true}, "is_superuser": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Superuser", "default": false}, "is_verified": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Verified", "default": false}, "is_researcher": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Researcher", "default": false}}, "type": "object", "required": ["email", "password"], "title": "UserCreate"}, "UserQuestionAdmin": {"properties": {"id": {"type": "integer", "title": "Id"}, "order": {"type": "integer", "title": "Order"}, "input": {"type": "string", "title": "Input"}, "options": {"type": "string", "title": "Options"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/UserQuestionText"}, "type": "object", "title": "Text", "default": {}}}, "type": "object", "required": ["id", "order", "input", "options"], "title": "UserQuestionAdmin"}, "UserQuestionPublic": {"properties": {"id": {"type": "integer", "title": "Id"}, "input": {"type": "string", "title": "Input"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/UserQuestionTextPublic"}, "type": "object", "title": "Text", "default": {}}}, "type": "object", "required": ["id", "input"], "title": "UserQuestionPublic"}, "UserQuestionText": {"properties": {"question": {"type": "string", "title": "Question", "default": ""}, "options_json": {"type": "string", "title": "Options Json", "default": ""}, "user_question_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "User Question Id"}, "lang_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Lang Id"}, "options": {"type": "string", "title": "Options", "default": ""}}, "type": "object", "title": "UserQuestionText"}, "UserQuestionTextPublic": {"properties": {"question": {"type": "string", "title": "Question", "default": ""}, "options_json": {"type": "string", "title": "Options Json", "default": ""}}, "type": "object", "title": "UserQuestionTextPublic"}, "UserRead": {"properties": {"id": {"type": "integer", "title": "Id"}, "email": {"type": "string", "format": "email", "title": "Email"}, "is_active": {"type": "boolean", "title": "Is Active", "default": true}, "is_superuser": {"type": "boolean", "title": "Is Superuser", "default": false}, "is_verified": {"type": "boolean", "title": "Is Verified", "default": false}, "is_researcher": {"type": "boolean", "title": "Is Researcher"}}, "type": "object", "required": ["id", "email", "is_researcher"], "title": "UserRead"}, "UserUpdate": {"properties": {"password": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Password"}, "email": {"anyOf": [{"type": "string", "format": "email"}, {"type": "null"}], "title": "Email"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}, "is_superuser": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Superuser"}, "is_verified": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Verified"}, "is_researcher": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Researcher"}}, "type": "object", "title": "UserUpdate"}, "ValidationError": {"properties": {"loc": {"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, "type": "array", "title": "Location"}, "msg": {"type": "string", "title": "Message"}, "type": {"type": "string", "title": "Error Type"}}, "type": "object", "required": ["loc", "msg", "type"], "title": "ValidationError"}}, "securitySchemes": {"APIKeyCookie": {"type": "apiKey", "in": "cookie", "name": "fastapiusersauth"}}}} \ No newline at end of file +{"openapi": "3.1.0", "info": {"title": "MONDEY API", "version": "0.1.0"}, "paths": {"/languages/": {"get": {"tags": ["milestones"], "summary": "Get Languages", "operationId": "get_languages", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"additionalProperties": {"type": "integer"}, "type": "object", "title": "Response Get Languages Languages Get"}}}}}}}, "/milestones/": {"get": {"tags": ["milestones"], "summary": "Get Milestones", "operationId": "get_milestones", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/MilestonePublic"}, "type": "array", "title": "Response Get Milestones Milestones Get"}}}}}}}, "/milestones/{milestone_id}": {"get": {"tags": ["milestones"], "summary": "Get Milestone", "operationId": "get_milestone", "parameters": [{"name": "milestone_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestonePublic"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/milestone-groups/": {"get": {"tags": ["milestones"], "summary": "Get Milestone Groups", "operationId": "get_milestone_groups", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/MilestoneGroupPublic"}, "type": "array", "title": "Response Get Milestone Groups Milestone Groups Get"}}}}}}}, "/milestone-groups/{milestone_group_id}": {"get": {"tags": ["milestones"], "summary": "Get Milestone Group", "operationId": "get_milestone_group", "parameters": [{"name": "milestone_group_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Group Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneGroupPublic"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/user-questions/": {"get": {"tags": ["questions"], "summary": "Get User Questions", "operationId": "get_user_questions", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/UserQuestionPublic"}, "type": "array", "title": "Response Get User Questions User Questions Get"}}}}}}}, "/admin/languages/": {"post": {"tags": ["admin"], "summary": "Create Language", "operationId": "create_language", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LanguageCreate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Language"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/admin/languages/{language_id}": {"delete": {"tags": ["admin"], "summary": "Delete Language", "operationId": "delete_language", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "language_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Language Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/i18n/{language_id}": {"put": {"tags": ["admin"], "summary": "Update I18N", "operationId": "update_i18n", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "language_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Language Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": {"type": "object", "additionalProperties": {"type": "string"}}, "title": "I18Dict"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/milestone-groups/": {"get": {"tags": ["admin"], "summary": "Get Milestone Groups Admin", "operationId": "get_milestone_groups_admin", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/MilestoneGroupAdmin"}, "type": "array", "title": "Response Get Milestone Groups Admin Admin Milestone Groups Get"}}}}}, "security": [{"APIKeyCookie": []}]}, "post": {"tags": ["admin"], "summary": "Create Milestone Group Admin", "operationId": "create_milestone_group_admin", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneGroupAdmin"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/admin/milestone-groups": {"put": {"tags": ["admin"], "summary": "Update Milestone Group Admin", "operationId": "update_milestone_group_admin", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneGroupAdmin"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneGroupAdmin"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/admin/milestone-groups/{milestone_group_id}": {"delete": {"tags": ["admin"], "summary": "Delete Milestone Group Admin", "operationId": "delete_milestone_group_admin", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_group_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Group Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/milestone-group-images/{milestone_group_id}": {"put": {"tags": ["admin"], "summary": "Upload Milestone Group Image", "operationId": "upload_milestone_group_image", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_group_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Group Id"}}], "requestBody": {"required": true, "content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_upload_milestone_group_image_admin_milestone_group_images__milestone_group_id__put"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/milestones/{milestone_group_id}": {"post": {"tags": ["admin"], "summary": "Create Milestone", "operationId": "create_milestone", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_group_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Group Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneAdmin"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/milestones/": {"put": {"tags": ["admin"], "summary": "Update Milestone", "operationId": "update_milestone", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneAdmin"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneAdmin"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/admin/milestones/{milestone_id}": {"delete": {"tags": ["admin"], "summary": "Delete Milestone", "operationId": "delete_milestone", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/milestone-images/{milestone_id}": {"post": {"tags": ["admin"], "summary": "Upload Milestone Image", "operationId": "upload_milestone_image", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "milestone_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Milestone Id"}}], "requestBody": {"required": true, "content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_upload_milestone_image_admin_milestone_images__milestone_id__post"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MilestoneImage"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/admin/user-questions/": {"get": {"tags": ["admin"], "summary": "Get User Questions Admin", "operationId": "get_user_questions_admin", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/UserQuestionAdmin"}, "type": "array", "title": "Response Get User Questions Admin Admin User Questions Get"}}}}}, "security": [{"APIKeyCookie": []}]}, "put": {"tags": ["admin"], "summary": "Update User Question", "operationId": "update_user_question", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserQuestionAdmin"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserQuestionAdmin"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}, "post": {"tags": ["admin"], "summary": "Create User Question", "operationId": "create_user_question", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserQuestionAdmin"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/admin/user-questions/{user_question_id}": {"delete": {"tags": ["admin"], "summary": "Delete User Question", "operationId": "delete_user_question", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "user_question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "User Question Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/users/me": {"get": {"tags": ["users"], "summary": "Users:Current User", "operationId": "users:current_user", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "401": {"description": "Missing token or inactive user."}}, "security": [{"APIKeyCookie": []}]}, "patch": {"tags": ["users"], "summary": "Users:Patch Current User", "operationId": "users:patch_current_user", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserUpdate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "401": {"description": "Missing token or inactive user."}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorModel"}, "examples": {"UPDATE_USER_EMAIL_ALREADY_EXISTS": {"summary": "A user with this email already exists.", "value": {"detail": "UPDATE_USER_EMAIL_ALREADY_EXISTS"}}, "UPDATE_USER_INVALID_PASSWORD": {"summary": "Password validation failed.", "value": {"detail": {"code": "UPDATE_USER_INVALID_PASSWORD", "reason": "Password should beat least 3 characters"}}}}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/users/{id}": {"get": {"tags": ["users"], "summary": "Users:User", "operationId": "users:user", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string", "title": "Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "401": {"description": "Missing token or inactive user."}, "403": {"description": "Not a superuser."}, "404": {"description": "The user does not exist."}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "patch": {"tags": ["users"], "summary": "Users:Patch User", "operationId": "users:patch_user", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string", "title": "Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserUpdate"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "401": {"description": "Missing token or inactive user."}, "403": {"description": "Not a superuser."}, "404": {"description": "The user does not exist."}, "400": {"content": {"application/json": {"examples": {"UPDATE_USER_EMAIL_ALREADY_EXISTS": {"summary": "A user with this email already exists.", "value": {"detail": "UPDATE_USER_EMAIL_ALREADY_EXISTS"}}, "UPDATE_USER_INVALID_PASSWORD": {"summary": "Password validation failed.", "value": {"detail": {"code": "UPDATE_USER_INVALID_PASSWORD", "reason": "Password should beat least 3 characters"}}}}, "schema": {"$ref": "#/components/schemas/ErrorModel"}}}, "description": "Bad Request"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["users"], "summary": "Users:Delete User", "operationId": "users:delete_user", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string", "title": "Id"}}], "responses": {"204": {"description": "Successful Response"}, "401": {"description": "Missing token or inactive user."}, "403": {"description": "Not a superuser."}, "404": {"description": "The user does not exist."}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/users/children/": {"get": {"tags": ["users"], "summary": "Get Children", "operationId": "get_children", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/ChildPublic"}, "type": "array", "title": "Response Get Children Users Children Get"}}}}}, "security": [{"APIKeyCookie": []}]}, "post": {"tags": ["users"], "summary": "Create Child", "operationId": "create_child", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChildCreate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChildPublic"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/users/children": {"put": {"tags": ["users"], "summary": "Update Child", "operationId": "update_child", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChildPublic"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChildPublic"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"APIKeyCookie": []}]}}, "/users/children/{child_id}": {"delete": {"tags": ["users"], "summary": "Delete Child", "operationId": "delete_child", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "child_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Child Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/users/children-images/{child_id}": {"get": {"tags": ["users"], "summary": "Get Child Image", "operationId": "get_child_image", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "child_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Child Id"}}], "responses": {"200": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "put": {"tags": ["users"], "summary": "Upload Child Image", "operationId": "upload_child_image", "security": [{"APIKeyCookie": []}], "parameters": [{"name": "child_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Child Id"}}], "requestBody": {"required": true, "content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_upload_child_image_users_children_images__child_id__put"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/login": {"post": {"tags": ["auth"], "summary": "Auth:Cookie.Login", "operationId": "auth:cookie.login", "requestBody": {"content": {"application/x-www-form-urlencoded": {"schema": {"$ref": "#/components/schemas/Body_auth_cookie_login_auth_login_post"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorModel"}, "examples": {"LOGIN_BAD_CREDENTIALS": {"summary": "Bad credentials or the user is inactive.", "value": {"detail": "LOGIN_BAD_CREDENTIALS"}}, "LOGIN_USER_NOT_VERIFIED": {"summary": "The user is not verified.", "value": {"detail": "LOGIN_USER_NOT_VERIFIED"}}}}}}, "204": {"description": "No Content"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/logout": {"post": {"tags": ["auth"], "summary": "Auth:Cookie.Logout", "operationId": "auth:cookie.logout", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "401": {"description": "Missing token or inactive user."}, "204": {"description": "No Content"}}, "security": [{"APIKeyCookie": []}]}}, "/auth/register": {"post": {"tags": ["auth"], "summary": "Register:Register", "operationId": "register:register", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserCreate"}}}, "required": true}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorModel"}, "examples": {"REGISTER_USER_ALREADY_EXISTS": {"summary": "A user with this email already exists.", "value": {"detail": "REGISTER_USER_ALREADY_EXISTS"}}, "REGISTER_INVALID_PASSWORD": {"summary": "Password validation failed.", "value": {"detail": {"code": "REGISTER_INVALID_PASSWORD", "reason": "Password should beat least 3 characters"}}}}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/forgot-password": {"post": {"tags": ["auth"], "summary": "Reset:Forgot Password", "operationId": "reset:forgot_password", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Body_reset_forgot_password_auth_forgot_password_post"}}}, "required": true}, "responses": {"202": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/reset-password": {"post": {"tags": ["auth"], "summary": "Reset:Reset Password", "operationId": "reset:reset_password", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Body_reset_reset_password_auth_reset_password_post"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorModel"}, "examples": {"RESET_PASSWORD_BAD_TOKEN": {"summary": "Bad or expired token.", "value": {"detail": "RESET_PASSWORD_BAD_TOKEN"}}, "RESET_PASSWORD_INVALID_PASSWORD": {"summary": "Password validation failed.", "value": {"detail": {"code": "RESET_PASSWORD_INVALID_PASSWORD", "reason": "Password should be at least 3 characters"}}}}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/request-verify-token": {"post": {"tags": ["auth"], "summary": "Verify:Request-Token", "operationId": "verify:request-token", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Body_verify_request_token_auth_request_verify_token_post"}}}, "required": true}, "responses": {"202": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/auth/verify": {"post": {"tags": ["auth"], "summary": "Verify:Verify", "operationId": "verify:verify", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Body_verify_verify_auth_verify_post"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserRead"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorModel"}, "examples": {"VERIFY_USER_BAD_TOKEN": {"summary": "Bad token, not existing user ornot the e-mail currently set for the user.", "value": {"detail": "VERIFY_USER_BAD_TOKEN"}}, "VERIFY_USER_ALREADY_VERIFIED": {"summary": "The user is already verified.", "value": {"detail": "VERIFY_USER_ALREADY_VERIFIED"}}}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/research/auth/": {"get": {"tags": ["research"], "summary": "Auth", "operationId": "auth", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"APIKeyCookie": []}]}}}, "components": {"schemas": {"Body_auth_cookie_login_auth_login_post": {"properties": {"grant_type": {"anyOf": [{"type": "string", "pattern": "password"}, {"type": "null"}], "title": "Grant Type"}, "username": {"type": "string", "title": "Username"}, "password": {"type": "string", "title": "Password"}, "scope": {"type": "string", "title": "Scope", "default": ""}, "client_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Client Id"}, "client_secret": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Client Secret"}}, "type": "object", "required": ["username", "password"], "title": "Body_auth_cookie_login_auth_login_post"}, "Body_reset_forgot_password_auth_forgot_password_post": {"properties": {"email": {"type": "string", "format": "email", "title": "Email"}}, "type": "object", "required": ["email"], "title": "Body_reset_forgot_password_auth_forgot_password_post"}, "Body_reset_reset_password_auth_reset_password_post": {"properties": {"token": {"type": "string", "title": "Token"}, "password": {"type": "string", "title": "Password"}}, "type": "object", "required": ["token", "password"], "title": "Body_reset_reset_password_auth_reset_password_post"}, "Body_upload_child_image_users_children_images__child_id__put": {"properties": {"file": {"type": "string", "format": "binary", "title": "File"}}, "type": "object", "required": ["file"], "title": "Body_upload_child_image_users_children_images__child_id__put"}, "Body_upload_milestone_group_image_admin_milestone_group_images__milestone_group_id__put": {"properties": {"file": {"type": "string", "format": "binary", "title": "File"}}, "type": "object", "required": ["file"], "title": "Body_upload_milestone_group_image_admin_milestone_group_images__milestone_group_id__put"}, "Body_upload_milestone_image_admin_milestone_images__milestone_id__post": {"properties": {"file": {"type": "string", "format": "binary", "title": "File"}}, "type": "object", "required": ["file"], "title": "Body_upload_milestone_image_admin_milestone_images__milestone_id__post"}, "Body_verify_request_token_auth_request_verify_token_post": {"properties": {"email": {"type": "string", "format": "email", "title": "Email"}}, "type": "object", "required": ["email"], "title": "Body_verify_request_token_auth_request_verify_token_post"}, "Body_verify_verify_auth_verify_post": {"properties": {"token": {"type": "string", "title": "Token"}}, "type": "object", "required": ["token"], "title": "Body_verify_verify_auth_verify_post"}, "ChildCreate": {"properties": {"name": {"type": "string", "title": "Name", "default": ""}, "birth_year": {"type": "integer", "title": "Birth Year"}, "birth_month": {"type": "integer", "title": "Birth Month"}}, "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"}}, "type": "object", "required": ["birth_year", "birth_month", "id"], "title": "ChildPublic"}, "ErrorModel": {"properties": {"detail": {"anyOf": [{"type": "string"}, {"additionalProperties": {"type": "string"}, "type": "object"}], "title": "Detail"}}, "type": "object", "required": ["detail"], "title": "ErrorModel"}, "HTTPValidationError": {"properties": {"detail": {"items": {"$ref": "#/components/schemas/ValidationError"}, "type": "array", "title": "Detail"}}, "type": "object", "title": "HTTPValidationError"}, "Language": {"properties": {"id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Id"}, "lang": {"type": "string", "maxLength": 2, "title": "Lang"}}, "type": "object", "required": ["lang"], "title": "Language"}, "LanguageCreate": {"properties": {"lang": {"type": "string", "maxLength": 2, "title": "Lang"}}, "type": "object", "required": ["lang"], "title": "LanguageCreate"}, "MilestoneAdmin": {"properties": {"id": {"type": "integer", "title": "Id"}, "group_id": {"type": "integer", "title": "Group Id"}, "order": {"type": "integer", "title": "Order"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneText"}, "type": "object", "title": "Text", "default": {}}, "images": {"items": {"$ref": "#/components/schemas/MilestoneImage"}, "type": "array", "title": "Images", "default": []}}, "type": "object", "required": ["id", "group_id", "order"], "title": "MilestoneAdmin"}, "MilestoneGroupAdmin": {"properties": {"id": {"type": "integer", "title": "Id"}, "order": {"type": "integer", "title": "Order"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneGroupText"}, "type": "object", "title": "Text", "default": {}}, "milestones": {"items": {"$ref": "#/components/schemas/MilestoneAdmin"}, "type": "array", "title": "Milestones", "default": []}}, "type": "object", "required": ["id", "order"], "title": "MilestoneGroupAdmin"}, "MilestoneGroupPublic": {"properties": {"id": {"type": "integer", "title": "Id"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneGroupTextPublic"}, "type": "object", "title": "Text", "default": {}}, "milestones": {"items": {"$ref": "#/components/schemas/MilestonePublic"}, "type": "array", "title": "Milestones", "default": []}}, "type": "object", "required": ["id"], "title": "MilestoneGroupPublic"}, "MilestoneGroupText": {"properties": {"title": {"type": "string", "title": "Title", "default": ""}, "desc": {"type": "string", "title": "Desc", "default": ""}, "group_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Group Id"}, "lang_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Lang Id"}}, "type": "object", "title": "MilestoneGroupText"}, "MilestoneGroupTextPublic": {"properties": {"title": {"type": "string", "title": "Title", "default": ""}, "desc": {"type": "string", "title": "Desc", "default": ""}}, "type": "object", "title": "MilestoneGroupTextPublic"}, "MilestoneImage": {"properties": {"id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Id"}, "milestone_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Milestone Id"}, "filename": {"type": "string", "title": "Filename", "default": ""}, "approved": {"type": "boolean", "title": "Approved", "default": false}}, "type": "object", "title": "MilestoneImage"}, "MilestoneImagePublic": {"properties": {"filename": {"type": "string", "title": "Filename"}, "approved": {"type": "boolean", "title": "Approved"}}, "type": "object", "required": ["filename", "approved"], "title": "MilestoneImagePublic"}, "MilestonePublic": {"properties": {"id": {"type": "integer", "title": "Id"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/MilestoneTextPublic"}, "type": "object", "title": "Text", "default": {}}, "images": {"items": {"$ref": "#/components/schemas/MilestoneImagePublic"}, "type": "array", "title": "Images", "default": []}}, "type": "object", "required": ["id"], "title": "MilestonePublic"}, "MilestoneText": {"properties": {"title": {"type": "string", "title": "Title", "default": ""}, "desc": {"type": "string", "title": "Desc", "default": ""}, "obs": {"type": "string", "title": "Obs", "default": ""}, "help": {"type": "string", "title": "Help", "default": ""}, "milestone_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Milestone Id"}, "lang_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Lang Id"}}, "type": "object", "title": "MilestoneText"}, "MilestoneTextPublic": {"properties": {"title": {"type": "string", "title": "Title", "default": ""}, "desc": {"type": "string", "title": "Desc", "default": ""}, "obs": {"type": "string", "title": "Obs", "default": ""}, "help": {"type": "string", "title": "Help", "default": ""}}, "type": "object", "title": "MilestoneTextPublic"}, "UserCreate": {"properties": {"email": {"type": "string", "format": "email", "title": "Email"}, "password": {"type": "string", "title": "Password"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active", "default": true}, "is_superuser": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Superuser", "default": false}, "is_verified": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Verified", "default": false}, "is_researcher": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Researcher", "default": false}}, "type": "object", "required": ["email", "password"], "title": "UserCreate"}, "UserQuestionAdmin": {"properties": {"id": {"type": "integer", "title": "Id"}, "order": {"type": "integer", "title": "Order"}, "input": {"type": "string", "title": "Input"}, "options": {"type": "string", "title": "Options"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/UserQuestionText"}, "type": "object", "title": "Text", "default": {}}}, "type": "object", "required": ["id", "order", "input", "options"], "title": "UserQuestionAdmin"}, "UserQuestionPublic": {"properties": {"id": {"type": "integer", "title": "Id"}, "input": {"type": "string", "title": "Input"}, "text": {"additionalProperties": {"$ref": "#/components/schemas/UserQuestionTextPublic"}, "type": "object", "title": "Text", "default": {}}}, "type": "object", "required": ["id", "input"], "title": "UserQuestionPublic"}, "UserQuestionText": {"properties": {"question": {"type": "string", "title": "Question", "default": ""}, "options_json": {"type": "string", "title": "Options Json", "default": ""}, "user_question_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "User Question Id"}, "lang_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Lang Id"}, "options": {"type": "string", "title": "Options", "default": ""}}, "type": "object", "title": "UserQuestionText"}, "UserQuestionTextPublic": {"properties": {"question": {"type": "string", "title": "Question", "default": ""}, "options_json": {"type": "string", "title": "Options Json", "default": ""}}, "type": "object", "title": "UserQuestionTextPublic"}, "UserRead": {"properties": {"id": {"type": "integer", "title": "Id"}, "email": {"type": "string", "format": "email", "title": "Email"}, "is_active": {"type": "boolean", "title": "Is Active", "default": true}, "is_superuser": {"type": "boolean", "title": "Is Superuser", "default": false}, "is_verified": {"type": "boolean", "title": "Is Verified", "default": false}, "is_researcher": {"type": "boolean", "title": "Is Researcher"}}, "type": "object", "required": ["id", "email", "is_researcher"], "title": "UserRead"}, "UserUpdate": {"properties": {"password": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Password"}, "email": {"anyOf": [{"type": "string", "format": "email"}, {"type": "null"}], "title": "Email"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}, "is_superuser": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Superuser"}, "is_verified": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Verified"}, "is_researcher": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Researcher"}}, "type": "object", "title": "UserUpdate"}, "ValidationError": {"properties": {"loc": {"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, "type": "array", "title": "Location"}, "msg": {"type": "string", "title": "Message"}, "type": {"type": "string", "title": "Error Type"}}, "type": "object", "required": ["loc", "msg", "type"], "title": "ValidationError"}}, "securitySchemes": {"APIKeyCookie": {"type": "apiKey", "in": "cookie", "name": "fastapiusersauth"}}}} \ No newline at end of file diff --git a/mondey_backend/src/mondey_backend/dependencies.py b/mondey_backend/src/mondey_backend/dependencies.py index f3505894..ed2a5e85 100644 --- a/mondey_backend/src/mondey_backend/dependencies.py +++ b/mondey_backend/src/mondey_backend/dependencies.py @@ -33,6 +33,8 @@ def current_active_researcher( ResearchDep = Depends(current_active_researcher) +CurrentActiveResearchDep = Annotated[User, Depends(current_active_user)] + current_active_superuser = fastapi_users.current_user(active=True, superuser=True) AdminDep = Depends(current_active_superuser) diff --git a/mondey_backend/src/mondey_backend/main.py b/mondey_backend/src/mondey_backend/main.py index 25d36bf7..cf83a8fc 100644 --- a/mondey_backend/src/mondey_backend/main.py +++ b/mondey_backend/src/mondey_backend/main.py @@ -14,8 +14,8 @@ from .routers import admin from .routers import auth from .routers import milestones -from .routers import research from .routers import questions +from .routers import research from .routers import users from .settings import app_settings diff --git a/mondey_backend/src/mondey_backend/models/children.py b/mondey_backend/src/mondey_backend/models/children.py new file mode 100644 index 00000000..2781a46b --- /dev/null +++ b/mondey_backend/src/mondey_backend/models/children.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from sqlmodel import Field +from sqlmodel import SQLModel + + +class ChildBase(SQLModel): + name: str = "" + birth_year: int + birth_month: int + has_image: bool + + +class Child(ChildBase, table=True): + id: int | None = Field(default=None, primary_key=True) + user_id: int + + +class ChildCreate(ChildBase): + pass + + +class ChildPublic(ChildBase): + id: int diff --git a/mondey_backend/src/mondey_backend/models/milestones.py b/mondey_backend/src/mondey_backend/models/milestones.py index 5438c5ca..b5c7ec96 100644 --- a/mondey_backend/src/mondey_backend/models/milestones.py +++ b/mondey_backend/src/mondey_backend/models/milestones.py @@ -145,15 +145,3 @@ class MilestoneImage(SQLModel, table=True): class MilestoneImagePublic(SQLModel): filename: str approved: bool - - -## MilestoneAnswer - - -# class MilestoneAnswer(SQLModel, table=True): -# milestone_id: int | None = Field(default=None, foreign_key="milestone.id") -# user_id: int | None = Field(default=None, foreign_key="user.id") -# created_at: datetime.datetime = Field( -# default_factory=datetime.datetime.now, -# ) -# answer: int diff --git a/mondey_backend/src/mondey_backend/routers/research.py b/mondey_backend/src/mondey_backend/routers/research.py index 52f53074..67d16ca5 100644 --- a/mondey_backend/src/mondey_backend/routers/research.py +++ b/mondey_backend/src/mondey_backend/routers/research.py @@ -2,6 +2,7 @@ from fastapi import APIRouter +from ..dependencies import CurrentActiveResearchDep from ..dependencies import ResearchDep @@ -11,7 +12,8 @@ def create_router() -> APIRouter: ) @router.get("/auth/") - def auth(): + def auth(current_active_researcher: CurrentActiveResearchDep): + print(current_active_researcher.id) return {"ok": True} return router diff --git a/mondey_backend/src/mondey_backend/routers/users.py b/mondey_backend/src/mondey_backend/routers/users.py index e04bcf23..d6c5afd1 100644 --- a/mondey_backend/src/mondey_backend/routers/users.py +++ b/mondey_backend/src/mondey_backend/routers/users.py @@ -1,14 +1,107 @@ from __future__ import annotations +import pathlib + from fastapi import APIRouter +from fastapi import HTTPException +from fastapi import UploadFile +from fastapi.responses import FileResponse +from sqlmodel import col +from sqlmodel import select +from ..dependencies import CurrentActiveUserDep +from ..dependencies import SessionDep +from ..models.children import Child +from ..models.children import ChildCreate +from ..models.children import ChildPublic from ..models.users import UserRead from ..models.users import UserUpdate +from ..settings import app_settings from ..users import fastapi_users +from .utils import add +from .utils import get +from .utils import write_file def create_router() -> APIRouter: router = APIRouter(prefix="/users", tags=["users"]) router.include_router(fastapi_users.get_users_router(UserRead, UserUpdate)) + @router.get("/children/", response_model=list[ChildPublic]) + def get_children(session: SessionDep, current_active_user: CurrentActiveUserDep): + return [ + child + for child in session.exec( + select(Child).where(col(Child.user_id) == current_active_user.id) + ).all() + ] + + @router.post("/children/", response_model=ChildPublic) + def create_child( + session: SessionDep, + current_active_user: CurrentActiveUserDep, + child: ChildCreate, + ): + child.user_id = current_active_user.id + db_child = Child.model_validate(child) + add(session, db_child) + return db_child + + @router.put("/children", response_model=ChildPublic) + def update_child( + session: SessionDep, + current_active_user: CurrentActiveUserDep, + child: ChildPublic, + ): + db_child = get(session, Child, child.id) + if db_child.user_id != current_active_user.id: + raise HTTPException(401) + for key, value in child.model_dump().items(): + setattr(db_child, key, value) + add(session, db_child) + return db_child + + @router.delete("/children/{child_id}") + def delete_child( + session: SessionDep, current_active_user: CurrentActiveUserDep, child_id: int + ): + child = get(session, Child, child_id) + if child.user_id != current_active_user.id: + raise HTTPException(401) + session.delete(child) + session.commit() + return {"ok": True} + + @router.get("/children-images/{child_id}", response_class=FileResponse) + async def get_child_image( + session: SessionDep, + current_active_user: CurrentActiveUserDep, + child_id: int, + ): + child = get(session, Child, child_id) + if child.user_id != current_active_user.id: + raise HTTPException(401) + image_path = pathlib.Path( + f"{app_settings.PRIVATE_FILES_PATH}/children/{child.id}.jpg" + ) + if not image_path.exists(): + raise HTTPException(404) + return image_path + + @router.put("/children-images/{child_id}") + async def upload_child_image( + session: SessionDep, + current_active_user: CurrentActiveUserDep, + child_id: int, + file: UploadFile, + ): + child = get(session, Child, child_id) + if child.user_id != current_active_user.id: + raise HTTPException(401) + child.has_image = True + session.commit() + filename = f"{app_settings.PRIVATE_FILES_PATH}/children/{child.id}.jpg" + write_file(file, filename) + return {"ok": True} + return router diff --git a/mondey_backend/src/mondey_backend/routers/utils.py b/mondey_backend/src/mondey_backend/routers/utils.py index 1c5aee79..e684ae5a 100644 --- a/mondey_backend/src/mondey_backend/routers/utils.py +++ b/mondey_backend/src/mondey_backend/routers/utils.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +import pathlib from collections.abc import Iterable from fastapi import HTTPException @@ -21,6 +22,7 @@ def write_file(file: UploadFile, filename: str): logging.warning(f"Saving file {file.filename} to {filename}") try: + pathlib.Path(filename).parent.mkdir(exist_ok=True) contents = file.file.read() with open(filename, "wb") as f: f.write(contents) diff --git a/mondey_backend/src/mondey_backend/settings.py b/mondey_backend/src/mondey_backend/settings.py index 5db58b6d..af2a7acd 100644 --- a/mondey_backend/src/mondey_backend/settings.py +++ b/mondey_backend/src/mondey_backend/settings.py @@ -1,7 +1,5 @@ from __future__ import annotations -import os - from pydantic_settings import BaseSettings from pydantic_settings import SettingsConfigDict @@ -13,6 +11,7 @@ class AppSettings(BaseSettings): SECRET: str = "abc123" DATABASE_PATH: str = "db" STATIC_FILES_PATH: str = "static" + PRIVATE_FILES_PATH: str = "private" ENABLE_CORS: bool = True HOST: str = "localhost" PORT: int = 8000 @@ -22,5 +21,3 @@ class AppSettings(BaseSettings): app_settings = AppSettings() -if os.environ.get("PYTEST_VERSION"): - app_settings.STATIC_FILES_PATH = "static2" diff --git a/mondey_backend/tests/conftest.py b/mondey_backend/tests/conftest.py index ff39e889..a801eeab 100644 --- a/mondey_backend/tests/conftest.py +++ b/mondey_backend/tests/conftest.py @@ -16,6 +16,7 @@ from mondey_backend.dependencies import current_active_user from mondey_backend.dependencies import get_session from mondey_backend.main import create_app +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 MilestoneGroup @@ -36,8 +37,21 @@ def static_dir(tmp_path_factory: pytest.TempPathFactory): return static_dir +@pytest.fixture(scope="session") +def private_dir(tmp_path_factory: pytest.TempPathFactory): + # use the same single temporary directory in all tests for private files + private_dir = tmp_path_factory.mktemp("private") + children_dir = private_dir / "children" + children_dir.mkdir() + # add some child image files + for filename in ["2.jpg", "3.jpg"]: + with (children_dir / filename).open("w") as f: + f.write(filename) + return private_dir + + @pytest.fixture -def session(static_dir: pathlib.Path): +def session(): # use a new in-memory SQLite database for each test engine = create_engine( "sqlite://", @@ -103,12 +117,37 @@ def session(static_dir: pathlib.Path): 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 + session.add( + Child( + user_id=1, + name="child1", + birth_year=2022, + birth_month=3, + has_image=False, + ) + ) + session.add( + Child( + user_id=1, + name="child2", + birth_year=2024, + birth_month=12, + has_image=True, + ) + ) + # add a child for user 3 + session.add( + Child( + user_id=3, name="child3", birth_year=2021, birth_month=1, has_image=True + ) + ) yield session @pytest.fixture def active_admin_user(): - UserRead( + return UserRead( id=3, email="admin@mondey.de", is_active=True, @@ -120,7 +159,7 @@ def active_admin_user(): @pytest.fixture def active_research_user(): - UserRead( + return UserRead( id=2, email="research@mondey.de", is_active=True, @@ -132,7 +171,7 @@ def active_research_user(): @pytest.fixture def active_user(): - UserRead( + return UserRead( id=1, email="user@mondey.de", is_active=True, @@ -143,12 +182,21 @@ def active_user(): @pytest.fixture(scope="session") -def app(static_dir: pathlib.Path): +def app(static_dir: pathlib.Path, private_dir: pathlib.Path): settings.app_settings.STATIC_FILES_PATH = str(static_dir) + settings.app_settings.PRIVATE_FILES_PATH = str(private_dir) app = create_app() return app +@pytest.fixture +def public_client(app: FastAPI, session: Session): + app.dependency_overrides[get_session] = lambda: session + client = TestClient(app) + yield client + app.dependency_overrides.clear() + + @pytest.fixture def user_client(app: FastAPI, session: Session, active_user: UserRead): app.dependency_overrides[get_session] = lambda: session @@ -165,6 +213,7 @@ def research_client( active_research_user: UserRead, ): app.dependency_overrides[get_session] = lambda: session + app.dependency_overrides[current_active_user] = lambda: active_research_user app.dependency_overrides[current_active_researcher] = lambda: active_research_user client = TestClient(app) yield client @@ -178,6 +227,7 @@ def admin_client( active_admin_user: UserRead, ): app.dependency_overrides[get_session] = lambda: session + app.dependency_overrides[current_active_user] = lambda: active_admin_user app.dependency_overrides[current_active_superuser] = lambda: active_admin_user client = TestClient(app) yield client diff --git a/mondey_backend/tests/routers/test_admin.py b/mondey_backend/tests/routers/test_admin.py index 898d192f..50cdabd4 100644 --- a/mondey_backend/tests/routers/test_admin.py +++ b/mondey_backend/tests/routers/test_admin.py @@ -114,7 +114,9 @@ def test_delete_milestone_group_invalid_group_id(admin_client: TestClient): "endpoint", ["/admin/languages/1", "/admin/milestone-groups/1", "/admin/milestones/1"], ) -@pytest.mark.parametrize("client_type", ["user_client", "research_client"]) +@pytest.mark.parametrize( + "client_type", ["public_client", "user_client", "research_client"] +) def test_delete_endpoints_invalid_admin_user( endpoint: str, client_type: str, request: pytest.FixtureRequest ): diff --git a/mondey_backend/tests/routers/test_milestones.py b/mondey_backend/tests/routers/test_milestones.py index d002942c..9c9c8813 100644 --- a/mondey_backend/tests/routers/test_milestones.py +++ b/mondey_backend/tests/routers/test_milestones.py @@ -2,7 +2,7 @@ @pytest.mark.parametrize( - "client_type", ["user_client", "research_client", "admin_client"] + "client_type", ["public_client", "user_client", "research_client", "admin_client"] ) class TestMilestones: def test_get_languages(self, client_type: str, request: pytest.FixtureRequest): diff --git a/mondey_backend/tests/routers/test_users.py b/mondey_backend/tests/routers/test_users.py new file mode 100644 index 00000000..968ac3a0 --- /dev/null +++ b/mondey_backend/tests/routers/test_users.py @@ -0,0 +1,80 @@ +import pathlib + +from fastapi.testclient import TestClient + + +def test_get_children(user_client: TestClient): + 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, + }, + ] + + +def test_get_children_no_children(research_client: TestClient): + response = research_client.get("/users/children/") + assert response.status_code == 200 + assert response.json() == [] + + +def test_get_children_admin_user_1_child(admin_client: TestClient): + 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, + } + ] + + +def test_get_children_invalid_user(public_client: TestClient): + response = public_client.get("/users/children/") + assert response.status_code == 401 + + +def test_get_child_image(user_client: TestClient): + response = user_client.get("/users/children-images/2") + assert response.status_code == 200 + assert response.content == b"2.jpg" + + +def test_get_child_image_no_image(user_client: TestClient): + response = user_client.get("/users/children-images/1") + assert response.status_code == 404 + + +def test_upload_child_image( + user_client: TestClient, private_dir: pathlib.Path, jpg_file: pathlib.Path +): + children_dir = private_dir / "children" + # child 1 does not have an image: + assert not (children_dir / "1.jpg").is_file() + assert user_client.get("/users/children/").json()[0]["has_image"] is False + assert user_client.get("/users/children-images/1").status_code == 404 + # add an image for the first child + with open(jpg_file, "rb") as f: + response = user_client.put( + "/users/children-images/1", + files={"file": ("filename", f, "image/jpeg")}, + ) + assert response.status_code == 200 + assert (children_dir / "1.jpg").is_file() + assert user_client.get("/users/children/").json()[0]["has_image"] is True + assert user_client.get("/users/children-images/1").status_code == 200