From ffe77d1ce0a0b5835388a06bcbae61983512394a Mon Sep 17 00:00:00 2001 From: Timofey Date: Fri, 29 Aug 2025 14:01:39 +0300 Subject: [PATCH 1/7] create base components --- backend/Pipfile | 5 +- backend/Pipfile.lock | 138 ++- backend/requirements.txt | 7 + frontend/.prettierignore | 9 + frontend/.prettierrc | 9 + frontend/app/(auth)/login/page.tsx | 7 + frontend/app/(protected)/dashboard/page.tsx | 9 + frontend/app/(protected)/objects/page.tsx | 9 + frontend/app/hooks/useClientFetch.ts | 131 ++ frontend/app/hooks/useForm.ts | 150 +++ frontend/app/types/index.ts | 9 + frontend/components/ui/Selector.tsx | 9 + frontend/components/ui/TextInput.tsx | 9 + frontend/package-lock.json | 1226 ++++++++++++++++--- frontend/package.json | 10 +- 15 files changed, 1577 insertions(+), 160 deletions(-) create mode 100644 frontend/.prettierignore create mode 100644 frontend/.prettierrc create mode 100644 frontend/app/(auth)/login/page.tsx create mode 100644 frontend/app/(protected)/dashboard/page.tsx create mode 100644 frontend/app/(protected)/objects/page.tsx create mode 100644 frontend/app/hooks/useClientFetch.ts create mode 100644 frontend/app/hooks/useForm.ts create mode 100644 frontend/app/types/index.ts create mode 100644 frontend/components/ui/Selector.tsx create mode 100644 frontend/components/ui/TextInput.tsx diff --git a/backend/Pipfile b/backend/Pipfile index 566c752..1eb723b 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -6,12 +6,15 @@ name = "pypi" [packages] django = "*" djangorestframework = "*" -pycodestyle = "*" django-cors-headers = "*" psycopg2-binary = "*" dotenv = "*" pytest = "*" pytest-django = "*" +djangorestframework-simplejwt = "*" +pycodestyle = "*" +requests = "*" +pyjwt = "*" [dev-packages] diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 4a34ced..37338d1 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e150d0e5553cb65551da7428eb7dd6810099cf0188118c2031d785f562315b37" + "sha256": "952a50ce3f529481ea1b021ac88ac52462e1b6b7ace31e1f03125e6eca1b281e" }, "pipfile-spec": 6, "requires": { @@ -24,6 +24,99 @@ "markers": "python_version >= '3.9'", "version": "==3.9.1" }, + "certifi": { + "hashes": [ + "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", + "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5" + ], + "markers": "python_version >= '3.7'", + "version": "==2025.8.3" + }, + "charset-normalizer": { + "hashes": [ + "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", + "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0", + "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", + "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601", + "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", + "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", + "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c", + "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", + "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", + "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", + "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432", + "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", + "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", + "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", + "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", + "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19", + "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", + "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e", + "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4", + "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7", + "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312", + "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", + "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", + "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", + "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", + "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99", + "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b", + "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", + "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", + "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", + "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", + "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", + "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0", + "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc", + "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", + "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", + "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a", + "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40", + "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", + "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", + "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", + "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", + "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05", + "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", + "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c", + "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a", + "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", + "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34", + "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9", + "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", + "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", + "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", + "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b", + "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", + "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942", + "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", + "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", + "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b", + "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", + "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669", + "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0", + "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", + "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", + "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe", + "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", + "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", + "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", + "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2", + "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca", + "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", + "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f", + "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb", + "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", + "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557", + "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", + "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7", + "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72", + "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", + "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.3" + }, "django": { "hashes": [ "sha256:0745b25681b129a77aae3d4f6549b62d3913d74407831abaa0d9021a03954bae", @@ -51,6 +144,15 @@ "markers": "python_version >= '3.9'", "version": "==3.16.1" }, + "djangorestframework-simplejwt": { + "hashes": [ + "sha256:2c30f3707053d384e9f315d11c2daccfcb548d4faa453111ca19a542b732e469", + "sha256:e72c5572f51d7803021288e2057afcbd03f17fe11d484096f40a460abc76e87f" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==5.5.1" + }, "dotenv": { "hashes": [ "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9" @@ -58,6 +160,14 @@ "index": "pypi", "version": "==0.9.9" }, + "idna": { + "hashes": [ + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + ], + "markers": "python_version >= '3.6'", + "version": "==3.10" + }, "iniconfig": { "hashes": [ "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", @@ -174,6 +284,15 @@ "markers": "python_version >= '3.8'", "version": "==2.19.2" }, + "pyjwt": { + "hashes": [ + "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", + "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==2.10.1" + }, "pytest": { "hashes": [ "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", @@ -200,6 +319,15 @@ "markers": "python_version >= '3.9'", "version": "==1.1.1" }, + "requests": { + "hashes": [ + "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", + "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==2.32.5" + }, "sqlparse": { "hashes": [ "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", @@ -207,6 +335,14 @@ ], "markers": "python_version >= '3.8'", "version": "==0.5.3" + }, + "urllib3": { + "hashes": [ + "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", + "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" + ], + "markers": "python_version >= '3.9'", + "version": "==2.5.0" } }, "develop": {} diff --git a/backend/requirements.txt b/backend/requirements.txt index 2758db7..d509463 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,15 +1,22 @@ asgiref==3.9.1 +certifi==2025.8.3 +charset-normalizer==3.4.3 Django==5.2.5 django-cors-headers==4.7.0 djangorestframework==3.16.1 +djangorestframework_simplejwt==5.5.1 dotenv==0.9.9 +idna==3.10 iniconfig==2.1.0 packaging==25.0 pluggy==1.6.0 psycopg2-binary==2.9.10 pycodestyle==2.14.0 Pygments==2.19.2 +PyJWT==2.10.1 pytest==8.4.1 pytest-django==4.11.1 python-dotenv==1.1.1 +requests==2.32.5 sqlparse==0.5.3 +urllib3==2.5.0 diff --git a/frontend/.prettierignore b/frontend/.prettierignore new file mode 100644 index 0000000..200c99d --- /dev/null +++ b/frontend/.prettierignore @@ -0,0 +1,9 @@ +node_modules +.next +dist +out +coverage +public +.vscode +*.log +*.lock \ No newline at end of file diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 0000000..4ac25a5 --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,9 @@ +{ + "semi": false, + "singleQuote": true, + "trailingComma": "es5", + "printWidth": 100, + "tabWidth": 2, + "arrowParens": "avoid", + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/frontend/app/(auth)/login/page.tsx b/frontend/app/(auth)/login/page.tsx new file mode 100644 index 0000000..1621d08 --- /dev/null +++ b/frontend/app/(auth)/login/page.tsx @@ -0,0 +1,7 @@ +import React from 'react' + +const LoginPage = () => { + return
LoginPage
+} + +export default LoginPage diff --git a/frontend/app/(protected)/dashboard/page.tsx b/frontend/app/(protected)/dashboard/page.tsx new file mode 100644 index 0000000..983ebb8 --- /dev/null +++ b/frontend/app/(protected)/dashboard/page.tsx @@ -0,0 +1,9 @@ +import React from 'react' + +const page = () => { + return ( +
page
+ ) +} + +export default page \ No newline at end of file diff --git a/frontend/app/(protected)/objects/page.tsx b/frontend/app/(protected)/objects/page.tsx new file mode 100644 index 0000000..983ebb8 --- /dev/null +++ b/frontend/app/(protected)/objects/page.tsx @@ -0,0 +1,9 @@ +import React from 'react' + +const page = () => { + return ( +
page
+ ) +} + +export default page \ No newline at end of file diff --git a/frontend/app/hooks/useClientFetch.ts b/frontend/app/hooks/useClientFetch.ts new file mode 100644 index 0000000..ebca539 --- /dev/null +++ b/frontend/app/hooks/useClientFetch.ts @@ -0,0 +1,131 @@ +import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query' +import axios, { AxiosError, AxiosRequestConfig } from 'axios' + +type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' + +interface FetchOptions { + method?: HttpMethod + config?: AxiosRequestConfig + queryOptions?: Omit, 'queryKey' | 'queryFn'> + mutationOptions?: Omit, 'mutationFn'> +} + +interface QueryResult { + data: TData | undefined + isLoading: boolean + error: TError | null + refetch: () => Promise +} + +interface MutationResult { + mutate: (variables: TVariables) => void + isLoading: boolean + error: TError | null + data: TData | undefined +} + +export function useClientFetch( + url: string, + options: FetchOptions = {} +): TVariables extends void + ? QueryResult + : MutationResult { + const { method = 'GET', config = {}, queryOptions = {}, mutationOptions = {} } = options + + const API_URL = process.env.NEXT_PUBLIC_API_URL + const fullUrl = url.startsWith('http') ? url : `${API_URL}${url}` + + // всегда вызываем оба хука + const query = useQuery({ + queryKey: [url, config.params], + queryFn: async () => { + const response = await axios.get(fullUrl, config) + return response.data + }, + ...queryOptions, + // отключаем автоматическое выполнение для мутаций + enabled: method === 'GET' && queryOptions.enabled !== false, + }) + + const mutation = useMutation({ + mutationFn: async (variables: TVariables) => { + const response = await axios({ + method: method.toLowerCase(), + url: fullUrl, + data: variables, + ...config, + }) + return response.data + }, + ...mutationOptions, + }) + + // возвращаем соответствующий результат в зависимости от метода + if (method === 'GET') { + return { + data: query.data, + isLoading: query.isLoading, + error: query.error, + refetch: query.refetch, + } as TVariables extends void ? QueryResult : never + } + + return { + mutate: mutation.mutate, + isLoading: mutation.isPending, + error: mutation.error, + data: mutation.data, + } as TVariables extends void ? never : MutationResult +} + +// примеры использования: + +/* +// GET запрос +interface UserData { + id: number + name: string + email: string +} + +const { data, isLoading, error } = useClientFetch('/users/me') + +// POST запрос с типизированным payload +interface LoginPayload { + email: string + password: string +} + +interface LoginResponse { + token: string + user: UserData +} + +const { mutate, isLoading } = useClientFetch('/auth/login', { + method: 'POST', + mutationOptions: { + onSuccess: (data) => { + // data типизирован как LoginResponse + }, + onError: (error) => { + // error типизирован как AxiosError + } + } +}) + +// использование: +mutate({ email: 'user@example.com', password: '123456' }) + +// PATCH запрос +interface UpdateUserPayload { + name?: string + email?: string +} + +const { mutate } = useClientFetch('/users/me', { + method: 'PATCH' +}) + +// использование: +mutate({ name: 'New Name' }) +*/ diff --git a/frontend/app/hooks/useForm.ts b/frontend/app/hooks/useForm.ts new file mode 100644 index 0000000..5a0d752 --- /dev/null +++ b/frontend/app/hooks/useForm.ts @@ -0,0 +1,150 @@ +'use client' + +import { useState, ChangeEvent, FormEvent } from 'react' +import toast from 'react-hot-toast' +import { ValidationRules, ValidationErrors } from '../types' + +type FormValue = string | number | boolean | string[] | number[] | null + +type CustomChangeEvent = { + target: { + id: string + value: FormValue + type?: string + checked?: boolean + } +} + +export function useForm>( + initialValues: T, + validationRules?: { [K in keyof T]?: ValidationRules }, + onSubmit?: (values: T) => void +) { + const [values, setValues] = useState(initialValues) + const [errors, setErrors] = useState({}) + const [isVisible, setIsVisible] = useState(false) + + const handleChange = ( + e: ChangeEvent | CustomChangeEvent + ) => { + const { id, value, type } = e.target + const isCheckbox = type === 'checkbox' && 'checked' in e.target + + setValues(prev => ({ + ...prev, + [id]: isCheckbox ? (e.target as HTMLInputElement).checked : value, + })) + + // скидываем ошибки + if (errors[id]) { + setErrors(prev => { + const newErrors = { ...prev } + delete newErrors[id] + return newErrors + }) + } + } + + const resetField = (fieldName: keyof T, value: FormValue = '') => { + setValues(prev => ({ + ...prev, + [fieldName]: value, + })) + + // очищаем ошибки для этого поля, если они есть + if (errors[fieldName as string]) { + setErrors(prev => { + const newErrors = { ...prev } + delete newErrors[fieldName as string] + return newErrors + }) + } + } + + const fieldNames: { [key: string]: string } = { + login: 'Логин', + password: 'Пароль', + accountType: 'Роль', + } + + const validate = () => { + if (!validationRules) return true + + const newErrors: ValidationErrors = {} + + Object.keys(validationRules).forEach(key => { + const value = values[key] + const rules = validationRules[key as keyof T] + + if (rules?.required && !value) { + newErrors[key] = 'Это поле обязательно' + toast.error(`Поле "${fieldNames[key] || key}" обязательно для заполнения`, { + duration: 2000, + position: 'top-right', + style: { + background: '#FEE2E2', + color: '#991B1B', + padding: '16px', + borderRadius: '8px', + }, + }) + } + + if (rules?.minLength && typeof value === 'string' && value.length < rules.minLength) { + newErrors[key] = `Минимальная длина ${rules.minLength} символов` + toast.error( + `Минимальная длина поля "${fieldNames[key] || key}" - ${rules.minLength} символов`, + { + duration: 2000, + position: 'top-right', + style: { + background: '#FEE2E2', + color: '#991B1B', + padding: '16px', + borderRadius: '8px', + }, + } + ) + } + + if (rules?.pattern && typeof value === 'string' && !rules.pattern.test(value)) { + newErrors[key] = 'Неверный формат' + toast.error(`Поле "${fieldNames[key] || key}" заполнено некорректно`, { + duration: 2000, + position: 'top-right', + style: { + background: '#FEE2E2', + color: '#991B1B', + padding: '16px', + borderRadius: '8px', + }, + }) + } + }) + + setErrors(newErrors) + return Object.keys(newErrors).length === 0 + } + + const handleSubmit = (e: FormEvent) => { + e.preventDefault() + if (validate() && onSubmit) { + onSubmit(values) + } + } + + const togglePasswordVisibility = () => { + setIsVisible(!isVisible) + } + + return { + values, + errors, + isVisible, + setValues, + handleChange, + handleSubmit, + togglePasswordVisibility, + resetField, + } +} diff --git a/frontend/app/types/index.ts b/frontend/app/types/index.ts new file mode 100644 index 0000000..2432b97 --- /dev/null +++ b/frontend/app/types/index.ts @@ -0,0 +1,9 @@ +export interface ValidationRules { + required?: boolean + minLength?: number + pattern?: RegExp +} + +export interface ValidationErrors { + [key: string]: string +} \ No newline at end of file diff --git a/frontend/components/ui/Selector.tsx b/frontend/components/ui/Selector.tsx new file mode 100644 index 0000000..a0d3e22 --- /dev/null +++ b/frontend/components/ui/Selector.tsx @@ -0,0 +1,9 @@ +import React from 'react' + +const Selector = () => { + return ( +
Selector
+ ) +} + +export default Selector \ No newline at end of file diff --git a/frontend/components/ui/TextInput.tsx b/frontend/components/ui/TextInput.tsx new file mode 100644 index 0000000..eed2790 --- /dev/null +++ b/frontend/components/ui/TextInput.tsx @@ -0,0 +1,9 @@ +import React from 'react' + +const TextInput = () => { + return ( +
TextInput
+ ) +} + +export default TextInput \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2a52ec9..d6edc9e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,9 +10,15 @@ "dependencies": { "@babylonjs/core": "^6.44.0", "@babylonjs/loaders": "^6.49.0", + "@tanstack/react-query": "^5.85.5", + "axios": "^1.11.0", "next": "^15.4.3", + "next-auth": "^4.24.11", "react": "19.1.0", - "react-dom": "19.1.0" + "react-dom": "19.1.0", + "react-hot-toast": "^2.6.0", + "react-select": "^5.10.2", + "zustand": "^5.0.8" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -22,6 +28,8 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.4.3", + "prettier": "^3.6.2", + "prettier-plugin-tailwindcss": "^0.6.14", "tailwindcss": "^4", "typescript": "^5" } @@ -39,6 +47,145 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babylonjs/core": { "version": "6.49.0", "resolved": "https://registry.npmjs.org/@babylonjs/core/-/core-6.49.0.tgz", @@ -56,21 +203,21 @@ } }, "node_modules/@emnapi/core": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", - "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", + "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.0.4", + "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", - "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", "license": "MIT", "optional": true, "dependencies": { @@ -78,9 +225,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", - "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", "dev": true, "license": "MIT", "optional": true, @@ -88,6 +235,120 @@ "tslib": "^2.4.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -229,6 +490,31 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -730,7 +1016,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -752,7 +1037,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -762,14 +1046,12 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.30", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -790,9 +1072,9 @@ } }, "node_modules/@next/env": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.0.tgz", - "integrity": "sha512-sDaprBAfzCQiOgo2pO+LhnV0Wt2wBgartjrr+dpcTORYVnnXD0gwhHhiiyIih9hQbq+JnbqH4odgcFWhqCGidw==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.2.tgz", + "integrity": "sha512-Qe06ew4zt12LeO6N7j8/nULSOe3fMXE4dM6xgpBQNvdzyK1sv5y4oAP3bq4LamrvGCZtmRYnW8URFCeX5nFgGg==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -806,9 +1088,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.0.tgz", - "integrity": "sha512-v7Jj9iqC6enxIRBIScD/o0lH7QKvSxq2LM8UTyqJi+S2w2QzhMYjven4vgu/RzgsdtdbpkyCxBTzHl/gN5rTRg==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.2.tgz", + "integrity": "sha512-8bGt577BXGSd4iqFygmzIfTYizHb0LGWqH+qgIF/2EDxS5JsSdERJKA8WgwDyNBZgTIIA4D8qUtoQHmxIIquoQ==", "cpu": [ "arm64" ], @@ -822,9 +1104,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.0.tgz", - "integrity": "sha512-s2Nk6ec+pmYmAb/utawuURy7uvyYKDk+TRE5aqLRsdnj3AhwC9IKUBmhfnLmY/+P+DnwqpeXEFIKe9tlG0p6CA==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.2.tgz", + "integrity": "sha512-2DjnmR6JHK4X+dgTXt5/sOCu/7yPtqpYt8s8hLkHFK3MGkka2snTv3yRMdHvuRtJVkPwCGsvBSwmoQCHatauFQ==", "cpu": [ "x64" ], @@ -838,9 +1120,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.0.tgz", - "integrity": "sha512-mGlPJMZReU4yP5fSHjOxiTYvZmwPSWn/eF/dcg21pwfmiUCKS1amFvf1F1RkLHPIMPfocxLViNWFvkvDB14Isg==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.2.tgz", + "integrity": "sha512-3j7SWDBS2Wov/L9q0mFJtEvQ5miIqfO4l7d2m9Mo06ddsgUK8gWfHGgbjdFlCp2Ek7MmMQZSxpGFqcC8zGh2AA==", "cpu": [ "arm64" ], @@ -854,9 +1136,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.0.tgz", - "integrity": "sha512-biWqIOE17OW/6S34t1X8K/3vb1+svp5ji5QQT/IKR+VfM3B7GvlCwmz5XtlEan2ukOUf9tj2vJJBffaGH4fGRw==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.2.tgz", + "integrity": "sha512-s6N8k8dF9YGc5T01UPQ08yxsK6fUow5gG1/axWc1HVVBYQBgOjca4oUZF7s4p+kwhkB1bDSGR8QznWrFZ/Rt5g==", "cpu": [ "arm64" ], @@ -870,9 +1152,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.0.tgz", - "integrity": "sha512-zPisT+obYypM/l6EZ0yRkK3LEuoZqHaSoYKj+5jiD9ESHwdr6QhnabnNxYkdy34uCigNlWIaCbjFmQ8FY5AlxA==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.2.tgz", + "integrity": "sha512-o1RV/KOODQh6dM6ZRJGZbc+MOAHww33Vbs5JC9Mp1gDk8cpEO+cYC/l7rweiEalkSm5/1WGa4zY7xrNwObN4+Q==", "cpu": [ "x64" ], @@ -886,9 +1168,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.0.tgz", - "integrity": "sha512-+t3+7GoU9IYmk+N+FHKBNFdahaReoAktdOpXHFIPOU1ixxtdge26NgQEEkJkCw2dHT9UwwK5zw4mAsURw4E8jA==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.2.tgz", + "integrity": "sha512-/VUnh7w8RElYZ0IV83nUcP/J4KJ6LLYliiBIri3p3aW2giF+PAVgZb6mk8jbQSB3WlTai8gEmCAr7kptFa1H6g==", "cpu": [ "x64" ], @@ -902,9 +1184,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.0.tgz", - "integrity": "sha512-d8MrXKh0A+c9DLiy1BUFwtg3Hu90Lucj3k6iKTUdPOv42Ve2UiIG8HYi3UAb8kFVluXxEfdpCoPPCSODk5fDcw==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.2.tgz", + "integrity": "sha512-sMPyTvRcNKXseNQ/7qRfVRLa0VhR0esmQ29DD6pqvG71+JdVnESJaHPA8t7bc67KD5spP3+DOCNLhqlEI2ZgQg==", "cpu": [ "arm64" ], @@ -918,9 +1200,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.0.tgz", - "integrity": "sha512-Fe1tGHxOWEyQjmygWkkXSwhFcTJuimrNu52JEuwItrKJVV4iRjbWp9I7zZjwqtiNnQmxoEvoisn8wueFLrNpvQ==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.2.tgz", + "integrity": "sha512-W5VvyZHnxG/2ukhZF/9Ikdra5fdNftxI6ybeVKYvBPDtyx7x4jPPSNduUkfH5fo3zG0JQ0bPxgy41af2JX5D4Q==", "cpu": [ "x64" ], @@ -981,6 +1263,15 @@ "node": ">=12.4.0" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1280,6 +1571,32 @@ "tailwindcss": "4.1.12" } }, + "node_modules/@tanstack/query-core": { + "version": "5.85.5", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.85.5.tgz", + "integrity": "sha512-KO0WTob4JEApv69iYp1eGvfMSUkgw//IpMnq+//cORBzXf0smyRwPLrUvEe5qtAEGjwZTXrjxg+oJNP/C00t6w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.85.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.85.5.tgz", + "integrity": "sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.85.5" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", @@ -1322,38 +1639,52 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, "node_modules/@types/react": { - "version": "19.1.11", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.11.tgz", - "integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==", - "dev": true, + "version": "19.1.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", + "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", "license": "MIT", "dependencies": { "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "19.1.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz", - "integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==", + "version": "19.1.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", + "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.40.0.tgz", - "integrity": "sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==", + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.41.0.tgz", + "integrity": "sha512-8fz6oa6wEKZrhXWro/S3n2eRJqlRcIa6SlDh59FXJ5Wp5XRZ8B9ixpJDcjadHq47hMx0u+HW6SNa6LjJQ6NLtw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.40.0", - "@typescript-eslint/type-utils": "8.40.0", - "@typescript-eslint/utils": "8.40.0", - "@typescript-eslint/visitor-keys": "8.40.0", + "@typescript-eslint/scope-manager": "8.41.0", + "@typescript-eslint/type-utils": "8.41.0", + "@typescript-eslint/utils": "8.41.0", + "@typescript-eslint/visitor-keys": "8.41.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -1367,7 +1698,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.40.0", + "@typescript-eslint/parser": "^8.41.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -1383,16 +1714,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.40.0.tgz", - "integrity": "sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==", + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.41.0.tgz", + "integrity": "sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.40.0", - "@typescript-eslint/types": "8.40.0", - "@typescript-eslint/typescript-estree": "8.40.0", - "@typescript-eslint/visitor-keys": "8.40.0", + "@typescript-eslint/scope-manager": "8.41.0", + "@typescript-eslint/types": "8.41.0", + "@typescript-eslint/typescript-estree": "8.41.0", + "@typescript-eslint/visitor-keys": "8.41.0", "debug": "^4.3.4" }, "engines": { @@ -1408,14 +1739,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.40.0.tgz", - "integrity": "sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==", + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.41.0.tgz", + "integrity": "sha512-b8V9SdGBQzQdjJ/IO3eDifGpDBJfvrNTp2QD9P2BeqWTGrRibgfgIlBSw6z3b6R7dPzg752tOs4u/7yCLxksSQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.40.0", - "@typescript-eslint/types": "^8.40.0", + "@typescript-eslint/tsconfig-utils": "^8.41.0", + "@typescript-eslint/types": "^8.41.0", "debug": "^4.3.4" }, "engines": { @@ -1430,14 +1761,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.40.0.tgz", - "integrity": "sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==", + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.41.0.tgz", + "integrity": "sha512-n6m05bXn/Cd6DZDGyrpXrELCPVaTnLdPToyhBoFkLIMznRUQUEQdSp96s/pcWSQdqOhrgR1mzJ+yItK7T+WPMQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.40.0", - "@typescript-eslint/visitor-keys": "8.40.0" + "@typescript-eslint/types": "8.41.0", + "@typescript-eslint/visitor-keys": "8.41.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1448,9 +1779,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.40.0.tgz", - "integrity": "sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==", + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.41.0.tgz", + "integrity": "sha512-TDhxYFPUYRFxFhuU5hTIJk+auzM/wKvWgoNYOPcOf6i4ReYlOoYN8q1dV5kOTjNQNJgzWN3TUUQMtlLOcUgdUw==", "dev": true, "license": "MIT", "engines": { @@ -1465,15 +1796,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.40.0.tgz", - "integrity": "sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==", + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.41.0.tgz", + "integrity": "sha512-63qt1h91vg3KsjVVonFJWjgSK7pZHSQFKH6uwqxAH9bBrsyRhO6ONoKyXxyVBzG1lJnFAJcKAcxLS54N1ee1OQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.40.0", - "@typescript-eslint/typescript-estree": "8.40.0", - "@typescript-eslint/utils": "8.40.0", + "@typescript-eslint/types": "8.41.0", + "@typescript-eslint/typescript-estree": "8.41.0", + "@typescript-eslint/utils": "8.41.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -1490,9 +1821,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.40.0.tgz", - "integrity": "sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==", + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.41.0.tgz", + "integrity": "sha512-9EwxsWdVqh42afLbHP90n2VdHaWU/oWgbH2P0CfcNfdKL7CuKpwMQGjwev56vWu9cSKU7FWSu6r9zck6CVfnag==", "dev": true, "license": "MIT", "engines": { @@ -1504,16 +1835,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.40.0.tgz", - "integrity": "sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==", + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.41.0.tgz", + "integrity": "sha512-D43UwUYJmGhuwHfY7MtNKRZMmfd8+p/eNSfFe6tH5mbVDto+VQCayeAt35rOx3Cs6wxD16DQtIKw/YXxt5E0UQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.40.0", - "@typescript-eslint/tsconfig-utils": "8.40.0", - "@typescript-eslint/types": "8.40.0", - "@typescript-eslint/visitor-keys": "8.40.0", + "@typescript-eslint/project-service": "8.41.0", + "@typescript-eslint/tsconfig-utils": "8.41.0", + "@typescript-eslint/types": "8.41.0", + "@typescript-eslint/visitor-keys": "8.41.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1589,16 +1920,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.40.0.tgz", - "integrity": "sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==", + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.41.0.tgz", + "integrity": "sha512-udbCVstxZ5jiPIXrdH+BZWnPatjlYwJuJkDA4Tbo3WyYLh8NvB+h/bKeSZHDOFKfphsZYJQqaFtLeXEqurQn1A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.40.0", - "@typescript-eslint/types": "8.40.0", - "@typescript-eslint/typescript-estree": "8.40.0" + "@typescript-eslint/scope-manager": "8.41.0", + "@typescript-eslint/types": "8.41.0", + "@typescript-eslint/typescript-estree": "8.41.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1613,13 +1944,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.40.0.tgz", - "integrity": "sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==", + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.41.0.tgz", + "integrity": "sha512-+GeGMebMCy0elMNg67LRNoVnUFPIm37iu5CmHESVx56/9Jsfdpsvbv605DQ81Pi/x11IdKUsS5nzgTYbCQU9fg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/types": "8.41.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -2149,6 +2480,12 @@ "node": ">= 0.4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2175,6 +2512,17 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -2185,6 +2533,21 @@ "node": ">= 0.4" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/babylonjs-gltf2interface": { "version": "6.49.0", "resolved": "https://registry.npmjs.org/babylonjs-gltf2interface/-/babylonjs-gltf2interface-6.49.0.tgz", @@ -2246,7 +2609,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2277,7 +2639,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2381,6 +2742,18 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2388,6 +2761,37 @@ "dev": true, "license": "MIT" }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2407,7 +2811,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -2475,7 +2878,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2532,6 +2934,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", @@ -2555,11 +2966,20 @@ "node": ">=0.10.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -2591,6 +3011,15 @@ "node": ">=10.13.0" } }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-abstract": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", @@ -2664,7 +3093,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2674,7 +3102,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2712,7 +3139,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2725,7 +3151,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2772,7 +3197,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -3294,6 +3718,12 @@ "node": ">=8" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3332,6 +3762,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -3348,11 +3798,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3393,7 +3858,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3418,7 +3882,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -3502,11 +3965,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3585,7 +4056,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3598,7 +4068,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -3614,7 +4083,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3623,6 +4091,15 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3637,7 +4114,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -3694,11 +4170,10 @@ } }, "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT", - "optional": true + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" }, "node_modules/is-async-function": { "version": "2.1.1", @@ -3780,7 +4255,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -4125,11 +4599,19 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -4145,6 +4627,18 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -4152,6 +4646,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -4478,6 +4978,12 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4505,7 +5011,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -4514,6 +5019,18 @@ "loose-envify": "cli.js" } }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/magic-string": { "version": "0.30.18", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", @@ -4528,12 +5045,17 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4558,6 +5080,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4624,7 +5167,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -4669,12 +5211,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.0.tgz", - "integrity": "sha512-N1lp9Hatw3a9XLt0307lGB4uTKsXDhyOKQo7uYMzX4i0nF/c27grcGXkLdb7VcT8QPYLBa8ouIyEoUQJ2OyeNQ==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.2.tgz", + "integrity": "sha512-H8Otr7abj1glFhbGnvUt3gz++0AF1+QoCXEBmd/6aKbfdFwrn0LpA836Ed5+00va/7HQSDD+mOoVhn3tNy3e/Q==", "license": "MIT", "dependencies": { - "@next/env": "15.5.0", + "@next/env": "15.5.2", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -4687,14 +5229,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.0", - "@next/swc-darwin-x64": "15.5.0", - "@next/swc-linux-arm64-gnu": "15.5.0", - "@next/swc-linux-arm64-musl": "15.5.0", - "@next/swc-linux-x64-gnu": "15.5.0", - "@next/swc-linux-x64-musl": "15.5.0", - "@next/swc-win32-arm64-msvc": "15.5.0", - "@next/swc-win32-x64-msvc": "15.5.0", + "@next/swc-darwin-arm64": "15.5.2", + "@next/swc-darwin-x64": "15.5.2", + "@next/swc-linux-arm64-gnu": "15.5.2", + "@next/swc-linux-arm64-musl": "15.5.2", + "@next/swc-linux-x64-gnu": "15.5.2", + "@next/swc-linux-x64-musl": "15.5.2", + "@next/swc-win32-arm64-msvc": "15.5.2", + "@next/swc-win32-x64-msvc": "15.5.2", "sharp": "^0.34.3" }, "peerDependencies": { @@ -4720,6 +5262,38 @@ } } }, + "node_modules/next-auth": { + "version": "4.24.11", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.11.tgz", + "integrity": "sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw==", + "license": "ISC", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.7.0", + "jose": "^4.15.5", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "@auth/core": "0.34.2", + "next": "^12.2.5 || ^13 || ^14 || ^15", + "nodemailer": "^6.6.5", + "react": "^17.0.2 || ^18 || ^19", + "react-dom": "^17.0.2 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@auth/core": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -4748,16 +5322,30 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -4871,6 +5459,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oidc-token-hash": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.1.tgz", + "integrity": "sha512-D7EmwxJV6DsEB6vOFLrBM2OzsVgQzgPWyHlV2OOAVj772n+WTXpudC9e9u5BVKQnYwaD30Ivhi9b+4UeBcGu9g==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, + "node_modules/openid-client": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz", + "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", + "license": "MIT", + "dependencies": { + "jose": "^4.15.9", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4943,7 +5555,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -4952,6 +5563,24 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4976,9 +5605,17 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, "license": "MIT" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5037,6 +5674,28 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.27.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.1.tgz", + "integrity": "sha512-V79raXEWch/rbqoNc7nT9E4ep7lu+mI3+sBmfRD4i1M73R3WLYcCtdI0ibxGVf4eQL8ZIz2nFacqEC+rmnOORQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "license": "MIT", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5047,11 +5706,119 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.14", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz", + "integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -5059,6 +5826,12 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5111,13 +5884,66 @@ "react": "^19.1.0" } }, + "node_modules/react-hot-toast": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, + "node_modules/react-select": { + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.2.tgz", + "integrity": "sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@floating-ui/dom": "^1.0.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0", + "use-isomorphic-layout-effect": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -5166,7 +5992,6 @@ "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.0", @@ -5187,7 +6012,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -5513,6 +6337,22 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT", + "optional": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5702,6 +6542,12 @@ } } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5719,7 +6565,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5767,6 +6612,16 @@ "node": ">=18" } }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", @@ -6036,6 +6891,29 @@ "punycode": "^2.1.0" } }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", + "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6152,13 +7030,18 @@ } }, "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", "engines": { - "node": ">=18" + "node": ">= 6" } }, "node_modules/yocto-queue": { @@ -6173,6 +7056,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", + "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/frontend/package.json b/frontend/package.json index 3adbca3..9b31920 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,9 +11,15 @@ "dependencies": { "@babylonjs/core": "^6.44.0", "@babylonjs/loaders": "^6.49.0", + "@tanstack/react-query": "^5.85.5", + "axios": "^1.11.0", "next": "^15.4.3", + "next-auth": "^4.24.11", "react": "19.1.0", - "react-dom": "19.1.0" + "react-dom": "19.1.0", + "react-hot-toast": "^2.6.0", + "react-select": "^5.10.2", + "zustand": "^5.0.8" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -23,6 +29,8 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.4.3", + "prettier": "^3.6.2", + "prettier-plugin-tailwindcss": "^0.6.14", "tailwindcss": "^4", "typescript": "^5" } From 9d604e896590ae5e19455eff6d1526555eae41b4 Mon Sep 17 00:00:00 2001 From: Timofey Date: Fri, 29 Aug 2025 14:09:35 +0300 Subject: [PATCH 2/7] remove existed code to protected/model --- frontend/app/(protected)/model/page.tsx | 60 +++++++++++++++++++++++++ frontend/app/default.ts | 1 + frontend/app/page.tsx | 59 +----------------------- frontend/app/store/userStore.ts | 47 +++++++++++++++++++ frontend/app/types/index.ts | 27 ++++++++++- frontend/components/ui/ShowToast.tsx | 59 ++++++++++++++++++++++++ 6 files changed, 195 insertions(+), 58 deletions(-) create mode 100644 frontend/app/(protected)/model/page.tsx create mode 100644 frontend/app/default.ts create mode 100644 frontend/app/store/userStore.ts create mode 100644 frontend/components/ui/ShowToast.tsx diff --git a/frontend/app/(protected)/model/page.tsx b/frontend/app/(protected)/model/page.tsx new file mode 100644 index 0000000..17a6231 --- /dev/null +++ b/frontend/app/(protected)/model/page.tsx @@ -0,0 +1,60 @@ +'use client' + +import React, { useState } from 'react' +import ModelViewer from '@/components/ModelViewer' + +export default function Home() { + const [modelInfo, setModelInfo] = useState<{ + meshes: unknown[] + boundingBox: { + min: { x: number; y: number; z: number } + max: { x: number; y: number; z: number } + } + } | null>(null) + const [error, setError] = useState(null) + + const handleModelLoaded = (data: { + meshes: unknown[] + boundingBox: { + min: { x: number; y: number; z: number } + max: { x: number; y: number; z: number } + } + }) => { + setModelInfo(data) + setError(null) + console.log('Model loaded successfully:', data) + } + + const handleError = (errorMessage: string) => { + setError(errorMessage) + setModelInfo(null) + } + + return ( +
+ + + {error && ( +
+ Error: {error} +
+ )} + + {modelInfo && ( +
+

EXPO Building Model

+ +
+
🖱️ Left click + drag: Rotate
+
🖱️ Right click + drag: Pan
+
🖱️ Scroll: Zoom in/out
+
+
+ )} +
+ ) +} diff --git a/frontend/app/default.ts b/frontend/app/default.ts new file mode 100644 index 0000000..8bc948e --- /dev/null +++ b/frontend/app/default.ts @@ -0,0 +1 @@ +export { redirect as default } from 'next/navigation' diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index 00f5bb1..0a35558 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -1,60 +1,5 @@ -'use client' - -import React, { useState } from 'react' -import ModelViewer from '../components/ModelViewer' +import { redirect } from 'next/navigation' export default function Home() { - const [modelInfo, setModelInfo] = useState<{ - meshes: unknown[] - boundingBox: { - min: { x: number; y: number; z: number } - max: { x: number; y: number; z: number } - } - } | null>(null) - const [error, setError] = useState(null) - - const handleModelLoaded = (data: { - meshes: unknown[] - boundingBox: { - min: { x: number; y: number; z: number } - max: { x: number; y: number; z: number } - } - }) => { - setModelInfo(data) - setError(null) - console.log('Model loaded successfully:', data) - } - - const handleError = (errorMessage: string) => { - setError(errorMessage) - setModelInfo(null) - } - - return ( -
- - - {error && ( -
- Error: {error} -
- )} - - {modelInfo && ( -
-

EXPO Building Model

- -
-
🖱️ Left click + drag: Rotate
-
🖱️ Right click + drag: Pan
-
🖱️ Scroll: Zoom in/out
-
-
- )} -
- ) + redirect('/objects') } diff --git a/frontend/app/store/userStore.ts b/frontend/app/store/userStore.ts new file mode 100644 index 0000000..bbaec70 --- /dev/null +++ b/frontend/app/store/userStore.ts @@ -0,0 +1,47 @@ +import { create } from 'zustand' +import { persist } from 'zustand/middleware' +import { User, UserState } from '../types' + +interface UserStore extends UserState { + // состояние + isAuthenticated: boolean + user: User | null + + // действия + setUser: (user: User | null) => void + setAuthenticated: (isAuthenticated: boolean) => void + logout: () => void + + // асинхронные действия + //! что пользователь может делать асинхронно? +} + +const useUserStore = create()( + persist( + set => ({ + // начальное состояние + isAuthenticated: false, + user: null, + favorites: [], + + // синхронные действия + setUser: user => set({ user }), + setAuthenticated: isAuthenticated => set({ isAuthenticated }), + logout: () => + set({ + isAuthenticated: false, + user: null, + }), + }), + + //! асинхронщина? + { name: 'user-store' } + ) +) + +export default useUserStore + +// пример использования +// const { user, isAuthenticated } = useUserStore() -- получаем данные из стора +// const { setUser, setAuthenticated } = useUserStore() -- устанавливаем данные в стор +// const { logout } = useUserStore() -- выходим из пользовательского аккаунта diff --git a/frontend/app/types/index.ts b/frontend/app/types/index.ts index 2432b97..a5cf347 100644 --- a/frontend/app/types/index.ts +++ b/frontend/app/types/index.ts @@ -6,4 +6,29 @@ export interface ValidationRules { export interface ValidationErrors { [key: string]: string -} \ No newline at end of file +} + +export type ToastProps = { + type: 'error' | 'success' | 'loading' + message: string + action?: { + text: string + onClick: () => void + } + duration?: number +} + +export interface User { + id?: number | undefined + name: string + surname: string + image?: string + email: string + account_type?: string + login: string +} + +export interface UserState { + isAuthenticated: boolean + user: User | null +} diff --git a/frontend/components/ui/ShowToast.tsx b/frontend/components/ui/ShowToast.tsx new file mode 100644 index 0000000..7d16387 --- /dev/null +++ b/frontend/components/ui/ShowToast.tsx @@ -0,0 +1,59 @@ +import toast from 'react-hot-toast' +import { ToastProps } from '@/app/types' + +const toastStyles = { + success: { + background: 'bg-green-50', + text: 'text-green-800', + border: 'border-green-200', + }, + error: { + background: 'bg-red-50', + text: 'text-red-800', + border: 'border-red-200', + }, + loading: { + background: 'bg-blue-50', + text: 'text-blue-800', + border: 'border-blue-200', + }, +} + +const showToast = ({ type, message, action, duration }: ToastProps) => { + const styles = toastStyles[type] + + toast.custom( + t => ( +
+
+
+
+

{message}

+ {action && ( +
+ +
+ )} +
+
+
+
+ ), + { duration: duration || 4000 } + ) +} + +export default showToast + +//пример использования: showToast({ type: 'error', message: 'Неверный email или пароль' }) From d314303066392acbfd783e473ecde6808aab7927 Mon Sep 17 00:00:00 2001 From: Timofey Date: Fri, 29 Aug 2025 14:20:50 +0300 Subject: [PATCH 3/7] backend router --- backend/api/auth/__init__.py | 0 backend/api/auth/serializers.py | 0 backend/api/auth/urls.py | 6 ++++++ backend/api/auth/views.py | 0 backend/api/urls.py | 5 +++++ backend/api/views.py | 3 --- backend/base/asgi.py | 9 --------- backend/base/settings.py | 29 ++++++++++++++++++++++++++--- backend/base/urls.py | 24 +++++++----------------- backend/base/wsgi.py | 9 --------- 10 files changed, 44 insertions(+), 41 deletions(-) create mode 100644 backend/api/auth/__init__.py create mode 100644 backend/api/auth/serializers.py create mode 100644 backend/api/auth/urls.py create mode 100644 backend/api/auth/views.py create mode 100644 backend/api/urls.py delete mode 100644 backend/api/views.py diff --git a/backend/api/auth/__init__.py b/backend/api/auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/auth/serializers.py b/backend/api/auth/serializers.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/auth/urls.py b/backend/api/auth/urls.py new file mode 100644 index 0000000..8cd93a9 --- /dev/null +++ b/backend/api/auth/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +# from . views import () + +urlpatterns = [ + +] \ No newline at end of file diff --git a/backend/api/auth/views.py b/backend/api/auth/views.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/urls.py b/backend/api/urls.py new file mode 100644 index 0000000..c194f05 --- /dev/null +++ b/backend/api/urls.py @@ -0,0 +1,5 @@ +from django.urls import path, include + +urlpatterns = [ + path('v1/auth/', include('api.auth.urls')) +] \ No newline at end of file diff --git a/backend/api/views.py b/backend/api/views.py deleted file mode 100644 index 91ea44a..0000000 --- a/backend/api/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/backend/base/asgi.py b/backend/base/asgi.py index 71a4cef..266428d 100644 --- a/backend/base/asgi.py +++ b/backend/base/asgi.py @@ -1,12 +1,3 @@ -""" -ASGI config for base project. - -It exposes the ASGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/ -""" - import os from django.core.asgi import get_asgi_application diff --git a/backend/base/settings.py b/backend/base/settings.py index 8618a55..5f03605 100644 --- a/backend/base/settings.py +++ b/backend/base/settings.py @@ -1,6 +1,7 @@ import os from pathlib import Path from dotenv import load_dotenv +from datetime import timedelta BASE_DIR = Path(__file__).resolve().parent.parent load_dotenv(dotenv_path=BASE_DIR / './.env') @@ -33,6 +34,26 @@ CORS_ALLOW_HEADERS = [ 'cookie' ] +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework_simplejwt.authentication.JWTAuthentication', + 'rest_framework.authentication.SessionAuthentication', + ], +} + + +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), + 'ROTATE_REFRESH_TOKENS': True, + 'BLACKLIST_AFTER_ROTATION': True, + 'UPDATE_LAST_LOGIN': True, + 'ALGORITHM': 'HS256', + 'SIGNING_KEY': SECRET_KEY, + 'VERIFYING_KEY': None, + 'AUTH_HEADER_TYPES': ('Bearer',), + 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), +} INSTALLED_APPS = [ 'django.contrib.admin', @@ -48,6 +69,7 @@ INSTALLED_APPS = [ ] MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -104,10 +126,11 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] -LANGUAGE_CODE = 'en-us' -TIME_ZONE = 'UTC' -USE_I18N = True +LANGUAGE_CODE = 'ru-ru' +TIME_ZONE = 'Europe/Minsk' USE_TZ = True +USE_I18N = True +USE_L10N = True STATIC_URL = 'static/' diff --git a/backend/base/urls.py b/backend/base/urls.py index c7a45b4..2d42021 100644 --- a/backend/base/urls.py +++ b/backend/base/urls.py @@ -1,22 +1,12 @@ -""" -URL configuration for base project. - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/5.2/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) -""" from django.contrib import admin -from django.urls import path +from django.urls import path, include +from django.conf import settings +from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), + path('api/', include('api.urls')), ] + +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/backend/base/wsgi.py b/backend/base/wsgi.py index 70ae90a..f846bd0 100644 --- a/backend/base/wsgi.py +++ b/backend/base/wsgi.py @@ -1,12 +1,3 @@ -""" -WSGI config for base project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/ -""" - import os from django.core.wsgi import get_wsgi_application From 5a06a625fbf0d29a3eed6b75ae5b573eb784b741 Mon Sep 17 00:00:00 2001 From: Timofey Date: Fri, 29 Aug 2025 14:44:26 +0300 Subject: [PATCH 4/7] login route --- backend/Pipfile | 1 + backend/Pipfile.lock | 287 +++++++++++++++++- backend/api/auth/serializers.py | 62 ++++ backend/api/auth/urls.py | 28 +- backend/api/auth/views.py | 167 ++++++++++ backend/api/models.py | 55 +++- backend/api/types.py | 9 + backend/api/utils/cookies.py | 65 ++++ backend/base/settings.py | 12 + backend/requirements.txt | 1 + .../sitemanagement/constants/account_types.py | 19 ++ 11 files changed, 701 insertions(+), 5 deletions(-) create mode 100644 backend/api/types.py create mode 100644 backend/api/utils/cookies.py create mode 100644 backend/sitemanagement/constants/account_types.py diff --git a/backend/Pipfile b/backend/Pipfile index 1eb723b..7510ff1 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -15,6 +15,7 @@ djangorestframework-simplejwt = "*" pycodestyle = "*" requests = "*" pyjwt = "*" +drf-spectacular = "*" [dev-packages] diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 37338d1..b075d50 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "952a50ce3f529481ea1b021ac88ac52462e1b6b7ace31e1f03125e6eca1b281e" + "sha256": "c8a08d8710d5b37141e66db971439bf41996f2ea1330f2d5716f8be2a841a796" }, "pipfile-spec": 6, "requires": { @@ -24,6 +24,14 @@ "markers": "python_version >= '3.9'", "version": "==3.9.1" }, + "attrs": { + "hashes": [ + "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", + "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b" + ], + "markers": "python_version >= '3.8'", + "version": "==25.3.0" + }, "certifi": { "hashes": [ "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", @@ -160,6 +168,15 @@ "index": "pypi", "version": "==0.9.9" }, + "drf-spectacular": { + "hashes": [ + "sha256:2c778a47a40ab2f5078a7c42e82baba07397bb35b074ae4680721b2805943061", + "sha256:856e7edf1056e49a4245e87a61e8da4baff46c83dbc25be1da2df77f354c7cb4" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==0.28.0" + }, "idna": { "hashes": [ "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", @@ -168,6 +185,14 @@ "markers": "python_version >= '3.6'", "version": "==3.10" }, + "inflection": { + "hashes": [ + "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", + "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2" + ], + "markers": "python_version >= '3.5'", + "version": "==0.5.1" + }, "iniconfig": { "hashes": [ "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", @@ -176,6 +201,22 @@ "markers": "python_version >= '3.8'", "version": "==2.1.0" }, + "jsonschema": { + "hashes": [ + "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", + "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85" + ], + "markers": "python_version >= '3.9'", + "version": "==4.25.1" + }, + "jsonschema-specifications": { + "hashes": [ + "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", + "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608" + ], + "markers": "python_version >= '3.9'", + "version": "==2025.4.1" + }, "packaging": { "hashes": [ "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", @@ -319,6 +360,73 @@ "markers": "python_version >= '3.9'", "version": "==1.1.1" }, + "pyyaml": { + "hashes": [ + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" + ], + "markers": "python_version >= '3.8'", + "version": "==6.0.2" + }, + "referencing": { + "hashes": [ + "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", + "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0" + ], + "markers": "python_version >= '3.9'", + "version": "==0.36.2" + }, "requests": { "hashes": [ "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", @@ -328,6 +436,167 @@ "markers": "python_version >= '3.9'", "version": "==2.32.5" }, + "rpds-py": { + "hashes": [ + "sha256:008b839781d6c9bf3b6a8984d1d8e56f0ec46dc56df61fd669c49b58ae800400", + "sha256:037a2361db72ee98d829bc2c5b7cc55598ae0a5e0ec1823a56ea99374cfd73c1", + "sha256:079bc583a26db831a985c5257797b2b5d3affb0386e7ff886256762f82113b5e", + "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f", + "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60", + "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059", + "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2", + "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff", + "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef", + "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd", + "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf", + "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d", + "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e", + "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52", + "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8", + "sha256:1fea2b1a922c47c51fd07d656324531adc787e415c8b116530a1d29c0516c62d", + "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc", + "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5", + "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", + "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf", + "sha256:2c426b99a068601b5f4623573df7a7c3d72e87533a2dd2253353a03e7502566c", + "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418", + "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746", + "sha256:2fd50659a069c15eef8aa3d64bbef0d69fd27bb4a50c9ab4f17f83a16cbf8905", + "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688", + "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39", + "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb", + "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502", + "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66", + "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b", + "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc", + "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675", + "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013", + "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1", + "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1", + "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a", + "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734", + "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5", + "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e", + "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92", + "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c", + "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195", + "sha256:4e44099bd522cba71a2c6b97f68e19f40e7d85399de899d66cdb67b32d7cb786", + "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274", + "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3", + "sha256:4fc9b7fe29478824361ead6e14e4f5aed570d477e06088826537e202d25fe859", + "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a", + "sha256:5281ed1cc1d49882f9997981c88df1a22e140ab41df19071222f7e5fc4e72125", + "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71", + "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83", + "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3", + "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5", + "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817", + "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48", + "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", + "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2", + "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948", + "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef", + "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde", + "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9", + "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802", + "sha256:70d0738ef8fee13c003b100c2fbd667ec4f133468109b3472d249231108283a3", + "sha256:71108900c9c3c8590697244b9519017a400d9ba26a36c48381b3f64743a44aab", + "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be", + "sha256:78af06ddc7fe5cc0e967085a9115accee665fb912c22a3f54bad70cc65b05fe6", + "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8", + "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad", + "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf", + "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec", + "sha256:7e32721e5d4922deaaf963469d795d5bde6093207c52fec719bd22e5d1bedbc4", + "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1", + "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a", + "sha256:8177002868d1426305bb5de1e138161c2ec9eb2d939be38291d7c431c4712df8", + "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39", + "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4", + "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab", + "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808", + "sha256:8a63b640a7845f2bdd232eb0d0a4a2dd939bcdd6c57e6bb134526487f3160ec5", + "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10", + "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797", + "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3", + "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61", + "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228", + "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4", + "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf", + "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881", + "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002", + "sha256:9e71f5a087ead99563c11fdaceee83ee982fd39cf67601f4fd66cb386336ee52", + "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9", + "sha256:a46fdec0083a26415f11d5f236b79fa1291c32aaa4a17684d82f7017a1f818b1", + "sha256:a50431bf02583e21bf273c71b89d710e7a710ad5e39c725b14e685610555926f", + "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998", + "sha256:a55b9132bb1ade6c734ddd2759c8dc132aa63687d259e725221f106b83a0e485", + "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456", + "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd", + "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e", + "sha256:aa8933159edc50be265ed22b401125c9eebff3171f570258854dbce3ecd55475", + "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e", + "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c", + "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334", + "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90", + "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2", + "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657", + "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15", + "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b", + "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33", + "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2", + "sha256:bbf94c58e8e0cd6b6f38d8de67acae41b3a515c26169366ab58bdca4a6883bb8", + "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881", + "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136", + "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212", + "sha256:c2a8fed130ce946d5c585eddc7c8eeef0051f58ac80a8ee43bd17835c144c2cc", + "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0", + "sha256:c4b676c4ae3921649a15d28ed10025548e9b561ded473aa413af749503c6737e", + "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819", + "sha256:c918c65ec2e42c2a78d19f18c553d77319119bf43aa9e2edf7fb78d624355527", + "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed", + "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df", + "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb", + "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a", + "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a", + "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21", + "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf", + "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8", + "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594", + "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a", + "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e", + "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7", + "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8", + "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6", + "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3", + "sha256:e202e6d4188e53c6661af813b46c37ca2c45e497fc558bacc1a7630ec2695aec", + "sha256:e2f6fd8a1cea5bbe599b6e78a6e5ee08db434fc8ffea51ff201c8765679698b3", + "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723", + "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b", + "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb", + "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081", + "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7", + "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d", + "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9", + "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9", + "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4", + "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444", + "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a", + "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0", + "sha256:f41f814b8eaa48768d1bb551591f6ba45f87ac76899453e8ccd41dba1289b04b", + "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83", + "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3", + "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636", + "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc", + "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2", + "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a", + "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb", + "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec", + "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21" + ], + "markers": "python_version >= '3.9'", + "version": "==0.27.1" + }, "sqlparse": { "hashes": [ "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", @@ -336,6 +605,22 @@ "markers": "python_version >= '3.8'", "version": "==0.5.3" }, + "typing-extensions": { + "hashes": [ + "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", + "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" + ], + "markers": "python_version >= '3.9'", + "version": "==4.15.0" + }, + "uritemplate": { + "hashes": [ + "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", + "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686" + ], + "markers": "python_version >= '3.9'", + "version": "==4.2.0" + }, "urllib3": { "hashes": [ "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", diff --git a/backend/api/auth/serializers.py b/backend/api/auth/serializers.py index e69de29..63c1962 100644 --- a/backend/api/auth/serializers.py +++ b/backend/api/auth/serializers.py @@ -0,0 +1,62 @@ +from typing import Any, Optional +from rest_framework import serializers +from django.conf import settings +from api.types import User + +class UserResponseSerializer(serializers.Serializer): + id = serializers.IntegerField(read_only=True) + email = serializers.EmailField(read_only=True) + account_type = serializers.CharField( + source='userprofile.account_type', + read_only=True + ) + name = serializers.CharField(source='first_name', read_only=True) + surname = serializers.CharField(source='last_name', read_only=True) + imageURL = serializers.SerializerMethodField() + uuid = serializers.SerializerMethodField() + + class Meta: + ref_name = "UserResponse" # для OpenAPI + + def get_uuid(self, obj: User) -> Optional[str]: + """Получает короткий UUID (первые 6 символов) из профиля пользователя""" + return obj.userprofile.short_uuid if hasattr(obj, 'userprofile') else None + + def get_imageURL(self, obj: User) -> Optional[str]: + """Получает полный URL для изображения профиля пользователя""" + try: + if not hasattr(obj, 'userprofile') or not obj.userprofile.imageURL: + return None + + relative_url = obj.userprofile.imageURL.lstrip('/') + base_url = settings.BASE_URL.rstrip('/') + return f"{base_url}/{relative_url}" + except Exception: + return None + + def to_representation(self, instance: User) -> dict[str, Any]: + """Переопределяется для добавления проверки типа для вывода""" + data = super().to_representation(instance) + return { + 'id': data['id'], # int + 'email': data['email'], # str + 'account_type': data['account_type'], # AccountTypeLiteral + 'name': data['name'], # str + 'surname': data['surname'], # str + 'imageURL': data['imageURL'], # Optional[str] + 'uuid': data['uuid'], # Optional[str] + } + + +class LoginRequestSerializer(serializers.Serializer): + """Сериализатор для запроса авторизации""" + login = serializers.CharField(help_text="Логин пользователя") + password = serializers.CharField(help_text="Пароль пользователя", write_only=True) + + +class LoginResponseSerializer(serializers.Serializer): + """Сериализатор для ответа при успешной авторизации""" + message = serializers.CharField() + access = serializers.CharField() + refresh = serializers.CharField() + user = UserResponseSerializer() \ No newline at end of file diff --git a/backend/api/auth/urls.py b/backend/api/auth/urls.py index 8cd93a9..8862821 100644 --- a/backend/api/auth/urls.py +++ b/backend/api/auth/urls.py @@ -1,6 +1,28 @@ -from django.urls import path -# from . views import () +from django.urls import path, include +from .views import LoginViewSet, LogoutView +from rest_framework.routers import DefaultRouter +from drf_spectacular.views import ( + SpectacularAPIView, + SpectacularSwaggerView, + SpectacularRedocView, +) + +router = DefaultRouter() +router.register(r'', LoginViewSet, basename='auth') urlpatterns = [ - + path('', include(router.urls)), + path('logout/', LogoutView.as_view(), name='auth-logout'), + path('schema/', SpectacularAPIView.as_view(), name='schema'), + path( + 'docs/', + SpectacularSwaggerView.as_view(url_name='schema'), + name='swagger-ui', + ), + # ReDoc UI - альтернативный вариант отображения доков: + path( + 'redoc/', + SpectacularRedocView.as_view(url_name='schema'), + name='redoc', + ), ] \ No newline at end of file diff --git a/backend/api/auth/views.py b/backend/api/auth/views.py index e69de29..a04b351 100644 --- a/backend/api/auth/views.py +++ b/backend/api/auth/views.py @@ -0,0 +1,167 @@ +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework_simplejwt.tokens import RefreshToken +from rest_framework.views import APIView +from drf_spectacular.utils import extend_schema, OpenApiResponse, OpenApiExample +from drf_spectacular.types import OpenApiTypes + +from django.contrib.auth.models import User + +from .serializers import ( + UserResponseSerializer, + LoginRequestSerializer, + LoginResponseSerializer +) + +from api.utils.cookies import AuthBaseViewSet +from api.types import User + + +class LoginViewSet(AuthBaseViewSet): + """ViewSet для авторизации пользователей""" + serializer_class = LoginRequestSerializer + + @extend_schema( + summary="Авторизация пользователя", + description="Эндпоинт для авторизации пользователя по логину и паролю", + request=LoginRequestSerializer, + responses={ + 200: OpenApiResponse( + response=LoginResponseSerializer, + description="Успешная авторизация", + examples=[ + OpenApiExample( + 'Успешный ответ', + value={ + "message": "Успешная авторизация", + "access": "eyJ0eXAiOiJKV1QiLCJhbGc...", + "refresh": "eyJ0eXAiOiJKV1QiLCJhbGc...", + "user": { + "id": 1, + "email": "user@example.com", + "account_type": "engieneer", + "name": "Иван", + "surname": "Иванов", + "imageURL": "https://example.com/avatar.jpg", + "uuid": "abc123" + } + } + ) + ] + ), + 400: OpenApiResponse( + description="Неверные параметры запроса", + response=OpenApiTypes.OBJECT, + examples=[ + OpenApiExample( + 'Отсутствуют обязательные поля', + value={"error": "Логин и пароль обязательны"} + ) + ] + ), + 403: OpenApiResponse( + description="Неверный пароль", + response=OpenApiTypes.OBJECT, + examples=[ + OpenApiExample( + 'Неверный пароль', + value={"error": "Неверный пароль"} + ) + ] + ), + 404: OpenApiResponse( + description="Пользователь не найден", + response=OpenApiTypes.OBJECT, + examples=[ + OpenApiExample( + 'Пользователь не найден', + value={"error": "Пользователь не найден"} + ) + ] + ), + 500: OpenApiResponse( + description="Внутренняя ошибка сервера", + response=OpenApiTypes.OBJECT, + examples=[ + OpenApiExample( + 'Ошибка сервера', + value={"error": "Ошибка авторизации"} + ) + ] + ) + } + ) + @action(detail=False, methods=['post'], url_path="login") + def login_client(self, request): + try: + login = request.data.get("login") + password = request.data.get("password") + + if not login or not password: + return Response( + {"error": "Логин и пароль обязательны"}, + status=status.HTTP_400_BAD_REQUEST + ) + + try: + user = User.objects.get(login=login) + except User.DoesNotExist: + return Response( + {"error": "Пользователь не найден"}, + status=status.HTTP_404_NOT_FOUND + ) + if not user.check_password(password): + return Response( + {"error": "Неверный пароль"}, + status=status.HTTP_403_FORBIDDEN + ) + + refresh = RefreshToken.for_user(user) + + user_data = UserResponseSerializer(user).data + + response = Response({ + "message": "Успешная авторизация", + "access": str(refresh.access_token), + "refresh": str(refresh), + "user": user_data + }, status=status.HTTP_200_OK) + + # сеттим куки + return self._set_auth_cookies(response, refresh) + + except Exception as e: + return Response( + {"error": "Ошибка авторизации"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) + +class LogoutView(APIView): + """ViewSet для выхода из системы""" + + @extend_schema( + summary="Выход из системы", + description="Эндпоинт для выхода из системы, очищает все токены и куки", + responses={ + 200: OpenApiResponse( + description="Успешный выход", + response=OpenApiTypes.OBJECT, + examples=[ + OpenApiExample( + 'Успешный выход', + value={"message": "Logged out"} + ) + ] + ) + } + ) + def post(self, request): + response = Response({'message': 'Logged out'}, status=status.HTTP_200_OK) + + # чистим куки и sessionID + response.delete_cookie('access_token') + response.delete_cookie('refresh_token') + response.delete_cookie('sessionid') + + return response \ No newline at end of file diff --git a/backend/api/models.py b/backend/api/models.py index 71a8362..60ac79d 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -1,3 +1,56 @@ +from django.contrib.auth.models import User from django.db import models +from django.db.models.fields.related import OneToOneField +from typing import Optional +import uuid -# Create your models here. +from sitemanagement.constants.account_types import account_types, AccountType, AccountTypeLiteral + +class UserProfile(models.Model): + """ + Профиль пользователя с дополнительной информацией + """ + + def get_account_type_display(self) -> str: + """Автоматически добавляется Django для полей с choices""" + ... + + user: OneToOneField[User] = models.OneToOneField( + User, + on_delete=models.CASCADE, + related_name='userprofile' + ) + uuid: models.UUIDField = models.UUIDField( + default=uuid.uuid4, + editable=False, + unique=True, + null=True + ) + account_type: models.CharField = models.CharField( + max_length=10, + verbose_name="Тип аккаунта", + choices=account_types, + db_index=True + ) + imageURL: models.CharField = models.CharField( + max_length=255, + null=True, + blank=True, + verbose_name="URL изображения профиля" + ) + + class Meta: + verbose_name = "Профиль пользователя" + verbose_name_plural = "Профили пользователей" + + def __str__(self) -> str: + return f"{self.user.first_name} ({self.get_account_type_display()})" + + @property + def short_uuid(self) -> Optional[str]: + """Возвращает первые 6 символов UUID или None, если UUID не установлен""" + return str(self.uuid)[:6] if self.uuid else None + + def get_account_type(self) -> AccountTypeLiteral: + """Возвращает тип аккаунта пользователя""" + return AccountType(self.account_type).value \ No newline at end of file diff --git a/backend/api/types.py b/backend/api/types.py new file mode 100644 index 0000000..0b3c811 --- /dev/null +++ b/backend/api/types.py @@ -0,0 +1,9 @@ +from typing import TYPE_CHECKING +from django.contrib.auth.models import User as DjangoUser + +if TYPE_CHECKING: + from .models import UserProfile + +class User(DjangoUser): + """Тип для Django User с кастомной моделью UserProfile""" + userprofile: 'UserProfile' diff --git a/backend/api/utils/cookies.py b/backend/api/utils/cookies.py new file mode 100644 index 0000000..2eab99f --- /dev/null +++ b/backend/api/utils/cookies.py @@ -0,0 +1,65 @@ +from rest_framework.viewsets import ViewSet +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework import status +from datetime import datetime +from django.conf import settings +from rest_framework_simplejwt.tokens import RefreshToken + +class AuthBaseViewSet(ViewSet): + """Базовый класс для аутентификации с общими методами""" + + def _set_auth_cookies(self, response, refresh): + """Устанавливает куки для токенов аутентификации""" + response.set_cookie( + 'access_token', + str(refresh.access_token), + httponly=True, + secure=True, + samesite='Lax', + max_age=300 + ) + response.set_cookie( + 'refresh_token', + str(refresh), + httponly=True, + secure=True, + samesite='Lax', + max_age=86400 + ) + return response + + @action(detail=False, methods=['post'], url_path="refresh") + def refresh_token(self, request): + try: + refresh_token = request.data.get('refresh') + + if not refresh_token: + return Response( + {'error': 'Refresh token is required'}, + status=status.HTTP_400_BAD_REQUEST + ) + + try: + token = RefreshToken(refresh_token) + response_data = { + 'access': str(token.access_token), + 'refresh': str(token), + 'expires_at': datetime.timestamp( + datetime.now() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'] + ) + } + + return Response(response_data) + + except Exception as e: + return Response( + {'error': f'Invalid refresh token: {str(e)}'}, + status=status.HTTP_400_BAD_REQUEST + ) + + except Exception as e: + return Response( + {'error': f'Token refresh failed: {str(e)}'}, + status=status.HTTP_400_BAD_REQUEST + ) diff --git a/backend/base/settings.py b/backend/base/settings.py index 5f03605..080608d 100644 --- a/backend/base/settings.py +++ b/backend/base/settings.py @@ -39,6 +39,7 @@ REST_FRAMEWORK = { 'rest_framework_simplejwt.authentication.JWTAuthentication', 'rest_framework.authentication.SessionAuthentication', ], + 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', } @@ -66,8 +67,19 @@ INSTALLED_APPS = [ 'api.apps.ApiConfig', 'sitemanagement.apps.SitemanagementConfig', 'rest_framework', + 'drf_spectacular', ] +# Настройки OpenAPI +SPECTACULAR_SETTINGS = { + 'TITLE': 'AERBIM API', + 'DESCRIPTION': 'API для работы с AERBIM', + 'VERSION': '1.0.0', + 'SERVE_INCLUDE_SCHEMA': False, + 'COMPONENT_SPLIT_REQUEST': True, + 'SCHEMA_PATH_PREFIX': '/api/v[0-9]', +} + MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', diff --git a/backend/requirements.txt b/backend/requirements.txt index d509463..fb476c5 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -20,3 +20,4 @@ python-dotenv==1.1.1 requests==2.32.5 sqlparse==0.5.3 urllib3==2.5.0 +drf-spectacular==0.27.1 diff --git a/backend/sitemanagement/constants/account_types.py b/backend/sitemanagement/constants/account_types.py new file mode 100644 index 0000000..239bd10 --- /dev/null +++ b/backend/sitemanagement/constants/account_types.py @@ -0,0 +1,19 @@ +from typing import Literal, Tuple, List +from enum import Enum + +class AccountType(str, Enum): + ENGINEER = "engineer" + OPERATOR = "operator" + ADMIN = "admin" + + @classmethod + def choices(cls) -> List[Tuple[str, str]]: + return [ + (cls.ENGINEER.value, "Инженер"), + (cls.OPERATOR.value, "Оператор"), + (cls.ADMIN.value, "Администратор"), + ] + +AccountTypeLiteral = Literal["engineer", "operator", "admin"] + +account_types = AccountType.choices() \ No newline at end of file From 90461b61cc186c2f3a7faff9cc6412ca6a3195db Mon Sep 17 00:00:00 2001 From: Timofey Date: Fri, 29 Aug 2025 14:48:55 +0300 Subject: [PATCH 5/7] docs --- backend/api/auth/serializers.py | 7 ++++++- backend/api/auth/views.py | 26 ++++++++++++++++---------- backend/base/settings.py | 2 +- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/backend/api/auth/serializers.py b/backend/api/auth/serializers.py index 63c1962..abbda93 100644 --- a/backend/api/auth/serializers.py +++ b/backend/api/auth/serializers.py @@ -59,4 +59,9 @@ class LoginResponseSerializer(serializers.Serializer): message = serializers.CharField() access = serializers.CharField() refresh = serializers.CharField() - user = UserResponseSerializer() \ No newline at end of file + user = UserResponseSerializer() + + +class LogoutResponseSerializer(serializers.Serializer): + """Сериализатор для ответа при выходе из системы""" + message = serializers.CharField() \ No newline at end of file diff --git a/backend/api/auth/views.py b/backend/api/auth/views.py index a04b351..e329f76 100644 --- a/backend/api/auth/views.py +++ b/backend/api/auth/views.py @@ -11,7 +11,8 @@ from django.contrib.auth.models import User from .serializers import ( UserResponseSerializer, LoginRequestSerializer, - LoginResponseSerializer + LoginResponseSerializer, + LogoutResponseSerializer ) from api.utils.cookies import AuthBaseViewSet @@ -138,30 +139,35 @@ class LoginViewSet(AuthBaseViewSet): ) class LogoutView(APIView): - """ViewSet для выхода из системы""" + """View для выхода из системы""" + serializer_class = LogoutResponseSerializer @extend_schema( summary="Выход из системы", - description="Эндпоинт для выхода из системы, очищает все токены и куки", + description="Эндпоинт для выхода из системы. Очищает JWT токены и сессионные куки.", responses={ 200: OpenApiResponse( - description="Успешный выход", - response=OpenApiTypes.OBJECT, + response=LogoutResponseSerializer, + description="Успешный выход из системы", examples=[ OpenApiExample( 'Успешный выход', - value={"message": "Logged out"} + value={"message": "Успешный выход из системы"} ) ] ) - } + }, + tags=['Аутентификация'] ) def post(self, request): - response = Response({'message': 'Logged out'}, status=status.HTTP_200_OK) + """Выход из системы с очисткой всех токенов и куки""" + response = Response( + {'message': 'Успешный выход из системы'}, + status=status.HTTP_200_OK + ) - # чистим куки и sessionID response.delete_cookie('access_token') response.delete_cookie('refresh_token') - response.delete_cookie('sessionid') + response.delete_cookie('sessionid') return response \ No newline at end of file diff --git a/backend/base/settings.py b/backend/base/settings.py index 080608d..4869b7b 100644 --- a/backend/base/settings.py +++ b/backend/base/settings.py @@ -73,7 +73,7 @@ INSTALLED_APPS = [ # Настройки OpenAPI SPECTACULAR_SETTINGS = { 'TITLE': 'AERBIM API', - 'DESCRIPTION': 'API для работы с AERBIM', + 'DESCRIPTION': 'AERBIM - документация по API', 'VERSION': '1.0.0', 'SERVE_INCLUDE_SCHEMA': False, 'COMPONENT_SPLIT_REQUEST': True, From 9228a3851112cbd8515b67fd9b03bea548e9c0bc Mon Sep 17 00:00:00 2001 From: Timofey Date: Fri, 29 Aug 2025 14:56:50 +0300 Subject: [PATCH 6/7] docs fix --- backend/api/auth/serializers.py | 37 ++++++++++++++++++++++++++------- backend/api/auth/views.py | 1 - backend/base/settings.py | 18 ++++++++++++++-- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/backend/api/auth/serializers.py b/backend/api/auth/serializers.py index abbda93..3377cb5 100644 --- a/backend/api/auth/serializers.py +++ b/backend/api/auth/serializers.py @@ -50,18 +50,41 @@ class UserResponseSerializer(serializers.Serializer): class LoginRequestSerializer(serializers.Serializer): """Сериализатор для запроса авторизации""" - login = serializers.CharField(help_text="Логин пользователя") - password = serializers.CharField(help_text="Пароль пользователя", write_only=True) + login = serializers.CharField( + help_text="Логин пользователя", + required=True + ) + password = serializers.CharField( + help_text="Пароль пользователя", + write_only=True, + required=True, + style={'input_type': 'password'} + ) class LoginResponseSerializer(serializers.Serializer): """Сериализатор для ответа при успешной авторизации""" - message = serializers.CharField() - access = serializers.CharField() - refresh = serializers.CharField() - user = UserResponseSerializer() + message = serializers.CharField( + help_text="Сообщение о успешной авторизации", + read_only=True + ) + access = serializers.CharField( + help_text="JWT access token для авторизации запросов", + read_only=True + ) + refresh = serializers.CharField( + help_text="JWT refresh token для обновления access token", + read_only=True + ) + user = UserResponseSerializer( + help_text="Данные авторизованного пользователя", + read_only=True + ) class LogoutResponseSerializer(serializers.Serializer): """Сериализатор для ответа при выходе из системы""" - message = serializers.CharField() \ No newline at end of file + message = serializers.CharField( + help_text="Сообщение о успешном выходе", + read_only=True + ) \ No newline at end of file diff --git a/backend/api/auth/views.py b/backend/api/auth/views.py index e329f76..24cfd49 100644 --- a/backend/api/auth/views.py +++ b/backend/api/auth/views.py @@ -26,7 +26,6 @@ class LoginViewSet(AuthBaseViewSet): @extend_schema( summary="Авторизация пользователя", description="Эндпоинт для авторизации пользователя по логину и паролю", - request=LoginRequestSerializer, responses={ 200: OpenApiResponse( response=LoginResponseSerializer, diff --git a/backend/base/settings.py b/backend/base/settings.py index 4869b7b..603fa36 100644 --- a/backend/base/settings.py +++ b/backend/base/settings.py @@ -70,14 +70,28 @@ INSTALLED_APPS = [ 'drf_spectacular', ] -# Настройки OpenAPI +#!OpenAPI SPECTACULAR_SETTINGS = { 'TITLE': 'AERBIM API', 'DESCRIPTION': 'AERBIM - документация по API', 'VERSION': '1.0.0', 'SERVE_INCLUDE_SCHEMA': False, - 'COMPONENT_SPLIT_REQUEST': True, 'SCHEMA_PATH_PREFIX': '/api/v[0-9]', + 'COMPONENT_SPLIT_REQUEST': False, # не создавать автоматически *Request схемы + + 'COMPONENT_NO_READ_ONLY_REQUIRED': True, # не требовать read_only поля + 'COMPONENT_SPLIT_PATCHES': False, # не создавать отдельные схемы для PATCH + + # настройки безопасности + 'SECURITY': [{'Bearer': []}], + 'SWAGGER_UI_SETTINGS': { + 'persistAuthorization': True, + }, + + # сортировка тегов и операций + 'TAGS': [ + {'name': 'Аутентификация', 'description': 'Методы для работы с аутентификацией'}, + ], } MIDDLEWARE = [ From 752afd372b6a633ebc3b11d71f87278e1e261a15 Mon Sep 17 00:00:00 2001 From: Timofey Date: Fri, 29 Aug 2025 15:13:15 +0300 Subject: [PATCH 7/7] refresh token route --- backend/api/auth/serializers.py | 24 +++++++++ backend/api/auth/urls.py | 3 +- backend/api/auth/views.py | 93 +++++++++++++++++++++++++++++++-- backend/base/settings.py | 3 +- 4 files changed, 118 insertions(+), 5 deletions(-) diff --git a/backend/api/auth/serializers.py b/backend/api/auth/serializers.py index 3377cb5..4eda53e 100644 --- a/backend/api/auth/serializers.py +++ b/backend/api/auth/serializers.py @@ -87,4 +87,28 @@ class LogoutResponseSerializer(serializers.Serializer): message = serializers.CharField( help_text="Сообщение о успешном выходе", read_only=True + ) + + +class RefreshTokenRequestSerializer(serializers.Serializer): + """Сериализатор для запроса обновления токена""" + refresh = serializers.CharField( + help_text="Refresh token для обновления", + required=True + ) + + +class RefreshTokenResponseSerializer(serializers.Serializer): + """Сериализатор для ответа с обновленными токенами""" + access = serializers.CharField( + help_text="Новый JWT access token", + read_only=True + ) + refresh = serializers.CharField( + help_text="Новый JWT refresh token", + read_only=True + ) + expires_at = serializers.FloatField( + help_text="Timestamp времени истечения access token", + read_only=True ) \ No newline at end of file diff --git a/backend/api/auth/urls.py b/backend/api/auth/urls.py index 8862821..0f80943 100644 --- a/backend/api/auth/urls.py +++ b/backend/api/auth/urls.py @@ -1,5 +1,5 @@ from django.urls import path, include -from .views import LoginViewSet, LogoutView +from .views import LoginViewSet, LogoutView, RefreshTokenView from rest_framework.routers import DefaultRouter from drf_spectacular.views import ( SpectacularAPIView, @@ -13,6 +13,7 @@ router.register(r'', LoginViewSet, basename='auth') urlpatterns = [ path('', include(router.urls)), path('logout/', LogoutView.as_view(), name='auth-logout'), + path('refresh/', RefreshTokenView.as_view(), name='token-refresh'), path('schema/', SpectacularAPIView.as_view(), name='schema'), path( 'docs/', diff --git a/backend/api/auth/views.py b/backend/api/auth/views.py index 24cfd49..d58e6d2 100644 --- a/backend/api/auth/views.py +++ b/backend/api/auth/views.py @@ -1,4 +1,7 @@ from rest_framework import status +from datetime import datetime +import traceback +from django.conf import settings from rest_framework.decorators import action from rest_framework.response import Response from rest_framework_simplejwt.tokens import RefreshToken @@ -12,13 +15,16 @@ from .serializers import ( UserResponseSerializer, LoginRequestSerializer, LoginResponseSerializer, - LogoutResponseSerializer + LogoutResponseSerializer, + RefreshTokenRequestSerializer, + RefreshTokenResponseSerializer ) from api.utils.cookies import AuthBaseViewSet from api.types import User +@extend_schema(tags=['Логин']) class LoginViewSet(AuthBaseViewSet): """ViewSet для авторизации пользователей""" serializer_class = LoginRequestSerializer @@ -136,7 +142,89 @@ class LoginViewSet(AuthBaseViewSet): {"error": "Ошибка авторизации"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) + +@extend_schema(tags=['Логин']) +class RefreshTokenView(APIView): + """View для обновления JWT токенов""" + serializer_class = RefreshTokenRequestSerializer + + @extend_schema( + summary="Обновление токенов", + description="Эндпоинт для обновления JWT токенов. Принимает refresh token и возвращает новую пару токенов.", + request=RefreshTokenRequestSerializer, + responses={ + 200: OpenApiResponse( + response=RefreshTokenResponseSerializer, + description="Токены успешно обновлены", + examples=[ + OpenApiExample( + 'Успешное обновление', + value={ + "access": "eyJ0eXAiOiJKV1QiLCJhbGc...", + "refresh": "eyJ0eXAiOiJKV1QiLCJhbGc...", + "expires_at": 1679831642.0 + } + ) + ] + ), + 400: OpenApiResponse( + description="Ошибка обновления токена", + response=OpenApiTypes.OBJECT, + examples=[ + OpenApiExample( + 'Отсутствует refresh token', + value={"error": "Refresh token is required"} + ), + OpenApiExample( + 'Невалидный refresh token', + value={"error": "Invalid refresh token: Token is invalid or expired"} + ) + ] + ) + } + ) + def post(self, request): + try: + refresh_token = request.data.get('refresh') + if not refresh_token: + return Response( + {'error': 'Требуется refresh token'}, + status=status.HTTP_400_BAD_REQUEST + ) + + try: + token = RefreshToken(refresh_token) + + # точное время истечения токена + expires_at = datetime.now() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'] + + response_data = { + 'access': str(token.access_token), + 'refresh': str(token), + 'expires_at': datetime.timestamp(expires_at) + } + + return Response(response_data) + + except Exception as e: + # логируем ошибки + + print(f"Token refresh error: {str(e)}") + print(traceback.format_exc()) + + return Response( + {'error': f'Невалидный refresh token: {str(e)}'}, + status=status.HTTP_400_BAD_REQUEST + ) + + except Exception as e: + return Response( + {'error': f'Ошибка обновления токена: {str(e)}'}, + status=status.HTTP_400_BAD_REQUEST + ) + +@extend_schema(tags=['Логаут']) class LogoutView(APIView): """View для выхода из системы""" serializer_class = LogoutResponseSerializer @@ -155,8 +243,7 @@ class LogoutView(APIView): ) ] ) - }, - tags=['Аутентификация'] + } ) def post(self, request): """Выход из системы с очисткой всех токенов и куки""" diff --git a/backend/base/settings.py b/backend/base/settings.py index 603fa36..bd40943 100644 --- a/backend/base/settings.py +++ b/backend/base/settings.py @@ -90,7 +90,8 @@ SPECTACULAR_SETTINGS = { # сортировка тегов и операций 'TAGS': [ - {'name': 'Аутентификация', 'description': 'Методы для работы с аутентификацией'}, + {'name': 'Логаут', 'description': 'Метод для работы с логаутом'}, + {'name': 'Логин', 'description': 'Методы для работы с логином'}, ], }