diff --git a/Dockerfile b/Dockerfile index c804c64..96d2de6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,10 @@ RUN pip install --no-cache-dir -r requirements.txt COPY app.py . COPY templates/ ./templates/ +# Создаем и копируем папку uploads (если нужно предзаполнение) +RUN mkdir -p /app/uploads +COPY uploads/ ./uploads/ + # Экспонируем оба порта EXPOSE 8001 8002 diff --git a/app.py b/app.py index d08d2f5..e83daa8 100644 --- a/app.py +++ b/app.py @@ -7,6 +7,9 @@ import markdown from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse, StreamingResponse import io +import os +from fastapi.middleware.cors import CORSMiddleware +from werkzeug.utils import secure_filename # Создаем два экземпляра FastAPI web_app = FastAPI(title="Web Interface") # Для веб-интерфейса (порт 8001) @@ -59,6 +62,15 @@ if not unsorted_folder: else: unsorted_folder_id = unsorted_folder[0] +# Папка для загрузки изображений +UPLOAD_FOLDER = "/app/uploads" +if not os.path.exists(UPLOAD_FOLDER): + os.makedirs(UPLOAD_FOLDER) + +# Монтируем папку uploads для доступа к изображениям +api_app.mount("/uploads", StaticFiles(directory=UPLOAD_FOLDER), name="uploads") +web_app.mount("/uploads", StaticFiles(directory=UPLOAD_FOLDER), name="uploads") + # Модели данных class Folder(BaseModel): name: str @@ -275,8 +287,30 @@ async def import_csv(file: UploadFile = File(...), folder_id: int | None = Query except Exception as e: raise HTTPException(status_code=400, detail=f"Ошибка импорта: {str(e)}") +# Новый эндпоинт для загрузки изображений +@api_app.post("/api/upload-image") +async def upload_image(install_id: int = Query(..., description="ID записи для ассоциации изображения"), file: UploadFile = File(...)): + if not file.filename: + raise HTTPException(status_code=400, detail="No file provided") + + # Проверка расширения файла + allowed_extensions = {'png', 'jpg', 'jpeg', 'gif'} + if not secure_filename(file.filename).rsplit('.', 1)[1].lower() in allowed_extensions: + raise HTTPException(status_code=400, detail="Invalid file format. Use png, jpg, jpeg, or gif") + + # Генерация безопасного имени файла + filename = secure_filename(f"{install_id}_{file.filename}") + file_path = os.path.join(UPLOAD_FOLDER, filename) + + # Сохранение файла + with open(file_path, "wb") as buffer: + buffer.write(await file.read()) + + # Возвращаем URL изображения + url = f"/uploads/{filename}" + return {"url": url} + # CORS для API -from fastapi.middleware.cors import CORSMiddleware api_app.add_middleware( CORSMiddleware, allow_origins=["http://10.0.0.10:8001", "http://localhost:8001"], diff --git a/docker-compose.yml b/docker-compose.yml index 77426fe..d99585d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,4 +7,5 @@ services: - "8002:8002" # API (доступ для POST-запросов) volumes: - ./db:/db + - ./uploads:/app/uploads # Монтируем папку uploads restart: unless-stopped \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index ff20ead..c5bb776 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ fastapi uvicorn python-multipart -markdown \ No newline at end of file +markdown +werkzeug \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index d55b023..3780598 100644 --- a/templates/index.html +++ b/templates/index.html @@ -267,6 +267,10 @@ background: #222; } #notes-content { margin-top: 10px; } + #notes-content img { + max-width: 100%; /* Ограничение ширины изображений */ + height: auto; + } #note-modal { display: none; position: fixed; @@ -293,6 +297,12 @@ color: #e0e0e0; border: 1px solid #555; } + #note-modal .image-upload { + margin-bottom: 10px; + } + #note-modal .image-upload input { + margin-right: 10px; + } #note-modal button { margin-right: 10px; } #note-modal.dark-theme button { background: #007bff; @@ -407,6 +417,10 @@ +
Нет заметок
'); } @@ -1056,7 +1106,7 @@ function formatItalic() { const textarea = $('#note-textarea'); const start = textarea[0].selectionStart; - const end = textarea[0].selectionEnd; + end = textarea[0].selectionEnd; const text = textarea.val(); const selected = text.substring(start, end); const newText = `*${selected}*`;