diff --git a/CHANGELOG.md b/CHANGELOG.md index feab610..513eca3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,92 @@ +## [0.5.0] - 2025-09-01 + +### Added +- 🔧 **JWT декодирование** с поддержкой jsonwebtoken crate для работы с сессионными токенами +- 🔧 **Прямая интеграция с Redis** для получения данных пользователя из сессий вместо внешних API +- 🔧 Автоматическое обновление `last_activity` при каждом запросе к / +- 📝 Поддержка переменной окружения JWT_SECRET для конфигурации ключа декодирования +- 📝 Валидация сессий через Redis TTL и проверка expiration в JWT +- 🚀 **HTTP кэширование** с ETag и Cache-Control заголовками для статических файлов +- 🚀 **Оптимизация proxy_handler** - добавлена поддержка 304 Not Modified ответов +- 📊 **Метрики производительности** - timing логирование для всех запросов файлов + +### Changed +- 🔄 **Кардинальное изменение архитектуры GET /**: переход от GraphQL API к Redis сессиям +- 🔄 Структура данных Author теперь содержит session-данные: user_id, username, token_type, created_at, last_activity, auth_data, device_info +- 📝 Обновлена документация API с новой структурой ответа на основе Redis сессий +- 🔧 Функция get_user_by_token теперь принимает параметр redis: &mut MultiplexedConnection + +### Removed +- 🗑️ **Удалена legacy OpenGraph overlay логика** - теперь обрабатывается пакетом Vercel +- 🗑️ Удален файл `src/overlay.rs` с функциями генерации overlay +- 🗑️ Удален файл `src/core.rs` с GraphQL запросами для shout +- 🗑️ Удален файл шрифта `src/Muller-Regular.woff2` +- 🗑️ Удалены зависимости: `imageproc`, `ab_glyph` +- 🗑️ Удален параметр `s=` из GET запросов файлов +- 🗑️ Упрощена функция serve_file - убран параметр shout_id + +### Technical Details +- Redis key pattern: `session:{user_id}:{token}` +- JWT claims structure: `{ user_id, username, exp?, iat? }` +- Session data включает метаданные устройства и авторизации в JSON формате +- Automatic last_activity updates для tracking активности пользователей +- OpenGraph overlay теперь полностью вынесен в отдельный Vercel пакет + +### Status +- 🧪 tests: требуется обновление тестов для новой Redis-based архитектуры +- 🚀 deploy: требует настройки JWT_SECRET environment variable + +## [0.6.0] - 2025-01-28 + +### Added +- 👤 **Новый endpoint GET /** для получения информации о текущем пользователе +- 👤 Интеграция с внешним сервисом аутентификации для получения полных данных профиля +- 👤 Автоматическое включение данных квоты в ответ о пользователе (current_quota, max_quota, usage_percentage) +- 📝 Поддержка Bearer token в заголовке Authorization с автоматическим парсингом +- 📝 Детальная обработка ошибок для невалидных/устаревших токенов + +### Changed +- 📝 Обновлена документация API с новым endpoint / +- 📝 Добавлена детальная документация структуры пользователя в upload-api-detailed.md +- 🔄 Улучшена архитектура auth.rs с новыми структурами для пользователей + +### Status +- 🧪 tests: требуется добавление тестов для нового endpoint +- 🚀 deploy: готово к продакшену, обратная совместимость сохранена + +## [0.5.0] - 2025-01-28 + +### Added +- 🔄 Улучшенная логика загрузки файлов с streaming обработкой и проверкой квот во время чтения +- 📝 Лимит размера одного файла: 500 МБ для предотвращения перегрузки памяти +- 📝 Поддержка множественных файлов в одном запросе с детальными ответами +- 📝 Предварительная проверка квоты перед началом загрузки +- 📝 Улучшенное логирование с процентом использования квоты + +### Fixed +- 🧪 Правильный HTTP код ошибки для превышения квоты: 413 Payload Too Large вместо 401 Unauthorized +- 🔄 Эффективное использование памяти: streaming вместо полного чтения файла в память +- 🔄 Пропуск пустых файлов с соответствующими сообщениями об ошибках +- 📝 Улучшенная обработка ошибок Redis без прерывания загрузки + +### Changed +- 📝 Обновлена документация API с точными кодами ошибок и лимитами +- 📝 Создан детальный документ API с описанием улучшений (`docs/upload-api-detailed.md`) +- 🔄 Реструктурирована логика валидации токенов с детальными сообщениями об ошибках + +### Status +- 🧪 tests: требуется обновление для новой логики множественных файлов +- 🚀 deploy: значительные улучшения производительности и надежности + +## [0.4.1] - 2025-08-12 + +### Fixed +- 🧪 Линтинг: подавлены предупреждения о неиспользуемых полях в `src/core.rs` через `#[allow(dead_code)]` на структурах `ShoutTopic`, `ShoutAuthor`, `Shout` для прохождения `cargo clippy -D warnings` в CI + +### Status +- 🧪 tests: все тесты проходят локально (36/36) +- 🚀 deploy: без изменений в логике, безопасно для деплоя + ## [0.4.0] - 2025-01-27 ### Added diff --git a/Cargo.lock b/Cargo.lock index b4a46a4..ce32f9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,22 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "ab_glyph" -version = "0.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" -dependencies = [ - "ab_glyph_rasterizer", - "owned_ttf_parser", -] - -[[package]] -name = "ab_glyph_rasterizer" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" - [[package]] name = "actix" version = "0.13.5" @@ -27,7 +11,7 @@ dependencies = [ "actix-macros", "actix-rt", "actix_derive", - "bitflags 2.6.0", + "bitflags", "bytes", "crossbeam-channel", "futures-core", @@ -49,7 +33,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.6.0", + "bitflags", "bytes", "futures-core", "futures-sink", @@ -86,7 +70,7 @@ dependencies = [ "actix-service", "actix-utils", "base64 0.22.1", - "bitflags 2.6.0", + "bitflags", "brotli", "bytes", "bytestring", @@ -189,9 +173,9 @@ dependencies = [ [[package]] name = "actix-server" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca2549781d8dd6d75c40cf6b6051260a2cc2f3c62343d761a969a0640646894" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" dependencies = [ "actix-rt", "actix-service", @@ -227,9 +211,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.9.0" +version = "4.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9180d76e5cc7ccbc4d60a506f2c727730b154010262df5b910eb17dbe4b8cb38" +checksum = "a597b77b5c6d6a1e1097fddde329a83665e25c5437c696a3a9a4aa514a614dea" dependencies = [ "actix-codec", "actix-http", @@ -240,13 +224,13 @@ dependencies = [ "actix-service", "actix-utils", "actix-web-codegen", - "ahash", "bytes", "bytestring", "cfg-if", "cookie", - "derive_more 0.99.18", + "derive_more 2.0.1", "encoding_rs", + "foldhash", "futures-core", "futures-util", "impl-more", @@ -264,6 +248,7 @@ dependencies = [ "smallvec", "socket2 0.5.7", "time", + "tracing", "url", ] @@ -305,19 +290,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "getrandom 0.2.15", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -424,15 +396,6 @@ version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", -] - [[package]] name = "arbitrary" version = "1.4.1" @@ -484,18 +447,18 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.2" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" dependencies = [ "arrayvec", ] [[package]] name = "aws-config" -version = "1.5.10" +version = "1.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b49afaa341e8dd8577e1a2200468f98956d6eda50bcf4a53246cc00174ba924" +checksum = "8bc1b40fb26027769f16960d2f4a6bc20c4bb755d403e552c8c1a73af433c246" dependencies = [ "aws-credential-types", "aws-runtime", @@ -512,7 +475,7 @@ dependencies = [ "bytes", "fastrand", "hex", - "http 0.2.12", + "http 1.1.0", "ring", "time", "tokio", @@ -523,9 +486,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.1" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60e8f6b615cb5fc60a98132268508ad104310f0cfb25a1c22eee76efdf9154da" +checksum = "d025db5d9f52cbc413b167136afb3d8aeea708c0d8884783cf6253be5e22f6f2" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -534,10 +497,33 @@ dependencies = [ ] [[package]] -name = "aws-runtime" -version = "1.4.3" +name = "aws-lc-rs" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a10d5c055aa540164d9561a0e2e74ad30f0dcf7393c3a92f6733ddf9c5762468" +checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "aws-runtime" +version = "1.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c034a1bc1d70e16e7f4e4caf7e9f7693e4c9c24cd91cf17c2a0b21abaebc7c8b" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -552,7 +538,6 @@ dependencies = [ "fastrand", "http 0.2.12", "http-body 0.4.6", - "once_cell", "percent-encoding", "pin-project-lite", "tracing", @@ -561,9 +546,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.60.0" +version = "1.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0506cc60e392e33712d47717d5ae5760a3b134bf8ee7aea7e43df3d7e2669ae0" +checksum = "38c488cd6abb0ec9811c401894191932e941c5f91dc226043edacd0afa1634bc" dependencies = [ "aws-credential-types", "aws-runtime", @@ -583,9 +568,9 @@ dependencies = [ "hex", "hmac", "http 0.2.12", + "http 1.1.0", "http-body 0.4.6", "lru", - "once_cell", "percent-encoding", "regex-lite", "sha2", @@ -595,9 +580,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.49.0" +version = "1.83.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09677244a9da92172c8dc60109b4a9658597d4d298b188dd0018b6a66b410ca4" +checksum = "643cd43af212d2a1c4dedff6f044d7e1961e5d9e7cfe773d70f31d9842413886" dependencies = [ "aws-credential-types", "aws-runtime", @@ -609,17 +594,17 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", + "fastrand", "http 0.2.12", - "once_cell", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-ssooidc" -version = "1.50.0" +version = "1.84.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fea2f3a8bb3bd10932ae7ad59cc59f65f270fc9183a7e91f501dc5efbef7ee" +checksum = "20ec4a95bd48e0db7a424356a161f8d87bd6a4f0af37204775f0da03d9e39fc3" dependencies = [ "aws-credential-types", "aws-runtime", @@ -631,17 +616,17 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", + "fastrand", "http 0.2.12", - "once_cell", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-sts" -version = "1.49.0" +version = "1.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53dcf5e7d9bd1517b8b998e170e650047cea8a2b85fe1835abe3210713e541b7" +checksum = "410309ad0df4606bc721aff0d89c3407682845453247213a0ccc5ff8801ee107" dependencies = [ "aws-credential-types", "aws-runtime", @@ -654,17 +639,17 @@ dependencies = [ "aws-smithy-types", "aws-smithy-xml", "aws-types", + "fastrand", "http 0.2.12", - "once_cell", "regex-lite", "tracing", ] [[package]] name = "aws-sigv4" -version = "1.2.5" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5619742a0d8f253be760bfbb8e8e8368c69e3587e4637af5754e488a611499b1" +checksum = "084c34162187d39e3740cb635acd73c4e3a551a36146ad6fe8883c929c9f876c" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", @@ -678,7 +663,6 @@ dependencies = [ "hmac", "http 0.2.12", "http 1.1.0", - "once_cell", "p256", "percent-encoding", "ring", @@ -691,9 +675,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.1" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c" +checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c" dependencies = [ "futures-util", "pin-project-lite", @@ -702,15 +686,14 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.60.13" +version = "0.63.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1a71073fca26775c8b5189175ea8863afb1c9ea2cceb02a5de5ad9dfbaa795" +checksum = "56d2df0314b8e307995a3b86d44565dfe9de41f876901a7d71886c756a25979f" dependencies = [ "aws-smithy-http", "aws-smithy-types", "bytes", - "crc32c", - "crc32fast", + "crc-fast", "hex", "http 0.2.12", "http-body 0.4.6", @@ -723,9 +706,9 @@ dependencies = [ [[package]] name = "aws-smithy-eventstream" -version = "0.60.5" +version = "0.60.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef7d0a272725f87e51ba2bf89f8c21e4df61b9e49ae1ac367a6d69916ef7c90" +checksum = "182b03393e8c677347fb5705a04a9392695d47d20ef0a2f8cfe28c8e6b9b9778" dependencies = [ "aws-smithy-types", "bytes", @@ -734,9 +717,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.60.11" +version = "0.62.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8bc3e8fdc6b8d07d976e301c02fe553f72a39b7a9fea820e023268467d7ab6" +checksum = "7c4dacf2d38996cf729f55e7a762b30918229917eca115de45dfa8dfb97796c9" dependencies = [ "aws-smithy-eventstream", "aws-smithy-runtime-api", @@ -745,8 +728,8 @@ dependencies = [ "bytes-utils", "futures-core", "http 0.2.12", + "http 1.1.0", "http-body 0.4.6", - "once_cell", "percent-encoding", "pin-project-lite", "pin-utils", @@ -754,14 +737,53 @@ dependencies = [ ] [[package]] -name = "aws-smithy-json" -version = "0.60.7" +name = "aws-smithy-http-client" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6" +checksum = "147e8eea63a40315d704b97bf9bc9b8c1402ae94f89d5ad6f7550d963309da1b" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.3.26", + "h2 0.4.12", + "http 0.2.12", + "http 1.1.0", + "http-body 0.4.6", + "hyper 0.14.31", + "hyper 1.7.0", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.3", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.31", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.2", + "tower", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaa31b350998e703e9826b2104dd6f63be0508666e1aba88137af060e8944047" dependencies = [ "aws-smithy-types", ] +[[package]] +name = "aws-smithy-observability" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9364d5989ac4dd918e5cc4c4bdcc61c9be17dcd2586ea7f69e348fc7c6cab393" +dependencies = [ + "aws-smithy-runtime-api", +] + [[package]] name = "aws-smithy-query" version = "0.60.7" @@ -774,36 +796,33 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.7.3" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be28bd063fa91fd871d131fc8b68d7cd4c5fa0869bea68daca50dcb1cbd76be2" +checksum = "d3946acbe1ead1301ba6862e712c7903ca9bb230bdf1fbd1b5ac54158ef2ab1f" dependencies = [ "aws-smithy-async", "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", "aws-smithy-runtime-api", "aws-smithy-types", "bytes", "fastrand", - "h2 0.3.26", "http 0.2.12", + "http 1.1.0", "http-body 0.4.6", "http-body 1.0.1", - "httparse", - "hyper 0.14.31", - "hyper-rustls 0.24.2", - "once_cell", "pin-project-lite", "pin-utils", - "rustls 0.21.12", "tokio", "tracing", ] [[package]] name = "aws-smithy-runtime-api" -version = "1.7.3" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" +checksum = "07f5e0fc8a6b3f2303f331b94504bbf754d85488f402d6f1dd7a6080f99afe56" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -818,9 +837,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.2.9" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510" +checksum = "d498595448e43de7f4296b7b7a18a8a02c61ec9349128c80a368f7c3b4ab11a8" dependencies = [ "base64-simd", "bytes", @@ -844,18 +863,18 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.9" +version = "0.60.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +checksum = "3db87b96cb1b16c024980f133968d52882ca0daaee3a086c6decc500f6c99728" dependencies = [ "xmlparser", ] [[package]] name = "aws-types" -version = "1.3.3" +version = "1.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5221b91b3e441e6675310829fd8984801b772cb1546ef6c0e54dec9f1ac13fef" +checksum = "b069d19bf01e46298eaedd7c6f283fe565a59263e53eebec945f3e6398f42390" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -914,18 +933,35 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + [[package]] name = "bit_field" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.6.0" @@ -1000,9 +1036,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bytes-utils" @@ -1034,6 +1070,15 @@ dependencies = [ "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfb" version = "0.7.3" @@ -1076,6 +1121,26 @@ dependencies = [ "windows-link", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -1135,6 +1200,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1151,12 +1226,31 @@ dependencies = [ ] [[package]] -name = "crc32c" -version = "0.6.8" +name = "crc" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ - "rustc_version", + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc-fast" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf62af4cc77d8fe1c22dde4e721d87f2f54056139d8c412e1366b740305f56f" +dependencies = [ + "crc", + "digest", + "libc", + "rand 0.9.1", + "regex", ] [[package]] @@ -1370,6 +1464,12 @@ dependencies = [ "syn", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ecdsa" version = "0.14.8" @@ -1429,14 +1529,14 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.5" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff", "log", ] @@ -1473,9 +1573,29 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "fdeflate" @@ -1510,9 +1630,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.34" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -1554,6 +1674,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.31" @@ -1694,6 +1820,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "group" version = "0.12.1" @@ -1726,9 +1858,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -1791,6 +1923,15 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "hostname" version = "0.4.0" @@ -1870,12 +2011,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" version = "0.14.31" @@ -1902,19 +2037,21 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", - "h2 0.4.6", + "futures-core", + "h2 0.4.12", "http 1.1.0", "http-body 1.0.1", "httparse", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -1931,7 +2068,7 @@ dependencies = [ "hyper 0.14.31", "log", "rustls 0.21.12", - "rustls-native-certs", + "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", ] @@ -1944,12 +2081,13 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.5.0", + "hyper 1.7.0", "hyper-util", - "rustls 0.23.16", + "rustls 0.23.31", + "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls 0.26.2", "tower-service", ] @@ -1961,7 +2099,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.0", + "hyper 1.7.0", "hyper-util", "native-tls", "tokio", @@ -1971,21 +2109,28 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.5.0", + "hyper 1.7.0", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.6.0", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2159,9 +2304,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.5" +version = "0.25.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +checksum = "1c6a3ce16143778e24df6f95365f12ed105425b22abefd289dd88a64bab59605" dependencies = [ "bytemuck", "byteorder-lite", @@ -2169,6 +2314,7 @@ dependencies = [ "exr", "gif", "image-webp", + "moxcms", "num-traits", "png", "qoi", @@ -2190,24 +2336,6 @@ dependencies = [ "quick-error", ] -[[package]] -name = "imageproc" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2393fb7808960751a52e8a154f67e7dd3f8a2ef9bd80d1553078a7b4e8ed3f0d" -dependencies = [ - "ab_glyph", - "approx", - "getrandom 0.2.15", - "image", - "itertools", - "nalgebra", - "num", - "rand 0.8.5", - "rand_distr", - "rayon", -] - [[package]] name = "imgref" version = "1.11.0" @@ -2250,12 +2378,33 @@ dependencies = [ "syn", ] +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "ipnet" version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -2277,6 +2426,30 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "jobserver" version = "0.1.32" @@ -2287,18 +2460,28 @@ dependencies = [ ] [[package]] -name = "jpeg-decoder" -version = "0.3.1" +name = "js-sys" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] [[package]] -name = "js-sys" -version = "0.3.72" +name = "jsonwebtoken" +version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ - "wasm-bindgen", + "base64 0.22.1", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", ] [[package]] @@ -2322,6 +2505,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "lebe" version = "0.5.2" @@ -2345,10 +2534,14 @@ dependencies = [ ] [[package]] -name = "libm" -version = "0.2.11" +name = "libloading" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets", +] [[package]] name = "linux-raw-sys" @@ -2413,16 +2606,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "matrixmultiply" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" -dependencies = [ - "autocfg", - "rawpointer", -] - [[package]] name = "maybe-rayon" version = "0.1.1" @@ -2473,9 +2656,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", @@ -2494,27 +2677,22 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "moxcms" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "mutate_once" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" -[[package]] -name = "nalgebra" -version = "0.32.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" -dependencies = [ - "approx", - "matrixmultiply", - "num-complex", - "num-rational", - "num-traits", - "simba", - "typenum", -] - [[package]] name = "native-tls" version = "0.2.12" @@ -2527,7 +2705,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -2554,20 +2732,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - [[package]] name = "num-bigint" version = "0.4.6" @@ -2578,15 +2742,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - [[package]] name = "num-conv" version = "0.1.0" @@ -2613,17 +2768,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.2" @@ -2642,7 +2786,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -2656,9 +2799,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" @@ -2666,7 +2809,7 @@ version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags 2.6.0", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -2721,15 +2864,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" -[[package]] -name = "owned_ttf_parser" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" -dependencies = [ - "ttf-parser", -] - [[package]] name = "p256" version = "0.11.1" @@ -2776,6 +2910,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64 0.22.1", + "serde", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2821,17 +2965,32 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "png" -version = "0.17.14" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" dependencies = [ - "bitflags 1.3.2", + "bitflags", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2848,10 +3007,20 @@ dependencies = [ ] [[package]] -name = "proc-macro2" -version = "1.0.89" +name = "prettyplease" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -2875,6 +3044,15 @@ dependencies = [ "syn", ] +[[package]] +name = "pxfm" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e790881194f6f6e86945f0a42a6981977323669aeb6c40e9c7ec253133b96f8" +dependencies = [ + "num-traits", +] + [[package]] name = "qoi" version = "0.4.1" @@ -2892,30 +3070,30 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "quoter" -version = "0.3.0" +version = "0.5.0" dependencies = [ - "ab_glyph", "actix", "actix-cors", "actix-multipart", "actix-web", "aws-config", "aws-sdk-s3", + "base64 0.22.1", "chrono", "env_logger", "futures", "image", - "imageproc", "infer", + "jsonwebtoken", "kamadak-exif", "log", "mime_guess", @@ -2995,16 +3173,6 @@ dependencies = [ "getrandom 0.3.3", ] -[[package]] -name = "rand_distr" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" -dependencies = [ - "num-traits", - "rand 0.8.5", -] - [[package]] name = "rav1e" version = "0.7.1" @@ -3042,9 +3210,9 @@ dependencies = [ [[package]] name = "ravif" -version = "0.11.11" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" +checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" dependencies = [ "avif-serialize", "imgref", @@ -3055,12 +3223,6 @@ dependencies = [ "rgb", ] -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - [[package]] name = "rayon" version = "1.10.0" @@ -3083,9 +3245,9 @@ dependencies = [ [[package]] name = "redis" -version = "0.32.4" +version = "0.32.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1f66bf4cac9733a23bcdf1e0e01effbaaad208567beba68be8f67e5f4af3ee1" +checksum = "7cd3650deebc68526b304898b192fa4102a4ef0b9ada24da096559cb60e0eef8" dependencies = [ "bytes", "cfg-if", @@ -3109,7 +3271,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 2.6.0", + "bitflags", ] [[package]] @@ -3149,9 +3311,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.9" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64 0.22.1", "bytes", @@ -3159,36 +3321,34 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.6", + "h2 0.4.12", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.7.0", "hyper-rustls 0.27.3", "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile 2.2.0", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", "tokio-native-tls", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows-registry", ] [[package]] @@ -3229,6 +3389,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.1" @@ -3244,7 +3410,7 @@ version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ - "bitflags 2.6.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -3265,13 +3431,14 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.16" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ + "aws-lc-rs", "once_cell", "rustls-pki-types", - "rustls-webpki 0.102.8", + "rustls-webpki 0.103.4", "subtle", "zeroize", ] @@ -3285,7 +3452,19 @@ dependencies = [ "openssl-probe", "rustls-pemfile 1.0.4", "schannel", - "security-framework", + "security-framework 2.11.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.3.0", ] [[package]] @@ -3327,30 +3506,28 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" -[[package]] -name = "safe_arch" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" -dependencies = [ - "bytemuck", -] - [[package]] name = "schannel" version = "0.1.26" @@ -3396,8 +3573,21 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.6.0", - "core-foundation", + "bitflags", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -3405,9 +3595,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -3516,7 +3706,7 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fac841c7050aa73fc2bec8f7d8e9cb1159af0b3095757b99820823f3e54e5080" dependencies = [ - "bitflags 2.6.0", + "bitflags", "sentry-backtrace", "sentry-core", "tracing-core", @@ -3542,18 +3732,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.215" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -3562,9 +3752,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", "memchr", @@ -3655,19 +3845,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "simba" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" -dependencies = [ - "approx", - "num-complex", - "num-traits", - "paste", - "wide", -] - [[package]] name = "simd-adler32" version = "0.3.7" @@ -3683,6 +3860,18 @@ dependencies = [ "quote", ] +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.12", + "time", +] + [[package]] name = "slab" version = "0.4.9" @@ -3754,9 +3943,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.87" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -3789,8 +3978,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.6.0", - "core-foundation", + "bitflags", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3878,13 +4067,16 @@ dependencies = [ [[package]] name = "tiff" -version = "0.9.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" dependencies = [ + "fax", "flate2", - "jpeg-decoder", + "half", + "quick-error", "weezl", + "zune-jpeg", ] [[package]] @@ -3930,20 +4122,22 @@ dependencies = [ [[package]] name = "tokio" -version = "1.45.1" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.7", + "slab", + "socket2 0.6.0", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3979,12 +4173,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.16", - "rustls-pki-types", + "rustls 0.23.31", "tokio", ] @@ -4035,6 +4228,45 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -4089,12 +4321,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "ttf-parser" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5902c5d130972a0000f60860bfbf46f7ca3db5391eddfedd1b8728bd9dc96c0e" - [[package]] name = "typenum" version = "1.17.0" @@ -4166,9 +4392,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -4208,12 +4434,14 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.11.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.3.3", + "js-sys", "serde", + "wasm-bindgen", ] [[package]] @@ -4283,24 +4511,24 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -4321,9 +4549,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4331,9 +4559,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -4344,9 +4572,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" @@ -4378,18 +4609,20 @@ dependencies = [ [[package]] name = "weezl" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" [[package]] -name = "wide" -version = "0.7.28" +name = "which" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ - "bytemuck", - "safe_arch", + "either", + "home", + "once_cell", + "rustix", ] [[package]] @@ -4442,8 +4675,8 @@ dependencies = [ "windows-implement", "windows-interface", "windows-link", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-result", + "windows-strings", ] [[package]] @@ -4476,22 +4709,13 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-registry" -version = "0.2.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -4503,16 +4727,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets", -] - [[package]] name = "windows-strings" version = "0.4.2" @@ -4619,7 +4833,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.6.0", + "bitflags", ] [[package]] @@ -4779,9 +4993,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.4.13" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +checksum = "fc1f7e205ce79eb2da3cd71c5f55f3589785cb7c79f6a03d1c8d1491bda5d089" dependencies = [ "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index bb2140f..5e5b253 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,35 +1,35 @@ [package] name = "quoter" -version = "0.3.0" +version = "0.5.0" edition = "2024" [dependencies] futures = "0.3.30" -serde_json = "1.0.115" -actix-web = "4.5.1" +serde_json = "1.0.143" +actix-web = "4.11.0" actix-cors = "0.7.0" -reqwest = { version = "0.12.3", features = ["json"] } +reqwest = { version = "0.12.23", features = ["json"] } sentry = { version = "0.42", features = ["tokio"] } -uuid = { version = "1.8.0", features = ["v4"] } -redis = { version = "0.32", features = ["tokio-comp"] } -tokio = { version = "1.37.0", features = ["full"] } -serde = { version = "1.0.209", features = ["derive"] } +uuid = { version = "1.18.0", features = ["v4"] } +redis = { version = "0.32.5", features = ["tokio-comp"] } +tokio = { version = "1.47.1", features = ["full"] } +serde = { version = "1.0.219", features = ["derive"] } sentry-actix = "0.42" -aws-sdk-s3 = "1.47.0" -image = "0.25.2" +aws-sdk-s3 = "1.104.0" +image = "0.25.7" mime_guess = "2.0.5" -aws-config = "1.5.5" +aws-config = "1.8.6" actix-multipart = "0.7.2" log = "0.4.22" -env_logger = "0.11.5" +env_logger = "0.11.8" actix = "0.13.5" -imageproc = "0.25.0" -ab_glyph = "0.2.29" # libheif-sys = "1.12.0" -once_cell = "1.18" +once_cell = "1.21.3" kamadak-exif = "0.6.1" infer = "0.19.0" chrono = { version = "0.4", features = ["serde"] } +jsonwebtoken = "9.2.0" +base64 = "0.22.1" [[bin]] name = "quoter" diff --git a/docs/api-reference.md b/docs/api-reference.md index bd5ee74..6e93dce 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -52,27 +52,56 @@ filename.ext ``` **Ошибки:** -- `401 Unauthorized` - неверный токен -- `413 Payload Too Large` - превышена квота +- `400 Bad Request` - нет файлов или все файлы пустые +- `401 Unauthorized` - неверный или отсутствующий токен +- `413 Payload Too Large` - превышена квота пользователя или лимит размера файла - `415 Unsupported Media Type` - неподдерживаемый тип файла +- `500 Internal Server Error` - ошибка загрузки в S3 или обновления квоты -### 3. Получение файлов +### 3. Получение информации о текущем пользователе + +#### GET / +Получает информацию о залогиненном пользователе с данными о квоте. + +**Заголовки:** +``` +Authorization: Bearer +``` + +**Ответ:** +```json +{ + "user_id": "user123", + "username": "john_doe", + "token_type": "session", + "created_at": "1642248600", + "last_activity": "1642335000", + "auth_data": "{\"roles\": [\"user\"]}", + "device_info": "{\"platform\": \"web\"}", + "quota": { + "current_quota": 1073741824, + "max_quota": 5368709120, + "usage_percentage": 20.0 + } +} +``` + +**Ошибки:** +- `401 Unauthorized` - неверный или отсутствующий токен + +### 4. Получение файлов #### GET /{filename} Получает файл по имени. -**Параметры запроса:** -- `s=` - добавляет оверлей с данными shout (только для изображений) - **Примеры:** ``` GET /image.jpg -GET /image.jpg?s=123 GET /image_300.jpg GET /image_300.jpg/webp ``` -### 4. Управление квотами +### 5. Управление квотами #### GET /quota Получает информацию о квоте пользователя. @@ -139,10 +168,10 @@ GET /quota?user_id=user123 | Код | Описание | |-----|----------| | 200 | Успешный запрос | -| 400 | Неверные параметры запроса | +| 400 | Неверные параметры запроса или нет файлов | | 401 | Неавторизованный доступ | | 404 | Файл не найден | -| 413 | Превышена квота | +| 413 | Превышена квота пользователя или лимит размера файла (500 МБ) | | 415 | Неподдерживаемый тип файла | | 500 | Внутренняя ошибка сервера | @@ -155,6 +184,12 @@ curl -X POST http://localhost:8080/ \ -F "file=@image.jpg" ``` +### Получение информации о пользователе +```bash +curl -H "Authorization: Bearer your-token" \ + http://localhost:8080/ +``` + ### Получение миниатюры ```bash curl http://localhost:8080/image_300.jpg diff --git a/docs/architecture.md b/docs/architecture.md index 580a1f4..cbd2584 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -76,7 +76,7 @@ sequenceDiagram Client->>Quoter: POST / (file + token) Quoter->>Core API: Validate token - Core API-->>Quoter: User ID + Core API-->>Quoter: Author ID Quoter->>Redis: Check quota Redis-->>Quoter: Current quota Quoter->>S3: Upload file diff --git a/docs/deployment.md b/docs/deployment.md index 0197bbd..c683b72 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -196,7 +196,7 @@ After=network.target redis.service [Service] Type=simple -User=quoter +Author=quoter Group=quoter WorkingDirectory=/opt/quoter Environment=REDIS_URL=redis://localhost:6379 diff --git a/docs/development.md b/docs/development.md index 29b4cbf..42bf88e 100644 --- a/docs/development.md +++ b/docs/development.md @@ -215,7 +215,7 @@ use log::{debug, info, warn, error}; // В коде debug!("Processing file: {}", filename); info!("File uploaded successfully"); -warn!("User quota is getting low: {} bytes", quota); +warn!("Author quota is getting low: {} bytes", quota); error!("Failed to upload file: {}", e); ``` diff --git a/docs/monitoring.md b/docs/monitoring.md index 3771498..d80a555 100644 --- a/docs/monitoring.md +++ b/docs/monitoring.md @@ -137,7 +137,7 @@ lazy_static! { pub static ref QUOTA_USAGE: Histogram = Histogram::new( "quoter_quota_usage_bytes", - "User quota usage in bytes" + "Author quota usage in bytes" ).unwrap(); } ``` diff --git a/docs/upload-api-detailed.md b/docs/upload-api-detailed.md new file mode 100644 index 0000000..b5c8cda --- /dev/null +++ b/docs/upload-api-detailed.md @@ -0,0 +1,298 @@ +# 📤 API загрузки файлов с квотами - Точная документация + +## Обзор + +Quoter предоставляет API для загрузки файлов с системой квот и автоматической обработкой различных типов медиа. + +## Базовый URL +``` +http://localhost:8080 +``` + +## Аутентификация +Все эндпоинты загрузки требуют JWT токен в заголовке: +``` +Authorization: Bearer +``` + +--- + +## 📤 Загрузка файлов (УЛУЧШЕННАЯ ВЕРСИЯ) + +### POST / + +Загружает файл(ы) в S3-совместимое хранилище (Storj) с улучшенной проверкой квот и валидацией. + +#### Заголовки запроса +```http +Authorization: Bearer +Content-Type: multipart/form-data +``` + +#### Параметры +- **file** (required) - файл(ы) для загрузки в multipart/form-data + +#### Поддерживаемые форматы +Автоматическое определение MIME-типа из содержимого файла: +- **Изображения**: JPEG, PNG, GIF, WebP, HEIC +- **Видео**: MP4, WebM, AVI +- **Аудио**: MP3, WAV, OGG +- **Документы**: PDF + +#### 🔄 Улучшенная логика обработки + +1. **Проверка авторизации** - извлечение и валидация JWT токена +2. **Получение текущей квоты** пользователя из Redis +3. **Предварительная проверка квоты** - пользователь не достиг лимита +4. **Streaming чтение файла** с проверками на каждом chunk: + - Проверка лимита одного файла (500 МБ) + - Проверка общей квоты пользователя +5. **Пропуск пустых файлов** +6. **Определение MIME-типа** из содержимого (не из расширения!) +7. **Генерация UUID имени** файла с правильным расширением +8. **Загрузка в Storj S3** +9. **Обновление квоты** пользователя +10. **Сохранение метаданных** в Redis (с обработкой ошибок) + +#### Ограничения + +- **Максимальная квота на пользователя**: 5 ГБ (5,368,709,120 байт) +- **Максимальный размер одного файла**: 500 МБ (524,288,000 байт) +- **Проверка квоты происходит во время чтения** (streaming) +- **Поддержка множественных файлов** в одном запросе + +#### Успешные ответы + +**Один файл:** +```http +HTTP/1.1 200 OK +Content-Type: text/plain + +uuid-filename.ext +``` + +**Несколько файлов:** +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "uploaded_files": ["uuid1.jpg", "uuid2.png"], + "count": 2 +} +``` + +#### Коды ошибок (ИСПРАВЛЕННЫЕ) + +| Код | Условие | Описание | +|-----|---------|----------| +| **400 Bad Request** | Нет файлов | `"No files provided or all files were empty"` | +| **401 Unauthorized** | Отсутствует токен | `"Authorization token required"` | +| **401 Unauthorized** | Неверный токен | `"Invalid authorization token"` | +| **413 Payload Too Large** | 🎯 Превышена квота | `"Author quota limit exceeded"` | +| **413 Payload Too Large** | 🎯 Большой файл | `"Single file size limit exceeded"` | +| **413 Payload Too Large** | 🎯 Превышение при загрузке | `"Author quota limit would be exceeded"` | +| **415 Unsupported Media Type** | Неподдерживаемый MIME | `"Unsupported file format"` | +| **415 Unsupported Media Type** | Нет расширения для MIME | `"Unsupported content type"` | +| **500 Internal Server Error** | Ошибка S3 | `"File upload failed"` | +| **500 Internal Server Error** | Ошибка квоты | `"Failed to update user quota"` | + +#### ✅ Исправленные проблемы + +1. **Правильный код ошибки для квоты**: 413 Payload Too Large +2. **Efficient memory usage**: streaming с проверками на каждом chunk +3. **Предварительная проверка квоты** перед началом загрузки +4. **Лимит размера одного файла**: 500 МБ +5. **Улучшенная обработка ошибок** с детальными сообщениями +6. **Поддержка множественных файлов** в одном запросе +7. **Детальное логирование** с процентом использования квоты + +--- + +#### 🔄 Как это работает + +1. **JWT декодирование** - извлекается `user_id` из токена +2. **Redis lookup** - опциональный поиск сессии по ключу `session:{user_id}:{token}` +3. **Quota lookup** - получение квоты по ключу `quota:{user_id}` из Redis +4. **Activity update** - обновление `last_activity` timestamp (если сессия найдена) +5. **Response building** - объединение данных пользователя и квоты + +#### Заголовки запроса +```http +Authorization: Bearer +``` + +#### Успешный ответ +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "user_id": "user_12345", + "username": "john_doe", + "token_type": "session", + "created_at": "1642248600", + "last_activity": "1642335000", + "auth_data": "{\"roles\": [\"user\"], \"permissions\": [...]}", + "device_info": "{\"platform\": \"web\", \"browser\": \"Chrome\"}", + "quota": { + "current_quota": 1073741824, + "max_quota": 5368709120, + "usage_percentage": 20.0 + } +} +``` + +#### Поля ответа + +| Поле | Тип | Описание | +|------|-----|----------| +| `user_id` | string | Уникальный ID пользователя | +| `username` | string \| null | Имя пользователя | +| `token_type` | string \| null | Тип токена (обычно "session") | +| `created_at` | string \| null | Unix timestamp создания сессии | +| `last_activity` | string \| null | Unix timestamp последней активности | +| `auth_data` | string \| null | JSON-строка с данными авторизации | +| `device_info` | string \| null | JSON-строка с информацией об устройстве | +| `quota.current_quota` | number | Текущее использование квоты в байтах | +| `quota.max_quota` | number | Максимальная квота в байтах | +| `quota.usage_percentage` | number | Процент использования квоты | + +#### Коды ошибок + +| Код | Условие | Описание | +|-----|---------|----------| +| **401 Unauthorized** | Отсутствует токен | `"Authorization token required"` | +| **401 Unauthorized** | Неверный JWT | `"Invalid or expired session token"` | +| **401 Unauthorized** | Сессия не найдена | `"Session not found or expired"` | + +#### Примеры использования + +```bash +# Получение информации о текущем пользователе +curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc..." \ + http://localhost:8080/ +``` + +--- + +## 📊 Управление квотами + +### GET /quota + +Получает информацию о квоте пользователя. + +#### Параметры запроса +``` +GET /quota?user_id= +``` + +#### Ответ +```json +{ + "user_id": "user123", + "current_quota": 1073741824, + "max_quota": 5368709120 +} +``` + +### POST /quota/increase + +Увеличивает квоту пользователя (admin-only). + +#### Тело запроса +```json +{ + "user_id": "user123", + "additional_bytes": 1073741824 +} +``` + +#### Валидация +- `additional_bytes` должно быть > 0 +- Требуется админский токен + +### POST /quota/set + +Устанавливает абсолютное значение квоты (admin-only). + +#### Тело запроса +```json +{ + "user_id": "user123", + "new_quota_bytes": 2147483648 +} +``` + +--- + +## 🔍 Получение файлов + +### GET /{filename} + +Возвращает файл по имени с возможными трансформациями. + +#### Параметры URL +- `filename` - имя файла или имя_размер.расширение для миниатюр + +#### Query параметры +- `s=` - добавляет оверлей с данными shout (только изображения) + +#### Примеры +```bash +GET /uuid-file.jpg # Оригинальный файл +GET /uuid-file_300.jpg # Миниатюра 300px +GET /uuid-file_300.jpg/webp # Миниатюра в WebP +GET /uuid-file.jpg?s=123 # С оверлеем shout +``` + +--- + +## 🧪 Примеры использования + +### Загрузка файла +```bash +curl -X POST http://localhost:8080/ \ + -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc..." \ + -F "file=@photo.jpg" +``` + +**Ответ при успехе:** +``` +c4ca4238-a0b9-23f1-8429-81dc9bdb9a1f.jpg +``` + +**Ответ при превышении квоты:** +``` +HTTP/1.1 401 Unauthorized +Quota exceeded +``` + +### Проверка квоты +```bash +curl "http://localhost:8080/quota?user_id=user123" \ + -H "Authorization: Bearer admin-token" +``` + +### Увеличение квоты (admin) +```bash +curl -X POST http://localhost:8080/quota/increase \ + -H "Authorization: Bearer admin-token" \ + -H "Content-Type: application/json" \ + -d '{"user_id": "user123", "additional_bytes": 1073741824}' +``` + +--- + +## 🔧 Рекомендации по улучшению + +1. **Исправить код ошибки квоты**: 401 → 413 +2. **Добавить предварительную проверку размера** из Content-Length +3. **Streaming загрузка** вместо полного чтения в память +4. **Лимит размера одного файла** +5. **Детальная валидация MIME-типов** +6. **Метрики использования квот** + +--- + +*Документация актуальна для версии кода на момент создания. Для изменений см. CHANGELOG.md.* diff --git a/src/Muller-Regular.woff2 b/src/Muller-Regular.woff2 deleted file mode 100644 index 3977200..0000000 Binary files a/src/Muller-Regular.woff2 and /dev/null differ diff --git a/src/auth.rs b/src/auth.rs index a6287c8..d045f31 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,12 +1,14 @@ use actix_web::error::ErrorInternalServerError; use redis::{aio::MultiplexedConnection, AsyncCommands}; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, env, error::Error}; +use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; +use log::{info, warn}; use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE}; use reqwest::Client as HTTPClient; -use serde::Deserialize; use serde_json::json; -use std::{collections::HashMap, env, error::Error}; -// Структура для десериализации ответа от сервиса аутентификации +// Старые структуры для совместимости с get_id_by_token #[derive(Deserialize)] struct AuthResponse { data: Option, @@ -28,6 +30,27 @@ struct Claims { sub: Option, } +// Структуры для JWT токенов +#[derive(Debug, Deserialize)] +struct TokenClaims { + user_id: String, + username: Option, + exp: Option, + iat: Option, +} + +// Структура для данных пользователя из Redis сессии +#[derive(Deserialize, Serialize, Clone, Debug)] +pub struct Author { + pub user_id: String, + pub username: Option, + pub token_type: Option, + pub created_at: Option, + pub last_activity: Option, + pub auth_data: Option, + pub device_info: Option, +} + /// Получает айди пользователя из токена в заголовке pub async fn get_id_by_token(token: &str) -> Result> { let auth_api_base = env::var("CORE_URL")?; @@ -78,6 +101,124 @@ pub async fn get_id_by_token(token: &str) -> Result> { } } +/// Декодирует JWT токен и извлекает claims с проверкой истечения +fn decode_jwt_token(token: &str) -> Result> { + // В реальном приложении здесь должен быть настоящий секретный ключ + let secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| "your-secret-key".to_string()); + let key = DecodingKey::from_secret(secret.as_ref()); + + let mut validation = Validation::new(Algorithm::HS256); + validation.validate_exp = true; // Включаем проверку истечения срока действия + + match decode::(token, &key, &validation) { + Ok(token_data) => { + let claims = token_data.claims; + + // Дополнительная проверка exp если поле присутствует + if let Some(exp) = claims.exp { + let current_time = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() as usize; + + if exp < current_time { + warn!("JWT token expired: exp={}, current={}", exp, current_time); + return Err(Box::new(std::io::Error::other("Token expired"))); + } + + info!("JWT token valid until: {} (current: {})", exp, current_time); + } + + info!("Successfully decoded and validated JWT token for user: {}", claims.user_id); + Ok(claims) + } + Err(e) => { + warn!("Failed to decode JWT token: {}", e); + Err(Box::new(e)) + } + } +} + +/// Быстро извлекает user_id из JWT токена для работы с квотами +pub fn extract_user_id_from_token(token: &str) -> Result> { + let claims = decode_jwt_token(token)?; + Ok(claims.user_id) +} + +/// Проверяет валидность JWT токена (включая истечение срока действия) +pub fn validate_token(token: &str) -> Result> { + match decode_jwt_token(token) { + Ok(_) => Ok(true), + Err(e) => { + warn!("Token validation failed: {}", e); + Ok(false) + } + } +} + +/// Получает user_id из JWT токена и базовые данные пользователя +pub async fn get_user_by_token( + token: &str, + redis: &mut MultiplexedConnection, +) -> Result> { + // Декодируем JWT токен для получения user_id + let claims = decode_jwt_token(token)?; + let user_id = &claims.user_id; + + info!("Extracted user_id from JWT token: {}", user_id); + + // Проверяем валидность токена через сессию в Redis (опционально) + let token_key = format!("session:{}:{}", user_id, token); + let session_exists: bool = redis + .exists(&token_key) + .await + .map_err(|e| { + warn!("Failed to check session existence in Redis: {}", e); + // Не критичная ошибка, продолжаем с базовыми данными + }) + .unwrap_or(false); + + if session_exists { + // Обновляем last_activity если сессия существует + let current_time = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + + let _: () = redis + .hset(&token_key, "last_activity", current_time.to_string()) + .await + .map_err(|e| { + warn!("Failed to update last_activity: {}", e); + }) + .unwrap_or(()); + + info!("Updated last_activity for session: {}", token_key); + } else { + info!("Session not found in Redis, proceeding with JWT-only data"); + } + + // Создаем базовый объект Author с данными из JWT + let author = Author { + user_id: user_id.clone(), + username: claims.username.clone(), + token_type: Some("jwt".to_string()), + created_at: claims.iat.map(|ts| ts.to_string()), + last_activity: Some( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() + .to_string() + ), + auth_data: None, + device_info: None, + }; + + info!("Successfully created author data for user_id: {}", user_id); + Ok(author) +} + /// Сохраняет имя файла в Redis для пользователя pub async fn user_added_file( redis: &mut MultiplexedConnection, diff --git a/src/core.rs b/src/core.rs deleted file mode 100644 index d0acce9..0000000 --- a/src/core.rs +++ /dev/null @@ -1,61 +0,0 @@ -use reqwest::Client as HTTPClient; -use serde::Deserialize; -use serde_json::json; -use std::{collections::HashMap, env, error::Error}; - -// Структура для десериализации ответа от сервиса аутентификации -#[derive(Deserialize)] -struct CoreResponse { - data: Option, -} - -#[derive(Deserialize)] -pub struct ShoutTopic { - pub slug: String, - pub title: String, -} - -#[derive(Deserialize)] -pub struct ShoutAuthor { - pub name: String, -} - -#[derive(Deserialize)] -pub struct Shout { - pub title: String, - pub created_at: String, - pub main_topic: ShoutTopic, - pub authors: Vec, - pub layout: String, -} - -pub async fn get_shout_by_id(shout_id: i32) -> Result> { - let mut variables = HashMap::::new(); - let api_base = env::var("CORE_URL")?; - let query_name = "get_shout"; - let operation = "GetShout"; - if shout_id != 0 { - variables.insert("shout_id".to_string(), shout_id); - } - - let gql = json!({ - "query": format!("query {}($slug: String, $shout_id: Int) {{ {}(slug: $slug, shout_id: $shout_id) {{ title created_at main_topic {{ title slug }} authors {{ id name }} }} }}", operation, query_name), - "operationName": operation, - "variables": variables - }); - - let client = HTTPClient::new(); - let response = client.post(&api_base).json(&gql).send().await?; - - if response.status().is_success() { - let core_response: CoreResponse = response.json().await?; - if let Some(shout) = core_response.data { - return Ok(shout); - } - Err(Box::new(std::io::Error::other("Shout not found"))) - } else { - Err(Box::new(std::io::Error::other( - response.status().to_string(), - ))) - } -} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 78b4d8f..ab05a91 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -2,20 +2,12 @@ mod proxy; mod quota; mod serve_file; mod upload; +mod user; pub use proxy::proxy_handler; pub use quota::{get_quota_handler, increase_quota_handler, set_quota_handler}; pub use upload::upload_handler; +pub use user::get_current_user_handler; // Общий лимит квоты на пользователя: 5 ГБ -pub const MAX_USER_QUOTA_BYTES: u64 = 5 * 1024 * 1024 * 1024; - -use actix_web::{HttpRequest, HttpResponse, Result}; - -/// Обработчик для корневого пути / -pub async fn root_handler(req: HttpRequest) -> Result { - match req.method().as_str() { - "GET" => Ok(HttpResponse::Ok().content_type("text/plain").body("ok")), - _ => Ok(HttpResponse::MethodNotAllowed().finish()), - } -} +pub const MAX_USER_QUOTA_BYTES: u64 = 5 * 1024 * 1024 * 1024; \ No newline at end of file diff --git a/src/handlers/proxy.rs b/src/handlers/proxy.rs index 8cc2db2..90d5641 100644 --- a/src/handlers/proxy.rs +++ b/src/handlers/proxy.rs @@ -1,6 +1,6 @@ use actix_web::error::ErrorNotFound; use actix_web::{error::ErrorInternalServerError, web, HttpRequest, HttpResponse, Result}; -use log::{error, warn}; +use log::{info, error, warn}; use crate::app_state::AppState; use crate::handlers::serve_file::serve_file; @@ -8,29 +8,51 @@ use crate::lookup::{find_file_by_pattern, get_mime_type}; use crate::s3_utils::{check_file_exists, load_file_from_s3, upload_to_s3}; use crate::thumbnail::{find_closest_width, parse_file_path, thumbdata_save}; +/// Создает HTTP ответ с оптимальными заголовками кэширования +fn create_cached_response(content_type: &str, data: Vec, file_etag: &str) -> HttpResponse { + HttpResponse::Ok() + .content_type(content_type) + .insert_header(("etag", file_etag)) + .insert_header(("cache-control", "public, max-age=31536000, immutable")) // 1 год + .insert_header(("access-control-allow-origin", "*")) + .body(data) +} + /// Обработчик для скачивания файла и генерации миниатюры, если она недоступна. pub async fn proxy_handler( req: HttpRequest, requested_res: web::Path, state: web::Data, ) -> Result { - warn!("\t>>>\tGET {} [START]", requested_res); + let start_time = std::time::Instant::now(); + info!("GET {} [START]", requested_res); + let normalized_path = if requested_res.ends_with("/webp") { - warn!("Removing /webp suffix from path"); + info!("Converting to WebP format: {}", requested_res); requested_res.replace("/webp", "") } else { requested_res.to_string() }; + // Проверяем If-None-Match заголовок для кэширования + let client_etag = req.headers().get("if-none-match") + .and_then(|h| h.to_str().ok()); + // парсим GET запрос let (base_filename, requested_width, extension) = parse_file_path(&normalized_path); - warn!("detected file extension: {}", extension); - warn!("base_filename: {}", base_filename); - warn!("requested width: {}", requested_width); let ext = extension.as_str().to_lowercase(); - warn!("normalized to lowercase: {}", ext); let filekey = format!("{}.{}", base_filename, &ext); - warn!("filekey: {}", filekey); + + info!("Parsed request - base: {}, width: {}, ext: {}", base_filename, requested_width, ext); + + // Генерируем ETag для кэширования + let file_etag = format!("\"{}\"", &filekey); + if let Some(etag) = client_etag { + if etag == file_etag { + info!("Cache hit for {}, returning 304", filekey); + return Ok(HttpResponse::NotModified().finish()); + } + } let content_type = match get_mime_type(&ext) { Some(mime) => mime.to_string(), None => { @@ -46,24 +68,15 @@ pub async fn proxy_handler( } } _ => { - error!("unsupported file format"); - return Err(ErrorInternalServerError("unsupported file format")); + error!("Unsupported file format for: {}", base_filename); + return Err(ErrorInternalServerError("Unsupported file format")); } } } }; - warn!("content_type: {}", content_type); + info!("Content-Type: {}", content_type); - let shout_id = match req.query_string().contains("s=") { - true => req - .query_string() - .split("s=") - .collect::>() - .pop() - .unwrap_or(""), - false => "", - }; return match state.get_path(&filekey).await { Ok(Some(stored_path)) => { @@ -78,7 +91,7 @@ pub async fn proxy_handler( warn!("Processing image file with width: {}", requested_width); if requested_width == 0 { warn!("Serving original file without resizing"); - serve_file(&stored_path, &state, shout_id).await + serve_file(&stored_path, &state).await } else { let closest: u32 = find_closest_width(requested_width); warn!( @@ -94,12 +107,12 @@ pub async fn proxy_handler( { Ok(true) => { warn!("serve existed thumb file: {}", thumb_filename); - serve_file(thumb_filename, &state, shout_id).await + serve_file(thumb_filename, &state).await } Ok(false) => { // Миниатюра не существует, возвращаем оригинал и запускаем генерацию миниатюры let original_file = - serve_file(&stored_path, &state, shout_id).await?; + serve_file(&stored_path, &state).await?; // Запускаем асинхронную задачу для генерации миниатюры let state_clone = state.clone(); @@ -139,7 +152,7 @@ pub async fn proxy_handler( } } else { warn!("File is not an image, proceeding with normal serving"); - serve_file(&stored_path, &state, shout_id).await + serve_file(&stored_path, &state).await } } else { warn!( @@ -193,9 +206,9 @@ pub async fn proxy_handler( warn!("Successfully uploaded to Storj: {}", filekey); } - return Ok(HttpResponse::Ok() - .content_type(content_type) - .body(filedata)); + let elapsed = start_time.elapsed(); + info!("File served from AWS in {:?}: {}", elapsed, path); + return Ok(create_cached_response(&content_type, filedata, &file_etag)); } Err(err) => { warn!("Failed to load from AWS path {}: {:?}", path, err); @@ -299,7 +312,9 @@ pub async fn proxy_handler( warn!("file {} uploaded to storj", filekey); state.set_path(&filekey, &filepath).await; } - Ok(HttpResponse::Ok().content_type(content_type).body(filedata)) + let elapsed = start_time.elapsed(); + info!("File served from AWS in {:?}: {}", elapsed, filepath); + Ok(create_cached_response(&content_type, filedata, &file_etag)) } Err(e) => { error!("Failed to download from AWS: {} - Error: {}", filepath, e); @@ -312,9 +327,10 @@ pub async fn proxy_handler( } } Err(e) => { + let elapsed = start_time.elapsed(); error!( - "Database error while getting path: {} - Full error: {:?}", - filekey, e + "Database error while getting path: {} in {:?} - Full error: {:?}", + filekey, elapsed, e ); Err(ErrorInternalServerError(e)) } diff --git a/src/handlers/quota.rs b/src/handlers/quota.rs index d39db29..28ec7fe 100644 --- a/src/handlers/quota.rs +++ b/src/handlers/quota.rs @@ -36,7 +36,14 @@ pub async fn get_quota_handler( let _admin_id = get_id_by_token(token.unwrap()) .await - .map_err(|_| actix_web::error::ErrorUnauthorized("Invalid token"))?; + .map_err(|e| { + let error_msg = if e.to_string().contains("expired") { + "Admin token has expired" + } else { + "Invalid admin token" + }; + actix_web::error::ErrorUnauthorized(error_msg) + })?; // Получаем user_id из query параметров let user_id = req @@ -76,7 +83,14 @@ pub async fn increase_quota_handler( let _admin_id = get_id_by_token(token.unwrap()) .await - .map_err(|_| actix_web::error::ErrorUnauthorized("Invalid token"))?; + .map_err(|e| { + let error_msg = if e.to_string().contains("expired") { + "Admin token has expired" + } else { + "Invalid admin token" + }; + actix_web::error::ErrorUnauthorized(error_msg) + })?; let additional_bytes = quota_data .additional_bytes @@ -125,7 +139,14 @@ pub async fn set_quota_handler( let _admin_id = get_id_by_token(token.unwrap()) .await - .map_err(|_| actix_web::error::ErrorUnauthorized("Invalid token"))?; + .map_err(|e| { + let error_msg = if e.to_string().contains("expired") { + "Admin token has expired" + } else { + "Invalid admin token" + }; + actix_web::error::ErrorUnauthorized(error_msg) + })?; let new_quota_bytes = quota_data .new_quota_bytes diff --git a/src/handlers/serve_file.rs b/src/handlers/serve_file.rs index 4219202..edd7dd6 100644 --- a/src/handlers/serve_file.rs +++ b/src/handlers/serve_file.rs @@ -2,14 +2,12 @@ use actix_web::{error::ErrorInternalServerError, HttpResponse, Result}; use mime_guess::MimeGuess; use crate::app_state::AppState; -use crate::overlay::generate_overlay; use crate::s3_utils::check_file_exists; /// Функция для обслуживания файла по заданному пути. pub async fn serve_file( filepath: &str, state: &AppState, - shout_id: &str, ) -> Result { if filepath.is_empty() { return Err(ErrorInternalServerError("Filename is empty".to_string())); @@ -42,14 +40,17 @@ pub async fn serve_file( .await .map_err(|_| ErrorInternalServerError("Failed to read object body"))?; - let data_bytes = match shout_id.is_empty() { - true => data.into_bytes(), - false => generate_overlay(shout_id, data.into_bytes()).await?, - }; + let data_bytes = data.into_bytes(); let mime_type = MimeGuess::from_path(filepath).first_or_octet_stream(); + + // Генерируем ETag для кэширования на основе пути файла + let etag = format!("\"{}\"", filepath); Ok(HttpResponse::Ok() .content_type(mime_type.as_ref()) + .insert_header(("etag", etag.as_str())) + .insert_header(("cache-control", "public, max-age=31536000, immutable")) // 1 год + .insert_header(("access-control-allow-origin", "*")) .body(data_bytes)) } diff --git a/src/handlers/upload.rs b/src/handlers/upload.rs index e524e26..5aa66fd 100644 --- a/src/handlers/upload.rs +++ b/src/handlers/upload.rs @@ -1,16 +1,19 @@ use actix_multipart::Multipart; use actix_web::{web, HttpRequest, HttpResponse, Result}; -use log::{error, warn}; +use log::{error, info, warn}; use crate::app_state::AppState; -use crate::auth::{get_id_by_token, user_added_file}; +use crate::auth::{extract_user_id_from_token, user_added_file, validate_token}; use crate::handlers::MAX_USER_QUOTA_BYTES; use crate::lookup::store_file_info; use crate::s3_utils::{self, generate_key_with_extension, upload_to_s3}; use futures::TryStreamExt; // use crate::thumbnail::convert_heic_to_jpeg; -/// Обработчик для аплоада файлов. +// Максимальный размер одного файла: 500 МБ +const MAX_SINGLE_FILE_BYTES: u64 = 500 * 1024 * 1024; + +/// Обработчик для аплоада файлов с улучшенной логикой квот и валидацией. pub async fn upload_handler( req: HttpRequest, mut payload: Multipart, @@ -21,40 +24,91 @@ pub async fn upload_handler( .headers() .get("Authorization") .and_then(|header_value| header_value.to_str().ok()); + if token.is_none() { - return Err(actix_web::error::ErrorUnauthorized("Unauthorized")); // Если токен отсутствует, возвращаем ошибку + warn!("Upload attempt without authorization token"); + return Err(actix_web::error::ErrorUnauthorized("Authorization token required")); } - let user_id = get_id_by_token(token.unwrap()).await?; + let token = token.unwrap(); + + // Сначала валидируем токен + if !validate_token(token).unwrap_or(false) { + warn!("Token validation failed"); + return Err(actix_web::error::ErrorUnauthorized("Invalid or expired token")); + } + + // Затем извлекаем user_id + let user_id = extract_user_id_from_token(token) + .map_err(|e| { + warn!("Failed to extract user_id from token: {}", e); + actix_web::error::ErrorUnauthorized("Invalid authorization token") + })?; // Получаем текущую квоту пользователя let current_quota: u64 = state.get_or_create_quota(&user_id).await.unwrap_or(0); - let mut body = "ok".to_string(); + info!("Author {} current quota: {} bytes", user_id, current_quota); + + // Предварительная проверка: есть ли вообще место для файлов + if current_quota >= MAX_USER_QUOTA_BYTES { + warn!("Author {} quota already at maximum: {}", user_id, current_quota); + return Err(actix_web::error::ErrorPayloadTooLarge("Author quota limit exceeded")); + } + + let mut uploaded_files = Vec::new(); + while let Ok(Some(field)) = payload.try_next().await { let mut field = field; let mut file_bytes = Vec::new(); let mut file_size: u64 = 0; - // Читаем данные файла + // Читаем данные файла с проверкой размера while let Ok(Some(chunk)) = field.try_next().await { - file_size += chunk.len() as u64; + let chunk_size = chunk.len() as u64; + + // Проверка лимита одного файла + if file_size + chunk_size > MAX_SINGLE_FILE_BYTES { + warn!("File size exceeds single file limit: {} > {}", + file_size + chunk_size, MAX_SINGLE_FILE_BYTES); + return Err(actix_web::error::ErrorPayloadTooLarge("Single file size limit exceeded")); + } + + // Проверка общей квоты пользователя + if current_quota + file_size + chunk_size > MAX_USER_QUOTA_BYTES { + warn!("Upload would exceed user quota: current={}, adding={}, limit={}", + current_quota, file_size + chunk_size, MAX_USER_QUOTA_BYTES); + return Err(actix_web::error::ErrorPayloadTooLarge("Author quota limit would be exceeded")); + } + + file_size += chunk_size; file_bytes.extend_from_slice(&chunk); } + // Пропускаем пустые файлы + if file_size == 0 { + warn!("Skipping empty file upload"); + continue; + } + + info!("Processing file: {} bytes", file_size); + // Определяем MIME-тип из содержимого файла let detected_mime_type = match s3_utils::detect_mime_type(&file_bytes) { - Some(mime) => mime, + Some(mime) => { + info!("Detected MIME type: {}", mime); + mime + } None => { - warn!("Неподдерживаемый формат файла"); + warn!("Unsupported file format detected"); return Err(actix_web::error::ErrorUnsupportedMediaType( - "Неподдерживаемый формат файла", + "Unsupported file format", )); } }; // Для HEIC файлов просто сохраняем как есть let (file_bytes, content_type) = if detected_mime_type == "image/heic" { - warn!("HEIC support is temporarily disabled, saving original file"); + info!("Processing HEIC file (saved as original)"); (file_bytes, detected_mime_type) } else { (file_bytes, detected_mime_type) @@ -64,24 +118,16 @@ pub async fn upload_handler( let extension = match s3_utils::get_extension_from_mime(&content_type) { Some(ext) => ext, None => { - warn!("Неподдерживаемый тип содержимого: {}", content_type); + warn!("No file extension found for MIME type: {}", content_type); return Err(actix_web::error::ErrorUnsupportedMediaType( - "Неподдерживаемый тип содержимого", + "Unsupported content type", )); } }; - // Проверяем, что добавление файла не превышает лимит квоты - if current_quota + file_size > MAX_USER_QUOTA_BYTES { - warn!( - "Quota would exceed limit: current={}, adding={}, limit={}", - current_quota, file_size, MAX_USER_QUOTA_BYTES - ); - return Err(actix_web::error::ErrorUnauthorized("Quota exceeded")); - } - // Генерируем имя файла с правильным расширением let filename = format!("{}.{}", uuid::Uuid::new_v4(), extension); + info!("Generated filename: {}", filename); // Загружаем файл в S3 storj match upload_to_s3( @@ -94,36 +140,66 @@ pub async fn upload_handler( .await { Ok(_) => { - warn!( - "file {} uploaded to storj, incrementing quota by {} bytes", - filename, file_size - ); + info!("File {} successfully uploaded to S3 ({} bytes)", filename, file_size); + + // Обновляем квоту пользователя if let Err(e) = state.increment_uploaded_bytes(&user_id, file_size).await { - error!("Failed to increment quota: {}", e); - return Err(e); + error!("Failed to increment quota for user {}: {}", user_id, e); + return Err(actix_web::error::ErrorInternalServerError( + "Failed to update user quota" + )); } // Сохраняем информацию о файле в Redis let mut redis = state.redis.clone(); - store_file_info(&mut redis, &filename, &content_type).await?; - user_added_file(&mut redis, &user_id, &filename).await?; - - // Сохраняем маппинг пути - let generated_key = - generate_key_with_extension(filename.clone(), content_type.clone()); - state.set_path(&filename, &generated_key).await; - - if let Ok(new_quota) = state.get_or_create_quota(&user_id).await { - warn!("New quota for user {}: {} bytes", user_id, new_quota); + if let Err(e) = store_file_info(&mut redis, &filename, &content_type).await { + error!("Failed to store file info in Redis: {}", e); + // Не прерываем процесс, файл уже загружен в S3 + } + + if let Err(e) = user_added_file(&mut redis, &user_id, &filename).await { + error!("Failed to record user file association: {}", e); + // Не прерываем процесс } - body = filename; + // Сохраняем маппинг пути + let generated_key = generate_key_with_extension(filename.clone(), content_type.clone()); + state.set_path(&filename, &generated_key).await; + + // Логируем новую квоту + if let Ok(new_quota) = state.get_or_create_quota(&user_id).await { + info!("Updated quota for user {}: {} bytes ({:.1}% used)", + user_id, new_quota, + (new_quota as f64 / MAX_USER_QUOTA_BYTES as f64) * 100.0); + } + + uploaded_files.push(filename); } Err(e) => { - warn!("Failed to upload to storj: {}", e); - return Err(actix_web::error::ErrorInternalServerError(e)); + error!("Failed to upload file to S3: {}", e); + return Err(actix_web::error::ErrorInternalServerError( + "File upload failed" + )); } } } - Ok(HttpResponse::Ok().body(body)) + + // Возвращаем результат + match uploaded_files.len() { + 0 => { + warn!("No files were uploaded"); + Err(actix_web::error::ErrorBadRequest("No files provided or all files were empty")) + } + 1 => { + info!("Successfully uploaded 1 file: {}", uploaded_files[0]); + Ok(HttpResponse::Ok().body(uploaded_files[0].clone())) + } + n => { + info!("Successfully uploaded {} files", n); + Ok(HttpResponse::Ok().json(serde_json::json!({ + "uploaded_files": uploaded_files, + "count": n + }))) + } + } } diff --git a/src/handlers/user.rs b/src/handlers/user.rs new file mode 100644 index 0000000..f7bc64f --- /dev/null +++ b/src/handlers/user.rs @@ -0,0 +1,102 @@ +use actix_web::{web, HttpRequest, HttpResponse, Result}; +use log::{error, info, warn}; +use serde::Serialize; + +use crate::app_state::AppState; +use crate::auth::{get_user_by_token, Author, validate_token}; + +#[derive(Serialize)] +pub struct UserWithQuotaResponse { + #[serde(flatten)] + pub user: Author, + pub quota: QuotaInfo, +} + +#[derive(Serialize)] +pub struct QuotaInfo { + pub current_quota: u64, + pub max_quota: u64, + pub usage_percentage: f64, +} + +/// Обработчик для получения информации о текущем пользователе +pub async fn get_current_user_handler( + req: HttpRequest, + state: web::Data, +) -> Result { + // Извлекаем токен из заголовка авторизации + let token = req + .headers() + .get("Authorization") + .and_then(|header_value| header_value.to_str().ok()) + .and_then(|auth_str| { + // Убираем префикс "Bearer " если он есть + if auth_str.starts_with("Bearer ") { + Some(&auth_str[7..]) + } else { + Some(auth_str) + } + }); + + if token.is_none() { + warn!("Request for current user without authorization token"); + return Err(actix_web::error::ErrorUnauthorized("Authorization token required")); + } + + let token = token.unwrap(); + + // Сначала валидируем токен + if !validate_token(token).unwrap_or(false) { + warn!("Token validation failed in user endpoint"); + return Err(actix_web::error::ErrorUnauthorized("Invalid or expired token")); + } + + info!("Getting user info for valid token"); + + // Получаем информацию о пользователе из Redis сессии + let mut redis = state.redis.clone(); + let user = match get_user_by_token(token, &mut redis).await { + Ok(user) => { + info!("Successfully retrieved user info: user_id={}, username={:?}", + user.user_id, user.username); + user + } + Err(e) => { + warn!("Failed to get user info from Redis: {}", e); + let error_msg = if e.to_string().contains("expired") { + "Token has expired" + } else { + "Invalid or expired session token" + }; + return Err(actix_web::error::ErrorUnauthorized(error_msg)); + } + }; + + // Получаем квоту пользователя + let current_quota = match state.get_or_create_quota(&user.user_id).await { + Ok(quota) => quota, + Err(e) => { + error!("Failed to get user quota: {}", e); + 0 // Возвращаем 0 если не удалось получить квоту + } + }; + + let max_quota = crate::handlers::MAX_USER_QUOTA_BYTES; + let usage_percentage = if max_quota > 0 { + (current_quota as f64 / max_quota as f64) * 100.0 + } else { + 0.0 + }; + + let response = UserWithQuotaResponse { + user, + quota: QuotaInfo { + current_quota, + max_quota, + usage_percentage, + }, + }; + + info!("Author info response prepared successfully"); + Ok(HttpResponse::Ok().json(response)) +} diff --git a/src/main.rs b/src/main.rs index aeabc7c..1a2ebe8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,7 @@ mod app_state; mod auth; -mod core; mod handlers; mod lookup; -mod overlay; mod s3_utils; mod thumbnail; @@ -16,8 +14,9 @@ use actix_web::{ use app_state::AppState; use handlers::{ - get_quota_handler, increase_quota_handler, proxy_handler, root_handler, set_quota_handler, - upload_handler, + get_current_user_handler, get_quota_handler, + increase_quota_handler, proxy_handler, + set_quota_handler, upload_handler, }; use log::warn; use std::env; @@ -64,7 +63,7 @@ async fn main() -> std::io::Result<()> { .app_data(web::Data::new(app_state.clone())) .wrap(cors) .wrap(Logger::default()) - .route("/", web::get().to(root_handler)) + .route("/", web::get().to(get_current_user_handler)) .route("/", web::post().to(upload_handler)) .route("/quota", web::get().to(get_quota_handler)) .route("/quota/increase", web::post().to(increase_quota_handler)) diff --git a/src/overlay.rs b/src/overlay.rs deleted file mode 100644 index 32a1e25..0000000 --- a/src/overlay.rs +++ /dev/null @@ -1,93 +0,0 @@ -use ab_glyph::{Font, FontArc, PxScale}; -use actix_web::web::Bytes; -use image::Rgba; -use imageproc::drawing::{draw_filled_rect_mut, draw_text_mut}; -use imageproc::rect::Rect; -use log::warn; -use std::{error::Error, io::Cursor}; - -use crate::core::get_shout_by_id; - -pub async fn generate_overlay(shout_id: &str, filedata: Bytes) -> Result> { - // Получаем shout из GraphQL - let shout_id_int = shout_id.parse::().unwrap_or(0); - match get_shout_by_id(shout_id_int).await { - Ok(shout) => { - // Преобразуем Bytes в ImageBuffer - let img = image::load_from_memory(&filedata)?; - let mut img = img.to_rgba8(); - - // Загружаем шрифт - let font_vec = Vec::from(include_bytes!("Muller-Regular.woff2") as &[u8]); - let font = FontArc::try_from_vec(font_vec).unwrap(); - - // Получаем размеры изображения - let (img_width, img_height) = img.dimensions(); - let max_text_width = (img_width as f32) * 0.8; - let max_text_height = (img_height as f32) * 0.8; - - // Начальный масштаб - let mut scale: f32 = 24.0; - let text_length = shout.title.chars().count() as f32; - let mut text_width = scale * text_length; - let text_height = scale; - - // Регулируем масштаб, пока текст не впишется в 80% от размеров изображения - while text_width > max_text_width || text_height > max_text_height { - scale -= 1.0; - if scale <= 0.0 { - break; - } - text_width = scale * text_length; - // text_height остается равным scale - } - - // Рассчёт позиции текста для центрирования - let x = ((img_width as f32 - text_width) / 2.0).ceil() as i32; - let y = ((img_height as f32 - text_height) / 2.0).ceil() as i32; - - // Задаём отступы для подложки - let padding_x = 10; - let padding_y = 5; - - // Определяем размеры подложки - let rect_width = text_width.ceil() as u32 + (2 * padding_x); - let rect_height = text_height.ceil() as u32 + (2 * padding_y); - - // Определяем координаты подложки - let rect_x = x - padding_x as i32; - let rect_y = y - padding_y as i32; - - // Создаём прямоугольник - let rect = Rect::at(rect_x, rect_y).of_size(rect_width, rect_height); - - // Задаём цвет подложки (полупрозрачный серый) - let background_color = Rgba([128u8, 128u8, 128u8, 128u8]); // RGBA: серый с прозрачностью 50% - - // Рисуем подложку - draw_filled_rect_mut(&mut img, rect, background_color); - - // Рисуем текст поверх подложки - let scaled_font = font.as_scaled(scale).font; - draw_text_mut( - &mut img, - Rgba([255u8, 255u8, 255u8, 255u8]), // Белый цвет текста - x, - y, - PxScale::from(scale), - &scaled_font, - &shout.title, - ); - - // Преобразуем ImageBuffer обратно в Bytes - let mut buffer = Vec::new(); - img.write_to(&mut Cursor::new(&mut buffer), image::ImageFormat::Png)?; - - Ok(Bytes::from(buffer)) - } - Err(e) => { - warn!("Error getting shout: {}", e); - Ok(filedata) - } - } -}