diff --git a/frontend/api/class-interfaces/users.ts b/frontend/api/class-interfaces/users.ts index ba547719d2b..df5a89e4162 100644 --- a/frontend/api/class-interfaces/users.ts +++ b/frontend/api/class-interfaces/users.ts @@ -16,12 +16,12 @@ const prefix = "/api"; const routes = { usersSelf: `${prefix}/users/self`, passwordReset: `${prefix}/users/reset-password`, + passwordChange: `${prefix}/users/password`, users: `${prefix}/users`, usersIdImage: (id: string) => `${prefix}/users/${id}/image`, usersIdResetPassword: (id: string) => `${prefix}/users/${id}/reset-password`, usersId: (id: string) => `${prefix}/users/${id}`, - usersIdPassword: (id: string) => `${prefix}/users/${id}/password`, usersIdFavorites: (id: string) => `${prefix}/users/${id}/favorites`, usersIdFavoritesSlug: (id: string, slug: string) => `${prefix}/users/${id}/favorites/${slug}`, @@ -45,8 +45,8 @@ export class UserApi extends BaseCRUDAPI { return await this.requests.get(routes.usersIdFavorites(id)); } - async changePassword(id: string, changePassword: ChangePassword) { - return await this.requests.put(routes.usersIdPassword(id), changePassword); + async changePassword(changePassword: ChangePassword) { + return await this.requests.put(routes.passwordChange, changePassword); } async createAPIToken(tokenName: LongLiveTokenIn) { diff --git a/frontend/pages/user/profile/edit.vue b/frontend/pages/user/profile/edit.vue index cef35876629..e2408964da1 100644 --- a/frontend/pages/user/profile/edit.vue +++ b/frontend/pages/user/profile/edit.vue @@ -161,7 +161,7 @@ export default defineComponent({ if (!userCopy.value?.id) { return; } - const { response } = await api.users.changePassword(userCopy.value.id, { + const { response } = await api.users.changePassword({ currentPassword: password.current, newPassword: password.newOne, }); diff --git a/mealie/routes/recipe/__init__.py b/mealie/routes/recipe/__init__.py index dca1ba949be..2d241f95c54 100644 --- a/mealie/routes/recipe/__init__.py +++ b/mealie/routes/recipe/__init__.py @@ -10,6 +10,5 @@ router.include_router(recipe_crud_routes.router_exports) router.include_router(recipe_crud_routes.router) router.include_router(comments.router, prefix=prefix, tags=["Recipe: Comments"]) -router.include_router(bulk_actions.router, prefix=prefix) router.include_router(bulk_actions.router, prefix=prefix, tags=["Recipe: Bulk Exports"]) router.include_router(shared_routes.router, prefix=prefix, tags=["Recipe: Shared"]) diff --git a/mealie/routes/users/crud.py b/mealie/routes/users/crud.py index 1a0560b61ba..4c2ecc2dd97 100644 --- a/mealie/routes/users/crud.py +++ b/mealie/routes/users/crud.py @@ -85,7 +85,7 @@ def update_user(self, item_id: UUID4, new_data: UserBase): return SuccessResponse.respond("User updated") - @user_router.put("/{item_id}/password") + @user_router.put("/password") def update_password(self, password_change: ChangePassword): """Resets the User Password""" if not verify_password(password_change.current_password, self.user.password): diff --git a/mealie/schema/user/user.py b/mealie/schema/user/user.py index 19e656d40ee..9ee5eb62eb7 100644 --- a/mealie/schema/user/user.py +++ b/mealie/schema/user/user.py @@ -82,11 +82,13 @@ def getter_dict(cls, name_orm: User): } schema_extra = { - "username": "ChangeMe", - "fullName": "Change Me", - "email": "changeme@email.com", - "group": settings.DEFAULT_GROUP, - "admin": "false", + "example": { + "username": "ChangeMe", + "fullName": "Change Me", + "email": "changeme@email.com", + "group": settings.DEFAULT_GROUP, + "admin": "false", + } } diff --git a/poetry.lock b/poetry.lock index c5e566b9e38..5009f5fcc1a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -581,6 +581,22 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jsonschema" +version = "4.9.0" +description = "An implementation of JSON Schema validation for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=17.4.0" +pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + [[package]] name = "jstyleson" version = "0.0.2" @@ -769,6 +785,38 @@ rsa = ["cryptography (>=3.0.0)"] signals = ["blinker (>=1.4.0)"] signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] +[[package]] +name = "openapi-schema-validator" +version = "0.2.3" +description = "OpenAPI schema validation for Python" +category = "dev" +optional = false +python-versions = ">=3.7.0,<4.0.0" + +[package.dependencies] +jsonschema = ">=3.0.0,<5.0.0" + +[package.extras] +isodate = ["isodate"] +strict-rfc3339 = ["strict-rfc3339"] +rfc3339-validator = ["rfc3339-validator"] + +[[package]] +name = "openapi-spec-validator" +version = "0.4.0" +description = "OpenAPI 2.0 (aka Swagger) and OpenAPI 3.0 spec validator" +category = "dev" +optional = false +python-versions = ">=3.7.0,<4.0.0" + +[package.dependencies] +jsonschema = ">=3.2.0,<5.0.0" +openapi-schema-validator = ">=0.2.0,<0.3.0" +PyYAML = ">=5.1" + +[package.extras] +requests = ["requests"] + [[package]] name = "packaging" version = "21.3" @@ -1027,6 +1075,14 @@ python-versions = "*" html5lib = "*" rdflib = "*" +[[package]] +name = "pyrsistent" +version = "0.18.1" +description = "Persistent/Functional/Immutable data structures" +category = "dev" +optional = false +python-versions = ">=3.7" + [[package]] name = "pytest" version = "6.2.5" @@ -1563,7 +1619,7 @@ pgsql = ["psycopg2-binary"] [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "99607fe4da1369b768d0ed90300a32b1374a1f78e0eb06ac0c04bfb0cde0b7b9" +content-hash = "8eeaaa2bd62d5448fc93d003c6c132056ab40c31f63a5b9fdd558bbaa2cab63e" [metadata.files] aiofiles = [ @@ -1842,6 +1898,10 @@ jinja2 = [ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] +jsonschema = [ + {file = "jsonschema-4.9.0-py3-none-any.whl", hash = "sha256:5d0be0cd1b670438b71c3d3145b2abba1f9d197e3e91adc4c4bae4c0e114e252"}, + {file = "jsonschema-4.9.0.tar.gz", hash = "sha256:df10e65c8f3687a48e93d0d348ce0ce5f897b5a28e9bbcbbe8f7c7eaf019e850"}, +] jstyleson = [ {file = "jstyleson-0.0.2.tar.gz", hash = "sha256:680003f3b15a2959e4e6a351f3b858e3c07dd3e073a0d54954e34d8ea5e1308e"}, ] @@ -1986,6 +2046,14 @@ oauthlib = [ {file = "oauthlib-3.2.0-py3-none-any.whl", hash = "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe"}, {file = "oauthlib-3.2.0.tar.gz", hash = "sha256:23a8208d75b902797ea29fd31fa80a15ed9dc2c6c16fe73f5d346f83f6fa27a2"}, ] +openapi-schema-validator = [ + {file = "openapi-schema-validator-0.2.3.tar.gz", hash = "sha256:2c64907728c3ef78e23711c8840a423f0b241588c9ed929855e4b2d1bb0cf5f2"}, + {file = "openapi_schema_validator-0.2.3-py3-none-any.whl", hash = "sha256:9bae709212a19222892cabcc60cafd903cbf4b220223f48583afa3c0e3cc6fc4"}, +] +openapi-spec-validator = [ + {file = "openapi-spec-validator-0.4.0.tar.gz", hash = "sha256:97f258850afc97b048f7c2653855e0f88fa66ac103c2be5077c7960aca2ad49a"}, + {file = "openapi_spec_validator-0.4.0-py3-none-any.whl", hash = "sha256:06900ac4d546a1df3642a779da0055be58869c598e3042a2fef067cfd99d04d0"}, +] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, @@ -2218,6 +2286,29 @@ pyrdfa3 = [ {file = "pyRdfa3-3.5.3-py3-none-any.whl", hash = "sha256:4da7ed49e8f524b573ed67e4f7bc7f403bff3be00546d7438fe263c924a91ccf"}, {file = "pyRdfa3-3.5.3.tar.gz", hash = "sha256:157663a92b87df345b6f69bde235dff5f797891608e12fe1e4fa8dad687131ae"}, ] +pyrsistent = [ + {file = "pyrsistent-0.18.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1"}, + {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26"}, + {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e"}, + {file = "pyrsistent-0.18.1-cp310-cp310-win32.whl", hash = "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6"}, + {file = "pyrsistent-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-win32.whl", hash = "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286"}, + {file = "pyrsistent-0.18.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6"}, + {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec"}, + {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c"}, + {file = "pyrsistent-0.18.1-cp38-cp38-win32.whl", hash = "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca"}, + {file = "pyrsistent-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a"}, + {file = "pyrsistent-0.18.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5"}, + {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045"}, + {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c"}, + {file = "pyrsistent-0.18.1-cp39-cp39-win32.whl", hash = "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc"}, + {file = "pyrsistent-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07"}, + {file = "pyrsistent-0.18.1.tar.gz", hash = "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96"}, +] pytest = [ {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, diff --git a/pyproject.toml b/pyproject.toml index 57ae91fe0a7..0643eea515c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ types-requests = "^2.27.12" types-urllib3 = "^1.26.11" pre-commit = "^2.17.0" types-python-dateutil = "^2.8.18" +openapi-spec-validator = "^0.4.0" [build-system] diff --git a/tests/unit_tests/test_open_api.py b/tests/unit_tests/test_open_api.py new file mode 100644 index 00000000000..a7d97de35f4 --- /dev/null +++ b/tests/unit_tests/test_open_api.py @@ -0,0 +1,8 @@ +from openapi_spec_validator import validate_v3_spec + +from mealie.app import app + + +def test_validate_open_api_spec(): + open_api = app.openapi() + validate_v3_spec(open_api)