diff --git a/.github/workflows/web-lint.yaml b/.github/workflows/web-lint.yaml index fb8312eb..66c53566 100644 --- a/.github/workflows/web-lint.yaml +++ b/.github/workflows/web-lint.yaml @@ -9,16 +9,22 @@ on: jobs: next-lint: runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18] steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Use Node.js - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: pnpm/action-setup@v2 with: - node-version: 18.x + version: 8 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: "pnpm" - name: Install dependencies - run: npm ci + run: pnpm install working-directory: ./apps/web - - name: Lint code - run: npm run lint + - name: Lint + run: pnpm run lint working-directory: ./apps/web diff --git a/README.md b/README.md index 5786e783..892b0bde 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,15 @@ LearnHouse is an open source platform that makes it easy for anyone to provide w ![image](https://docs.learnhouse.app/img/pages/features.png) - πŸ“„βœ¨Dynamic notion-like pages -- πŸ‘¨β€πŸŽ“ Easy to use +- 🏎️ Easy to use - πŸ‘₯ Multi-Organization - πŸ“Ή Supports Uploadable Videos and external videos like YouTube - πŸ“„ Supports documents like PDF - 🍱 Course Collections +- πŸ‘¨β€πŸŽ“ Users Management - πŸ™‹ Quizzes - πŸ‘Ÿ Course progress -- ⚑ (Incoming) Live Collaboration +- ✨ LearnHouse AI : The Teachers and Students copilot - More to come ## Community @@ -48,13 +49,14 @@ Thank you for you interest πŸ’–, here is how you can help : LearnHouse uses a number of open source projects to work properly: -- **Next.js** (13 with the App Directory) - The React Framework +- **Next.js** (14 with the App Directory) - The React Framework - **TailwindCSS** - Styling - **Radix UI** - Accessible UI Components - **Tiptap** - An editor framework and headless wrapper around ProseMirror - **FastAPI** - A high performance, async API framework for Python - **YJS** - Shared data types for building collaborative software -- **MongoDB** - NoSQL Database +- **PostgreSQL** - SQL Database +- **LangChain** - LangChain is a framework for developing applications powered by language models - **React** - duh ## A word diff --git a/apps/api/.gitignore b/apps/api/.gitignore index 010e2064..2a59b52b 100644 --- a/apps/api/.gitignore +++ b/apps/api/.gitignore @@ -10,7 +10,7 @@ __pycache__/ .vscode/ # Learnhouse -content/org_* +content/* # Flyio fly.toml diff --git a/apps/api/app.py b/apps/api/app.py index 3a7f62c6..603a265c 100644 --- a/apps/api/app.py +++ b/apps/api/app.py @@ -9,7 +9,6 @@ from fastapi_jwt_auth.exceptions import AuthJWTException from fastapi.middleware.gzip import GZipMiddleware - # from src.services.mocks.initial import create_initial_data ######################## @@ -26,7 +25,6 @@ app = FastAPI( title=learnhouse_config.site_name, description=learnhouse_config.site_description, version="0.1.0", - root_path="/", ) app.add_middleware( @@ -61,8 +59,8 @@ app.mount("/content", StaticFiles(directory="content"), name="content") # Global Routes app.include_router(v1_router) + # General Routes @app.get("/") async def root(): return {"Message": "Welcome to LearnHouse ✨"} - diff --git a/apps/api/poetry.lock b/apps/api/poetry.lock index 8a262db2..cb98a6f1 100644 --- a/apps/api/poetry.lock +++ b/apps/api/poetry.lock @@ -2,87 +2,87 @@ [[package]] name = "aiohttp" -version = "3.9.1" +version = "3.9.2" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590"}, - {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0"}, - {file = "aiohttp-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501"}, - {file = "aiohttp-3.9.1-cp310-cp310-win32.whl", hash = "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489"}, - {file = "aiohttp-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a"}, - {file = "aiohttp-3.9.1-cp311-cp311-win32.whl", hash = "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544"}, - {file = "aiohttp-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f"}, - {file = "aiohttp-3.9.1-cp312-cp312-win32.whl", hash = "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed"}, - {file = "aiohttp-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8"}, - {file = "aiohttp-3.9.1-cp38-cp38-win32.whl", hash = "sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4"}, - {file = "aiohttp-3.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c"}, - {file = "aiohttp-3.9.1-cp39-cp39-win32.whl", hash = "sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7"}, - {file = "aiohttp-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf"}, - {file = "aiohttp-3.9.1.tar.gz", hash = "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d"}, + {file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:772fbe371788e61c58d6d3d904268e48a594ba866804d08c995ad71b144f94cb"}, + {file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:edd4f1af2253f227ae311ab3d403d0c506c9b4410c7fc8d9573dec6d9740369f"}, + {file = "aiohttp-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cfee9287778399fdef6f8a11c9e425e1cb13cc9920fd3a3df8f122500978292b"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc158466f6a980a6095ee55174d1de5730ad7dec251be655d9a6a9dd7ea1ff9"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54ec82f45d57c9a65a1ead3953b51c704f9587440e6682f689da97f3e8defa35"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abeb813a18eb387f0d835ef51f88568540ad0325807a77a6e501fed4610f864e"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc91d07280d7d169f3a0f9179d8babd0ee05c79d4d891447629ff0d7d8089ec2"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b65e861f4bebfb660f7f0f40fa3eb9f2ab9af10647d05dac824390e7af8f75b7"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04fd8ffd2be73d42bcf55fd78cde7958eeee6d4d8f73c3846b7cba491ecdb570"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3d8d962b439a859b3ded9a1e111a4615357b01620a546bc601f25b0211f2da81"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:8ceb658afd12b27552597cf9a65d9807d58aef45adbb58616cdd5ad4c258c39e"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0e4ee4df741670560b1bc393672035418bf9063718fee05e1796bf867e995fad"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2dec87a556f300d3211decf018bfd263424f0690fcca00de94a837949fbcea02"}, + {file = "aiohttp-3.9.2-cp310-cp310-win32.whl", hash = "sha256:3e1a800f988ce7c4917f34096f81585a73dbf65b5c39618b37926b1238cf9bc4"}, + {file = "aiohttp-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:ea510718a41b95c236c992b89fdfc3d04cc7ca60281f93aaada497c2b4e05c46"}, + {file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6aaa6f99256dd1b5756a50891a20f0d252bd7bdb0854c5d440edab4495c9f973"}, + {file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a27d8c70ad87bcfce2e97488652075a9bdd5b70093f50b10ae051dfe5e6baf37"}, + {file = "aiohttp-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:54287bcb74d21715ac8382e9de146d9442b5f133d9babb7e5d9e453faadd005e"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb3d05569aa83011fcb346b5266e00b04180105fcacc63743fc2e4a1862a891"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8534e7d69bb8e8d134fe2be9890d1b863518582f30c9874ed7ed12e48abe3c4"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd9d5b989d57b41e4ff56ab250c5ddf259f32db17159cce630fd543376bd96b"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa6904088e6642609981f919ba775838ebf7df7fe64998b1a954fb411ffb4663"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda42eb410be91b349fb4ee3a23a30ee301c391e503996a638d05659d76ea4c2"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:193cc1ccd69d819562cc7f345c815a6fc51d223b2ef22f23c1a0f67a88de9a72"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b9f1cb839b621f84a5b006848e336cf1496688059d2408e617af33e3470ba204"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d22a0931848b8c7a023c695fa2057c6aaac19085f257d48baa24455e67df97ec"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4112d8ba61fbd0abd5d43a9cb312214565b446d926e282a6d7da3f5a5aa71d36"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c4ad4241b52bb2eb7a4d2bde060d31c2b255b8c6597dd8deac2f039168d14fd7"}, + {file = "aiohttp-3.9.2-cp311-cp311-win32.whl", hash = "sha256:ee2661a3f5b529f4fc8a8ffee9f736ae054adfb353a0d2f78218be90617194b3"}, + {file = "aiohttp-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:4deae2c165a5db1ed97df2868ef31ca3cc999988812e82386d22937d9d6fed52"}, + {file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6f4cdba12539215aaecf3c310ce9d067b0081a0795dd8a8805fdb67a65c0572a"}, + {file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:84e843b33d5460a5c501c05539809ff3aee07436296ff9fbc4d327e32aa3a326"}, + {file = "aiohttp-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8008d0f451d66140a5aa1c17e3eedc9d56e14207568cd42072c9d6b92bf19b52"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61c47ab8ef629793c086378b1df93d18438612d3ed60dca76c3422f4fbafa792"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc71f748e12284312f140eaa6599a520389273174b42c345d13c7e07792f4f57"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a1c3a4d0ab2f75f22ec80bca62385db2e8810ee12efa8c9e92efea45c1849133"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a87aa0b13bbee025faa59fa58861303c2b064b9855d4c0e45ec70182bbeba1b"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2cc0d04688b9f4a7854c56c18aa7af9e5b0a87a28f934e2e596ba7e14783192"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1956e3ac376b1711c1533266dec4efd485f821d84c13ce1217d53e42c9e65f08"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:114da29f39eccd71b93a0fcacff178749a5c3559009b4a4498c2c173a6d74dff"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3f17999ae3927d8a9a823a1283b201344a0627272f92d4f3e3a4efe276972fe8"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:f31df6a32217a34ae2f813b152a6f348154f948c83213b690e59d9e84020925c"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7a75307ffe31329928a8d47eae0692192327c599113d41b278d4c12b54e1bd11"}, + {file = "aiohttp-3.9.2-cp312-cp312-win32.whl", hash = "sha256:972b63d589ff8f305463593050a31b5ce91638918da38139b9d8deaba9e0fed7"}, + {file = "aiohttp-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:200dc0246f0cb5405c80d18ac905c8350179c063ea1587580e3335bfc243ba6a"}, + {file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:158564d0d1020e0d3fe919a81d97aadad35171e13e7b425b244ad4337fc6793a"}, + {file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:da1346cd0ccb395f0ed16b113ebb626fa43b7b07fd7344fce33e7a4f04a8897a"}, + {file = "aiohttp-3.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:eaa9256de26ea0334ffa25f1913ae15a51e35c529a1ed9af8e6286dd44312554"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1543e7fb00214fb4ccead42e6a7d86f3bb7c34751ec7c605cca7388e525fd0b4"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:186e94570433a004e05f31f632726ae0f2c9dee4762a9ce915769ce9c0a23d89"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d52d20832ac1560f4510d68e7ba8befbc801a2b77df12bd0cd2bcf3b049e52a4"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c45e4e815ac6af3b72ca2bde9b608d2571737bb1e2d42299fc1ffdf60f6f9a1"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa906b9bdfd4a7972dd0628dbbd6413d2062df5b431194486a78f0d2ae87bd55"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:68bbee9e17d66f17bb0010aa15a22c6eb28583edcc8b3212e2b8e3f77f3ebe2a"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4c189b64bd6d9a403a1a3f86a3ab3acbc3dc41a68f73a268a4f683f89a4dec1f"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:8a7876f794523123bca6d44bfecd89c9fec9ec897a25f3dd202ee7fc5c6525b7"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:d23fba734e3dd7b1d679b9473129cd52e4ec0e65a4512b488981a56420e708db"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b141753be581fab842a25cb319f79536d19c2a51995d7d8b29ee290169868eab"}, + {file = "aiohttp-3.9.2-cp38-cp38-win32.whl", hash = "sha256:103daf41ff3b53ba6fa09ad410793e2e76c9d0269151812e5aba4b9dd674a7e8"}, + {file = "aiohttp-3.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:328918a6c2835861ff7afa8c6d2c70c35fdaf996205d5932351bdd952f33fa2f"}, + {file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5264d7327c9464786f74e4ec9342afbbb6ee70dfbb2ec9e3dfce7a54c8043aa3"}, + {file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07205ae0015e05c78b3288c1517afa000823a678a41594b3fdc870878d645305"}, + {file = "aiohttp-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae0a1e638cffc3ec4d4784b8b4fd1cf28968febc4bd2718ffa25b99b96a741bd"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d43302a30ba1166325974858e6ef31727a23bdd12db40e725bec0f759abce505"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16a967685907003765855999af11a79b24e70b34dc710f77a38d21cd9fc4f5fe"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fa3ee92cd441d5c2d07ca88d7a9cef50f7ec975f0117cd0c62018022a184308"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b500c5ad9c07639d48615a770f49618130e61be36608fc9bc2d9bae31732b8f"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c07327b368745b1ce2393ae9e1aafed7073d9199e1dcba14e035cc646c7941bf"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc7d6502c23a0ec109687bf31909b3fb7b196faf198f8cff68c81b49eb316ea9"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:07be2be7071723c3509ab5c08108d3a74f2181d4964e869f2504aaab68f8d3e8"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:122468f6fee5fcbe67cb07014a08c195b3d4c41ff71e7b5160a7bcc41d585a5f"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:00a9abcea793c81e7f8778ca195a1714a64f6d7436c4c0bb168ad2a212627000"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a9825fdd64ecac5c670234d80bb52bdcaa4139d1f839165f548208b3779c6c6"}, + {file = "aiohttp-3.9.2-cp39-cp39-win32.whl", hash = "sha256:5422cd9a4a00f24c7244e1b15aa9b87935c85fb6a00c8ac9b2527b38627a9211"}, + {file = "aiohttp-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:7d579dcd5d82a86a46f725458418458fa43686f6a7b252f2966d359033ffc8ab"}, + {file = "aiohttp-3.9.2.tar.gz", hash = "sha256:b0ad0a5e86ce73f5368a164c10ada10504bf91869c05ab75d982c6048217fbf7"}, ] [package.dependencies] @@ -635,23 +635,22 @@ python-dateutil = ">=2.4" [[package]] name = "fastapi" -version = "0.104.1" +version = "0.109.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.104.1-py3-none-any.whl", hash = "sha256:752dc31160cdbd0436bb93bad51560b57e525cbb1d4bbf6f4904ceee75548241"}, - {file = "fastapi-0.104.1.tar.gz", hash = "sha256:e5e4540a7c5e1dcfbbcf5b903c234feddcdcd881f191977a1c5dfd917487e7ae"}, + {file = "fastapi-0.109.1-py3-none-any.whl", hash = "sha256:510042044906b17b6d9149135d90886ade170bf615efcfb5533f568ae6d88534"}, + {file = "fastapi-0.109.1.tar.gz", hash = "sha256:5402389843a3561918634eb327e86b9ae98645a9e7696bede9074449c48d610a"}, ] [package.dependencies] -anyio = ">=3.7.1,<4.0.0" pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.27.0,<0.28.0" +starlette = ">=0.35.0,<0.36.0" typing-extensions = ">=4.8.0" [package.extras] -all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "fastapi-jwt-auth" @@ -1205,23 +1204,6 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[[package]] -name = "jinja2" -version = "3.1.3" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - [[package]] name = "jmespath" version = "1.0.1" @@ -1233,17 +1215,6 @@ files = [ {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, ] -[[package]] -name = "joblib" -version = "1.3.2" -description = "Lightweight pipelining with Python functions" -optional = false -python-versions = ">=3.7" -files = [ - {file = "joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9"}, - {file = "joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1"}, -] - [[package]] name = "jsonpatch" version = "1.33" @@ -1416,75 +1387,6 @@ files = [ pydantic = ">=1,<3" requests = ">=2,<3" -[[package]] -name = "markupsafe" -version = "2.1.3" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, -] - [[package]] name = "marshmallow" version = "3.20.2" @@ -1742,49 +1644,6 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] -[[package]] -name = "networkx" -version = "3.2.1" -description = "Python package for creating and manipulating graphs and networks" -optional = false -python-versions = ">=3.9" -files = [ - {file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"}, - {file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"}, -] - -[package.extras] -default = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] -developer = ["changelist (==0.4)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] -doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"] -test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] - -[[package]] -name = "nltk" -version = "3.8.1" -description = "Natural Language Toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "nltk-3.8.1-py3-none-any.whl", hash = "sha256:fd5c9109f976fa86bcadba8f91e47f5e9293bd034474752e92a520f81c93dda5"}, - {file = "nltk-3.8.1.zip", hash = "sha256:1834da3d0682cba4f2cede2f9aad6b0fafb6461ba451db0efb6f9c39798d64d3"}, -] - -[package.dependencies] -click = "*" -joblib = "*" -regex = ">=2021.8.3" -tqdm = "*" - -[package.extras] -all = ["matplotlib", "numpy", "pyparsing", "python-crfsuite", "requests", "scikit-learn", "scipy", "twython"] -corenlp = ["requests"] -machine-learning = ["numpy", "python-crfsuite", "scikit-learn", "scipy"] -plot = ["matplotlib"] -tgrep = ["pyparsing"] -twitter = ["twython"] - [[package]] name = "numpy" version = "1.26.3" @@ -1830,147 +1689,6 @@ files = [ {file = "numpy-1.26.3.tar.gz", hash = "sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4"}, ] -[[package]] -name = "nvidia-cublas-cu12" -version = "12.1.3.1" -description = "CUBLAS native runtime libraries" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_cublas_cu12-12.1.3.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:ee53ccca76a6fc08fb9701aa95b6ceb242cdaab118c3bb152af4e579af792728"}, - {file = "nvidia_cublas_cu12-12.1.3.1-py3-none-win_amd64.whl", hash = "sha256:2b964d60e8cf11b5e1073d179d85fa340c120e99b3067558f3cf98dd69d02906"}, -] - -[[package]] -name = "nvidia-cuda-cupti-cu12" -version = "12.1.105" -description = "CUDA profiling tools runtime libs." -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_cuda_cupti_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:e54fde3983165c624cb79254ae9818a456eb6e87a7fd4d56a2352c24ee542d7e"}, - {file = "nvidia_cuda_cupti_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:bea8236d13a0ac7190bd2919c3e8e6ce1e402104276e6f9694479e48bb0eb2a4"}, -] - -[[package]] -name = "nvidia-cuda-nvrtc-cu12" -version = "12.1.105" -description = "NVRTC native runtime libraries" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:339b385f50c309763ca65456ec75e17bbefcbbf2893f462cb8b90584cd27a1c2"}, - {file = "nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:0a98a522d9ff138b96c010a65e145dc1b4850e9ecb75a0172371793752fd46ed"}, -] - -[[package]] -name = "nvidia-cuda-runtime-cu12" -version = "12.1.105" -description = "CUDA Runtime native Libraries" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_cuda_runtime_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:6e258468ddf5796e25f1dc591a31029fa317d97a0a94ed93468fc86301d61e40"}, - {file = "nvidia_cuda_runtime_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:dfb46ef84d73fababab44cf03e3b83f80700d27ca300e537f85f636fac474344"}, -] - -[[package]] -name = "nvidia-cudnn-cu12" -version = "8.9.2.26" -description = "cuDNN runtime libraries" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_cudnn_cu12-8.9.2.26-py3-none-manylinux1_x86_64.whl", hash = "sha256:5ccb288774fdfb07a7e7025ffec286971c06d8d7b4fb162525334616d7629ff9"}, -] - -[package.dependencies] -nvidia-cublas-cu12 = "*" - -[[package]] -name = "nvidia-cufft-cu12" -version = "11.0.2.54" -description = "CUFFT native runtime libraries" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_cufft_cu12-11.0.2.54-py3-none-manylinux1_x86_64.whl", hash = "sha256:794e3948a1aa71fd817c3775866943936774d1c14e7628c74f6f7417224cdf56"}, - {file = "nvidia_cufft_cu12-11.0.2.54-py3-none-win_amd64.whl", hash = "sha256:d9ac353f78ff89951da4af698f80870b1534ed69993f10a4cf1d96f21357e253"}, -] - -[[package]] -name = "nvidia-curand-cu12" -version = "10.3.2.106" -description = "CURAND native runtime libraries" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_curand_cu12-10.3.2.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:9d264c5036dde4e64f1de8c50ae753237c12e0b1348738169cd0f8a536c0e1e0"}, - {file = "nvidia_curand_cu12-10.3.2.106-py3-none-win_amd64.whl", hash = "sha256:75b6b0c574c0037839121317e17fd01f8a69fd2ef8e25853d826fec30bdba74a"}, -] - -[[package]] -name = "nvidia-cusolver-cu12" -version = "11.4.5.107" -description = "CUDA solver native runtime libraries" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd"}, - {file = "nvidia_cusolver_cu12-11.4.5.107-py3-none-win_amd64.whl", hash = "sha256:74e0c3a24c78612192a74fcd90dd117f1cf21dea4822e66d89e8ea80e3cd2da5"}, -] - -[package.dependencies] -nvidia-cublas-cu12 = "*" -nvidia-cusparse-cu12 = "*" -nvidia-nvjitlink-cu12 = "*" - -[[package]] -name = "nvidia-cusparse-cu12" -version = "12.1.0.106" -description = "CUSPARSE native runtime libraries" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c"}, - {file = "nvidia_cusparse_cu12-12.1.0.106-py3-none-win_amd64.whl", hash = "sha256:b798237e81b9719373e8fae8d4f091b70a0cf09d9d85c95a557e11df2d8e9a5a"}, -] - -[package.dependencies] -nvidia-nvjitlink-cu12 = "*" - -[[package]] -name = "nvidia-nccl-cu12" -version = "2.18.1" -description = "NVIDIA Collective Communication Library (NCCL) Runtime" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_nccl_cu12-2.18.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:1a6c4acefcbebfa6de320f412bf7866de856e786e0462326ba1bac40de0b5e71"}, -] - -[[package]] -name = "nvidia-nvjitlink-cu12" -version = "12.3.101" -description = "Nvidia JIT LTO Library" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_nvjitlink_cu12-12.3.101-py3-none-manylinux1_x86_64.whl", hash = "sha256:64335a8088e2b9d196ae8665430bc6a2b7e6ef2eb877a9c735c804bd4ff6467c"}, - {file = "nvidia_nvjitlink_cu12-12.3.101-py3-none-win_amd64.whl", hash = "sha256:1b2e317e437433753530792f13eece58f0aec21a2b05903be7bffe58a606cbd1"}, -] - -[[package]] -name = "nvidia-nvtx-cu12" -version = "12.1.105" -description = "NVIDIA Tools Extension" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_nvtx_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:dc21cf308ca5691e7c04d962e213f8a4aa9bbfa23d95412f452254c2caeb09e5"}, - {file = "nvidia_nvtx_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:65f4d98982b31b60026e0e6de73fbdfc09d08a96f4656dd3665ca616a11e1e82"}, -] - [[package]] name = "oauthlib" version = "3.2.2" @@ -2256,91 +1974,6 @@ bcrypt = ["bcrypt (>=3.1.0)"] build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"] totp = ["cryptography"] -[[package]] -name = "pillow" -version = "10.2.0" -description = "Python Imaging Library (Fork)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, - {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, - {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, - {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, - {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, - {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, - {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, - {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, - {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, - {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, - {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, - {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, - {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, - {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, - {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, - {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, - {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, - {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, - {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, - {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, - {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, - {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, - {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, - {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, - {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, - {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, - {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, - {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, - {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, - {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, - {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, - {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, - {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, - {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, - {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, - {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] -fpx = ["olefile"] -mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions"] -xmp = ["defusedxml"] - [[package]] name = "pluggy" version = "1.3.0" @@ -2796,6 +2429,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -3020,290 +2654,6 @@ botocore = ">=1.33.2,<2.0a.0" [package.extras] crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] -[[package]] -name = "safetensors" -version = "0.4.1" -description = "" -optional = false -python-versions = ">=3.7" -files = [ - {file = "safetensors-0.4.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:cba01c6b76e01ec453933b3b3c0157c59b52881c83eaa0f7666244e71aa75fd1"}, - {file = "safetensors-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a8f6f679d97ea0135c7935c202feefbd042c149aa70ee759855e890c01c7814"}, - {file = "safetensors-0.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc2ce1f5ae5143a7fb72b71fa71db6a42b4f6cf912aa3acdc6b914084778e68"}, - {file = "safetensors-0.4.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d87d993eaefe6611a9c241a8bd364a5f1ffed5771c74840363a6c4ed8d868f6"}, - {file = "safetensors-0.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:097e9af2efa8778cd2f0cba451784253e62fa7cc9fc73c0744d27212f7294e25"}, - {file = "safetensors-0.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d10a9f7bae608ccfdc009351f01dc3d8535ff57f9488a58a4c38e45bf954fe93"}, - {file = "safetensors-0.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:270b99885ec14abfd56c1d7f28ada81740a9220b4bae960c3de1c6fe84af9e4d"}, - {file = "safetensors-0.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:285b52a481e7ba93e29ad4ec5841ef2c4479ef0a6c633c4e2629e0508453577b"}, - {file = "safetensors-0.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c3c9f0ca510e0de95abd6424789dcbc879942a3a4e29b0dfa99d9427bf1da75c"}, - {file = "safetensors-0.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:88b4653059c903015284a9722f9a46838c654257173b279c8f6f46dbe80b612d"}, - {file = "safetensors-0.4.1-cp310-none-win32.whl", hash = "sha256:2fe6926110e3d425c4b684a4379b7796fdc26ad7d16922ea1696c8e6ea7e920f"}, - {file = "safetensors-0.4.1-cp310-none-win_amd64.whl", hash = "sha256:a79e16222106b2f5edbca1b8185661477d8971b659a3c814cc6f15181a9b34c8"}, - {file = "safetensors-0.4.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:d93321eea0dd7e81b283e47a1d20dee6069165cc158286316d0d06d340de8fe8"}, - {file = "safetensors-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ff8e41c8037db17de0ea2a23bc684f43eaf623be7d34906fe1ac10985b8365e"}, - {file = "safetensors-0.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39d36f1d88468a87c437a1bc27c502e71b6ca44c385a9117a9f9ba03a75cc9c6"}, - {file = "safetensors-0.4.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ef010e9afcb4057fb6be3d0a0cfa07aac04fe97ef73fe4a23138d8522ba7c17"}, - {file = "safetensors-0.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b287304f2b2220d51ccb51fd857761e78bcffbeabe7b0238f8dc36f2edfd9542"}, - {file = "safetensors-0.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e09000b2599e1836314430f81a3884c66a5cbabdff5d9f175b5d560d4de38d78"}, - {file = "safetensors-0.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9c80ce0001efa16066358d2dd77993adc25f5a6c61850e4ad096a2232930bce"}, - {file = "safetensors-0.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:413e1f6ac248f7d1b755199a06635e70c3515493d3b41ba46063dec33aa2ebb7"}, - {file = "safetensors-0.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3ac139377cfe71ba04573f1cda66e663b7c3e95be850e9e6c2dd4b5984bd513"}, - {file = "safetensors-0.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:04157d008385bea66d12fe90844a80d4a76dc25ec5230b5bd9a630496d1b7c03"}, - {file = "safetensors-0.4.1-cp311-none-win32.whl", hash = "sha256:5f25297148ec665f0deb8bd67e9564634d8d6841041ab5393ccfe203379ea88b"}, - {file = "safetensors-0.4.1-cp311-none-win_amd64.whl", hash = "sha256:b2f8877990a72ff595507b80f4b69036a9a1986a641f8681adf3425d97d3d2a5"}, - {file = "safetensors-0.4.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:eb2c1da1cc39509d1a55620a5f4d14f8911c47a89c926a96e6f4876e864375a3"}, - {file = "safetensors-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:303d2c0415cf15a28f8d7f17379ea3c34c2b466119118a34edd9965983a1a8a6"}, - {file = "safetensors-0.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb4cb3e37a9b961ddd68e873b29fe9ab4a081e3703412e34aedd2b7a8e9cafd9"}, - {file = "safetensors-0.4.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae5497adc68669db2fed7cb2dad81e6a6106e79c9a132da3efdb6af1db1014fa"}, - {file = "safetensors-0.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b30abd0cddfe959d1daedf92edcd1b445521ebf7ddefc20860ed01486b33c90"}, - {file = "safetensors-0.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d784a98c492c751f228a4a894c3b8a092ff08b24e73b5568938c28b8c0e8f8df"}, - {file = "safetensors-0.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57a5ab08b0ec7a7caf30d2ac79bb30c89168431aca4f8854464bb9461686925"}, - {file = "safetensors-0.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:edcf3121890b5f0616aa5a54683b1a5d2332037b970e507d6bb7841a3a596556"}, - {file = "safetensors-0.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fdb58dee173ef33634c3016c459d671ca12d11e6acf9db008261cbe58107e579"}, - {file = "safetensors-0.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:780dc21eb3fd32ddd0e8c904bdb0290f2454f4ac21ae71e94f9ce72db1900a5a"}, - {file = "safetensors-0.4.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:48901bd540f8a3c1791314bc5c8a170927bf7f6acddb75bf0a263d081a3637d4"}, - {file = "safetensors-0.4.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:3b0b7b2d5976fbed8a05e2bbdce5816a59e6902e9e7c7e07dc723637ed539787"}, - {file = "safetensors-0.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f69903ff49cb30b9227fb5d029bea276ea20d04b06803877a420c5b1b74c689"}, - {file = "safetensors-0.4.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0ddd050e01f3e843aa8c1c27bf68675b8a08e385d0045487af4d70418c3cb356"}, - {file = "safetensors-0.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a82bc2bd7a9a0e08239bdd6d7774d64121f136add93dfa344a2f1a6d7ef35fa"}, - {file = "safetensors-0.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6ace9e66a40f98a216ad661245782483cf79cf56eb2b112650bb904b0baa9db5"}, - {file = "safetensors-0.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82cbb8f4d022f2e94498cbefca900698b8ded3d4f85212f47da614001ff06652"}, - {file = "safetensors-0.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:791edc10a3c359a2f5f52d5cddab0df8a45107d91027d86c3d44e57162e5d934"}, - {file = "safetensors-0.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:83c2cfbe8c6304f0891e7bb378d56f66d2148972eeb5f747cd8a2246886f0d8c"}, - {file = "safetensors-0.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:04dd14f53f5500eb4c4149674216ba1000670efbcf4b1b5c2643eb244e7882ea"}, - {file = "safetensors-0.4.1-cp37-none-win32.whl", hash = "sha256:d5b3defa74f3723a388bfde2f5d488742bc4879682bd93267c09a3bcdf8f869b"}, - {file = "safetensors-0.4.1-cp37-none-win_amd64.whl", hash = "sha256:25a043cbb59d4f75e9dd87fdf5c009dd8830105a2c57ace49b72167dd9808111"}, - {file = "safetensors-0.4.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:3f6a520af7f2717c5ecba112041f2c8af1ca6480b97bf957aba81ed9642e654c"}, - {file = "safetensors-0.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3807ac3b16288dffebb3474b555b56fe466baa677dfc16290dcd02dca1ab228"}, - {file = "safetensors-0.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b58ba13a9e82b4bc3fc221914f6ef237fe6c2adb13cede3ace64d1aacf49610"}, - {file = "safetensors-0.4.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dac4bb42f8679aadc59bd91a4c5a1784a758ad49d0912995945cd674089f628e"}, - {file = "safetensors-0.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911b48dc09e321a194def3a7431662ff4f03646832f3a8915bbf0f449b8a5fcb"}, - {file = "safetensors-0.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82571d20288c975c1b30b08deb9b1c3550f36b31191e1e81fae87669a92217d0"}, - {file = "safetensors-0.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da52ee0dc8ba03348ffceab767bd8230842fdf78f8a996e2a16445747143a778"}, - {file = "safetensors-0.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2536b11ce665834201072e9397404170f93f3be10cca9995b909f023a04501ee"}, - {file = "safetensors-0.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:998fbac99ca956c3a09fe07cc0b35fac26a521fa8865a690686d889f0ff4e4a6"}, - {file = "safetensors-0.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:845be0aafabf2a60c2d482d4e93023fecffe5e5443d801d7a7741bae9de41233"}, - {file = "safetensors-0.4.1-cp38-none-win32.whl", hash = "sha256:ce7a28bc8af685a69d7e869d09d3e180a275e3281e29cf5f1c7319e231932cc7"}, - {file = "safetensors-0.4.1-cp38-none-win_amd64.whl", hash = "sha256:e056fb9e22d118cc546107f97dc28b449d88274207dd28872bd668c86216e4f6"}, - {file = "safetensors-0.4.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:bdc0d039e44a727824639824090bd8869535f729878fa248addd3dc01db30eae"}, - {file = "safetensors-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c1b1d510c7aba71504ece87bf393ea82638df56303e371e5e2cf09d18977dd7"}, - {file = "safetensors-0.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bd0afd95c1e497f520e680ea01e0397c0868a3a3030e128438cf6e9e3fcd671"}, - {file = "safetensors-0.4.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f603bdd8deac6726d39f41688ed353c532dd53935234405d79e9eb53f152fbfb"}, - {file = "safetensors-0.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8a85e3e47e0d4eebfaf9a58b40aa94f977a56050cb5598ad5396a9ee7c087c6"}, - {file = "safetensors-0.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0ccb5aa0f3be2727117e5631200fbb3a5b3a2b3757545a92647d6dd8be6658f"}, - {file = "safetensors-0.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d784938534e255473155e4d9f276ee69eb85455b6af1292172c731409bf9adee"}, - {file = "safetensors-0.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a257de175c254d39ccd6a21341cd62eb7373b05c1e618a78096a56a857e0c316"}, - {file = "safetensors-0.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6fd80f7794554091836d4d613d33a7d006e2b8d6ba014d06f97cebdfda744f64"}, - {file = "safetensors-0.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:35803201d980efcf964b75a0a2aee97fe5e9ecc5f3ad676b38fafdfe98e0620d"}, - {file = "safetensors-0.4.1-cp39-none-win32.whl", hash = "sha256:7ff8a36e0396776d3ed9a106fc9a9d7c55d4439ca9a056a24bf66d343041d3e6"}, - {file = "safetensors-0.4.1-cp39-none-win_amd64.whl", hash = "sha256:bfa2e20342b81921b98edba52f8deb68843fa9c95250739a56b52ceda5ea5c61"}, - {file = "safetensors-0.4.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ae2d5a31cfb8a973a318f7c4d2cffe0bd1fe753cdf7bb41a1939d45a0a06f964"}, - {file = "safetensors-0.4.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1a45dbf03e8334d3a5dc93687d98b6dc422f5d04c7d519dac09b84a3c87dd7c6"}, - {file = "safetensors-0.4.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297b359d91126c0f9d4fd17bae3cfa2fe3a048a6971b8db07db746ad92f850c"}, - {file = "safetensors-0.4.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda3d98e2bcece388232cfc551ebf063b55bdb98f65ab54df397da30efc7dcc5"}, - {file = "safetensors-0.4.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8934bdfd202ebd0697040a3dff40dd77bc4c5bbf3527ede0532f5e7fb4d970f"}, - {file = "safetensors-0.4.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:42c3710cec7e5c764c7999697516370bee39067de0aa089b7e2cfb97ac8c6b20"}, - {file = "safetensors-0.4.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53134226053e56bd56e73f7db42596e7908ed79f3c9a1016e4c1dade593ac8e5"}, - {file = "safetensors-0.4.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:257d59e40a1b367cb544122e7451243d65b33c3f34d822a347f4eea6fdf97fdf"}, - {file = "safetensors-0.4.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d54c2f1826e790d1eb2d2512bfd0ee443f0206b423d6f27095057c7f18a0687"}, - {file = "safetensors-0.4.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:645b3f1138fce6e818e79d4128afa28f0657430764cc045419c1d069ff93f732"}, - {file = "safetensors-0.4.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e9a7ffb1e551c6df51d267f5a751f042b183df22690f6feceac8d27364fd51d7"}, - {file = "safetensors-0.4.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:44e230fbbe120de564b64f63ef3a8e6ff02840fa02849d9c443d56252a1646d4"}, - {file = "safetensors-0.4.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:9d16b3b2fcc6fca012c74bd01b5619c655194d3e3c13e4d4d0e446eefa39a463"}, - {file = "safetensors-0.4.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:5d95ea4d8b32233910734a904123bdd3979c137c461b905a5ed32511defc075f"}, - {file = "safetensors-0.4.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:dab431699b5d45e0ca043bc580651ce9583dda594e62e245b7497adb32e99809"}, - {file = "safetensors-0.4.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16d8bbb7344e39cb9d4762e85c21df94ebeb03edac923dd94bb9ed8c10eac070"}, - {file = "safetensors-0.4.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1faf5111c66a6ba91f85dff2e36edaaf36e6966172703159daeef330de4ddc7b"}, - {file = "safetensors-0.4.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:660ca1d8bff6c7bc7c6b30b9b32df74ef3ab668f5df42cefd7588f0d40feadcb"}, - {file = "safetensors-0.4.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ae2f67f04ed0bb2e56fd380a8bd3eef03f609df53f88b6f5c7e89c08e52aae00"}, - {file = "safetensors-0.4.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:c8ed5d2c04cdc1afc6b3c28d59580448ac07732c50d94c15e14670f9c473a2ce"}, - {file = "safetensors-0.4.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:2b6a2814278b6660261aa9a9aae524616de9f1ec364e3716d219b6ed8f91801f"}, - {file = "safetensors-0.4.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3cfd1ca35eacc635f0eaa894e5c5ed83ffebd0f95cac298fd430014fa7323631"}, - {file = "safetensors-0.4.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4177b456c6b0c722d82429127b5beebdaf07149d265748e97e0a34ff0b3694c8"}, - {file = "safetensors-0.4.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:313e8472197bde54e3ec54a62df184c414582979da8f3916981b6a7954910a1b"}, - {file = "safetensors-0.4.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fdb4adb76e21bad318210310590de61c9f4adcef77ee49b4a234f9dc48867869"}, - {file = "safetensors-0.4.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:1d568628e9c43ca15eb96c217da73737c9ccb07520fafd8a1eba3f2750614105"}, - {file = "safetensors-0.4.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:573b6023a55a2f28085fc0a84e196c779b6cbef4d9e73acea14c8094fee7686f"}, - {file = "safetensors-0.4.1.tar.gz", hash = "sha256:2304658e6ada81a5223225b4efe84748e760c46079bffedf7e321763cafb36c9"}, -] - -[package.extras] -all = ["safetensors[jax]", "safetensors[numpy]", "safetensors[paddlepaddle]", "safetensors[pinned-tf]", "safetensors[quality]", "safetensors[testing]", "safetensors[torch]"] -dev = ["safetensors[all]"] -jax = ["flax (>=0.6.3)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "safetensors[numpy]"] -numpy = ["numpy (>=1.21.6)"] -paddlepaddle = ["paddlepaddle (>=2.4.1)", "safetensors[numpy]"] -pinned-tf = ["safetensors[numpy]", "tensorflow (==2.11.0)"] -quality = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "isort (>=5.5.4)"] -tensorflow = ["safetensors[numpy]", "tensorflow (>=2.11.0)"] -testing = ["h5py (>=3.7.0)", "huggingface_hub (>=0.12.1)", "hypothesis (>=6.70.2)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "safetensors[numpy]", "setuptools_rust (>=1.5.2)"] -torch = ["safetensors[numpy]", "torch (>=1.10)"] - -[[package]] -name = "scikit-learn" -version = "1.3.2" -description = "A set of python modules for machine learning and data mining" -optional = false -python-versions = ">=3.8" -files = [ - {file = "scikit-learn-1.3.2.tar.gz", hash = "sha256:a2f54c76accc15a34bfb9066e6c7a56c1e7235dda5762b990792330b52ccfb05"}, - {file = "scikit_learn-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e326c0eb5cf4d6ba40f93776a20e9a7a69524c4db0757e7ce24ba222471ee8a1"}, - {file = "scikit_learn-1.3.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:535805c2a01ccb40ca4ab7d081d771aea67e535153e35a1fd99418fcedd1648a"}, - {file = "scikit_learn-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1215e5e58e9880b554b01187b8c9390bf4dc4692eedeaf542d3273f4785e342c"}, - {file = "scikit_learn-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ee107923a623b9f517754ea2f69ea3b62fc898a3641766cb7deb2f2ce450161"}, - {file = "scikit_learn-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:35a22e8015048c628ad099da9df5ab3004cdbf81edc75b396fd0cff8699ac58c"}, - {file = "scikit_learn-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6fb6bc98f234fda43163ddbe36df8bcde1d13ee176c6dc9b92bb7d3fc842eb66"}, - {file = "scikit_learn-1.3.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:18424efee518a1cde7b0b53a422cde2f6625197de6af36da0b57ec502f126157"}, - {file = "scikit_learn-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3271552a5eb16f208a6f7f617b8cc6d1f137b52c8a1ef8edf547db0259b2c9fb"}, - {file = "scikit_learn-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4144a5004a676d5022b798d9e573b05139e77f271253a4703eed295bde0433"}, - {file = "scikit_learn-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:67f37d708f042a9b8d59551cf94d30431e01374e00dc2645fa186059c6c5d78b"}, - {file = "scikit_learn-1.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8db94cd8a2e038b37a80a04df8783e09caac77cbe052146432e67800e430c028"}, - {file = "scikit_learn-1.3.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:61a6efd384258789aa89415a410dcdb39a50e19d3d8410bd29be365bcdd512d5"}, - {file = "scikit_learn-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb06f8dce3f5ddc5dee1715a9b9f19f20d295bed8e3cd4fa51e1d050347de525"}, - {file = "scikit_learn-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b2de18d86f630d68fe1f87af690d451388bb186480afc719e5f770590c2ef6c"}, - {file = "scikit_learn-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:0402638c9a7c219ee52c94cbebc8fcb5eb9fe9c773717965c1f4185588ad3107"}, - {file = "scikit_learn-1.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a19f90f95ba93c1a7f7924906d0576a84da7f3b2282ac3bfb7a08a32801add93"}, - {file = "scikit_learn-1.3.2-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:b8692e395a03a60cd927125eef3a8e3424d86dde9b2370d544f0ea35f78a8073"}, - {file = "scikit_learn-1.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15e1e94cc23d04d39da797ee34236ce2375ddea158b10bee3c343647d615581d"}, - {file = "scikit_learn-1.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:785a2213086b7b1abf037aeadbbd6d67159feb3e30263434139c98425e3dcfcf"}, - {file = "scikit_learn-1.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:64381066f8aa63c2710e6b56edc9f0894cc7bf59bd71b8ce5613a4559b6145e0"}, - {file = "scikit_learn-1.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6c43290337f7a4b969d207e620658372ba3c1ffb611f8bc2b6f031dc5c6d1d03"}, - {file = "scikit_learn-1.3.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:dc9002fc200bed597d5d34e90c752b74df516d592db162f756cc52836b38fe0e"}, - {file = "scikit_learn-1.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d08ada33e955c54355d909b9c06a4789a729977f165b8bae6f225ff0a60ec4a"}, - {file = "scikit_learn-1.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:763f0ae4b79b0ff9cca0bf3716bcc9915bdacff3cebea15ec79652d1cc4fa5c9"}, - {file = "scikit_learn-1.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:ed932ea780517b00dae7431e031faae6b49b20eb6950918eb83bd043237950e0"}, -] - -[package.dependencies] -joblib = ">=1.1.1" -numpy = ">=1.17.3,<2.0" -scipy = ">=1.5.0" -threadpoolctl = ">=2.0.0" - -[package.extras] -benchmark = ["matplotlib (>=3.1.3)", "memory-profiler (>=0.57.0)", "pandas (>=1.0.5)"] -docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.1.3)", "memory-profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)", "sphinx (>=6.0.0)", "sphinx-copybutton (>=0.5.2)", "sphinx-gallery (>=0.10.1)", "sphinx-prompt (>=1.3.0)", "sphinxext-opengraph (>=0.4.2)"] -examples = ["matplotlib (>=3.1.3)", "pandas (>=1.0.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)"] -tests = ["black (>=23.3.0)", "matplotlib (>=3.1.3)", "mypy (>=1.3)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.0.272)", "scikit-image (>=0.16.2)"] - -[[package]] -name = "scipy" -version = "1.11.4" -description = "Fundamental algorithms for scientific computing in Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "scipy-1.11.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc9a714581f561af0848e6b69947fda0614915f072dfd14142ed1bfe1b806710"}, - {file = "scipy-1.11.4-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:cf00bd2b1b0211888d4dc75656c0412213a8b25e80d73898083f402b50f47e41"}, - {file = "scipy-1.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9999c008ccf00e8fbcce1236f85ade5c569d13144f77a1946bef8863e8f6eb4"}, - {file = "scipy-1.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:933baf588daa8dc9a92c20a0be32f56d43faf3d1a60ab11b3f08c356430f6e56"}, - {file = "scipy-1.11.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8fce70f39076a5aa62e92e69a7f62349f9574d8405c0a5de6ed3ef72de07f446"}, - {file = "scipy-1.11.4-cp310-cp310-win_amd64.whl", hash = "sha256:6550466fbeec7453d7465e74d4f4b19f905642c89a7525571ee91dd7adabb5a3"}, - {file = "scipy-1.11.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f313b39a7e94f296025e3cffc2c567618174c0b1dde173960cf23808f9fae4be"}, - {file = "scipy-1.11.4-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1b7c3dca977f30a739e0409fb001056484661cb2541a01aba0bb0029f7b68db8"}, - {file = "scipy-1.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00150c5eae7b610c32589dda259eacc7c4f1665aedf25d921907f4d08a951b1c"}, - {file = "scipy-1.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:530f9ad26440e85766509dbf78edcfe13ffd0ab7fec2560ee5c36ff74d6269ff"}, - {file = "scipy-1.11.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5e347b14fe01003d3b78e196e84bd3f48ffe4c8a7b8a1afbcb8f5505cb710993"}, - {file = "scipy-1.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:acf8ed278cc03f5aff035e69cb511741e0418681d25fbbb86ca65429c4f4d9cd"}, - {file = "scipy-1.11.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:028eccd22e654b3ea01ee63705681ee79933652b2d8f873e7949898dda6d11b6"}, - {file = "scipy-1.11.4-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c6ff6ef9cc27f9b3db93a6f8b38f97387e6e0591600369a297a50a8e96e835d"}, - {file = "scipy-1.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b030c6674b9230d37c5c60ab456e2cf12f6784596d15ce8da9365e70896effc4"}, - {file = "scipy-1.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad669df80528aeca5f557712102538f4f37e503f0c5b9541655016dd0932ca79"}, - {file = "scipy-1.11.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce7fff2e23ab2cc81ff452a9444c215c28e6305f396b2ba88343a567feec9660"}, - {file = "scipy-1.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:36750b7733d960d7994888f0d148d31ea3017ac15eef664194b4ef68d36a4a97"}, - {file = "scipy-1.11.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e619aba2df228a9b34718efb023966da781e89dd3d21637b27f2e54db0410d7"}, - {file = "scipy-1.11.4-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:f3cd9e7b3c2c1ec26364856f9fbe78695fe631150f94cd1c22228456404cf1ec"}, - {file = "scipy-1.11.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d10e45a6c50211fe256da61a11c34927c68f277e03138777bdebedd933712fea"}, - {file = "scipy-1.11.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91af76a68eeae0064887a48e25c4e616fa519fa0d38602eda7e0f97d65d57937"}, - {file = "scipy-1.11.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6df1468153a31cf55ed5ed39647279beb9cfb5d3f84369453b49e4b8502394fd"}, - {file = "scipy-1.11.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee410e6de8f88fd5cf6eadd73c135020bfbbbdfcd0f6162c36a7638a1ea8cc65"}, - {file = "scipy-1.11.4.tar.gz", hash = "sha256:90a2b78e7f5733b9de748f589f09225013685f9b218275257f8a8168ededaeaa"}, -] - -[package.dependencies] -numpy = ">=1.21.6,<1.28.0" - -[package.extras] -dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] -doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] -test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] - -[[package]] -name = "sentence-transformers" -version = "2.2.2" -description = "Multilingual text embeddings" -optional = false -python-versions = ">=3.6.0" -files = [ - {file = "sentence-transformers-2.2.2.tar.gz", hash = "sha256:dbc60163b27de21076c9a30d24b5b7b6fa05141d68cf2553fa9a77bf79a29136"}, -] - -[package.dependencies] -huggingface-hub = ">=0.4.0" -nltk = "*" -numpy = "*" -scikit-learn = "*" -scipy = "*" -sentencepiece = "*" -torch = ">=1.6.0" -torchvision = "*" -tqdm = "*" -transformers = ">=4.6.0,<5.0.0" - -[[package]] -name = "sentencepiece" -version = "0.1.99" -description = "SentencePiece python wrapper" -optional = false -python-versions = "*" -files = [ - {file = "sentencepiece-0.1.99-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0eb528e70571b7c02723e5804322469b82fe7ea418c96051d0286c0fa028db73"}, - {file = "sentencepiece-0.1.99-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:77d7fafb2c4e4659cbdf303929503f37a26eabc4ff31d3a79bf1c5a1b338caa7"}, - {file = "sentencepiece-0.1.99-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be9cf5b9e404c245aeb3d3723c737ba7a8f5d4ba262ef233a431fa6c45f732a0"}, - {file = "sentencepiece-0.1.99-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baed1a26464998f9710d20e52607c29ffd4293e7c71c6a1f83f51ad0911ec12c"}, - {file = "sentencepiece-0.1.99-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9832f08bb372d4c8b567612f8eab9e36e268dff645f1c28f9f8e851be705f6d1"}, - {file = "sentencepiece-0.1.99-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:019e7535108e309dae2b253a75834fc3128240aa87c00eb80732078cdc182588"}, - {file = "sentencepiece-0.1.99-cp310-cp310-win32.whl", hash = "sha256:fa16a830416bb823fa2a52cbdd474d1f7f3bba527fd2304fb4b140dad31bb9bc"}, - {file = "sentencepiece-0.1.99-cp310-cp310-win_amd64.whl", hash = "sha256:14b0eccb7b641d4591c3e12ae44cab537d68352e4d3b6424944f0c447d2348d5"}, - {file = "sentencepiece-0.1.99-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6d3c56f24183a1e8bd61043ff2c58dfecdc68a5dd8955dc13bab83afd5f76b81"}, - {file = "sentencepiece-0.1.99-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed6ea1819fd612c989999e44a51bf556d0ef6abfb553080b9be3d347e18bcfb7"}, - {file = "sentencepiece-0.1.99-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2a0260cd1fb7bd8b4d4f39dc2444a8d5fd4e0a0c4d5c899810ef1abf99b2d45"}, - {file = "sentencepiece-0.1.99-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a1abff4d1ff81c77cac3cc6fefa34fa4b8b371e5ee51cb7e8d1ebc996d05983"}, - {file = "sentencepiece-0.1.99-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:004e6a621d4bc88978eecb6ea7959264239a17b70f2cbc348033d8195c9808ec"}, - {file = "sentencepiece-0.1.99-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db361e03342c41680afae5807590bc88aa0e17cfd1a42696a160e4005fcda03b"}, - {file = "sentencepiece-0.1.99-cp311-cp311-win32.whl", hash = "sha256:2d95e19168875b70df62916eb55428a0cbcb834ac51d5a7e664eda74def9e1e0"}, - {file = "sentencepiece-0.1.99-cp311-cp311-win_amd64.whl", hash = "sha256:f90d73a6f81248a909f55d8e6ef56fec32d559e1e9af045f0b0322637cb8e5c7"}, - {file = "sentencepiece-0.1.99-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:62e24c81e74bd87a6e0d63c51beb6527e4c0add67e1a17bac18bcd2076afcfeb"}, - {file = "sentencepiece-0.1.99-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57efcc2d51caff20d9573567d9fd3f854d9efe613ed58a439c78c9f93101384a"}, - {file = "sentencepiece-0.1.99-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a904c46197993bd1e95b93a6e373dca2f170379d64441041e2e628ad4afb16f"}, - {file = "sentencepiece-0.1.99-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d89adf59854741c0d465f0e1525b388c0d174f611cc04af54153c5c4f36088c4"}, - {file = "sentencepiece-0.1.99-cp36-cp36m-win32.whl", hash = "sha256:47c378146928690d1bc106fdf0da768cebd03b65dd8405aa3dd88f9c81e35dba"}, - {file = "sentencepiece-0.1.99-cp36-cp36m-win_amd64.whl", hash = "sha256:9ba142e7a90dd6d823c44f9870abdad45e6c63958eb60fe44cca6828d3b69da2"}, - {file = "sentencepiece-0.1.99-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b7b1a9ae4d7c6f1f867e63370cca25cc17b6f4886729595b885ee07a58d3cec3"}, - {file = "sentencepiece-0.1.99-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0f644c9d4d35c096a538507b2163e6191512460035bf51358794a78515b74f7"}, - {file = "sentencepiece-0.1.99-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8843d23a0f686d85e569bd6dcd0dd0e0cbc03731e63497ca6d5bacd18df8b85"}, - {file = "sentencepiece-0.1.99-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e6f690a1caebb4867a2e367afa1918ad35be257ecdb3455d2bbd787936f155"}, - {file = "sentencepiece-0.1.99-cp37-cp37m-win32.whl", hash = "sha256:8a321866c2f85da7beac74a824b4ad6ddc2a4c9bccd9382529506d48f744a12c"}, - {file = "sentencepiece-0.1.99-cp37-cp37m-win_amd64.whl", hash = "sha256:c42f753bcfb7661c122a15b20be7f684b61fc8592c89c870adf52382ea72262d"}, - {file = "sentencepiece-0.1.99-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:85b476406da69c70586f0bb682fcca4c9b40e5059814f2db92303ea4585c650c"}, - {file = "sentencepiece-0.1.99-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cfbcfe13c69d3f87b7fcd5da168df7290a6d006329be71f90ba4f56bc77f8561"}, - {file = "sentencepiece-0.1.99-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:445b0ec381af1cd4eef95243e7180c63d9c384443c16c4c47a28196bd1cda937"}, - {file = "sentencepiece-0.1.99-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6890ea0f2b4703f62d0bf27932e35808b1f679bdb05c7eeb3812b935ba02001"}, - {file = "sentencepiece-0.1.99-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb71af492b0eefbf9f2501bec97bcd043b6812ab000d119eaf4bd33f9e283d03"}, - {file = "sentencepiece-0.1.99-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b866b5bd3ddd54166bbcbf5c8d7dd2e0b397fac8537991c7f544220b1f67bc"}, - {file = "sentencepiece-0.1.99-cp38-cp38-win32.whl", hash = "sha256:b133e8a499eac49c581c3c76e9bdd08c338cc1939e441fee6f92c0ccb5f1f8be"}, - {file = "sentencepiece-0.1.99-cp38-cp38-win_amd64.whl", hash = "sha256:0eaf3591dd0690a87f44f4df129cf8d05d8a4029b5b6709b489b8e27f9a9bcff"}, - {file = "sentencepiece-0.1.99-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38efeda9bbfb55052d482a009c6a37e52f42ebffcea9d3a98a61de7aee356a28"}, - {file = "sentencepiece-0.1.99-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6c030b081dc1e1bcc9fadc314b19b740715d3d566ad73a482da20d7d46fd444c"}, - {file = "sentencepiece-0.1.99-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:84dbe53e02e4f8a2e45d2ac3e430d5c83182142658e25edd76539b7648928727"}, - {file = "sentencepiece-0.1.99-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b0f55d0a0ee1719b4b04221fe0c9f0c3461dc3dabd77a035fa2f4788eb3ef9a"}, - {file = "sentencepiece-0.1.99-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e800f206cd235dc27dc749299e05853a4e4332e8d3dfd81bf13d0e5b9007d9"}, - {file = "sentencepiece-0.1.99-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae1c40cda8f9d5b0423cfa98542735c0235e7597d79caf318855cdf971b2280"}, - {file = "sentencepiece-0.1.99-cp39-cp39-win32.whl", hash = "sha256:c84ce33af12ca222d14a1cdd37bd76a69401e32bc68fe61c67ef6b59402f4ab8"}, - {file = "sentencepiece-0.1.99-cp39-cp39-win_amd64.whl", hash = "sha256:350e5c74d739973f1c9643edb80f7cc904dc948578bcb1d43c6f2b173e5d18dd"}, - {file = "sentencepiece-0.1.99.tar.gz", hash = "sha256:189c48f5cb2949288f97ccdb97f0473098d9c3dcf5a3d99d4eabe719ec27297f"}, -] - [[package]] name = "sentry-sdk" version = "1.39.2" @@ -3499,13 +2849,13 @@ sqlalchemy2-stubs = "*" [[package]] name = "starlette" -version = "0.27.0" +version = "0.35.1" description = "The little ASGI library that shines." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"}, - {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"}, + {file = "starlette-0.35.1-py3-none-any.whl", hash = "sha256:50bbbda9baa098e361f398fda0928062abbaf1f54f4fadcbe17c092a01eb9a25"}, + {file = "starlette-0.35.1.tar.gz", hash = "sha256:3e2639dac3520e4f58734ed22553f950d3f3cb1001cd2eaac4d57e8cdc5f66bc"}, ] [package.dependencies] @@ -3542,17 +2892,6 @@ files = [ [package.extras] doc = ["reno", "sphinx", "tornado (>=4.5)"] -[[package]] -name = "threadpoolctl" -version = "3.2.0" -description = "threadpoolctl" -optional = false -python-versions = ">=3.8" -files = [ - {file = "threadpoolctl-3.2.0-py3-none-any.whl", hash = "sha256:2b7818516e423bdaebb97c723f86a7c6b0a83d3f3b0970328d66f4d9104dc032"}, - {file = "threadpoolctl-3.2.0.tar.gz", hash = "sha256:c96a0ba3bdddeaca37dc4cc7344aafad41cdb8c313f74fdfe387a867bba93355"}, -] - [[package]] name = "tiktoken" version = "0.5.2" @@ -3720,97 +3059,6 @@ dev = ["tokenizers[testing]"] docs = ["setuptools_rust", "sphinx", "sphinx_rtd_theme"] testing = ["black (==22.3)", "datasets", "numpy", "pytest", "requests"] -[[package]] -name = "torch" -version = "2.1.2" -description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "torch-2.1.2-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:3a871edd6c02dae77ad810335c0833391c1a4ce49af21ea8cf0f6a5d2096eea8"}, - {file = "torch-2.1.2-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:bef6996c27d8f6e92ea4e13a772d89611da0e103b48790de78131e308cf73076"}, - {file = "torch-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:0e13034fd5fb323cbbc29e56d0637a3791e50dd589616f40c79adfa36a5a35a1"}, - {file = "torch-2.1.2-cp310-none-macosx_10_9_x86_64.whl", hash = "sha256:d9b535cad0df3d13997dbe8bd68ac33e0e3ae5377639c9881948e40794a61403"}, - {file = "torch-2.1.2-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:f9a55d55af02826ebfbadf4e9b682f0f27766bc33df8236b48d28d705587868f"}, - {file = "torch-2.1.2-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:a6ebbe517097ef289cc7952783588c72de071d4b15ce0f8b285093f0916b1162"}, - {file = "torch-2.1.2-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:8f32ce591616a30304f37a7d5ea80b69ca9e1b94bba7f308184bf616fdaea155"}, - {file = "torch-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e0ee6cf90c8970e05760f898d58f9ac65821c37ffe8b04269ec787aa70962b69"}, - {file = "torch-2.1.2-cp311-none-macosx_10_9_x86_64.whl", hash = "sha256:76d37967c31c99548ad2c4d3f2cf191db48476f2e69b35a0937137116da356a1"}, - {file = "torch-2.1.2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:e2d83f07b4aac983453ea5bf8f9aa9dacf2278a8d31247f5d9037f37befc60e4"}, - {file = "torch-2.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f41fe0c7ecbf903a568c73486139a75cfab287a0f6c17ed0698fdea7a1e8641d"}, - {file = "torch-2.1.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e3225f47d50bb66f756fe9196a768055d1c26b02154eb1f770ce47a2578d3aa7"}, - {file = "torch-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:33d59cd03cb60106857f6c26b36457793637512998666ee3ce17311f217afe2b"}, - {file = "torch-2.1.2-cp38-none-macosx_10_9_x86_64.whl", hash = "sha256:8e221deccd0def6c2badff6be403e0c53491805ed9915e2c029adbcdb87ab6b5"}, - {file = "torch-2.1.2-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:05b18594f60a911a0c4f023f38a8bda77131fba5fd741bda626e97dcf5a3dd0a"}, - {file = "torch-2.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:9ca96253b761e9aaf8e06fb30a66ee301aecbf15bb5a303097de1969077620b6"}, - {file = "torch-2.1.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d93ba70f67b08c2ae5598ee711cbc546a1bc8102cef938904b8c85c2089a51a0"}, - {file = "torch-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:255b50bc0608db177e6a3cc118961d77de7e5105f07816585fa6f191f33a9ff3"}, - {file = "torch-2.1.2-cp39-none-macosx_10_9_x86_64.whl", hash = "sha256:6984cd5057c0c977b3c9757254e989d3f1124f4ce9d07caa6cb637783c71d42a"}, - {file = "torch-2.1.2-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:bc195d7927feabc0eb7c110e457c955ed2ab616f3c7c28439dd4188cf589699f"}, -] - -[package.dependencies] -filelock = "*" -fsspec = "*" -jinja2 = "*" -networkx = "*" -nvidia-cublas-cu12 = {version = "12.1.3.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cuda-cupti-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cuda-nvrtc-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cuda-runtime-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cudnn-cu12 = {version = "8.9.2.26", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cufft-cu12 = {version = "11.0.2.54", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-curand-cu12 = {version = "10.3.2.106", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cusolver-cu12 = {version = "11.4.5.107", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cusparse-cu12 = {version = "12.1.0.106", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-nccl-cu12 = {version = "2.18.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-nvtx-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -sympy = "*" -triton = {version = "2.1.0", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -typing-extensions = "*" - -[package.extras] -dynamo = ["jinja2"] -opt-einsum = ["opt-einsum (>=3.3)"] - -[[package]] -name = "torchvision" -version = "0.16.2" -description = "image and video datasets and models for torch deep learning" -optional = false -python-versions = ">=3.8" -files = [ - {file = "torchvision-0.16.2-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:bc86f2800cb2c0c1a09c581409cdd6bff66e62f103dc83fc63f73346264c3756"}, - {file = "torchvision-0.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b024bd412df6d3a007dcebf311a894eb3c5c21e1af80d12be382bbcb097a7c3a"}, - {file = "torchvision-0.16.2-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:e89f10f3c8351972b6e3fda95bc3e479ea8dbfc9dfcfd2c32902dbad4ba5cfc5"}, - {file = "torchvision-0.16.2-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:96c7583700112a410bdc4e1e4f118c429dab49c29c9a31a2cc3579bc9b08b19d"}, - {file = "torchvision-0.16.2-cp310-cp310-win_amd64.whl", hash = "sha256:9f4032ebb3277fb07ff6a9b818d50a547fb8fcd89d958cfd9e773322454bb688"}, - {file = "torchvision-0.16.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:67b1aaf8b8cb02ce75dd445f291a27c8036a502f8c0aa76e28c37a0faac2e153"}, - {file = "torchvision-0.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bef30d03e1d1c629761f4dca51d3b7d8a0dc0acce6f4068ab2a1634e8e7b64e0"}, - {file = "torchvision-0.16.2-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:e59cc7b2bd1ab5c0ce4ae382e4e37be8f1c174e8b5de2f6a23c170de9ae28495"}, - {file = "torchvision-0.16.2-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:e130b08cc9b3cc73a6c59d6edf032394a322f9579bfd21d14bc2e1d0999aa758"}, - {file = "torchvision-0.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:8692ab1e48807e9604046a6f4beeb67b523294cee1b00828654bb0df2cfce2b2"}, - {file = "torchvision-0.16.2-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:b82732dcf876a37c852772342aa6ee3480c03bb3e2a802ae109fc5f7e28d26e9"}, - {file = "torchvision-0.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4b065143d1a720fe8a9077fd4be35d491f98819ec80b3dbbc3ec64d0b707a906"}, - {file = "torchvision-0.16.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bc5f274e4ecd1b86062063cdf4fd385a1d39d147a3a2685fbbde9ff08bb720b8"}, - {file = "torchvision-0.16.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:335959c43b371c0474af34c1ef2a52efdc7603c45700d29e4475eeb02984170c"}, - {file = "torchvision-0.16.2-cp38-cp38-win_amd64.whl", hash = "sha256:7fd22d86e08eba321af70cad291020c2cdeac069b00ce88b923ca52e06174769"}, - {file = "torchvision-0.16.2-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:56115268b37f0b75364e3654e47ad9abc66ac34c1f9e5e3dfa89a22d6a40017a"}, - {file = "torchvision-0.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:82805f8445b094f9d1e770390ee6cc86855e89955e08ce34af2e2274fc0e5c45"}, - {file = "torchvision-0.16.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3f4bd5fcbc361476e2e78016636ac7d5509e59d9962521f06eb98e6803898182"}, - {file = "torchvision-0.16.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8199acdf8ab066a28b84a5b6f4d97b58976d9e164b1acc3a9d14fccfaf74bb3a"}, - {file = "torchvision-0.16.2-cp39-cp39-win_amd64.whl", hash = "sha256:41dd4fa9f176d563fe9f1b9adef3b7e582cdfb60ce8c9bc51b094a025be687c9"}, -] - -[package.dependencies] -numpy = "*" -pillow = ">=5.3.0,<8.3.dev0 || >=8.4.dev0" -requests = "*" -torch = "2.1.2" - -[package.extras] -scipy = ["scipy"] - [[package]] name = "tqdm" version = "4.66.1" @@ -3831,99 +3079,6 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] -[[package]] -name = "transformers" -version = "4.36.2" -description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "transformers-4.36.2-py3-none-any.whl", hash = "sha256:462066c4f74ee52516f12890dcc9ec71d1a5e97998db621668455117a54330f6"}, - {file = "transformers-4.36.2.tar.gz", hash = "sha256:d8068e897e47793281501e547d2bbdfc5b8556409c2cb6c3d9e2ca77d4c0b4ec"}, -] - -[package.dependencies] -filelock = "*" -huggingface-hub = ">=0.19.3,<1.0" -numpy = ">=1.17" -packaging = ">=20.0" -pyyaml = ">=5.1" -regex = "!=2019.12.17" -requests = "*" -safetensors = ">=0.3.1" -tokenizers = ">=0.14,<0.19" -tqdm = ">=4.27" - -[package.extras] -accelerate = ["accelerate (>=0.21.0)"] -agents = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "datasets (!=2.5.0)", "diffusers", "opencv-python", "sentencepiece (>=0.1.91,!=0.1.92)", "torch (>=1.10,!=1.12.0)"] -all = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune] (>=2.7.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>=2.6,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timm", "tokenizers (>=0.14,<0.19)", "torch (>=1.10,!=1.12.0)", "torchaudio", "torchvision"] -audio = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] -codecarbon = ["codecarbon (==1.2.0)"] -deepspeed = ["accelerate (>=0.21.0)", "deepspeed (>=0.9.3)"] -deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.21.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "deepspeed (>=0.9.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder (>=0.3.0)", "nltk", "optuna", "parameterized", "protobuf", "psutil", "pydantic (<2)", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.1.5)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] -dev = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "av (==9.2.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "decord (==0.6.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.7.0)", "fugashi (>=1.0)", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic (<2)", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.1.5)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "tensorflow (>=2.6,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "timm", "tokenizers (>=0.14,<0.19)", "torch (>=1.10,!=1.12.0)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] -dev-tensorflow = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "isort (>=5.5.4)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic (<2)", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.1.5)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "tensorflow (>=2.6,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.14,<0.19)", "urllib3 (<2.0.0)"] -dev-torch = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "kenlm", "librosa", "nltk", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic (<2)", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.1.5)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "timeout-decorator", "timm", "tokenizers (>=0.14,<0.19)", "torch (>=1.10,!=1.12.0)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] -docs = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1,<=0.7.0)", "hf-doc-builder", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune] (>=2.7.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>=2.6,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timm", "tokenizers (>=0.14,<0.19)", "torch (>=1.10,!=1.12.0)", "torchaudio", "torchvision"] -docs-specific = ["hf-doc-builder"] -flax = ["flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "optax (>=0.0.8,<=0.1.4)"] -flax-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] -ftfy = ["ftfy"] -integrations = ["optuna", "ray[tune] (>=2.7.0)", "sigopt"] -ja = ["fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "rhoknp (>=1.1.0,<1.3.1)", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)"] -modelcreation = ["cookiecutter (==1.7.3)"] -natten = ["natten (>=0.14.6)"] -onnx = ["onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "tf2onnx"] -onnxruntime = ["onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)"] -optuna = ["optuna"] -quality = ["GitPython (<3.1.19)", "datasets (!=2.5.0)", "hf-doc-builder (>=0.3.0)", "isort (>=5.5.4)", "ruff (==0.1.5)", "urllib3 (<2.0.0)"] -ray = ["ray[tune] (>=2.7.0)"] -retrieval = ["datasets (!=2.5.0)", "faiss-cpu"] -sagemaker = ["sagemaker (>=2.31.0)"] -sentencepiece = ["protobuf", "sentencepiece (>=0.1.91,!=0.1.92)"] -serving = ["fastapi", "pydantic (<2)", "starlette", "uvicorn"] -sigopt = ["sigopt"] -sklearn = ["scikit-learn"] -speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] -testing = ["GitPython (<3.1.19)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder (>=0.3.0)", "nltk", "parameterized", "protobuf", "psutil", "pydantic (<2)", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.1.5)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "tensorboard", "timeout-decorator"] -tf = ["keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow (>=2.6,<2.16)", "tensorflow-text (<2.16)", "tf2onnx"] -tf-cpu = ["keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow-cpu (>=2.6,<2.16)", "tensorflow-text (<2.16)", "tf2onnx"] -tf-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] -timm = ["timm"] -tokenizers = ["tokenizers (>=0.14,<0.19)"] -torch = ["accelerate (>=0.21.0)", "torch (>=1.10,!=1.12.0)"] -torch-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] -torch-vision = ["Pillow (>=10.0.1,<=15.0)", "torchvision"] -torchhub = ["filelock", "huggingface-hub (>=0.19.3,<1.0)", "importlib-metadata", "numpy (>=1.17)", "packaging (>=20.0)", "protobuf", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.14,<0.19)", "torch (>=1.10,!=1.12.0)", "tqdm (>=4.27)"] -video = ["av (==9.2.0)", "decord (==0.6.0)"] -vision = ["Pillow (>=10.0.1,<=15.0)"] - -[[package]] -name = "triton" -version = "2.1.0" -description = "A language and compiler for custom Deep Learning operations" -optional = false -python-versions = "*" -files = [ - {file = "triton-2.1.0-0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:66439923a30d5d48399b08a9eae10370f6c261a5ec864a64983bae63152d39d7"}, - {file = "triton-2.1.0-0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:919b06453f0033ea52c13eaf7833de0e57db3178d23d4e04f9fc71c4f2c32bf8"}, - {file = "triton-2.1.0-0-cp37-cp37m-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae4bb8a91de790e1866405211c4d618379781188f40d5c4c399766914e84cd94"}, - {file = "triton-2.1.0-0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39f6fb6bdccb3e98f3152e3fbea724f1aeae7d749412bbb1fa9c441d474eba26"}, - {file = "triton-2.1.0-0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21544e522c02005a626c8ad63d39bdff2f31d41069592919ef281e964ed26446"}, - {file = "triton-2.1.0-0-pp37-pypy37_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:143582ca31dd89cd982bd3bf53666bab1c7527d41e185f9e3d8a3051ce1b663b"}, - {file = "triton-2.1.0-0-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82fc5aeeedf6e36be4e4530cbdcba81a09d65c18e02f52dc298696d45721f3bd"}, - {file = "triton-2.1.0-0-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:81a96d110a738ff63339fc892ded095b31bd0d205e3aace262af8400d40b6fa8"}, -] - -[package.dependencies] -filelock = "*" - -[package.extras] -build = ["cmake (>=3.18)", "lit"] -tests = ["autopep8", "flake8", "isort", "numpy", "pytest", "scipy (>=1.7.1)"] -tutorials = ["matplotlib", "pandas", "tabulate"] - [[package]] name = "typer" version = "0.9.0" @@ -4441,4 +3596,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "76237f0e04218f9ca9a2593ccf952452bd6d45657066feec87373279fb7fe6a2" +content-hash = "a29d79a2581c778d53ea000990ae65f35dd0e8ce2c65cdbfdb0e3f2e19d18d16" diff --git a/apps/api/pyproject.toml b/apps/api/pyproject.toml index ba16002d..b57f6280 100644 --- a/apps/api/pyproject.toml +++ b/apps/api/pyproject.toml @@ -11,7 +11,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.11" -fastapi = "0.104.1" +fastapi = "0.109.1" pydantic = {version = ">=1.8.0,<2.0.0", extras = ["email"]} sqlmodel = "0.0.10" uvicorn = "0.23.2" @@ -34,7 +34,6 @@ langchain = "0.1.0" tiktoken = "^0.5.2" openai = "^1.7.1" chromadb = "^0.4.22" -sentence-transformers = "^2.2.2" python-dotenv = "^1.0.0" redis = "^5.0.1" langchain-community = "^0.0.11" diff --git a/apps/api/requirements.txt b/apps/api/requirements.txt index e3c28897..9f7e1c7f 100644 --- a/apps/api/requirements.txt +++ b/apps/api/requirements.txt @@ -24,6 +24,5 @@ langchain-openai tiktoken openai chromadb -sentence-transformers python-dotenv redis diff --git a/apps/api/src/db/activities.py b/apps/api/src/db/activities.py index 95b6ecef..3db42b2a 100644 --- a/apps/api/src/db/activities.py +++ b/apps/api/src/db/activities.py @@ -1,5 +1,5 @@ from typing import Optional -from sqlalchemy import JSON, BigInteger, Column, ForeignKey +from sqlalchemy import JSON, Column, ForeignKey, Integer from sqlmodel import Field, SQLModel from enum import Enum @@ -38,12 +38,12 @@ class ActivityBase(SQLModel): class Activity(ActivityBase, table=True): id: Optional[int] = Field(default=None, primary_key=True) - org_id: int = Field(default=None, foreign_key="organization.id") + org_id: int = Field( + sa_column=Column(Integer, ForeignKey("organization.id", ondelete="CASCADE")) + ) course_id: int = Field( default=None, - sa_column=Column( - BigInteger, ForeignKey("course.id", ondelete="CASCADE") - ), + sa_column=Column(Integer, ForeignKey("course.id", ondelete="CASCADE")), ) activity_uuid: str = "" creation_date: str = "" diff --git a/apps/api/src/db/blocks.py b/apps/api/src/db/blocks.py index 59972a04..453429f7 100644 --- a/apps/api/src/db/blocks.py +++ b/apps/api/src/db/blocks.py @@ -21,7 +21,7 @@ class BlockBase(SQLModel): class Block(BlockBase, table=True): id: Optional[int] = Field(default=None, primary_key=True) content: dict = Field(default={}, sa_column=Column(JSON)) - org_id: int = Field(default=None, foreign_key="organization.id") + org_id: int = Field(sa_column= Column("org_id", ForeignKey("organization.id", ondelete="CASCADE"))) course_id: int = Field(sa_column= Column("course_id", ForeignKey("course.id", ondelete="CASCADE"))) chapter_id: int = Field(sa_column= Column("chapter_id", ForeignKey("chapter.id", ondelete="CASCADE"))) activity_id: int = Field(sa_column= Column("activity_id", ForeignKey("activity.id", ondelete="CASCADE"))) diff --git a/apps/api/src/db/collections.py b/apps/api/src/db/collections.py index 9b191c8d..fb0b1e94 100644 --- a/apps/api/src/db/collections.py +++ b/apps/api/src/db/collections.py @@ -1,4 +1,5 @@ from typing import Optional +from sqlalchemy import BigInteger, Column, ForeignKey from sqlmodel import Field, SQLModel @@ -10,7 +11,9 @@ class CollectionBase(SQLModel): class Collection(CollectionBase, table=True): id: Optional[int] = Field(default=None, primary_key=True) - org_id: int = Field(default=None, foreign_key="organization.id") + org_id: int = Field( + sa_column=Column(BigInteger, ForeignKey("organization.id", ondelete="CASCADE")) + ) collection_uuid: str = "" creation_date: str = "" update_date: str = "" diff --git a/apps/api/src/db/collections_courses.py b/apps/api/src/db/collections_courses.py index 6b30c2ee..4e0fc270 100644 --- a/apps/api/src/db/collections_courses.py +++ b/apps/api/src/db/collections_courses.py @@ -1,12 +1,12 @@ from typing import Optional -from sqlalchemy import BigInteger, Column, ForeignKey +from sqlalchemy import Column, ForeignKey, Integer from sqlmodel import Field, SQLModel class CollectionCourse(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - collection_id: int = Field(sa_column=Column(BigInteger, ForeignKey("collection.id", ondelete="CASCADE"))) - course_id: int = Field(sa_column=Column(BigInteger, ForeignKey("course.id", ondelete="CASCADE"))) + collection_id: int = Field(sa_column=Column(Integer, ForeignKey("collection.id", ondelete="CASCADE"))) + course_id: int = Field(sa_column=Column(Integer, ForeignKey("course.id", ondelete="CASCADE"))) org_id: int = Field(default=None, foreign_key="organization.id") creation_date: str update_date: str diff --git a/apps/api/src/db/course_chapters.py b/apps/api/src/db/course_chapters.py index dec820c6..97e9d623 100644 --- a/apps/api/src/db/course_chapters.py +++ b/apps/api/src/db/course_chapters.py @@ -1,5 +1,5 @@ from typing import Optional -from sqlalchemy import BigInteger, Column, ForeignKey +from sqlalchemy import Column, ForeignKey, Integer from sqlmodel import Field, SQLModel @@ -7,10 +7,10 @@ class CourseChapter(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) order: int course_id: int = Field( - sa_column=Column(BigInteger, ForeignKey("course.id", ondelete="CASCADE")) + sa_column=Column(Integer, ForeignKey("course.id", ondelete="CASCADE")) ) chapter_id: int = Field( - sa_column=Column(BigInteger, ForeignKey("chapter.id", ondelete="CASCADE")) + sa_column=Column(Integer, ForeignKey("chapter.id", ondelete="CASCADE")) ) org_id: int = Field(default=None, foreign_key="organization.id") creation_date: str diff --git a/apps/api/src/db/courses.py b/apps/api/src/db/courses.py index 7623aa1f..31586d25 100644 --- a/apps/api/src/db/courses.py +++ b/apps/api/src/db/courses.py @@ -1,4 +1,5 @@ from typing import List, Optional +from sqlalchemy import Column, ForeignKey, Integer from sqlmodel import Field, SQLModel from src.db.users import UserRead from src.db.trails import TrailRead @@ -17,7 +18,9 @@ class CourseBase(SQLModel): class Course(CourseBase, table=True): id: Optional[int] = Field(default=None, primary_key=True) - org_id: int = Field(default=None, foreign_key="organization.id") + org_id: int = Field( + sa_column=Column(Integer, ForeignKey("organization.id", ondelete="CASCADE")) + ) course_uuid: str = "" creation_date: str = "" update_date: str = "" diff --git a/apps/api/src/db/organization_config.py b/apps/api/src/db/organization_config.py index 214a5d0d..53d4fa1d 100644 --- a/apps/api/src/db/organization_config.py +++ b/apps/api/src/db/organization_config.py @@ -21,8 +21,8 @@ class AIConfig(BaseModel): enabled : bool = True limits: AILimitsSettings = AILimitsSettings() embeddings: Literal[ - "text-embedding-ada-002", "all-MiniLM-L6-v2" - ] = "all-MiniLM-L6-v2" + "text-embedding-ada-002", + ] = "text-embedding-ada-002" ai_model: Literal["gpt-3.5-turbo", "gpt-4-1106-preview"] = "gpt-3.5-turbo" features: AIEnabledFeatures = AIEnabledFeatures() diff --git a/apps/api/src/db/organizations.py b/apps/api/src/db/organizations.py index f9257be6..427c57d1 100644 --- a/apps/api/src/db/organizations.py +++ b/apps/api/src/db/organizations.py @@ -1,5 +1,8 @@ from typing import Optional +from pydantic import BaseModel from sqlmodel import Field, SQLModel +from src.db.roles import RoleRead + from src.db.organization_config import OrganizationConfig @@ -32,3 +35,9 @@ class OrganizationRead(OrganizationBase): config: Optional[OrganizationConfig | dict] creation_date: str update_date: str + + +class OrganizationUser(BaseModel): + from src.db.users import UserRead + user: UserRead + role: RoleRead diff --git a/apps/api/src/db/resource_authors.py b/apps/api/src/db/resource_authors.py index 758a59c3..3f938397 100644 --- a/apps/api/src/db/resource_authors.py +++ b/apps/api/src/db/resource_authors.py @@ -1,5 +1,6 @@ from enum import Enum from typing import Optional +from sqlalchemy import Column, ForeignKey, Integer from sqlmodel import Field, SQLModel @@ -12,7 +13,9 @@ class ResourceAuthorshipEnum(str, Enum): class ResourceAuthor(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) resource_uuid: str - user_id: int = Field(default=None, foreign_key="user.id") + user_id: int = Field( + sa_column=Column(Integer, ForeignKey("user.id", ondelete="CASCADE")) + ) authorship: ResourceAuthorshipEnum = ResourceAuthorshipEnum.CREATOR creation_date: str = "" update_date: str = "" diff --git a/apps/api/src/db/roles.py b/apps/api/src/db/roles.py index 09ff4206..8ea66dbb 100644 --- a/apps/api/src/db/roles.py +++ b/apps/api/src/db/roles.py @@ -1,7 +1,7 @@ from enum import Enum from typing import Optional, Union from pydantic import BaseModel -from sqlalchemy import JSON, Column +from sqlalchemy import JSON, Column, ForeignKey, Integer from sqlmodel import Field, SQLModel @@ -45,7 +45,10 @@ class RoleBase(SQLModel): class Role(RoleBase, table=True): id: Optional[int] = Field(default=None, primary_key=True) - org_id: int = Field(default=None, foreign_key="organization.id") + org_id: Optional[int] = Field( + default=None, + sa_column=Column(Integer, ForeignKey("organization.id", ondelete="CASCADE")) + ) role_type: RoleTypeEnum = RoleTypeEnum.TYPE_GLOBAL role_uuid: str = "" creation_date: str = "" diff --git a/apps/api/src/db/trail_runs.py b/apps/api/src/db/trail_runs.py index 26bfec8f..c2308162 100644 --- a/apps/api/src/db/trail_runs.py +++ b/apps/api/src/db/trail_runs.py @@ -1,6 +1,6 @@ from typing import Optional from pydantic import BaseModel -from sqlalchemy import JSON, Column +from sqlalchemy import JSON, Column, ForeignKey, Integer from sqlmodel import Field, SQLModel from enum import Enum @@ -23,10 +23,18 @@ class TrailRun(SQLModel, table=True): data: dict = Field(default={}, sa_column=Column(JSON)) status: StatusEnum = StatusEnum.STATUS_IN_PROGRESS # foreign keys - trail_id: int = Field(default=None, foreign_key="trail.id") - course_id: int = Field(default=None, foreign_key="course.id") - org_id: int = Field(default=None, foreign_key="organization.id") - user_id: int = Field(default=None, foreign_key="user.id") + trail_id: int = Field( + sa_column=Column(Integer, ForeignKey("trail.id", ondelete="CASCADE")) + ) + course_id: int = Field( + sa_column=Column(Integer, ForeignKey("course.id", ondelete="CASCADE")) + ) + org_id: int = Field( + sa_column=Column(Integer, ForeignKey("organization.id", ondelete="CASCADE")) + ) + user_id: int = Field( + sa_column=Column(Integer, ForeignKey("user.id", ondelete="CASCADE")) + ) # timestamps creation_date: str update_date: str diff --git a/apps/api/src/db/trail_steps.py b/apps/api/src/db/trail_steps.py index af13c95d..b11d148a 100644 --- a/apps/api/src/db/trail_steps.py +++ b/apps/api/src/db/trail_steps.py @@ -1,7 +1,7 @@ from enum import Enum from typing import Optional from sqlmodel import Field, SQLModel -from sqlalchemy import BigInteger, ForeignKey, JSON, Column +from sqlalchemy import ForeignKey, JSON, Column, Integer class TrailStepTypeEnum(str, Enum): @@ -18,13 +18,23 @@ class TrailStep(SQLModel, table=True): data: dict = Field(default={}, sa_column=Column(JSON)) # foreign keys trailrun_id: int = Field( - sa_column=Column(BigInteger, ForeignKey("trailrun.id", ondelete="CASCADE")) + sa_column=Column(Integer, ForeignKey("trailrun.id", ondelete="CASCADE")) + ) + trail_id: int = Field( + sa_column=Column(Integer, ForeignKey("trail.id", ondelete="CASCADE")) + ) + activity_id: int = Field( + sa_column=Column(Integer, ForeignKey("activity.id", ondelete="CASCADE")) + ) + course_id: int = Field( + sa_column=Column(Integer, ForeignKey("course.id", ondelete="CASCADE")) + ) + org_id: int = Field( + sa_column=Column(Integer, ForeignKey("organization.id", ondelete="CASCADE")) + ) + user_id: int = Field( + sa_column=Column(Integer, ForeignKey("user.id", ondelete="CASCADE")) ) - trail_id: int = Field(default=None, foreign_key="trail.id") - activity_id: int = Field(default=None, foreign_key="activity.id") - course_id: int = Field(default=None, foreign_key="course.id") - org_id: int = Field(default=None, foreign_key="organization.id") - user_id: int = Field(default=None, foreign_key="user.id") # timestamps creation_date: str update_date: str diff --git a/apps/api/src/db/trails.py b/apps/api/src/db/trails.py index e29f241f..950388d6 100644 --- a/apps/api/src/db/trails.py +++ b/apps/api/src/db/trails.py @@ -1,5 +1,6 @@ from typing import Optional from pydantic import BaseModel +from sqlalchemy import Column, ForeignKey, Integer from sqlmodel import Field, SQLModel from src.db.trail_runs import TrailRunRead @@ -24,8 +25,12 @@ class TrailCreate(TrailBase): class TrailRead(BaseModel): id: Optional[int] = Field(default=None, primary_key=True) trail_uuid: Optional[str] - org_id: int = Field(default=None, foreign_key="organization.id") - user_id: int = Field(default=None, foreign_key="user.id") + org_id: int = Field( + sa_column=Column(Integer, ForeignKey("organization.id", ondelete="CASCADE")) + ) + user_id: int = Field( + sa_column=Column(Integer, ForeignKey("user.id", ondelete="CASCADE")) + ) creation_date: Optional[str] update_date: Optional[str] runs: list[TrailRunRead] diff --git a/apps/api/src/db/user_organizations.py b/apps/api/src/db/user_organizations.py index c842d41c..4ce1c8cc 100644 --- a/apps/api/src/db/user_organizations.py +++ b/apps/api/src/db/user_organizations.py @@ -1,5 +1,5 @@ from typing import Optional -from sqlalchemy import BigInteger, Column, ForeignKey +from sqlalchemy import Column, ForeignKey, Integer from sqlmodel import Field, SQLModel @@ -7,7 +7,7 @@ class UserOrganization(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(default=None, foreign_key="user.id") org_id: int = Field( - sa_column=Column(BigInteger, ForeignKey("organization.id", ondelete="CASCADE")) + sa_column=Column(Integer, ForeignKey("organization.id", ondelete="CASCADE")) ) role_id: int = Field(default=None, foreign_key="role.id") creation_date: str diff --git a/apps/api/src/db/users.py b/apps/api/src/db/users.py index 6e1831a6..8d98c3e7 100644 --- a/apps/api/src/db/users.py +++ b/apps/api/src/db/users.py @@ -1,9 +1,8 @@ from typing import Optional from pydantic import BaseModel, EmailStr from sqlmodel import Field, SQLModel - from src.db.roles import RoleRead -from src.db.organizations import OrganizationRead + class UserBase(SQLModel): @@ -45,6 +44,7 @@ class PublicUser(UserRead): class UserRoleWithOrg(BaseModel): + from src.db.organizations import OrganizationRead role: RoleRead org: OrganizationRead diff --git a/apps/api/src/routers/auth.py b/apps/api/src/routers/auth.py index 307ad70b..fd6e87e9 100644 --- a/apps/api/src/routers/auth.py +++ b/apps/api/src/routers/auth.py @@ -1,3 +1,4 @@ +from datetime import timedelta from fastapi import Depends, APIRouter, HTTPException, Response, status, Request from fastapi.security import OAuth2PasswordRequestForm from sqlmodel import Session @@ -10,7 +11,7 @@ from src.security.auth import AuthJWT, authenticate_user router = APIRouter() -@router.post("/refresh") +@router.get("/refresh") def refresh(response: Response, Authorize: AuthJWT = Depends()): """ The jwt_refresh_token_required() function insures a valid refresh @@ -28,6 +29,7 @@ def refresh(response: Response, Authorize: AuthJWT = Depends()): value=new_access_token, httponly=False, domain=get_learnhouse_config().hosting_config.cookie_config.domain, + expires=int(timedelta(hours=8).total_seconds()), ) return {"access_token": new_access_token} @@ -53,14 +55,16 @@ async def login( access_token = Authorize.create_access_token(subject=form_data.username) refresh_token = Authorize.create_refresh_token(subject=form_data.username) Authorize.set_refresh_cookies(refresh_token) + # set cookies using fastapi response.set_cookie( key="access_token_cookie", value=access_token, httponly=False, domain=get_learnhouse_config().hosting_config.cookie_config.domain, + expires=int(timedelta(hours=8).total_seconds()), ) - + user = UserRead.from_orm(user) result = { diff --git a/apps/api/src/routers/courses/courses.py b/apps/api/src/routers/courses/courses.py index f9485e80..6244c838 100644 --- a/apps/api/src/routers/courses/courses.py +++ b/apps/api/src/routers/courses/courses.py @@ -31,8 +31,8 @@ async def api_create_course( name: str = Form(), description: str = Form(), public: bool = Form(), - learnings: str = Form(), - tags: str = Form(), + learnings: str = Form(None), + tags: str = Form(None), about: str = Form(), current_user: PublicUser = Depends(get_current_user), db_session: Session = Depends(get_db_session), diff --git a/apps/api/src/routers/orgs.py b/apps/api/src/routers/orgs.py index 9870b3db..72ce616e 100644 --- a/apps/api/src/routers/orgs.py +++ b/apps/api/src/routers/orgs.py @@ -1,6 +1,20 @@ -from typing import List +from typing import List, Literal from fastapi import APIRouter, Depends, Request, UploadFile from sqlmodel import Session +from src.services.orgs.invites import ( + create_invite_code, + delete_invite_code, + get_invite_code, + get_invite_codes, +) +from src.services.orgs.users import ( + get_list_of_invited_users, + get_organization_users, + invite_batch_users, + remove_invited_user, + remove_user_from_org, + update_user_role, +) from src.db.organization_config import OrganizationConfigBase from src.db.users import PublicUser from src.db.organizations import ( @@ -8,6 +22,7 @@ from src.db.organizations import ( OrganizationCreate, OrganizationRead, OrganizationUpdate, + OrganizationUser, ) from src.core.events.database import get_db_session from src.security.auth import get_current_user @@ -20,6 +35,7 @@ from src.services.orgs.orgs import ( get_orgs_by_user, update_org, update_org_logo, + update_org_signup_mechanism, ) @@ -69,6 +85,166 @@ async def api_get_org( return await get_organization(request, org_id, db_session, current_user) +@router.get("/{org_id}/users") +async def api_get_org_users( + request: Request, + org_id: str, + current_user: PublicUser = Depends(get_current_user), + db_session: Session = Depends(get_db_session), +) -> list[OrganizationUser]: + """ + Get single Org by ID + """ + return await get_organization_users(request, org_id, db_session, current_user) + + +@router.put("/{org_id}/users/{user_id}/role/{role_uuid}") +async def api_update_user_role( + request: Request, + org_id: str, + user_id: str, + role_uuid: str, + current_user: PublicUser = Depends(get_current_user), + db_session: Session = Depends(get_db_session), +): + """ + Update user role + """ + return await update_user_role( + request, org_id, user_id, role_uuid, db_session, current_user + ) + + +@router.delete("/{org_id}/users/{user_id}") +async def api_remove_user_from_org( + request: Request, + org_id: int, + user_id: int, + current_user: PublicUser = Depends(get_current_user), + db_session: Session = Depends(get_db_session), +): + """ + Remove user from org + """ + return await remove_user_from_org( + request, org_id, user_id, db_session, current_user + ) + + +# Config related routes +@router.put("/{org_id}/signup_mechanism") +async def api_get_org_signup_mechanism( + request: Request, + org_id: int, + signup_mechanism: Literal["open", "inviteOnly"], + current_user: PublicUser = Depends(get_current_user), + db_session: Session = Depends(get_db_session), +): + """ + Get org signup mechanism + """ + return await update_org_signup_mechanism( + request, signup_mechanism, org_id, current_user, db_session + ) + + +# Invites related routes +@router.post("/{org_id}/invites") +async def api_create_invite_code( + request: Request, + org_id: int, + current_user: PublicUser = Depends(get_current_user), + db_session: Session = Depends(get_db_session), +): + """ + Create invite code + """ + return await create_invite_code(request, org_id, current_user, db_session) + + +@router.get("/{org_id}/invites") +async def api_get_invite_codes( + request: Request, + org_id: int, + current_user: PublicUser = Depends(get_current_user), + db_session: Session = Depends(get_db_session), +): + """ + Get invite codes + """ + return await get_invite_codes(request, org_id, current_user, db_session) + +@router.get("/{org_id}/invites/code/{invite_code}") +async def api_get_invite_code( + request: Request, + org_id: int, + invite_code: str, + current_user: PublicUser = Depends(get_current_user), + db_session: Session = Depends(get_db_session), +): + """ + Get invite code + """ + print(f"org_id: {org_id}, invite_code: {invite_code}") + return await get_invite_code(request, org_id,invite_code, current_user, db_session) + + +@router.delete("/{org_id}/invites/{org_invite_code_uuid}") +async def api_delete_invite_code( + request: Request, + org_id: int, + org_invite_code_uuid: str, + current_user: PublicUser = Depends(get_current_user), + db_session: Session = Depends(get_db_session), +): + """ + Delete invite code + """ + return await delete_invite_code( + request, org_id, org_invite_code_uuid, current_user, db_session + ) + + +@router.post("/{org_id}/invites/users/batch") +async def api_invite_batch_users( + request: Request, + org_id: int, + users: str, + current_user: PublicUser = Depends(get_current_user), + db_session: Session = Depends(get_db_session), +): + """ + Invite batch users + """ + return await invite_batch_users(request, org_id, users, db_session, current_user) + + +@router.get("/{org_id}/invites/users") +async def api_get_org_users_invites( + request: Request, + org_id: int, + current_user: PublicUser = Depends(get_current_user), + db_session: Session = Depends(get_db_session), +): + """ + Get org users invites + """ + return await get_list_of_invited_users(request, org_id, db_session, current_user) + +@router.delete("/{org_id}/invites/users/{email}") +async def api_delete_org_users_invites( + request: Request, + org_id: int, + email: str, + current_user: PublicUser = Depends(get_current_user), + db_session: Session = Depends(get_db_session), +): + """ + Delete org users invites + """ + return await remove_invited_user(request, org_id, email, db_session, current_user) + + @router.get("/slug/{org_slug}") async def api_get_org_by_slug( request: Request, diff --git a/apps/api/src/routers/users.py b/apps/api/src/routers/users.py index 808f77b5..55365db2 100644 --- a/apps/api/src/routers/users.py +++ b/apps/api/src/routers/users.py @@ -1,6 +1,7 @@ from typing import Literal -from fastapi import APIRouter, Depends, Request +from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile from sqlmodel import Session +from src.services.orgs.orgs import get_org_join_mechanism from src.security.auth import get_current_user from src.core.events.database import get_db_session @@ -16,12 +17,14 @@ from src.db.users import ( from src.services.users.users import ( authorize_user_action, create_user, + create_user_with_invite, create_user_without_org, delete_user_by_id, get_user_session, read_user_by_id, read_user_by_uuid, update_user, + update_user_avatar, update_user_password, ) @@ -77,7 +80,48 @@ async def api_create_user_with_orgid( """ Create User with Org ID """ - return await create_user(request, db_session, current_user, user_object, org_id) + print(await get_org_join_mechanism(request, org_id, current_user, db_session)) + + # TODO(fix) : This is temporary, logic should be moved to service + if ( + await get_org_join_mechanism(request, org_id, current_user, db_session) + == "inviteOnly" + ): + raise HTTPException( + status_code=403, + detail="You need an invite to join this organization", + ) + else: + return await create_user(request, db_session, current_user, user_object, org_id) + + +@router.post("/{org_id}/invite/{invite_code}", response_model=UserRead, tags=["users"]) +async def api_create_user_with_orgid_and_invite( + *, + request: Request, + db_session: Session = Depends(get_db_session), + current_user: PublicUser = Depends(get_current_user), + user_object: UserCreate, + invite_code: str, + org_id: int, +) -> UserRead: + """ + Create User with Org ID and invite code + """ + + # TODO: This is temporary, logic should be moved to service + if ( + await get_org_join_mechanism(request, org_id, current_user, db_session) + == "inviteOnly" + ): + return await create_user_with_invite( + request, db_session, current_user, user_object, org_id, invite_code + ) + else: + raise HTTPException( + status_code=403, + detail="This organization does not require an invite code", + ) @router.post("/", response_model=UserRead, tags=["users"]) @@ -137,6 +181,20 @@ async def api_update_user( return await update_user(request, db_session, user_id, current_user, user_object) +@router.put("/update_avatar/{user_id}", response_model=UserRead, tags=["users"]) +async def api_update_avatar_user( + *, + request: Request, + db_session: Session = Depends(get_db_session), + current_user: PublicUser = Depends(get_current_user), + avatar_file: UploadFile | None = None, +) -> UserRead: + """ + Update User + """ + return await update_user_avatar(request, db_session, current_user, avatar_file) + + @router.put("/change_password/{user_id}", response_model=UserRead, tags=["users"]) async def api_update_user_password( *, diff --git a/apps/api/src/security/auth.py b/apps/api/src/security/auth.py index 4d6d290a..f90bf9d6 100644 --- a/apps/api/src/security/auth.py +++ b/apps/api/src/security/auth.py @@ -21,7 +21,9 @@ class Settings(BaseModel): authjwt_secret_key: str = "secret" if isDevModeEnabled() else SECRET_KEY authjwt_token_location = {"cookies", "headers"} authjwt_cookie_csrf_protect = False - authjwt_access_token_expires = False if isDevModeEnabled() else 28800 + authjwt_access_token_expires = ( + False if isDevModeEnabled() else timedelta(hours=8).total_seconds() + ) authjwt_cookie_samesite = "lax" authjwt_cookie_secure = True authjwt_cookie_domain = get_learnhouse_config().hosting_config.cookie_config.domain diff --git a/apps/api/src/security/rbac/rbac.py b/apps/api/src/security/rbac/rbac.py index 34098744..01f5a343 100644 --- a/apps/api/src/security/rbac/rbac.py +++ b/apps/api/src/security/rbac/rbac.py @@ -16,12 +16,11 @@ async def authorization_verify_if_element_is_public( element_uuid: str, action: Literal["read"], db_session: Session, -): +): element_nature = await check_element_type(element_uuid) # Verifies if the element is public - if element_nature == ("courses" or "collections") and action == "read": + if element_nature == ("courses") and action == "read": if element_nature == "courses": - print("looking for course") statement = select(Course).where( Course.public == True, Course.course_uuid == element_uuid ) @@ -29,20 +28,29 @@ async def authorization_verify_if_element_is_public( if course: return True else: - return False + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="User rights : You don't have the right to perform this action", + ) + + if element_nature == "collections" and action == "read": - if element_nature == "collections": statement = select(Collection).where( Collection.public == True, Collection.collection_uuid == element_uuid ) collection = db_session.exec(statement).first() - if collection: return True else: - return False + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="User rights : You don't have the right to perform this action", + ) else: - return False + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="User rights : You don't have the right to perform this action", + ) # Tested and working @@ -106,6 +114,34 @@ async def authorization_verify_based_on_roles( return False +async def authorization_verify_based_on_org_admin_status( + request: Request, + user_id: int, + action: Literal["read", "update", "delete", "create"], + element_uuid: str, + db_session: Session, +): + await check_element_type(element_uuid) + + # Get user roles bound to an organization and standard roles + statement = ( + select(Role) + .join(UserOrganization) + .where((UserOrganization.org_id == Role.org_id) | (Role.org_id == null())) + .where(UserOrganization.user_id == user_id) + ) + + user_roles_in_organization_and_standard_roles = db_session.exec(statement).all() + + # Find in roles list if there is a role that matches users action for this type of element + for role in user_roles_in_organization_and_standard_roles: + role = Role.from_orm(role) + if role.id == 1 or role.id == 2: + return True + else: + return False + + # Tested and working async def authorization_verify_based_on_roles_and_authorship( request: Request, diff --git a/apps/api/src/security/rbac/utils.py b/apps/api/src/security/rbac/utils.py index 51835ee9..0b2d707d 100644 --- a/apps/api/src/security/rbac/utils.py +++ b/apps/api/src/security/rbac/utils.py @@ -5,7 +5,6 @@ async def check_element_type(element_id): """ Check if the element is a course, a user, a house or a collection, by checking its prefix """ - print("element_id", element_id) if element_id.startswith("course_"): return "courses" elif element_id.startswith("user_"): diff --git a/apps/api/src/services/ai/ai.py b/apps/api/src/services/ai/ai.py index 9cd18064..5b8cc7b7 100644 --- a/apps/api/src/services/ai/ai.py +++ b/apps/api/src/services/ai/ai.py @@ -67,8 +67,11 @@ def ai_start_activity_chat_session( # Serialize Activity Content Blocks to a text comprehensible by the AI structured = structure_activity_content_by_type(content) + + isEmpty = structured == [] + ai_friendly_text = serialize_activity_text_to_ai_comprehensible_text( - structured, course, activity + structured, course, activity, isActivityEmpty=isEmpty ) # Get Activity Organization diff --git a/apps/api/src/services/ai/base.py b/apps/api/src/services/ai/base.py index 49929b8c..4bffa1f0 100644 --- a/apps/api/src/services/ai/base.py +++ b/apps/api/src/services/ai/base.py @@ -2,7 +2,6 @@ from typing import Optional from uuid import uuid4 from langchain.agents import AgentExecutor from langchain.text_splitter import CharacterTextSplitter -from langchain.embeddings.sentence_transformer import SentenceTransformerEmbeddings from langchain_community.vectorstores import Chroma from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent from langchain.prompts import MessagesPlaceholder @@ -45,7 +44,6 @@ def ask_ai( texts = text_splitter.split_documents(documents) embedding_models = { - "all-MiniLM-L6-v2": SentenceTransformerEmbeddings, "text-embedding-ada-002": OpenAIEmbeddings, } @@ -53,11 +51,11 @@ def ask_ai( if embedding_model_name in embedding_models: if embedding_model_name == "text-embedding-ada-002": - embedding_function = embedding_models[embedding_model_name](model=embedding_model_name, api_key=openai_api_key) - if embedding_model_name == "all-MiniLM-L6-v2": - embedding_function = embedding_models[embedding_model_name](model_name=embedding_model_name) + embedding_function = embedding_models[embedding_model_name]( + model=embedding_model_name, api_key=openai_api_key + ) else: - embedding_function = embedding_models[embedding_model_name](model_name=embedding_model_name) + raise Exception("Embedding model not found") # load it into Chroma and use it as a retriever db = Chroma.from_documents(texts, embedding_function) @@ -75,7 +73,10 @@ def ask_ai( memory_key = "history" memory = AgentTokenBufferMemory( - memory_key=memory_key, llm=llm, chat_memory=message_history, max_token_limit=1000 + memory_key=memory_key, + llm=llm, + chat_memory=message_history, + max_token_limit=1000, ) system_message = SystemMessage(content=(message_for_the_prompt)) diff --git a/apps/api/src/services/blocks/utils/upload_files.py b/apps/api/src/services/blocks/utils/upload_files.py index 76ebd1c7..32bd8024 100644 --- a/apps/api/src/services/blocks/utils/upload_files.py +++ b/apps/api/src/services/blocks/utils/upload_files.py @@ -50,7 +50,8 @@ async def upload_file_and_return_file_object( await upload_content( f"courses/{course_uuid}/activities/{activity_uuid}/dynamic/blocks/{type_of_block}/{block_id}", - org_uuid=org_uuid, + type_of_dir='orgs', + uuid=org_uuid, file_binary=file_binary, file_and_format=f"{file_id}.{file_format}", ) diff --git a/apps/api/src/services/courses/activities/activities.py b/apps/api/src/services/courses/activities/activities.py index 58e7de7d..81f1f501 100644 --- a/apps/api/src/services/courses/activities/activities.py +++ b/apps/api/src/services/courses/activities/activities.py @@ -1,5 +1,6 @@ from typing import Literal from sqlmodel import Session, select +from src.db.courses import Course from src.db.chapters import Chapter from src.security.rbac.rbac import ( authorization_verify_based_on_roles_and_authorship, @@ -25,7 +26,6 @@ async def create_activity( current_user: PublicUser | AnonymousUser, db_session: Session, ): - activity = Activity.from_orm(activity_object) # CHeck if org exists statement = select(Chapter).where(Chapter.id == activity_object.chapter_id) @@ -40,6 +40,9 @@ async def create_activity( # RBAC check await rbac_check(request, chapter.chapter_uuid, current_user, "create", db_session) + # Create Activity + activity = Activity(**activity_object.dict()) + activity.activity_uuid = str(f"activity_{uuid4()}") activity.creation_date = str(datetime.now()) activity.update_date = str(datetime.now()) @@ -96,8 +99,18 @@ async def get_activity( detail="Activity not found", ) + # Get course from that activity + statement = select(Course).where(Course.id == activity.course_id) + course = db_session.exec(statement).first() + + if not course: + raise HTTPException( + status_code=404, + detail="Course not found", + ) + # RBAC check - await rbac_check(request, activity.activity_uuid, current_user, "read", db_session) + await rbac_check(request, course.course_uuid, current_user, "read", db_session) activity = ActivityRead.from_orm(activity) @@ -223,7 +236,6 @@ async def rbac_check( res = await authorization_verify_if_element_is_public( request, course_uuid, action, db_session ) - print('res',res) return res else: res = await authorization_verify_based_on_roles_and_authorship( diff --git a/apps/api/src/services/courses/activities/uploads/pdfs.py b/apps/api/src/services/courses/activities/uploads/pdfs.py index 3d4f5ef6..48576048 100644 --- a/apps/api/src/services/courses/activities/uploads/pdfs.py +++ b/apps/api/src/services/courses/activities/uploads/pdfs.py @@ -8,6 +8,7 @@ async def upload_pdf(pdf_file, activity_uuid, org_uuid, course_uuid): try: await upload_content( f"courses/{course_uuid}/activities/{activity_uuid}/documentpdf", + "orgs", org_uuid, contents, f"documentpdf.{pdf_format}", diff --git a/apps/api/src/services/courses/activities/uploads/videos.py b/apps/api/src/services/courses/activities/uploads/videos.py index 2da6c35e..bebd5afe 100644 --- a/apps/api/src/services/courses/activities/uploads/videos.py +++ b/apps/api/src/services/courses/activities/uploads/videos.py @@ -9,6 +9,7 @@ async def upload_video(video_file, activity_uuid, org_uuid, course_uuid): try: await upload_content( f"courses/{course_uuid}/activities/{activity_uuid}/video", + 'orgs', org_uuid, contents, f"video.{video_format}", diff --git a/apps/api/src/services/courses/activities/utils.py b/apps/api/src/services/courses/activities/utils.py index 1b7b3913..c2904d18 100644 --- a/apps/api/src/services/courses/activities/utils.py +++ b/apps/api/src/services/courses/activities/utils.py @@ -4,6 +4,10 @@ from src.db.courses import CourseRead def structure_activity_content_by_type(activity): ### Get Headings, Texts, Callouts, Answers and Paragraphs from the activity as a big list of strings (text only) and return it + + if "content" not in activity or not activity["content"]: + return [] + content = activity["content"] headings = [] @@ -11,10 +15,12 @@ def structure_activity_content_by_type(activity): paragraphs = [] for item in content: - if 'content' in item: + if "content" in item: if item["type"] == "heading" and "text" in item["content"][0]: headings.append(item["content"][0]["text"]) - elif item["type"] in ["calloutInfo", "calloutWarning"] and all("text" in text_item for text_item in item["content"]): + elif item["type"] in ["calloutInfo", "calloutWarning"] and all( + "text" in text_item for text_item in item["content"] + ): callouts.append( "".join([text_item["text"] for text_item in item["content"]]) ) @@ -34,15 +40,29 @@ def structure_activity_content_by_type(activity): # Add Paragraphs data_array.append({"Paragraphs": paragraphs}) - print(data_array) - return data_array def serialize_activity_text_to_ai_comprehensible_text( - data_array, course: CourseRead, activity: ActivityRead + data_array, + course: CourseRead, + activity: ActivityRead, + isActivityEmpty: bool = False, ): - ### Serialize the text to a format that is comprehensible by the AI + + if isActivityEmpty: + text = ( + "Use this as a context " + + 'This is a course about "' + + course.name + + '". ' + + 'This is a lecture about "' + + activity.name + + '". ' + + "There is no content yet in this lecture." + ) + + return text # Serialize Headings serialized_headings = "" @@ -51,7 +71,6 @@ def serialize_activity_text_to_ai_comprehensible_text( # Serialize Callouts serialized_callouts = "" - for callout in data_array[1]["Callouts"]: serialized_callouts += callout + " " diff --git a/apps/api/src/services/courses/chapters.py b/apps/api/src/services/courses/chapters.py index 5d5b78ca..1e5895b2 100644 --- a/apps/api/src/services/courses/chapters.py +++ b/apps/api/src/services/courses/chapters.py @@ -112,8 +112,17 @@ async def get_chapter( status_code=status.HTTP_409_CONFLICT, detail="Chapter does not exist" ) + # get COurse + statement = select(Course).where(Course.id == chapter.course_id) + course = db_session.exec(statement).first() + + if not course: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Course does not exist" + ) + # RBAC check - await rbac_check(request, chapter.chapter_uuid, current_user, "read", db_session) + await rbac_check(request, course.course_uuid, current_user, "read", db_session) # Get activities for this chapter statement = ( @@ -208,7 +217,7 @@ async def get_course_chapters( page: int = 1, limit: int = 10, ) -> List[ChapterRead]: - + statement = select(Course).where(Course.id == course_id) course = db_session.exec(statement).first() @@ -225,7 +234,7 @@ async def get_course_chapters( chapters = [ChapterRead(**chapter.dict(), activities=[]) for chapter in chapters] # RBAC check - await rbac_check(request, course.course_uuid, current_user, "read", db_session) + await rbac_check(request, course.course_uuid, current_user, "read", db_session) # type: ignore # Get activities for each chapter for chapter in chapters: @@ -473,12 +482,15 @@ async def reorder_chapters_and_activities( db_session.delete(chapter_activity) db_session.commit() - # If links do not exist, create them chapter_activity_map = {} for chapter_order in chapters_order.chapter_order_by_ids: for activity_order in chapter_order.activities_order_by_ids: - if activity_order.activity_id in chapter_activity_map and chapter_activity_map[activity_order.activity_id] != chapter_order.chapter_id: + if ( + activity_order.activity_id in chapter_activity_map + and chapter_activity_map[activity_order.activity_id] + != chapter_order.chapter_id + ): continue statement = ( @@ -547,7 +559,7 @@ async def rbac_check( res = await authorization_verify_if_element_is_public( request, course_uuid, action, db_session ) - print('res',res) + print("res", res) return res else: res = await authorization_verify_based_on_roles_and_authorship( diff --git a/apps/api/src/services/courses/collections.py b/apps/api/src/services/courses/collections.py index e04e5c3d..8ee257e0 100644 --- a/apps/api/src/services/courses/collections.py +++ b/apps/api/src/services/courses/collections.py @@ -26,7 +26,10 @@ from fastapi import HTTPException, status, Request async def get_collection( - request: Request, collection_uuid: str, current_user: PublicUser, db_session: Session + request: Request, + collection_uuid: str, + current_user: PublicUser, + db_session: Session, ) -> CollectionRead: statement = select(Collection).where(Collection.collection_uuid == collection_uuid) collection = db_session.exec(statement).first() @@ -42,11 +45,23 @@ async def get_collection( ) # get courses in collection - statement = ( + statement_all = ( select(Course) .join(CollectionCourse, Course.id == CollectionCourse.course_id) .distinct(Course.id) ) + + statement_public = ( + select(Course) + .join(CollectionCourse, Course.id == CollectionCourse.course_id) + .where(CollectionCourse.org_id == collection.org_id, Course.public == True) + ) + + if current_user.id == 0: + statement = statement_public + else: + statement = statement_all + courses = db_session.exec(statement).all() collection = CollectionRead(**collection.dict(), courses=courses) @@ -180,7 +195,10 @@ async def update_collection( async def delete_collection( - request: Request, collection_uuid: str, current_user: PublicUser, db_session: Session + request: Request, + collection_uuid: str, + current_user: PublicUser, + db_session: Session, ): statement = select(Collection).where(Collection.collection_uuid == collection_uuid) collection = db_session.exec(statement).first() @@ -216,23 +234,40 @@ async def get_collections( page: int = 1, limit: int = 10, ) -> List[CollectionRead]: - # RBAC check - await rbac_check(request, "collection_x", current_user, "read", db_session) - statement = ( + statement_public = select(Collection).where( + Collection.org_id == org_id, Collection.public == True + ) + statement_all = ( select(Collection).where(Collection.org_id == org_id).distinct(Collection.id) ) + + if current_user.id == 0: + statement = statement_public + else: + statement = statement_all + collections = db_session.exec(statement).all() - - collections_with_courses = [] + for collection in collections: - statement = ( + statement_all = ( select(Course) .join(CollectionCourse, Course.id == CollectionCourse.course_id) .distinct(Course.id) ) + statement_public = ( + select(Course) + .join(CollectionCourse, Course.id == CollectionCourse.course_id) + .where(CollectionCourse.org_id == org_id, Course.public == True) + ) + if current_user.id == 0: + statement = statement_public + else: + # RBAC check + statement = statement_all + courses = db_session.exec(statement).all() collection = CollectionRead(**collection.dict(), courses=courses) @@ -256,8 +291,11 @@ async def rbac_check( res = await authorization_verify_if_element_is_public( request, collection_uuid, action, db_session ) - print('res',res) - return res + if res == False: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="User rights : You are not allowed to read this collection", + ) else: res = await authorization_verify_based_on_roles_and_authorship( request, current_user.id, action, collection_uuid, db_session @@ -276,4 +314,3 @@ async def rbac_check( ## πŸ”’ RBAC Utils ## - diff --git a/apps/api/src/services/courses/courses.py b/apps/api/src/services/courses/courses.py index cd667db7..cc007a56 100644 --- a/apps/api/src/services/courses/courses.py +++ b/apps/api/src/services/courses/courses.py @@ -146,6 +146,9 @@ async def create_course( ) course.thumbnail_image = name_in_disk + else: + course.thumbnail_image = "" + # Insert course db_session.add(course) db_session.commit() diff --git a/apps/api/src/services/courses/thumbnails.py b/apps/api/src/services/courses/thumbnails.py index 46b59172..b00a0049 100644 --- a/apps/api/src/services/courses/thumbnails.py +++ b/apps/api/src/services/courses/thumbnails.py @@ -1,13 +1,13 @@ - from src.services.utils.upload_content import upload_content -async def upload_thumbnail(thumbnail_file, name_in_disk, org_id, course_id): +async def upload_thumbnail(thumbnail_file, name_in_disk, org_uuid, course_id): contents = thumbnail_file.file.read() try: await upload_content( f"courses/{course_id}/thumbnails", - org_id, + "orgs", + org_uuid, contents, f"{name_in_disk}", ) diff --git a/apps/api/src/services/orgs/invites.py b/apps/api/src/services/orgs/invites.py new file mode 100644 index 00000000..5b2ab948 --- /dev/null +++ b/apps/api/src/services/orgs/invites.py @@ -0,0 +1,253 @@ +import json +import random +import string +import uuid +import redis +from datetime import datetime, timedelta +from sqlmodel import Session, select +from config.config import get_learnhouse_config +from src.services.orgs.orgs import rbac_check +from src.db.users import AnonymousUser, PublicUser +from src.db.organizations import ( + Organization, +) +from fastapi import HTTPException, Request + + +async def create_invite_code( + request: Request, + org_id: int, + current_user: PublicUser | AnonymousUser, + db_session: Session, +): + # Redis init + LH_CONFIG = get_learnhouse_config() + redis_conn_string = LH_CONFIG.redis_config.redis_connection_string + + if not redis_conn_string: + raise HTTPException( + status_code=500, + detail="Redis connection string not found", + ) + + statement = select(Organization).where(Organization.id == org_id) + result = db_session.exec(statement) + + org = result.first() + + if not org: + raise HTTPException( + status_code=404, + detail="Organization not found", + ) + + # RBAC check + await rbac_check(request, org.org_uuid, current_user, "update", db_session) + + # Connect to Redis + r = redis.Redis.from_url(redis_conn_string) + + if not r: + raise HTTPException( + status_code=500, + detail="Could not connect to Redis", + ) + + # Check if this org has more than 6 invite codes + invite_codes = r.keys(f"*:org:{org.org_uuid}:code:*") + + if len(invite_codes) >= 6: + raise HTTPException( + status_code=400, + detail="Organization has reached the maximum number of invite codes", + ) + + # Generate invite code + def generate_code(length=5): + letters_and_digits = string.ascii_letters + string.digits + return "".join(random.choice(letters_and_digits) for _ in range(length)) + + generated_invite_code = generate_code() + invite_code_uuid = f"org_invite_code_{uuid.uuid4()}" + + # time to live in days to seconds + ttl = int(timedelta(days=365).total_seconds()) + + inviteCodeObject = { + "invite_code": generated_invite_code, + "invite_code_uuid": invite_code_uuid, + "invite_code_expires": ttl, + "invite_code_type": "signup", + "created_at": datetime.now().isoformat(), + "created_by": current_user.user_uuid, + } + + r.set( + f"{invite_code_uuid}:org:{org.org_uuid}:code:{generated_invite_code}", + json.dumps(inviteCodeObject), + ex=ttl, + ) + + return inviteCodeObject + + +async def get_invite_codes( + request: Request, + org_id: int, + current_user: PublicUser | AnonymousUser, + db_session: Session, +): + # Redis init + LH_CONFIG = get_learnhouse_config() + redis_conn_string = LH_CONFIG.redis_config.redis_connection_string + + if not redis_conn_string: + raise HTTPException( + status_code=500, + detail="Redis connection string not found", + ) + + statement = select(Organization).where(Organization.id == org_id) + result = db_session.exec(statement) + + org = result.first() + + if not org: + raise HTTPException( + status_code=404, + detail="Organization not found", + ) + + # RBAC check + await rbac_check(request, org.org_uuid, current_user, "update", db_session) + + # Connect to Redis + r = redis.Redis.from_url(redis_conn_string) + + if not r: + raise HTTPException( + status_code=500, + detail="Could not connect to Redis", + ) + + # Get invite codes + invite_codes = r.keys(f"org_invite_code_*:org:{org.org_uuid}:code:*") + + invite_codes_list = [] + + for invite_code in invite_codes: + invite_code = r.get(invite_code) + invite_code = json.loads(invite_code) + invite_codes_list.append(invite_code) + + return invite_codes_list + +async def get_invite_code( + request: Request, + org_id: int, + invite_code: str, + current_user: PublicUser | AnonymousUser, + db_session: Session, +): + # Redis init + LH_CONFIG = get_learnhouse_config() + redis_conn_string = LH_CONFIG.redis_config.redis_connection_string + + if not redis_conn_string: + raise HTTPException( + status_code=500, + detail="Redis connection string not found", + ) + + statement = select(Organization).where(Organization.id == org_id) + result = db_session.exec(statement) + + org = result.first() + + if not org: + raise HTTPException( + status_code=404, + detail="Organization not found", + ) + + # RBAC check + # await rbac_check(request, org.org_uuid, current_user, "update", db_session) + + # Connect to Redis + r = redis.Redis.from_url(redis_conn_string) + + if not r: + raise HTTPException( + status_code=500, + detail="Could not connect to Redis", + ) + + + # Get invite code + invite_code = r.keys(f"org_invite_code_*:org:{org.org_uuid}:code:{invite_code}") # type: ignore + + if not invite_code: + raise HTTPException( + status_code=404, + detail="Invite code not found", + ) + + invite_code = r.get(invite_code[0]) # type: ignore + invite_code = json.loads(invite_code) + + return invite_code + + + +async def delete_invite_code( + request: Request, + org_id: int, + invite_code_uuid: str, + current_user: PublicUser | AnonymousUser, + db_session: Session, +): + # Redis init + LH_CONFIG = get_learnhouse_config() + redis_conn_string = LH_CONFIG.redis_config.redis_connection_string + + if not redis_conn_string: + raise HTTPException( + status_code=500, + detail="Redis connection string not found", + ) + + statement = select(Organization).where(Organization.id == org_id) + result = db_session.exec(statement) + + org = result.first() + + if not org: + raise HTTPException( + status_code=404, + detail="Organization not found", + ) + + # RBAC check + await rbac_check(request, org.org_uuid, current_user, "update", db_session) + + # Connect to Redis + r = redis.Redis.from_url(redis_conn_string) + + if not r: + raise HTTPException( + status_code=500, + detail="Could not connect to Redis", + ) + + # Delete invite code + keys = r.keys(f"{invite_code_uuid}:org:{org.org_uuid}:code:*") + if keys: + r.delete(*keys) + + if not keys: + raise HTTPException( + status_code=404, + detail="Invite code not found", + ) + + return keys diff --git a/apps/api/src/services/orgs/logos.py b/apps/api/src/services/orgs/logos.py index 09fe048a..9bb173d3 100644 --- a/apps/api/src/services/orgs/logos.py +++ b/apps/api/src/services/orgs/logos.py @@ -9,6 +9,7 @@ async def upload_org_logo(logo_file, org_uuid): await upload_content( "logos", + "orgs", org_uuid, contents, name_in_disk, diff --git a/apps/api/src/services/orgs/orgs.py b/apps/api/src/services/orgs/orgs.py index 3ef6b304..262adb93 100644 --- a/apps/api/src/services/orgs/orgs.py +++ b/apps/api/src/services/orgs/orgs.py @@ -15,7 +15,7 @@ from src.db.organization_config import ( OrganizationConfigBase, ) from src.security.rbac.rbac import ( - authorization_verify_based_on_roles_and_authorship, + authorization_verify_based_on_org_admin_status, authorization_verify_if_user_is_anon, ) from src.db.users import AnonymousUser, PublicUser @@ -169,7 +169,7 @@ async def create_org( limits_enabled=False, max_asks=0, ), - embeddings="all-MiniLM-L6-v2", + embeddings="text-embedding-ada-002", ai_model="gpt-3.5-turbo", features=AIEnabledFeatures( editor=False, @@ -438,12 +438,106 @@ async def get_orgs_by_user( return orgs +# Config related +async def update_org_signup_mechanism( + request: Request, + signup_mechanism: Literal["open", "inviteOnly"], + org_id: int, + current_user: PublicUser | AnonymousUser, + db_session: Session, +): + statement = select(Organization).where(Organization.id == org_id) + result = db_session.exec(statement) + + org = result.first() + + if not org: + raise HTTPException( + status_code=404, + detail="Organization not found", + ) + + # RBAC check + await rbac_check(request, org.org_uuid, current_user, "update", db_session) + + # Get org config + statement = select(OrganizationConfig).where(OrganizationConfig.org_id == org.id) + result = db_session.exec(statement) + + org_config = result.first() + + if org_config is None: + logging.error(f"Organization {org_id} has no config") + raise HTTPException( + status_code=404, + detail="Organization config not found", + ) + + updated_config = org_config.config + + # Update config + updated_config = OrganizationConfigBase(**updated_config) + updated_config.GeneralConfig.users.signup_mechanism = signup_mechanism + + # Update the database + org_config.config = json.loads(updated_config.json()) + org_config.update_date = str(datetime.now()) + + db_session.add(org_config) + db_session.commit() + db_session.refresh(org_config) + + return {"detail": "Signup mechanism updated"} + + +async def get_org_join_mechanism( + request: Request, + org_id: int, + current_user: PublicUser | AnonymousUser, + db_session: Session, +): + statement = select(Organization).where(Organization.id == org_id) + result = db_session.exec(statement) + + org = result.first() + + if not org: + raise HTTPException( + status_code=404, + detail="Organization not found", + ) + + # RBAC check + await rbac_check(request, org.org_uuid, current_user, "read", db_session) + + # Get org config + statement = select(OrganizationConfig).where(OrganizationConfig.org_id == org.id) + result = db_session.exec(statement) + + org_config = result.first() + + if org_config is None: + logging.error(f"Organization {org_id} has no config") + raise HTTPException( + status_code=404, + detail="Organization config not found", + ) + + config = org_config.config + + # Get the signup mechanism + config = OrganizationConfigBase(**config) + signup_mechanism = config.GeneralConfig.users.signup_mechanism + + return signup_mechanism + + ## πŸ”’ RBAC Utils ## async def rbac_check( request: Request, - org_id: str, + org_uuid: str, current_user: PublicUser | AnonymousUser, action: Literal["create", "read", "update", "delete"], db_session: Session, @@ -453,11 +547,25 @@ async def rbac_check( return True else: - await authorization_verify_if_user_is_anon(current_user.id) + isUserAnon = await authorization_verify_if_user_is_anon(current_user.id) - await authorization_verify_based_on_roles_and_authorship( - request, current_user.id, action, org_id, db_session + isAllowedOnOrgAdminStatus = ( + await authorization_verify_based_on_org_admin_status( + request, current_user.id, action, org_uuid, db_session + ) ) + if isUserAnon: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="You should be logged in to be able to achieve this action", + ) + + if not isAllowedOnOrgAdminStatus: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="User rights (admin status) : You don't have the right to perform this action", + ) + ## πŸ”’ RBAC Utils ## diff --git a/apps/api/src/services/orgs/users.py b/apps/api/src/services/orgs/users.py new file mode 100644 index 00000000..28422fb5 --- /dev/null +++ b/apps/api/src/services/orgs/users.py @@ -0,0 +1,410 @@ +from datetime import datetime, timedelta +import json +import logging + +import redis +from fastapi import HTTPException, Request +from sqlmodel import Session, select +from config.config import get_learnhouse_config +from src.services.orgs.orgs import rbac_check +from src.db.roles import Role, RoleRead +from src.db.users import AnonymousUser, PublicUser, User, UserRead +from src.db.user_organizations import UserOrganization +from src.db.organizations import ( + Organization, + OrganizationUser, +) + + +async def get_organization_users( + request: Request, + org_id: str, + db_session: Session, + current_user: PublicUser | AnonymousUser, +) -> list[OrganizationUser]: + statement = select(Organization).where(Organization.id == org_id) + result = db_session.exec(statement) + + org = result.first() + + if not org: + raise HTTPException( + status_code=404, + detail="Organization not found", + ) + + # RBAC check + await rbac_check(request, org.org_uuid, current_user, "read", db_session) + + statement = ( + select(User) + .join(UserOrganization) + .join(Organization) + .where(Organization.id == org_id) + ) + users = db_session.exec(statement) + users = users.all() + + org_users_list = [] + + for user in users: + statement = select(UserOrganization).where( + UserOrganization.user_id == user.id, UserOrganization.org_id == org_id + ) + result = db_session.exec(statement) + user_org = result.first() + + if not user_org: + logging.error(f"User {user.id} not found") + + # skip this user + continue + + statement = select(Role).where(Role.id == user_org.role_id) + result = db_session.exec(statement) + + role = result.first() + + if not role: + logging.error(f"Role {user_org.role_id} not found") + + # skip this user + continue + + statement = select(User).where(User.id == user_org.user_id) + result = db_session.exec(statement) + + user = result.first() + + if not user: + logging.error(f"User {user_org.user_id} not found") + + # skip this user + continue + + user = UserRead.from_orm(user) + role = RoleRead.from_orm(role) + + org_user = OrganizationUser( + user=user, + role=role, + ) + + org_users_list.append(org_user) + + return org_users_list + + +async def remove_user_from_org( + request: Request, + org_id: int, + user_id: int, + db_session: Session, + current_user: PublicUser | AnonymousUser, +): + statement = select(Organization).where(Organization.id == org_id) + result = db_session.exec(statement) + + org = result.first() + + if not org: + raise HTTPException( + status_code=404, + detail="Organization not found", + ) + + # RBAC check + await rbac_check(request, org.org_uuid, current_user, "delete", db_session) + + statement = select(UserOrganization).where( + UserOrganization.user_id == user_id, UserOrganization.org_id == org.id + ) + result = db_session.exec(statement) + + user_org = result.first() + + if not user_org: + raise HTTPException( + status_code=404, + detail="User not found", + ) + + # Check if user is the last admin + statement = select(UserOrganization).where( + UserOrganization.org_id == org.id, UserOrganization.role_id == 1 + ) + result = db_session.exec(statement) + admins = result.all() + + if len(admins) == 1 and admins[0].user_id == user_id: + raise HTTPException( + status_code=400, + detail="You can't remove the last admin of the organization", + ) + + db_session.delete(user_org) + db_session.commit() + + return {"detail": "User removed from org"} + + +async def update_user_role( + request: Request, + org_id: str, + user_id: str, + role_uuid: str, + db_session: Session, + current_user: PublicUser | AnonymousUser, +): + # find role + statement = select(Role).where(Role.role_uuid == role_uuid) + result = db_session.exec(statement) + + role = result.first() + + if not role: + raise HTTPException( + status_code=404, + detail="Role not found", + ) + + role_id = role.id + + statement = select(Organization).where(Organization.id == org_id) + result = db_session.exec(statement) + + org = result.first() + + if not org: + raise HTTPException( + status_code=404, + detail="Organization not found", + ) + + # RBAC check + await rbac_check(request, org.org_uuid, current_user, "update", db_session) + + # Check if user is the last admin and if the new role is not admin + statement = select(UserOrganization).where( + UserOrganization.org_id == org.id, UserOrganization.role_id == 1 + ) + result = db_session.exec(statement) + admins = result.all() + + if not admins: + raise HTTPException( + status_code=400, + detail="There is no admin in the organization", + ) + + if ( + len(admins) == 1 + and int(admins[0].user_id) == int(user_id) + and str(role_uuid) != "role_global_admin" + ): + raise HTTPException( + status_code=400, + detail="Organization must have at least one admin", + ) + + statement = select(UserOrganization).where( + UserOrganization.user_id == user_id, UserOrganization.org_id == org.id + ) + result = db_session.exec(statement) + + user_org = result.first() + + if not user_org: + raise HTTPException( + status_code=404, + detail="User not found", + ) + + if role_id is not None: + user_org.role_id = role_id + + db_session.add(user_org) + db_session.commit() + db_session.refresh(user_org) + + return {"detail": "User role updated"} + + +async def invite_batch_users( + request: Request, + org_id: int, + emails: str, + db_session: Session, + current_user: PublicUser | AnonymousUser, +): + # Redis init + LH_CONFIG = get_learnhouse_config() + redis_conn_string = LH_CONFIG.redis_config.redis_connection_string + + if not redis_conn_string: + raise HTTPException( + status_code=500, + detail="Redis connection string not found", + ) + + statement = select(Organization).where(Organization.id == org_id) + result = db_session.exec(statement) + + org = result.first() + + if not org: + raise HTTPException( + status_code=404, + detail="Organization not found", + ) + + # RBAC check + await rbac_check(request, org.org_uuid, current_user, "create", db_session) + + # Connect to Redis + r = redis.Redis.from_url(redis_conn_string) + + if not r: + raise HTTPException( + status_code=500, + detail="Could not connect to Redis", + ) + + invite_list = emails.split(",") + + # invitations expire after 30 days + ttl = int(timedelta(days=365).total_seconds()) + + for email in invite_list: + email = email.strip() + + # Check if user is already invited + invited_user = r.get(f"invited_user:{email}:org:{org.org_uuid}") + + if invited_user: + logging.error(f"User {email} already invited") + # skip this user + continue + + invited_user_object = { + "email": email, + "org_id": org.id, + "pending": True, + "email_sent": False, + "expires": ttl, + "created_at": datetime.now().isoformat(), + "created_by": current_user.user_uuid, + } + + invited_user = r.set( + f"invited_user:{email}:org:{org.org_uuid}", + json.dumps(invited_user_object), + ex=ttl, + ) + + return {"detail": "Users invited"} + + +async def get_list_of_invited_users( + request: Request, + org_id: int, + db_session: Session, + current_user: PublicUser | AnonymousUser, +): + # Redis init + LH_CONFIG = get_learnhouse_config() + redis_conn_string = LH_CONFIG.redis_config.redis_connection_string + + if not redis_conn_string: + raise HTTPException( + status_code=500, + detail="Redis connection string not found", + ) + + statement = select(Organization).where(Organization.id == org_id) + result = db_session.exec(statement) + + org = result.first() + + if not org: + raise HTTPException( + status_code=404, + detail="Organization not found", + ) + + # RBAC check + await rbac_check(request, org.org_uuid, current_user, "read", db_session) + + # Connect to Redis + r = redis.Redis.from_url(redis_conn_string) + + if not r: + raise HTTPException( + status_code=500, + detail="Could not connect to Redis", + ) + + invited_users = r.keys(f"invited_user:*:org:{org.org_uuid}") + + invited_users_list = [] + + for user in invited_users: + invited_user = r.get(user) + if invited_user: + invited_user = json.loads(invited_user.decode("utf-8")) + invited_users_list.append(invited_user) + + return invited_users_list + + +async def remove_invited_user( + request: Request, + org_id: int, + email: str, + db_session: Session, + current_user: PublicUser | AnonymousUser, +): + # Redis init + LH_CONFIG = get_learnhouse_config() + redis_conn_string = LH_CONFIG.redis_config.redis_connection_string + + if not redis_conn_string: + raise HTTPException( + status_code=500, + detail="Redis connection string not found", + ) + + statement = select(Organization).where(Organization.id == org_id) + result = db_session.exec(statement) + + org = result.first() + + if not org: + raise HTTPException( + status_code=404, + detail="Organization not found", + ) + + # RBAC check + await rbac_check(request, org.org_uuid, current_user, "delete", db_session) + + # Connect to Redis + r = redis.Redis.from_url(redis_conn_string) + + if not r: + raise HTTPException( + status_code=500, + detail="Could not connect to Redis", + ) + + invited_user = r.get(f"invited_user:{email}:org:{org.org_uuid}") + + if not invited_user: + raise HTTPException( + status_code=404, + detail="User not found", + ) + + r.delete(f"invited_user:{email}:org:{org.org_uuid}") + + return {"detail": "User removed"} diff --git a/apps/api/src/services/trail/trail.py b/apps/api/src/services/trail/trail.py index 0b1ee454..e3485b9d 100644 --- a/apps/api/src/services/trail/trail.py +++ b/apps/api/src/services/trail/trail.py @@ -17,7 +17,9 @@ async def create_user_trail( trail_object: TrailCreate, db_session: Session, ) -> Trail: - statement = select(Trail).where(Trail.org_id == trail_object.org_id, Trail.user_id == user.id) + statement = select(Trail).where( + Trail.org_id == trail_object.org_id, Trail.user_id == user.id + ) trail = db_session.exec(statement).first() if trail: @@ -124,7 +126,7 @@ async def check_trail_presence( async def get_user_trail_with_orgid( request: Request, user: PublicUser | AnonymousUser, org_id: int, db_session: Session ) -> TrailRead: - + if isinstance(user, AnonymousUser): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -151,7 +153,7 @@ async def get_user_trail_with_orgid( for trail_run in trail_runs: statement = select(Course).where(Course.id == trail_run.course_id) course = db_session.exec(statement).first() - trail_run.course = course + trail_run.course = course # Add number of activities (steps) in a course statement = select(ChapterActivity).where( @@ -213,7 +215,7 @@ async def add_activity_to_trail( ) statement = select(TrailRun).where( - TrailRun.trail_id == trail.id, TrailRun.course_id == course.id + TrailRun.trail_id == trail.id, TrailRun.course_id == course.id, TrailRun.user_id == user.id ) trailrun = db_session.exec(statement).first() @@ -231,7 +233,7 @@ async def add_activity_to_trail( db_session.refresh(trailrun) statement = select(TrailStep).where( - TrailStep.trailrun_id == trailrun.id, TrailStep.activity_id == activity.id + TrailStep.trailrun_id == trailrun.id, TrailStep.activity_id == activity.id, TrailStep.user_id == user.id ) trailstep = db_session.exec(statement).first() @@ -253,7 +255,7 @@ async def add_activity_to_trail( db_session.commit() db_session.refresh(trailstep) - statement = select(TrailRun).where(TrailRun.trail_id == trail.id) + statement = select(TrailRun).where(TrailRun.trail_id == trail.id , TrailRun.user_id == user.id) trail_runs = db_session.exec(statement).all() trail_runs = [ @@ -262,7 +264,7 @@ async def add_activity_to_trail( ] for trail_run in trail_runs: - statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id) + statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id, TrailStep.user_id == user.id) trail_steps = db_session.exec(statement).all() trail_steps = [TrailStep(**trail_step.__dict__) for trail_step in trail_steps] @@ -296,7 +298,9 @@ async def add_course_to_trail( ) # check if run already exists - statement = select(TrailRun).where(TrailRun.course_id == course.id) + statement = select(TrailRun).where( + TrailRun.course_id == course.id, TrailRun.user_id == user.id + ) trailrun = db_session.exec(statement).first() if trailrun: @@ -315,7 +319,7 @@ async def add_course_to_trail( ) statement = select(TrailRun).where( - TrailRun.trail_id == trail.id, TrailRun.course_id == course.id + TrailRun.trail_id == trail.id, TrailRun.course_id == course.id, TrailRun.user_id == user.id ) trail_run = db_session.exec(statement).first() @@ -332,7 +336,7 @@ async def add_course_to_trail( db_session.commit() db_session.refresh(trail_run) - statement = select(TrailRun).where(TrailRun.trail_id == trail.id) + statement = select(TrailRun).where(TrailRun.trail_id == trail.id, TrailRun.user_id == user.id) trail_runs = db_session.exec(statement).all() trail_runs = [ @@ -341,7 +345,7 @@ async def add_course_to_trail( ] for trail_run in trail_runs: - statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id) + statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id , TrailStep.user_id == user.id) trail_steps = db_session.exec(statement).all() trail_steps = [TrailStep(**trail_step.__dict__) for trail_step in trail_steps] @@ -385,7 +389,7 @@ async def remove_course_from_trail( ) statement = select(TrailRun).where( - TrailRun.trail_id == trail.id, TrailRun.course_id == course.id + TrailRun.trail_id == trail.id, TrailRun.course_id == course.id, TrailRun.user_id == user.id ) trail_run = db_session.exec(statement).first() @@ -394,14 +398,14 @@ async def remove_course_from_trail( db_session.commit() # Delete all trail steps for this course - statement = select(TrailStep).where(TrailStep.course_id == course.id) + statement = select(TrailStep).where(TrailStep.course_id == course.id, TrailStep.user_id == user.id) trail_steps = db_session.exec(statement).all() for trail_step in trail_steps: db_session.delete(trail_step) db_session.commit() - statement = select(TrailRun).where(TrailRun.trail_id == trail.id) + statement = select(TrailRun).where(TrailRun.trail_id == trail.id, TrailRun.user_id == user.id) trail_runs = db_session.exec(statement).all() trail_runs = [ @@ -410,7 +414,7 @@ async def remove_course_from_trail( ] for trail_run in trail_runs: - statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id) + statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id, TrailStep.user_id == user.id) trail_steps = db_session.exec(statement).all() trail_steps = [TrailStep(**trail_step.__dict__) for trail_step in trail_steps] diff --git a/apps/api/src/services/users/avatars.py b/apps/api/src/services/users/avatars.py new file mode 100644 index 00000000..53a841ba --- /dev/null +++ b/apps/api/src/services/users/avatars.py @@ -0,0 +1,16 @@ +from src.services.utils.upload_content import upload_content + + +async def upload_avatar(avatar_file, name_in_disk, user_uuid): + contents = avatar_file.file.read() + try: + await upload_content( + "avatars", + "users", + user_uuid, + contents, + f"{name_in_disk}", + ) + + except Exception: + return {"message": "There was an error uploading the file"} diff --git a/apps/api/src/services/users/users.py b/apps/api/src/services/users/users.py index bce3301b..8d208b32 100644 --- a/apps/api/src/services/users/users.py +++ b/apps/api/src/services/users/users.py @@ -1,13 +1,15 @@ from datetime import datetime from typing import Literal from uuid import uuid4 -from fastapi import HTTPException, Request, status +from fastapi import HTTPException, Request, UploadFile, status from sqlmodel import Session, select +from src.services.orgs.invites import get_invite_code +from src.services.users.avatars import upload_avatar from src.db.roles import Role, RoleRead from src.security.rbac.rbac import ( authorization_verify_based_on_roles_and_authorship, authorization_verify_if_user_is_anon, -) +) from src.db.organizations import Organization, OrganizationRead from src.db.users import ( AnonymousUser, @@ -102,6 +104,27 @@ async def create_user( return user +async def create_user_with_invite( + request: Request, + db_session: Session, + current_user: PublicUser | AnonymousUser, + user_object: UserCreate, + org_id: int, + invite_code: str, +): + + # Check if invite code exists + isInviteCodeCorrect = await get_invite_code(request, org_id, invite_code, current_user, db_session) + + if not isInviteCodeCorrect: + raise HTTPException( + status_code=400, + detail="Invite code is incorrect", + ) + + user = await create_user(request, db_session, current_user, user_object, org_id) + + return user async def create_user_without_org( request: Request, @@ -195,6 +218,49 @@ async def update_user( return user +async def update_user_avatar( + request: Request, + db_session: Session, + current_user: PublicUser | AnonymousUser, + avatar_file: UploadFile | None = None, +): + # Get user + statement = select(User).where(User.id == current_user.id) + user = db_session.exec(statement).first() + + if not user: + raise HTTPException( + status_code=400, + detail="User does not exist", + ) + + # RBAC check + await rbac_check(request, current_user, "update", user.user_uuid, db_session) + + # Upload thumbnail + if avatar_file and avatar_file.filename: + name_in_disk = f"{user.user_uuid}_avatar_{uuid4()}.{avatar_file.filename.split('.')[-1]}" + await upload_avatar(avatar_file, name_in_disk, user.user_uuid) + + # Update course + if name_in_disk: + user.avatar_image = name_in_disk + else: + raise HTTPException( + status_code=500, + detail="Issue with Avatar upload", + ) + + # Update user in database + db_session.add(user) + db_session.commit() + db_session.refresh(user) + + user = UserRead.from_orm(user) + + return user + + async def update_user_password( request: Request, db_session: Session, diff --git a/apps/api/src/services/utils/upload_content.py b/apps/api/src/services/utils/upload_content.py index 33200231..04448346 100644 --- a/apps/api/src/services/utils/upload_content.py +++ b/apps/api/src/services/utils/upload_content.py @@ -1,3 +1,4 @@ +from typing import Literal import boto3 from botocore.exceptions import ClientError import os @@ -6,7 +7,11 @@ from config.config import get_learnhouse_config async def upload_content( - directory: str, org_uuid: str, file_binary: bytes, file_and_format: str + directory: str, + type_of_dir: Literal["orgs", "users"], + uuid: str, # org_uuid or user_uuid + file_binary: bytes, + file_and_format: str, ): # Get Learnhouse Config learnhouse_config = get_learnhouse_config() @@ -16,12 +21,12 @@ async def upload_content( if content_delivery == "filesystem": # create folder for activity - if not os.path.exists(f"content/{org_uuid}/{directory}"): + if not os.path.exists(f"content/{type_of_dir}/{uuid}/{directory}"): # create folder for activity - os.makedirs(f"content/{org_uuid}/{directory}") + os.makedirs(f"content/{type_of_dir}/{uuid}/{directory}") # upload file to server with open( - f"content/{org_uuid}/{directory}/{file_and_format}", + f"content/{type_of_dir}/{uuid}/{directory}/{file_and_format}", "wb", ) as f: f.write(file_binary) @@ -37,13 +42,13 @@ async def upload_content( ) # Create folder for activity - if not os.path.exists(f"content/{org_uuid}/{directory}"): + if not os.path.exists(f"content/{type_of_dir}/{uuid}/{directory}"): # create folder for activity - os.makedirs(f"content/{org_uuid}/{directory}") + os.makedirs(f"content/{type_of_dir}/{uuid}/{directory}") # Upload file to server with open( - f"content/{org_uuid}/{directory}/{file_and_format}", + f"content/{type_of_dir}/{uuid}/{directory}/{file_and_format}", "wb", ) as f: f.write(file_binary) @@ -52,9 +57,9 @@ async def upload_content( print("Uploading to s3 using boto3...") try: s3.upload_file( - f"content/{org_uuid}/{directory}/{file_and_format}", + f"content/{type_of_dir}/{uuid}/{directory}/{file_and_format}", "learnhouse-media", - f"content/{org_uuid}/{directory}/{file_and_format}", + f"content/{type_of_dir}/{uuid}/{directory}/{file_and_format}", ) except ClientError as e: print(e) @@ -63,7 +68,7 @@ async def upload_content( try: s3.head_object( Bucket="learnhouse-media", - Key=f"content/{org_uuid}/{directory}/{file_and_format}", + Key=f"content/{type_of_dir}/{uuid}/{directory}/{file_and_format}", ) print("File upload successful!") except Exception as e: diff --git a/apps/web/.eslintrc b/apps/web/.eslintrc index af133940..e562b59a 100644 --- a/apps/web/.eslintrc +++ b/apps/web/.eslintrc @@ -3,6 +3,8 @@ "rules": { "react/no-unescaped-entities": "off", "@next/next/no-page-custom-font": "off", - "@next/next/no-img-element": "off" - } + "@next/next/no-img-element": "off", + "unused-imports/no-unused-imports": "warn" + }, + "plugins": ["unused-imports"] } diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 50d30a8c..9e6311c2 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -1,5 +1,5 @@ # -FROM node:16-alpine +FROM node:18-alpine # WORKDIR /usr/learnhouse/front diff --git a/apps/web/app/editor/course/[courseid]/activity/[activityuuid]/edit/page.tsx b/apps/web/app/editor/course/[courseid]/activity/[activityuuid]/edit/page.tsx index f648cd84..14aa9953 100644 --- a/apps/web/app/editor/course/[courseid]/activity/[activityuuid]/edit/page.tsx +++ b/apps/web/app/editor/course/[courseid]/activity/[activityuuid]/edit/page.tsx @@ -4,11 +4,10 @@ import { getCourseMetadataWithAuthHeader } from "@services/courses/courses"; import { cookies } from "next/headers"; import { Metadata } from "next"; import { getActivityWithAuthHeader } from "@services/courses/activities"; -import { getAccessTokenFromRefreshTokenCookie, getNewAccessTokenUsingRefreshTokenServer } from "@services/auth/auth"; -import { getOrganizationContextInfo, getOrganizationContextInfoWithId } from "@services/organizations/orgs"; +import { getAccessTokenFromRefreshTokenCookie } from "@services/auth/auth"; +import { getOrganizationContextInfoWithId } from "@services/organizations/orgs"; import SessionProvider from "@components/Contexts/SessionContext"; import EditorOptionsProvider from "@components/Contexts/Editor/EditorContext"; -import AIChatBotProvider from "@components/Contexts/AI/AIChatBotContext"; import AIEditorProvider from "@components/Contexts/AI/AIEditorContext"; type MetadataProps = { diff --git a/apps/web/app/install/install.tsx b/apps/web/app/install/install.tsx index f7458a7f..01039e41 100644 --- a/apps/web/app/install/install.tsx +++ b/apps/web/app/install/install.tsx @@ -1,13 +1,24 @@ 'use client' -import React, { use, useEffect } from 'react' +import React, { useEffect } from 'react' import { INSTALL_STEPS } from './steps/steps' import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper' import { useRouter, useSearchParams } from 'next/navigation' - - +import { Suspense } from 'react' function InstallClient() { + return ( + + + <> + + + + + ) +} + +const Stepscomp = () => { const searchParams = useSearchParams() const router = useRouter() const step: any = parseInt(searchParams.get('step') || '0'); @@ -24,7 +35,7 @@ function InstallClient() { }, [step]) return ( - +
@@ -54,7 +65,7 @@ function InstallClient() { {stepsState[stepNumber].component}
- +
) } diff --git a/apps/web/app/install/steps/account_creation.tsx b/apps/web/app/install/steps/account_creation.tsx index ca2a053d..db7fb1fd 100644 --- a/apps/web/app/install/steps/account_creation.tsx +++ b/apps/web/app/install/steps/account_creation.tsx @@ -1,5 +1,5 @@ "use client"; -import FormLayout, { ButtonBlack, FormField, FormLabel, FormLabelAndMessage, FormMessage, Input } from '@components/StyledElements/Form/Form' +import FormLayout, { ButtonBlack, FormField, FormLabelAndMessage, Input } from '@components/StyledElements/Form/Form' import * as Form from '@radix-ui/react-form'; import { getAPIUrl } from '@services/config/config'; import { createNewUserInstall, updateInstall } from '@services/install/install'; @@ -8,7 +8,7 @@ import { useFormik } from 'formik'; import { useRouter } from 'next/navigation'; import React from 'react' import { BarLoader } from 'react-spinners'; -import useSWR, { mutate } from "swr"; +import useSWR from "swr"; const validate = (values: any) => { const errors: any = {}; diff --git a/apps/web/app/install/steps/get_started.tsx b/apps/web/app/install/steps/get_started.tsx index f57a9931..91691249 100644 --- a/apps/web/app/install/steps/get_started.tsx +++ b/apps/web/app/install/steps/get_started.tsx @@ -2,7 +2,7 @@ import PageLoading from '@components/Objects/Loaders/PageLoading'; import { getAPIUrl } from '@services/config/config'; import { swrFetcher } from '@services/utils/ts/requests'; import { useRouter } from 'next/navigation'; -import React, { use, useEffect } from 'react' +import React, { useEffect } from 'react' import useSWR, { mutate } from "swr"; function GetStarted() { diff --git a/apps/web/app/install/steps/org_creation.tsx b/apps/web/app/install/steps/org_creation.tsx index 88b60c89..0fbd3996 100644 --- a/apps/web/app/install/steps/org_creation.tsx +++ b/apps/web/app/install/steps/org_creation.tsx @@ -1,13 +1,12 @@ -import FormLayout, { ButtonBlack, FormField, FormLabel, FormLabelAndMessage, FormMessage, Input } from '@components/StyledElements/Form/Form' +import FormLayout, { ButtonBlack, FormField, FormLabelAndMessage, Input } from '@components/StyledElements/Form/Form' import * as Form from '@radix-ui/react-form'; import { useFormik } from 'formik'; import { BarLoader } from 'react-spinners'; import React from 'react' -import { createNewOrganization } from '@services/organizations/orgs'; import { swrFetcher } from '@services/utils/ts/requests'; import { getAPIUrl } from '@services/config/config'; -import useSWR, { mutate } from "swr"; +import useSWR from "swr"; import { createNewOrgInstall, updateInstall } from '@services/install/install'; import { useRouter } from 'next/navigation'; import { Check } from 'lucide-react'; diff --git a/apps/web/app/install/steps/sample_data.tsx b/apps/web/app/install/steps/sample_data.tsx index c03cfcb2..8386002f 100644 --- a/apps/web/app/install/steps/sample_data.tsx +++ b/apps/web/app/install/steps/sample_data.tsx @@ -3,7 +3,7 @@ import { createSampleDataInstall, updateInstall } from '@services/install/instal import { swrFetcher } from '@services/utils/ts/requests'; import { useRouter } from 'next/navigation'; import React from 'react' -import useSWR, { mutate } from "swr"; +import useSWR from "swr"; function SampleData() { const { data: install, error: error, isLoading } = useSWR(`${getAPIUrl()}install/latest`, swrFetcher); diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/collection/[collectionid]/page.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/collection/[collectionid]/page.tsx index 95b61d7e..1609be04 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/collection/[collectionid]/page.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/collection/[collectionid]/page.tsx @@ -1,6 +1,6 @@ import GeneralWrapperStyled from "@components/StyledElements/Wrappers/GeneralWrapper"; -import { getAccessTokenFromRefreshTokenCookie, getNewAccessTokenUsingRefreshTokenServer } from "@services/auth/auth"; -import { getBackendUrl, getUriWithOrg } from "@services/config/config"; +import { getAccessTokenFromRefreshTokenCookie } from "@services/auth/auth"; +import { getUriWithOrg } from "@services/config/config"; import { getCollectionByIdWithAuthHeader } from "@services/courses/collections"; import { getCourseThumbnailMediaDirectory } from "@services/media/media"; import { getOrganizationContextInfo } from "@services/organizations/orgs"; diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/collections/new/page.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/collections/new/page.tsx index 85922e14..b36f368b 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/collections/new/page.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/collections/new/page.tsx @@ -1,29 +1,26 @@ "use client"; import { useRouter } from "next/navigation"; -import React from "react"; +import React, { useState } from "react"; import { createCollection } from "@services/courses/collections"; import useSWR from "swr"; import { getAPIUrl, getUriWithOrg } from "@services/config/config"; import { revalidateTags, swrFetcher } from "@services/utils/ts/requests"; -import { getOrganizationContextInfo } from "@services/organizations/orgs"; +import { useOrg } from "@components/Contexts/OrgContext"; function NewCollection(params: any) { + const org = useOrg() as any; const orgslug = params.params.orgslug; const [name, setName] = React.useState(""); - const [org, setOrg] = React.useState({}) as any; const [description, setDescription] = React.useState(""); const [selectedCourses, setSelectedCourses] = React.useState([]) as any; const router = useRouter(); - const { data: courses, error: error } = useSWR(`${getAPIUrl()}courses/org_slug/${orgslug}/page/1/limit/10`, swrFetcher); + const [isPublic, setIsPublic] = useState('true'); + + const handleVisibilityChange = (e: React.ChangeEvent) => { + setIsPublic(e.target.value); + }; - React.useEffect(() => { - async function getOrg() { - const org = await getOrganizationContextInfo(orgslug, { revalidate: 1800 }); - setOrg(org); - } - getOrg(); - }, []); const handleNameChange = (event: React.ChangeEvent) => { setName(event.target.value); @@ -35,83 +32,94 @@ function NewCollection(params: any) { const handleSubmit = async (e: any) => { e.preventDefault(); - + const collection = { name: name, description: description, courses: selectedCourses, - public: true, + public: isPublic, org_id: org.id, }; await createCollection(collection); - await revalidateTags(["collections"], orgslug); + await revalidateTags(["collections"], org.slug); + // reload the page router.refresh(); - router.prefetch(getUriWithOrg(orgslug, "/collections")); - router.push(getUriWithOrg(orgslug, "/collections")); + + // wait for 2s before reloading the page + setTimeout(() => { + router.push(getUriWithOrg(orgslug, "/collections")); + } + , 1000); }; return ( <>
-
Add new
+
Add new
- + -{!courses ? ( + + + + {!courses ? (

Loading...

) : ( -
+
+

Courses

{courses.map((course: any) => ( -
+
- { + if (e.target.checked) { + setSelectedCourses([...selectedCourses, course.id]); + } + else { + setSelectedCourses(selectedCourses.filter((course_uuid: any) => course_uuid !== course.course_uuid)); + } + }} + className="text-blue-500 rounded focus:ring-2 focus:ring-blue-500" + /> - type="checkbox" - id={course.id} - name={course.name} - value={course.id} -// id is an integer, not a string - - onChange={(e) => { - if (e.target.checked) { - setSelectedCourses([...selectedCourses, course.id]); - } - else { - setSelectedCourses(selectedCourses.filter((course_uuid: any) => course_uuid !== course.course_uuid)); - } - } - } - className="mr-2" -/> - - +
))} -
)} - + - +
diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/activity/[activityid]/page.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/activity/[activityid]/page.tsx index 8a12424d..f81abeb7 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/activity/[activityid]/page.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/activity/[activityid]/page.tsx @@ -4,7 +4,7 @@ import { cookies } from "next/headers"; import ActivityClient from "./activity"; import { getOrganizationContextInfo } from "@services/organizations/orgs"; import { Metadata } from "next"; -import { getAccessTokenFromRefreshTokenCookie, getNewAccessTokenUsingRefreshTokenServer } from "@services/auth/auth"; +import { getAccessTokenFromRefreshTokenCookie } from "@services/auth/auth"; type MetadataProps = { diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/course.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/course.tsx index bd391921..a95fe4c0 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/course.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/course.tsx @@ -1,18 +1,17 @@ "use client"; import { removeCourse, startCourse } from "@services/courses/activity"; import Link from "next/link"; -import React, { use, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { getUriWithOrg } from "@services/config/config"; import PageLoading from "@components/Objects/Loaders/PageLoading"; import { revalidateTags } from "@services/utils/ts/requests"; import ActivityIndicators from "@components/Pages/Courses/ActivityIndicators"; import { useRouter } from "next/navigation"; import GeneralWrapperStyled from "@components/StyledElements/Wrappers/GeneralWrapper"; -import { getCourseThumbnailMediaDirectory } from "@services/media/media"; -import { ArrowRight, Check, File, Sparkles, Star, Video } from "lucide-react"; -import Avvvatars from "avvvatars-react"; -import { getUser } from "@services/users/users"; +import { getCourseThumbnailMediaDirectory, getUserAvatarMediaDirectory } from "@services/media/media"; +import { ArrowRight, Check, File, Sparkles, Video } from "lucide-react"; import { useOrg } from "@components/Contexts/OrgContext"; +import UserAvatar from "@components/Objects/UserAvatar"; const CourseClient = (props: any) => { const [user, setUser] = useState({}); @@ -25,7 +24,7 @@ const CourseClient = (props: any) => { function getLearningTags() { // create array of learnings from a string object (comma separated) - let learnings = course.learnings.split(","); + let learnings = course?.learnings ? course?.learnings.split(",") : []; setLearnings(learnings); } @@ -56,13 +55,13 @@ const CourseClient = (props: any) => { } useEffect(() => { - + getLearningTags(); } - , [org]); + , [org, course]); return ( <> - {!course ? ( + {!course && !org ? ( ) : ( @@ -73,9 +72,13 @@ const CourseClient = (props: any) => {
- -
-
+ {props.course?.thumbnail_image && org ? +
+
+ : +
+
+ } @@ -86,21 +89,25 @@ const CourseClient = (props: any) => {

{course.description}

-

What you will learn

-
- {learnings.map((learning: any) => { - return ( -
-
- -
-

{learning}

-
- ); - } - )} -
+ {learnings.length > 0 && learnings[0] !== "null" && +
+

What you will learn

+
+ {learnings.map((learning: any) => { + return ( +
+
+ +
+

{learning}

+
+ ); + } + )} +
+
+ }

Course Lessons

@@ -185,15 +192,22 @@ const CourseClient = (props: any) => {
-
+
{user && -
-
- -
+
+
Author
-
{course.authors[0].first_name} {course.authors[0].last_name} {(course.authors[0].first_name && course.authors[0].last_name) ? course.authors[0].first_name + ' ' + course.authors[0].last_name : course.authors[0].username}
+
+ {course.authors[0].first_name && course.authors[0].last_name && ( +
+

{course.authors[0].first_name + ' ' + course.authors[0].last_name}

@{course.authors[0].username} +
)} + {!course.authors[0].first_name && !course.authors[0].last_name && ( +
+

@{course.authors[0].username}

+
)} +
} @@ -214,12 +228,4 @@ const CourseClient = (props: any) => { }; -const StyledBox = (props: any) => ( -
- {props.children} -
- -); - - export default CourseClient; diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/page.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/page.tsx index 75e3f295..7a659988 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/page.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/page.tsx @@ -4,7 +4,7 @@ import { cookies } from 'next/headers'; import { getCourseMetadataWithAuthHeader } from '@services/courses/courses'; import { getOrganizationContextInfo } from '@services/organizations/orgs'; import { Metadata } from 'next'; -import { getAccessTokenFromRefreshTokenCookie, getNewAccessTokenUsingRefreshTokenServer } from '@services/auth/auth'; +import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'; type MetadataProps = { params: { orgslug: string, courseuuid: string }; diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/courses/page.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/courses/page.tsx index 11345bff..301a2a3c 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/courses/page.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/courses/page.tsx @@ -5,7 +5,7 @@ import { getOrgCoursesWithAuthHeader } from "@services/courses/courses"; import { Metadata } from "next"; import { getOrganizationContextInfo } from "@services/organizations/orgs"; import { cookies } from "next/headers"; -import { getAccessTokenFromRefreshTokenCookie, getNewAccessTokenUsingRefreshTokenServer } from "@services/auth/auth"; +import { getAccessTokenFromRefreshTokenCookie } from "@services/auth/auth"; type MetadataProps = { params: { orgslug: string }; diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/error.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/error.tsx index 8bac23db..52120114 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/error.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/error.tsx @@ -17,7 +17,7 @@ export default function Error({ return (
- +
); } \ No newline at end of file diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/page.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/page.tsx index 999456ec..9196ca21 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/page.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/page.tsx @@ -12,7 +12,6 @@ import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'; import CourseThumbnail from '@components/Objects/Thumbnails/CourseThumbnail'; import CollectionThumbnail from '@components/Objects/Thumbnails/CollectionThumbnail'; import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement'; -import { Plus, PlusCircle } from 'lucide-react'; import NewCourseButton from '@components/StyledElements/Buttons/NewCourseButton'; import NewCollectionButton from '@components/StyledElements/Buttons/NewCollectionButton'; diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/trail/trail.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/trail/trail.tsx index fa1bf139..6f684dfc 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/trail/trail.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/trail/trail.tsx @@ -5,10 +5,9 @@ import TrailCourseElement from "@components/Pages/Trail/TrailCourseElement"; import TypeOfContentTitle from "@components/StyledElements/Titles/TypeOfContentTitle"; import GeneralWrapperStyled from "@components/StyledElements/Wrappers/GeneralWrapper"; import { getAPIUrl } from "@services/config/config"; -import { removeCourse } from "@services/courses/activity"; -import { revalidateTags, swrFetcher } from "@services/utils/ts/requests"; +import { swrFetcher } from "@services/utils/ts/requests"; import React, { useEffect } from "react"; -import useSWR, { mutate } from "swr"; +import useSWR from "swr"; function Trail(params: any) { let orgslug = params.orgslug; diff --git a/apps/web/app/orgs/[orgslug]/dash/courses/client.tsx b/apps/web/app/orgs/[orgslug]/dash/courses/client.tsx index 9a290f15..c2308527 100644 --- a/apps/web/app/orgs/[orgslug]/dash/courses/client.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/courses/client.tsx @@ -5,7 +5,6 @@ import CourseThumbnail from '@components/Objects/Thumbnails/CourseThumbnail'; import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement'; import NewCourseButton from '@components/StyledElements/Buttons/NewCourseButton'; import Modal from '@components/StyledElements/Modal/Modal'; -import Link from 'next/link' import { useSearchParams } from 'next/navigation'; import React from 'react' diff --git a/apps/web/app/orgs/[orgslug]/dash/courses/course/[courseuuid]/[subpage]/page.tsx b/apps/web/app/orgs/[orgslug]/dash/courses/course/[courseuuid]/[subpage]/page.tsx index f49069dc..36482263 100644 --- a/apps/web/app/orgs/[orgslug]/dash/courses/course/[courseuuid]/[subpage]/page.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/courses/course/[courseuuid]/[subpage]/page.tsx @@ -1,20 +1,13 @@ 'use client'; import EditCourseStructure from '../../../../../../../../components/Dashboard/Course/EditCourseStructure/EditCourseStructure' -import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs' -import PageLoading from '@components/Objects/Loaders/PageLoading'; -import ClientComponentSkeleton from '@components/Utils/ClientComp'; -import { getAPIUrl, getUriWithOrg } from '@services/config/config'; -import { swrFetcher } from '@services/utils/ts/requests'; -import React, { createContext, use, useEffect, useState } from 'react' -import useSWR from 'swr'; -import { CourseProvider, useCourse } from '../../../../../../../../components/Contexts/CourseContext'; -import SaveState from '@components/Dashboard/UI/SaveState'; +import { getUriWithOrg } from '@services/config/config'; +import React from 'react' +import { CourseProvider } from '../../../../../../../../components/Contexts/CourseContext'; import Link from 'next/link'; import { CourseOverviewTop } from '@components/Dashboard/UI/CourseOverviewTop'; -import { CSSTransition } from 'react-transition-group'; import { motion } from 'framer-motion'; import EditCourseGeneral from '@components/Dashboard/Course/EditCourseGeneral/EditCourseGeneral'; -import { GalleryVertical, GalleryVerticalEnd, Info } from 'lucide-react'; +import { GalleryVerticalEnd, Info } from 'lucide-react'; export type CourseOverviewParams = { orgslug: string, @@ -32,9 +25,9 @@ function CourseOverviewPage({ params }: { params: CourseOverviewParams }) { } return ( -
+
-
+
@@ -57,12 +50,12 @@ function CourseOverviewPage({ params }: { params: CourseOverviewParams }) {
-
{params.subpage == 'content' ? : ''} {params.subpage == 'general' ? : ''} diff --git a/apps/web/app/orgs/[orgslug]/dash/layout.tsx b/apps/web/app/orgs/[orgslug]/dash/layout.tsx index a469b6ae..cbb45810 100644 --- a/apps/web/app/orgs/[orgslug]/dash/layout.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/layout.tsx @@ -1,17 +1,20 @@ import SessionProvider from '@components/Contexts/SessionContext' import LeftMenu from '@components/Dashboard/UI/LeftMenu' +import AdminAuthorization from '@components/Security/AdminAuthorization' import React from 'react' function DashboardLayout({ children, params }: { children: React.ReactNode, params: any }) { return ( <> -
- -
- {children} -
-
+ +
+ +
+ {children} +
+
+
) diff --git a/apps/web/app/orgs/[orgslug]/dash/page.tsx b/apps/web/app/orgs/[orgslug]/dash/page.tsx index ae18e474..861c9f0d 100644 --- a/apps/web/app/orgs/[orgslug]/dash/page.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/page.tsx @@ -1,11 +1,62 @@ -import PageLoading from '@components/Objects/Loaders/PageLoading' +import Image from 'next/image' import React from 'react' +import learnhousetextlogo from '../../../../public/learnhouse_logo.png' +import { BookCopy, School, Settings, Users } from 'lucide-react' +import Link from 'next/link' +import AdminAuthorization from '@components/Security/AdminAuthorization' function DashboardHome() { return (
- -
This page is work in progress
+
+ learnhouse logo +
+ +
+ +
+ +
Courses
+

Create and manage courses, chapters and ativities

+
+ + +
+ +
Organization
+

Configure your Organization general settings

+
+ + +
+ +
Users
+

Manage your Organization's users, roles

+
+ + +
+
+
+ +
+
+ + +
Learn LearnHouse
+ +
+
+
+ + +
+ +
Account Settings
+

Configure your personal settings, passwords, email

+
+ +
) } diff --git a/apps/web/app/orgs/[orgslug]/dash/user/settings/[subpage]/page.tsx b/apps/web/app/orgs/[orgslug]/dash/user-account/settings/[subpage]/page.tsx similarity index 87% rename from apps/web/app/orgs/[orgslug]/dash/user/settings/[subpage]/page.tsx rename to apps/web/app/orgs/[orgslug]/dash/user-account/settings/[subpage]/page.tsx index 575b235f..f1ef8bcf 100644 --- a/apps/web/app/orgs/[orgslug]/dash/user/settings/[subpage]/page.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/user-account/settings/[subpage]/page.tsx @@ -1,8 +1,8 @@ 'use client'; import React, { useEffect } from 'react' import { motion } from 'framer-motion'; -import UserEditGeneral from '@components/Dashboard/User/UserEditGeneral/UserEditGeneral'; -import UserEditPassword from '@components/Dashboard/User/UserEditPassword/UserEditPassword'; +import UserEditGeneral from '@components/Dashboard/UserAccount/UserEditGeneral/UserEditGeneral'; +import UserEditPassword from '@components/Dashboard/UserAccount/UserEditPassword/UserEditPassword'; import Link from 'next/link'; import { getUriWithOrg } from '@services/config/config'; import { Info, Lock } from 'lucide-react'; @@ -24,7 +24,7 @@ function SettingsPage({ params }: { params: SettingsParams }) { return (
-
+
@@ -32,7 +32,7 @@ function SettingsPage({ params }: { params: SettingsParams }) {
- +
@@ -41,7 +41,7 @@ function SettingsPage({ params }: { params: SettingsParams }) {
- +
@@ -58,6 +58,7 @@ function SettingsPage({ params }: { params: SettingsParams }) { animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.10, type: "spring", stiffness: 80 }} + className='h-full overflow-y-auto' > {params.subpage == 'general' ? : ''} {params.subpage == 'security' ? : ''} diff --git a/apps/web/app/orgs/[orgslug]/dash/users/settings/[subpage]/page.tsx b/apps/web/app/orgs/[orgslug]/dash/users/settings/[subpage]/page.tsx new file mode 100644 index 00000000..656793ab --- /dev/null +++ b/apps/web/app/orgs/[orgslug]/dash/users/settings/[subpage]/page.tsx @@ -0,0 +1,101 @@ +'use client'; +import React, { useEffect } from 'react' +import { motion } from 'framer-motion'; +import Link from 'next/link'; +import { getUriWithOrg } from '@services/config/config'; +import { ScanEye, UserPlus, Users } from 'lucide-react'; +import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'; +import { useSession } from '@components/Contexts/SessionContext'; +import { useOrg } from '@components/Contexts/OrgContext'; +import OrgUsers from '@components/Dashboard/Users/OrgUsers/OrgUsers'; +import OrgAccess from '@components/Dashboard/Users/OrgAccess/OrgAccess'; + +export type SettingsParams = { + subpage: string + orgslug: string +} + +function UsersSettingsPage({ params }: { params: SettingsParams }) { + const session = useSession() as any; + const org = useOrg() as any; + const [H1Label, setH1Label] = React.useState('') + const [H2Label, setH2Label] = React.useState('') + + function handleLabels() { + if (params.subpage == 'users') { + setH1Label('Users') + setH2Label('Manage your organization users, assign roles and permissions') + } + if (params.subpage == 'signups') { + setH1Label('Signup Access') + setH2Label('Choose from where users can join your organization') + } + if (params.subpage == 'add') { + setH1Label('Invite users') + setH2Label('Invite users to join your organization') + } + } + + + + + + useEffect(() => { + handleLabels() + } + , [session, org, params.subpage, params]) + + return ( +
+
+ +
+
+
{H1Label}
+
{H2Label}
+
+
+
+ +
+ +
+ +
Users
+
+
+ + +
+
+ +
Invite users
+
+
+ + +
+
+ +
Signup Access
+
+
+ + +
+
+ + {params.subpage == 'users' ? : ''} + {params.subpage == 'signups' ? : ''} + +
+ ) +} + +export default UsersSettingsPage \ No newline at end of file diff --git a/apps/web/app/orgs/[orgslug]/login/login.tsx b/apps/web/app/orgs/[orgslug]/login/login.tsx index d8f32cb8..b5bf3c0c 100644 --- a/apps/web/app/orgs/[orgslug]/login/login.tsx +++ b/apps/web/app/orgs/[orgslug]/login/login.tsx @@ -1,11 +1,10 @@ -"use client"; +"use client";; import learnhouseIcon from "public/learnhouse_bigicon_1.png"; -import FormLayout, { ButtonBlack, FormField, FormLabel, FormLabelAndMessage, FormMessage, Input } from '@components/StyledElements/Form/Form' +import FormLayout, { FormField, FormLabelAndMessage, Input } from '@components/StyledElements/Form/Form'; import Image from 'next/image'; import * as Form from '@radix-ui/react-form'; import { useFormik } from 'formik'; import { getOrgLogoMediaDirectory } from "@services/media/media"; -import { BarLoader } from "react-spinners"; import React from "react"; import { loginAndGetToken } from "@services/auth/auth"; import { AlertTriangle } from "lucide-react"; @@ -79,14 +78,14 @@ const LoginClient = (props: LoginClientProps) => {
Login to
- {props.org?.logo ? ( + {props.org?.logo_image ? ( Learnhouse - ) : ( + ) : ( )}
diff --git a/apps/web/app/orgs/[orgslug]/login/page.tsx b/apps/web/app/orgs/[orgslug]/login/page.tsx index 11dd3f5b..faf71cc8 100644 --- a/apps/web/app/orgs/[orgslug]/login/page.tsx +++ b/apps/web/app/orgs/[orgslug]/login/page.tsx @@ -12,7 +12,7 @@ export async function generateMetadata( ): Promise { const orgslug = params.orgslug; // Get Org context information - const org = await getOrganizationContextInfo(orgslug, { revalidate: 1800, tags: ['organizations'] }); + const org = await getOrganizationContextInfo(orgslug, { revalidate: 0, tags: ['organizations'] }); return { title: 'Login' + ` β€” ${org.name}`, @@ -21,7 +21,7 @@ export async function generateMetadata( const Login = async (params: any) => { const orgslug = params.params.orgslug; - const org = await getOrganizationContextInfo(orgslug, { revalidate: 1800, tags: ['organizations'] }); + const org = await getOrganizationContextInfo(orgslug, { revalidate: 0, tags: ['organizations'] }); return (
diff --git a/apps/web/app/orgs/[orgslug]/signup/InviteOnlySignUp.tsx b/apps/web/app/orgs/[orgslug]/signup/InviteOnlySignUp.tsx new file mode 100644 index 00000000..1f554fd2 --- /dev/null +++ b/apps/web/app/orgs/[orgslug]/signup/InviteOnlySignUp.tsx @@ -0,0 +1,166 @@ +"use client"; +import { useFormik } from 'formik'; +import { useRouter } from 'next/navigation'; +import React, { useEffect } from 'react' +import FormLayout, { FormField, FormLabelAndMessage, Input, Textarea } from '@components/StyledElements/Form/Form'; +import * as Form from '@radix-ui/react-form'; +import { AlertTriangle, Check, User } from 'lucide-react'; +import Link from 'next/link'; +import { signUpWithInviteCode } from '@services/auth/auth'; +import { useOrg } from '@components/Contexts/OrgContext'; + + + + +const validate = (values: any) => { + const errors: any = {}; + + if (!values.email) { + errors.email = 'Required'; + } + else if ( + !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email) + ) { + errors.email = 'Invalid email address'; + } + + if (!values.password) { + errors.password = 'Required'; + } + else if (values.password.length < 8) { + errors.password = 'Password must be at least 8 characters'; + } + + if (!values.username) { + errors.username = 'Required'; + } + + if (!values.username || values.username.length < 4) { + errors.username = 'Username must be at least 4 characters'; + } + + if (!values.bio) { + errors.bio = 'Required'; + } + + + return errors; +}; + +interface InviteOnlySignUpProps { + inviteCode: string; +} + +function InviteOnlySignUpComponent(props : InviteOnlySignUpProps) { + const [isSubmitting, setIsSubmitting] = React.useState(false); + const org = useOrg() as any; + const router = useRouter(); + const [error, setError] = React.useState(''); + const [message, setMessage] = React.useState(''); + const formik = useFormik({ + initialValues: { + org_slug: org?.slug, + org_id: org?.id, + email: '', + password: '', + username: '', + bio: '', + first_name: '', + last_name: '', + }, + validate, + onSubmit: async values => { + setError('') + setMessage('') + setIsSubmitting(true); + let res = await signUpWithInviteCode(values, props.inviteCode); + let message = await res.json(); + if (res.status == 200) { + //router.push(`/login`); + setMessage('Your account was successfully created') + setIsSubmitting(false); + } + else if (res.status == 401 || res.status == 400 || res.status == 404 || res.status == 409) { + setError(message.detail); + setIsSubmitting(false); + + } + else { + setError("Something went wrong"); + setIsSubmitting(false); + } + + }, + }); + + useEffect(() => { + + } + , [org]); + + return ( +
+ {error && ( +
+ +
{error}
+
+ )} + {message && ( +
+
+ +
{message}
+
+
+
Login
+
+ )} + + + + + + + + {/* for password */} + + + + + + + + {/* for username */} + + + + + + + + + {/* for bio */} + + + + +