4913 lines
209 KiB
Plaintext
4913 lines
209 KiB
Plaintext
3.11-slim: Pulling from library/python
|
||
Digest: sha256:c24e9effa2821a6885165d930d939fec2af0dcf819276138f11dd45e200bd032
|
||
Status: Downloaded newer image for python:3.11-slim
|
||
+ cd backend
|
||
+ pip install flake8 pylint black isort
|
||
Collecting flake8
|
||
Downloading flake8-7.3.0-py2.py3-none-any.whl.metadata (3.8 kB)
|
||
Collecting pylint
|
||
Downloading pylint-4.0.4-py3-none-any.whl.metadata (12 kB)
|
||
Collecting black
|
||
Downloading black-25.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (86 kB)
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 86.4/86.4 kB 496.5 kB/s eta 0:00:00
|
||
Collecting isort
|
||
Downloading isort-7.0.0-py3-none-any.whl.metadata (11 kB)
|
||
Collecting mccabe<0.8.0,>=0.7.0 (from flake8)
|
||
Downloading mccabe-0.7.0-py2.py3-none-any.whl.metadata (5.0 kB)
|
||
Collecting pycodestyle<2.15.0,>=2.14.0 (from flake8)
|
||
Downloading pycodestyle-2.14.0-py2.py3-none-any.whl.metadata (4.5 kB)
|
||
Collecting pyflakes<3.5.0,>=3.4.0 (from flake8)
|
||
Downloading pyflakes-3.4.0-py2.py3-none-any.whl.metadata (3.5 kB)
|
||
Collecting astroid<=4.1.dev0,>=4.0.2 (from pylint)
|
||
Downloading astroid-4.0.3-py3-none-any.whl.metadata (4.4 kB)
|
||
Collecting dill>=0.3.6 (from pylint)
|
||
Downloading dill-0.4.0-py3-none-any.whl.metadata (10 kB)
|
||
Collecting platformdirs>=2.2 (from pylint)
|
||
Downloading platformdirs-4.5.1-py3-none-any.whl.metadata (12 kB)
|
||
Collecting tomlkit>=0.10.1 (from pylint)
|
||
Downloading tomlkit-0.14.0-py3-none-any.whl.metadata (2.8 kB)
|
||
Collecting click>=8.0.0 (from black)
|
||
Downloading click-8.3.1-py3-none-any.whl.metadata (2.6 kB)
|
||
Collecting mypy-extensions>=0.4.3 (from black)
|
||
Downloading mypy_extensions-1.1.0-py3-none-any.whl.metadata (1.1 kB)
|
||
Collecting packaging>=22.0 (from black)
|
||
Downloading packaging-25.0-py3-none-any.whl.metadata (3.3 kB)
|
||
Collecting pathspec>=0.9.0 (from black)
|
||
Downloading pathspec-1.0.3-py3-none-any.whl.metadata (13 kB)
|
||
Collecting pytokens>=0.3.0 (from black)
|
||
Downloading pytokens-0.3.0-py3-none-any.whl.metadata (2.0 kB)
|
||
Downloading flake8-7.3.0-py2.py3-none-any.whl (57 kB)
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 57.9/57.9 kB 688.6 kB/s eta 0:00:00
|
||
Downloading pylint-4.0.4-py3-none-any.whl (536 kB)
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 536.4/536.4 kB 2.3 MB/s eta 0:00:00
|
||
Downloading black-25.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (1.8 MB)
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.8/1.8 MB 7.8 MB/s eta 0:00:00
|
||
Downloading isort-7.0.0-py3-none-any.whl (94 kB)
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 94.7/94.7 kB 1.4 MB/s eta 0:00:00
|
||
Downloading astroid-4.0.3-py3-none-any.whl (276 kB)
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 276.4/276.4 kB 3.4 MB/s eta 0:00:00
|
||
Downloading click-8.3.1-py3-none-any.whl (108 kB)
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 108.3/108.3 kB 1.5 MB/s eta 0:00:00
|
||
Downloading dill-0.4.0-py3-none-any.whl (119 kB)
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 119.7/119.7 kB 1.7 MB/s eta 0:00:00
|
||
Downloading mccabe-0.7.0-py2.py3-none-any.whl (7.3 kB)
|
||
Downloading mypy_extensions-1.1.0-py3-none-any.whl (5.0 kB)
|
||
Downloading packaging-25.0-py3-none-any.whl (66 kB)
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 66.5/66.5 kB 2.2 MB/s eta 0:00:00
|
||
Downloading pathspec-1.0.3-py3-none-any.whl (55 kB)
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 55.0/55.0 kB 918.4 kB/s eta 0:00:00
|
||
Downloading platformdirs-4.5.1-py3-none-any.whl (18 kB)
|
||
Downloading pycodestyle-2.14.0-py2.py3-none-any.whl (31 kB)
|
||
Downloading pyflakes-3.4.0-py2.py3-none-any.whl (63 kB)
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 63.6/63.6 kB 2.1 MB/s eta 0:00:00
|
||
Downloading pytokens-0.3.0-py3-none-any.whl (12 kB)
|
||
Downloading tomlkit-0.14.0-py3-none-any.whl (39 kB)
|
||
Installing collected packages: tomlkit, pytokens, pyflakes, pycodestyle, platformdirs, pathspec, packaging, mypy-extensions, mccabe, isort, dill, click, astroid, pylint, flake8, black
|
||
Successfully installed astroid-4.0.3 black-25.12.0 click-8.3.1 dill-0.4.0 flake8-7.3.0 isort-7.0.0 mccabe-0.7.0 mypy-extensions-1.1.0 packaging-25.0 pathspec-1.0.3 platformdirs-4.5.1 pycodestyle-2.14.0 pyflakes-3.4.0 pylint-4.0.4 pytokens-0.3.0 tomlkit-0.14.0
|
||
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
|
||
|
||
[notice] A new release of pip is available: 24.0 -> 25.3
|
||
[notice] To update, run: pip install --upgrade pip
|
||
+ echo "Running flake8..."
|
||
Running flake8...
|
||
+ flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||
0
|
||
+ flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||
./auth.py:20:1: E302 expected 2 blank lines, found 1
|
||
./auth.py:26:1: E302 expected 2 blank lines, found 1
|
||
./auth.py:30:1: E302 expected 2 blank lines, found 1
|
||
./auth.py:33:1: E302 expected 2 blank lines, found 1
|
||
./auth.py:36:1: E302 expected 2 blank lines, found 1
|
||
./auth.py:46:1: E302 expected 2 blank lines, found 1
|
||
./auth.py:53:1: E302 expected 2 blank lines, found 1
|
||
./auth.py:56:1: W293 blank line contains whitespace
|
||
./auth.py:62:1: W293 blank line contains whitespace
|
||
./auth.py:69:1: W293 blank line contains whitespace
|
||
./auth.py:76:1: W293 blank line contains whitespace
|
||
./auth.py:79:1: E302 expected 2 blank lines, found 1
|
||
./auth.py:88:1: E302 expected 2 blank lines, found 1
|
||
./auth.py:92:1: W293 blank line contains whitespace
|
||
./auth.py:102:1: E302 expected 2 blank lines, found 1
|
||
./auth.py:109:1: E302 expected 2 blank lines, found 1
|
||
./auth.py:121:1: E302 expected 2 blank lines, found 1
|
||
./auth.py:131:1: E302 expected 2 blank lines, found 1
|
||
./auth.py:143:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1:1: F401 'fastapi.status' imported but unused
|
||
./main.py:13:1: F401 'typing.Optional' imported but unused
|
||
./main.py:20:1: F401 'httpx' imported but unused
|
||
./main.py:22:1: F401 'oidc_config.OIDC_PROVIDERS' imported but unused
|
||
./main.py:74:1: E302 expected 2 blank lines, found 1
|
||
./main.py:85:1: E302 expected 2 blank lines, found 1
|
||
./main.py:91:1: E302 expected 2 blank lines, found 1
|
||
./main.py:95:1: E302 expected 2 blank lines, found 1
|
||
./main.py:106:1: E302 expected 2 blank lines, found 1
|
||
./main.py:112:1: E302 expected 2 blank lines, found 1
|
||
./main.py:118:1: E302 expected 2 blank lines, found 1
|
||
./main.py:122:1: E305 expected 2 blank lines after class or function definition, found 1
|
||
./main.py:125:1: E302 expected 2 blank lines, found 1
|
||
./main.py:128:1: E302 expected 2 blank lines, found 1
|
||
./main.py:131:1: E302 expected 2 blank lines, found 1
|
||
./main.py:138:1: E302 expected 2 blank lines, found 1
|
||
./main.py:141:1: W293 blank line contains whitespace
|
||
./main.py:148:1: W293 blank line contains whitespace
|
||
./main.py:152:1: W293 blank line contains whitespace
|
||
./main.py:154:1: W293 blank line contains whitespace
|
||
./main.py:158:1: W293 blank line contains whitespace
|
||
./main.py:163:1: E302 expected 2 blank lines, found 1
|
||
./main.py:178:1: E302 expected 2 blank lines, found 1
|
||
./main.py:190:1: E302 expected 2 blank lines, found 1
|
||
./main.py:195:1: W293 blank line contains whitespace
|
||
./main.py:202:1: E302 expected 2 blank lines, found 1
|
||
./main.py:207:1: W293 blank line contains whitespace
|
||
./main.py:210:1: W293 blank line contains whitespace
|
||
./main.py:213:1: W293 blank line contains whitespace
|
||
./main.py:221:1: W293 blank line contains whitespace
|
||
./main.py:224:1: W293 blank line contains whitespace
|
||
./main.py:229:1: W293 blank line contains whitespace
|
||
./main.py:233:1: W293 blank line contains whitespace
|
||
./main.py:241:1: E302 expected 2 blank lines, found 1
|
||
./main.py:244:1: W293 blank line contains whitespace
|
||
./main.py:249:1: W293 blank line contains whitespace
|
||
./main.py:257:1: W293 blank line contains whitespace
|
||
./main.py:261:1: W293 blank line contains whitespace
|
||
./main.py:269:1: W293 blank line contains whitespace
|
||
./main.py:284:1: W293 blank line contains whitespace
|
||
./main.py:300:1: E302 expected 2 blank lines, found 1
|
||
./main.py:305:1: W293 blank line contains whitespace
|
||
./main.py:308:1: W293 blank line contains whitespace
|
||
./main.py:311:1: W293 blank line contains whitespace
|
||
./main.py:314:1: W293 blank line contains whitespace
|
||
./main.py:332:1: W293 blank line contains whitespace
|
||
./main.py:334:1: W293 blank line contains whitespace
|
||
./main.py:343:1: E302 expected 2 blank lines, found 1
|
||
./main.py:348:1: W293 blank line contains whitespace
|
||
./main.py:351:1: W293 blank line contains whitespace
|
||
./main.py:355:1: W293 blank line contains whitespace
|
||
./main.py:364:1: E302 expected 2 blank lines, found 1
|
||
./main.py:368:1: W293 blank line contains whitespace
|
||
./main.py:379:1: W293 blank line contains whitespace
|
||
./main.py:388:1: E302 expected 2 blank lines, found 1
|
||
./main.py:407:1: E302 expected 2 blank lines, found 1
|
||
./main.py:412:1: W293 blank line contains whitespace
|
||
./main.py:418:1: W293 blank line contains whitespace
|
||
./main.py:422:1: W293 blank line contains whitespace
|
||
./main.py:430:1: W293 blank line contains whitespace
|
||
./main.py:435:1: E302 expected 2 blank lines, found 1
|
||
./main.py:440:1: W293 blank line contains whitespace
|
||
./main.py:443:1: W293 blank line contains whitespace
|
||
./main.py:447:1: W293 blank line contains whitespace
|
||
./main.py:453:1: W293 blank line contains whitespace
|
||
./main.py:456:1: W293 blank line contains whitespace
|
||
./main.py:459:1: E302 expected 2 blank lines, found 1
|
||
./main.py:464:1: W293 blank line contains whitespace
|
||
./main.py:467:1: W293 blank line contains whitespace
|
||
./main.py:471:1: W293 blank line contains whitespace
|
||
./main.py:475:1: W293 blank line contains whitespace
|
||
./main.py:479:1: W293 blank line contains whitespace
|
||
./main.py:482:1: W293 blank line contains whitespace
|
||
./main.py:485:1: E302 expected 2 blank lines, found 1
|
||
./main.py:491:1: W293 blank line contains whitespace
|
||
./main.py:495:1: W293 blank line contains whitespace
|
||
./main.py:497:1: W293 blank line contains whitespace
|
||
./main.py:508:1: W293 blank line contains whitespace
|
||
./main.py:515:1: E302 expected 2 blank lines, found 1
|
||
./main.py:520:1: W293 blank line contains whitespace
|
||
./main.py:523:1: W293 blank line contains whitespace
|
||
./main.py:527:1: W293 blank line contains whitespace
|
||
./main.py:529:1: W293 blank line contains whitespace
|
||
./main.py:533:1: W293 blank line contains whitespace
|
||
./main.py:535:1: W293 blank line contains whitespace
|
||
./main.py:541:1: W293 blank line contains whitespace
|
||
./main.py:550:1: W293 blank line contains whitespace
|
||
./main.py:554:1: W293 blank line contains whitespace
|
||
./main.py:560:1: E302 expected 2 blank lines, found 1
|
||
./main.py:561:1: C901 'revoke_user_access' is too complex (12)
|
||
./main.py:565:1: W293 blank line contains whitespace
|
||
./main.py:569:1: W293 blank line contains whitespace
|
||
./main.py:571:1: W293 blank line contains whitespace
|
||
./main.py:575:1: W293 blank line contains whitespace
|
||
./main.py:577:1: W293 blank line contains whitespace
|
||
./main.py:603:1: W293 blank line contains whitespace
|
||
./main.py:606:1: W293 blank line contains whitespace
|
||
./main.py:612:1: E302 expected 2 blank lines, found 1
|
||
./main.py:617:1: W293 blank line contains whitespace
|
||
./main.py:621:1: W293 blank line contains whitespace
|
||
./main.py:624:1: W293 blank line contains whitespace
|
||
./main.py:632:1: W293 blank line contains whitespace
|
||
./main.py:648:1: W293 blank line contains whitespace
|
||
./main.py:651:1: W293 blank line contains whitespace
|
||
./main.py:658:1: E302 expected 2 blank lines, found 1
|
||
./main.py:663:1: W293 blank line contains whitespace
|
||
./main.py:666:1: W293 blank line contains whitespace
|
||
./main.py:669:1: W293 blank line contains whitespace
|
||
./main.py:671:1: W293 blank line contains whitespace
|
||
./main.py:675:1: W293 blank line contains whitespace
|
||
./main.py:679:1: W293 blank line contains whitespace
|
||
./main.py:683:1: W293 blank line contains whitespace
|
||
./main.py:688:1: W293 blank line contains whitespace
|
||
./main.py:696:1: W293 blank line contains whitespace
|
||
./main.py:701:1: W293 blank line contains whitespace
|
||
./main.py:703:1: W293 blank line contains whitespace
|
||
./main.py:706:1: W293 blank line contains whitespace
|
||
./main.py:713:1: E302 expected 2 blank lines, found 1
|
||
./main.py:718:1: W293 blank line contains whitespace
|
||
./main.py:721:1: W293 blank line contains whitespace
|
||
./main.py:724:1: W293 blank line contains whitespace
|
||
./main.py:726:1: W293 blank line contains whitespace
|
||
./main.py:730:1: W293 blank line contains whitespace
|
||
./main.py:734:1: W293 blank line contains whitespace
|
||
./main.py:737:1: E302 expected 2 blank lines, found 1
|
||
./main.py:742:1: W293 blank line contains whitespace
|
||
./main.py:746:1: W293 blank line contains whitespace
|
||
./main.py:760:1: W293 blank line contains whitespace
|
||
./main.py:764:1: W293 blank line contains whitespace
|
||
./main.py:771:1: W293 blank line contains whitespace
|
||
./main.py:781:1: E302 expected 2 blank lines, found 1
|
||
./main.py:787:1: W293 blank line contains whitespace
|
||
./main.py:789:1: W293 blank line contains whitespace
|
||
./main.py:793:1: W293 blank line contains whitespace
|
||
./main.py:795:1: W293 blank line contains whitespace
|
||
./main.py:799:1: W293 blank line contains whitespace
|
||
./main.py:813:1: W293 blank line contains whitespace
|
||
./main.py:817:1: W293 blank line contains whitespace
|
||
./main.py:824:1: W293 blank line contains whitespace
|
||
./main.py:836:1: E302 expected 2 blank lines, found 1
|
||
./main.py:842:1: W293 blank line contains whitespace
|
||
./main.py:848:1: W293 blank line contains whitespace
|
||
./main.py:850:1: W293 blank line contains whitespace
|
||
./main.py:858:1: W293 blank line contains whitespace
|
||
./main.py:869:1: E302 expected 2 blank lines, found 1
|
||
./main.py:874:1: W293 blank line contains whitespace
|
||
./main.py:878:1: W293 blank line contains whitespace
|
||
./main.py:880:1: W293 blank line contains whitespace
|
||
./main.py:888:1: W293 blank line contains whitespace
|
||
./main.py:898:1: W293 blank line contains whitespace
|
||
./main.py:901:1: E302 expected 2 blank lines, found 1
|
||
./main.py:905:1: W293 blank line contains whitespace
|
||
./main.py:909:1: W293 blank line contains whitespace
|
||
./main.py:914:1: E302 expected 2 blank lines, found 1
|
||
./main.py:918:1: W293 blank line contains whitespace
|
||
./main.py:922:1: W293 blank line contains whitespace
|
||
./main.py:925:1: W293 blank line contains whitespace
|
||
./main.py:929:1: E302 expected 2 blank lines, found 1
|
||
./main.py:933:1: W293 blank line contains whitespace
|
||
./main.py:937:1: W293 blank line contains whitespace
|
||
./main.py:940:1: W293 blank line contains whitespace
|
||
./main.py:945:1: C901 'read_server_output' is too complex (11)
|
||
./main.py:945:1: E302 expected 2 blank lines, found 1
|
||
./main.py:949:1: W293 blank line contains whitespace
|
||
./main.py:954:1: W293 blank line contains whitespace
|
||
./main.py:959:1: W293 blank line contains whitespace
|
||
./main.py:965:1: W293 blank line contains whitespace
|
||
./main.py:971:1: W293 blank line contains whitespace
|
||
./main.py:980:1: E302 expected 2 blank lines, found 1
|
||
./main.py:984:1: W293 blank line contains whitespace
|
||
./main.py:988:1: W293 blank line contains whitespace
|
||
./main.py:991:1: W293 blank line contains whitespace
|
||
./main.py:994:1: W293 blank line contains whitespace
|
||
./main.py:996:1: W293 blank line contains whitespace
|
||
./main.py:1019:1: W293 blank line contains whitespace
|
||
./main.py:1022:1: W293 blank line contains whitespace
|
||
./main.py:1024:1: W293 blank line contains whitespace
|
||
./main.py:1031:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1035:1: W293 blank line contains whitespace
|
||
./main.py:1038:1: W293 blank line contains whitespace
|
||
./main.py:1040:1: W293 blank line contains whitespace
|
||
./main.py:1045:1: W293 blank line contains whitespace
|
||
./main.py:1057:9: E722 do not use bare 'except'
|
||
./main.py:1063:1: W293 blank line contains whitespace
|
||
./main.py:1066:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1070:1: W293 blank line contains whitespace
|
||
./main.py:1073:1: W293 blank line contains whitespace
|
||
./main.py:1075:1: W293 blank line contains whitespace
|
||
./main.py:1079:1: W293 blank line contains whitespace
|
||
./main.py:1093:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1097:1: W293 blank line contains whitespace
|
||
./main.py:1099:1: W293 blank line contains whitespace
|
||
./main.py:1103:5: E722 do not use bare 'except'
|
||
./main.py:1105:1: W293 blank line contains whitespace
|
||
./main.py:1113:1: W293 blank line contains whitespace
|
||
./main.py:1124:1: W293 blank line contains whitespace
|
||
./main.py:1128:1: W293 blank line contains whitespace
|
||
./main.py:1153:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1157:1: W293 blank line contains whitespace
|
||
./main.py:1165:1: W293 blank line contains whitespace
|
||
./main.py:1167:1: W293 blank line contains whitespace
|
||
./main.py:1182:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1183:1: C901 'list_files' is too complex (11)
|
||
./main.py:1186:1: W293 blank line contains whitespace
|
||
./main.py:1190:1: W293 blank line contains whitespace
|
||
./main.py:1192:1: W293 blank line contains whitespace
|
||
./main.py:1198:5: E722 do not use bare 'except'
|
||
./main.py:1200:1: W293 blank line contains whitespace
|
||
./main.py:1203:1: W293 blank line contains whitespace
|
||
./main.py:1206:1: W293 blank line contains whitespace
|
||
./main.py:1218:1: W293 blank line contains whitespace
|
||
./main.py:1221:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1225:1: W293 blank line contains whitespace
|
||
./main.py:1228:1: W293 blank line contains whitespace
|
||
./main.py:1231:1: W293 blank line contains whitespace
|
||
./main.py:1234:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1237:1: W293 blank line contains whitespace
|
||
./main.py:1240:1: W293 blank line contains whitespace
|
||
./main.py:1243:1: W293 blank line contains whitespace
|
||
./main.py:1247:1: W293 blank line contains whitespace
|
||
./main.py:1250:1: W293 blank line contains whitespace
|
||
./main.py:1257:1: W293 blank line contains whitespace
|
||
./main.py:1266:1: W293 blank line contains whitespace
|
||
./main.py:1269:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1274:1: W293 blank line contains whitespace
|
||
./main.py:1278:1: W293 blank line contains whitespace
|
||
./main.py:1281:1: W293 blank line contains whitespace
|
||
./main.py:1284:1: W293 blank line contains whitespace
|
||
./main.py:1286:1: W293 blank line contains whitespace
|
||
./main.py:1292:1: W293 blank line contains whitespace
|
||
./main.py:1294:1: W293 blank line contains whitespace
|
||
./main.py:1298:1: W293 blank line contains whitespace
|
||
./main.py:1312:1: W293 blank line contains whitespace
|
||
./main.py:1318:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1322:1: W293 blank line contains whitespace
|
||
./main.py:1325:1: W293 blank line contains whitespace
|
||
./main.py:1328:1: W293 blank line contains whitespace
|
||
./main.py:1333:1: W293 blank line contains whitespace
|
||
./main.py:1336:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1340:1: W293 blank line contains whitespace
|
||
./main.py:1343:1: W293 blank line contains whitespace
|
||
./main.py:1346:1: W293 blank line contains whitespace
|
||
./main.py:1354:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1358:1: W293 blank line contains whitespace
|
||
./main.py:1361:1: W293 blank line contains whitespace
|
||
./main.py:1364:1: W293 blank line contains whitespace
|
||
./main.py:1372:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1376:1: W293 blank line contains whitespace
|
||
./main.py:1379:1: W293 blank line contains whitespace
|
||
./main.py:1382:1: W293 blank line contains whitespace
|
||
./main.py:1384:1: W293 blank line contains whitespace
|
||
./main.py:1387:1: W293 blank line contains whitespace
|
||
./main.py:1390:1: W293 blank line contains whitespace
|
||
./main.py:1394:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1399:1: W293 blank line contains whitespace
|
||
./main.py:1402:1: W293 blank line contains whitespace
|
||
./main.py:1405:1: W293 blank line contains whitespace
|
||
./main.py:1408:1: W293 blank line contains whitespace
|
||
./main.py:1418:1: W293 blank line contains whitespace
|
||
./main.py:1420:1: W293 blank line contains whitespace
|
||
./main.py:1424:1: W293 blank line contains whitespace
|
||
./main.py:1427:1: W293 blank line contains whitespace
|
||
./main.py:1430:1: W293 blank line contains whitespace
|
||
./main.py:1433:1: W293 blank line contains whitespace
|
||
./main.py:1437:1: W293 blank line contains whitespace
|
||
./main.py:1441:1: W293 blank line contains whitespace
|
||
./main.py:1448:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1449:1: F811 redefinition of unused 'rename_file' from line 1373
|
||
./main.py:1452:1: W293 blank line contains whitespace
|
||
./main.py:1455:1: W293 blank line contains whitespace
|
||
./main.py:1458:1: W293 blank line contains whitespace
|
||
./main.py:1460:1: W293 blank line contains whitespace
|
||
./main.py:1463:1: W293 blank line contains whitespace
|
||
./main.py:1466:1: W293 blank line contains whitespace
|
||
./main.py:1471:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1477:1: W293 blank line contains whitespace
|
||
./main.py:1479:1: W293 blank line contains whitespace
|
||
./main.py:1483:1: W293 blank line contains whitespace
|
||
./main.py:1488:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1494:1: W293 blank line contains whitespace
|
||
./main.py:1496:1: W293 blank line contains whitespace
|
||
./main.py:1499:1: W293 blank line contains whitespace
|
||
./main.py:1516:1: W293 blank line contains whitespace
|
||
./main.py:1519:1: W293 blank line contains whitespace
|
||
./main.py:1522:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1528:1: W293 blank line contains whitespace
|
||
./main.py:1530:1: W293 blank line contains whitespace
|
||
./main.py:1533:1: W293 blank line contains whitespace
|
||
./main.py:1535:1: W293 blank line contains whitespace
|
||
./main.py:1539:1: W293 blank line contains whitespace
|
||
./main.py:1542:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1548:1: W293 blank line contains whitespace
|
||
./main.py:1550:1: W293 blank line contains whitespace
|
||
./main.py:1553:1: W293 blank line contains whitespace
|
||
./main.py:1555:1: W293 blank line contains whitespace
|
||
./main.py:1559:1: W293 blank line contains whitespace
|
||
./main.py:1565:1: W293 blank line contains whitespace
|
||
./main.py:1568:1: W293 blank line contains whitespace
|
||
./main.py:1571:1: W293 blank line contains whitespace
|
||
./main.py:1574:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1579:1: W293 blank line contains whitespace
|
||
./main.py:1583:1: W293 blank line contains whitespace
|
||
./main.py:1585:1: W293 blank line contains whitespace
|
||
./main.py:1588:1: W293 blank line contains whitespace
|
||
./main.py:1592:1: W293 blank line contains whitespace
|
||
./main.py:1596:1: W293 blank line contains whitespace
|
||
./main.py:1603:1: W293 blank line contains whitespace
|
||
./main.py:1609:1: W293 blank line contains whitespace
|
||
./main.py:1611:1: W293 blank line contains whitespace
|
||
./main.py:1614:1: W293 blank line contains whitespace
|
||
./main.py:1630:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1635:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1639:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1644:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1645:1: F811 redefinition of unused 'get_users' from line 389
|
||
./main.py:1647:1: W293 blank line contains whitespace
|
||
./main.py:1654:1: W293 blank line contains whitespace
|
||
./main.py:1658:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1661:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1664:1: W293 blank line contains whitespace
|
||
./main.py:1666:1: W293 blank line contains whitespace
|
||
./main.py:1669:1: W293 blank line contains whitespace
|
||
./main.py:1672:1: W293 blank line contains whitespace
|
||
./main.py:1675:53: F541 f-string is missing placeholders
|
||
./main.py:1676:1: W293 blank line contains whitespace
|
||
./main.py:1679:1: W293 blank line contains whitespace
|
||
./main.py:1682:1: W293 blank line contains whitespace
|
||
./main.py:1714:1: W293 blank line contains whitespace
|
||
./main.py:1716:1: W293 blank line contains whitespace
|
||
./main.py:1717:128: E501 line too long (129 > 127 characters)
|
||
./main.py:1720:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1723:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1726:1: W293 blank line contains whitespace
|
||
./main.py:1728:1: W293 blank line contains whitespace
|
||
./main.py:1731:1: W293 blank line contains whitespace
|
||
./main.py:1734:1: W293 blank line contains whitespace
|
||
./main.py:1739:128: E501 line too long (140 > 127 characters)
|
||
./main.py:1740:1: W293 blank line contains whitespace
|
||
./main.py:1748:1: W293 blank line contains whitespace
|
||
./main.py:1750:1: W293 blank line contains whitespace
|
||
./main.py:1754:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1757:1: W293 blank line contains whitespace
|
||
./main.py:1759:1: W293 blank line contains whitespace
|
||
./main.py:1762:1: W293 blank line contains whitespace
|
||
./main.py:1765:1: W293 blank line contains whitespace
|
||
./main.py:1773:1: W293 blank line contains whitespace
|
||
./main.py:1775:1: W293 blank line contains whitespace
|
||
./main.py:1779:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1780:1: F811 redefinition of unused 'delete_user' from line 436
|
||
./main.py:1782:1: W293 blank line contains whitespace
|
||
./main.py:1784:1: W293 blank line contains whitespace
|
||
./main.py:1787:1: W293 blank line contains whitespace
|
||
./main.py:1790:1: W293 blank line contains whitespace
|
||
./main.py:1795:128: E501 line too long (134 > 127 characters)
|
||
./main.py:1796:1: W293 blank line contains whitespace
|
||
./main.py:1799:1: W293 blank line contains whitespace
|
||
./main.py:1803:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1806:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1809:1: W293 blank line contains whitespace
|
||
./main.py:1811:1: W293 blank line contains whitespace
|
||
./main.py:1814:1: W293 blank line contains whitespace
|
||
./main.py:1817:1: W293 blank line contains whitespace
|
||
./main.py:1820:1: W293 blank line contains whitespace
|
||
./main.py:1826:1: W293 blank line contains whitespace
|
||
./main.py:1828:1: W293 blank line contains whitespace
|
||
./main.py:1832:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1835:1: W293 blank line contains whitespace
|
||
./main.py:1837:1: W293 blank line contains whitespace
|
||
./main.py:1840:1: W293 blank line contains whitespace
|
||
./main.py:1844:1: W293 blank line contains whitespace
|
||
./main.py:1848:1: W293 blank line contains whitespace
|
||
./main.py:1850:1: W293 blank line contains whitespace
|
||
./main.py:1854:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1857:1: E302 expected 2 blank lines, found 1
|
||
./main.py:1858:1: F811 redefinition of unused 'update_user_permissions' from line 516
|
||
./main.py:1860:1: W293 blank line contains whitespace
|
||
./main.py:1862:1: W293 blank line contains whitespace
|
||
./main.py:1865:1: W293 blank line contains whitespace
|
||
./main.py:1868:1: W293 blank line contains whitespace
|
||
./migrate_users.py:11:1: C901 'migrate_users' is too complex (22)
|
||
./migrate_users.py:11:1: E302 expected 2 blank lines, found 1
|
||
./migrate_users.py:13:1: W293 blank line contains whitespace
|
||
./migrate_users.py:15:1: W293 blank line contains whitespace
|
||
./migrate_users.py:21:1: W293 blank line contains whitespace
|
||
./migrate_users.py:33:1: W293 blank line contains whitespace
|
||
./migrate_users.py:44:1: W293 blank line contains whitespace
|
||
./migrate_users.py:59:1: W293 blank line contains whitespace
|
||
./migrate_users.py:63:1: W293 blank line contains whitespace
|
||
./migrate_users.py:66:1: W293 blank line contains whitespace
|
||
./migrate_users.py:87:1: W293 blank line contains whitespace
|
||
./migrate_users.py:92:1: W293 blank line contains whitespace
|
||
./migrate_users.py:95:1: W293 blank line contains whitespace
|
||
./migrate_users.py:99:19: F541 f-string is missing placeholders
|
||
./migrate_users.py:100:1: W293 blank line contains whitespace
|
||
./migrate_users.py:147:1: W293 blank line contains whitespace
|
||
./migrate_users.py:156:1: W293 blank line contains whitespace
|
||
./migrate_users.py:168:1: W293 blank line contains whitespace
|
||
./migrate_users.py:180:1: E302 expected 2 blank lines, found 1
|
||
./migrate_users.py:183:1: W293 blank line contains whitespace
|
||
./migrate_users.py:187:1: W293 blank line contains whitespace
|
||
./migrate_users.py:194:1: W293 blank line contains whitespace
|
||
./migrate_users.py:200:1: W293 blank line contains whitespace
|
||
./migrate_users.py:204:1: W293 blank line contains whitespace
|
||
./migrate_users.py:208:15: F541 f-string is missing placeholders
|
||
./migrate_users.py:212:1: W293 blank line contains whitespace
|
||
./migrate_users.py:220:1: E305 expected 2 blank lines after class or function definition, found 1
|
||
./migrate_users.py:224:1: W293 blank line contains whitespace
|
||
./migrate_users.py:227:1: W293 blank line contains whitespace
|
||
./migrate_users.py:231:1: W293 blank line contains whitespace
|
||
./models.py:2:1: F401 'typing.Optional' imported but unused
|
||
./models.py:4:1: E302 expected 2 blank lines, found 1
|
||
./models.py:8:1: E302 expected 2 blank lines, found 1
|
||
./models.py:12:1: E302 expected 2 blank lines, found 1
|
||
./models.py:18:1: E302 expected 2 blank lines, found 1
|
||
./models.py:22:1: E302 expected 2 blank lines, found 1
|
||
./oidc_config.py:21:1: E302 expected 2 blank lines, found 1
|
||
./oidc_config.py:29:1: E302 expected 2 blank lines, found 1
|
||
./oidc_config.py:31:62: W292 no newline at end of file
|
||
./user_management_endpoints.py:8:1: F401 'typing.Optional' imported but unused
|
||
./user_management_endpoints.py:8:1: F401 'typing.List' imported but unused
|
||
./user_management_endpoints.py:15:1: E302 expected 2 blank lines, found 1
|
||
./user_management_endpoints.py:18:1: E302 expected 2 blank lines, found 1
|
||
./user_management_endpoints.py:21:1: E302 expected 2 blank lines, found 1
|
||
./user_management_endpoints.py:24:1: E302 expected 2 blank lines, found 1
|
||
./user_management_endpoints.py:28:1: E302 expected 2 blank lines, found 1
|
||
./user_management_endpoints.py:32:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:37:1: E302 expected 2 blank lines, found 1
|
||
./user_management_endpoints.py:42:1: E302 expected 2 blank lines, found 1
|
||
./user_management_endpoints.py:46:1: E302 expected 2 blank lines, found 1
|
||
./user_management_endpoints.py:51:1: E302 expected 2 blank lines, found 1
|
||
./user_management_endpoints.py:54:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:56:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:63:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:67:1: E302 expected 2 blank lines, found 1
|
||
./user_management_endpoints.py:68:1: C901 'change_user_role' is too complex (11)
|
||
./user_management_endpoints.py:70:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:72:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:75:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:78:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:83:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:89:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:93:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:145:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:147:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:157:1: E302 expected 2 blank lines, found 1
|
||
./user_management_endpoints.py:160:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:162:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:165:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:168:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:175:1: E302 expected 2 blank lines, found 1
|
||
./user_management_endpoints.py:178:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:180:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:183:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:186:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:189:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:195:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:197:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:205:1: E302 expected 2 blank lines, found 1
|
||
./user_management_endpoints.py:208:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:210:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:213:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:217:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:221:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:223:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:231:1: E302 expected 2 blank lines, found 1
|
||
./user_management_endpoints.py:234:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:236:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:239:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:242:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:245:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:248:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:255:1: E302 expected 2 blank lines, found 1
|
||
./user_management_endpoints.py:258:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:260:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:263:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:266:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:269:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:281:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:283:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:291:1: E302 expected 2 blank lines, found 1
|
||
./user_management_endpoints.py:294:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:296:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:299:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:302:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:314:1: W293 blank line contains whitespace
|
||
./user_management_endpoints.py:316:1: W293 blank line contains whitespace
|
||
5 C901 'revoke_user_access' is too complex (12)
|
||
111 E302 expected 2 blank lines, found 1
|
||
2 E305 expected 2 blank lines after class or function definition, found 1
|
||
3 E501 line too long (129 > 127 characters)
|
||
3 E722 do not use bare 'except'
|
||
7 F401 'fastapi.status' imported but unused
|
||
3 F541 f-string is missing placeholders
|
||
4 F811 redefinition of unused 'rename_file' from line 1373
|
||
1 W292 no newline at end of file
|
||
366 W293 blank line contains whitespace
|
||
505
|
||
+ echo "Running pylint..."
|
||
Running pylint...
|
||
+ pylint **/*.py --exit-zero --max-line-length=127
|
||
************* Module migrate_users
|
||
migrate_users.py:13:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:15:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:21:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:33:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:44:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:59:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:63:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:66:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:87:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:92:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:95:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:100:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:147:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:156:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:168:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:183:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:187:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:194:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:200:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:204:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:212:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:224:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:227:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:231:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
migrate_users.py:30:11: W0718: Catching too general exception Exception (broad-exception-caught)
|
||
migrate_users.py:41:11: W0718: Catching too general exception Exception (broad-exception-caught)
|
||
migrate_users.py:99:18: W1309: Using an f-string that does not have any interpolated variables (f-string-without-interpolation)
|
||
migrate_users.py:175:11: W0718: Catching too general exception Exception (broad-exception-caught)
|
||
migrate_users.py:11:0: R0911: Too many return statements (8/6) (too-many-return-statements)
|
||
migrate_users.py:11:0: R0912: Too many branches (21/12) (too-many-branches)
|
||
migrate_users.py:11:0: R0915: Too many statements (82/50) (too-many-statements)
|
||
migrate_users.py:191:11: W0718: Catching too general exception Exception (broad-exception-caught)
|
||
migrate_users.py:208:14: W1309: Using an f-string that does not have any interpolated variables (f-string-without-interpolation)
|
||
migrate_users.py:226:4: C0103: Constant name "success" doesn't conform to UPPER_CASE naming style (invalid-name)
|
||
************* Module user_management_endpoints
|
||
user_management_endpoints.py:32:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:54:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:56:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:63:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:70:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:72:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:75:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:78:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:83:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:89:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:93:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:145:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:147:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:160:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:162:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:165:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:168:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:178:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:180:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:183:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:186:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:189:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:195:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:197:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:208:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:210:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:213:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:217:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:221:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:223:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:234:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:236:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:239:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:242:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:245:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:248:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:258:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:260:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:263:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:266:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:269:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:281:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:283:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:294:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:296:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:299:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:302:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:314:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:316:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
user_management_endpoints.py:6:0: E0401: Unable to import 'fastapi' (import-error)
|
||
user_management_endpoints.py:7:0: E0401: Unable to import 'pydantic' (import-error)
|
||
user_management_endpoints.py:15:0: C0115: Missing class docstring (missing-class-docstring)
|
||
user_management_endpoints.py:15:0: R0903: Too few public methods (0/2) (too-few-public-methods)
|
||
user_management_endpoints.py:18:0: C0115: Missing class docstring (missing-class-docstring)
|
||
user_management_endpoints.py:18:0: R0903: Too few public methods (0/2) (too-few-public-methods)
|
||
user_management_endpoints.py:21:0: C0115: Missing class docstring (missing-class-docstring)
|
||
user_management_endpoints.py:21:0: R0903: Too few public methods (0/2) (too-few-public-methods)
|
||
user_management_endpoints.py:24:0: C0115: Missing class docstring (missing-class-docstring)
|
||
user_management_endpoints.py:24:0: R0903: Too few public methods (0/2) (too-few-public-methods)
|
||
user_management_endpoints.py:28:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
user_management_endpoints.py:37:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
user_management_endpoints.py:42:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
user_management_endpoints.py:46:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
user_management_endpoints.py:52:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
user_management_endpoints.py:59:8: W0612: Unused variable 'username' (unused-variable)
|
||
user_management_endpoints.py:68:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
user_management_endpoints.py:158:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
user_management_endpoints.py:176:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
user_management_endpoints.py:206:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
user_management_endpoints.py:232:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
user_management_endpoints.py:256:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
user_management_endpoints.py:292:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
user_management_endpoints.py:8:0: C0411: standard import "typing.Optional" should be placed before third party imports "fastapi.APIRouter", "pydantic.BaseModel" (wrong-import-order)
|
||
user_management_endpoints.py:9:0: C0411: standard import "json" should be placed before third party imports "fastapi.APIRouter", "pydantic.BaseModel" (wrong-import-order)
|
||
user_management_endpoints.py:10:0: C0411: standard import "pathlib.Path" should be placed before third party imports "fastapi.APIRouter", "pydantic.BaseModel" (wrong-import-order)
|
||
user_management_endpoints.py:8:0: W0611: Unused Optional imported from typing (unused-import)
|
||
user_management_endpoints.py:8:0: W0611: Unused List imported from typing (unused-import)
|
||
************* Module oidc_config
|
||
oidc_config.py:31:0: C0304: Final newline missing (missing-final-newline)
|
||
************* Module models
|
||
models.py:1:0: C0114: Missing module docstring (missing-module-docstring)
|
||
models.py:1:0: E0401: Unable to import 'pydantic' (import-error)
|
||
models.py:4:0: C0115: Missing class docstring (missing-class-docstring)
|
||
models.py:4:0: R0903: Too few public methods (0/2) (too-few-public-methods)
|
||
models.py:8:0: C0115: Missing class docstring (missing-class-docstring)
|
||
models.py:8:0: R0903: Too few public methods (0/2) (too-few-public-methods)
|
||
models.py:12:0: C0115: Missing class docstring (missing-class-docstring)
|
||
models.py:12:0: R0903: Too few public methods (0/2) (too-few-public-methods)
|
||
models.py:18:0: C0115: Missing class docstring (missing-class-docstring)
|
||
models.py:18:0: R0903: Too few public methods (0/2) (too-few-public-methods)
|
||
models.py:22:0: C0115: Missing class docstring (missing-class-docstring)
|
||
models.py:22:0: R0903: Too few public methods (0/2) (too-few-public-methods)
|
||
models.py:2:0: C0411: standard import "typing.Optional" should be placed before third party import "pydantic.BaseModel" (wrong-import-order)
|
||
models.py:2:0: W0611: Unused Optional imported from typing (unused-import)
|
||
************* Module main
|
||
main.py:141:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:148:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:152:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:154:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:158:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:195:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:207:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:210:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:213:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:221:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:224:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:229:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:233:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:244:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:249:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:257:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:261:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:269:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:284:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:305:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:308:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:311:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:314:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:332:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:334:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:348:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:351:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:355:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:368:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:379:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:412:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:418:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:422:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:430:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:440:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:443:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:447:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:453:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:456:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:464:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:467:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:471:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:475:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:479:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:482:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:491:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:495:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:497:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:508:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:520:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:523:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:527:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:529:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:533:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:535:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:541:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:550:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:554:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:565:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:569:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:571:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:575:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:577:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:603:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:606:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:617:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:621:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:624:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:632:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:648:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:651:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:663:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:666:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:669:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:671:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:675:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:679:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:683:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:688:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:696:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:701:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:703:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:706:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:718:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:721:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:724:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:726:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:730:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:734:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:742:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:746:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:760:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:764:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:771:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:787:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:789:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:793:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:795:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:799:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:813:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:817:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:824:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:842:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:848:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:850:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:858:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:874:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:878:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:880:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:888:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:898:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:905:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:909:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:918:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:922:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:925:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:933:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:937:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:940:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:949:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:954:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:959:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:965:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:971:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:984:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:988:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:991:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:994:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:996:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1019:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1022:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1024:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1035:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1038:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1040:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1045:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1063:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1070:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1073:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1075:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1079:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1097:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1099:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1105:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1113:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1124:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1128:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1157:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1165:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1167:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1186:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1190:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1192:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1200:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1203:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1206:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1218:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1225:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1228:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1231:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1237:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1240:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1243:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1247:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1250:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1257:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1266:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1274:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1278:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1281:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1284:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1286:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1292:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1294:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1298:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1312:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1322:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1325:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1328:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1333:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1340:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1343:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1346:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1358:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1361:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1364:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1376:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1379:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1382:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1384:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1387:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1390:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1399:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1402:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1405:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1408:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1418:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1420:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1424:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1427:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1430:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1433:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1437:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1441:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1452:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1455:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1458:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1460:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1463:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1466:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1477:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1479:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1483:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1494:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1496:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1499:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1516:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1519:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1528:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1530:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1533:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1535:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1539:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1548:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1550:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1553:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1555:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1559:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1565:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1568:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1571:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1579:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1583:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1585:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1588:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1592:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1596:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1603:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1609:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1611:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1614:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1647:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1654:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1664:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1666:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1669:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1672:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1676:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1679:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1682:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1714:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1716:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1717:0: C0301: Line too long (129/127) (line-too-long)
|
||
main.py:1726:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1728:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1731:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1734:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1739:0: C0301: Line too long (140/127) (line-too-long)
|
||
main.py:1740:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1748:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1750:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1757:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1759:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1762:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1765:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1773:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1775:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1782:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1784:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1787:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1790:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1795:0: C0301: Line too long (134/127) (line-too-long)
|
||
main.py:1796:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1799:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1809:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1811:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1814:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1817:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1820:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1826:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1828:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1835:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1837:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1840:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1844:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1848:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1850:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1860:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1862:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1865:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1868:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
main.py:1:0: C0302: Too many lines in module (1874/1000) (too-many-lines)
|
||
main.py:1:0: C0114: Missing module docstring (missing-module-docstring)
|
||
main.py:1:0: E0401: Unable to import 'fastapi' (import-error)
|
||
main.py:2:0: E0401: Unable to import 'fastapi.middleware.cors' (import-error)
|
||
main.py:3:0: E0401: Unable to import 'fastapi.responses' (import-error)
|
||
main.py:4:0: E0401: Unable to import 'fastapi.security' (import-error)
|
||
main.py:5:0: E0401: Unable to import 'pydantic' (import-error)
|
||
main.py:8:0: E0401: Unable to import 'psutil' (import-error)
|
||
main.py:15:0: E0401: Unable to import 'passlib.context' (import-error)
|
||
main.py:16:0: E0401: Unable to import 'jose' (import-error)
|
||
main.py:18:0: E0401: Unable to import 'authlib.integrations.starlette_client' (import-error)
|
||
main.py:19:0: E0401: Unable to import 'authlib.common.errors' (import-error)
|
||
main.py:20:0: E0401: Unable to import 'httpx' (import-error)
|
||
main.py:21:0: E0401: Unable to import 'dotenv' (import-error)
|
||
main.py:74:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:85:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:91:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:95:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:106:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:106:41: W0621: Redefining name 'config' from outer scope (line 35) (redefined-outer-name)
|
||
main.py:112:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:118:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:125:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:128:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:131:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:138:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:161:8: W0707: Consider explicitly re-raising using 'except JWTError as exc' and 'raise HTTPException(status_code=401, detail='Неверный токен') from exc' (raise-missing-from)
|
||
main.py:163:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:182:21: W0621: Redefining name 'config' from outer scope (line 35) (redefined-outer-name)
|
||
main.py:200:8: W0707: Consider explicitly re-raising using 'raise HTTPException(500, f'Ошибка инициализации OAuth: {str(e)}') from e' (raise-missing-from)
|
||
main.py:236:8: W0707: Consider explicitly re-raising using 'raise HTTPException(400, f'OAuth ошибка: {str(e)}') from e' (raise-missing-from)
|
||
main.py:239:8: W0707: Consider explicitly re-raising using 'raise HTTPException(500, f'Ошибка аутентификации: {str(e)}') from e' (raise-missing-from)
|
||
main.py:259:4: C0415: Import outside toplevel (re) (import-outside-toplevel)
|
||
main.py:270:4: R1705: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it (no-else-return)
|
||
main.py:301:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:323:21: R1719: The if expression can be replaced with 'test' (simplifiable-if-expression)
|
||
main.py:344:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:365:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:389:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:389:20: W0613: Unused argument 'user' (unused-argument)
|
||
main.py:408:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:427:12: W0621: Redefining name 'config' from outer scope (line 35) (redefined-outer-name)
|
||
main.py:436:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:460:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:692:12: W0621: Redefining name 'config' from outer scope (line 35) (redefined-outer-name)
|
||
main.py:698:8: W0612: Unused variable 'username' (unused-variable)
|
||
main.py:749:12: W0621: Redefining name 'config' from outer scope (line 35) (redefined-outer-name)
|
||
main.py:802:12: W0621: Redefining name 'config' from outer scope (line 35) (redefined-outer-name)
|
||
main.py:837:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:849:16: W0621: Redefining name 'config' from outer scope (line 35) (redefined-outer-name)
|
||
main.py:865:11: W0718: Catching too general exception Exception (broad-exception-caught)
|
||
main.py:870:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:881:4: W0621: Redefining name 'config' from outer scope (line 35) (redefined-outer-name)
|
||
main.py:902:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:910:4: W0621: Redefining name 'config' from outer scope (line 35) (redefined-outer-name)
|
||
main.py:915:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:915:49: W0621: Redefining name 'config' from outer scope (line 35) (redefined-outer-name)
|
||
main.py:930:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:945:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:972:11: W0718: Catching too general exception Exception (broad-exception-caught)
|
||
main.py:968:19: W0718: Catching too general exception Exception (broad-exception-caught)
|
||
main.py:981:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:992:4: W0621: Redefining name 'config' from outer scope (line 35) (redefined-outer-name)
|
||
main.py:1029:8: W0707: Consider explicitly re-raising using 'raise HTTPException(500, f'Ошибка запуска сервера: {str(e)}') from e' (raise-missing-from)
|
||
main.py:1032:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1052:11: W0718: Catching too general exception Exception (broad-exception-caught)
|
||
main.py:1057:8: W0702: No exception type(s) specified (bare-except)
|
||
main.py:1067:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1082:8: R1705: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it (no-else-return)
|
||
main.py:1091:8: W0707: Consider explicitly re-raising using 'raise HTTPException(500, f'Ошибка отправки команды: {str(e)}') from e' (raise-missing-from)
|
||
main.py:1094:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1103:4: W0702: No exception type(s) specified (bare-except)
|
||
main.py:1144:11: W0718: Catching too general exception Exception (broad-exception-caught)
|
||
main.py:1154:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1177:11: W0718: Catching too general exception Exception (broad-exception-caught)
|
||
main.py:1179:8: W0107: Unnecessary pass statement (unnecessary-pass)
|
||
main.py:1183:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1199:8: W0707: Consider explicitly re-raising using 'except Exception as exc' and 'raise HTTPException(404, 'Путь не найден') from exc' (raise-missing-from)
|
||
main.py:1217:8: W0707: Consider explicitly re-raising using 'raise HTTPException(500, f'Ошибка чтения директории: {str(e)}') from e' (raise-missing-from)
|
||
main.py:1222:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1235:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1256:8: W0707: Consider explicitly re-raising using 'raise HTTPException(500, f'Ошибка создания директории: {str(e)}') from e' (raise-missing-from)
|
||
main.py:1265:8: W0707: Consider explicitly re-raising using 'raise HTTPException(500, f'Ошибка записи файла: {str(e)}') from e' (raise-missing-from)
|
||
main.py:1316:8: W0707: Consider explicitly re-raising using 'raise HTTPException(500, f'Ошибка создания: {str(e)}') from e' (raise-missing-from)
|
||
main.py:1319:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1337:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1352:8: W0707: Consider explicitly re-raising using 'except UnicodeDecodeError as exc' and 'raise HTTPException(400, 'Файл не является текстовым') from exc' (raise-missing-from)
|
||
main.py:1355:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1370:8: W0707: Consider explicitly re-raising using 'raise HTTPException(400, f'Ошибка сохранения файла: {str(e)}') from e' (raise-missing-from)
|
||
main.py:1373:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1439:8: W0621: Redefining name 'shutil' from outer scope (line 10) (redefined-outer-name)
|
||
main.py:1439:8: W0404: Reimport 'shutil' (imported line 10) (reimported)
|
||
main.py:1439:8: C0415: Import outside toplevel (shutil) (import-outside-toplevel)
|
||
main.py:1446:8: W0707: Consider explicitly re-raising using 'raise HTTPException(500, f'Ошибка перемещения: {str(e)}') from e' (raise-missing-from)
|
||
main.py:1449:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1449:0: E0102: function already defined line 1373 (function-redefined)
|
||
main.py:1623:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1010:22: R1732: Consider using 'with' for resource-allocating operations (consider-using-with)
|
||
main.py:1630:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1635:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1639:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1645:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1645:0: E0102: function already defined line 389 (function-redefined)
|
||
main.py:1650:8: W0612: Unused variable 'username' (unused-variable)
|
||
main.py:1658:0: C0115: Missing class docstring (missing-class-docstring)
|
||
main.py:1658:0: R0903: Too few public methods (0/2) (too-few-public-methods)
|
||
main.py:1662:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1675:52: W1309: Using an f-string that does not have any interpolated variables (f-string-without-interpolation)
|
||
main.py:1720:0: C0115: Missing class docstring (missing-class-docstring)
|
||
main.py:1720:0: R0903: Too few public methods (0/2) (too-few-public-methods)
|
||
main.py:1724:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1755:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1780:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1780:0: E0102: function already defined line 436 (function-redefined)
|
||
main.py:1803:0: C0115: Missing class docstring (missing-class-docstring)
|
||
main.py:1803:0: R0903: Too few public methods (0/2) (too-few-public-methods)
|
||
main.py:1807:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1833:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1854:0: C0115: Missing class docstring (missing-class-docstring)
|
||
main.py:1854:0: R0903: Too few public methods (0/2) (too-few-public-methods)
|
||
main.py:1858:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
main.py:1858:0: E0102: function already defined line 516 (function-redefined)
|
||
main.py:1873:4: E0401: Unable to import 'uvicorn' (import-error)
|
||
main.py:6:0: C0411: standard import "asyncio" should be placed before third party imports "fastapi.FastAPI", "fastapi.middleware.cors.CORSMiddleware", "fastapi.responses.FileResponse", "fastapi.security.HTTPBearer", "pydantic.BaseModel" (wrong-import-order)
|
||
main.py:7:0: C0411: standard import "subprocess" should be placed before third party imports "fastapi.FastAPI", "fastapi.middleware.cors.CORSMiddleware", "fastapi.responses.FileResponse", "fastapi.security.HTTPBearer", "pydantic.BaseModel" (wrong-import-order)
|
||
main.py:9:0: C0411: standard import "os" should be placed before third party imports "fastapi.FastAPI", "fastapi.middleware.cors.CORSMiddleware", "fastapi.responses.FileResponse", "fastapi.security.HTTPBearer", "pydantic.BaseModel", "psutil" (wrong-import-order)
|
||
main.py:10:0: C0411: standard import "shutil" should be placed before third party imports "fastapi.FastAPI", "fastapi.middleware.cors.CORSMiddleware", "fastapi.responses.FileResponse", "fastapi.security.HTTPBearer", "pydantic.BaseModel", "psutil" (wrong-import-order)
|
||
main.py:11:0: C0411: standard import "sys" should be placed before third party imports "fastapi.FastAPI", "fastapi.middleware.cors.CORSMiddleware", "fastapi.responses.FileResponse", "fastapi.security.HTTPBearer", "pydantic.BaseModel", "psutil" (wrong-import-order)
|
||
main.py:12:0: C0411: standard import "pathlib.Path" should be placed before third party imports "fastapi.FastAPI", "fastapi.middleware.cors.CORSMiddleware", "fastapi.responses.FileResponse", "fastapi.security.HTTPBearer", "pydantic.BaseModel", "psutil" (wrong-import-order)
|
||
main.py:13:0: C0411: standard import "typing.Optional" should be placed before third party imports "fastapi.FastAPI", "fastapi.middleware.cors.CORSMiddleware", "fastapi.responses.FileResponse", "fastapi.security.HTTPBearer", "pydantic.BaseModel", "psutil" (wrong-import-order)
|
||
main.py:14:0: C0411: standard import "json" should be placed before third party imports "fastapi.FastAPI", "fastapi.middleware.cors.CORSMiddleware", "fastapi.responses.FileResponse", "fastapi.security.HTTPBearer", "pydantic.BaseModel", "psutil" (wrong-import-order)
|
||
main.py:17:0: C0411: standard import "datetime.datetime" should be placed before third party imports "fastapi.FastAPI", "fastapi.middleware.cors.CORSMiddleware", "fastapi.responses.FileResponse" (...) "psutil", "passlib.context.CryptContext", "jose.JWTError" (wrong-import-order)
|
||
main.py:1:0: W0611: Unused status imported from fastapi (unused-import)
|
||
main.py:13:0: W0611: Unused Optional imported from typing (unused-import)
|
||
main.py:20:0: W0611: Unused import httpx (unused-import)
|
||
main.py:22:0: W0611: Unused OIDC_PROVIDERS imported from oidc_config (unused-import)
|
||
************* Module auth
|
||
auth.py:56:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
auth.py:62:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
auth.py:69:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
auth.py:76:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
auth.py:92:0: C0303: Trailing whitespace (trailing-whitespace)
|
||
auth.py:1:0: C0114: Missing module docstring (missing-module-docstring)
|
||
auth.py:3:0: E0401: Unable to import 'jose' (import-error)
|
||
auth.py:4:0: E0401: Unable to import 'passlib.context' (import-error)
|
||
auth.py:5:0: E0401: Unable to import 'fastapi' (import-error)
|
||
auth.py:6:0: E0401: Unable to import 'fastapi.security' (import-error)
|
||
auth.py:20:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
auth.py:26:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
auth.py:30:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
auth.py:33:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
auth.py:36:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
auth.py:46:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
auth.py:53:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
auth.py:79:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
auth.py:88:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
auth.py:7:0: C0411: standard import "json" should be placed before third party imports "jose.JWTError", "passlib.context.CryptContext", "fastapi.Depends", "fastapi.security.HTTPBearer" (wrong-import-order)
|
||
auth.py:8:0: C0411: standard import "pathlib.Path" should be placed before third party imports "jose.JWTError", "passlib.context.CryptContext", "fastapi.Depends", "fastapi.security.HTTPBearer" (wrong-import-order)
|
||
auth.py:1:0: R0801: Similar lines in 2 files
|
||
==main:[1623:1647]
|
||
==user_management_endpoints:[28:54]
|
||
users_file = Path("users.json")
|
||
if not users_file.exists():
|
||
return {}
|
||
with open(users_file, "r", encoding="utf-8") as f:
|
||
return json.load(f)
|
||
|
||
def save_users_dict(users):
|
||
with open("users.json", "w", encoding="utf-8") as f:
|
||
json.dump(users, f, indent=2, ensure_ascii=False)
|
||
|
||
# Проверка прав
|
||
def require_owner(current_user: dict):
|
||
if current_user.get("role") != "owner":
|
||
raise HTTPException(status_code=403, detail="Требуется роль владельца")
|
||
|
||
def require_admin_or_owner(current_user: dict):
|
||
if current_user.get("role") not in ["owner", "admin"]:
|
||
raise HTTPException(status_code=403, detail="Требуется роль администратора или владельца")
|
||
|
||
# 1. Получить список пользователей
|
||
@app.get("/api/users")
|
||
async def get_users(current_user: dict = Depends(get_current_user)):
|
||
require_admin_or_owner(current_user)
|
||
(duplicate-code)
|
||
auth.py:1:0: R0801: Similar lines in 2 files
|
||
==main:[1811:1826]
|
||
==user_management_endpoints:[180:195]
|
||
if username not in users:
|
||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
|
||
if "resource_access" not in users[username]:
|
||
users[username]["resource_access"] = {"servers": [], "tickets": [], "files": []}
|
||
|
||
if access.server_name not in users[username]["resource_access"]["servers"]:
|
||
users[username]["resource_access"]["servers"].append(access.server_name)
|
||
|
||
# Также добавляем в старое поле servers для совместимости
|
||
if "servers" not in users[username]:
|
||
users[username]["servers"] = []
|
||
if access.server_name not in users[username]["servers"]:
|
||
users[username]["servers"].append(access.server_name)
|
||
(duplicate-code)
|
||
auth.py:1:0: R0801: Similar lines in 2 files
|
||
==migrate_users:[72:80]
|
||
==user_management_endpoints:[96:104]
|
||
"manage_users": True,
|
||
"manage_roles": True,
|
||
"manage_servers": True,
|
||
"manage_tickets": True,
|
||
"manage_files": True,
|
||
"delete_users": True,
|
||
"view_all_resources": True
|
||
} (duplicate-code)
|
||
auth.py:1:0: R0801: Similar lines in 2 files
|
||
==migrate_users:[104:112]
|
||
==user_management_endpoints:[106:114]
|
||
"manage_users": True,
|
||
"manage_roles": False,
|
||
"manage_servers": True,
|
||
"manage_tickets": True,
|
||
"manage_files": True,
|
||
"delete_users": False,
|
||
"view_all_resources": True
|
||
} (duplicate-code)
|
||
auth.py:1:0: R0801: Similar lines in 2 files
|
||
==migrate_users:[115:123]
|
||
==user_management_endpoints:[116:124]
|
||
"manage_users": False,
|
||
"manage_roles": False,
|
||
"manage_servers": False,
|
||
"manage_tickets": True,
|
||
"manage_files": False,
|
||
"delete_users": False,
|
||
"view_all_resources": False
|
||
} (duplicate-code)
|
||
auth.py:1:0: R0801: Similar lines in 2 files
|
||
==migrate_users:[126:134]
|
||
==user_management_endpoints:[126:134]
|
||
"manage_users": False,
|
||
"manage_roles": False,
|
||
"manage_servers": False,
|
||
"manage_tickets": False,
|
||
"manage_files": False,
|
||
"delete_users": False,
|
||
"view_all_resources": False
|
||
} (duplicate-code)
|
||
auth.py:1:0: R0801: Similar lines in 2 files
|
||
==main:[1837:1848]
|
||
==user_management_endpoints:[210:221]
|
||
if username not in users:
|
||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
|
||
if "resource_access" in users[username] and "servers" in users[username]["resource_access"]:
|
||
if server_name in users[username]["resource_access"]["servers"]:
|
||
users[username]["resource_access"]["servers"].remove(server_name)
|
||
|
||
# Также удаляем из старого поля servers
|
||
if "servers" in users[username] and server_name in users[username]["servers"]:
|
||
users[username]["servers"].remove(server_name)
|
||
(duplicate-code)
|
||
auth.py:1:0: R0801: Similar lines in 2 files
|
||
==main:[1648:1657]
|
||
==user_management_endpoints:[57:68]
|
||
users_list = []
|
||
for username, user_data in users.items():
|
||
user_copy = user_data.copy()
|
||
user_copy.pop("password", None)
|
||
users_list.append(user_copy)
|
||
|
||
return users_list
|
||
|
||
# 2. Изменить роль пользователя (duplicate-code)
|
||
auth.py:1:0: R0801: Similar lines in 2 files
|
||
==main:[1666:1674]
|
||
==user_management_endpoints:[72:81]
|
||
if username not in users:
|
||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
|
||
if username == current_user.get("username"):
|
||
raise HTTPException(status_code=400, detail="Нельзя изменить свою роль")
|
||
|
||
valid_roles = ["owner", "admin", "support", "user", "banned"]
|
||
if role_data.role not in valid_roles: (duplicate-code)
|
||
auth.py:1:0: R0801: Similar lines in 2 files
|
||
==main:[1759:1767]
|
||
==user_management_endpoints:[296:304]
|
||
if username not in users:
|
||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
|
||
if users[username].get("role") != "banned":
|
||
raise HTTPException(status_code=400, detail="Пользователь не заблокирован")
|
||
|
||
users[username]["role"] = "user"
|
||
users[username]["permissions"] = { (duplicate-code)
|
||
auth.py:1:0: R0801: Similar lines in 2 files
|
||
==auth:[20:30]
|
||
==main:[85:95]
|
||
if USERS_FILE.exists():
|
||
with open(USERS_FILE, 'r', encoding='utf-8') as f:
|
||
return json.load(f)
|
||
return {}
|
||
|
||
def save_users(users: dict):
|
||
with open(USERS_FILE, 'w', encoding='utf-8') as f:
|
||
json.dump(users, f, indent=2, ensure_ascii=False)
|
||
|
||
def load_server_config(server_name: str) -> dict: (duplicate-code)
|
||
auth.py:1:0: R0801: Similar lines in 2 files
|
||
==migrate_users:[105:110]
|
||
==user_management_endpoints:[137:142]
|
||
"manage_roles": False,
|
||
"manage_servers": True,
|
||
"manage_tickets": True,
|
||
"manage_files": True,
|
||
"delete_users": False, (duplicate-code)
|
||
auth.py:1:0: R0801: Similar lines in 2 files
|
||
==migrate_users:[138:143]
|
||
==user_management_endpoints:[107:112]
|
||
"manage_roles": False,
|
||
"manage_servers": True,
|
||
"manage_tickets": True,
|
||
"manage_files": True,
|
||
"delete_users": False, (duplicate-code)
|
||
auth.py:1:0: R0801: Similar lines in 2 files
|
||
==migrate_users:[137:145]
|
||
==user_management_endpoints:[136:145]
|
||
"manage_users": False,
|
||
"manage_roles": False,
|
||
"manage_servers": True,
|
||
"manage_tickets": True,
|
||
"manage_files": True,
|
||
"delete_users": False,
|
||
"view_all_resources": False
|
||
} (duplicate-code)
|
||
auth.py:1:0: R0801: Similar lines in 2 files
|
||
==main:[1784:1792]
|
||
==user_management_endpoints:[236:243]
|
||
if username not in users:
|
||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
|
||
if username == current_user.get("username"):
|
||
raise HTTPException(status_code=400, detail="Нельзя удалить самого себя")
|
||
|
||
# Проверяем, что не удаляем последнего владельца
|
||
if users[username].get("role") == "owner": (duplicate-code)
|
||
auth.py:1:0: R0801: Similar lines in 2 files
|
||
==main:[1728:1736]
|
||
==user_management_endpoints:[260:267]
|
||
if username not in users:
|
||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
|
||
if username == current_user.get("username"):
|
||
raise HTTPException(status_code=400, detail="Нельзя заблокировать самого себя")
|
||
|
||
if users[username].get("role") == "owner": (duplicate-code)
|
||
|
||
-----------------------------------
|
||
Your code has been rated at 5.22/10
|
||
|
||
+ echo "Checking code formatting with black..."
|
||
Checking code formatting with black...
|
||
+ black --check --diff .
|
||
--- /drone/src/backend/models.py 2026-01-15 13:56:18.978682+00:00
|
||
+++ /drone/src/backend/models.py 2026-01-15 13:56:47.031210+00:00
|
||
@@ -1,23 +1,28 @@
|
||
from pydantic import BaseModel
|
||
from typing import Optional, List
|
||
+
|
||
|
||
class UserRegister(BaseModel):
|
||
username: str
|
||
password: str
|
||
|
||
+
|
||
class UserLogin(BaseModel):
|
||
username: str
|
||
password: str
|
||
+
|
||
|
||
class Token(BaseModel):
|
||
access_token: str
|
||
token_type: str
|
||
username: str
|
||
role: str
|
||
|
||
+
|
||
class ServerAccess(BaseModel):
|
||
username: str
|
||
server_name: str
|
||
|
||
+
|
||
class ServerAccessList(BaseModel):
|
||
users: List[dict]
|
||
would reformat /drone/src/backend/models.py
|
||
--- /drone/src/backend/oidc_config.py 2026-01-15 13:56:18.978682+00:00
|
||
+++ /drone/src/backend/oidc_config.py 2026-01-15 13:56:47.059348+00:00
|
||
@@ -1,31 +1,35 @@
|
||
"""
|
||
Конфигурация OpenID Connect провайдеров
|
||
"""
|
||
+
|
||
import os
|
||
from typing import Dict, Any
|
||
|
||
# Конфигурация провайдеров OpenID Connect
|
||
OIDC_PROVIDERS = {
|
||
"zitadel": {
|
||
"name": "ZITADEL",
|
||
"client_id": os.getenv("ZITADEL_CLIENT_ID", ""),
|
||
"client_secret": os.getenv("ZITADEL_CLIENT_SECRET", ""),
|
||
- "server_metadata_url": os.getenv("ZITADEL_ISSUER", "") + "/.well-known/openid-configuration",
|
||
+ "server_metadata_url": os.getenv("ZITADEL_ISSUER", "")
|
||
+ + "/.well-known/openid-configuration",
|
||
"issuer": os.getenv("ZITADEL_ISSUER", ""),
|
||
"scopes": ["openid", "email", "profile"],
|
||
"icon": "🔐",
|
||
- "color": "bg-purple-600 hover:bg-purple-700"
|
||
+ "color": "bg-purple-600 hover:bg-purple-700",
|
||
}
|
||
}
|
||
+
|
||
|
||
def get_enabled_providers() -> Dict[str, Dict[str, Any]]:
|
||
"""Получить список включённых провайдеров (с настроенными client_id)"""
|
||
enabled = {}
|
||
for provider_id, config in OIDC_PROVIDERS.items():
|
||
if config.get("client_id") and config.get("issuer"):
|
||
enabled[provider_id] = config
|
||
return enabled
|
||
|
||
+
|
||
def get_redirect_uri(provider_id: str, base_url: str = "http://localhost:8000") -> str:
|
||
"""Получить redirect URI для провайдера"""
|
||
- return f"{base_url}/api/auth/oidc/{provider_id}/callback"
|
||
\ No newline at end of file
|
||
+ return f"{base_url}/api/auth/oidc/{provider_id}/callback"
|
||
would reformat /drone/src/backend/oidc_config.py
|
||
--- /drone/src/backend/auth.py 2026-01-15 13:56:18.974682+00:00
|
||
+++ /drone/src/backend/auth.py 2026-01-15 13:56:47.113090+00:00
|
||
@@ -15,25 +15,30 @@
|
||
security = HTTPBearer()
|
||
|
||
USERS_FILE = Path("data/users.json")
|
||
USERS_FILE.parent.mkdir(exist_ok=True)
|
||
|
||
+
|
||
def load_users():
|
||
if USERS_FILE.exists():
|
||
- with open(USERS_FILE, 'r', encoding='utf-8') as f:
|
||
+ with open(USERS_FILE, "r", encoding="utf-8") as f:
|
||
return json.load(f)
|
||
return {}
|
||
|
||
+
|
||
def save_users(users):
|
||
- with open(USERS_FILE, 'w', encoding='utf-8') as f:
|
||
+ with open(USERS_FILE, "w", encoding="utf-8") as f:
|
||
json.dump(users, f, indent=2, ensure_ascii=False)
|
||
+
|
||
|
||
def verify_password(plain_password, hashed_password):
|
||
return pwd_context.verify(plain_password, hashed_password)
|
||
|
||
+
|
||
def get_password_hash(password):
|
||
return pwd_context.hash(password)
|
||
+
|
||
|
||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
||
to_encode = data.copy()
|
||
if expires_delta:
|
||
expire = datetime.utcnow() + expires_delta
|
||
@@ -41,72 +46,79 @@
|
||
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||
to_encode.update({"exp": expire})
|
||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||
return encoded_jwt
|
||
|
||
+
|
||
def decode_token(token: str):
|
||
try:
|
||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||
return payload
|
||
except JWTError:
|
||
return None
|
||
|
||
-async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
|
||
+
|
||
+async def get_current_user(
|
||
+ credentials: HTTPAuthorizationCredentials = Depends(security),
|
||
+):
|
||
token = credentials.credentials
|
||
payload = decode_token(token)
|
||
-
|
||
+
|
||
if payload is None:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||
- detail="Неверный токен авторизации"
|
||
+ detail="Неверный токен авторизации",
|
||
)
|
||
-
|
||
+
|
||
username: str = payload.get("sub")
|
||
if username is None:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||
- detail="Неверный токен авторизации"
|
||
+ detail="Неверный токен авторизации",
|
||
)
|
||
-
|
||
+
|
||
users = load_users()
|
||
if username not in users:
|
||
raise HTTPException(
|
||
- status_code=status.HTTP_401_UNAUTHORIZED,
|
||
- detail="Пользователь не найден"
|
||
+ status_code=status.HTTP_401_UNAUTHORIZED, detail="Пользователь не найден"
|
||
)
|
||
-
|
||
+
|
||
return {"username": username, "role": users[username].get("role", "user")}
|
||
+
|
||
|
||
def authenticate_user(username: str, password: str):
|
||
users = load_users()
|
||
if username not in users:
|
||
return False
|
||
user = users[username]
|
||
if not verify_password(password, user["password"]):
|
||
return False
|
||
return user
|
||
|
||
+
|
||
def create_user(username: str, password: str, role: str = "user"):
|
||
users = load_users()
|
||
if username in users:
|
||
return False
|
||
-
|
||
+
|
||
users[username] = {
|
||
"password": get_password_hash(password),
|
||
"role": role,
|
||
"created_at": datetime.utcnow().isoformat(),
|
||
- "servers": [] # Список серверов к которым есть доступ
|
||
+ "servers": [], # Список серверов к которым есть доступ
|
||
}
|
||
save_users(users)
|
||
return True
|
||
+
|
||
|
||
def get_user_servers(username: str):
|
||
"""Получить список серверов пользователя"""
|
||
users = load_users()
|
||
if username not in users:
|
||
return []
|
||
return users[username].get("servers", [])
|
||
+
|
||
|
||
def add_server_to_user(username: str, server_name: str):
|
||
"""Добавить сервер пользователю"""
|
||
users = load_users()
|
||
if username not in users:
|
||
@@ -116,31 +128,31 @@
|
||
if server_name not in users[username]["servers"]:
|
||
users[username]["servers"].append(server_name)
|
||
save_users(users)
|
||
return True
|
||
|
||
+
|
||
def remove_server_from_user(username: str, server_name: str):
|
||
"""Удалить сервер у пользователя"""
|
||
users = load_users()
|
||
if username not in users:
|
||
return False
|
||
if "servers" in users[username] and server_name in users[username]["servers"]:
|
||
users[username]["servers"].remove(server_name)
|
||
save_users(users)
|
||
return True
|
||
|
||
+
|
||
def get_server_users(server_name: str):
|
||
"""Получить список пользователей с доступом к серверу"""
|
||
users = load_users()
|
||
result = []
|
||
for username, user_data in users.items():
|
||
if server_name in user_data.get("servers", []):
|
||
- result.append({
|
||
- "username": username,
|
||
- "role": user_data.get("role", "user")
|
||
- })
|
||
+ result.append({"username": username, "role": user_data.get("role", "user")})
|
||
return result
|
||
+
|
||
|
||
def has_server_access(username: str, server_name: str):
|
||
"""Проверить есть ли доступ к серверу"""
|
||
users = load_users()
|
||
if username not in users:
|
||
would reformat /drone/src/backend/auth.py
|
||
--- /drone/src/backend/migrate_users.py 2026-01-15 13:56:18.978682+00:00
|
||
+++ /drone/src/backend/migrate_users.py 2026-01-15 13:56:47.227169+00:00
|
||
@@ -6,21 +6,24 @@
|
||
|
||
import json
|
||
from pathlib import Path
|
||
from datetime import datetime
|
||
|
||
+
|
||
def migrate_users():
|
||
"""Миграция пользователей на новую систему прав"""
|
||
-
|
||
+
|
||
users_file = Path("users.json")
|
||
-
|
||
+
|
||
# Проверка существования файла
|
||
if not users_file.exists():
|
||
print("❌ Файл users.json не найден")
|
||
- print("ℹ️ Создайте файл users.json или запустите панель для автоматического создания")
|
||
- return False
|
||
-
|
||
+ print(
|
||
+ "ℹ️ Создайте файл users.json или запустите панель для автоматического создания"
|
||
+ )
|
||
+ return False
|
||
+
|
||
# Создание backup
|
||
backup_file = Path(f"users_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json")
|
||
try:
|
||
with open(users_file, "r", encoding="utf-8") as f:
|
||
backup_data = f.read()
|
||
@@ -28,22 +31,22 @@
|
||
f.write(backup_data)
|
||
print(f"✅ Backup создан: {backup_file}")
|
||
except Exception as e:
|
||
print(f"❌ Ошибка создания backup: {e}")
|
||
return False
|
||
-
|
||
+
|
||
# Загрузка пользователей
|
||
try:
|
||
with open(users_file, "r", encoding="utf-8") as f:
|
||
users_data = json.load(f)
|
||
except json.JSONDecodeError:
|
||
print("❌ Ошибка чтения users.json - неверный формат JSON")
|
||
return False
|
||
except Exception as e:
|
||
print(f"❌ Ошибка чтения файла: {e}")
|
||
return False
|
||
-
|
||
+
|
||
# Проверка формата (объект или список)
|
||
if isinstance(users_data, dict):
|
||
# Формат: {"username": {...}}
|
||
users_list = list(users_data.values())
|
||
is_dict_format = True
|
||
@@ -54,18 +57,18 @@
|
||
is_dict_format = False
|
||
print("ℹ️ Обнаружен формат: список")
|
||
else:
|
||
print("❌ Неизвестный формат users.json")
|
||
return False
|
||
-
|
||
+
|
||
if not users_list:
|
||
print("ℹ️ Нет пользователей для миграции")
|
||
return True
|
||
-
|
||
+
|
||
print(f"\n📊 Найдено пользователей: {len(users_list)}")
|
||
print("=" * 50)
|
||
-
|
||
+
|
||
# Миграция первого пользователя (владелец)
|
||
if users_list:
|
||
first_user = users_list[0]
|
||
print(f"\n👑 Назначение владельца: {first_user.get('username', 'Unknown')}")
|
||
first_user["role"] = "owner"
|
||
@@ -74,88 +77,93 @@
|
||
"manage_roles": True,
|
||
"manage_servers": True,
|
||
"manage_tickets": True,
|
||
"manage_files": True,
|
||
"delete_users": True,
|
||
- "view_all_resources": True
|
||
+ "view_all_resources": True,
|
||
}
|
||
if "resource_access" not in first_user:
|
||
first_user["resource_access"] = {
|
||
"servers": first_user.get("servers", []),
|
||
"tickets": [],
|
||
- "files": []
|
||
+ "files": [],
|
||
}
|
||
-
|
||
+
|
||
# Миграция остальных пользователей
|
||
for i, user in enumerate(users_list[1:], start=2):
|
||
username = user.get("username", f"User{i}")
|
||
current_role = user.get("role", "user")
|
||
-
|
||
+
|
||
print(f"\n👤 Пользователь {i}: {username}")
|
||
print(f" Текущая роль: {current_role}")
|
||
-
|
||
+
|
||
# Установка роли по умолчанию
|
||
- if "role" not in user or user["role"] not in ["admin", "support", "user", "banned"]:
|
||
+ if "role" not in user or user["role"] not in [
|
||
+ "admin",
|
||
+ "support",
|
||
+ "user",
|
||
+ "banned",
|
||
+ ]:
|
||
user["role"] = "user"
|
||
print(f" ➜ Установлена роль: user")
|
||
-
|
||
+
|
||
# Добавление прав
|
||
if "permissions" not in user:
|
||
if user["role"] == "admin":
|
||
user["permissions"] = {
|
||
"manage_users": True,
|
||
"manage_roles": False,
|
||
"manage_servers": True,
|
||
"manage_tickets": True,
|
||
"manage_files": True,
|
||
"delete_users": False,
|
||
- "view_all_resources": True
|
||
+ "view_all_resources": True,
|
||
}
|
||
print(" ➜ Добавлены права администратора")
|
||
elif user["role"] == "support":
|
||
user["permissions"] = {
|
||
"manage_users": False,
|
||
"manage_roles": False,
|
||
"manage_servers": False,
|
||
"manage_tickets": True,
|
||
"manage_files": False,
|
||
"delete_users": False,
|
||
- "view_all_resources": False
|
||
+ "view_all_resources": False,
|
||
}
|
||
print(" ➜ Добавлены права поддержки")
|
||
elif user["role"] == "banned":
|
||
user["permissions"] = {
|
||
"manage_users": False,
|
||
"manage_roles": False,
|
||
"manage_servers": False,
|
||
"manage_tickets": False,
|
||
"manage_files": False,
|
||
"delete_users": False,
|
||
- "view_all_resources": False
|
||
+ "view_all_resources": False,
|
||
}
|
||
print(" ➜ Пользователь заблокирован")
|
||
else: # user
|
||
user["permissions"] = {
|
||
"manage_users": False,
|
||
"manage_roles": False,
|
||
"manage_servers": True,
|
||
"manage_tickets": True,
|
||
"manage_files": True,
|
||
"delete_users": False,
|
||
- "view_all_resources": False
|
||
+ "view_all_resources": False,
|
||
}
|
||
print(" ➜ Добавлены права пользователя")
|
||
-
|
||
+
|
||
# Добавление доступа к ресурсам
|
||
if "resource_access" not in user:
|
||
user["resource_access"] = {
|
||
"servers": user.get("servers", []),
|
||
"tickets": [],
|
||
- "files": []
|
||
+ "files": [],
|
||
}
|
||
print(" ➜ Добавлен доступ к ресурсам")
|
||
-
|
||
+
|
||
would reformat /drone/src/backend/migrate_users.py
|
||
# Сохранение в правильном формате
|
||
try:
|
||
if is_dict_format:
|
||
# Сохраняем обратно как объект
|
||
users_dict = {user["username"]: user for user in users_list}
|
||
@@ -163,11 +171,11 @@
|
||
json.dump(users_dict, f, indent=2, ensure_ascii=False)
|
||
else:
|
||
# Сохраняем как список
|
||
with open(users_file, "w", encoding="utf-8") as f:
|
||
json.dump(users_list, f, indent=2, ensure_ascii=False)
|
||
-
|
||
+
|
||
print("\n" + "=" * 50)
|
||
print("✅ Миграция успешно завершена!")
|
||
print(f"✅ Обновлено пользователей: {len(users_list)}")
|
||
print(f"👑 Владелец: {users_list[0]['username']}")
|
||
print(f"📁 Backup: {backup_file}")
|
||
@@ -175,62 +183,64 @@
|
||
except Exception as e:
|
||
print(f"\n❌ Ошибка сохранения: {e}")
|
||
print(f"ℹ️ Восстановите из backup: {backup_file}")
|
||
return False
|
||
|
||
+
|
||
def show_users():
|
||
"""Показать список пользователей после миграции"""
|
||
users_file = Path("users.json")
|
||
-
|
||
+
|
||
if not users_file.exists():
|
||
print("❌ Файл users.json не найден")
|
||
return
|
||
-
|
||
+
|
||
try:
|
||
with open(users_file, "r", encoding="utf-8") as f:
|
||
users_data = json.load(f)
|
||
except Exception as e:
|
||
print(f"❌ Ошибка чтения файла: {e}")
|
||
return
|
||
-
|
||
+
|
||
# Преобразуем в список если это объект
|
||
if isinstance(users_data, dict):
|
||
users_list = list(users_data.values())
|
||
else:
|
||
users_list = users_data
|
||
-
|
||
+
|
||
print("\n" + "=" * 50)
|
||
print("📋 СПИСОК ПОЛЬЗОВАТЕЛЕЙ")
|
||
print("=" * 50)
|
||
-
|
||
+
|
||
for i, user in enumerate(users_list, start=1):
|
||
print(f"\n{i}. {user.get('username', 'Unknown')}")
|
||
print(f" Роль: {user.get('role', 'unknown')}")
|
||
print(f" Права:")
|
||
- for perm, value in user.get('permissions', {}).items():
|
||
+ for perm, value in user.get("permissions", {}).items():
|
||
status = "✅" if value else "❌"
|
||
print(f" {status} {perm}")
|
||
-
|
||
+
|
||
# Показать доступ к ресурсам
|
||
- resource_access = user.get('resource_access', {})
|
||
+ resource_access = user.get("resource_access", {})
|
||
if resource_access:
|
||
- servers = resource_access.get('servers', [])
|
||
+ servers = resource_access.get("servers", [])
|
||
if servers:
|
||
print(f" Серверы: {', '.join(servers)}")
|
||
|
||
+
|
||
if __name__ == "__main__":
|
||
print("=" * 50)
|
||
print("MC Panel - Миграция пользователей v1.1.0")
|
||
print("=" * 50)
|
||
-
|
||
+
|
||
# Запуск миграции
|
||
success = migrate_users()
|
||
-
|
||
+
|
||
if success:
|
||
# Показать результат
|
||
show_users()
|
||
-
|
||
+
|
||
print("\n" + "=" * 50)
|
||
print("📝 СЛЕДУЮЩИЕ ШАГИ:")
|
||
print("=" * 50)
|
||
print("1. Перезапустите панель")
|
||
print("2. Войдите как владелец")
|
||
--- /drone/src/backend/user_management_endpoints.py 2026-01-15 13:56:18.978682+00:00
|
||
+++ /drone/src/backend/user_management_endpoints.py 2026-01-15 13:56:47.279409+00:00
|
||
@@ -9,312 +9,335 @@
|
||
import json
|
||
from pathlib import Path
|
||
|
||
router = APIRouter()
|
||
|
||
+
|
||
# Модели данных
|
||
class RoleChange(BaseModel):
|
||
role: str
|
||
|
||
+
|
||
class PermissionsUpdate(BaseModel):
|
||
permissions: dict
|
||
|
||
+
|
||
class ServerAccess(BaseModel):
|
||
server_name: str
|
||
|
||
+
|
||
class BanRequest(BaseModel):
|
||
reason: str = "Заблокирован администратором"
|
||
+
|
||
|
||
# Загрузка пользователей
|
||
def load_users():
|
||
users_file = Path("users.json")
|
||
if not users_file.exists():
|
||
return {}
|
||
-
|
||
+
|
||
with open(users_file, "r", encoding="utf-8") as f:
|
||
return json.load(f)
|
||
+
|
||
|
||
# Сохранение пользователей
|
||
def save_users(users):
|
||
with open("users.json", "w", encoding="utf-8") as f:
|
||
json.dump(users, f, indent=2, ensure_ascii=False)
|
||
|
||
+
|
||
# Проверка прав
|
||
def require_owner(current_user: dict):
|
||
if current_user.get("role") != "owner":
|
||
raise HTTPException(status_code=403, detail="Требуется роль владельца")
|
||
|
||
+
|
||
def require_admin_or_owner(current_user: dict):
|
||
if current_user.get("role") not in ["owner", "admin"]:
|
||
- raise HTTPException(status_code=403, detail="Требуется роль администратора или владельца")
|
||
+ raise HTTPException(
|
||
+ status_code=403, detail="Требуется роль администратора или владельца"
|
||
+ )
|
||
+
|
||
|
||
# 1. Получить список пользователей
|
||
@router.get("/api/users")
|
||
async def get_users(current_user: dict = Depends()):
|
||
require_admin_or_owner(current_user)
|
||
-
|
||
- users = load_users()
|
||
-
|
||
+
|
||
+ users = load_users()
|
||
+
|
||
# Возвращаем список пользователей (без паролей)
|
||
users_list = []
|
||
for username, user_data in users.items():
|
||
user_copy = user_data.copy()
|
||
user_copy.pop("password", None)
|
||
users_list.append(user_copy)
|
||
-
|
||
+
|
||
return users_list
|
||
+
|
||
|
||
# 2. Изменить роль пользователя
|
||
@router.put("/api/users/{username}/role")
|
||
-async def change_user_role(username: str, role_data: RoleChange, current_user: dict = Depends()):
|
||
+async def change_user_role(
|
||
+ username: str, role_data: RoleChange, current_user: dict = Depends()
|
||
+):
|
||
require_owner(current_user)
|
||
-
|
||
- users = load_users()
|
||
-
|
||
- if username not in users:
|
||
- raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
-
|
||
+
|
||
+ users = load_users()
|
||
+
|
||
+ if username not in users:
|
||
+ raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
+
|
||
if username == current_user.get("username"):
|
||
raise HTTPException(status_code=400, detail="Нельзя изменить свою роль")
|
||
-
|
||
+
|
||
# Проверка валидности роли
|
||
valid_roles = ["owner", "admin", "support", "user", "banned"]
|
||
if role_data.role not in valid_roles:
|
||
- raise HTTPException(status_code=400, detail=f"Неверная роль. Доступные: {', '.join(valid_roles)}")
|
||
-
|
||
+ raise HTTPException(
|
||
+ status_code=400,
|
||
+ detail=f"Неверная роль. Доступные: {', '.join(valid_roles)}",
|
||
+ )
|
||
+
|
||
# Если назначается новый owner, текущий owner становится admin
|
||
if role_data.role == "owner":
|
||
for user in users.values():
|
||
if user.get("role") == "owner":
|
||
user["role"] = "admin"
|
||
-
|
||
+
|
||
# Изменяем роль
|
||
old_role = users[username].get("role", "user")
|
||
users[username]["role"] = role_data.role
|
||
-
|
||
+
|
||
# Обновляем права в зависимости от роли
|
||
if role_data.role == "owner":
|
||
users[username]["permissions"] = {
|
||
would reformat /drone/src/backend/user_management_endpoints.py
|
||
"manage_users": True,
|
||
"manage_roles": True,
|
||
"manage_servers": True,
|
||
"manage_tickets": True,
|
||
"manage_files": True,
|
||
"delete_users": True,
|
||
- "view_all_resources": True
|
||
+ "view_all_resources": True,
|
||
}
|
||
elif role_data.role == "admin":
|
||
users[username]["permissions"] = {
|
||
"manage_users": True,
|
||
"manage_roles": False,
|
||
"manage_servers": True,
|
||
"manage_tickets": True,
|
||
"manage_files": True,
|
||
"delete_users": False,
|
||
- "view_all_resources": True
|
||
+ "view_all_resources": True,
|
||
}
|
||
elif role_data.role == "support":
|
||
users[username]["permissions"] = {
|
||
"manage_users": False,
|
||
"manage_roles": False,
|
||
"manage_servers": False,
|
||
"manage_tickets": True,
|
||
"manage_files": False,
|
||
"delete_users": False,
|
||
- "view_all_resources": False
|
||
+ "view_all_resources": False,
|
||
}
|
||
elif role_data.role == "banned":
|
||
users[username]["permissions"] = {
|
||
"manage_users": False,
|
||
"manage_roles": False,
|
||
"manage_servers": False,
|
||
"manage_tickets": False,
|
||
"manage_files": False,
|
||
"delete_users": False,
|
||
- "view_all_resources": False
|
||
+ "view_all_resources": False,
|
||
}
|
||
else: # user
|
||
users[username]["permissions"] = {
|
||
"manage_users": False,
|
||
"manage_roles": False,
|
||
"manage_servers": True,
|
||
"manage_tickets": True,
|
||
"manage_files": True,
|
||
"delete_users": False,
|
||
- "view_all_resources": False
|
||
- }
|
||
-
|
||
- save_users(users)
|
||
-
|
||
+ "view_all_resources": False,
|
||
+ }
|
||
+
|
||
+ save_users(users)
|
||
+
|
||
return {
|
||
"message": f"Роль пользователя {username} изменена с {old_role} на {role_data.role}",
|
||
- "user": {
|
||
- "username": username,
|
||
- "role": role_data.role
|
||
- }
|
||
- }
|
||
+ "user": {"username": username, "role": role_data.role},
|
||
+ }
|
||
+
|
||
|
||
# 3. Изменить права пользователя
|
||
@router.put("/api/users/{username}/permissions")
|
||
-async def update_user_permissions(username: str, perms: PermissionsUpdate, current_user: dict = Depends()):
|
||
+async def update_user_permissions(
|
||
+ username: str, perms: PermissionsUpdate, current_user: dict = Depends()
|
||
+):
|
||
require_owner(current_user)
|
||
-
|
||
- users = load_users()
|
||
-
|
||
- if username not in users:
|
||
- raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
-
|
||
+
|
||
+ users = load_users()
|
||
+
|
||
+ if username not in users:
|
||
+ raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
+
|
||
users[username]["permissions"] = perms.permissions
|
||
save_users(users)
|
||
-
|
||
+
|
||
return {
|
||
"message": f"Права пользователя {username} обновлены",
|
||
- "permissions": perms.permissions
|
||
- }
|
||
+ "permissions": perms.permissions,
|
||
+ }
|
||
+
|
||
|
||
# 4. Выдать доступ к серверу
|
||
@router.post("/api/users/{username}/access/servers")
|
||
-async def grant_server_access(username: str, access: ServerAccess, current_user: dict = Depends()):
|
||
- require_admin_or_owner(current_user)
|
||
-
|
||
- users = load_users()
|
||
-
|
||
- if username not in users:
|
||
- raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
-
|
||
+async def grant_server_access(
|
||
+ username: str, access: ServerAccess, current_user: dict = Depends()
|
||
+):
|
||
+ require_admin_or_owner(current_user)
|
||
+
|
||
+ users = load_users()
|
||
+
|
||
+ if username not in users:
|
||
+ raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
+
|
||
if "resource_access" not in users[username]:
|
||
users[username]["resource_access"] = {"servers": [], "tickets": [], "files": []}
|
||
-
|
||
+
|
||
if access.server_name not in users[username]["resource_access"]["servers"]:
|
||
users[username]["resource_access"]["servers"].append(access.server_name)
|
||
-
|
||
+
|
||
# Также добавляем в старое поле servers для совместимости
|
||
if "servers" not in users[username]:
|
||
users[username]["servers"] = []
|
||
if access.server_name not in users[username]["servers"]:
|
||
users[username]["servers"].append(access.server_name)
|
||
-
|
||
- save_users(users)
|
||
-
|
||
+
|
||
+ save_users(users)
|
||
+
|
||
return {
|
||
"message": f"Доступ к серверу {access.server_name} выдан пользователю {username}",
|
||
"server": access.server_name,
|
||
- "user": username
|
||
- }
|
||
+ "user": username,
|
||
+ }
|
||
+
|
||
|
||
# 5. Забрать доступ к серверу
|
||
@router.delete("/api/users/{username}/access/servers/{server_name}")
|
||
-async def revoke_server_access(username: str, server_name: str, current_user: dict = Depends()):
|
||
- require_admin_or_owner(current_user)
|
||
-
|
||
- users = load_users()
|
||
-
|
||
- if username not in users:
|
||
- raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
-
|
||
- if "resource_access" in users[username] and "servers" in users[username]["resource_access"]:
|
||
+async def revoke_server_access(
|
||
+ username: str, server_name: str, current_user: dict = Depends()
|
||
+):
|
||
+ require_admin_or_owner(current_user)
|
||
+
|
||
+ users = load_users()
|
||
+
|
||
+ if username not in users:
|
||
+ raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
+
|
||
+ if (
|
||
+ "resource_access" in users[username]
|
||
+ and "servers" in users[username]["resource_access"]
|
||
+ ):
|
||
if server_name in users[username]["resource_access"]["servers"]:
|
||
users[username]["resource_access"]["servers"].remove(server_name)
|
||
-
|
||
+
|
||
# Также удаляем из старого поля servers
|
||
if "servers" in users[username] and server_name in users[username]["servers"]:
|
||
users[username]["servers"].remove(server_name)
|
||
-
|
||
- save_users(users)
|
||
-
|
||
+
|
||
+ save_users(users)
|
||
+
|
||
return {
|
||
"message": f"Доступ к серверу {server_name} отозван у пользователя {username}",
|
||
"server": server_name,
|
||
- "user": username
|
||
- }
|
||
+ "user": username,
|
||
+ }
|
||
+
|
||
|
||
# 6. Удалить пользователя
|
||
@router.delete("/api/users/{username}")
|
||
async def delete_user(username: str, current_user: dict = Depends()):
|
||
require_owner(current_user)
|
||
-
|
||
- users = load_users()
|
||
-
|
||
- if username not in users:
|
||
- raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
-
|
||
+
|
||
+ users = load_users()
|
||
+
|
||
+ if username not in users:
|
||
+ raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
+
|
||
if username == current_user.get("username"):
|
||
raise HTTPException(status_code=400, detail="Нельзя удалить самого себя")
|
||
-
|
||
+
|
||
if users[username].get("role") == "owner":
|
||
raise HTTPException(status_code=400, detail="Нельзя удалить владельца")
|
||
-
|
||
+
|
||
del users[username]
|
||
save_users(users)
|
||
-
|
||
- return {
|
||
- "message": f"Пользователь {username} удалён",
|
||
- "username": username
|
||
- }
|
||
+
|
||
+ return {"message": f"Пользователь {username} удалён", "username": username}
|
||
+
|
||
|
||
# 7. Заблокировать пользователя
|
||
@router.post("/api/users/{username}/ban")
|
||
async def ban_user(username: str, ban_data: BanRequest, current_user: dict = Depends()):
|
||
require_admin_or_owner(current_user)
|
||
-
|
||
- users = load_users()
|
||
-
|
||
- if username not in users:
|
||
- raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
-
|
||
+
|
||
+ users = load_users()
|
||
+
|
||
+ if username not in users:
|
||
+ raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
+
|
||
if username == current_user.get("username"):
|
||
raise HTTPException(status_code=400, detail="Нельзя заблокировать самого себя")
|
||
-
|
||
+
|
||
if users[username].get("role") == "owner":
|
||
raise HTTPException(status_code=400, detail="Нельзя заблокировать владельца")
|
||
-
|
||
+
|
||
users[username]["role"] = "banned"
|
||
users[username]["permissions"] = {
|
||
"manage_users": False,
|
||
"manage_roles": False,
|
||
"manage_servers": False,
|
||
"manage_tickets": False,
|
||
"manage_files": False,
|
||
"delete_users": False,
|
||
- "view_all_resources": False
|
||
+ "view_all_resources": False,
|
||
}
|
||
users[username]["ban_reason"] = ban_data.reason
|
||
-
|
||
- save_users(users)
|
||
-
|
||
+
|
||
+ save_users(users)
|
||
+
|
||
return {
|
||
"message": f"Пользователь {username} заблокирован",
|
||
"username": username,
|
||
- "reason": ban_data.reason
|
||
- }
|
||
+ "reason": ban_data.reason,
|
||
+ }
|
||
+
|
||
|
||
# 8. Разблокировать пользователя
|
||
@router.post("/api/users/{username}/unban")
|
||
async def unban_user(username: str, current_user: dict = Depends()):
|
||
require_admin_or_owner(current_user)
|
||
-
|
||
- users = load_users()
|
||
-
|
||
- if username not in users:
|
||
- raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
-
|
||
+
|
||
+ users = load_users()
|
||
+
|
||
+ if username not in users:
|
||
+ raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
+
|
||
if users[username].get("role") != "banned":
|
||
raise HTTPException(status_code=400, detail="Пользователь не заблокирован")
|
||
-
|
||
+
|
||
users[username]["role"] = "user"
|
||
users[username]["permissions"] = {
|
||
"manage_users": False,
|
||
"manage_roles": False,
|
||
"manage_servers": True,
|
||
"manage_tickets": True,
|
||
"manage_files": True,
|
||
"delete_users": False,
|
||
- "view_all_resources": False
|
||
+ "view_all_resources": False,
|
||
}
|
||
users[username].pop("ban_reason", None)
|
||
-
|
||
- save_users(users)
|
||
-
|
||
- return {
|
||
- "message": f"Пользователь {username} разблокирован",
|
||
- "username": username
|
||
- }
|
||
+
|
||
+ save_users(users)
|
||
+
|
||
+ return {"message": f"Пользователь {username} разблокирован", "username": username}
|
||
--- /drone/src/backend/main.py 2026-01-15 13:56:18.978682+00:00
|
||
+++ /drone/src/backend/main.py 2026-01-15 13:56:48.258411+00:00
|
||
@@ -1,6 +1,15 @@
|
||
-from fastapi import FastAPI, WebSocket, UploadFile, File, HTTPException, Depends, status, Request
|
||
+from fastapi import (
|
||
+ FastAPI,
|
||
+ WebSocket,
|
||
+ UploadFile,
|
||
+ File,
|
||
+ HTTPException,
|
||
+ Depends,
|
||
+ status,
|
||
+ Request,
|
||
+)
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
from fastapi.responses import FileResponse, RedirectResponse
|
||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||
from pydantic import BaseModel
|
||
import asyncio
|
||
@@ -36,11 +45,11 @@
|
||
oauth.register(
|
||
name="zitadel",
|
||
client_id=config["client_id"],
|
||
client_secret=config["client_secret"],
|
||
server_metadata_url=config["server_metadata_url"],
|
||
- client_kwargs={"scope": " ".join(config["scopes"])}
|
||
+ client_kwargs={"scope": " ".join(config["scopes"])},
|
||
)
|
||
print(f"✓ ZITADEL провайдер зарегистрирован: {config['issuer']}")
|
||
else:
|
||
print("⚠ ZITADEL провайдер не настроен. Проверьте .env файл.")
|
||
|
||
@@ -66,101 +75,114 @@
|
||
TICKETS_FILE = Path("tickets.json")
|
||
|
||
server_processes: dict[str, subprocess.Popen] = {}
|
||
server_logs: dict[str, list[str]] = {}
|
||
|
||
-IS_WINDOWS = sys.platform == 'win32'
|
||
+IS_WINDOWS = sys.platform == "win32"
|
||
+
|
||
|
||
# Инициализация файла пользователей
|
||
def init_users():
|
||
if not USERS_FILE.exists():
|
||
admin_user = {
|
||
"username": "Root",
|
||
"password": pwd_context.hash("Admin"),
|
||
"role": "admin",
|
||
- "servers": []
|
||
+ "servers": [],
|
||
}
|
||
save_users({"Sofa12345": admin_user})
|
||
print("Создан пользователь по умолчанию: none / none")
|
||
|
||
+
|
||
def load_users() -> dict:
|
||
if USERS_FILE.exists():
|
||
- with open(USERS_FILE, 'r', encoding='utf-8') as f:
|
||
+ with open(USERS_FILE, "r", encoding="utf-8") as f:
|
||
return json.load(f)
|
||
return {}
|
||
|
||
+
|
||
def save_users(users: dict):
|
||
- with open(USERS_FILE, 'w', encoding='utf-8') as f:
|
||
+ with open(USERS_FILE, "w", encoding="utf-8") as f:
|
||
json.dump(users, f, indent=2, ensure_ascii=False)
|
||
+
|
||
|
||
def load_server_config(server_name: str) -> dict:
|
||
config_path = SERVERS_DIR / server_name / "panel_config.json"
|
||
if config_path.exists():
|
||
- with open(config_path, 'r', encoding='utf-8') as f:
|
||
+ with open(config_path, "r", encoding="utf-8") as f:
|
||
return json.load(f)
|
||
return {
|
||
"name": server_name,
|
||
"displayName": server_name,
|
||
- "startCommand": "java -Xmx2G -Xms1G -jar server.jar nogui"
|
||
- }
|
||
+ "startCommand": "java -Xmx2G -Xms1G -jar server.jar nogui",
|
||
+ }
|
||
+
|
||
|
||
def save_server_config(server_name: str, config: dict):
|
||
config_path = SERVERS_DIR / server_name / "panel_config.json"
|
||
- with open(config_path, 'w', encoding='utf-8') as f:
|
||
+ with open(config_path, "w", encoding="utf-8") as f:
|
||
json.dump(config, f, indent=2, ensure_ascii=False)
|
||
+
|
||
|
||
# Функции для работы с тикетами
|
||
def load_tickets() -> dict:
|
||
if TICKETS_FILE.exists():
|
||
- with open(TICKETS_FILE, 'r', encoding='utf-8') as f:
|
||
+ with open(TICKETS_FILE, "r", encoding="utf-8") as f:
|
||
return json.load(f)
|
||
return {}
|
||
|
||
+
|
||
def save_tickets(tickets: dict):
|
||
- with open(TICKETS_FILE, 'w', encoding='utf-8') as f:
|
||
+ with open(TICKETS_FILE, "w", encoding="utf-8") as f:
|
||
json.dump(tickets, f, indent=2, ensure_ascii=False)
|
||
|
||
+
|
||
init_users()
|
||
+
|
||
|
||
# Функции аутентификации
|
||
def verify_password(plain_password, hashed_password):
|
||
return pwd_context.verify(plain_password, hashed_password)
|
||
|
||
+
|
||
def get_password_hash(password):
|
||
return pwd_context.hash(password)
|
||
+
|
||
|
||
def create_access_token(data: dict):
|
||
to_encode = data.copy()
|
||
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||
to_encode.update({"exp": expire})
|
||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||
return encoded_jwt
|
||
would reformat /drone/src/backend/main.py
|
||
|
||
+
|
||
def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
|
||
if not credentials:
|
||
raise HTTPException(status_code=401, detail="Требуется авторизация")
|
||
-
|
||
+
|
||
token = credentials.credentials
|
||
try:
|
||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||
username: str = payload.get("sub")
|
||
if username is None:
|
||
raise HTTPException(status_code=401, detail="Неверный токен")
|
||
-
|
||
+
|
||
users = load_users()
|
||
if username not in users:
|
||
raise HTTPException(status_code=401, detail="Пользователь не найден")
|
||
-
|
||
+
|
||
user = users[username]
|
||
-
|
||
+
|
||
# Проверка на бан
|
||
if user.get("role") == "banned":
|
||
raise HTTPException(status_code=403, detail="Ваш аккаунт заблокирован")
|
||
-
|
||
+
|
||
return user
|
||
except JWTError:
|
||
raise HTTPException(status_code=401, detail="Неверный токен")
|
||
+
|
||
|
||
def check_server_access(user: dict, server_name: str):
|
||
# Владелец имеет доступ ко всем серверам
|
||
if user["role"] == "owner":
|
||
return True
|
||
@@ -170,105 +192,117 @@
|
||
# Проверяем права на серверы
|
||
if not user.get("permissions", {}).get("servers", True):
|
||
return False
|
||
return server_name in user.get("servers", [])
|
||
|
||
+
|
||
# API для аутентификации
|
||
+
|
||
|
||
# OpenID Connect endpoints
|
||
@app.get("/api/auth/oidc/providers")
|
||
async def get_oidc_providers():
|
||
"""Получить список доступных OpenID Connect провайдеров"""
|
||
providers = {}
|
||
for provider_id, config in get_enabled_providers().items():
|
||
providers[provider_id] = {
|
||
"name": config["name"],
|
||
"icon": config["icon"],
|
||
- "color": config["color"]
|
||
+ "color": config["color"],
|
||
}
|
||
return providers
|
||
+
|
||
|
||
@app.get("/api/auth/oidc/{provider}/login")
|
||
async def oidc_login(provider: str, request: Request):
|
||
"""Начать процесс аутентификации через OpenID Connect"""
|
||
if provider not in get_enabled_providers():
|
||
raise HTTPException(404, f"Провайдер {provider} не найден или не настроен")
|
||
-
|
||
+
|
||
try:
|
||
- redirect_uri = get_redirect_uri(provider, os.getenv("BASE_URL", "http://localhost:8000"))
|
||
- return await oauth.create_client(provider).authorize_redirect(request, redirect_uri)
|
||
+ redirect_uri = get_redirect_uri(
|
||
+ provider, os.getenv("BASE_URL", "http://localhost:8000")
|
||
+ )
|
||
+ return await oauth.create_client(provider).authorize_redirect(
|
||
+ request, redirect_uri
|
||
+ )
|
||
except Exception as e:
|
||
raise HTTPException(500, f"Ошибка инициализации OAuth: {str(e)}")
|
||
+
|
||
|
||
@app.get("/api/auth/oidc/{provider}/callback")
|
||
async def oidc_callback(provider: str, request: Request):
|
||
"""Обработка callback от OpenID Connect провайдера"""
|
||
if provider not in get_enabled_providers():
|
||
raise HTTPException(404, f"Провайдер {provider} не найден или не настроен")
|
||
-
|
||
+
|
||
try:
|
||
client = oauth.create_client(provider)
|
||
-
|
||
+
|
||
# Получаем токен от провайдера
|
||
token = await client.authorize_access_token(request)
|
||
-
|
||
+
|
||
# Получаем данные пользователя
|
||
user_data = token.get("userinfo")
|
||
if not user_data:
|
||
# Если userinfo нет в токене, парсим id_token
|
||
user_data = token.get("id_token")
|
||
if not user_data:
|
||
raise HTTPException(400, "Не удалось получить данные пользователя")
|
||
-
|
||
+
|
||
# Создаём или обновляем пользователя
|
||
username = create_or_update_oidc_user(user_data, provider)
|
||
-
|
||
+
|
||
# Создаём JWT токен для нашей системы
|
||
users = load_users()
|
||
user = users[username]
|
||
access_token = create_access_token({"sub": username, "role": user["role"]})
|
||
-
|
||
+
|
||
# Перенаправляем на фронтенд с токеном
|
||
frontend_url = os.getenv("FRONTEND_URL", "http://localhost:3000")
|
||
- return RedirectResponse(f"{frontend_url}/?token={access_token}&username={username}")
|
||
-
|
||
+ return RedirectResponse(
|
||
+ f"{frontend_url}/?token={access_token}&username={username}"
|
||
+ )
|
||
+
|
||
except AuthlibBaseError as e:
|
||
print(f"OAuth ошибка для {provider}: {str(e)}")
|
||
raise HTTPException(400, f"OAuth ошибка: {str(e)}")
|
||
except Exception as e:
|
||
print(f"Ошибка аутентификации для {provider}: {str(e)}")
|
||
raise HTTPException(500, f"Ошибка аутентификации: {str(e)}")
|
||
|
||
+
|
||
def create_or_update_oidc_user(user_data: dict, provider: str) -> str:
|
||
"""Создать или обновить пользователя из OpenID Connect данных"""
|
||
users = load_users()
|
||
-
|
||
+
|
||
# Генерируем уникальное имя пользователя
|
||
email = user_data.get("email", "")
|
||
name = user_data.get("name", "")
|
||
sub = user_data.get("sub", "")
|
||
-
|
||
+
|
||
# Пытаемся использовать email как username
|
||
if email:
|
||
base_username = email.split("@")[0]
|
||
elif name:
|
||
base_username = name.replace(" ", "_").lower()
|
||
else:
|
||
base_username = f"{provider}_user"
|
||
-
|
||
+
|
||
# Убираем недопустимые символы
|
||
import re
|
||
- base_username = re.sub(r'[^a-zA-Z0-9_-]', '', base_username)
|
||
-
|
||
+
|
||
+ base_username = re.sub(r"[^a-zA-Z0-9_-]", "", base_username)
|
||
+
|
||
# Ищем существующего пользователя по OIDC ID
|
||
oidc_id = f"{provider}:{sub}"
|
||
existing_user = None
|
||
for username, user_info in users.items():
|
||
if user_info.get("oidc_id") == oidc_id:
|
||
existing_user = username
|
||
break
|
||
-
|
||
+
|
||
if existing_user:
|
||
# Обновляем существующего пользователя
|
||
users[existing_user]["email"] = email
|
||
users[existing_user]["name"] = name
|
||
users[existing_user]["picture"] = user_data.get("picture")
|
||
@@ -279,112 +313,115 @@
|
||
username = base_username
|
||
counter = 1
|
||
while username in users:
|
||
username = f"{base_username}_{counter}"
|
||
counter += 1
|
||
-
|
||
+
|
||
users[username] = {
|
||
"username": username,
|
||
"password": "", # Пустой пароль для OIDC пользователей
|
||
"role": "user",
|
||
"servers": [],
|
||
"oidc_id": oidc_id,
|
||
"email": email,
|
||
"name": name,
|
||
"picture": user_data.get("picture"),
|
||
"provider": provider,
|
||
- "created_at": datetime.utcnow().isoformat()
|
||
+ "created_at": datetime.utcnow().isoformat(),
|
||
}
|
||
save_users(users)
|
||
return username
|
||
+
|
||
|
||
@app.post("/api/auth/register")
|
||
async def register(data: dict):
|
||
users = load_users()
|
||
username = data.get("username", "").strip()
|
||
password = data.get("password", "").strip()
|
||
-
|
||
+
|
||
if not username or not password:
|
||
raise HTTPException(400, "Имя пользователя и пароль обязательны")
|
||
-
|
||
+
|
||
if username in users:
|
||
raise HTTPException(400, "Пользователь уже существует")
|
||
-
|
||
+
|
||
# Первый пользователь становится владельцем
|
||
role = "owner" if len(users) == 0 else "user"
|
||
-
|
||
+
|
||
users[username] = {
|
||
"username": username,
|
||
"password": get_password_hash(password),
|
||
"role": role,
|
||
"servers": [],
|
||
- "permissions": {
|
||
- "servers": True,
|
||
- "tickets": True,
|
||
- "users": True if role == "owner" else False,
|
||
- "files": True
|
||
- } if role == "owner" else {
|
||
- "servers": True,
|
||
- "tickets": True,
|
||
- "users": False,
|
||
- "files": True
|
||
- }
|
||
- }
|
||
-
|
||
+ "permissions": (
|
||
+ {
|
||
+ "servers": True,
|
||
+ "tickets": True,
|
||
+ "users": True if role == "owner" else False,
|
||
+ "files": True,
|
||
+ }
|
||
+ if role == "owner"
|
||
+ else {"servers": True, "tickets": True, "users": False, "files": True}
|
||
+ ),
|
||
+ }
|
||
+
|
||
save_users(users)
|
||
-
|
||
+
|
||
access_token = create_access_token(data={"sub": username})
|
||
return {
|
||
"access_token": access_token,
|
||
"token_type": "bearer",
|
||
"username": username,
|
||
- "role": role
|
||
- }
|
||
+ "role": role,
|
||
+ }
|
||
+
|
||
|
||
@app.post("/api/auth/login")
|
||
async def login(data: dict):
|
||
users = load_users()
|
||
username = data.get("username", "").strip()
|
||
password = data.get("password", "").strip()
|
||
-
|
||
+
|
||
if username not in users:
|
||
raise HTTPException(401, "Неверное имя пользователя или пароль")
|
||
-
|
||
+
|
||
user = users[username]
|
||
if not verify_password(password, user["password"]):
|
||
raise HTTPException(401, "Неверное имя пользователя или пароль")
|
||
-
|
||
+
|
||
access_token = create_access_token(data={"sub": username})
|
||
return {
|
||
"access_token": access_token,
|
||
"token_type": "bearer",
|
||
"username": username,
|
||
- "role": user["role"]
|
||
- }
|
||
+ "role": user["role"],
|
||
+ }
|
||
+
|
||
|
||
@app.get("/api/auth/me")
|
||
async def get_me(user: dict = Depends(get_current_user)):
|
||
users = load_users()
|
||
user_data = users.get(user["username"], {})
|
||
-
|
||
+
|
||
# Если у пользователя нет прав, создаем дефолтные
|
||
if "permissions" not in user_data:
|
||
user_data["permissions"] = {
|
||
"servers": True,
|
||
"tickets": True,
|
||
"users": user_data["role"] in ["owner", "admin"],
|
||
- "files": True
|
||
+ "files": True,
|
||
}
|
||
users[user["username"]] = user_data
|
||
save_users(users)
|
||
-
|
||
+
|
||
return {
|
||
"username": user["username"],
|
||
"role": user["role"],
|
||
"servers": user.get("servers", []),
|
||
- "permissions": user_data.get("permissions", {})
|
||
- }
|
||
+ "permissions": user_data.get("permissions", {}),
|
||
+ }
|
||
+
|
||
|
||
# API для управления пользователями
|
||
@app.get("/api/users")
|
||
async def get_users(user: dict = Depends(get_current_user)):
|
||
# Владелец, админы и тех. поддержка видят всех пользователей
|
||
@@ -392,191 +429,207 @@
|
||
return [
|
||
{
|
||
"username": u["username"],
|
||
"role": u["role"],
|
||
"servers": u.get("servers", []),
|
||
- "permissions": u.get("permissions", {
|
||
- "servers": True,
|
||
- "tickets": True,
|
||
- "users": u["role"] in ["owner", "admin"],
|
||
- "files": True
|
||
- })
|
||
+ "permissions": u.get(
|
||
+ "permissions",
|
||
+ {
|
||
+ "servers": True,
|
||
+ "tickets": True,
|
||
+ "users": u["role"] in ["owner", "admin"],
|
||
+ "files": True,
|
||
+ },
|
||
+ ),
|
||
}
|
||
for u in users.values()
|
||
]
|
||
|
||
+
|
||
@app.put("/api/users/{username}/servers")
|
||
-async def update_user_servers(username: str, data: dict, user: dict = Depends(get_current_user)):
|
||
+async def update_user_servers(
|
||
+ username: str, data: dict, user: dict = Depends(get_current_user)
|
||
+):
|
||
users = load_users()
|
||
if username not in users:
|
||
raise HTTPException(404, "Пользователь не найден")
|
||
-
|
||
+
|
||
# Админы могут управлять доступом к любым серверам
|
||
if user["role"] == "admin":
|
||
users[username]["servers"] = data.get("servers", [])
|
||
save_users(users)
|
||
return {"message": "Доступ обновлен"}
|
||
-
|
||
+
|
||
# Обычные пользователи могут управлять доступом только к своим серверам
|
||
requested_servers = data.get("servers", [])
|
||
current_servers = users[username].get("servers", [])
|
||
-
|
||
+
|
||
# Проверяем, что пользователь пытается изменить доступ только к своим серверам
|
||
for server_name in requested_servers:
|
||
if server_name not in current_servers:
|
||
# Проверяем, является ли текущий пользователь владельцем этого сервера
|
||
config = load_server_config(server_name)
|
||
if config.get("owner") != user["username"]:
|
||
- raise HTTPException(403, f"Вы не можете выдать доступ к серверу {server_name}")
|
||
-
|
||
+ raise HTTPException(
|
||
+ 403, f"Вы не можете выдать доступ к серверу {server_name}"
|
||
+ )
|
||
+
|
||
users[username]["servers"] = requested_servers
|
||
save_users(users)
|
||
return {"message": "Доступ обновлен"}
|
||
+
|
||
|
||
@app.delete("/api/users/{username}")
|
||
async def delete_user(username: str, user: dict = Depends(get_current_user)):
|
||
# Только владелец может удалять пользователей
|
||
if user["role"] != "owner":
|
||
raise HTTPException(403, "Только владелец может удалять пользователей")
|
||
-
|
||
+
|
||
if username == user["username"]:
|
||
raise HTTPException(400, "Нельзя удалить самого себя")
|
||
-
|
||
+
|
||
users = load_users()
|
||
if username not in users:
|
||
raise HTTPException(404, "Пользователь не найден")
|
||
-
|
||
+
|
||
# Проверяем, что не удаляем последнего владельца
|
||
if users[username]["role"] == "owner":
|
||
owners_count = sum(1 for u in users.values() if u.get("role") == "owner")
|
||
if owners_count <= 1:
|
||
raise HTTPException(400, "Нельзя удалить последнего владельца")
|
||
-
|
||
+
|
||
del users[username]
|
||
save_users(users)
|
||
-
|
||
+
|
||
return {"message": "Пользователь удален"}
|
||
|
||
+
|
||
@app.put("/api/users/{username}/role")
|
||
-async def update_user_role(username: str, data: dict, user: dict = Depends(get_current_user)):
|
||
+async def update_user_role(
|
||
+ username: str, data: dict, user: dict = Depends(get_current_user)
|
||
+):
|
||
# Только владелец может изменять роли
|
||
if user["role"] != "owner":
|
||
raise HTTPException(403, "Только владелец может изменять роли")
|
||
-
|
||
+
|
||
if username == user["username"]:
|
||
raise HTTPException(400, "Нельзя изменить свою роль")
|
||
-
|
||
+
|
||
users = load_users()
|
||
if username not in users:
|
||
raise HTTPException(404, "Пользователь не найден")
|
||
-
|
||
+
|
||
new_role = data.get("role")
|
||
if new_role not in ["admin", "user", "support", "banned"]:
|
||
raise HTTPException(400, "Неверная роль")
|
||
-
|
||
+
|
||
# Нельзя назначить роль owner
|
||
if new_role == "owner":
|
||
raise HTTPException(400, "Нельзя назначить роль владельца")
|
||
-
|
||
+
|
||
users[username]["role"] = new_role
|
||
save_users(users)
|
||
-
|
||
+
|
||
return {"message": "Роль обновлена"}
|
||
+
|
||
|
||
@app.get("/api/users/{username}/permissions")
|
||
async def get_user_permissions(username: str, user: dict = Depends(get_current_user)):
|
||
"""Получить права пользователя"""
|
||
# Только владелец и админы могут просматривать права
|
||
if user["role"] not in ["owner", "admin"]:
|
||
raise HTTPException(403, "Недостаточно прав")
|
||
-
|
||
+
|
||
users = load_users()
|
||
if username not in users:
|
||
raise HTTPException(404, "Пользователь не найден")
|
||
-
|
||
+
|
||
target_user = users[username]
|
||
-
|
||
+
|
||
# Если у пользователя нет прав, создаем дефолтные
|
||
if "permissions" not in target_user:
|
||
target_user["permissions"] = {
|
||
"servers": True,
|
||
"tickets": True,
|
||
"users": target_user["role"] in ["owner", "admin"],
|
||
- "files": True
|
||
+ "files": True,
|
||
}
|
||
users[username] = target_user
|
||
save_users(users)
|
||
-
|
||
+
|
||
return {
|
||
"username": username,
|
||
"role": target_user["role"],
|
||
- "permissions": target_user["permissions"]
|
||
- }
|
||
+ "permissions": target_user["permissions"],
|
||
+ }
|
||
+
|
||
|
||
@app.put("/api/users/{username}/permissions")
|
||
-async def update_user_permissions(username: str, data: dict, user: dict = Depends(get_current_user)):
|
||
+async def update_user_permissions(
|
||
+ username: str, data: dict, user: dict = Depends(get_current_user)
|
||
+):
|
||
"""Обновить права пользователя (только для владельца)"""
|
||
if user["role"] != "owner":
|
||
raise HTTPException(403, "Только владелец может изменять права")
|
||
-
|
||
+
|
||
if username == user["username"]:
|
||
raise HTTPException(400, "Нельзя изменить свои права")
|
||
-
|
||
+
|
||
users = load_users()
|
||
if username not in users:
|
||
raise HTTPException(404, "Пользователь не найден")
|
||
-
|
||
+
|
||
target_user = users[username]
|
||
-
|
||
+
|
||
# Нельзя изменять права владельца
|
||
if target_user["role"] == "owner":
|
||
raise HTTPException(400, "Нельзя изменять права владельца")
|
||
-
|
||
+
|
||
permissions = data.get("permissions", {})
|
||
-
|
||
+
|
||
# Валидация прав
|
||
valid_permissions = ["servers", "tickets", "users", "files"]
|
||
for perm in permissions:
|
||
if perm not in valid_permissions:
|
||
raise HTTPException(400, f"Неверное право: {perm}")
|
||
-
|
||
+
|
||
# Обновляем права
|
||
if "permissions" not in target_user:
|
||
target_user["permissions"] = {
|
||
"servers": True,
|
||
"tickets": True,
|
||
"users": False,
|
||
- "files": True
|
||
+ "files": True,
|
||
}
|
||
-
|
||
+
|
||
target_user["permissions"].update(permissions)
|
||
users[username] = target_user
|
||
save_users(users)
|
||
-
|
||
- return {
|
||
- "message": "Права обновлены",
|
||
- "permissions": target_user["permissions"]
|
||
- }
|
||
+
|
||
+ return {"message": "Права обновлены", "permissions": target_user["permissions"]}
|
||
+
|
||
|
||
@app.post("/api/users/{username}/revoke-access")
|
||
-async def revoke_user_access(username: str, data: dict, user: dict = Depends(get_current_user)):
|
||
+async def revoke_user_access(
|
||
+ username: str, data: dict, user: dict = Depends(get_current_user)
|
||
+):
|
||
"""Забрать доступ к определенным ресурсам (только для владельца)"""
|
||
if user["role"] != "owner":
|
||
raise HTTPException(403, "Только владелец может забирать доступ")
|
||
-
|
||
+
|
||
users = load_users()
|
||
if username not in users:
|
||
raise HTTPException(404, "Пользователь не найден")
|
||
-
|
||
+
|
||
target_user = users[username]
|
||
-
|
||
+
|
||
# Нельзя забирать доступ у владельца
|
||
if target_user["role"] == "owner":
|
||
raise HTTPException(400, "Нельзя забирать доступ у владельца")
|
||
-
|
||
+
|
||
resource_type = data.get("type") # "servers", "tickets", "all"
|
||
-
|
||
+
|
||
if resource_type == "servers":
|
||
# Забираем доступ ко всем серверам
|
||
target_user["servers"] = []
|
||
if "permissions" in target_user:
|
||
target_user["permissions"]["servers"] = False
|
||
@@ -594,44 +647,47 @@
|
||
if "permissions" in target_user:
|
||
target_user["permissions"] = {
|
||
"servers": False,
|
||
"tickets": False,
|
||
"users": False,
|
||
- "files": False
|
||
+ "files": False,
|
||
}
|
||
else:
|
||
raise HTTPException(400, "Неверный тип ресурса")
|
||
-
|
||
+
|
||
users[username] = target_user
|
||
save_users(users)
|
||
-
|
||
+
|
||
return {
|
||
"message": f"Доступ к {resource_type} забран",
|
||
- "permissions": target_user.get("permissions", {})
|
||
- }
|
||
+ "permissions": target_user.get("permissions", {}),
|
||
+ }
|
||
+
|
||
|
||
@app.post("/api/users/{username}/grant-access")
|
||
-async def grant_user_access(username: str, data: dict, user: dict = Depends(get_current_user)):
|
||
+async def grant_user_access(
|
||
+ username: str, data: dict, user: dict = Depends(get_current_user)
|
||
+):
|
||
"""Выдать доступ к определенным ресурсам (только для владельца)"""
|
||
if user["role"] != "owner":
|
||
raise HTTPException(403, "Только владелец может выдавать доступ")
|
||
-
|
||
+
|
||
users = load_users()
|
||
if username not in users:
|
||
raise HTTPException(404, "Пользователь не найден")
|
||
-
|
||
+
|
||
target_user = users[username]
|
||
resource_type = data.get("type") # "servers", "tickets", "files"
|
||
-
|
||
+
|
||
if "permissions" not in target_user:
|
||
target_user["permissions"] = {
|
||
"servers": False,
|
||
"tickets": False,
|
||
"users": False,
|
||
- "files": False
|
||
+ "files": False,
|
||
}
|
||
-
|
||
+
|
||
if resource_type == "servers":
|
||
target_user["permissions"]["servers"] = True
|
||
elif resource_type == "tickets":
|
||
target_user["permissions"]["tickets"] = True
|
||
elif resource_type == "files":
|
||
@@ -639,416 +695,462 @@
|
||
elif resource_type == "all":
|
||
target_user["permissions"] = {
|
||
"servers": True,
|
||
"tickets": True,
|
||
"users": target_user["role"] in ["admin"],
|
||
- "files": True
|
||
+ "files": True,
|
||
}
|
||
else:
|
||
raise HTTPException(400, "Неверный тип ресурса")
|
||
-
|
||
+
|
||
users[username] = target_user
|
||
save_users(users)
|
||
-
|
||
+
|
||
return {
|
||
"message": f"Доступ к {resource_type} выдан",
|
||
- "permissions": target_user["permissions"]
|
||
- }
|
||
+ "permissions": target_user["permissions"],
|
||
+ }
|
||
+
|
||
|
||
# API для личного кабинета
|
||
@app.put("/api/profile/username")
|
||
async def update_username(data: dict, user: dict = Depends(get_current_user)):
|
||
"""Изменить имя пользователя"""
|
||
new_username = data.get("new_username", "").strip()
|
||
password = data.get("password", "")
|
||
-
|
||
+
|
||
if not new_username:
|
||
raise HTTPException(400, "Имя пользователя не может быть пустым")
|
||
-
|
||
+
|
||
if len(new_username) < 3:
|
||
raise HTTPException(400, "Имя пользователя должно быть не менее 3 символов")
|
||
-
|
||
+
|
||
users = load_users()
|
||
-
|
||
+
|
||
# Проверяем пароль
|
||
if not verify_password(password, users[user["username"]]["password"]):
|
||
raise HTTPException(400, "Неверный пароль")
|
||
-
|
||
+
|
||
# Проверяем, не занято ли новое имя
|
||
if new_username in users and new_username != user["username"]:
|
||
raise HTTPException(400, "Это имя пользователя уже занято")
|
||
-
|
||
+
|
||
# Сохраняем данные пользователя
|
||
old_username = user["username"]
|
||
user_data = users[old_username]
|
||
-
|
||
+
|
||
# Удаляем старую запись и создаём новую
|
||
del users[old_username]
|
||
user_data["username"] = new_username
|
||
users[new_username] = user_data
|
||
-
|
||
+
|
||
# Обновляем владельцев серверов
|
||
for server_dir in SERVERS_DIR.iterdir():
|
||
if server_dir.is_dir():
|
||
config = load_server_config(server_dir.name)
|
||
if config.get("owner") == old_username:
|
||
config["owner"] = new_username
|
||
save_server_config(server_dir.name, config)
|
||
-
|
||
+
|
||
# Обновляем доступы к серверам у других пользователей
|
||
for username, user_info in users.items():
|
||
if "servers" in user_info and old_username in user_info.get("servers", []):
|
||
- user_info["servers"] = [new_username if s == old_username else s for s in user_info["servers"]]
|
||
-
|
||
+ user_info["servers"] = [
|
||
+ new_username if s == old_username else s for s in user_info["servers"]
|
||
+ ]
|
||
+
|
||
save_users(users)
|
||
-
|
||
+
|
||
# Создаём новый токен
|
||
new_token = create_access_token({"sub": new_username, "role": user_data["role"]})
|
||
-
|
||
+
|
||
return {
|
||
"message": "Имя пользователя изменено",
|
||
"access_token": new_token,
|
||
- "username": new_username
|
||
- }
|
||
+ "username": new_username,
|
||
+ }
|
||
+
|
||
|
||
@app.put("/api/profile/password")
|
||
async def update_password(data: dict, user: dict = Depends(get_current_user)):
|
||
"""Изменить пароль"""
|
||
old_password = data.get("old_password", "")
|
||
new_password = data.get("new_password", "")
|
||
-
|
||
+
|
||
if not old_password or not new_password:
|
||
raise HTTPException(400, "Заполните все поля")
|
||
-
|
||
+
|
||
if len(new_password) < 6:
|
||
raise HTTPException(400, "Новый пароль должен быть не менее 6 символов")
|
||
-
|
||
+
|
||
users = load_users()
|
||
-
|
||
+
|
||
# Проверяем старый пароль
|
||
if not verify_password(old_password, users[user["username"]]["password"]):
|
||
raise HTTPException(400, "Неверный старый пароль")
|
||
-
|
||
+
|
||
# Устанавливаем новый пароль
|
||
users[user["username"]]["password"] = get_password_hash(new_password)
|
||
save_users(users)
|
||
-
|
||
+
|
||
return {"message": "Пароль изменён"}
|
||
+
|
||
|
||
@app.get("/api/profile/stats")
|
||
async def get_profile_stats(user: dict = Depends(get_current_user)):
|
||
"""Получить статистику профиля"""
|
||
users = load_users()
|
||
user_data = users.get(user["username"], {})
|
||
-
|
||
+
|
||
# Подсчитываем серверы пользователя
|
||
owned_servers = []
|
||
accessible_servers = []
|
||
-
|
||
+
|
||
for server_dir in SERVERS_DIR.iterdir():
|
||
if server_dir.is_dir():
|
||
config = load_server_config(server_dir.name)
|
||
if config.get("owner") == user["username"]:
|
||
- owned_servers.append({
|
||
- "name": server_dir.name,
|
||
- "displayName": config.get("displayName", server_dir.name)
|
||
- })
|
||
- elif user["username"] in user_data.get("servers", []) or user["role"] == "admin":
|
||
- accessible_servers.append({
|
||
- "name": server_dir.name,
|
||
- "displayName": config.get("displayName", server_dir.name)
|
||
- })
|
||
-
|
||
+ owned_servers.append(
|
||
+ {
|
||
+ "name": server_dir.name,
|
||
+ "displayName": config.get("displayName", server_dir.name),
|
||
+ }
|
||
+ )
|
||
+ elif (
|
||
+ user["username"] in user_data.get("servers", [])
|
||
+ or user["role"] == "admin"
|
||
+ ):
|
||
+ accessible_servers.append(
|
||
+ {
|
||
+ "name": server_dir.name,
|
||
+ "displayName": config.get("displayName", server_dir.name),
|
||
+ }
|
||
+ )
|
||
+
|
||
# Подсчитываем тикеты
|
||
tickets = load_tickets()
|
||
user_tickets = [t for t in tickets.values() if t["author"] == user["username"]]
|
||
-
|
||
+
|
||
tickets_stats = {
|
||
"total": len(user_tickets),
|
||
"pending": len([t for t in user_tickets if t["status"] == "pending"]),
|
||
"in_progress": len([t for t in user_tickets if t["status"] == "in_progress"]),
|
||
- "closed": len([t for t in user_tickets if t["status"] == "closed"])
|
||
- }
|
||
-
|
||
+ "closed": len([t for t in user_tickets if t["status"] == "closed"]),
|
||
+ }
|
||
+
|
||
return {
|
||
"username": user["username"],
|
||
"role": user["role"],
|
||
"owned_servers": owned_servers,
|
||
"accessible_servers": accessible_servers,
|
||
"tickets": tickets_stats,
|
||
- "total_servers": len(owned_servers) + len(accessible_servers)
|
||
- }
|
||
+ "total_servers": len(owned_servers) + len(accessible_servers),
|
||
+ }
|
||
+
|
||
|
||
@app.get("/api/profile/stats/{username}")
|
||
async def get_user_profile_stats(username: str, user: dict = Depends(get_current_user)):
|
||
"""Получить статистику профиля другого пользователя (только для админов и тех. поддержки)"""
|
||
# Проверка прав доступа
|
||
if user["role"] not in ["admin", "support"]:
|
||
- raise HTTPException(403, "Недостаточно прав для просмотра профилей других пользователей")
|
||
-
|
||
+ raise HTTPException(
|
||
+ 403, "Недостаточно прав для просмотра профилей других пользователей"
|
||
+ )
|
||
+
|
||
users = load_users()
|
||
-
|
||
+
|
||
# Проверка существования пользователя
|
||
if username not in users:
|
||
raise HTTPException(404, "Пользователь не найден")
|
||
-
|
||
+
|
||
target_user = users[username]
|
||
-
|
||
+
|
||
# Подсчитываем серверы пользователя
|
||
owned_servers = []
|
||
accessible_servers = []
|
||
-
|
||
+
|
||
for server_dir in SERVERS_DIR.iterdir():
|
||
if server_dir.is_dir():
|
||
config = load_server_config(server_dir.name)
|
||
if config.get("owner") == username:
|
||
- owned_servers.append({
|
||
- "name": server_dir.name,
|
||
- "displayName": config.get("displayName", server_dir.name)
|
||
- })
|
||
- elif username in target_user.get("servers", []) or target_user["role"] == "admin":
|
||
- accessible_servers.append({
|
||
- "name": server_dir.name,
|
||
- "displayName": config.get("displayName", server_dir.name)
|
||
- })
|
||
-
|
||
+ owned_servers.append(
|
||
+ {
|
||
+ "name": server_dir.name,
|
||
+ "displayName": config.get("displayName", server_dir.name),
|
||
+ }
|
||
+ )
|
||
+ elif (
|
||
+ username in target_user.get("servers", [])
|
||
+ or target_user["role"] == "admin"
|
||
+ ):
|
||
+ accessible_servers.append(
|
||
+ {
|
||
+ "name": server_dir.name,
|
||
+ "displayName": config.get("displayName", server_dir.name),
|
||
+ }
|
||
+ )
|
||
+
|
||
# Подсчитываем тикеты
|
||
tickets = load_tickets()
|
||
user_tickets = [t for t in tickets.values() if t["author"] == username]
|
||
-
|
||
+
|
||
tickets_stats = {
|
||
"total": len(user_tickets),
|
||
"pending": len([t for t in user_tickets if t["status"] == "pending"]),
|
||
"in_progress": len([t for t in user_tickets if t["status"] == "in_progress"]),
|
||
- "closed": len([t for t in user_tickets if t["status"] == "closed"])
|
||
- }
|
||
-
|
||
+ "closed": len([t for t in user_tickets if t["status"] == "closed"]),
|
||
+ }
|
||
+
|
||
return {
|
||
"username": username,
|
||
"role": target_user["role"],
|
||
"owned_servers": owned_servers,
|
||
"accessible_servers": accessible_servers,
|
||
"tickets": tickets_stats,
|
||
"total_servers": len(owned_servers) + len(accessible_servers),
|
||
- "is_viewing_other": True # Флаг что это чужой профиль
|
||
- }
|
||
+ "is_viewing_other": True, # Флаг что это чужой профиль
|
||
+ }
|
||
+
|
||
|
||
# API для серверов
|
||
@app.get("/api/servers")
|
||
async def get_servers(user: dict = Depends(get_current_user)):
|
||
servers = []
|
||
try:
|
||
# Владелец и администратор видят все серверы
|
||
- can_view_all = user.get("role") in ["owner", "admin"] or user.get("permissions", {}).get("view_all_resources", False)
|
||
-
|
||
+ can_view_all = user.get("role") in ["owner", "admin"] or user.get(
|
||
+ "permissions", {}
|
||
+ ).get("view_all_resources", False)
|
||
+
|
||
for server_dir in SERVERS_DIR.iterdir():
|
||
if server_dir.is_dir():
|
||
# Проверка доступа: владелец/админ видят всё, остальные только свои
|
||
if not can_view_all and server_dir.name not in user.get("servers", []):
|
||
continue
|
||
-
|
||
+
|
||
config = load_server_config(server_dir.name)
|
||
-
|
||
+
|
||
is_running = False
|
||
if server_dir.name in server_processes:
|
||
process = server_processes[server_dir.name]
|
||
if process.poll() is None:
|
||
is_running = True
|
||
else:
|
||
del server_processes[server_dir.name]
|
||
-
|
||
- servers.append({
|
||
- "name": server_dir.name,
|
||
- "displayName": config.get("displayName", server_dir.name),
|
||
- "status": "running" if is_running else "stopped"
|
||
- })
|
||
- print(f"Найдено серверов для {user['username']} ({user.get('role', 'user')}): {len(servers)}")
|
||
+
|
||
+ servers.append(
|
||
+ {
|
||
+ "name": server_dir.name,
|
||
+ "displayName": config.get("displayName", server_dir.name),
|
||
+ "status": "running" if is_running else "stopped",
|
||
+ }
|
||
+ )
|
||
+ print(
|
||
+ f"Найдено серверов для {user['username']} ({user.get('role', 'user')}): {len(servers)}"
|
||
+ )
|
||
except Exception as e:
|
||
print(f"Ошибка загрузки серверов: {e}")
|
||
return servers
|
||
+
|
||
|
||
@app.post("/api/servers/create")
|
||
async def create_server(data: dict, user: dict = Depends(get_current_user)):
|
||
server_name = data.get("name", "").strip()
|
||
if not server_name or not server_name.replace("_", "").replace("-", "").isalnum():
|
||
raise HTTPException(400, "Недопустимое имя сервера")
|
||
-
|
||
+
|
||
server_path = SERVERS_DIR / server_name
|
||
if server_path.exists():
|
||
raise HTTPException(400, "Сервер с таким именем уже существует")
|
||
-
|
||
+
|
||
server_path.mkdir(parents=True)
|
||
-
|
||
+
|
||
config = {
|
||
"name": server_name,
|
||
"displayName": data.get("displayName", server_name),
|
||
- "startCommand": data.get("startCommand", "java -Xmx2G -Xms1G -jar server.jar nogui"),
|
||
- "owner": user["username"] # Сохраняем владельца
|
||
+ "startCommand": data.get(
|
||
+ "startCommand", "java -Xmx2G -Xms1G -jar server.jar nogui"
|
||
+ ),
|
||
+ "owner": user["username"], # Сохраняем владельца
|
||
}
|
||
save_server_config(server_name, config)
|
||
-
|
||
+
|
||
# Если пользователь не админ, автоматически выдаем ему доступ
|
||
if user["role"] != "admin":
|
||
users = load_users()
|
||
if user["username"] in users:
|
||
if "servers" not in users[user["username"]]:
|
||
users[user["username"]]["servers"] = []
|
||
if server_name not in users[user["username"]]["servers"]:
|
||
users[user["username"]]["servers"].append(server_name)
|
||
save_users(users)
|
||
-
|
||
+
|
||
return {"message": "Сервер создан", "name": server_name}
|
||
+
|
||
|
||
@app.get("/api/servers/{server_name}/config")
|
||
async def get_server_config(server_name: str, user: dict = Depends(get_current_user)):
|
||
if not check_server_access(user, server_name):
|
||
raise HTTPException(403, "Нет доступа к этому серверу")
|
||
-
|
||
+
|
||
server_path = SERVERS_DIR / server_name
|
||
if not server_path.exists():
|
||
raise HTTPException(404, "Сервер не найден")
|
||
-
|
||
+
|
||
config = load_server_config(server_name)
|
||
print(f"Загружена конфигурация для {server_name}: {config}")
|
||
return config
|
||
|
||
+
|
||
@app.put("/api/servers/{server_name}/config")
|
||
-async def update_server_config(server_name: str, config: dict, user: dict = Depends(get_current_user)):
|
||
+async def update_server_config(
|
||
+ server_name: str, config: dict, user: dict = Depends(get_current_user)
|
||
+):
|
||
if not check_server_access(user, server_name):
|
||
raise HTTPException(403, "Нет доступа к этому серверу")
|
||
-
|
||
+
|
||
server_path = SERVERS_DIR / server_name
|
||
if not server_path.exists():
|
||
raise HTTPException(404, "Сервер не найден")
|
||
-
|
||
+
|
||
if server_name in server_processes:
|
||
raise HTTPException(400, "Остановите сервер перед изменением настроек")
|
||
-
|
||
+
|
||
save_server_config(server_name, config)
|
||
return {"message": "Настройки сохранены"}
|
||
+
|
||
|
||
@app.delete("/api/servers/{server_name}")
|
||
async def delete_server(server_name: str, user: dict = Depends(get_current_user)):
|
||
if user["role"] != "admin":
|
||
raise HTTPException(403, "Только администраторы могут удалять серверы")
|
||
-
|
||
+
|
||
server_path = SERVERS_DIR / server_name
|
||
if not server_path.exists():
|
||
raise HTTPException(404, "Сервер не найден")
|
||
-
|
||
+
|
||
if server_name in server_processes:
|
||
raise HTTPException(400, "Остановите сервер перед удалением")
|
||
-
|
||
+
|
||
shutil.rmtree(server_path)
|
||
return {"message": "Сервер удален"}
|
||
+
|
||
|
||
# Управление процессами серверов
|
||
async def read_server_output(server_name: str, process: subprocess.Popen):
|
||
try:
|
||
print(f"Начало чтения вывода для сервера {server_name}")
|
||
loop = asyncio.get_event_loop()
|
||
-
|
||
+
|
||
while True:
|
||
if process.poll() is not None:
|
||
- print(f"Процесс сервера {server_name} завершился с кодом {process.poll()}")
|
||
+ print(
|
||
+ f"Процесс сервера {server_name} завершился с кодом {process.poll()}"
|
||
+ )
|
||
break
|
||
-
|
||
+
|
||
try:
|
||
line = await loop.run_in_executor(None, process.stdout.readline)
|
||
if not line:
|
||
break
|
||
-
|
||
+
|
||
line = line.strip()
|
||
if line:
|
||
if server_name not in server_logs:
|
||
server_logs[server_name] = []
|
||
server_logs[server_name].append(line)
|
||
-
|
||
+
|
||
if len(server_logs[server_name]) > 1000:
|
||
server_logs[server_name].pop(0)
|
||
except Exception as e:
|
||
print(f"Ошибка чтения строки для {server_name}: {e}")
|
||
await asyncio.sleep(0.1)
|
||
-
|
||
+
|
||
except Exception as e:
|
||
print(f"Ошибка чтения вывода сервера {server_name}: {e}")
|
||
finally:
|
||
print(f"Чтение вывода для сервера {server_name} завершено")
|
||
if server_name in server_processes and process.poll() is not None:
|
||
del server_processes[server_name]
|
||
print(f"Сервер {server_name} удален из списка процессов")
|
||
|
||
+
|
||
@app.post("/api/servers/{server_name}/start")
|
||
async def start_server(server_name: str, user: dict = Depends(get_current_user)):
|
||
if not check_server_access(user, server_name):
|
||
raise HTTPException(403, "Нет доступа к этому серверу")
|
||
-
|
||
+
|
||
server_path = SERVERS_DIR / server_name
|
||
if not server_path.exists():
|
||
raise HTTPException(404, "Сервер не найден")
|
||
-
|
||
+
|
||
if server_name in server_processes:
|
||
raise HTTPException(400, "Сервер уже запущен")
|
||
-
|
||
+
|
||
config = load_server_config(server_name)
|
||
- start_command = config.get("startCommand", "java -Xmx2G -Xms1G -jar server.jar nogui")
|
||
-
|
||
+ start_command = config.get(
|
||
+ "startCommand", "java -Xmx2G -Xms1G -jar server.jar nogui"
|
||
+ )
|
||
+
|
||
cmd_parts = start_command.split()
|
||
-
|
||
+
|
||
try:
|
||
if IS_WINDOWS:
|
||
process = subprocess.Popen(
|
||
cmd_parts,
|
||
cwd=server_path,
|
||
stdin=subprocess.PIPE,
|
||
stdout=subprocess.PIPE,
|
||
stderr=subprocess.STDOUT,
|
||
text=True,
|
||
bufsize=1,
|
||
- creationflags=subprocess.CREATE_NO_WINDOW
|
||
+ creationflags=subprocess.CREATE_NO_WINDOW,
|
||
)
|
||
else:
|
||
process = subprocess.Popen(
|
||
cmd_parts,
|
||
cwd=server_path,
|
||
stdin=subprocess.PIPE,
|
||
stdout=subprocess.PIPE,
|
||
stderr=subprocess.STDOUT,
|
||
text=True,
|
||
- bufsize=1
|
||
+ bufsize=1,
|
||
)
|
||
-
|
||
+
|
||
server_processes[server_name] = process
|
||
server_logs[server_name] = []
|
||
-
|
||
+
|
||
asyncio.create_task(read_server_output(server_name, process))
|
||
-
|
||
+
|
||
print(f"Сервер {server_name} запущен с PID {process.pid}")
|
||
return {"message": "Сервер запущен", "pid": process.pid}
|
||
except Exception as e:
|
||
print(f"Ошибка запуска сервера {server_name}: {e}")
|
||
raise HTTPException(500, f"Ошибка запуска сервера: {str(e)}")
|
||
|
||
+
|
||
@app.post("/api/servers/{server_name}/stop")
|
||
async def stop_server(server_name: str, user: dict = Depends(get_current_user)):
|
||
if not check_server_access(user, server_name):
|
||
raise HTTPException(403, "Нет доступа к этому серверу")
|
||
-
|
||
+
|
||
if server_name not in server_processes:
|
||
raise HTTPException(400, "Сервер не запущен")
|
||
-
|
||
+
|
||
process = server_processes[server_name]
|
||
-
|
||
+
|
||
try:
|
||
if process.stdin and not process.stdin.closed:
|
||
process.stdin.write("stop\n")
|
||
process.stdin.flush()
|
||
-
|
||
+
|
||
try:
|
||
process.wait(timeout=30)
|
||
except subprocess.TimeoutExpired:
|
||
- print(f"Сервер {server_name} не остановился за 30 секунд, принудительное завершение")
|
||
+ print(
|
||
+ f"Сервер {server_name} не остановился за 30 секунд, принудительное завершение"
|
||
+ )
|
||
process.kill()
|
||
process.wait()
|
||
except Exception as e:
|
||
print(f"Ошибка при остановке сервера {server_name}: {e}")
|
||
try:
|
||
@@ -1058,27 +1160,30 @@
|
||
pass
|
||
finally:
|
||
if server_name in server_processes:
|
||
del server_processes[server_name]
|
||
print(f"Сервер {server_name} остановлен")
|
||
-
|
||
+
|
||
return {"message": "Сервер остановлен"}
|
||
|
||
+
|
||
@app.post("/api/servers/{server_name}/command")
|
||
-async def send_command(server_name: str, command: dict, user: dict = Depends(get_current_user)):
|
||
+async def send_command(
|
||
+ server_name: str, command: dict, user: dict = Depends(get_current_user)
|
||
+):
|
||
if not check_server_access(user, server_name):
|
||
raise HTTPException(403, "Нет доступа к этому серверу")
|
||
-
|
||
+
|
||
if server_name not in server_processes:
|
||
raise HTTPException(400, "Сервер не запущен")
|
||
-
|
||
+
|
||
process = server_processes[server_name]
|
||
-
|
||
+
|
||
if process.poll() is not None:
|
||
del server_processes[server_name]
|
||
raise HTTPException(400, "Сервер не запущен")
|
||
-
|
||
+
|
||
try:
|
||
cmd = command["command"]
|
||
if process.stdin and not process.stdin.closed:
|
||
process.stdin.write(cmd + "\n")
|
||
process.stdin.flush()
|
||
@@ -1088,85 +1193,76 @@
|
||
raise HTTPException(400, "Невозможно отправить команду")
|
||
except Exception as e:
|
||
print(f"Ошибка отправки команды серверу {server_name}: {e}")
|
||
raise HTTPException(500, f"Ошибка отправки команды: {str(e)}")
|
||
|
||
+
|
||
@app.get("/api/servers/{server_name}/stats")
|
||
async def get_server_stats(server_name: str, user: dict = Depends(get_current_user)):
|
||
if not check_server_access(user, server_name):
|
||
raise HTTPException(403, "Нет доступа к этому серверу")
|
||
-
|
||
+
|
||
server_path = SERVERS_DIR / server_name
|
||
-
|
||
+
|
||
try:
|
||
- disk_usage = sum(f.stat().st_size for f in server_path.rglob('*') if f.is_file())
|
||
+ disk_usage = sum(
|
||
+ f.stat().st_size for f in server_path.rglob("*") if f.is_file()
|
||
+ )
|
||
disk_mb = disk_usage / 1024 / 1024
|
||
except:
|
||
disk_mb = 0
|
||
-
|
||
+
|
||
if server_name not in server_processes:
|
||
- return {
|
||
- "status": "stopped",
|
||
- "cpu": 0,
|
||
- "memory": 0,
|
||
- "disk": round(disk_mb, 2)
|
||
- }
|
||
-
|
||
+ return {"status": "stopped", "cpu": 0, "memory": 0, "disk": round(disk_mb, 2)}
|
||
+
|
||
process = server_processes[server_name]
|
||
try:
|
||
if process.poll() is not None:
|
||
del server_processes[server_name]
|
||
return {
|
||
"status": "stopped",
|
||
"cpu": 0,
|
||
"memory": 0,
|
||
- "disk": round(disk_mb, 2)
|
||
+ "disk": round(disk_mb, 2),
|
||
}
|
||
-
|
||
+
|
||
proc = psutil.Process(process.pid)
|
||
memory_mb = proc.memory_info().rss / 1024 / 1024
|
||
cpu_percent = proc.cpu_percent(interval=0.1)
|
||
-
|
||
+
|
||
return {
|
||
"status": "running",
|
||
"cpu": round(cpu_percent, 2),
|
||
"memory": round(memory_mb, 2),
|
||
- "disk": round(disk_mb, 2)
|
||
+ "disk": round(disk_mb, 2),
|
||
}
|
||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||
if server_name in server_processes:
|
||
del server_processes[server_name]
|
||
- return {
|
||
- "status": "stopped",
|
||
- "cpu": 0,
|
||
- "memory": 0,
|
||
- "disk": round(disk_mb, 2)
|
||
- }
|
||
+ return {"status": "stopped", "cpu": 0, "memory": 0, "disk": round(disk_mb, 2)}
|
||
except Exception as e:
|
||
print(f"Ошибка получения статистики для {server_name}: {e}")
|
||
- return {
|
||
- "status": "unknown",
|
||
- "cpu": 0,
|
||
- "memory": 0,
|
||
- "disk": round(disk_mb, 2)
|
||
- }
|
||
+ return {"status": "unknown", "cpu": 0, "memory": 0, "disk": round(disk_mb, 2)}
|
||
+
|
||
|
||
@app.websocket("/ws/servers/{server_name}/console")
|
||
async def console_websocket(websocket: WebSocket, server_name: str):
|
||
await websocket.accept()
|
||
print(f"WebSocket подключен для сервера: {server_name}")
|
||
-
|
||
+
|
||
if server_name in server_logs:
|
||
print(f"Отправка {len(server_logs[server_name])} существующих логов")
|
||
for log in server_logs[server_name]:
|
||
await websocket.send_text(log)
|
||
else:
|
||
print(f"Логов для сервера {server_name} пока нет")
|
||
- await websocket.send_text(f"[Панель] Ожидание логов от сервера {server_name}...")
|
||
-
|
||
+ await websocket.send_text(
|
||
+ f"[Панель] Ожидание логов от сервера {server_name}..."
|
||
+ )
|
||
+
|
||
last_sent_index = len(server_logs.get(server_name, []))
|
||
-
|
||
+
|
||
try:
|
||
while True:
|
||
if server_name in server_logs:
|
||
current_logs = server_logs[server_name]
|
||
if len(current_logs) > last_sent_index:
|
||
@@ -1176,128 +1272,149 @@
|
||
await asyncio.sleep(0.1)
|
||
except Exception as e:
|
||
print(f"WebSocket ошибка: {e}")
|
||
pass
|
||
|
||
+
|
||
# API для файлов
|
||
@app.get("/api/servers/{server_name}/files")
|
||
-async def list_files(server_name: str, path: str = "", user: dict = Depends(get_current_user)):
|
||
+async def list_files(
|
||
+ server_name: str, path: str = "", user: dict = Depends(get_current_user)
|
||
+):
|
||
if not check_server_access(user, server_name):
|
||
raise HTTPException(403, "Нет доступа к этому серверу")
|
||
-
|
||
+
|
||
server_path = SERVERS_DIR / server_name
|
||
if not server_path.exists():
|
||
raise HTTPException(404, "Сервер не найден")
|
||
-
|
||
+
|
||
target_path = server_path / path if path else server_path
|
||
-
|
||
+
|
||
try:
|
||
target_path = target_path.resolve()
|
||
server_path = server_path.resolve()
|
||
if not str(target_path).startswith(str(server_path)):
|
||
raise HTTPException(403, "Доступ запрещен")
|
||
except:
|
||
raise HTTPException(404, "Путь не найден")
|
||
-
|
||
+
|
||
if not target_path.exists():
|
||
raise HTTPException(404, "Путь не найден")
|
||
-
|
||
+
|
||
if not target_path.is_dir():
|
||
raise HTTPException(400, "Путь не является директорией")
|
||
-
|
||
+
|
||
files = []
|
||
try:
|
||
for item in target_path.iterdir():
|
||
- files.append({
|
||
- "name": item.name,
|
||
- "type": "directory" if item.is_dir() else "file",
|
||
- "size": item.stat().st_size if item.is_file() else 0
|
||
- })
|
||
+ files.append(
|
||
+ {
|
||
+ "name": item.name,
|
||
+ "type": "directory" if item.is_dir() else "file",
|
||
+ "size": item.stat().st_size if item.is_file() else 0,
|
||
+ }
|
||
+ )
|
||
except Exception as e:
|
||
print(f"Ошибка чтения директории: {e}")
|
||
raise HTTPException(500, f"Ошибка чтения директории: {str(e)}")
|
||
-
|
||
+
|
||
return files
|
||
|
||
+
|
||
@app.get("/api/servers/{server_name}/files/download")
|
||
-async def download_file(server_name: str, path: str, user: dict = Depends(get_current_user)):
|
||
+async def download_file(
|
||
+ server_name: str, path: str, user: dict = Depends(get_current_user)
|
||
+):
|
||
if not check_server_access(user, server_name):
|
||
raise HTTPException(403, "Нет доступа к этому серверу")
|
||
-
|
||
+
|
||
server_path = SERVERS_DIR / server_name
|
||
file_path = server_path / path
|
||
-
|
||
+
|
||
if not file_path.exists() or not str(file_path).startswith(str(server_path)):
|
||
raise HTTPException(404, "Файл не найден")
|
||
-
|
||
+
|
||
return FileResponse(file_path, filename=file_path.name)
|
||
|
||
+
|
||
@app.post("/api/servers/{server_name}/files/upload")
|
||
-async def upload_file(server_name: str, path: str, file: UploadFile = File(...), user: dict = Depends(get_current_user)):
|
||
- print(f"Upload request: server={server_name}, path='{path}', filename='{file.filename}'")
|
||
-
|
||
+async def upload_file(
|
||
+ server_name: str,
|
||
+ path: str,
|
||
+ file: UploadFile = File(...),
|
||
+ user: dict = Depends(get_current_user),
|
||
+):
|
||
+ print(
|
||
+ f"Upload request: server={server_name}, path='{path}', filename='{file.filename}'"
|
||
+ )
|
||
+
|
||
if not check_server_access(user, server_name):
|
||
raise HTTPException(403, "Нет доступа к этому серверу")
|
||
-
|
||
+
|
||
server_path = SERVERS_DIR / server_name
|
||
target_path = server_path / path / file.filename
|
||
-
|
||
+
|
||
print(f"Target path: {target_path}")
|
||
print(f"Server path: {server_path}")
|
||
- print(f"Path starts with server_path: {str(target_path).startswith(str(server_path))}")
|
||
-
|
||
+ print(
|
||
+ f"Path starts with server_path: {str(target_path).startswith(str(server_path))}"
|
||
+ )
|
||
+
|
||
if not str(target_path).startswith(str(server_path)):
|
||
raise HTTPException(400, "Недопустимый путь")
|
||
-
|
||
+
|
||
try:
|
||
target_path.parent.mkdir(parents=True, exist_ok=True)
|
||
print(f"Created directory: {target_path.parent}")
|
||
except Exception as e:
|
||
print(f"Error creating directory: {e}")
|
||
raise HTTPException(500, f"Ошибка создания директории: {str(e)}")
|
||
-
|
||
+
|
||
try:
|
||
with open(target_path, "wb") as f:
|
||
content = await file.read()
|
||
f.write(content)
|
||
print(f"File written successfully: {target_path}")
|
||
except Exception as e:
|
||
print(f"Error writing file: {e}")
|
||
raise HTTPException(500, f"Ошибка записи файла: {str(e)}")
|
||
-
|
||
+
|
||
return {"message": "Файл загружен"}
|
||
|
||
+
|
||
@app.post("/api/servers/{server_name}/files/create")
|
||
-async def create_file_or_folder(server_name: str, data: dict, user: dict = Depends(get_current_user)):
|
||
+async def create_file_or_folder(
|
||
+ server_name: str, data: dict, user: dict = Depends(get_current_user)
|
||
+):
|
||
"""Создать новый файл или папку"""
|
||
if not check_server_access(user, server_name):
|
||
raise HTTPException(403, "Нет доступа к этому серверу")
|
||
-
|
||
+
|
||
item_type = data.get("type") # "file" or "folder"
|
||
name = data.get("name", "").strip()
|
||
path = data.get("path", "") # Текущая папка
|
||
-
|
||
+
|
||
if not name:
|
||
raise HTTPException(400, "Имя не может быть пустым")
|
||
-
|
||
+
|
||
if item_type not in ["file", "folder"]:
|
||
raise HTTPException(400, "Тип должен быть 'file' или 'folder'")
|
||
-
|
||
+
|
||
server_path = SERVERS_DIR / server_name
|
||
-
|
||
+
|
||
# Формируем полный путь
|
||
if path:
|
||
full_path = server_path / path / name
|
||
else:
|
||
full_path = server_path / name
|
||
-
|
||
+
|
||
print(f"Creating {item_type}: {full_path}")
|
||
-
|
||
+
|
||
# Проверка безопасности
|
||
if not str(full_path).startswith(str(server_path)):
|
||
raise HTTPException(400, "Недопустимый путь")
|
||
-
|
||
+
|
||
try:
|
||
if item_type == "folder":
|
||
# Создаем папку
|
||
full_path.mkdir(parents=True, exist_ok=True)
|
||
# Создаем .gitkeep чтобы папка не была пустой
|
||
@@ -1307,198 +1424,242 @@
|
||
else:
|
||
# Создаем файл
|
||
full_path.parent.mkdir(parents=True, exist_ok=True)
|
||
full_path.touch()
|
||
print(f"File created: {full_path}")
|
||
-
|
||
- return {"message": f"{'Папка' if item_type == 'folder' else 'Файл'} создан(а)", "path": str(full_path)}
|
||
+
|
||
+ return {
|
||
+ "message": f"{'Папка' if item_type == 'folder' else 'Файл'} создан(а)",
|
||
+ "path": str(full_path),
|
||
+ }
|
||
except Exception as e:
|
||
print(f"Error creating {item_type}: {e}")
|
||
raise HTTPException(500, f"Ошибка создания: {str(e)}")
|
||
|
||
+
|
||
@app.delete("/api/servers/{server_name}/files")
|
||
-async def delete_file(server_name: str, path: str, user: dict = Depends(get_current_user)):
|
||
+async def delete_file(
|
||
+ server_name: str, path: str, user: dict = Depends(get_current_user)
|
||
+):
|
||
if not check_server_access(user, server_name):
|
||
raise HTTPException(403, "Нет доступа к этому серверу")
|
||
-
|
||
+
|
||
server_path = SERVERS_DIR / server_name
|
||
target_path = server_path / path
|
||
-
|
||
+
|
||
if not target_path.exists() or not str(target_path).startswith(str(server_path)):
|
||
raise HTTPException(404, "Файл не найден")
|
||
-
|
||
+
|
||
if target_path.is_dir():
|
||
shutil.rmtree(target_path)
|
||
else:
|
||
target_path.unlink()
|
||
-
|
||
+
|
||
return {"message": "Файл удален"}
|
||
|
||
+
|
||
@app.get("/api/servers/{server_name}/files/content")
|
||
-async def get_file_content(server_name: str, path: str, user: dict = Depends(get_current_user)):
|
||
+async def get_file_content(
|
||
+ server_name: str, path: str, user: dict = Depends(get_current_user)
|
||
+):
|
||
if not check_server_access(user, server_name):
|
||
raise HTTPException(403, "Нет доступа к этому серверу")
|
||
-
|
||
+
|
||
server_path = SERVERS_DIR / server_name
|
||
file_path = server_path / path
|
||
-
|
||
- if not file_path.exists() or not file_path.is_file() or not str(file_path).startswith(str(server_path)):
|
||
+
|
||
+ if (
|
||
+ not file_path.exists()
|
||
+ or not file_path.is_file()
|
||
+ or not str(file_path).startswith(str(server_path))
|
||
+ ):
|
||
raise HTTPException(404, "Файл не найден")
|
||
-
|
||
+
|
||
try:
|
||
- with open(file_path, 'r', encoding='utf-8') as f:
|
||
+ with open(file_path, "r", encoding="utf-8") as f:
|
||
content = f.read()
|
||
return {"content": content}
|
||
except UnicodeDecodeError:
|
||
raise HTTPException(400, "Файл не является текстовым")
|
||
|
||
+
|
||
@app.put("/api/servers/{server_name}/files/content")
|
||
-async def update_file_content(server_name: str, path: str, data: dict, user: dict = Depends(get_current_user)):
|
||
+async def update_file_content(
|
||
+ server_name: str, path: str, data: dict, user: dict = Depends(get_current_user)
|
||
+):
|
||
if not check_server_access(user, server_name):
|
||
raise HTTPException(403, "Нет доступа к этому серверу")
|
||
-
|
||
+
|
||
server_path = SERVERS_DIR / server_name
|
||
file_path = server_path / path
|
||
-
|
||
- if not file_path.exists() or not file_path.is_file() or not str(file_path).startswith(str(server_path)):
|
||
+
|
||
+ if (
|
||
+ not file_path.exists()
|
||
+ or not file_path.is_file()
|
||
+ or not str(file_path).startswith(str(server_path))
|
||
+ ):
|
||
raise HTTPException(404, "Файл не найден")
|
||
-
|
||
+
|
||
try:
|
||
- with open(file_path, 'w', encoding='utf-8') as f:
|
||
+ with open(file_path, "w", encoding="utf-8") as f:
|
||
f.write(data.get("content", ""))
|
||
return {"message": "Файл сохранен"}
|
||
except Exception as e:
|
||
raise HTTPException(400, f"Ошибка сохранения файла: {str(e)}")
|
||
|
||
+
|
||
@app.put("/api/servers/{server_name}/files/rename")
|
||
-async def rename_file(server_name: str, old_path: str, new_name: str, user: dict = Depends(get_current_user)):
|
||
+async def rename_file(
|
||
+ server_name: str,
|
||
+ old_path: str,
|
||
+ new_name: str,
|
||
+ user: dict = Depends(get_current_user),
|
||
+):
|
||
if not check_server_access(user, server_name):
|
||
raise HTTPException(403, "Нет доступа к этому серверу")
|
||
-
|
||
+
|
||
server_path = SERVERS_DIR / server_name
|
||
old_file_path = server_path / old_path
|
||
-
|
||
- if not old_file_path.exists() or not str(old_file_path).startswith(str(server_path)):
|
||
+
|
||
+ if not old_file_path.exists() or not str(old_file_path).startswith(
|
||
+ str(server_path)
|
||
+ ):
|
||
raise HTTPException(404, "Файл не найден")
|
||
-
|
||
+
|
||
new_file_path = old_file_path.parent / new_name
|
||
-
|
||
+
|
||
if new_file_path.exists():
|
||
raise HTTPException(400, "Файл с таким именем уже существует")
|
||
-
|
||
+
|
||
if not str(new_file_path).startswith(str(server_path)):
|
||
raise HTTPException(400, "Недопустимое имя файла")
|
||
-
|
||
+
|
||
old_file_path.rename(new_file_path)
|
||
return {"message": "Файл переименован"}
|
||
|
||
+
|
||
@app.post("/api/servers/{server_name}/files/move")
|
||
-async def move_file(server_name: str, data: dict, user: dict = Depends(get_current_user)):
|
||
+async def move_file(
|
||
+ server_name: str, data: dict, user: dict = Depends(get_current_user)
|
||
+):
|
||
"""Переместить файл или папку"""
|
||
if not check_server_access(user, server_name):
|
||
raise HTTPException(403, "Нет доступа к этому серверу")
|
||
-
|
||
+
|
||
source_path = data.get("source", "").strip()
|
||
destination_path = data.get("destination", "").strip()
|
||
-
|
||
+
|
||
if not source_path:
|
||
raise HTTPException(400, "Не указан исходный путь")
|
||
-
|
||
+
|
||
server_path = SERVERS_DIR / server_name
|
||
source_full = server_path / source_path
|
||
-
|
||
+
|
||
# Формируем путь назначения
|
||
if destination_path:
|
||
# Извлекаем имя файла из source_path
|
||
file_name = source_full.name
|
||
dest_full = server_path / destination_path / file_name
|
||
else:
|
||
# Перемещение в корень
|
||
file_name = source_full.name
|
||
dest_full = server_path / file_name
|
||
-
|
||
+
|
||
print(f"Moving: {source_full} -> {dest_full}")
|
||
-
|
||
+
|
||
# Проверки безопасности
|
||
if not source_full.exists():
|
||
raise HTTPException(404, "Исходный файл не найден")
|
||
-
|
||
+
|
||
if not str(source_full).startswith(str(server_path)):
|
||
raise HTTPException(400, "Недопустимый исходный путь")
|
||
-
|
||
+
|
||
if not str(dest_full).startswith(str(server_path)):
|
||
raise HTTPException(400, "Недопустимый путь назначения")
|
||
-
|
||
+
|
||
if dest_full.exists():
|
||
- raise HTTPException(400, "Файл с таким именем уже существует в папке назначения")
|
||
-
|
||
+ raise HTTPException(
|
||
+ 400, "Файл с таким именем уже существует в папке назначения"
|
||
+ )
|
||
+
|
||
try:
|
||
# Создаем папку назначения если не существует
|
||
dest_full.parent.mkdir(parents=True, exist_ok=True)
|
||
-
|
||
+
|
||
# Перемещаем файл/папку
|
||
import shutil
|
||
+
|
||
shutil.move(str(source_full), str(dest_full))
|
||
-
|
||
+
|
||
print(f"Moved successfully: {dest_full}")
|
||
return {"message": "Файл перемещен", "new_path": str(dest_full)}
|
||
except Exception as e:
|
||
print(f"Error moving file: {e}")
|
||
raise HTTPException(500, f"Ошибка перемещения: {str(e)}")
|
||
|
||
+
|
||
@app.put("/api/servers/{server_name}/files/rename")
|
||
-async def rename_file(server_name: str, old_path: str, new_name: str, user: dict = Depends(get_current_user)):
|
||
+async def rename_file(
|
||
+ server_name: str,
|
||
+ old_path: str,
|
||
+ new_name: str,
|
||
+ user: dict = Depends(get_current_user),
|
||
+):
|
||
if not check_server_access(user, server_name):
|
||
raise HTTPException(403, "Нет доступа к этому серверу")
|
||
-
|
||
+
|
||
server_path = SERVERS_DIR / server_name
|
||
old_file_path = server_path / old_path
|
||
-
|
||
- if not old_file_path.exists() or not str(old_file_path).startswith(str(server_path)):
|
||
+
|
||
+ if not old_file_path.exists() or not str(old_file_path).startswith(
|
||
+ str(server_path)
|
||
+ ):
|
||
raise HTTPException(404, "Файл не найден")
|
||
-
|
||
+
|
||
new_file_path = old_file_path.parent / new_name
|
||
-
|
||
+
|
||
if new_file_path.exists():
|
||
raise HTTPException(400, "Файл с таким именем уже существует")
|
||
-
|
||
+
|
||
if not str(new_file_path).startswith(str(server_path)):
|
||
raise HTTPException(400, "Недопустимое имя файла")
|
||
-
|
||
+
|
||
old_file_path.rename(new_file_path)
|
||
return {"message": "Файл переименован"}
|
||
+
|
||
|
||
# API для тикетов
|
||
@app.get("/api/tickets")
|
||
async def get_tickets(user: dict = Depends(get_current_user)):
|
||
"""Получить список тикетов"""
|
||
# Проверяем права на тикеты
|
||
if not user.get("permissions", {}).get("tickets", True):
|
||
raise HTTPException(403, "Нет доступа к тикетам")
|
||
-
|
||
+
|
||
tickets = load_tickets()
|
||
-
|
||
+
|
||
# Владелец, админы и тех. поддержка видят все тикеты
|
||
if user["role"] in ["owner", "admin", "support"]:
|
||
return list(tickets.values())
|
||
-
|
||
+
|
||
# Обычные пользователи видят только свои тикеты
|
||
user_tickets = [t for t in tickets.values() if t["author"] == user["username"]]
|
||
return user_tickets
|
||
+
|
||
|
||
@app.post("/api/tickets/create")
|
||
async def create_ticket(data: dict, user: dict = Depends(get_current_user)):
|
||
"""Создать новый тикет"""
|
||
# Проверяем права на тикеты
|
||
if not user.get("permissions", {}).get("tickets", True):
|
||
raise HTTPException(403, "Нет доступа к тикетам")
|
||
-
|
||
+
|
||
tickets = load_tickets()
|
||
-
|
||
+
|
||
# Генерируем ID тикета
|
||
ticket_id = str(len(tickets) + 1)
|
||
-
|
||
+
|
||
ticket = {
|
||
"id": ticket_id,
|
||
"title": data.get("title", "").strip(),
|
||
"description": data.get("description", "").strip(),
|
||
"author": user["username"],
|
||
@@ -1507,368 +1668,467 @@
|
||
"updated_at": datetime.utcnow().isoformat(),
|
||
"messages": [
|
||
{
|
||
"author": user["username"],
|
||
"text": data.get("description", "").strip(),
|
||
- "timestamp": datetime.utcnow().isoformat()
|
||
+ "timestamp": datetime.utcnow().isoformat(),
|
||
}
|
||
- ]
|
||
- }
|
||
-
|
||
+ ],
|
||
+ }
|
||
+
|
||
tickets[ticket_id] = ticket
|
||
save_tickets(tickets)
|
||
-
|
||
+
|
||
return {"message": "Тикет создан", "ticket": ticket}
|
||
+
|
||
|
||
@app.get("/api/tickets/{ticket_id}")
|
||
async def get_ticket(ticket_id: str, user: dict = Depends(get_current_user)):
|
||
"""Получить тикет по ID"""
|
||
# Проверяем права на тикеты
|
||
if not user.get("permissions", {}).get("tickets", True):
|
||
raise HTTPException(403, "Нет доступа к тикетам")
|
||
-
|
||
+
|
||
tickets = load_tickets()
|
||
-
|
||
+
|
||
if ticket_id not in tickets:
|
||
raise HTTPException(404, "Тикет не найден")
|
||
-
|
||
+
|
||
ticket = tickets[ticket_id]
|
||
-
|
||
+
|
||
# Проверка доступа
|
||
- if user["role"] not in ["owner", "admin", "support"] and ticket["author"] != user["username"]:
|
||
+ if (
|
||
+ user["role"] not in ["owner", "admin", "support"]
|
||
+ and ticket["author"] != user["username"]
|
||
+ ):
|
||
raise HTTPException(403, "Нет доступа к этому тикету")
|
||
-
|
||
+
|
||
return ticket
|
||
|
||
+
|
||
@app.post("/api/tickets/{ticket_id}/message")
|
||
-async def add_ticket_message(ticket_id: str, data: dict, user: dict = Depends(get_current_user)):
|
||
+async def add_ticket_message(
|
||
+ ticket_id: str, data: dict, user: dict = Depends(get_current_user)
|
||
+):
|
||
"""Добавить сообщение в тикет"""
|
||
# Проверяем права на тикеты
|
||
if not user.get("permissions", {}).get("tickets", True):
|
||
raise HTTPException(403, "Нет доступа к тикетам")
|
||
-
|
||
+
|
||
tickets = load_tickets()
|
||
-
|
||
+
|
||
if ticket_id not in tickets:
|
||
raise HTTPException(404, "Тикет не найден")
|
||
-
|
||
+
|
||
ticket = tickets[ticket_id]
|
||
-
|
||
+
|
||
# Проверка доступа
|
||
- if user["role"] not in ["owner", "admin", "support"] and ticket["author"] != user["username"]:
|
||
+ if (
|
||
+ user["role"] not in ["owner", "admin", "support"]
|
||
+ and ticket["author"] != user["username"]
|
||
+ ):
|
||
raise HTTPException(403, "Нет доступа к этому тикету")
|
||
-
|
||
+
|
||
message = {
|
||
"author": user["username"],
|
||
"text": data.get("text", "").strip(),
|
||
- "timestamp": datetime.utcnow().isoformat()
|
||
- }
|
||
-
|
||
+ "timestamp": datetime.utcnow().isoformat(),
|
||
+ }
|
||
+
|
||
ticket["messages"].append(message)
|
||
ticket["updated_at"] = datetime.utcnow().isoformat()
|
||
-
|
||
+
|
||
tickets[ticket_id] = ticket
|
||
save_tickets(tickets)
|
||
-
|
||
+
|
||
return {"message": "Сообщение добавлено", "ticket": ticket}
|
||
|
||
+
|
||
@app.put("/api/tickets/{ticket_id}/status")
|
||
-async def update_ticket_status(ticket_id: str, data: dict, user: dict = Depends(get_current_user)):
|
||
+async def update_ticket_status(
|
||
+ ticket_id: str, data: dict, user: dict = Depends(get_current_user)
|
||
+):
|
||
"""Изменить статус тикета (только для владельца, админов и тех. поддержки)"""
|
||
if user["role"] not in ["owner", "admin", "support"]:
|
||
raise HTTPException(403, "Недостаточно прав")
|
||
-
|
||
+
|
||
# Проверяем права на тикеты
|
||
if not user.get("permissions", {}).get("tickets", True):
|
||
raise HTTPException(403, "Нет доступа к тикетам")
|
||
-
|
||
+
|
||
tickets = load_tickets()
|
||
-
|
||
+
|
||
if ticket_id not in tickets:
|
||
raise HTTPException(404, "Тикет не найден")
|
||
-
|
||
+
|
||
new_status = data.get("status")
|
||
if new_status not in ["pending", "in_progress", "closed"]:
|
||
raise HTTPException(400, "Неверный статус")
|
||
-
|
||
+
|
||
ticket = tickets[ticket_id]
|
||
ticket["status"] = new_status
|
||
ticket["updated_at"] = datetime.utcnow().isoformat()
|
||
-
|
||
+
|
||
# Добавляем системное сообщение о смене статуса
|
||
status_names = {
|
||
"pending": "На рассмотрении",
|
||
"in_progress": "В работе",
|
||
- "closed": "Закрыт"
|
||
- }
|
||
-
|
||
+ "closed": "Закрыт",
|
||
+ }
|
||
+
|
||
message = {
|
||
"author": "system",
|
||
"text": f"Статус изменён на: {status_names[new_status]}",
|
||
- "timestamp": datetime.utcnow().isoformat()
|
||
- }
|
||
-
|
||
+ "timestamp": datetime.utcnow().isoformat(),
|
||
+ }
|
||
+
|
||
ticket["messages"].append(message)
|
||
-
|
||
+
|
||
tickets[ticket_id] = ticket
|
||
save_tickets(tickets)
|
||
-
|
||
+
|
||
return {"message": "Статус обновлён", "ticket": ticket}
|
||
|
||
|
||
# ============================================
|
||
# УПРАВЛЕНИЕ ПОЛЬЗОВАТЕЛЯМИ (v1.1.0)
|
||
# ============================================
|
||
+
|
||
|
||
# Загрузка пользователей
|
||
def load_users_dict():
|
||
users_file = Path("users.json")
|
||
if not users_file.exists():
|
||
return {}
|
||
with open(users_file, "r", encoding="utf-8") as f:
|
||
return json.load(f)
|
||
|
||
+
|
||
def save_users_dict(users):
|
||
with open("users.json", "w", encoding="utf-8") as f:
|
||
json.dump(users, f, indent=2, ensure_ascii=False)
|
||
+
|
||
|
||
# Проверка прав
|
||
def require_owner(current_user: dict):
|
||
if current_user.get("role") != "owner":
|
||
raise HTTPException(status_code=403, detail="Требуется роль владельца")
|
||
|
||
+
|
||
def require_admin_or_owner(current_user: dict):
|
||
if current_user.get("role") not in ["owner", "admin"]:
|
||
- raise HTTPException(status_code=403, detail="Требуется роль администратора или владельца")
|
||
+ raise HTTPException(
|
||
+ status_code=403, detail="Требуется роль администратора или владельца"
|
||
+ )
|
||
+
|
||
|
||
# 1. Получить список пользователей
|
||
@app.get("/api/users")
|
||
async def get_users(current_user: dict = Depends(get_current_user)):
|
||
require_admin_or_owner(current_user)
|
||
-
|
||
+
|
||
users = load_users_dict()
|
||
users_list = []
|
||
for username, user_data in users.items():
|
||
user_copy = user_data.copy()
|
||
user_copy.pop("password", None)
|
||
users_list.append(user_copy)
|
||
-
|
||
+
|
||
return users_list
|
||
+
|
||
|
||
# 2. Изменить роль пользователя
|
||
class RoleChange(BaseModel):
|
||
role: str
|
||
|
||
+
|
||
@app.put("/api/users/{username}/role")
|
||
-async def change_user_role(username: str, role_data: RoleChange, current_user: dict = Depends(get_current_user)):
|
||
+async def change_user_role(
|
||
+ username: str, role_data: RoleChange, current_user: dict = Depends(get_current_user)
|
||
+):
|
||
require_owner(current_user)
|
||
-
|
||
+
|
||
users = load_users_dict()
|
||
-
|
||
+
|
||
if username not in users:
|
||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
-
|
||
+
|
||
if username == current_user.get("username"):
|
||
raise HTTPException(status_code=400, detail="Нельзя изменить свою роль")
|
||
-
|
||
+
|
||
valid_roles = ["owner", "admin", "support", "user", "banned"]
|
||
if role_data.role not in valid_roles:
|
||
raise HTTPException(status_code=400, detail=f"Неверная роль")
|
||
-
|
||
+
|
||
# Разрешаем несколько владельцев (убрано ограничение на одного)
|
||
# Теперь можно назначить несколько пользователей с ролью owner
|
||
-
|
||
+
|
||
old_role = users[username].get("role", "user")
|
||
users[username]["role"] = role_data.role
|
||
-
|
||
+
|
||
# Обновляем права
|
||
if role_data.role == "owner":
|
||
users[username]["permissions"] = {
|
||
- "manage_users": True, "manage_roles": True, "manage_servers": True,
|
||
- "manage_tickets": True, "manage_files": True, "delete_users": True,
|
||
- "view_all_resources": True
|
||
+ "manage_users": True,
|
||
+ "manage_roles": True,
|
||
+ "manage_servers": True,
|
||
+ "manage_tickets": True,
|
||
+ "manage_files": True,
|
||
+ "delete_users": True,
|
||
+ "view_all_resources": True,
|
||
}
|
||
elif role_data.role == "admin":
|
||
users[username]["permissions"] = {
|
||
- "manage_users": True, "manage_roles": False, "manage_servers": True,
|
||
- "manage_tickets": True, "manage_files": True, "delete_users": False,
|
||
- "view_all_resources": True
|
||
+ "manage_users": True,
|
||
+ "manage_roles": False,
|
||
+ "manage_servers": True,
|
||
+ "manage_tickets": True,
|
||
+ "manage_files": True,
|
||
+ "delete_users": False,
|
||
+ "view_all_resources": True,
|
||
}
|
||
elif role_data.role == "support":
|
||
users[username]["permissions"] = {
|
||
- "manage_users": False, "manage_roles": False, "manage_servers": False,
|
||
- "manage_tickets": True, "manage_files": False, "delete_users": False,
|
||
- "view_all_resources": False
|
||
+ "manage_users": False,
|
||
+ "manage_roles": False,
|
||
+ "manage_servers": False,
|
||
+ "manage_tickets": True,
|
||
+ "manage_files": False,
|
||
+ "delete_users": False,
|
||
+ "view_all_resources": False,
|
||
}
|
||
elif role_data.role == "banned":
|
||
users[username]["permissions"] = {
|
||
- "manage_users": False, "manage_roles": False, "manage_servers": False,
|
||
- "manage_tickets": False, "manage_files": False, "delete_users": False,
|
||
- "view_all_resources": False
|
||
+ "manage_users": False,
|
||
+ "manage_roles": False,
|
||
+ "manage_servers": False,
|
||
+ "manage_tickets": False,
|
||
+ "manage_files": False,
|
||
+ "delete_users": False,
|
||
+ "view_all_resources": False,
|
||
}
|
||
else: # user
|
||
users[username]["permissions"] = {
|
||
- "manage_users": False, "manage_roles": False, "manage_servers": True,
|
||
- "manage_tickets": True, "manage_files": True, "delete_users": False,
|
||
- "view_all_resources": False
|
||
+ "manage_users": False,
|
||
+ "manage_roles": False,
|
||
+ "manage_servers": True,
|
||
+ "manage_tickets": True,
|
||
+ "manage_files": True,
|
||
+ "delete_users": False,
|
||
+ "view_all_resources": False,
|
||
}
|
||
-
|
||
+
|
||
save_users_dict(users)
|
||
-
|
||
- return {"message": f"Роль изменена с {old_role} на {role_data.role}", "user": {"username": username, "role": role_data.role}}
|
||
+
|
||
+ return {
|
||
+ "message": f"Роль изменена с {old_role} на {role_data.role}",
|
||
+ "user": {"username": username, "role": role_data.role},
|
||
+ }
|
||
+
|
||
|
||
# 3. Заблокировать пользователя
|
||
class BanRequest(BaseModel):
|
||
reason: str = "Заблокирован администратором"
|
||
|
||
+
|
||
@app.post("/api/users/{username}/ban")
|
||
-async def ban_user(username: str, ban_data: BanRequest, current_user: dict = Depends(get_current_user)):
|
||
+async def ban_user(
|
||
+ username: str, ban_data: BanRequest, current_user: dict = Depends(get_current_user)
|
||
+):
|
||
require_admin_or_owner(current_user)
|
||
-
|
||
+
|
||
users = load_users_dict()
|
||
-
|
||
+
|
||
if username not in users:
|
||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
-
|
||
+
|
||
if username == current_user.get("username"):
|
||
raise HTTPException(status_code=400, detail="Нельзя заблокировать самого себя")
|
||
-
|
||
+
|
||
# Проверяем, что не блокируем последнего владельца
|
||
if users[username].get("role") == "owner":
|
||
owners_count = sum(1 for u in users.values() if u.get("role") == "owner")
|
||
if owners_count <= 1:
|
||
- raise HTTPException(status_code=400, detail="Нельзя заблокировать последнего владельца. Должен остаться хотя бы один владелец.")
|
||
-
|
||
+ raise HTTPException(
|
||
+ status_code=400,
|
||
+ detail="Нельзя заблокировать последнего владельца. Должен остаться хотя бы один владелец.",
|
||
+ )
|
||
+
|
||
users[username]["role"] = "banned"
|
||
users[username]["permissions"] = {
|
||
- "manage_users": False, "manage_roles": False, "manage_servers": False,
|
||
- "manage_tickets": False, "manage_files": False, "delete_users": False,
|
||
- "view_all_resources": False
|
||
+ "manage_users": False,
|
||
+ "manage_roles": False,
|
||
+ "manage_servers": False,
|
||
+ "manage_tickets": False,
|
||
+ "manage_files": False,
|
||
+ "delete_users": False,
|
||
+ "view_all_resources": False,
|
||
}
|
||
users[username]["ban_reason"] = ban_data.reason
|
||
-
|
||
+
|
||
save_users_dict(users)
|
||
-
|
||
- return {"message": f"Пользователь {username} заблокирован", "username": username, "reason": ban_data.reason}
|
||
+
|
||
+ return {
|
||
+ "message": f"Пользователь {username} заблокирован",
|
||
+ "username": username,
|
||
+ "reason": ban_data.reason,
|
||
+ }
|
||
+
|
||
|
||
# 4. Разблокировать пользователя
|
||
@app.post("/api/users/{username}/unban")
|
||
async def unban_user(username: str, current_user: dict = Depends(get_current_user)):
|
||
require_admin_or_owner(current_user)
|
||
-
|
||
+
|
||
users = load_users_dict()
|
||
-
|
||
+
|
||
if username not in users:
|
||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
-
|
||
+
|
||
if users[username].get("role") != "banned":
|
||
raise HTTPException(status_code=400, detail="Пользователь не заблокирован")
|
||
-
|
||
+
|
||
users[username]["role"] = "user"
|
||
users[username]["permissions"] = {
|
||
- "manage_users": False, "manage_roles": False, "manage_servers": True,
|
||
- "manage_tickets": True, "manage_files": True, "delete_users": False,
|
||
- "view_all_resources": False
|
||
+ "manage_users": False,
|
||
+ "manage_roles": False,
|
||
+ "manage_servers": True,
|
||
+ "manage_tickets": True,
|
||
+ "manage_files": True,
|
||
+ "delete_users": False,
|
||
+ "view_all_resources": False,
|
||
}
|
||
users[username].pop("ban_reason", None)
|
||
-
|
||
+
|
||
save_users_dict(users)
|
||
-
|
||
+
|
||
return {"message": f"Пользователь {username} разблокирован", "username": username}
|
||
+
|
||
|
||
# 5. Удалить пользователя
|
||
@app.delete("/api/users/{username}")
|
||
async def delete_user(username: str, current_user: dict = Depends(get_current_user)):
|
||
require_owner(current_user)
|
||
-
|
||
+
|
||
users = load_users_dict()
|
||
-
|
||
+
|
||
if username not in users:
|
||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
-
|
||
+
|
||
if username == current_user.get("username"):
|
||
raise HTTPException(status_code=400, detail="Нельзя удалить самого себя")
|
||
-
|
||
+
|
||
# Проверяем, что не удаляем последнего владельца
|
||
if users[username].get("role") == "owner":
|
||
owners_count = sum(1 for u in users.values() if u.get("role") == "owner")
|
||
if owners_count <= 1:
|
||
- raise HTTPException(status_code=400, detail="Нельзя удалить последнего владельца. Должен остаться хотя бы один владелец.")
|
||
-
|
||
+ raise HTTPException(
|
||
+ status_code=400,
|
||
+ detail="Нельзя удалить последнего владельца. Должен остаться хотя бы один владелец.",
|
||
+ )
|
||
+
|
||
del users[username]
|
||
save_users_dict(users)
|
||
-
|
||
+
|
||
return {"message": f"Пользователь {username} удалён", "username": username}
|
||
+
|
||
|
||
# 6. Выдать доступ к серверу
|
||
class ServerAccess(BaseModel):
|
||
server_name: str
|
||
|
||
+
|
||
@app.post("/api/users/{username}/access/servers")
|
||
-async def grant_server_access(username: str, access: ServerAccess, current_user: dict = Depends(get_current_user)):
|
||
+async def grant_server_access(
|
||
+ username: str, access: ServerAccess, current_user: dict = Depends(get_current_user)
|
||
+):
|
||
require_admin_or_owner(current_user)
|
||
-
|
||
+
|
||
users = load_users_dict()
|
||
-
|
||
+
|
||
if username not in users:
|
||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
-
|
||
+
|
||
if "resource_access" not in users[username]:
|
||
users[username]["resource_access"] = {"servers": [], "tickets": [], "files": []}
|
||
-
|
||
+
|
||
if access.server_name not in users[username]["resource_access"]["servers"]:
|
||
users[username]["resource_access"]["servers"].append(access.server_name)
|
||
-
|
||
+
|
||
# Также добавляем в старое поле servers для совместимости
|
||
if "servers" not in users[username]:
|
||
users[username]["servers"] = []
|
||
if access.server_name not in users[username]["servers"]:
|
||
users[username]["servers"].append(access.server_name)
|
||
-
|
||
+
|
||
save_users_dict(users)
|
||
-
|
||
- return {"message": f"Доступ к серверу {access.server_name} выдан", "server": access.server_name, "user": username}
|
||
+
|
||
+ return {
|
||
+ "message": f"Доступ к серверу {access.server_name} выдан",
|
||
+ "server": access.server_name,
|
||
+ "user": username,
|
||
+ }
|
||
+
|
||
|
||
# 7. Забрать доступ к серверу
|
||
@app.delete("/api/users/{username}/access/servers/{server_name}")
|
||
-async def revoke_server_access(username: str, server_name: str, current_user: dict = Depends(get_current_user)):
|
||
+async def revoke_server_access(
|
||
+ username: str, server_name: str, current_user: dict = Depends(get_current_user)
|
||
+):
|
||
require_admin_or_owner(current_user)
|
||
-
|
||
+
|
||
users = load_users_dict()
|
||
-
|
||
+
|
||
if username not in users:
|
||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
-
|
||
- if "resource_access" in users[username] and "servers" in users[username]["resource_access"]:
|
||
+
|
||
+ if (
|
||
+ "resource_access" in users[username]
|
||
+ and "servers" in users[username]["resource_access"]
|
||
+ ):
|
||
if server_name in users[username]["resource_access"]["servers"]:
|
||
users[username]["resource_access"]["servers"].remove(server_name)
|
||
-
|
||
+
|
||
# Также удаляем из старого поля servers
|
||
if "servers" in users[username] and server_name in users[username]["servers"]:
|
||
users[username]["servers"].remove(server_name)
|
||
-
|
||
+
|
||
save_users_dict(users)
|
||
-
|
||
- return {"message": f"Доступ к серверу {server_name} отозван", "server": server_name, "user": username}
|
||
+
|
||
+ return {
|
||
+ "message": f"Доступ к серверу {server_name} отозван",
|
||
+ "server": server_name,
|
||
+ "user": username,
|
||
+ }
|
||
+
|
||
|
||
# 8. Изменить права пользователя
|
||
class PermissionsUpdate(BaseModel):
|
||
permissions: dict
|
||
|
||
+
|
||
@app.put("/api/users/{username}/permissions")
|
||
-async def update_user_permissions(username: str, perms: PermissionsUpdate, current_user: dict = Depends(get_current_user)):
|
||
+async def update_user_permissions(
|
||
+ username: str,
|
||
+ perms: PermissionsUpdate,
|
||
+ current_user: dict = Depends(get_current_user),
|
||
+):
|
||
require_owner(current_user)
|
||
-
|
||
+
|
||
users = load_users_dict()
|
||
-
|
||
+
|
||
if username not in users:
|
||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||
-
|
||
+
|
||
users[username]["permissions"] = perms.permissions
|
||
save_users_dict(users)
|
||
-
|
||
- return {"message": f"Права пользователя {username} обновлены", "permissions": perms.permissions}
|
||
+
|
||
+ return {
|
||
+ "message": f"Права пользователя {username} обновлены",
|
||
+ "permissions": perms.permissions,
|
||
+ }
|
||
|
||
|
||
if __name__ == "__main__":
|
||
import uvicorn
|
||
+
|
||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||
|
||
Oh no! 💥 💔 💥
|
||
6 files would be reformatted.
|