mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
Merge pull request #140 from learnhouse/swve/eng-141-january-bug-fixes
January Bug Fixes & Improvements
This commit is contained in:
commit
f736f41f76
103 changed files with 3347 additions and 1569 deletions
22
.github/workflows/web-lint.yaml
vendored
22
.github/workflows/web-lint.yaml
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
apps/api/.gitignore
vendored
2
apps/api/.gitignore
vendored
|
|
@ -10,7 +10,7 @@ __pycache__/
|
|||
.vscode/
|
||||
|
||||
# Learnhouse
|
||||
content/org_*
|
||||
content/*
|
||||
|
||||
# Flyio
|
||||
fly.toml
|
||||
|
|
|
|||
848
apps/api/poetry.lock
generated
848
apps/api/poetry.lock
generated
|
|
@ -1205,23 +1205,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 +1216,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 +1388,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 +1645,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 +1690,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 +1975,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 +2430,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 +2655,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"
|
||||
|
|
@ -3542,17 +2893,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 +3060,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 +3080,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 +3597,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 = "f1dfc749e30bbf23ec06ac8b236ef6c2072591fe9a8a56f26e05416151ca97bb"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -24,6 +24,5 @@ langchain-openai
|
|||
tiktoken
|
||||
openai
|
||||
chromadb
|
||||
sentence-transformers
|
||||
python-dotenv
|
||||
redis
|
||||
|
|
|
|||
|
|
@ -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 = ""
|
||||
|
|
|
|||
|
|
@ -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")))
|
||||
|
|
|
|||
|
|
@ -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 = ""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = ""
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = ""
|
||||
|
|
|
|||
|
|
@ -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 = ""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,12 +55,14 @@ 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)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,9 +80,50 @@ async def api_create_user_with_orgid(
|
|||
"""
|
||||
Create User with 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"])
|
||||
async def api_create_user_without_org(
|
||||
*,
|
||||
|
|
@ -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(
|
||||
*,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -19,9 +19,8 @@ async def authorization_verify_if_element_is_public(
|
|||
):
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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_"):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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}",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ 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())
|
||||
|
|
@ -223,7 +226,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(
|
||||
|
|
|
|||
|
|
@ -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}",
|
||||
|
|
|
|||
|
|
@ -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}",
|
||||
|
|
|
|||
|
|
@ -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 + " "
|
||||
|
||||
|
|
|
|||
|
|
@ -225,7 +225,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:
|
||||
|
|
|
|||
|
|
@ -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 ##
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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}",
|
||||
)
|
||||
|
|
|
|||
253
apps/api/src/services/orgs/invites.py
Normal file
253
apps/api/src/services/orgs/invites.py
Normal file
|
|
@ -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
|
||||
|
|
@ -9,6 +9,7 @@ async def upload_org_logo(logo_file, org_uuid):
|
|||
|
||||
await upload_content(
|
||||
"logos",
|
||||
"orgs",
|
||||
org_uuid,
|
||||
contents,
|
||||
name_in_disk,
|
||||
|
|
|
|||
|
|
@ -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,10 +547,24 @@ 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",
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
410
apps/api/src/services/orgs/users.py
Normal file
410
apps/api/src/services/orgs/users.py
Normal file
|
|
@ -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"}
|
||||
|
|
@ -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:
|
||||
|
|
@ -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]
|
||||
|
|
|
|||
16
apps/api/src/services/users/avatars.py
Normal file
16
apps/api/src/services/users/avatars.py
Normal file
|
|
@ -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"}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
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,
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<GeneralWrapperStyled>
|
||||
<Suspense>
|
||||
<>
|
||||
<Stepscomp />
|
||||
</>
|
||||
</Suspense>
|
||||
</GeneralWrapperStyled>
|
||||
)
|
||||
}
|
||||
|
||||
const Stepscomp = () => {
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
const step: any = parseInt(searchParams.get('step') || '0');
|
||||
|
|
@ -24,7 +35,7 @@ function InstallClient() {
|
|||
}, [step])
|
||||
|
||||
return (
|
||||
<GeneralWrapperStyled>
|
||||
<div>
|
||||
<div className='flex justify-center '>
|
||||
<div className='grow'>
|
||||
<LearnHouseLogo />
|
||||
|
|
@ -54,7 +65,7 @@ function InstallClient() {
|
|||
{stepsState[stepNumber].component}
|
||||
</div>
|
||||
</div>
|
||||
</GeneralWrapperStyled>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,27 @@
|
|||
"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<HTMLSelectElement>) => {
|
||||
setIsPublic(e.target.value);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
async function getOrg() {
|
||||
const org = await getOrganizationContextInfo(orgslug, { revalidate: 1800 });
|
||||
setOrg(org);
|
||||
}
|
||||
getOrg();
|
||||
}, []);
|
||||
|
||||
const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setName(event.target.value);
|
||||
|
|
@ -40,14 +38,19 @@ function NewCollection(params: any) {
|
|||
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"));
|
||||
|
||||
// wait for 2s before reloading the page
|
||||
setTimeout(() => {
|
||||
router.push(getUriWithOrg(orgslug, "/collections"));
|
||||
}
|
||||
, 1000);
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -64,21 +67,29 @@ function NewCollection(params: any) {
|
|||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
|
||||
<select
|
||||
onChange={handleVisibilityChange}
|
||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
defaultValue={isPublic}
|
||||
>
|
||||
<option value="false">Private Collection</option>
|
||||
<option value="true">Public Collection </option>
|
||||
</select>
|
||||
|
||||
|
||||
{!courses ? (
|
||||
<p className="text-gray-500">Loading...</p>
|
||||
) : (
|
||||
<div>
|
||||
<div className="space-y-4 p-3">
|
||||
<p>Courses</p>
|
||||
{courses.map((course: any) => (
|
||||
<div key={course.course_uuid} className="flex items-center mb-2">
|
||||
<div key={course.course_uuid} className="flex items-center space-x-2">
|
||||
|
||||
<input
|
||||
|
||||
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]);
|
||||
|
|
@ -86,15 +97,13 @@ function NewCollection(params: any) {
|
|||
else {
|
||||
setSelectedCourses(selectedCourses.filter((course_uuid: any) => course_uuid !== course.course_uuid));
|
||||
}
|
||||
}
|
||||
}
|
||||
className="mr-2"
|
||||
}}
|
||||
className="text-blue-500 rounded focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
|
||||
<label htmlFor={course.course_uuid} className="text-sm">{course.name}</label>
|
||||
<label htmlFor={course.course_uuid} className="text-sm text-gray-700">{course.name}</label>
|
||||
</div>
|
||||
))}
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@ 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 { getCourseThumbnailMediaDirectory, getUserAvatarMediaDirectory } 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 { useOrg } from "@components/Contexts/OrgContext";
|
||||
import UserAvatar from "@components/Objects/UserAvatar";
|
||||
|
||||
const CourseClient = (props: any) => {
|
||||
const [user, setUser] = useState<any>({});
|
||||
|
|
@ -25,7 +26,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 +57,13 @@ const CourseClient = (props: any) => {
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
getLearningTags();
|
||||
}
|
||||
, [org]);
|
||||
, [org, course]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!course ? (
|
||||
{!course && !org ? (
|
||||
<PageLoading></PageLoading>
|
||||
) : (
|
||||
<GeneralWrapperStyled>
|
||||
|
|
@ -73,9 +74,13 @@ const CourseClient = (props: any) => {
|
|||
</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="inset-0 ring-1 ring-inset ring-black/10 rounded-lg shadow-xl relative w-auto h-[300px] bg-cover bg-center mb-4" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(org?.org_uuid, course.course_uuid, course.thumbnail_image)})` }}>
|
||||
{props.course?.thumbnail_image && org ?
|
||||
<div className="inset-0 ring-1 ring-inset ring-black/10 rounded-lg shadow-xl relative w-auto h-[400px] bg-cover bg-center mb-4" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(org?.org_uuid, course?.course_uuid, course?.thumbnail_image)})` }}>
|
||||
</div>
|
||||
:
|
||||
<div className="inset-0 ring-1 ring-inset ring-black/10 rounded-lg shadow-xl relative w-auto h-[400px] bg-cover bg-center mb-4" style={{ backgroundImage: `url('../empty_thumbnail.png')`, backgroundSize: 'auto' }}>
|
||||
</div>
|
||||
}
|
||||
|
||||
<ActivityIndicators course_uuid={props.course.course_uuid} orgslug={orgslug} course={course} />
|
||||
|
||||
|
|
@ -86,12 +91,14 @@ const CourseClient = (props: any) => {
|
|||
<p className="py-5 px-5">{course.description}</p>
|
||||
</div>
|
||||
|
||||
{learnings.length > 0 && learnings[0] !== "null" &&
|
||||
<div>
|
||||
<h2 className="py-3 text-2xl font-bold">What you will learn</h2>
|
||||
<div className="bg-white shadow-md shadow-gray-300/25 outline outline-1 outline-neutral-200/40 rounded-lg overflow-hidden px-5 py-5 space-y-2">
|
||||
{learnings.map((learning: any) => {
|
||||
return (
|
||||
<div key={learning}
|
||||
className="flex space-x-2 items-center font-semibold text-gray-500 capitalize">
|
||||
className="flex space-x-2 items-center font-semibold text-gray-500">
|
||||
<div className="px-2 py-2 rounded-full">
|
||||
<Check className="text-gray-400" size={15} />
|
||||
</div>
|
||||
|
|
@ -101,6 +108,8 @@ const CourseClient = (props: any) => {
|
|||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<h2 className="py-3 text-2xl font-bold">Course Lessons</h2>
|
||||
<div className="bg-white shadow-md shadow-gray-300/25 outline outline-1 outline-neutral-200/40 rounded-lg overflow-hidden">
|
||||
|
|
@ -185,15 +194,22 @@ const CourseClient = (props: any) => {
|
|||
</div>
|
||||
|
||||
</div>
|
||||
<div className="course_metadata_right space-y-3 w-64 antialiased flex flex-col ml-10 h-fit p-3 py-5 bg-white shadow-md shadow-gray-300/25 outline outline-1 outline-neutral-200/40 rounded-lg overflow-hidden">
|
||||
<div className="course_metadata_right space-y-3 w-72 antialiased flex flex-col ml-10 h-fit p-3 py-5 bg-white shadow-md shadow-gray-300/25 outline outline-1 outline-neutral-200/40 rounded-lg overflow-hidden">
|
||||
{user &&
|
||||
<div className="flex mx-auto space-x-3 px-2 py-2 items-center">
|
||||
<div className="">
|
||||
<Avvvatars border borderSize={5} borderColor="white" size={50} shadow value={course.authors[0].username} style='shape' />
|
||||
</div>
|
||||
<div className="flex flex-col mx-auto space-y-3 px-2 py-2 items-center">
|
||||
<UserAvatar border="border-8" avatar_url={getUserAvatarMediaDirectory(course.authors[0].user_uuid, course.authors[0].avatar_image)} width={100} />
|
||||
<div className="-space-y-2 ">
|
||||
<div className="text-[12px] text-neutral-400 font-semibold">Author</div>
|
||||
<div className="text-xl font-bold text-neutral-800">{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}</div>
|
||||
<div className="text-xl font-bold text-neutral-800">
|
||||
{course.authors[0].first_name && course.authors[0].last_name && (
|
||||
<div className="flex space-x-2 items-center">
|
||||
<p>{course.authors[0].first_name + ' ' + course.authors[0].last_name}</p><span className="text-xs bg-neutral-100 p-1 px-3 rounded-full text-neutral-400 font-semibold"> @{course.authors[0].username}</span>
|
||||
</div>)}
|
||||
{!course.authors[0].first_name && !course.authors[0].last_name && (
|
||||
<div className="flex space-x-2 items-center">
|
||||
<p>@{course.authors[0].username}</p>
|
||||
</div>)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -214,12 +230,4 @@ const CourseClient = (props: any) => {
|
|||
};
|
||||
|
||||
|
||||
const StyledBox = (props: any) => (
|
||||
<div className="p-3 pl-10 bg-white w-[100%] h-auto ring-1 ring-inset ring-gray-400/10 rounded-lg shadow-sm">
|
||||
{props.children}
|
||||
</div>
|
||||
|
||||
);
|
||||
|
||||
|
||||
export default CourseClient;
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ function CourseOverviewPage({ params }: { params: CourseOverviewParams }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className='h-full w-full bg-[#f8f8f8]'>
|
||||
<div className='h-screen w-full bg-[#f8f8f8] grid grid-rows-[auto,1fr]'>
|
||||
<CourseProvider courseuuid={getEntireCourseUUID(params.courseuuid)}>
|
||||
<div className='pl-10 pr-10 tracking-tight bg-[#fcfbfc] shadow-[0px_4px_16px_rgba(0,0,0,0.02)]'>
|
||||
<div className='pl-10 pr-10 tracking-tight bg-[#fcfbfc] z-10 shadow-[0px_4px_16px_rgba(0,0,0,0.06)]'>
|
||||
<CourseOverviewTop params={params} />
|
||||
<div className='flex space-x-5 font-black text-sm'>
|
||||
<Link href={getUriWithOrg(params.orgslug, "") + `/dash/courses/course/${params.courseuuid}/general`}>
|
||||
|
|
@ -57,12 +57,12 @@ function CourseOverviewPage({ params }: { params: CourseOverviewParams }) {
|
|||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-6'></div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.10, type: "spring", stiffness: 80 }}
|
||||
className='h-full overflow-y-auto'
|
||||
>
|
||||
{params.subpage == 'content' ? <EditCourseStructure orgslug={params.orgslug} /> : ''}
|
||||
{params.subpage == 'general' ? <EditCourseGeneral orgslug={params.orgslug} /> : ''}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<>
|
||||
<SessionProvider>
|
||||
<AdminAuthorization authorizationMode="page">
|
||||
<div className='flex'>
|
||||
<LeftMenu />
|
||||
<div className='flex w-full'>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</AdminAuthorization>
|
||||
</SessionProvider>
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,63 @@
|
|||
import PageLoading from '@components/Objects/Loaders/PageLoading'
|
||||
import Image from 'next/image'
|
||||
import React from 'react'
|
||||
import learnhousetextlogo from '../../../../public/learnhouse_logo.png'
|
||||
import learnhouseiconlogo from '../../../../public/learnhouse_bigicon.png'
|
||||
import { BookCopy, School, Settings, Users } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import AdminAuthorization from '@components/Security/AdminAuthorization'
|
||||
|
||||
function DashboardHome() {
|
||||
return (
|
||||
<div className="flex items-center justify-center mx-auto min-h-screen flex-col space-x-3">
|
||||
<PageLoading />
|
||||
<div className='text-neutral-400 font-bold animate-pulse text-2xl'>This page is work in progress</div>
|
||||
<div className='mx-auto pb-10'>
|
||||
<Image alt='learnhouse logo' width={230} src={learnhousetextlogo}></Image>
|
||||
</div>
|
||||
<AdminAuthorization authorizationMode="component">
|
||||
<div className='flex space-x-10'>
|
||||
<Link href={`/dash/courses`} className='flex bg-white shadow-lg p-[35px] w-[250px] rounded-lg items-center mx-auto hover:scale-105 transition-all ease-linear cursor-pointer'>
|
||||
<div className='flex flex-col mx-auto space-y-2'>
|
||||
<BookCopy className='mx-auto text-gray-500' size={50}></BookCopy>
|
||||
<div className='text-center font-bold text-gray-500'>Courses</div>
|
||||
<p className='text-center text-sm text-gray-400'>Create and manage courses, chapters and ativities </p>
|
||||
</div>
|
||||
</Link>
|
||||
<Link href={`/dash/org/settings/general`} className='flex bg-white shadow-lg p-[35px] w-[250px] rounded-lg items-center mx-auto hover:scale-105 transition-all ease-linear cursor-pointer'>
|
||||
<div className='flex flex-col mx-auto space-y-2'>
|
||||
<School className='mx-auto text-gray-500' size={50}></School>
|
||||
<div className='text-center font-bold text-gray-500'>Organization</div>
|
||||
<p className='text-center text-sm text-gray-400'>Configure your Organization general settings </p>
|
||||
</div>
|
||||
</Link>
|
||||
<Link href={`/dash/users/settings/users`} className='flex bg-white shadow-lg p-[35px] w-[250px] rounded-lg items-center mx-auto hover:scale-105 transition-all ease-linear cursor-pointer'>
|
||||
<div className='flex flex-col mx-auto space-y-2'>
|
||||
<Users className='mx-auto text-gray-500' size={50}></Users>
|
||||
<div className='text-center font-bold text-gray-500'>Users</div>
|
||||
<p className='text-center text-sm text-gray-400'>Manage your Organization's users, roles </p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
</div>
|
||||
</AdminAuthorization>
|
||||
<div className='flex flex-col space-y-10 '>
|
||||
<AdminAuthorization authorizationMode="component">
|
||||
<div className='h-1 w-[100px] bg-neutral-200 rounded-full mx-auto'></div>
|
||||
<div className="flex justify-center items-center">
|
||||
<Link href={'https://learn.learnhouse.io/'} className='flex mt-[40px] bg-black space-x-2 items-center py-3 px-7 rounded-lg shadow-lg hover:scale-105 transition-all ease-linear cursor-pointer'>
|
||||
<BookCopy className=' text-gray-100' size={20}></BookCopy>
|
||||
<div className=' text-sm font-bold text-gray-100'>Learn LearnHouse</div>
|
||||
</Link>
|
||||
</div>
|
||||
<div className='mx-auto mt-[40px] w-28 h-1 bg-neutral-200 rounded-full'></div>
|
||||
</AdminAuthorization>
|
||||
|
||||
<Link href={'/dash/user-account/settings/general'} className='flex bg-white shadow-lg p-[15px] items-center rounded-lg items-center mx-auto hover:scale-105 transition-all ease-linear cursor-pointer'>
|
||||
<div className='flex flex-row mx-auto space-x-3 items-center'>
|
||||
<Settings className=' text-gray-500' size={20}></Settings>
|
||||
<div className=' font-bold text-gray-500'>Account Settings</div>
|
||||
<p className=' text-sm text-gray-400'>Configure your personal settings, passwords, email</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className='h-full w-full bg-[#f8f8f8]'>
|
||||
<div className='pl-10 pr-10 tracking-tight bg-[#fcfbfc] shadow-[0px_4px_16px_rgba(0,0,0,0.02)]'>
|
||||
<div className='pl-10 pr-10 tracking-tight bg-[#fcfbfc] z-10 shadow-[0px_4px_16px_rgba(0,0,0,0.06)]'>
|
||||
<BreadCrumbs type='user' last_breadcrumb={session?.user?.username} ></BreadCrumbs>
|
||||
<div className='my-2 tracking-tighter'>
|
||||
<div className='w-100 flex justify-between'>
|
||||
|
|
@ -32,7 +32,7 @@ function SettingsPage({ params }: { params: SettingsParams }) {
|
|||
</div>
|
||||
</div>
|
||||
<div className='flex space-x-5 font-black text-sm'>
|
||||
<Link href={getUriWithOrg(params.orgslug, "") + `/dash/user/settings/general`}>
|
||||
<Link href={getUriWithOrg(params.orgslug, "") + `/dash/user-account/settings/general`}>
|
||||
<div className={`py-2 w-fit text-center border-black transition-all ease-linear ${params.subpage.toString() === 'general' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>
|
||||
|
||||
<div className='flex items-center space-x-2.5 mx-2'>
|
||||
|
|
@ -41,7 +41,7 @@ function SettingsPage({ params }: { params: SettingsParams }) {
|
|||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link href={getUriWithOrg(params.orgslug, "") + `/dash/user/settings/security`}>
|
||||
<Link href={getUriWithOrg(params.orgslug, "") + `/dash/user-account/settings/security`}>
|
||||
<div className={`flex space-x-4 py-2 w-fit text-center border-black transition-all ease-linear ${params.subpage.toString() === 'security' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>
|
||||
<div className='flex items-center space-x-2.5 mx-2'>
|
||||
<Lock size={16} />
|
||||
|
|
@ -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' ? <UserEditGeneral /> : ''}
|
||||
{params.subpage == 'security' ? <UserEditPassword /> : ''}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
'use client';
|
||||
import React, { useEffect } from 'react'
|
||||
import { motion } from 'framer-motion';
|
||||
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, ScanEye, User, UserCog, 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 (
|
||||
<div className='h-screen w-full bg-[#f8f8f8] grid grid-rows-[auto,1fr]'>
|
||||
<div className='pl-10 pr-10 tracking-tight bg-[#fcfbfc] z-10 shadow-[0px_4px_16px_rgba(0,0,0,0.06)]'>
|
||||
<BreadCrumbs type='orgusers' ></BreadCrumbs>
|
||||
<div className='my-2 py-3'>
|
||||
<div className='w-100 flex flex-col space-y-1'>
|
||||
<div className='pt-3 flex font-bold text-4xl tracking-tighter'>{H1Label}</div>
|
||||
<div className='flex font-medium text-gray-400 text-md'>{H2Label} </div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex space-x-5 font-black text-sm'>
|
||||
<Link href={getUriWithOrg(params.orgslug, "") + `/dash/users/settings/users`}>
|
||||
<div className={`py-2 w-fit text-center border-black transition-all ease-linear ${params.subpage.toString() === 'users' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>
|
||||
|
||||
<div className='flex items-center space-x-2.5 mx-2'>
|
||||
<Users size={16} />
|
||||
<div>Users</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link href={getUriWithOrg(params.orgslug, "") + `/dash/users/settings/add`}>
|
||||
<div className={`py-2 w-fit text-center border-black transition-all ease-linear ${params.subpage.toString() === 'add' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>
|
||||
<div className='flex items-center space-x-2.5 mx-2'>
|
||||
<UserPlus size={16} />
|
||||
<div>Invite users</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link href={getUriWithOrg(params.orgslug, "") + `/dash/users/settings/signups`}>
|
||||
<div className={`py-2 w-fit text-center border-black transition-all ease-linear ${params.subpage.toString() === 'signups' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>
|
||||
<div className='flex items-center space-x-2.5 mx-2'>
|
||||
<ScanEye size={16} />
|
||||
<div>Signup Access</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.10, type: "spring", stiffness: 80 }}
|
||||
className='h-full overflow-y-auto'
|
||||
>
|
||||
{params.subpage == 'users' ? <OrgUsers /> : ''}
|
||||
{params.subpage == 'signups' ? <OrgAccess /> : ''}
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UsersSettingsPage
|
||||
|
|
@ -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,12 +78,12 @@ const LoginClient = (props: LoginClientProps) => {
|
|||
<div className="m-auto flex space-x-4 items-center flex-wrap">
|
||||
<div>Login to </div>
|
||||
<div className="shadow-[0px_4px_16px_rgba(0,0,0,0.02)]" >
|
||||
{props.org?.logo ? (
|
||||
{props.org?.logo_image ? (
|
||||
<img
|
||||
src={`${getOrgLogoMediaDirectory(props.org.org_id, props.org?.logo)}`}
|
||||
src={`${getOrgLogoMediaDirectory(props.org.org_uuid, props.org?.logo_image)}`}
|
||||
alt="Learnhouse"
|
||||
style={{ width: "auto", height: 70 }}
|
||||
className="rounded-md shadow-xl inset-0 ring-1 ring-inset ring-black/10 bg-white"
|
||||
className="rounded-xl shadow-xl inset-0 ring-1 ring-inset ring-black/10 bg-white"
|
||||
/>
|
||||
) : (
|
||||
<Image quality={100} width={70} height={70} src={learnhouseIcon} alt="" />
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export async function generateMetadata(
|
|||
): Promise<Metadata> {
|
||||
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 (
|
||||
<div>
|
||||
|
|
|
|||
166
apps/web/app/orgs/[orgslug]/signup/InviteOnlySignUp.tsx
Normal file
166
apps/web/app/orgs/[orgslug]/signup/InviteOnlySignUp.tsx
Normal file
|
|
@ -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 (
|
||||
<div className="login-form m-auto w-72">
|
||||
{error && (
|
||||
<div className="flex justify-center bg-red-200 rounded-md text-red-950 space-x-2 items-center p-4 transition-all shadow-sm">
|
||||
<AlertTriangle size={18} />
|
||||
<div className="font-bold text-sm">{error}</div>
|
||||
</div>
|
||||
)}
|
||||
{message && (
|
||||
<div className="flex flex-col space-y-4 justify-center bg-green-200 rounded-md text-green-950 space-x-2 items-center p-4 transition-all shadow-sm">
|
||||
<div className='flex space-x-2'>
|
||||
<Check size={18} />
|
||||
<div className="font-bold text-sm">{message}</div>
|
||||
</div>
|
||||
<hr className='border-green-900/20 800 w-40 border' />
|
||||
<Link className='flex space-x-2 items-center' href={'/login'}><User size={14} /> <div>Login </div></Link>
|
||||
</div>
|
||||
)}
|
||||
<FormLayout onSubmit={formik.handleSubmit}>
|
||||
<FormField name="email">
|
||||
<FormLabelAndMessage label='Email' message={formik.errors.email} />
|
||||
<Form.Control asChild>
|
||||
<Input onChange={formik.handleChange} value={formik.values.email} type="email" required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
{/* for password */}
|
||||
<FormField name="password">
|
||||
<FormLabelAndMessage label='Password' message={formik.errors.password} />
|
||||
|
||||
<Form.Control asChild>
|
||||
<Input onChange={formik.handleChange} value={formik.values.password} type="password" required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
{/* for username */}
|
||||
<FormField name="username">
|
||||
<FormLabelAndMessage label='Username' message={formik.errors.username} />
|
||||
|
||||
<Form.Control asChild>
|
||||
<Input onChange={formik.handleChange} value={formik.values.username} type="text" required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
{/* for bio */}
|
||||
<FormField name="bio">
|
||||
<FormLabelAndMessage label='Bio' message={formik.errors.bio} />
|
||||
|
||||
<Form.Control asChild>
|
||||
<Textarea onChange={formik.handleChange} value={formik.values.bio} required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
<div className="flex py-4">
|
||||
<Form.Submit asChild>
|
||||
<button className="w-full bg-black text-white font-bold text-center p-2 rounded-md shadow-md hover:cursor-pointer" >
|
||||
{isSubmitting ? "Loading..."
|
||||
: "Create an account & Join"}
|
||||
</button>
|
||||
</Form.Submit>
|
||||
</div>
|
||||
|
||||
</FormLayout>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default InviteOnlySignUpComponent
|
||||
163
apps/web/app/orgs/[orgslug]/signup/OpenSignup.tsx
Normal file
163
apps/web/app/orgs/[orgslug]/signup/OpenSignup.tsx
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
"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 { signup } 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;
|
||||
};
|
||||
|
||||
|
||||
function OpenSignUpComponent() {
|
||||
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 signup(values);
|
||||
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 (
|
||||
<div className="login-form m-auto w-72">
|
||||
{error && (
|
||||
<div className="flex justify-center bg-red-200 rounded-md text-red-950 space-x-2 items-center p-4 transition-all shadow-sm">
|
||||
<AlertTriangle size={18} />
|
||||
<div className="font-bold text-sm">{error}</div>
|
||||
</div>
|
||||
)}
|
||||
{message && (
|
||||
<div className="flex flex-col space-y-4 justify-center bg-green-200 rounded-md text-green-950 space-x-2 items-center p-4 transition-all shadow-sm">
|
||||
<div className='flex space-x-2'>
|
||||
<Check size={18} />
|
||||
<div className="font-bold text-sm">{message}</div>
|
||||
</div>
|
||||
<hr className='border-green-900/20 800 w-40 border' />
|
||||
<Link className='flex space-x-2 items-center' href={'/login'}><User size={14} /> <div>Login </div></Link>
|
||||
</div>
|
||||
)}
|
||||
<FormLayout onSubmit={formik.handleSubmit}>
|
||||
<FormField name="email">
|
||||
<FormLabelAndMessage label='Email' message={formik.errors.email} />
|
||||
<Form.Control asChild>
|
||||
<Input onChange={formik.handleChange} value={formik.values.email} type="email" required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
{/* for password */}
|
||||
<FormField name="password">
|
||||
<FormLabelAndMessage label='Password' message={formik.errors.password} />
|
||||
|
||||
<Form.Control asChild>
|
||||
<Input onChange={formik.handleChange} value={formik.values.password} type="password" required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
{/* for username */}
|
||||
<FormField name="username">
|
||||
<FormLabelAndMessage label='Username' message={formik.errors.username} />
|
||||
|
||||
<Form.Control asChild>
|
||||
<Input onChange={formik.handleChange} value={formik.values.username} type="text" required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
{/* for bio */}
|
||||
<FormField name="bio">
|
||||
<FormLabelAndMessage label='Bio' message={formik.errors.bio} />
|
||||
|
||||
<Form.Control asChild>
|
||||
<Textarea onChange={formik.handleChange} value={formik.values.bio} required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
<div className="flex py-4">
|
||||
<Form.Submit asChild>
|
||||
<button className="w-full bg-black text-white font-bold text-center p-2 rounded-md shadow-md hover:cursor-pointer" >
|
||||
{isSubmitting ? "Loading..."
|
||||
: "Create an account"}
|
||||
</button>
|
||||
</Form.Submit>
|
||||
</div>
|
||||
|
||||
</FormLayout>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OpenSignUpComponent
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
|
||||
import React from "react";
|
||||
import SignUpClient from "./signup";
|
||||
import { Metadata } from "next";
|
||||
import { getOrganizationContextInfo } from "@services/organizations/orgs";
|
||||
import SignUpClient from "./signup";
|
||||
import { Suspense } from "react";
|
||||
import PageLoading from "@components/Objects/Loaders/PageLoading";
|
||||
|
||||
type MetadataProps = {
|
||||
params: { orgslug: string, courseid: string };
|
||||
|
|
@ -14,7 +15,7 @@ export async function generateMetadata(
|
|||
): Promise<Metadata> {
|
||||
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: 'Sign up' + ` — ${org.name}`,
|
||||
|
|
@ -23,12 +24,14 @@ export async function generateMetadata(
|
|||
|
||||
const SignUp = 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 (
|
||||
<div>
|
||||
<SignUpClient org={org}></SignUpClient>
|
||||
</div>
|
||||
<>
|
||||
<Suspense fallback={<PageLoading/>}>
|
||||
<SignUpClient org={org} />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default SignUp;
|
||||
|
|
|
|||
|
|
@ -1,117 +1,62 @@
|
|||
"use client";
|
||||
import { useFormik } from 'formik';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import learnhouseIcon from "public/learnhouse_bigicon_1.png";
|
||||
import React from 'react'
|
||||
import FormLayout, { ButtonBlack, FormField, FormLabel, FormLabelAndMessage, FormMessage, Input, Textarea } from '@components/StyledElements/Form/Form'
|
||||
import Image from 'next/image';
|
||||
import * as Form from '@radix-ui/react-form';
|
||||
import { getOrgLogoMediaDirectory } from '@services/media/media';
|
||||
import { AlertTriangle, Check, User } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { signup } from '@services/auth/auth';
|
||||
import { getUriWithOrg } from '@services/config/config';
|
||||
|
||||
import { useSession } from "@components/Contexts/SessionContext";
|
||||
import React, { useEffect } from "react";
|
||||
import { MailWarning, Shield, UserPlus } from "lucide-react";
|
||||
import { useOrg } from "@components/Contexts/OrgContext";
|
||||
import UserAvatar from "@components/Objects/UserAvatar";
|
||||
import OpenSignUpComponent from "./OpenSignup";
|
||||
import InviteOnlySignUpComponent from "./InviteOnlySignUp";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { validateInviteCode } from "@services/organizations/invites";
|
||||
import PageLoading from "@components/Objects/Loaders/PageLoading";
|
||||
import Toast from "@components/StyledElements/Toast/Toast";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
interface SignUpClientProps {
|
||||
org: any;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
function SignUpClient(props: SignUpClientProps) {
|
||||
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
||||
const router = useRouter();
|
||||
const [error, setError] = React.useState('');
|
||||
const [message, setMessage] = React.useState('');
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
org_slug: props.org?.slug,
|
||||
org_id: props.org?.id,
|
||||
email: '',
|
||||
password: '',
|
||||
username: '',
|
||||
bio: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
},
|
||||
validate,
|
||||
onSubmit: async values => {
|
||||
setError('')
|
||||
setMessage('')
|
||||
setIsSubmitting(true);
|
||||
let res = await signup(values);
|
||||
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);
|
||||
const session = useSession() as any;
|
||||
const [joinMethod, setJoinMethod] = React.useState('open');
|
||||
const [inviteCode, setInviteCode] = React.useState('');
|
||||
const searchParams = useSearchParams()
|
||||
const inviteCodeParam = searchParams.get('inviteCode')
|
||||
|
||||
useEffect(() => {
|
||||
if (props.org.config) {
|
||||
setJoinMethod(props.org?.config?.config?.GeneralConfig.users.signup_mechanism);
|
||||
console.log(props.org?.config?.config?.GeneralConfig.users.signup_mechanism)
|
||||
}
|
||||
else {
|
||||
setError("Something went wrong");
|
||||
setIsSubmitting(false);
|
||||
if (inviteCodeParam) {
|
||||
setInviteCode(inviteCodeParam);
|
||||
}
|
||||
|
||||
},
|
||||
});
|
||||
}
|
||||
, [props.org, inviteCodeParam]);
|
||||
|
||||
return (
|
||||
<div><div className='grid grid-flow-col justify-stretch h-screen'>
|
||||
<div className='grid grid-flow-col justify-stretch h-screen'>
|
||||
<div className="right-login-part" style={{ background: "linear-gradient(041.61deg, #202020 7.15%, #000000 90.96%)" }} >
|
||||
<div className='login-topbar m-10'>
|
||||
<Link prefetch href={getUriWithOrg(props.org.slug, "/")}>
|
||||
<Image quality={100} width={30} height={30} src={learnhouseIcon} alt="" />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="ml-10 h-4/6 flex flex-row text-white">
|
||||
<div className="ml-10 h-3/4 flex flex-row text-white">
|
||||
<div className="m-auto flex space-x-4 items-center flex-wrap">
|
||||
<div>Join </div>
|
||||
<div>You've been invited to join </div>
|
||||
<div className="shadow-[0px_4px_16px_rgba(0,0,0,0.02)]" >
|
||||
{props.org?.logo ? (
|
||||
{props.org?.logo_image ? (
|
||||
<img
|
||||
src={`${getOrgLogoMediaDirectory(props.org.org_id, props.org?.logo)}`}
|
||||
src={`${getOrgLogoMediaDirectory(props.org.org_uuid, props.org?.logo_image)}`}
|
||||
alt="Learnhouse"
|
||||
style={{ width: "auto", height: 70 }}
|
||||
className="rounded-md shadow-xl inset-0 ring-1 ring-inset ring-black/10 bg-white"
|
||||
className="rounded-xl shadow-xl inset-0 ring-1 ring-inset ring-black/10 bg-white"
|
||||
/>
|
||||
) : (
|
||||
<Image quality={100} width={70} height={70} src={learnhouseIcon} alt="" />
|
||||
|
|
@ -121,70 +66,113 @@ function SignUpClient(props: SignUpClientProps) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="left-login-part bg-white flex flex-row">
|
||||
<div className="login-form m-auto w-72">
|
||||
{error && (
|
||||
<div className="flex justify-center bg-red-200 rounded-md text-red-950 space-x-2 items-center p-4 transition-all shadow-sm">
|
||||
<AlertTriangle size={18} />
|
||||
<div className="font-bold text-sm">{error}</div>
|
||||
</div>
|
||||
<div className="left-join-part bg-white flex flex-row">
|
||||
{joinMethod == 'open' && (
|
||||
session.isAuthenticated ? <LoggedInJoinScreen inviteCode={inviteCode} /> : <OpenSignUpComponent />
|
||||
)}
|
||||
{message && (
|
||||
<div className="flex flex-col space-y-4 justify-center bg-green-200 rounded-md text-green-950 space-x-2 items-center p-4 transition-all shadow-sm">
|
||||
<div className='flex space-x-2'>
|
||||
<Check size={18} />
|
||||
<div className="font-bold text-sm">{message}</div>
|
||||
</div>
|
||||
<hr className='border-green-900/20 800 w-40 border' />
|
||||
<Link className='flex space-x-2 items-center' href={'/login'}><User size={14} /> <div>Login </div></Link>
|
||||
</div>
|
||||
{joinMethod == 'inviteOnly' && (
|
||||
inviteCode ? (
|
||||
session.isAuthenticated ? <LoggedInJoinScreen /> : <InviteOnlySignUpComponent inviteCode={inviteCode} />
|
||||
) : <NoTokenScreen />
|
||||
)}
|
||||
<FormLayout onSubmit={formik.handleSubmit}>
|
||||
<FormField name="email">
|
||||
<FormLabelAndMessage label='Email' message={formik.errors.email} />
|
||||
<Form.Control asChild>
|
||||
<Input onChange={formik.handleChange} value={formik.values.email} type="email" required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
{/* for password */}
|
||||
<FormField name="password">
|
||||
<FormLabelAndMessage label='Password' message={formik.errors.password} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<Form.Control asChild>
|
||||
<Input onChange={formik.handleChange} value={formik.values.password} type="password" required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
{/* for username */}
|
||||
<FormField name="username">
|
||||
<FormLabelAndMessage label='Username' message={formik.errors.username} />
|
||||
|
||||
<Form.Control asChild>
|
||||
<Input onChange={formik.handleChange} value={formik.values.username} type="text" required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
{/* for bio */}
|
||||
<FormField name="bio">
|
||||
<FormLabelAndMessage label='Bio' message={formik.errors.bio} />
|
||||
|
||||
<Form.Control asChild>
|
||||
<Textarea onChange={formik.handleChange} value={formik.values.bio} required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
<div className="flex py-4">
|
||||
<Form.Submit asChild>
|
||||
<button className="w-full bg-black text-white font-bold text-center p-2 rounded-md shadow-md hover:cursor-pointer" >
|
||||
{isSubmitting ? "Loading..."
|
||||
: "Create an account"}
|
||||
const LoggedInJoinScreen = (props: any) => {
|
||||
const session = useSession() as any;
|
||||
const org = useOrg() as any;
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (session && org) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
}
|
||||
, [org, session]);
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex flex-row items-center mx-auto">
|
||||
<div className="flex space-y-7 flex-col justify-center items-center">
|
||||
<p className='pt-3 text-2xl font-semibold text-black/70 flex justify-center space-x-2 items-center'>
|
||||
<span className='items-center'>Hi</span>
|
||||
<span className='capitalize flex space-x-2 items-center'>
|
||||
<UserAvatar rounded='rounded-xl' border='border-4' width={35} />
|
||||
<span>{session.user.username},</span>
|
||||
</span>
|
||||
<span>join {org?.name} ?</span>
|
||||
</p>
|
||||
<button className="flex w-fit space-x-2 bg-black px-6 py-2 text-md rounded-lg font-semibold h-fit text-white items-center shadow-md">
|
||||
<UserPlus size={18} />
|
||||
<p>Join </p>
|
||||
</button>
|
||||
</Form.Submit>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
</FormLayout>
|
||||
}
|
||||
|
||||
const NoTokenScreen = (props: any) => {
|
||||
const session = useSession() as any;
|
||||
const org = useOrg() as any;
|
||||
const router = useRouter();
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
const [inviteCode, setInviteCode] = React.useState('');
|
||||
const [messsage, setMessage] = React.useState('bruh');
|
||||
|
||||
const handleInviteCodeChange = (e: any) => {
|
||||
setInviteCode(e.target.value);
|
||||
}
|
||||
|
||||
const validateCode = async () => {
|
||||
setIsLoading(true);
|
||||
let res = await validateInviteCode(org?.id, inviteCode);
|
||||
//wait for 1s
|
||||
if (res.success) {
|
||||
toast.success("Invite code is valid, you'll be redirected to the signup page in a few seconds");
|
||||
setTimeout(() => {
|
||||
router.push(`/signup?inviteCode=${inviteCode}`);
|
||||
}, 2000);
|
||||
}
|
||||
else {
|
||||
toast.error("Invite code is invalid");
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (session && org) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
}
|
||||
, [org, session]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-row items-center mx-auto">
|
||||
<Toast />
|
||||
{isLoading ? <div className="flex space-y-7 flex-col w-[300px] justify-center items-center"><PageLoading /></div> : <div className="flex space-y-7 flex-col justify-center items-center">
|
||||
|
||||
<p className="flex space-x-2 text-lg font-medium text-red-800 items-center">
|
||||
<MailWarning size={18} />
|
||||
<span>An invite code is required to join {org?.name}</span>
|
||||
</p>
|
||||
<input onChange={handleInviteCodeChange} className="bg-white outline-2 outline outline-gray-200 rounded-lg px-5 w-[300px] h-[50px]" placeholder="Please enter an invite code" type="text" />
|
||||
<button onClick={validateCode} className="flex w-fit space-x-2 bg-black px-6 py-2 text-md rounded-lg font-semibold h-fit text-white items-center shadow-md">
|
||||
<Shield size={18} />
|
||||
<p>Submit </p>
|
||||
</button>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div></div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
'use client';
|
||||
import { getNewAccessTokenUsingRefreshToken, getUserSession } from '@services/auth/auth';
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
import React, { useContext, createContext, useEffect } from 'react'
|
||||
import { useOrg } from './OrgContext';
|
||||
|
||||
export const SessionContext = createContext({}) as any;
|
||||
|
||||
const PATHS_THAT_REQUIRE_AUTH = ['/dash'];
|
||||
|
||||
type Session = {
|
||||
access_token: string;
|
||||
user: any;
|
||||
|
|
@ -18,10 +14,6 @@ type Session = {
|
|||
|
||||
function SessionProvider({ children }: { children: React.ReactNode }) {
|
||||
const [session, setSession] = React.useState<Session>({ access_token: "", user: {}, roles: {}, isLoading: true, isAuthenticated: false });
|
||||
const org = useOrg() as any;
|
||||
const pathname = usePathname()
|
||||
const router = useRouter()
|
||||
|
||||
|
||||
async function getNewAccessTokenUsingRefreshTokenUI() {
|
||||
let data = await getNewAccessTokenUsingRefreshToken();
|
||||
|
|
@ -39,6 +31,10 @@ function SessionProvider({ children }: { children: React.ReactNode }) {
|
|||
// Set session
|
||||
setSession({ access_token: access_token, user: user_session.user, roles: user_session.roles, isLoading: false, isAuthenticated: true });
|
||||
}
|
||||
|
||||
if (!access_token) {
|
||||
setSession({ access_token: "", user: {}, roles: {}, isLoading: false, isAuthenticated: false });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -47,8 +43,6 @@ function SessionProvider({ children }: { children: React.ReactNode }) {
|
|||
// Check session
|
||||
checkSession();
|
||||
|
||||
|
||||
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import * as Switch from '@radix-ui/react-switch';
|
|||
import * as Form from '@radix-ui/react-form';
|
||||
import React from 'react'
|
||||
import { useCourse, useCourseDispatch } from '../../../Contexts/CourseContext';
|
||||
import ThumbnailUpdate from './ThumbnailUpdate';
|
||||
|
||||
|
||||
type EditCourseStructureProps = {
|
||||
|
|
@ -84,6 +85,7 @@ function EditCourseGeneral(props: EditCourseStructureProps) {
|
|||
}, [course, formik.values, formik.initialValues]);
|
||||
|
||||
return (
|
||||
<div> <div className="h-6"></div>
|
||||
<div className='ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5'>
|
||||
|
||||
{course.courseStructure && (
|
||||
|
|
@ -130,6 +132,13 @@ function EditCourseGeneral(props: EditCourseStructureProps) {
|
|||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
<FormField name="thumbnail">
|
||||
<FormLabelAndMessage label='Thumbnail' />
|
||||
<Form.Control asChild>
|
||||
<ThumbnailUpdate />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
<FormField className="flex items-center h-10" name="public">
|
||||
<div className='flex my-auto items-center'>
|
||||
<label className="text-black text-[15px] leading-none pr-[15px]" htmlFor="public-course">
|
||||
|
|
@ -150,6 +159,7 @@ function EditCourseGeneral(props: EditCourseStructureProps) {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
import { useCourse } from '@components/Contexts/CourseContext';
|
||||
import { useOrg } from '@components/Contexts/OrgContext';
|
||||
import { getAPIUrl } from '@services/config/config';
|
||||
import { updateCourseThumbnail } from '@services/courses/courses';
|
||||
import { getCourseThumbnailMediaDirectory } from '@services/media/media';
|
||||
import { ArrowBigUpDash, UploadCloud } from 'lucide-react';
|
||||
import React from 'react'
|
||||
import { mutate } from 'swr';
|
||||
|
||||
function ThumbnailUpdate() {
|
||||
const course = useCourse() as any;
|
||||
const org = useOrg() as any;
|
||||
const [localThumbnail, setLocalThumbnail] = React.useState(null) as any;
|
||||
const [isLoading, setIsLoading] = React.useState(false) as any;
|
||||
const [error, setError] = React.useState('') as any;
|
||||
|
||||
|
||||
const handleFileChange = async (event: any) => {
|
||||
const file = event.target.files[0];
|
||||
setLocalThumbnail(file);
|
||||
setIsLoading(true);
|
||||
const res = await updateCourseThumbnail(course.courseStructure.course_uuid, file)
|
||||
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`);
|
||||
// wait for 1 second to show loading animation
|
||||
await new Promise(r => setTimeout(r, 1500));
|
||||
if (res.success === false) {
|
||||
setError(res.HTTPmessage);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
setError('');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='w-auto bg-gray-50 rounded-xl outline outline-1 outline-gray-200 h-[200px] shadow'>
|
||||
<div className='flex flex-col justify-center items-center h-full'>
|
||||
<div className='flex flex-col justify-center items-center'>
|
||||
<div className='flex flex-col justify-center items-center'>
|
||||
{error && (
|
||||
<div className="flex justify-center bg-red-200 rounded-md text-red-950 space-x-2 items-center p-2 transition-all shadow-sm">
|
||||
<div className="text-sm font-semibold">{error}</div>
|
||||
</div>
|
||||
)}
|
||||
{localThumbnail ? (
|
||||
<img src={URL.createObjectURL(localThumbnail)} className={`${isLoading ? 'animate-pulse' : ''} shadow w-[200px] h-[100px] rounded-md`} />
|
||||
|
||||
) : (
|
||||
<img src={`${getCourseThumbnailMediaDirectory(org?.org_uuid, course.courseStructure.course_uuid, course.courseStructure.thumbnail_image)}`} className='shadow w-[200px] h-[100px] rounded-md' />
|
||||
)}
|
||||
|
||||
</div>
|
||||
{isLoading ? (<div className='flex justify-center items-center'>
|
||||
<input type="file" id="fileInput" style={{ display: 'none' }} onChange={handleFileChange} />
|
||||
<div
|
||||
className='font-bold animate-pulse antialiased items-center bg-green-200 text-gray text-sm rounded-md px-4 py-2 mt-4 flex'
|
||||
>
|
||||
<ArrowBigUpDash size={16} className='mr-2' />
|
||||
<span>Uploading</span>
|
||||
</div>
|
||||
</div>) : (
|
||||
|
||||
<div className='flex justify-center items-center'>
|
||||
<input type="file" id="fileInput" style={{ display: 'none' }} onChange={handleFileChange} />
|
||||
<button
|
||||
className='font-bold antialiased items-center text-gray text-sm rounded-md px-4 mt-6 flex'
|
||||
onClick={() => document.getElementById('fileInput')?.click()}
|
||||
>
|
||||
<UploadCloud size={16} className='mr-2' />
|
||||
<span>Change Thumbnail</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ThumbnailUpdate
|
||||
|
|
@ -92,6 +92,7 @@ const EditCourseStructure = (props: EditCourseStructureProps) => {
|
|||
|
||||
return (
|
||||
<div className='flex flex-col'>
|
||||
<div className="h-6"></div>
|
||||
{winReady ?
|
||||
<DragDropContext onDragEnd={updateStructure}>
|
||||
<Droppable type='chapter' droppableId='chapters'>
|
||||
|
|
@ -129,7 +130,7 @@ const EditCourseStructure = (props: EditCourseStructureProps) => {
|
|||
dialogTitle="Create chapter"
|
||||
dialogDescription="Add a new chapter to the course"
|
||||
dialogTrigger={
|
||||
<div className="mt-4 w-44 max-w-screen-2xl mx-auto bg-cyan-800 text-white rounded-xl shadow-sm px-6 items-center flex flex-row h-10">
|
||||
<div className="w-44 my-16 py-5 max-w-screen-2xl mx-auto bg-cyan-800 text-white rounded-xl shadow-sm px-6 items-center flex flex-row h-10">
|
||||
<div className='mx-auto flex space-x-2 items-center hover:cursor-pointer'>
|
||||
<Hexagon strokeWidth={3} size={16} className="text-white text-sm " />
|
||||
<div className='font-bold text-sm'>Add Chapter</div></div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useCourse } from '@components/Contexts/CourseContext'
|
||||
import { Book, ChevronRight, School, User } from 'lucide-react'
|
||||
import { Book, ChevronRight, School, User, Users } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import React, { use, useEffect } from 'react'
|
||||
|
||||
type BreadCrumbsProps = {
|
||||
type: 'courses' | 'user' | 'users' | 'org'
|
||||
type: 'courses' | 'user' | 'users' | 'org' | 'orgusers'
|
||||
last_breadcrumb?: string
|
||||
}
|
||||
|
||||
|
|
@ -17,7 +17,9 @@ function BreadCrumbs(props: BreadCrumbsProps) {
|
|||
<div className='text-gray-400 tracking-tight font-medium text-sm flex space-x-1'>
|
||||
<div className='flex items-center space-x-1'>
|
||||
{props.type == 'courses' ? <div className='flex space-x-2 items-center'> <Book className='text-gray' size={14}></Book><Link href='/dash/courses'>Courses</Link></div> : ''}
|
||||
{props.type == 'user' ? <div className='flex space-x-2 items-center'> <User className='text-gray' size={14}></User><Link href='/dash/user/settings/general'>Account Settings</Link></div> : ''}
|
||||
{props.type == 'user' ? <div className='flex space-x-2 items-center'> <User className='text-gray' size={14}></User><Link href='/dash/user-account/settings/general'>Account Settings</Link></div> : ''}
|
||||
{props.type == 'orgusers' ? <div className='flex space-x-2 items-center'> <Users className='text-gray' size={14}></Users><Link href='/dash/users/settings/users'>Organization users</Link></div> : ''}
|
||||
|
||||
{props.type == 'org' ? <div className='flex space-x-2 items-center'> <School className='text-gray' size={14}></School><Link href='/dash/users'>Organization Settings</Link></div> : ''}
|
||||
<div className='flex items-center space-x-1 first-letter:uppercase'>
|
||||
{props.last_breadcrumb ? <ChevronRight size={17} /> : ''}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import { getUriWithOrg } from "@services/config/config";
|
|||
import { useOrg } from "@components/Contexts/OrgContext";
|
||||
import { getCourseThumbnailMediaDirectory } from "@services/media/media";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import EmptyThumbnailImage from '../../../public/empty_thumbnail.png';
|
||||
|
||||
export function CourseOverviewTop({ params }: { params: CourseOverviewParams }) {
|
||||
const course = useCourse() as any;
|
||||
|
|
@ -21,7 +23,10 @@ export function CourseOverviewTop({ params }: { params: CourseOverviewParams })
|
|||
<div className='flex'>
|
||||
<div className='flex py-5 grow items-center'>
|
||||
<Link href={getUriWithOrg(org?.slug, "") + `/course/${params.courseuuid}`}>
|
||||
{course?.courseStructure?.thumbnail_image ?
|
||||
<img className="w-[100px] h-[57px] rounded-md drop-shadow-md" src={`${getCourseThumbnailMediaDirectory(org?.org_uuid, "course_" + params.courseuuid, course.courseStructure.thumbnail_image)}`} alt="" />
|
||||
:
|
||||
<Image width={100} className="h-[57px] rounded-md drop-shadow-md" src={EmptyThumbnailImage} alt="" />}
|
||||
</Link>
|
||||
<div className="flex flex-col course_metadata justify-center pl-5">
|
||||
<div className='text-gray-400 font-semibold text-sm'>Course</div>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@ import { useSession } from '@components/Contexts/SessionContext';
|
|||
import ToolTip from '@components/StyledElements/Tooltip/Tooltip'
|
||||
import LearnHouseDashboardLogo from '@public/dashLogo.png';
|
||||
import { logout } from '@services/auth/auth';
|
||||
import Avvvatars from 'avvvatars-react';
|
||||
import { ArrowLeft, Book, BookCopy, Home, LogOut, School, Settings } from 'lucide-react'
|
||||
import { ArrowLeft, Book, BookCopy, Home, LogOut, School, Settings, Users } from 'lucide-react'
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React, { use, useEffect } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import UserAvatar from '../../Objects/UserAvatar';
|
||||
import AdminAuthorization from '@components/Security/AdminAuthorization';
|
||||
|
||||
function LeftMenu() {
|
||||
const org = useOrg() as any;
|
||||
|
|
@ -42,8 +43,8 @@ function LeftMenu() {
|
|||
|
||||
return (
|
||||
<div
|
||||
style={{ background: "linear-gradient(0deg, rgba(0, 0, 0, 0.20) 0%, rgba(0, 0, 0, 0.20) 100%), radial-gradient(271.56% 105.16% at 50% -5.16%, rgba(255, 255, 255, 0.18) 0%, rgba(0, 0, 0, 0.00) 100%), #2E2D2D" }}
|
||||
className='flex flex-col w-28 bg-black h-screen text-white shadow-xl'>
|
||||
style={{ background: "linear-gradient(0deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.2) 100%), radial-gradient(271.56% 105.16% at 50% -5.16%, rgba(255, 255, 255, 0.18) 0%, rgba(0, 0, 0, 0) 100%), rgb(20 19 19)" }}
|
||||
className='flex flex-col w-[90px] bg-black h-screen text-white shadow-xl'>
|
||||
<div className='flex flex-col h-full'>
|
||||
<div className='flex h-20 mt-6'>
|
||||
<Link className='flex flex-col items-center mx-auto space-y-3' href={"/"}>
|
||||
|
|
@ -59,27 +60,32 @@ function LeftMenu() {
|
|||
{/* <ToolTip content={"Back to " + org?.name + "'s Home"} slateBlack sideOffset={8} side='right' >
|
||||
<Link className='bg-white text-black hover:text-white rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/`} ><ArrowLeft className='hover:text-white' size={18} /></Link>
|
||||
</ToolTip> */}
|
||||
<AdminAuthorization authorizationMode="component">
|
||||
<ToolTip content={"Home"} slateBlack sideOffset={8} side='right' >
|
||||
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash`} ><Home size={18} /></Link>
|
||||
</ToolTip>
|
||||
<ToolTip content={"Courses"} slateBlack sideOffset={8} side='right' >
|
||||
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/courses`} ><BookCopy size={18} /></Link>
|
||||
</ToolTip>
|
||||
<ToolTip content={"Users"} slateBlack sideOffset={8} side='right' >
|
||||
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/users/settings/users`} ><Users size={18} /></Link>
|
||||
</ToolTip>
|
||||
<ToolTip content={"Organization"} slateBlack sideOffset={8} side='right' >
|
||||
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/org/settings/general`} ><School size={18} /></Link>
|
||||
</ToolTip>
|
||||
</AdminAuthorization>
|
||||
</div>
|
||||
<div className='flex flex-col mx-auto pb-7 space-y-2'>
|
||||
|
||||
<div className="flex items-center flex-col space-y-2">
|
||||
<ToolTip content={session.user.username} slateBlack sideOffset={8} side='right' >
|
||||
<div className="mx-auto shadow-lg">
|
||||
<Avvvatars radius={3} border borderColor='white' borderSize={3} size={35} value={session.user.user_uuid} style="shape" />
|
||||
<ToolTip content={'@' + session.user.username} slateBlack sideOffset={8} side='right' >
|
||||
<div className='mx-auto'>
|
||||
<UserAvatar border='border-4' width={35} />
|
||||
</div>
|
||||
</ToolTip>
|
||||
<div className='flex items-center flex-col space-y-1'>
|
||||
<ToolTip content={session.user.username + "'s Settings"} slateBlack sideOffset={8} side='right' >
|
||||
<Link href={'/dash/user/settings/general'} className='py-3'>
|
||||
<Link href={'/dash/user-account/settings/general'} className='py-3'>
|
||||
<Settings className='mx-auto text-neutral-400 cursor-pointer' size={18} />
|
||||
</Link>
|
||||
</ToolTip>
|
||||
|
|
|
|||
|
|
@ -1,100 +0,0 @@
|
|||
import { updateProfile } from '@services/settings/profile';
|
||||
import React, { useEffect } from 'react'
|
||||
import { Formik, Form, Field, ErrorMessage } from 'formik';
|
||||
import { useSession } from '@components/Contexts/SessionContext';
|
||||
|
||||
function UserEditGeneral() {
|
||||
const session = useSession() as any;
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
}
|
||||
, [session, session.user])
|
||||
|
||||
return (
|
||||
<div className='ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5'>
|
||||
{session.user && (
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={{
|
||||
username: session.user.username,
|
||||
first_name: session.user.first_name,
|
||||
last_name: session.user.last_name,
|
||||
email: session.user.email,
|
||||
bio: session.user.bio,
|
||||
}}
|
||||
onSubmit={(values, { setSubmitting }) => {
|
||||
setTimeout(() => {
|
||||
|
||||
setSubmitting(false);
|
||||
updateProfile(values,session.user.id)
|
||||
}, 400);
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting }) => (
|
||||
<Form className="max-w-md">
|
||||
<label className="block mb-2 font-bold" htmlFor="email">
|
||||
Email
|
||||
</label>
|
||||
<Field
|
||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
type="email"
|
||||
name="email"
|
||||
/>
|
||||
|
||||
<label className="block mb-2 font-bold" htmlFor="username">
|
||||
Username
|
||||
</label>
|
||||
<Field
|
||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
type="username"
|
||||
name="username"
|
||||
/>
|
||||
|
||||
<label className="block mb-2 font-bold" htmlFor="first_name">
|
||||
First Name
|
||||
</label>
|
||||
|
||||
<Field
|
||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
type="first_name"
|
||||
name="first_name"
|
||||
/>
|
||||
|
||||
<label className="block mb-2 font-bold" htmlFor="last_name">
|
||||
Last Name
|
||||
</label>
|
||||
|
||||
<Field
|
||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
type="last_name"
|
||||
name="last_name"
|
||||
/>
|
||||
|
||||
<label className="block mb-2 font-bold" htmlFor="bio">
|
||||
Bio
|
||||
</label>
|
||||
|
||||
<Field
|
||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
type="bio"
|
||||
name="bio"
|
||||
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="px-6 py-3 text-white bg-black rounded-lg shadow-md hover:bg-black focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</Form>
|
||||
|
||||
)}
|
||||
</Formik>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserEditGeneral
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
import { updateProfile } from '@services/settings/profile';
|
||||
import React, { useEffect } from 'react'
|
||||
import { Formik, Form, Field, ErrorMessage } from 'formik';
|
||||
import { useSession } from '@components/Contexts/SessionContext';
|
||||
import { ArrowBigUpDash, Check, FileWarning, Info, UploadCloud } from 'lucide-react';
|
||||
import UserAvatar from '@components/Objects/UserAvatar';
|
||||
import { updateUserAvatar } from '@services/users/users';
|
||||
|
||||
function UserEditGeneral() {
|
||||
const session = useSession() as any;
|
||||
const [localAvatar, setLocalAvatar] = React.useState(null) as any;
|
||||
const [isLoading, setIsLoading] = React.useState(false) as any;
|
||||
const [error, setError] = React.useState() as any;
|
||||
const [success, setSuccess] = React.useState('') as any;
|
||||
|
||||
const handleFileChange = async (event: any) => {
|
||||
const file = event.target.files[0];
|
||||
setLocalAvatar(file);
|
||||
setIsLoading(true);
|
||||
const res = await updateUserAvatar(session.user.user_uuid, file)
|
||||
// wait for 1 second to show loading animation
|
||||
await new Promise(r => setTimeout(r, 1500));
|
||||
if (res.success === false) {
|
||||
setError(res.HTTPmessage);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
setError('');
|
||||
setSuccess('Avatar Updated');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
}
|
||||
, [session, session.user])
|
||||
|
||||
return (
|
||||
<div className='ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5'>
|
||||
{session.user && (
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={{
|
||||
username: session.user.username,
|
||||
first_name: session.user.first_name,
|
||||
last_name: session.user.last_name,
|
||||
email: session.user.email,
|
||||
bio: session.user.bio,
|
||||
}}
|
||||
onSubmit={(values, { setSubmitting }) => {
|
||||
setTimeout(() => {
|
||||
|
||||
setSubmitting(false);
|
||||
updateProfile(values, session.user.id)
|
||||
}, 400);
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting }) => (
|
||||
<div className='flex space-x-8'>
|
||||
|
||||
<Form className="max-w-md">
|
||||
|
||||
<label className="block mb-2 font-bold" htmlFor="email">
|
||||
Email
|
||||
</label>
|
||||
<Field
|
||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
type="email"
|
||||
name="email"
|
||||
/>
|
||||
|
||||
<label className="block mb-2 font-bold" htmlFor="username">
|
||||
Username
|
||||
</label>
|
||||
<Field
|
||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
type="username"
|
||||
name="username"
|
||||
/>
|
||||
|
||||
<label className="block mb-2 font-bold" htmlFor="first_name">
|
||||
First Name
|
||||
</label>
|
||||
|
||||
<Field
|
||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
type="first_name"
|
||||
name="first_name"
|
||||
/>
|
||||
|
||||
<label className="block mb-2 font-bold" htmlFor="last_name">
|
||||
Last Name
|
||||
</label>
|
||||
|
||||
<Field
|
||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
type="last_name"
|
||||
name="last_name"
|
||||
/>
|
||||
|
||||
<label className="block mb-2 font-bold" htmlFor="bio">
|
||||
Bio
|
||||
</label>
|
||||
|
||||
<Field
|
||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
type="bio"
|
||||
name="bio"
|
||||
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="px-6 py-3 text-white bg-black rounded-lg shadow-md hover:bg-black focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</Form>
|
||||
<div className='flex flex-col grow justify-center align-middle space-y-3'>
|
||||
<label className="flex mx-auto mb-2 font-bold " >
|
||||
Avatar
|
||||
</label>
|
||||
{error && (
|
||||
<div className="flex justify-center mx-auto bg-red-200 rounded-md text-red-950 space-x-1 px-4 items-center p-2 transition-all shadow-sm">
|
||||
<FileWarning size={16} className='mr-2' />
|
||||
<div className="text-sm font-semibold first-letter:uppercase">{error}</div>
|
||||
</div>
|
||||
)}
|
||||
{success && (
|
||||
<div className="flex justify-center mx-auto bg-green-200 rounded-md text-green-950 space-x-1 px-4 items-center p-2 transition-all shadow-sm">
|
||||
<Check size={16} className='mr-2' />
|
||||
<div className="text-sm font-semibold first-letter:uppercase">{success}</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col space-y-3">
|
||||
|
||||
<div className='w-auto bg-gray-50 rounded-xl outline outline-1 outline-gray-200 h-[200px] shadow mx-20'>
|
||||
|
||||
<div className='flex flex-col justify-center items-center mt-10'>
|
||||
|
||||
{localAvatar ? (
|
||||
<UserAvatar border='border-8' width={100} avatar_url={URL.createObjectURL(localAvatar)} />
|
||||
|
||||
) : (
|
||||
<UserAvatar border='border-8' width={100} />
|
||||
)}
|
||||
</div>
|
||||
{isLoading ? (<div className='flex justify-center items-center'>
|
||||
<input type="file" id="fileInput" style={{ display: 'none' }} onChange={handleFileChange} />
|
||||
<div
|
||||
className='font-bold animate-pulse antialiased items-center bg-green-200 text-gray text-sm rounded-md px-4 py-2 mt-4 flex'
|
||||
>
|
||||
<ArrowBigUpDash size={16} className='mr-2' />
|
||||
<span>Uploading</span>
|
||||
</div>
|
||||
</div>) : (
|
||||
<div className='flex justify-center items-center'>
|
||||
<input type="file" id="fileInput" style={{ display: 'none' }} onChange={handleFileChange} />
|
||||
<button
|
||||
className='font-bold antialiased items-center text-gray text-sm rounded-md px-4 py-2 mt-4 flex'
|
||||
onClick={() => document.getElementById('fileInput')?.click()}
|
||||
>
|
||||
<UploadCloud size={16} className='mr-2' />
|
||||
<span>Change Thumbnail</span>
|
||||
</button>
|
||||
</div> )}
|
||||
</div>
|
||||
<div className='flex text-xs space-x-2 items-center text-gray-500 justify-center'>
|
||||
<Info size={13} /><p>Recommended size 100x100</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
)}
|
||||
</Formik>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserEditGeneral
|
||||
175
apps/web/components/Dashboard/Users/OrgAccess/OrgAccess.tsx
Normal file
175
apps/web/components/Dashboard/Users/OrgAccess/OrgAccess.tsx
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import PageLoading from '@components/Objects/Loaders/PageLoading';
|
||||
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal';
|
||||
import { getAPIUrl, getUriWithOrg } from '@services/config/config';
|
||||
import { swrFetcher } from '@services/utils/ts/requests';
|
||||
import { Globe, Shield, X } from 'lucide-react'
|
||||
import Link from 'next/link';
|
||||
import React, { use, useEffect } from 'react'
|
||||
import useSWR, { mutate } from 'swr';
|
||||
import dayjs from 'dayjs';
|
||||
import { changeSignupMechanism, createInviteCode, deleteInviteCode } from '@services/organizations/invites';
|
||||
import Toast from '@components/StyledElements/Toast/Toast';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
function OrgAccess() {
|
||||
const org = useOrg() as any;
|
||||
const { data: invites } = useSWR(org ? `${getAPIUrl()}orgs/${org?.id}/invites` : null, swrFetcher);
|
||||
const [isLoading, setIsLoading] = React.useState(false)
|
||||
const [joinMethod, setJoinMethod] = React.useState('closed')
|
||||
const router = useRouter()
|
||||
|
||||
async function getOrgJoinMethod() {
|
||||
if (org) {
|
||||
if (org.config.config.GeneralConfig.users.signup_mechanism == 'open') {
|
||||
setJoinMethod('open')
|
||||
}
|
||||
else {
|
||||
setJoinMethod('inviteOnly')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function createInvite() {
|
||||
let res = await createInviteCode(org.id)
|
||||
if (res.status == 200) {
|
||||
mutate(`${getAPIUrl()}orgs/${org.id}/invites`)
|
||||
}
|
||||
else {
|
||||
toast.error('Error ' + res.status + ': ' + res.data.detail)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function deleteInvite(invite: any) {
|
||||
let res = await deleteInviteCode(org.id, invite.invite_code_uuid)
|
||||
if (res.status == 200) {
|
||||
mutate(`${getAPIUrl()}orgs/${org.id}/invites`)
|
||||
}
|
||||
else {
|
||||
toast.error('Error ' + res.status + ': ' + res.data.detail)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function changeJoinMethod(method: 'open' | 'inviteOnly') {
|
||||
let res = await changeSignupMechanism(org.id, method)
|
||||
if (res.status == 200) {
|
||||
router.refresh()
|
||||
mutate(`${getAPIUrl()}orgs/slug/${org?.slug}`)
|
||||
}
|
||||
else {
|
||||
toast.error('Error ' + res.status + ': ' + res.data.detail)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (invites && org) {
|
||||
getOrgJoinMethod()
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
, [org, invites])
|
||||
|
||||
return (
|
||||
|
||||
<>
|
||||
<Toast></Toast>
|
||||
{!isLoading ? (<>
|
||||
<div className="h-6"></div>
|
||||
<div className='ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-4 py-4 anit '>
|
||||
<div className='flex flex-col bg-gray-50 -space-y-1 px-5 py-3 rounded-md mb-3 '>
|
||||
<h1 className='font-bold text-xl text-gray-800'>Join method</h1>
|
||||
<h2 className='text-gray-500 text-md'> Choose how users can join your organization </h2>
|
||||
</div>
|
||||
<div className='flex space-x-2 mx-auto'>
|
||||
<ConfirmationModal
|
||||
confirmationButtonText='Change to open '
|
||||
confirmationMessage='Are you sure you want to change the signup mechanism to open ? This will allow users to join your organization freely.'
|
||||
dialogTitle={'Change to open ?'}
|
||||
dialogTrigger={
|
||||
<div className='w-full h-[160px] bg-slate-100 rounded-lg cursor-pointer hover:bg-slate-200 ease-linear transition-all'>
|
||||
{joinMethod == 'open' ? <div className='bg-green-200 text-green-600 font-bold w-fit my-3 mx-3 absolute text-sm px-3 py-1 rounded-lg'>Active</div> : null}
|
||||
<div className='flex flex-col space-y-1 justify-center items-center h-full'>
|
||||
<Globe className='text-slate-400' size={40}></Globe>
|
||||
<div className='text-2xl text-slate-700 font-bold'>Open</div>
|
||||
<div className='text-gray-400 text-center'>Users can join freely from the signup page</div>
|
||||
</div>
|
||||
</div>}
|
||||
functionToExecute={() => { changeJoinMethod('open') }}
|
||||
status='info'
|
||||
></ConfirmationModal>
|
||||
<ConfirmationModal
|
||||
confirmationButtonText='Change to closed '
|
||||
confirmationMessage='Are you sure you want to change the signup mechanism to closed ? This will allow users to join your organization only by invitation.'
|
||||
dialogTitle={'Change to closed ?'}
|
||||
dialogTrigger={
|
||||
<div className='w-full h-[160px] bg-slate-100 rounded-lg cursor-pointer hover:bg-slate-200 ease-linear transition-all'>
|
||||
{joinMethod == 'inviteOnly' ? <div className='bg-green-200 text-green-600 font-bold w-fit my-3 mx-3 absolute text-sm px-3 py-1 rounded-lg'>Active</div> : null}
|
||||
<div className='flex flex-col space-y-1 justify-center items-center h-full'>
|
||||
<Shield className='text-slate-400' size={40}></Shield>
|
||||
<div className='text-2xl text-slate-700 font-bold'>Closed</div>
|
||||
<div className='text-gray-400 text-center'>Users can join only by invitation</div>
|
||||
</div>
|
||||
</div>}
|
||||
functionToExecute={() => { changeJoinMethod('inviteOnly') }}
|
||||
status='info'
|
||||
></ConfirmationModal>
|
||||
|
||||
</div>
|
||||
<div className={joinMethod == 'open' ? 'opacity-20 pointer-events-none' : 'pointer-events-auto'}>
|
||||
<div className='flex flex-col bg-gray-50 -space-y-1 px-5 py-3 rounded-md mt-3 mb-3 '>
|
||||
<h1 className='font-bold text-xl text-gray-800'>Invite codes</h1>
|
||||
<h2 className='text-gray-500 text-md'>Invite codes can be copied and used to join your organization </h2>
|
||||
</div>
|
||||
<table className="table-auto w-full text-left whitespace-nowrap rounded-md overflow-hidden">
|
||||
<thead className='bg-gray-100 text-gray-500 rounded-xl uppercase'>
|
||||
<tr className='font-bolder text-sm'>
|
||||
<th className='py-3 px-4'>Code</th>
|
||||
<th className='py-3 px-4'>Signup link</th>
|
||||
<th className='py-3 px-4'>Expiration date</th>
|
||||
<th className='py-3 px-4'>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<>
|
||||
<tbody className='mt-5 bg-white rounded-md' >
|
||||
{invites?.map((invite: any) => (
|
||||
<tr key={invite.invite_code_uuid} className='border-b border-gray-100 text-sm'>
|
||||
<td className='py-3 px-4'>{invite.invite_code}</td>
|
||||
<td className='py-3 px-4 '>
|
||||
<Link className='outline bg-gray-50 text-gray-600 px-2 py-1 rounded-md outline-gray-300 outline-dashed outline-1' target='_blank' href={getUriWithOrg(org?.slug, `/signup?inviteCode=${invite.invite_code}`)}>
|
||||
{getUriWithOrg(org?.slug, `/signup?inviteCode=${invite.invite_code}`)}
|
||||
</Link>
|
||||
</td>
|
||||
<td className='py-3 px-4'>{dayjs(invite.expiration_date).add(1, 'year').format('DD/MM/YYYY')} </td>
|
||||
<td className='py-3 px-4'>
|
||||
<ConfirmationModal
|
||||
confirmationButtonText='Delete Code'
|
||||
confirmationMessage='Are you sure you want remove this invite code ?'
|
||||
dialogTitle={'Delete code ?'}
|
||||
dialogTrigger={
|
||||
<button className='mr-2 flex space-x-2 hover:cursor-pointer p-1 px-3 bg-rose-700 rounded-md font-bold items-center text-sm text-rose-100'>
|
||||
<X className='w-4 h-4' />
|
||||
<span> Delete code</span>
|
||||
</button>}
|
||||
functionToExecute={() => { deleteInvite(invite) }}
|
||||
status='warning'
|
||||
></ConfirmationModal>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</>
|
||||
</table>
|
||||
<button onClick={() => createInvite()} className='mt-3 mr-2 flex space-x-2 hover:cursor-pointer p-1 px-3 bg-green-700 rounded-md font-bold items-center text-sm text-green-100'>
|
||||
<Shield className='w-4 h-4' />
|
||||
<span> Create invite code</span>
|
||||
</button>
|
||||
</div>
|
||||
</div></>) : <PageLoading />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrgAccess
|
||||
120
apps/web/components/Dashboard/Users/OrgUsers/OrgUsers.tsx
Normal file
120
apps/web/components/Dashboard/Users/OrgUsers/OrgUsers.tsx
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
import { useOrg } from '@components/Contexts/OrgContext';
|
||||
import PageLoading from '@components/Objects/Loaders/PageLoading';
|
||||
import RolesUpdate from '@components/Objects/Modals/Dash/OrgUsers/RolesUpdate';
|
||||
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal';
|
||||
import Modal from '@components/StyledElements/Modal/Modal';
|
||||
import Toast from '@components/StyledElements/Toast/Toast';
|
||||
import { getAPIUrl } from '@services/config/config';
|
||||
import { removeUserFromOrg } from '@services/organizations/orgs';
|
||||
import { swrFetcher } from '@services/utils/ts/requests';
|
||||
import { KeyRound, LogOut, X } from 'lucide-react';
|
||||
import React, { use, useEffect } from 'react'
|
||||
import toast from 'react-hot-toast';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
|
||||
function OrgUsers() {
|
||||
const org = useOrg() as any;
|
||||
const { data: orgUsers } = useSWR(org ? `${getAPIUrl()}orgs/${org?.id}/users` : null, swrFetcher);
|
||||
const [rolesModal, setRolesModal] = React.useState(false);
|
||||
const [selectedUser, setSelectedUser] = React.useState(null) as any;
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
|
||||
const handleRolesModal = (user_uuid: any) => {
|
||||
setSelectedUser(user_uuid);
|
||||
setRolesModal(!rolesModal);
|
||||
}
|
||||
|
||||
const handleRemoveUser = async (user_id: any) => {
|
||||
const res = await removeUserFromOrg(org.id, user_id);
|
||||
if (res.status === 200) {
|
||||
await mutate(`${getAPIUrl()}orgs/${org.id}/users`);
|
||||
}
|
||||
else {
|
||||
toast.error('Error ' + res.status + ': ' + res.data.detail)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (orgUsers) {
|
||||
setIsLoading(false)
|
||||
console.log(orgUsers)
|
||||
}
|
||||
}, [org, orgUsers])
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading ? <div><PageLoading /></div> :
|
||||
|
||||
<>
|
||||
<Toast></Toast>
|
||||
<div className="h-6"></div>
|
||||
<div className='ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-4 py-4 '>
|
||||
<div className='flex flex-col bg-gray-50 -space-y-1 px-5 py-3 rounded-md mb-3 '>
|
||||
<h1 className='font-bold text-xl text-gray-800'>Active users</h1>
|
||||
<h2 className='text-gray-500 text-md'> Manage your organization users, assign roles and permissions </h2>
|
||||
</div>
|
||||
<table className="table-auto w-full text-left whitespace-nowrap rounded-md overflow-hidden">
|
||||
<thead className='bg-gray-100 text-gray-500 rounded-xl uppercase'>
|
||||
<tr className='font-bolder text-sm'>
|
||||
<th className='py-3 px-4'>User</th>
|
||||
<th className='py-3 px-4'>Role</th>
|
||||
<th className='py-3 px-4'>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<>
|
||||
<tbody className='mt-5 bg-white rounded-md' >
|
||||
{orgUsers?.map((user: any) => (
|
||||
<tr key={user.user.id} className='border-b border-gray-200 border-dashed'>
|
||||
<td className='py-3 px-4 flex space-x-2 items-center'>
|
||||
<span>{user.user.first_name + ' ' + user.user.last_name}</span>
|
||||
<span className='text-xs bg-neutral-100 p-1 px-2 rounded-full text-neutral-400 font-semibold'>@{user.user.username}</span>
|
||||
</td>
|
||||
<td className='py-3 px-4'>{user.role.name}</td>
|
||||
<td className='py-3 px-4 flex space-x-2 items-end'>
|
||||
<Modal
|
||||
isDialogOpen={rolesModal && selectedUser === user.user.user_uuid}
|
||||
onOpenChange={() => handleRolesModal(user.user.user_uuid)}
|
||||
minHeight="no-min"
|
||||
dialogContent={
|
||||
<RolesUpdate
|
||||
alreadyAssignedRole={user.role.role_uuid}
|
||||
setRolesModal={setRolesModal}
|
||||
user={user} />
|
||||
}
|
||||
dialogTitle="Update Role"
|
||||
dialogDescription={"Update @" + user.user.username + "'s role"}
|
||||
dialogTrigger={
|
||||
<button className='flex space-x-2 hover:cursor-pointer p-1 px-3 bg-yellow-700 rounded-md font-bold items-center text-sm text-yellow-100'>
|
||||
<KeyRound className='w-4 h-4' />
|
||||
<span> Edit Role</span>
|
||||
</button>}
|
||||
/>
|
||||
|
||||
|
||||
<ConfirmationModal
|
||||
confirmationButtonText='Remove User'
|
||||
confirmationMessage='Are you sure you want remove this user from the organization?'
|
||||
dialogTitle={'Delete ' + user.user.username + ' ?'}
|
||||
dialogTrigger={
|
||||
<button className='mr-2 flex space-x-2 hover:cursor-pointer p-1 px-3 bg-rose-700 rounded-md font-bold items-center text-sm text-rose-100'>
|
||||
<LogOut className='w-4 h-4' />
|
||||
<span> Remove from organization</span>
|
||||
</button>}
|
||||
functionToExecute={() => { handleRemoveUser(user.user.id) }}
|
||||
status='warning'
|
||||
></ConfirmationModal>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
))}
|
||||
</tbody>
|
||||
</>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrgUsers
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { useSession } from '@components/Contexts/SessionContext'
|
||||
import { sendActivityAIChatMessage, startActivityAIChatSession } from '@services/ai/ai';
|
||||
import { AlertTriangle, BadgeInfo, NotebookTabs } from 'lucide-react';
|
||||
import { AlertTriangle, BadgeInfo, NotebookTabs, User } from 'lucide-react';
|
||||
import Avvvatars from 'avvvatars-react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { FlaskConical, Keyboard, MessageCircle, MessageSquareIcon, Sparkle, Sparkles, X } from 'lucide-react'
|
||||
|
|
@ -13,6 +13,7 @@ import { AIChatBotStateTypes, useAIChatBot, useAIChatBotDispatch } from '@compon
|
|||
import FeedbackModal from '@components/Objects/Modals/Feedback/Feedback';
|
||||
import Modal from '@components/StyledElements/Modal/Modal';
|
||||
import useGetAIFeatures from '../../../AI/Hooks/useGetAIFeatures';
|
||||
import UserAvatar from '@components/Objects/UserAvatar';
|
||||
|
||||
|
||||
type AIActivityAskProps = {
|
||||
|
|
@ -172,7 +173,7 @@ function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) {
|
|||
</div>
|
||||
<div className='bg-white/5 text-white/40 py-0.5 px-3 flex space-x-1 rounded-full items-center'>
|
||||
<FlaskConical size={14} />
|
||||
<span className='text-xs font-semibold '>Experimental</span>
|
||||
<span className='text-xs font-semibold antialiased '>Experimental</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -204,7 +205,7 @@ function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) {
|
|||
}
|
||||
<div className='flex space-x-2 items-center'>
|
||||
<div className=''>
|
||||
<Avvvatars radius={3} border borderColor='white' borderSize={3} size={35} value={session.user.user_uuid} style="shape" />
|
||||
<UserAvatar rounded='rounded-lg' border='border-2' width={35} />
|
||||
</div>
|
||||
<div className='w-full'>
|
||||
<input onKeyDown={handleKeyDown} onChange={handleChange} disabled={aiChatBotState.isWaitingForResponse} value={aiChatBotState.chatInputValue} placeholder='Ask AI About this Lecture' type="text" className={inputClass} name="" id="" />
|
||||
|
|
@ -235,7 +236,11 @@ function AIMessage(props: AIMessageProps) {
|
|||
return (
|
||||
<div className='flex space-x-2 w-full antialiased font-medium'>
|
||||
<div className=''>
|
||||
<Avvvatars radius={3} border borderColor='white' borderSize={3} size={35} value={props.message.type == 'ai' ? 'ai' : session.user.user_uuid} style="shape" />
|
||||
{props.message.sender == 'ai' ? (
|
||||
<UserAvatar rounded='rounded-lg' border='border-2' predefined_avatar='ai' width={35} />
|
||||
) : (
|
||||
<UserAvatar rounded='rounded-lg' border='border-2' width={35} />
|
||||
)}
|
||||
</div>
|
||||
<div className='w-full'>
|
||||
<p className='w-full rounded-lg outline-none px-2 py-1 text-white text-md placeholder:text-white/30' id="">
|
||||
|
|
@ -277,7 +282,8 @@ const AIMessagePlaceHolder = (props: { activity_uuid: string, sendMessage: any }
|
|||
<Image width={100} className='mx-auto' src={learnhouseAI_logo_black} alt="" />
|
||||
<p className='pt-3 text-2xl font-semibold text-white/70 flex justify-center space-x-2 items-center'>
|
||||
<span className='items-center'>Hello</span>
|
||||
<span className='capitalize flex space-x-2 items-center'> <Avvvatars radius={3} border borderColor='white' borderSize={3} size={25} value={session.user.user_uuid} style="shape" />
|
||||
<span className='capitalize flex space-x-2 items-center'>
|
||||
<UserAvatar rounded='rounded-lg' border='border-2' width={35} />
|
||||
<span>{session.user.username},</span>
|
||||
</span>
|
||||
<span>how can we help today ?</span>
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ function AIEditorToolkit(props: AIEditorToolkitProps) {
|
|||
<div className='pr-1'>
|
||||
<div className='flex w-full space-x-2 font-bold text-white/80 items-center'>
|
||||
<Image className='outline outline-1 outline-neutral-200/20 rounded-lg' width={24} src={learnhouseAI_icon} alt="" />
|
||||
<div >AI Editor</div>
|
||||
<div className='flex items-center'>AI Editor <span className='text-[10px] px-2 py-1 rounded-3xl ml-3 bg-white/10 uppercase'>PRE-ALPHA</span></div>
|
||||
<MoreVertical className='text-white/50' size={12} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import { CourseProvider } from "@components/Contexts/CourseContext";
|
|||
import { useSession } from "@components/Contexts/SessionContext";
|
||||
import AIEditorToolkit from "./AI/AIEditorToolkit";
|
||||
import useGetAIFeatures from "@components/AI/Hooks/useGetAIFeatures";
|
||||
import UserAvatar from "../UserAvatar";
|
||||
|
||||
|
||||
interface Editor {
|
||||
|
|
@ -163,7 +164,7 @@ function Editor(props: Editor) {
|
|||
<Link href="/">
|
||||
<EditorInfoLearnHouseLogo width={25} height={25} src={learnhouseIcon} alt="" />
|
||||
</Link>
|
||||
<Link target="_blank" href={`/course/${course_uuid}/edit`}>
|
||||
<Link target="_blank" href={`/course/${course_uuid}`}>
|
||||
<EditorInfoThumbnail src={`${getCourseThumbnailMediaDirectory(props.org?.org_uuid, props.course.course_uuid, props.course.thumbnail_image)}`} alt=""></EditorInfoThumbnail>
|
||||
</Link>
|
||||
<EditorInfoDocName>
|
||||
|
|
@ -207,7 +208,7 @@ function Editor(props: Editor) {
|
|||
|
||||
<EditorUserProfileWrapper>
|
||||
{!session.isAuthenticated && <span>Loading</span>}
|
||||
{session.isAuthenticated && <Avvvatars value={session.user.user_uuid} style="shape" />}
|
||||
{session.isAuthenticated && <UserAvatar width={40} border="border-4" rounded="rounded-full"/>}
|
||||
</EditorUserProfileWrapper>
|
||||
|
||||
</EditorUsersSection>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { usePathname } from "next/navigation";
|
|||
import { useRouter } from "next/router";
|
||||
import path from "path";
|
||||
import { Settings } from "lucide-react";
|
||||
import UserAvatar from "@components/Objects/UserAvatar";
|
||||
|
||||
export interface Auth {
|
||||
access_token: string;
|
||||
|
|
@ -91,7 +92,7 @@ function ProfileArea() {
|
|||
<AccountArea>
|
||||
<div>{auth.userInfo.user_object.username}</div>
|
||||
<div>
|
||||
<Avvvatars value={auth.userInfo.user_object.user_id} style="shape" />
|
||||
<UserAvatar width={40} />
|
||||
</div>
|
||||
<Link href={"/dash"}><Settings /></Link>
|
||||
</AccountArea>
|
||||
|
|
|
|||
|
|
@ -111,22 +111,22 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
|
|||
<FormMessage match="valueMissing">Please provide a thumbnail for your course</FormMessage>
|
||||
</Flex>
|
||||
<Form.Control asChild>
|
||||
<Input onChange={handleThumbnailChange} type="file" required />
|
||||
<Input onChange={handleThumbnailChange} type="file" />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
<FormField name="course-tags">
|
||||
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||
<FormLabel>Course tags (separated by comma)</FormLabel>
|
||||
<FormLabel>Course Learnings (separated by comma)</FormLabel>
|
||||
<FormMessage match="valueMissing">Please provide learning elements, separated by comma (,)</FormMessage>
|
||||
</Flex>
|
||||
<Form.Control asChild>
|
||||
<Textarea onChange={handleTagsChange} required />
|
||||
<Textarea onChange={handleTagsChange} />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
<FormField name="course-visibility">
|
||||
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||
<FormLabel>Course Visibility</FormLabel>
|
||||
<FormMessage match="valueMissing">Please choose cours visibility</FormMessage>
|
||||
<FormMessage match="valueMissing">Please choose course visibility</FormMessage>
|
||||
</Flex>
|
||||
<Form.Control asChild>
|
||||
<select onChange={handleVisibilityChange} className='border border-gray-300 rounded-md p-2' required>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
'use client';
|
||||
import { useOrg } from '@components/Contexts/OrgContext';
|
||||
import FormLayout, { ButtonBlack, Flex, FormField, FormLabel, Input, Textarea } from '@components/StyledElements/Form/Form'
|
||||
import * as Form from '@radix-ui/react-form'
|
||||
import { FormMessage } from "@radix-ui/react-form";
|
||||
import { getAPIUrl } from '@services/config/config';
|
||||
import { updateUserRole } from '@services/organizations/orgs';
|
||||
import { swrFetcher } from '@services/utils/ts/requests';
|
||||
import React, { useEffect } from 'react'
|
||||
import { BarLoader } from 'react-spinners';
|
||||
import { mutate } from 'swr';
|
||||
|
||||
interface Props {
|
||||
user: any
|
||||
setRolesModal: any
|
||||
alreadyAssignedRole: any
|
||||
}
|
||||
|
||||
function RolesUpdate(props: Props) {
|
||||
const org = useOrg() as any;
|
||||
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
||||
const [assignedRole, setAssignedRole] = React.useState(props.alreadyAssignedRole);
|
||||
const [error, setError] = React.useState(null) as any;
|
||||
|
||||
const handleAssignedRole = (event: React.ChangeEvent<any>) => {
|
||||
setError(null);
|
||||
setAssignedRole(event.target.value);
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: any) => {
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
const res = await updateUserRole(org.id, props.user.user.id, assignedRole);
|
||||
|
||||
if (res.status === 200) {
|
||||
await mutate(`${getAPIUrl()}orgs/${org.id}/users`);
|
||||
props.setRolesModal(false);
|
||||
}
|
||||
else {
|
||||
setIsSubmitting(false);
|
||||
setError('Error ' + res.status + ': ' + res.data.detail);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
}
|
||||
, [assignedRole])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormLayout onSubmit={handleSubmit}>
|
||||
<FormField name="course-visibility">
|
||||
{error ? <div className='text-red-500 font-bold text-xs px-3 py-2 bg-red-100 rounded-md'>{error}</div> : ''}
|
||||
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||
<FormLabel>Roles</FormLabel>
|
||||
<FormMessage match="valueMissing">Please choose a role for the user</FormMessage>
|
||||
</Flex>
|
||||
<Form.Control asChild>
|
||||
<select onChange={handleAssignedRole} defaultValue={assignedRole} className='border border-gray-300 rounded-md p-2' required>
|
||||
<option value="role_global_admin">Admin </option>
|
||||
<option value="role_global_maintainer">Maintainer</option>
|
||||
<option value="role_global_user">User</option>
|
||||
</select>
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
<div className='h-full'></div>
|
||||
<Flex css={{ marginTop: 25, justifyContent: 'flex-end' }}>
|
||||
<Form.Submit asChild>
|
||||
<ButtonBlack type="submit" css={{ marginTop: 10 }}>
|
||||
{isSubmitting ? <BarLoader cssOverride={{ borderRadius: 60, }} width={60} color="#ffffff" />
|
||||
: "Update user role"}
|
||||
</ButtonBlack>
|
||||
</Form.Submit>
|
||||
</Flex>
|
||||
</FormLayout>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RolesUpdate
|
||||
|
|
@ -41,9 +41,10 @@ function CourseThumbnail(props: PropsType) {
|
|||
<div className='relative'>
|
||||
<AdminEditsArea course={props.course} orgSlug={props.orgslug} courseId={props.course.course_uuid} deleteCourses={deleteCourses} />
|
||||
<Link href={getUriWithOrg(props.orgslug, "/course/" + removeCoursePrefix(props.course.course_uuid))}>
|
||||
<div className="inset-0 ring-1 ring-inset ring-black/10 rounded-xl shadow-xl w-[249px] h-[131px] bg-cover" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(org?.org_uuid, props.course.course_uuid, props.course.thumbnail_image)})` }}>
|
||||
|
||||
</div>
|
||||
{props.course.thumbnail_image ? <div className="inset-0 ring-1 ring-inset ring-black/10 rounded-xl shadow-xl w-[249px] h-[131px] bg-cover" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(org?.org_uuid, props.course.course_uuid, props.course.thumbnail_image)})` }} />
|
||||
: <div className="inset-0 ring-1 ring-inset ring-black/10 rounded-xl shadow-xl w-[249px] h-[131px] bg-cover" style={{ backgroundImage: `url('../empty_thumbnail.png')` , backgroundSize:'contain' }} />}
|
||||
|
||||
</Link>
|
||||
<h2 className="font-bold text-lg w-[250px] py-2">{props.course.name}</h2>
|
||||
</div>
|
||||
|
|
|
|||
65
apps/web/components/Objects/UserAvatar.tsx
Normal file
65
apps/web/components/Objects/UserAvatar.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { useSession } from '@components/Contexts/SessionContext';
|
||||
import emptyAvatar from '@public/empty_avatar.png';
|
||||
import aiAvatar from '@public/ai_avatar.png';
|
||||
import Image from 'next/image';
|
||||
import React, { use, useEffect } from 'react'
|
||||
import { getUriWithOrg } from '@services/config/config';
|
||||
import { useOrg } from '@components/Contexts/OrgContext';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { getUserAvatarMediaDirectory } from '@services/media/media';
|
||||
|
||||
type UserAvatarProps = {
|
||||
width?: number
|
||||
avatar_url?: string
|
||||
use_with_session?: boolean
|
||||
rounded?: 'rounded-md' | 'rounded-xl' | 'rounded-lg' | 'rounded-full' | 'rounded'
|
||||
border?: 'border-2' | 'border-4' | 'border-8'
|
||||
borderColor? : string
|
||||
predefined_avatar?: 'ai'
|
||||
}
|
||||
|
||||
function UserAvatar(props: UserAvatarProps) {
|
||||
const session = useSession() as any;
|
||||
const params = useParams() as any;
|
||||
|
||||
const predefinedAvatar = props.predefined_avatar === 'ai' ? getUriWithOrg(params.orgslug,'/ai_avatar.png') : null;
|
||||
const emptyAvatar = getUriWithOrg(params.orgslug,'/empty_avatar.png') as any;
|
||||
const uploadedAvatar = getUserAvatarMediaDirectory(session.user.user_uuid,session.user.avatar_image) as any;
|
||||
|
||||
const useAvatar = () => {
|
||||
if (props.predefined_avatar) {
|
||||
return predefinedAvatar
|
||||
} else {
|
||||
if (props.avatar_url) {
|
||||
console.log('avatar_url',props.avatar_url)
|
||||
return props.avatar_url
|
||||
}
|
||||
else {
|
||||
if (session.user.avatar_image) {
|
||||
return uploadedAvatar
|
||||
}
|
||||
else {
|
||||
return emptyAvatar
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
console.log('params', params)
|
||||
}
|
||||
, [session])
|
||||
|
||||
return (
|
||||
<img
|
||||
alt='User Avatar'
|
||||
width={props.width ? props.width : 50}
|
||||
height={props.width ? props.width : 50}
|
||||
src={useAvatar()}
|
||||
className={`${props.avatar_url && session.user.avatar_image ? '' : 'bg-gray-700'} ${props.border ? 'border ' + props.border : ''} ${props.borderColor ? props.borderColor : 'border-white'} shadow-xl aspect-square w-[${props.width ? props.width : 50}px] h-[${props.width ? props.width : 50}px] ${props.rounded ? props.rounded : 'rounded-xl'}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserAvatar
|
||||
139
apps/web/components/Security/AdminAuthorization.tsx
Normal file
139
apps/web/components/Security/AdminAuthorization.tsx
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
'use client';
|
||||
import { useOrg } from '@components/Contexts/OrgContext';
|
||||
import { useSession } from '@components/Contexts/SessionContext';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React from 'react'
|
||||
|
||||
type AuthorizationProps = {
|
||||
children: React.ReactNode;
|
||||
// Authorize components rendering or page rendering
|
||||
authorizationMode: 'component' | 'page';
|
||||
}
|
||||
|
||||
const ADMIN_PATHS = [
|
||||
'/dash/org/*',
|
||||
'/dash/org',
|
||||
'/dash/users/*',
|
||||
'/dash/users',
|
||||
'/dash/courses/*',
|
||||
'/dash/courses',
|
||||
'/dash/org/settings/general',
|
||||
]
|
||||
|
||||
function AdminAuthorization(props: AuthorizationProps) {
|
||||
const session = useSession() as any;
|
||||
const org = useOrg() as any;
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
|
||||
// States
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
const [isAuthorized, setIsAuthorized] = React.useState(false);
|
||||
|
||||
|
||||
// Verify if the user is authenticated
|
||||
const isUserAuthenticated = () => {
|
||||
if (session.isAuthenticated === true) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify if the user is an Admin (1), Maintainer (2) or Member (3) of the organization
|
||||
const isUserAdmin = () => {
|
||||
const isAdmin = session.roles.some((role: any) => {
|
||||
return (
|
||||
role.org.id === org.id &&
|
||||
(role.role.id === 1 ||
|
||||
role.role.id === 2 ||
|
||||
role.role.role_uuid === 'role_global_admin' ||
|
||||
role.role.role_uuid === 'role_global_maintainer'
|
||||
)
|
||||
);
|
||||
});
|
||||
return isAdmin;
|
||||
};
|
||||
|
||||
function checkPathname(pattern: string, pathname: string) {
|
||||
// Escape special characters in the pattern and replace '*' with a regex pattern
|
||||
const regexPattern = new RegExp(`^${pattern.replace(/\//g, '\\/').replace(/\*/g, '.*')}$`);
|
||||
|
||||
// Test if the pathname matches the regex pattern
|
||||
const isMatch = regexPattern.test(pathname);
|
||||
|
||||
return isMatch;
|
||||
}
|
||||
|
||||
|
||||
const Authorize = () => {
|
||||
if (props.authorizationMode === 'page') {
|
||||
|
||||
// Check if user is in an admin path
|
||||
if (ADMIN_PATHS.some((path) => checkPathname(path, pathname))) {
|
||||
console.log('Admin path')
|
||||
if (isUserAuthenticated()) {
|
||||
// Check if the user is an Admin
|
||||
if (isUserAdmin()) {
|
||||
setIsAuthorized(true);
|
||||
}
|
||||
else {
|
||||
setIsAuthorized(false);
|
||||
router.push('/dash');
|
||||
}
|
||||
}
|
||||
else {
|
||||
router.push('/login');
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
if (isUserAuthenticated()) {
|
||||
setIsAuthorized(true);
|
||||
}
|
||||
else {
|
||||
setIsAuthorized(false);
|
||||
router.push('/login');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (props.authorizationMode === 'component') {
|
||||
// Component mode
|
||||
if (isUserAuthenticated() && isUserAdmin()) {
|
||||
setIsAuthorized(true);
|
||||
}
|
||||
else {
|
||||
setIsAuthorized(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (session.isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
Authorize();
|
||||
setIsLoading(false);
|
||||
}, [session, org, pathname])
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
{props.authorizationMode === 'component' && isAuthorized === true && props.children}
|
||||
{props.authorizationMode === 'page' && isAuthorized === true && !isLoading && props.children}
|
||||
{props.authorizationMode === 'page' && isAuthorized === false && !isLoading &&
|
||||
<div className='flex justify-center items-center h-screen'>
|
||||
<h1 className='text-2xl'>You are not authorized to access this page</h1>
|
||||
</div>
|
||||
}
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AdminAuthorization
|
||||
|
|
@ -1,8 +1,5 @@
|
|||
'use client';
|
||||
import React from "react";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { getAPIUrl } from "@services/config/config";
|
||||
import { swrFetcher } from "@services/utils/ts/requests";
|
||||
import { useSession } from "@components/Contexts/SessionContext";
|
||||
import { useOrg } from "@components/Contexts/OrgContext";
|
||||
|
||||
|
|
@ -46,12 +43,17 @@ export const AuthenticatedClientElement = (props: AuthenticatedClientElementProp
|
|||
}
|
||||
|
||||
function check() {
|
||||
|
||||
if (session.isAuthenticated === false) {
|
||||
setIsAllowed(false);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (props.checkMethod === 'authentication') {
|
||||
setIsAllowed(session.isAuthenticated);
|
||||
} else if (props.checkMethod === 'roles') {
|
||||
return setIsAllowed(isUserAllowed(session.roles, props.action!, props.ressourceType!, org.org_uuid));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
'use client';
|
||||
import React, { use, useEffect } from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import styled from "styled-components";
|
||||
import Link from "next/link";
|
||||
import Avvvatars from "avvvatars-react";
|
||||
import { GearIcon } from "@radix-ui/react-icons";
|
||||
import { Settings } from "lucide-react";
|
||||
import { useSession } from "@components/Contexts/SessionContext";
|
||||
import UserAvatar from "@components/Objects/UserAvatar";
|
||||
|
||||
export const HeaderProfileBox = () => {
|
||||
const session = useSession() as any;
|
||||
|
|
@ -33,9 +34,7 @@ export const HeaderProfileBox = () => {
|
|||
<div className="flex items-center space-x-2">
|
||||
<div className="text-xs">{session.user.username} </div>
|
||||
<div className="py-4">
|
||||
<div className="shadow-sm rounded-xl">
|
||||
<Avvvatars radius={3} size={30} value={session.user.user_uuid} style="shape" />
|
||||
</div>
|
||||
<UserAvatar border="border-4" rounded='rounded-lg' width={30} />
|
||||
</div>
|
||||
<Link className="text-gray-600" href={"/dash"}><Settings size={14} /></Link>
|
||||
</div>
|
||||
|
|
@ -51,7 +50,6 @@ const AccountArea = styled.div`
|
|||
|
||||
img {
|
||||
width: 29px;
|
||||
border-radius: 19px;
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import { XCircle } from 'lucide-react'
|
||||
import React from 'react'
|
||||
|
||||
function ErrorUI() {
|
||||
return (
|
||||
<div className='flex items-center justify-center h-screen'>
|
||||
<div className='mx-auto bg-red-100 w-[800px] p-3 rounded-xl m-5 '>
|
||||
<div className='flex flex-row'>
|
||||
<div className='p-3 pr-4' >
|
||||
<svg width="35" height="35" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 2H5C4.20435 2 3.44129 2.31607 2.87868 2.87868C2.31607 3.44129 2 4.20435 2 5V15C2 15.7956 2.31607 16.5587 2.87868 17.1213C3.44129 17.6839 4.20435 18 5 18H16.59L20.29 21.71C20.3834 21.8027 20.4943 21.876 20.6161 21.9258C20.7379 21.9755 20.8684 22.0008 21 22C21.1312 22.0034 21.2613 21.976 21.38 21.92C21.5626 21.845 21.7189 21.7176 21.8293 21.5539C21.9396 21.3901 21.999 21.1974 22 21V5C22 4.20435 21.6839 3.44129 21.1213 2.87868C20.5587 2.31607 19.7956 2 19 2ZM20 18.59L17.71 16.29C17.6166 16.1973 17.5057 16.124 17.3839 16.0742C17.2621 16.0245 17.1316 15.9992 17 16H5C4.73478 16 4.48043 15.8946 4.29289 15.7071C4.10536 15.5196 4 15.2652 4 15V5C4 4.73478 4.10536 4.48043 4.29289 4.29289C4.48043 4.10536 4.73478 4 5 4H19C19.2652 4 19.5196 4.10536 19.7071 4.29289C19.8946 4.48043 20 4.73478 20 5V18.59ZM12 12C11.8022 12 11.6089 12.0586 11.4444 12.1685C11.28 12.2784 11.1518 12.4346 11.0761 12.6173C11.0004 12.8 10.9806 13.0011 11.0192 13.1951C11.0578 13.3891 11.153 13.5673 11.2929 13.7071C11.4327 13.847 11.6109 13.9422 11.8049 13.9808C11.9989 14.0194 12.2 13.9996 12.3827 13.9239C12.5654 13.8482 12.7216 13.72 12.8315 13.5556C12.9414 13.3911 13 13.1978 13 13C13 12.7348 12.8946 12.4804 12.7071 12.2929C12.5196 12.1054 12.2652 12 12 12ZM12 6C11.7348 6 11.4804 6.10536 11.2929 6.29289C11.1054 6.48043 11 6.73478 11 7V10C11 10.2652 11.1054 10.5196 11.2929 10.7071C11.4804 10.8946 11.7348 11 12 11C12.2652 11 12.5196 10.8946 12.7071 10.7071C12.8946 10.5196 13 10.2652 13 10V7C13 6.73478 12.8946 6.48043 12.7071 6.29289C12.5196 6.10536 12.2652 6 12 6Z" fill="#CC0505" />
|
||||
</svg>
|
||||
<div className='p-3 pr-4 items-center' >
|
||||
<XCircle size={40} className='text-red-600' />
|
||||
</div>
|
||||
<div className='p-3 '>
|
||||
<h1 className='text-2xl font-bold text-red-600'>Error</h1>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { blackA, violet, mauve } from '@radix-ui/colors';
|
|||
import { Info } from 'lucide-react';
|
||||
|
||||
const FormLayout = (props: any, onSubmit: any) => (
|
||||
<FormRoot onSubmit={props.onSubmit}>
|
||||
<FormRoot className='h-fit' onSubmit={props.onSubmit}>
|
||||
{props.children}
|
||||
</FormRoot>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const Modal = (params: ModalParams) => (
|
|||
|
||||
<Dialog.Portal>
|
||||
<DialogOverlay />
|
||||
<DialogContent minHeight={params.minHeight}>
|
||||
<DialogContent className='overflow-auto scrollbar-w-2 scrollbar-h-2 scrollbar scrollbar-thumb-black/20 scrollbar-thumb-rounded-full scrollbar-track-rounded-full' minHeight={params.minHeight}>
|
||||
<DialogTopBar className='-space-y-1'>
|
||||
<DialogTitle>{params.dialogTitle}</DialogTitle>
|
||||
<DialogDescription>
|
||||
|
|
@ -118,7 +118,6 @@ const DialogContent = styled(Dialog.Content, {
|
|||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: '90vw',
|
||||
overflow: 'hidden',
|
||||
maxHeight: '85vh',
|
||||
minHeight: '300px',
|
||||
maxWidth: '600px',
|
||||
|
|
|
|||
6
apps/web/package-lock.json
generated
6
apps/web/package-lock.json
generated
|
|
@ -25,6 +25,7 @@
|
|||
"@tiptap/react": "^2.0.0-beta.199",
|
||||
"@tiptap/starter-kit": "^2.0.0-beta.199",
|
||||
"avvvatars-react": "^0.4.2",
|
||||
"dayjs": "^1.11.10",
|
||||
"formik": "^2.2.9",
|
||||
"framer-motion": "^10.16.1",
|
||||
"lowlight": "^3.0.0",
|
||||
|
|
@ -4948,6 +4949,11 @@
|
|||
"integrity": "sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.10",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
|
||||
"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
"@radix-ui/react-icons": "^1.1.1",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tooltip": "^1.0.5",
|
||||
"@sentry/nextjs": "^7.92.0",
|
||||
"@sentry/nextjs": "^7.93.0",
|
||||
"@stitches/react": "^1.2.8",
|
||||
"@tiptap/extension-code-block-lowlight": "^2.1.11",
|
||||
"@tiptap/extension-collaboration": "^2.0.0-beta.199",
|
||||
|
|
@ -26,6 +26,7 @@
|
|||
"@tiptap/react": "^2.0.0-beta.199",
|
||||
"@tiptap/starter-kit": "^2.0.0-beta.199",
|
||||
"avvvatars-react": "^0.4.2",
|
||||
"dayjs": "^1.11.10",
|
||||
"formik": "^2.2.9",
|
||||
"framer-motion": "^10.16.1",
|
||||
"lowlight": "^3.0.0",
|
||||
|
|
|
|||
BIN
apps/web/public/ai_avatar.png
Normal file
BIN
apps/web/public/ai_avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
apps/web/public/empty_avatar.png
Normal file
BIN
apps/web/public/empty_avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
BIN
apps/web/public/empty_thumbnail.png
Normal file
BIN
apps/web/public/empty_thumbnail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
|
|
@ -4,8 +4,11 @@
|
|||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
|
||||
if (process.env.NODE_ENV !== "development") {
|
||||
const DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
|
||||
|
||||
Sentry.init({
|
||||
dsn: "https://5a456d54654c494b9a416c19e3b94573@o4505007882436608.ingest.sentry.io/4505008095625216",
|
||||
dsn: DSN,
|
||||
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1,
|
||||
|
|
@ -28,3 +31,4 @@ Sentry.init({
|
|||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
|
||||
// The config you add here will be used whenever one of the edge features is loaded.
|
||||
// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
|
||||
Sentry.init({
|
||||
dsn: "https://5a456d54654c494b9a416c19e3b94573@o4505007882436608.ingest.sentry.io/4505008095625216",
|
||||
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
});
|
||||
|
|
@ -4,12 +4,10 @@
|
|||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
|
||||
if (process.env.NODE_ENV !== "development") {
|
||||
const DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
|
||||
Sentry.init({
|
||||
dsn: "https://5a456d54654c494b9a416c19e3b94573@o4505007882436608.ingest.sentry.io/4505008095625216",
|
||||
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
dsn: DSN,
|
||||
tracesSampleRate: 1.0,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { getAPIUrl } from "@services/config/config";
|
||||
import { NextApiRequestCookies } from "next/dist/server/api-utils";
|
||||
|
||||
interface LoginAndGetTokenResponse {
|
||||
access_token: "string";
|
||||
|
|
@ -83,7 +82,7 @@ export async function getUserSession(token: string): Promise<any> {
|
|||
|
||||
export async function getNewAccessTokenUsingRefreshToken(): Promise<any> {
|
||||
const requestOptions: any = {
|
||||
method: "POST",
|
||||
method: "GET",
|
||||
redirect: "follow",
|
||||
credentials: "include",
|
||||
};
|
||||
|
|
@ -95,7 +94,7 @@ export async function getNewAccessTokenUsingRefreshToken(): Promise<any> {
|
|||
|
||||
export async function getNewAccessTokenUsingRefreshTokenServer(refresh_token_cookie: any): Promise<any> {
|
||||
const requestOptions: any = {
|
||||
method: "POST",
|
||||
method: "GET",
|
||||
redirect: "follow",
|
||||
headers: {
|
||||
Cookie: `refresh_token_cookie=${refresh_token_cookie}`,
|
||||
|
|
@ -139,3 +138,18 @@ export async function signup(body: NewAccountBody): Promise<any> {
|
|||
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function signUpWithInviteCode(body: NewAccountBody,invite_code:string): Promise<any> {
|
||||
const HeadersConfig = new Headers({ "Content-Type": "application/json" });
|
||||
|
||||
const requestOptions: any = {
|
||||
method: "POST",
|
||||
headers: HeadersConfig,
|
||||
body: JSON.stringify(body),
|
||||
redirect: "follow",
|
||||
};
|
||||
|
||||
const res = await fetch(`${getAPIUrl()}users/${body.org_id}/invite/${invite_code}`, requestOptions);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { getAPIUrl } from "@services/config/config";
|
||||
import { RequestBody, RequestBodyForm, RequestBodyWithAuthHeader, errorHandling } from "@services/utils/ts/requests";
|
||||
import { RequestBody, RequestBodyForm, RequestBodyWithAuthHeader, errorHandling, getResponseMetadata } from "@services/utils/ts/requests";
|
||||
|
||||
/*
|
||||
This file includes only POST, PUT, DELETE requests
|
||||
|
|
@ -36,6 +36,14 @@ export async function getCourse(course_uuid: string, next: any) {
|
|||
return res;
|
||||
}
|
||||
|
||||
export async function updateCourseThumbnail(course_uuid: any, thumbnail: any) {
|
||||
const formData = new FormData();
|
||||
formData.append("thumbnail", thumbnail);
|
||||
const result: any = await fetch(`${getAPIUrl()}courses/${course_uuid}/thumbnail`, RequestBodyForm("PUT", formData, null));
|
||||
const res = await getResponseMetadata(result);
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function createNewCourse(org_id: string, course_body: any, thumbnail: any) {
|
||||
// Send file thumbnail as form data
|
||||
const formData = new FormData();
|
||||
|
|
@ -45,7 +53,10 @@ export async function createNewCourse(org_id: string, course_body: any, thumbnai
|
|||
formData.append("learnings", course_body.tags);
|
||||
formData.append("tags", course_body.tags);
|
||||
formData.append("about", course_body.description);
|
||||
|
||||
if (thumbnail) {
|
||||
formData.append("thumbnail", thumbnail);
|
||||
}
|
||||
|
||||
const result = await fetch(`${getAPIUrl()}courses/?org_id=${org_id}`, RequestBodyForm("POST", formData, null));
|
||||
const res = await errorHandling(result);
|
||||
|
|
|
|||
|
|
@ -9,38 +9,43 @@ function getMediaUrl() {
|
|||
}
|
||||
}
|
||||
|
||||
export function getCourseThumbnailMediaDirectory(orgId: string, courseId: string, fileId: string) {
|
||||
let uri = `${getMediaUrl()}content/${orgId}/courses/${courseId}/thumbnails/${fileId}`;
|
||||
export function getCourseThumbnailMediaDirectory(orgUUID: string, courseId: string, fileId: string) {
|
||||
let uri = `${getMediaUrl()}content/orgs/${orgUUID}/courses/${courseId}/thumbnails/${fileId}`;
|
||||
return uri;
|
||||
}
|
||||
|
||||
export function getActivityBlockMediaDirectory(orgId: string, courseId: string, activityId: string, blockId: any, fileId: any, type: string) {
|
||||
export function getUserAvatarMediaDirectory(userUUID: string, fileId: string) {
|
||||
let uri = `${getMediaUrl()}content/users/${userUUID}/avatars/${fileId}`;
|
||||
return uri;
|
||||
}
|
||||
|
||||
export function getActivityBlockMediaDirectory(orgUUID: string, courseId: string, activityId: string, blockId: any, fileId: any, type: string) {
|
||||
if (type == "pdfBlock") {
|
||||
let uri = `${getMediaUrl()}content/${orgId}/courses/${courseId}/activities/${activityId}/dynamic/blocks/pdfBlock/${blockId}/${fileId}`;
|
||||
let uri = `${getMediaUrl()}content/orgs/${orgUUID}/courses/${courseId}/activities/${activityId}/dynamic/blocks/pdfBlock/${blockId}/${fileId}`;
|
||||
return uri;
|
||||
}
|
||||
if (type == "videoBlock") {
|
||||
let uri = `${getMediaUrl()}content/${orgId}/courses/${courseId}/activities/${activityId}/dynamic/blocks/videoBlock/${blockId}/${fileId}`;
|
||||
let uri = `${getMediaUrl()}content/orgs/${orgUUID}/courses/${courseId}/activities/${activityId}/dynamic/blocks/videoBlock/${blockId}/${fileId}`;
|
||||
return uri;
|
||||
}
|
||||
if (type == "imageBlock") {
|
||||
let uri = `${getMediaUrl()}content/${orgId}/courses/${courseId}/activities/${activityId}/dynamic/blocks/imageBlock/${blockId}/${fileId}`;
|
||||
let uri = `${getMediaUrl()}content/orgs/${orgUUID}/courses/${courseId}/activities/${activityId}/dynamic/blocks/imageBlock/${blockId}/${fileId}`;
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
|
||||
export function getActivityMediaDirectory(orgId: string, courseId: string, activityId: string, fileId: string, activityType: string) {
|
||||
export function getActivityMediaDirectory(orgUUID: string, courseId: string, activityId: string, fileId: string, activityType: string) {
|
||||
if (activityType == "video") {
|
||||
let uri = `${getMediaUrl()}content/${orgId}/courses/${courseId}/activities/${activityId}/video/${fileId}`;
|
||||
let uri = `${getMediaUrl()}content/orgs/${orgUUID}/courses/${courseId}/activities/${activityId}/video/${fileId}`;
|
||||
return uri;
|
||||
}
|
||||
if (activityType == "documentpdf") {
|
||||
let uri = `${getMediaUrl()}content/${orgId}/courses/${courseId}/activities/${activityId}/documentpdf/${fileId}`;
|
||||
let uri = `${getMediaUrl()}content/orgs/${orgUUID}/courses/${courseId}/activities/${activityId}/documentpdf/${fileId}`;
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
|
||||
export function getOrgLogoMediaDirectory(orgId: string, fileId: string) {
|
||||
let uri = `${getMediaUrl()}content/${orgId}/logos/${fileId}`;
|
||||
export function getOrgLogoMediaDirectory(orgUUID: string, fileId: string) {
|
||||
let uri = `${getMediaUrl()}content/orgs/${orgUUID}/logos/${fileId}`;
|
||||
return uri;
|
||||
}
|
||||
|
|
|
|||
26
apps/web/services/organizations/invites.ts
Normal file
26
apps/web/services/organizations/invites.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { getAPIUrl } from "@services/config/config";
|
||||
import { RequestBody, errorHandling, getResponseMetadata } from "@services/utils/ts/requests";
|
||||
|
||||
export async function createInviteCode(org_id: any) {
|
||||
const result = await fetch(`${getAPIUrl()}orgs/${org_id}/invites`, RequestBody("POST", null, null));
|
||||
const res = await getResponseMetadata(result);
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function deleteInviteCode(org_id: any, org_invite_code_uuid: string) {
|
||||
const result = await fetch(`${getAPIUrl()}orgs/${org_id}/invites/${org_invite_code_uuid}`, RequestBody("DELETE", null, null));
|
||||
const res = await getResponseMetadata(result);
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function changeSignupMechanism(org_id: any, signup_mechanism: string) {
|
||||
const result = await fetch(`${getAPIUrl()}orgs/${org_id}/signup_mechanism?signup_mechanism=${signup_mechanism}`, RequestBody("PUT", null, null));
|
||||
const res = await getResponseMetadata(result);
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function validateInviteCode(org_id: any, invite_code: string) {
|
||||
const result = await fetch(`${getAPIUrl()}orgs/${org_id}/invites/code/${invite_code}`, RequestBody("GET", null, null));
|
||||
const res = await getResponseMetadata(result);
|
||||
return res;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { getAPIUrl } from "@services/config/config";
|
||||
import { RequestBody, errorHandling } from "@services/utils/ts/requests";
|
||||
import { RequestBody, errorHandling, getResponseMetadata } from "@services/utils/ts/requests";
|
||||
|
||||
/*
|
||||
This file includes only POST, PUT, DELETE requests
|
||||
|
|
@ -49,3 +49,15 @@ export function getOrganizationContextInfoNoAsync(org_slug: any, next: any) {
|
|||
const result = fetch(`${getAPIUrl()}orgs/slug/${org_slug}`, RequestBody("GET", null, next));
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateUserRole(org_id: any, user_id: any, role_uuid: any) {
|
||||
const result = await fetch(`${getAPIUrl()}orgs/${org_id}/users/${user_id}/role/${role_uuid}`, RequestBody("PUT", null, null));
|
||||
const res = await getResponseMetadata(result);
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function removeUserFromOrg(org_id: any, user_id: any) {
|
||||
const result = await fetch(`${getAPIUrl()}orgs/${org_id}/users/${user_id}`, RequestBody("DELETE", null, null));
|
||||
const res = await getResponseMetadata(result);
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,16 @@
|
|||
import { getAPIUrl } from "@services/config/config";
|
||||
import { RequestBody, errorHandling } from "@services/utils/ts/requests";
|
||||
import { RequestBody, RequestBodyForm, errorHandling, getResponseMetadata } from "@services/utils/ts/requests";
|
||||
|
||||
export async function getUser(user_id: string) {
|
||||
const result = await fetch(`${getAPIUrl()}users/user_id/${user_id}`, RequestBody("GET", null, null));
|
||||
const res = await errorHandling(result);
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function updateUserAvatar(user_uuid: any, avatar_file: any) {
|
||||
const formData = new FormData();
|
||||
formData.append("avatar_file", avatar_file);
|
||||
const result: any = await fetch(`${getAPIUrl()}users/update_avatar/${user_uuid}`, RequestBodyForm("PUT", formData, null));
|
||||
const res = await getResponseMetadata(result);
|
||||
return res;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue