Skip to content

Latest commit

 

History

20 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cloud File Storage

Многопользовательское облачное файловое хранилище. Пользователи регистрируются, загружают файлы и папки, перемещают, переименовывают, удаляют, скачивают и ищут их. У каждого пользователя своя изолированная корневая папка — доступа к чужим файлам нет.

Проект выполнен по ТЗ роадмапа Сергея Жукова.

Развёрнутое приложение — http://194.226.142.85:8080/

Стек

Слой Технология
Backend Django 6, Django REST Framework
База данных PostgreSQL 16
Хранилище файлов MinIO (S3-совместимое), boto3
Сессии Redis 7
Раздача фронтенда WhiteNoise
Frontend React (готовый собранный бандл)
Инфраструктура Docker Compose
Тесты Django TestCase, django-test-plus

Архитектура

Приложение запускается напрямую через Python. В Docker Compose поднимается только инфраструктура — Postgres, Redis и MinIO. Такое разделение задано ТЗ.

Работа с S3 вынесена в files/service.py — это единственное место, где проект обращается к хранилищу. Views принимают запрос, вызывают сервис и возвращают ответ; знания о формате ключей в них нет.

Файлы хранятся в бакете user-files. Внутри бакета у каждого пользователя своя корневая папка вида user-{id}-files/, где id — идентификатор из базы. Файл docs/test.txt пользователя с id 1 лежит по ключу user-1-files/docs/test.txt.

Бакеты создаются автоматически при первом запуске стека — за это отвечает сервис minio-init. Он поднимает боевой user-files и user-files-test для тестов. Корневая папка пользователя создаётся при регистрации, в одной транзакции с записью в базу: если MinIO недоступен, пользователь в базе не появится.

Админка Django из проекта убрана — ни одна модель в ней не регистрировалась.

Требования

  • Python 3.14 — версия, на которой проект разрабатывался
  • Docker и Docker Compose

Запуск

1. Клонировать репозиторий

git clone https://github.com/gomode13/cloud-file-storage.git
cd cloud-file-storage

2. Создать виртуальное окружение и установить зависимости

python -m venv venv
venv\Scripts\activate        # Windows
source venv/bin/activate     # Linux / macOS
pip install -r requirements.txt

3. Создать файл .env

Скопировать .env.example в .env и заполнить значения — см. таблицу ниже. Опциональные переменные можно оставить пустыми, тогда подставятся значения по умолчанию.

Для локальной работы по HTTP нужно указать SESSION_COOKIE_SECURE=False, иначе браузер не сохранит сессионную cookie.

4. Поднять инфраструктуру

docker compose up -d

Поднимутся Postgres, Redis и MinIO. Сервис minio-init дождётся готовности MinIO, создаст бакеты и завершится — так и задумано.

5. Применить миграции

python manage.py migrate

6. Запустить сервер

python manage.py runserver

Приложение — на http://127.0.0.1:8000/, веб-консоль MinIO — на http://127.0.0.1:9001/.

Переменные окружения

Переменная Назначение Обязательна
SECRET_KEY Секретный ключ Django да
DEBUG Режим отладки, True или False да
ALLOWED_HOSTS Разрешённые хосты через запятую да
POSTGRES_DB Имя базы данных да
POSTGRES_USER Пользователь базы да
POSTGRES_PASSWORD Пароль базы да
MINIO_ROOT_USER Пользователь MinIO да
MINIO_ROOT_PASSWORD Пароль MinIO да
REDIS_PASSWORD Пароль Redis да
POSTGRES_HOST Хост базы, по умолчанию 127.0.0.1 нет
REDIS_HOST Хост Redis, по умолчанию 127.0.0.1 нет
MINIO_HOST Хост MinIO, по умолчанию 127.0.0.1 нет
S3_BUCKET Имя бакета, по умолчанию user-files нет
SESSION_COOKIE_SECURE Флаг Secure у сессионной cookie, по умолчанию True нет
MAX_FILE_SIZE Максимальный размер одного файла в байтах, по умолчанию 100 МБ нет
USER_QUOTA Квота на пользователя в байтах, по умолчанию 1 ГБ нет
MAX_UPLOAD_FILES Максимум файлов в одном запросе, по умолчанию 1000 нет

Если значение переменной в .env пустое, подставляется значение по умолчанию — пустая строка не считается заданным значением.

API

Все эндпоинты — под общим путём /api. Авторизация по сессии в cookie. Формат запросов и ответов — JSON, кроме загрузки и скачивания файлов.

Аутентификация

Метод Путь Описание
POST /api/auth/sign-up Регистрация, сразу создаёт сессию
POST /api/auth/sign-in Вход
POST /api/auth/sign-out Выход
GET /api/user/me Текущий пользователь

Ресурсы

Метод Путь Описание
GET /api/resource?path= Информация о файле или папке
DELETE /api/resource?path= Удаление
POST /api/resource?path= Загрузка файлов, тело multipart/form-data
GET /api/resource/download?path= Скачивание, папка отдаётся zip-архивом
POST /api/resource/move?from=&to= Перемещение или переименование
GET /api/resource/search?query= Поиск по имени
GET /api/directory?path= Содержимое папки, не рекурсивно
POST /api/directory?path= Создание пустой папки

Путь к папке обязан заканчиваться на / — это отличает папку от файла с тем же именем.

При загрузке path читается из query-параметра, а при его отсутствии — из поля формы: фронтенд передаёт путь телом запроса, ТЗ описывает query-параметр, поддерживаются оба варианта. Пустой path означает корневую папку и допустим только при загрузке и в /api/directory; для операций над конкретным ресурсом пустой путь отклоняется с кодом 400.

Формат ответа

Ресурс:

{
  "path": "folder1/folder2/",
  "name": "file.txt",
  "size": 123,
  "type": "FILE"
}

Поле size отсутствует у папок, type принимает значения FILE и DIRECTORY.

Ошибка:

{
  "message": "Текст ошибки"
}

Коды: 400 — невалидные данные, 401 — не авторизован, 404 — не найдено, 409 — конфликт, 500 — неизвестная ошибка.

Загрузка папок

Браузер передаёт относительный путь каждого файла внутри имени, например upload_folder/sub/b.txt. Django срезает из имени всё до последнего слэша — в двух местах, в разборе тела запроса и при создании объекта файла. Из-за этого вложенные папки загружались бы плоско в корень, а одноимённые файлы из разных подпапок затирали бы друг друга.

Проект переопределяет разбор имени файла в files/apps.py: сегменты пути склеиваются символом, который переживает срезание, а в files/views.py из них восстанавливается исходный путь. Сегменты .., . и пустые отклоняются — файл с таким именем не загружается.

Модель пользователя

Модель собрана на AbstractBaseUser и PermissionsMixin, а не на AbstractUser. Причина: по ТЗ нужны только логин и пароль, а AbstractUser тянет first_name, last_name, email и date_joined — поля, которые в этом проекте всегда оставались бы пустыми.

Поле username объявлено с unique=True, что создаёт уникальный индекс: уникальность логина обеспечивает база, а не только проверка в коде. Гонка при одновременной регистрации ловится через IntegrityError и возвращает 409.

Валидация и ограничения

Валидация путей вынесена в сериализаторы files/serializers.py, а не размазана по views. Отклоняются пути с сегментом .., с пустыми сегментами и с обратными слэшами.

  • Максимальный размер одного файла — MAX_FILE_SIZE, по умолчанию 100 МБ
  • Квота на пользователя — USER_QUOTA, по умолчанию 1 ГБ
  • Максимум файлов в одном запросе — MAX_UPLOAD_FILES, по умолчанию 1000
  • Поиск возвращает не более 100 результатов
  • Регистрация — не чаще 5 запросов в минуту, вход — не чаще 10

Безопасность

  • Сессионная cookie помечена SameSite=Strict и HttpOnly; флаг Secure включён по умолчанию и снимается переменной SESSION_COOKIE_SECURE — только если сервер работает по HTTP без TLS
  • Скачивание отдаётся с Content-Type: application/octet-stream, чтобы браузер не открывал загруженные пользователем .html и .svg прямо на домене приложения
  • Порты Postgres, Redis и MinIO в Docker Compose привязаны к 127.0.0.1 и наружу не публикуются
  • Redis требует пароль
  • Секреты вынесены в .env, в репозитории только .env.example

Логирование

Логи пишутся в консоль и в файл logs/app.log с ротацией: 10 МБ на файл, 5 архивов. Формат строки — время, уровень, имя модуля, сообщение.

Ошибки хранилища записываются вместе с исходным исключением и трейсбеком до того, как будут заменены на ответ клиенту. Это позволяет разбирать сбои при DEBUG=False, когда трейсбек в ответ не попадает.

Скачивание папок

Папка отдаётся zip-архивом. Архив собирается во временный файл, а каждый объект переливается из MinIO потоком, без чтения целиком в память, — размер папки не ограничен объёмом оперативной памяти.

Тесты

python manage.py test

Тесты изолированы от боевого окружения: кэш переключается на локальный вместо Redis, ограничения на частоту запросов снимаются, файлы пишутся в отдельный бакет user-files-test.

Тесты users работают только с базой — создание папки в хранилище подменяется заглушкой, запущенный MinIO им не нужен. Тесты files интеграционные, MinIO для них обязателен.

Деплой

Приложение развёрнуто вручную на облачном сервере с Linux.

1. Установить на сервер Python, Docker и Docker Compose

2. Скопировать проект и создать .env

Отличия от локального окружения:

DEBUG=False
ALLOWED_HOSTS=194.226.142.85
SESSION_COOKIE_SECURE=False

SESSION_COOKIE_SECURE=False нужен потому, что приложение работает по HTTP без TLS. При появлении сертификата переменную следует убрать.

3. Поднять инфраструктуру

docker compose up -d

4. Установить зависимости и применить миграции

pip install -r requirements.txt
python manage.py migrate

5. Запустить приложение

gunicorn config.wsgi:application --bind 0.0.0.0:8080 --workers 3

manage.py runserver — сервер для разработки, на боевом окружении не используется.

Приложение доступно на http://194.226.142.85:8080/

Структура проекта

config/         настройки, корневые маршруты, обработчик исключений
files/          работа с файлами: views, сериализаторы, сервисный слой S3
users/          регистрация, вход, модель пользователя, сервисный слой
frontend/dist/  собранный React-фронтенд
logs/           логи приложения

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages