diff --git a/docker-compose.yaml b/docker-compose.yaml index 7a3d218..f0e3433 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -15,8 +15,8 @@ services: tty: true networks: - main_network - profiles: - - ui +# profiles: +# - ui master-backend-api: build: @@ -41,7 +41,7 @@ services: sh -c " alembic upgrade head && \ - uvicorn main:app --port=10000 --host=0.0.0.0 --workers 1 --reload --no-access-log + uvicorn main:get_application --factory --port=10000 --host=0.0.0.0 --workers 1 --reload --no-access-log " # uvicorn main:app --uds=/app/tmp_uds/master-backend.sock --workers 1 --reload --no-access-log & # diff --git a/master_backend_api/app/dependencies/file_storage.py b/master_backend_api/app/dependencies/file_storage.py index 70dc626..5c0b9c6 100644 --- a/master_backend_api/app/dependencies/file_storage.py +++ b/master_backend_api/app/dependencies/file_storage.py @@ -8,8 +8,7 @@ MAX_FILE_SIZE = 5 * 1024 * 1024 # 5 MB -async def validate_image(mainImage: UploadFile = File(...)) -> File: - +async def validate_image(mainImage: UploadFile = File(...)) -> UploadFile: if mainImage.content_type not in ALLOWED_IMAGE_FILE_TYPES: raise HTTPException( status_code=400, @@ -17,7 +16,11 @@ async def validate_image(mainImage: UploadFile = File(...)) -> File: f"Allowed types: {', '.join(ALLOWED_IMAGE_FILE_TYPES)}.", ) - file_size = len(await mainImage.read()) + # Отримуємо розмір файлу без його повного зчитування + mainImage.file.seek(0, 2) # Переміщуємо курсор у кінець файлу + file_size = mainImage.file.tell() # Отримуємо поточну позицію (розмір у байтах) + mainImage.file.seek(0) # Повертаємо курсор на початок + if file_size > MAX_FILE_SIZE: raise HTTPException( status_code=400, diff --git a/master_backend_api/app/main.py b/master_backend_api/app/main.py index 76dee63..19479b9 100644 --- a/master_backend_api/app/main.py +++ b/master_backend_api/app/main.py @@ -1,4 +1,5 @@ from contextlib import asynccontextmanager +import socketio from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware @@ -10,6 +11,7 @@ from applications.users.router import router_users from applications.payment.routers import router as payment_router from services.redis_service import redis_service +from services.socketio_service import SocketIO, sio, NoPrefixNamespace # ? from settings import settings import sentry_sdk from prometheus_fastapi_instrumentator import Instrumentator @@ -80,17 +82,21 @@ def get_application() -> FastAPI: add_sqladmin_interface(_app) Instrumentator().instrument(_app).expose(_app) + + _app = socketio.ASGIApp(SocketIO().get_sio_server(), _app, socketio_path="/api/socket.io") + # _app.mount("/api/socket.io", SocketIO().sio_app) + return _app -app = get_application() +# app = get_application() -@app.get("/") -async def index(): - logging.debug("111111112222222222222") - logging.info("111111112222222222222") - logging.warning("111111112222222222222") - logging.error("111111112222222222222") - await redis_service.set_cache("hjhjhjhjhh55555555555555551111111", 45) - return {"status": "OK"} +# @app.get("/") +# async def index(): +# logging.debug("111111112222222222222") +# logging.info("111111112222222222222") +# logging.warning("111111112222222222222") +# logging.error("111111112222222222222") +# await redis_service.set_cache("hjhjhjhjhh55555555555555551111111", 45) +# return {"status": "OK"} diff --git a/master_backend_api/app/services/socketio_service.py b/master_backend_api/app/services/socketio_service.py new file mode 100644 index 0000000..465c16a --- /dev/null +++ b/master_backend_api/app/services/socketio_service.py @@ -0,0 +1,33 @@ +import socketio + + +class SocketIO: + + def __init__(self): + self.sio_server = socketio.AsyncServer( + async_mode="asgi", cors_allowed_origins=["*"], transports=["websocket", "polling"] + ) + self.sio_app = socketio.ASGIApp( + self.sio_server, + socketio_path="/api/socket.io/", + ) + + def get_sio_server(self): + return self.sio_server + + +sio = SocketIO().sio_server + + +class NoPrefixNamespace(socketio.AsyncNamespace): + # recheck this internet code + # how does it should works? + def on_connect(self, sid, environ): + print("connect ", sid) + + async def on_message(self, sid, data): + print("message ", data) + await sio.emit("response", "hi " + data) + + def on_disconnect(self, sid): + print("disconnect ", sid) diff --git a/master_backend_api/app/storage/s3.py b/master_backend_api/app/storage/s3.py index 1e068f1..9420ebe 100644 --- a/master_backend_api/app/storage/s3.py +++ b/master_backend_api/app/storage/s3.py @@ -25,7 +25,6 @@ async def get_s3_session(self) -> AsyncGenerator[aioboto3.Session.client, None]: async def upload_file(self, file: UploadFile, object_name: str) -> str: async for s3_client in self.get_s3_session(): - file.file.seek(0) # because of validate_image - it has read file try: await s3_client.upload_fileobj(file, self.bucket_name, object_name) return f"{settings.S3_PUBLIC_BUCKET_URL}/{object_name}" diff --git a/master_backend_api/poetry.lock b/master_backend_api/poetry.lock index 0e65602..7fd6303 100644 --- a/master_backend_api/poetry.lock +++ b/master_backend_api/poetry.lock @@ -399,6 +399,17 @@ files = [ tests = ["pytest (>=3.2.1,!=3.3.0)"] typecheck = ["mypy"] +[[package]] +name = "bidict" +version = "0.23.1" +description = "The bidirectional mapping library for Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5"}, + {file = "bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71"}, +] + [[package]] name = "black" version = "25.1.0" @@ -2058,6 +2069,25 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "python-engineio" +version = "4.11.2" +description = "Engine.IO server and client for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "python_engineio-4.11.2-py3-none-any.whl", hash = "sha256:f0971ac4c65accc489154fe12efd88f53ca8caf04754c46a66e85f5102ef22ad"}, + {file = "python_engineio-4.11.2.tar.gz", hash = "sha256:145bb0daceb904b4bb2d3eb2d93f7dbb7bb87a6a0c4f20a94cc8654dec977129"}, +] + +[package.dependencies] +simple-websocket = ">=0.10.0" + +[package.extras] +asyncio-client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] +docs = ["sphinx"] + [[package]] name = "python-multipart" version = "0.0.20" @@ -2069,6 +2099,27 @@ files = [ {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, ] +[[package]] +name = "python-socketio" +version = "5.12.1" +description = "Socket.IO server and client for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python_socketio-5.12.1-py3-none-any.whl", hash = "sha256:24a0ea7cfff0e021eb28c68edbf7914ee4111bdf030b95e4d250c4dc9af7a386"}, + {file = "python_socketio-5.12.1.tar.gz", hash = "sha256:0299ff1f470b676c09c1bfab1dead25405077d227b2c13cf217a34dadc68ba9c"}, +] + +[package.dependencies] +aiohttp = {version = ">=3.4", optional = true, markers = "extra == \"asyncio-client\""} +bidict = ">=0.21.0" +python-engineio = ">=4.11.0" + +[package.extras] +asyncio-client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] +docs = ["sphinx"] + [[package]] name = "pyyaml" version = "6.0.2" @@ -2314,6 +2365,24 @@ files = [ {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, ] +[[package]] +name = "simple-websocket" +version = "1.1.0" +description = "Simple WebSocket server and client for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c"}, + {file = "simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4"}, +] + +[package.dependencies] +wsproto = "*" + +[package.extras] +dev = ["flake8", "pytest", "pytest-cov", "tox"] +docs = ["sphinx"] + [[package]] name = "six" version = "1.17.0" @@ -2939,6 +3008,20 @@ files = [ {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, ] +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] + +[package.dependencies] +h11 = ">=0.9.0,<1" + [[package]] name = "wtforms" version = "3.1.2" @@ -3055,4 +3138,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "e82e391971243068f15ba176fa6d86d8c4da143e988586004b25de4d7a2c1e86" +content-hash = "d88771f198c3267e3acf078b11a3395f125d37c2cc654fd303bc51bdc241b522" diff --git a/master_backend_api/pyproject.toml b/master_backend_api/pyproject.toml index 64ca390..d0eee91 100644 --- a/master_backend_api/pyproject.toml +++ b/master_backend_api/pyproject.toml @@ -27,6 +27,7 @@ stripe = "^11.6.0" flagsmith = "^3.8.0" aioboto3 = "^14.1.0" transliterate = "^1.10.2" +python-socketio = {extras = ["asyncio-client"], version = "^5.12.1"} [tool.poetry.group.dev.dependencies] flake8 = "^7.1.1" diff --git a/nginx/nginx.conf b/nginx/nginx.conf index f0fcda3..255d21c 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -5,24 +5,24 @@ server { server_tokens off; charset utf-8; -# for dockerized minio -# location /storage/ { -# # https://min.io/docs/minio/linux/integrations/setup-nginx-proxy-with-minio.html -# # rewrite ^/storage(/.*)$ $1 break; -> do not enable it. This is caused because nginx will try to index the directory, and be blocked by itself. -# proxy_set_header Host $http_host; -# proxy_set_header X-Real-IP $remote_addr; -# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; -# proxy_set_header X-Forwarded-Proto $scheme; -# proxy_set_header X-NginX-Proxy true; -# real_ip_header X-Real-IP; -# proxy_connect_timeout 300; -# proxy_http_version 1.1; -# proxy_set_header Upgrade $http_upgrade; -# proxy_set_header Connection "upgrade"; -# chunked_transfer_encoding off; -# -# proxy_pass http://s3:9000; -# } + # for dockerized minio + # location /storage/ { + # # https://min.io/docs/minio/linux/integrations/setup-nginx-proxy-with-minio.html + # # rewrite ^/storage(/.*)$ $1 break; -> do not enable it. This is caused because nginx will try to index the directory, and be blocked by itself. + # proxy_set_header Host $http_host; + # proxy_set_header X-Real-IP $remote_addr; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # proxy_set_header X-Forwarded-Proto $scheme; + # proxy_set_header X-NginX-Proxy true; + # real_ip_header X-Real-IP; + # proxy_connect_timeout 300; + # proxy_http_version 1.1; + # proxy_set_header Upgrade $http_upgrade; + # proxy_set_header Connection "upgrade"; + # chunked_transfer_encoding off; + # + # proxy_pass http://s3:9000; + # } location / { proxy_set_header Host $http_host; @@ -48,6 +48,21 @@ server { send_timeout 60s; } + # do we need it? it doesnt work without nginx to + location /api/socket.io/ { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_pass http://master-backend-api:10000/; + } + + location /documentation/ { proxy_pass http://mkdocs:8010/; proxy_set_header Host $host;