Многопользовательское облачное файловое хранилище. Пользователи регистрируются, загружают файлы и папки, перемещают, переименовывают, удаляют, скачивают и ищут их. У каждого пользователя своя изолированная корневая папка — доступа к чужим файлам нет.
Проект выполнен по ТЗ роадмапа Сергея Жукова.
Развёрнутое приложение — 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-storage2. Создать виртуальное окружение и установить зависимости
python -m venv venv
venv\Scripts\activate # Windows
source venv/bin/activate # Linux / macOS
pip install -r requirements.txt3. Создать файл .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 migrate6. Запустить сервер
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. Авторизация по сессии в 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 -d4. Установить зависимости и применить миграции
pip install -r requirements.txt
python manage.py migrate5. Запустить приложение
gunicorn config.wsgi:application --bind 0.0.0.0:8080 --workers 3manage.py runserver — сервер для разработки, на боевом окружении не используется.
Приложение доступно на http://194.226.142.85:8080/
config/ настройки, корневые маршруты, обработчик исключений
files/ работа с файлами: views, сериализаторы, сервисный слой S3
users/ регистрация, вход, модель пользователя, сервисный слой
frontend/dist/ собранный React-фронтенд
logs/ логи приложения