Files
NeveTimePanel/logs_Arkon_NeveTimePanel_1_1_2.txt
2026-01-15 20:57:51 +06:00

4913 lines
209 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.