Merge branch 'dev' of https://dev.dscrs.site/discours.io/core into dev
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -182,4 +182,6 @@ docs/progress/*
|
||||
|
||||
panel/graphql/generated
|
||||
|
||||
test_e2e.db*
|
||||
test_e2e.db*
|
||||
|
||||
uv.lock
|
||||
65
CHANGELOG.md
65
CHANGELOG.md
@@ -1,6 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
## [0.9.29] - 2025-10-08
|
||||
## [0.9.32] - 2025-10-08
|
||||
|
||||
### 🎯 Search Quality Upgrade: ColBERT + Native MUVERA + FAISS
|
||||
|
||||
@@ -68,6 +68,69 @@ SEARCH_MODEL_TYPE=biencoder
|
||||
- pylate issue: https://github.com/lightonai/pylate/issues/142
|
||||
- Model: `answerdotai/answerai-colbert-small-v1`
|
||||
|
||||
## [0.9.32] - 2025-10-05
|
||||
|
||||
### ✨ Features
|
||||
- **Редактирование мигрированных шаутов**: Добавлена мутация `create_draft_from_shout` для создания черновика из существующего опубликованного шаута
|
||||
- Создаёт черновик со всеми данными из шаута (title, body, lead, topics, authors, media, etc.)
|
||||
- Проверяет авторство перед созданием черновика
|
||||
- Переиспользует существующий черновик если он уже создан для этого шаута
|
||||
- Копирует все связи: авторов и темы (включая main topic)
|
||||
|
||||
### 🔧 Fixed
|
||||
- **NotificationEntity enum**: Исправлена ошибка `NotificationEntity.FOLLOWER` → `NotificationEntity.AUTHOR`
|
||||
- В enum не было значения `FOLLOWER`, используется `AUTHOR` для уведомлений о подписчиках
|
||||
|
||||
### Technical Details
|
||||
- `core/schema/mutation.graphql`: добавлена мутация `create_draft_from_shout(shout_id: Int!): CommonResult!`
|
||||
- `core/resolvers/draft.py`: добавлен resolver `create_draft_from_shout` с валидацией авторства
|
||||
- `core/resolvers/notifier.py`: исправлено использование `NotificationEntity.AUTHOR` вместо несуществующего `FOLLOWER`
|
||||
|
||||
## [0.9.31] - 2025-10-04
|
||||
|
||||
### ✅ Fixed: Notifications TODOs
|
||||
- **Уведомления о followers**: Добавлена обработка уведомлений о подписчиках в `notifications_seen_thread`
|
||||
- Теперь при клике на группу "followers" все уведомления о подписках помечаются как прочитанные
|
||||
- Исправлена обработка thread ID `"followers"` отдельно от shout/reaction threads
|
||||
- **Уведомления о новых публикациях**: Добавлена обработка уведомлений о новых shouts в `notifications_seen_thread`
|
||||
- При открытии публикации уведомления о ней тоже помечаются как прочитанные
|
||||
- Исправлена логика парсинга thread ID для поддержки разных форматов
|
||||
- **Code Quality**: Использованы enum константы (`NotificationAction`, `NotificationEntity`) вместо строк
|
||||
- **Убраны устаревшие TODO**: Удален TODO про `notification_id` как offset (текущая логика с timestamp работает корректно)
|
||||
|
||||
### Technical Details
|
||||
- `core/resolvers/notifier.py`: расширена функция `notifications_seen_thread` для поддержки всех типов уведомлений
|
||||
- Добавлена обработка `thread == "followers"` для уведомлений о подписках
|
||||
- Добавлена обработка `NotificationEntity.SHOUT` для уведомлений о новых публикациях
|
||||
- Улучшена обработка ошибок с `logger.warning()` вместо исключений
|
||||
|
||||
## [0.9.30] - 2025-10-02
|
||||
|
||||
### 🔧 Fixed
|
||||
- **Ревалидация кеша featured материалов**: Критическое исправление инвалидации кеша при изменении featured статуса
|
||||
- Добавлены ключи кеша для featured материалов в `invalidate_shout_related_cache`
|
||||
- Исправлена функция `set_featured`: добавлена инвалидация кеша лент
|
||||
- Исправлена функция `set_unfeatured`: добавлена инвалидация кеша лент
|
||||
- Теперь материалы корректно появляются/исчезают с главной страницы при фичеринге/расфичеринге
|
||||
- Улучшена производительность через асинхронную инвалидацию кеша
|
||||
|
||||
### ✅ Code Quality
|
||||
- **Python Standards Compliance**: Код соответствует стандартам 003-python-standards.mdc
|
||||
- Пройдены проверки Ruff (linting & formatting)
|
||||
- Пройдены проверки MyPy (type checking)
|
||||
- Все функции имеют типы и докстринги
|
||||
- Тесты проходят успешно
|
||||
|
||||
## [0.9.29] - 2025-10-01
|
||||
|
||||
### 🔧 Fixed
|
||||
- **Фичерение публикаций**: Исправлена логика автоматического фичерения/расфичерения
|
||||
- Теперь учитываются все положительные реакции (LIKE, ACCEPT, PROOF), а не только LIKE
|
||||
- Исправлен подсчет реакций в `check_to_unfeature`: используется POSITIVE + NEGATIVE вместо только RATING_REACTIONS
|
||||
- Добавлена явная проверка `reply_to.is_(None)` для исключения комментариев
|
||||
- **Ревалидация кеша**: Добавлена ревалидация кеша публикаций, авторов и тем при изменении `featured_at`
|
||||
- Улучшено логирование для отладки процесса фичерения
|
||||
|
||||
## [0.9.28] - 2025-09-28
|
||||
|
||||
### 🍪 CRITICAL Cross-Origin Auth
|
||||
|
||||
@@ -160,7 +160,7 @@ core/
|
||||
### Environment Variables
|
||||
- `DATABASE_URL` - Database connection string
|
||||
- `REDIS_URL` - Redis connection string
|
||||
- `JWT_SECRET` - JWT signing secret
|
||||
- `JWT_SECRET_KEY` - JWT signing secret
|
||||
- `OAUTH_*` - OAuth provider credentials
|
||||
|
||||
### Database
|
||||
|
||||
@@ -819,16 +819,16 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon
|
||||
token_data["client_secret"] = client.client_secret
|
||||
|
||||
async with httpx.AsyncClient() as http_client:
|
||||
response = await http_client.post(
|
||||
token_response = await http_client.post(
|
||||
token_endpoint, data=token_data, headers={"Accept": "application/json"}
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
error_msg = f"Token request failed: {response.status_code} - {response.text}"
|
||||
if token_response.status_code != 200:
|
||||
error_msg = f"Token request failed: {token_response.status_code} - {token_response.text}"
|
||||
logger.error(f"❌ {error_msg}")
|
||||
raise ValueError(error_msg)
|
||||
|
||||
token = response.json()
|
||||
token = token_response.json()
|
||||
else:
|
||||
# Провайдеры с PKCE поддержкой
|
||||
code_verifier = oauth_data.get("code_verifier")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.2.5/schema.json",
|
||||
"files": {
|
||||
"includes": [
|
||||
"**/*.tsx",
|
||||
|
||||
10
cache/cache.py
vendored
10
cache/cache.py
vendored
@@ -513,6 +513,10 @@ async def invalidate_shout_related_cache(shout: Shout, author_id: int) -> None:
|
||||
"unrated", # неоцененные
|
||||
"recent", # последние
|
||||
"coauthored", # совместные
|
||||
# 🔧 Добавляем ключи с featured материалами
|
||||
"featured", # featured публикации
|
||||
"featured:recent", # недавние featured
|
||||
"featured:top", # топ featured
|
||||
}
|
||||
|
||||
# Добавляем ключи авторов
|
||||
@@ -523,6 +527,12 @@ async def invalidate_shout_related_cache(shout: Shout, author_id: int) -> None:
|
||||
cache_keys.update(f"topic_{t.id}" for t in shout.topics)
|
||||
cache_keys.update(f"topic_shouts_{t.id}" for t in shout.topics)
|
||||
|
||||
# 🔧 Добавляем ключи featured материалов для каждой темы
|
||||
for topic in shout.topics:
|
||||
cache_keys.update(
|
||||
[f"topic_{topic.id}:featured", f"topic_{topic.id}:featured:recent", f"topic_{topic.id}:featured:top"]
|
||||
)
|
||||
|
||||
await invalidate_shouts_cache(list(cache_keys))
|
||||
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ SESSION_COOKIE_SAMESITE=lax
|
||||
SESSION_COOKIE_MAX_AGE=2592000 # 30 дней
|
||||
|
||||
# JWT
|
||||
JWT_SECRET=your_jwt_secret_key
|
||||
JWT_SECRET_KEY=your_jwt_secret_key
|
||||
JWT_EXPIRATION_HOURS=720 # 30 дней
|
||||
|
||||
# Redis
|
||||
|
||||
@@ -520,7 +520,7 @@ async def test_redis_integration():
|
||||
### Подготовка
|
||||
- [ ] Настроен Redis connection pool с теми же параметрами
|
||||
- [ ] Установлены зависимости: `auth.tokens.*`, `auth.utils`
|
||||
- [ ] Настроены environment variables (JWT_SECRET, REDIS_URL)
|
||||
- [ ] Настроены environment variables (JWT_SECRET_KEY, REDIS_URL)
|
||||
|
||||
### Реализация
|
||||
- [ ] Реализована функция извлечения токенов из запросов
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
```python
|
||||
# settings.py
|
||||
JWT_ALGORITHM = "HS256" # HMAC with SHA-256
|
||||
JWT_SECRET = os.getenv("JWT_SECRET") # Минимум 256 бит
|
||||
JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY") # Минимум 256 бит
|
||||
JWT_EXPIRATION_DELTA = 30 * 24 * 60 * 60 # 30 дней
|
||||
```
|
||||
|
||||
@@ -439,7 +439,7 @@ async def detect_anomalies(user_id: str, event_type: str, ip_address: str):
|
||||
### Environment Variables
|
||||
```bash
|
||||
# JWT Security
|
||||
JWT_SECRET=your_super_secret_key_minimum_256_bits
|
||||
JWT_SECRET_KEY=your_super_secret_key_minimum_256_bits
|
||||
JWT_ALGORITHM=HS256
|
||||
JWT_EXPIRATION_HOURS=720
|
||||
|
||||
|
||||
@@ -281,7 +281,7 @@ async def delete_session(token: str) -> bool:
|
||||
|
||||
### JWT токены
|
||||
- **Алгоритм**: HS256
|
||||
- **Secret**: Из переменной окружения JWT_SECRET
|
||||
- **Secret**: Из переменной окружения JWT_SECRET_KEY
|
||||
- **Payload**: `{user_id, username, iat, exp}`
|
||||
- **Expiration**: 30 дней (настраивается)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
```bash
|
||||
# JWT настройки
|
||||
JWT_SECRET=your_super_secret_key_minimum_256_bits
|
||||
JWT_SECRET_KEY=your_super_secret_key_minimum_256_bits
|
||||
JWT_ALGORITHM=HS256
|
||||
JWT_EXPIRATION_HOURS=720 # 30 дней
|
||||
|
||||
@@ -69,7 +69,7 @@ LOCKOUT_DURATION=1800 # 30 минут
|
||||
# Проверка переменных окружения
|
||||
python -c "
|
||||
import os
|
||||
required = ['JWT_SECRET', 'REDIS_URL', 'GOOGLE_CLIENT_ID']
|
||||
required = ['JWT_SECRET_KEY', 'REDIS_URL', 'GOOGLE_CLIENT_ID']
|
||||
for var in required:
|
||||
print(f'{var}: {\"✅\" if os.getenv(var) else \"❌\"}')"
|
||||
|
||||
@@ -213,7 +213,7 @@ ab -n 1000 -c 10 -p login.json -T application/json http://localhost:8000/graphql
|
||||
### Docker
|
||||
```dockerfile
|
||||
# Dockerfile
|
||||
ENV JWT_SECRET=your_secret_here
|
||||
ENV JWT_SECRET_KEY=your_secret_here
|
||||
ENV REDIS_URL=redis://redis:6379/0
|
||||
ENV SESSION_COOKIE_SECURE=true
|
||||
```
|
||||
@@ -221,8 +221,8 @@ ENV SESSION_COOKIE_SECURE=true
|
||||
### Dokku/Heroku
|
||||
```bash
|
||||
# Установка переменных окружения
|
||||
dokku config:set myapp JWT_SECRET=xxx REDIS_URL=yyy
|
||||
heroku config:set JWT_SECRET=xxx REDIS_URL=yyy
|
||||
dokku config:set myapp JWT_SECRET_KEY=xxx REDIS_URL=yyy
|
||||
heroku config:set JWT_SECRET_KEY=xxx REDIS_URL=yyy
|
||||
```
|
||||
|
||||
### Nginx настройки
|
||||
|
||||
@@ -330,7 +330,7 @@ OAuth данные хранятся в JSON поле `oauth` модели `Autho
|
||||
### Переменные окружения
|
||||
```bash
|
||||
# JWT настройки
|
||||
JWT_SECRET=your_super_secret_key
|
||||
JWT_SECRET_KEY=your_super_secret_key
|
||||
JWT_EXPIRATION_HOURS=720 # 30 дней
|
||||
|
||||
# Redis подключение
|
||||
|
||||
@@ -819,7 +819,7 @@ jobs:
|
||||
pytest tests/auth/e2e/ -m e2e
|
||||
env:
|
||||
REDIS_URL: redis://localhost:6379/0
|
||||
JWT_SECRET: test_secret_key_for_ci
|
||||
JWT_SECRET_KEY: test_secret_key_for_ci
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
|
||||
@@ -127,7 +127,7 @@ env_vars:{variable_name} # STRING - значение перемен
|
||||
|
||||
### Примеры переменных
|
||||
```redis
|
||||
GET env_vars:JWT_SECRET # Секретный ключ JWT
|
||||
GET env_vars:JWT_SECRET_KEY # Секретный ключ JWT
|
||||
GET env_vars:REDIS_URL # URL Redis
|
||||
GET env_vars:OAUTH_GOOGLE_CLIENT_ID # Google OAuth Client ID
|
||||
GET env_vars:FEATURE_REGISTRATION # Флаг функции регистрации
|
||||
@@ -135,7 +135,7 @@ GET env_vars:FEATURE_REGISTRATION # Флаг функции регистра
|
||||
|
||||
**Категории переменных**:
|
||||
- **database**: DB_URL, POSTGRES_*
|
||||
- **auth**: JWT_SECRET, OAUTH_*
|
||||
- **auth**: JWT_SECRET_KEY, OAUTH_*
|
||||
- **redis**: REDIS_URL, REDIS_HOST, REDIS_PORT
|
||||
- **search**: SEARCH_*
|
||||
- **integrations**: GOOGLE_ANALYTICS_ID, SENTRY_DSN, SMTP_*
|
||||
|
||||
@@ -99,6 +99,23 @@ class NotificationSeen(Base):
|
||||
)
|
||||
|
||||
|
||||
class NotificationUnsubscribe(Base):
|
||||
"""Модель для хранения отписок пользователей от уведомлений по определенным thread_id."""
|
||||
|
||||
__tablename__ = "notification_unsubscribe"
|
||||
|
||||
author_id: Mapped[int] = mapped_column(ForeignKey("author.id"), nullable=False)
|
||||
thread_id: Mapped[str] = mapped_column(String, nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint(author_id, thread_id),
|
||||
Index("idx_notification_unsubscribe_author", "author_id"),
|
||||
Index("idx_notification_unsubscribe_thread", "thread_id"),
|
||||
{"extend_existing": True},
|
||||
)
|
||||
|
||||
|
||||
class Notification(Base):
|
||||
__tablename__ = "notification"
|
||||
|
||||
|
||||
518
package-lock.json
generated
518
package-lock.json
generated
@@ -1,32 +1,32 @@
|
||||
{
|
||||
"name": "publy-panel",
|
||||
"version": "0.9.28",
|
||||
"version": "0.9.30",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "publy-panel",
|
||||
"version": "0.9.28",
|
||||
"version": "0.9.30",
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.2.4",
|
||||
"@biomejs/biome": "^2.2.5",
|
||||
"@graphql-codegen/cli": "^6.0.0",
|
||||
"@graphql-codegen/client-preset": "^5.0.1",
|
||||
"@graphql-codegen/client-preset": "^5.0.2",
|
||||
"@graphql-codegen/introspection": "^5.0.0",
|
||||
"@graphql-codegen/typescript": "^5.0.0",
|
||||
"@graphql-codegen/typescript-operations": "^5.0.0",
|
||||
"@graphql-codegen/typescript-resolvers": "^5.0.0",
|
||||
"@graphql-codegen/typescript": "^5.0.1",
|
||||
"@graphql-codegen/typescript-operations": "^5.0.1",
|
||||
"@graphql-codegen/typescript-resolvers": "^5.0.1",
|
||||
"@solidjs/router": "^0.15.3",
|
||||
"@types/node": "^24.5.2",
|
||||
"@types/node": "^24.6.2",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"graphql": "^16.11.0",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"lightningcss": "^1.30.1",
|
||||
"lightningcss": "^1.30.2",
|
||||
"prismjs": "^1.30.0",
|
||||
"solid-js": "^1.9.9",
|
||||
"terser": "^5.44.0",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^7.1.7",
|
||||
"vite-plugin-solid": "^2.11.7"
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.1.9",
|
||||
"vite-plugin-solid": "^2.11.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@ardatan/relay-compiler": {
|
||||
@@ -347,9 +347,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/biome": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.2.4.tgz",
|
||||
"integrity": "sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.2.5.tgz",
|
||||
"integrity": "sha512-zcIi+163Rc3HtyHbEO7CjeHq8DjQRs40HsGbW6vx2WI0tg8mYQOPouhvHSyEnCBAorfYNnKdR64/IxO7xQ5faw==",
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"bin": {
|
||||
@@ -363,20 +363,20 @@
|
||||
"url": "https://opencollective.com/biome"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@biomejs/cli-darwin-arm64": "2.2.4",
|
||||
"@biomejs/cli-darwin-x64": "2.2.4",
|
||||
"@biomejs/cli-linux-arm64": "2.2.4",
|
||||
"@biomejs/cli-linux-arm64-musl": "2.2.4",
|
||||
"@biomejs/cli-linux-x64": "2.2.4",
|
||||
"@biomejs/cli-linux-x64-musl": "2.2.4",
|
||||
"@biomejs/cli-win32-arm64": "2.2.4",
|
||||
"@biomejs/cli-win32-x64": "2.2.4"
|
||||
"@biomejs/cli-darwin-arm64": "2.2.5",
|
||||
"@biomejs/cli-darwin-x64": "2.2.5",
|
||||
"@biomejs/cli-linux-arm64": "2.2.5",
|
||||
"@biomejs/cli-linux-arm64-musl": "2.2.5",
|
||||
"@biomejs/cli-linux-x64": "2.2.5",
|
||||
"@biomejs/cli-linux-x64-musl": "2.2.5",
|
||||
"@biomejs/cli-win32-arm64": "2.2.5",
|
||||
"@biomejs/cli-win32-x64": "2.2.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-arm64": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.4.tgz",
|
||||
"integrity": "sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.5.tgz",
|
||||
"integrity": "sha512-MYT+nZ38wEIWVcL5xLyOhYQQ7nlWD0b/4mgATW2c8dvq7R4OQjt/XGXFkXrmtWmQofaIM14L7V8qIz/M+bx5QQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -391,9 +391,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-x64": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.4.tgz",
|
||||
"integrity": "sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.5.tgz",
|
||||
"integrity": "sha512-FLIEl73fv0R7dI10EnEiZLw+IMz3mWLnF95ASDI0kbx6DDLJjWxE5JxxBfmG+udz1hIDd3fr5wsuP7nwuTRdAg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -408,9 +408,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.4.tgz",
|
||||
"integrity": "sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.5.tgz",
|
||||
"integrity": "sha512-5DjiiDfHqGgR2MS9D+AZ8kOfrzTGqLKywn8hoXpXXlJXIECGQ32t+gt/uiS2XyGBM2XQhR6ztUvbjZWeccFMoQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -425,9 +425,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.4.tgz",
|
||||
"integrity": "sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.5.tgz",
|
||||
"integrity": "sha512-5Ov2wgAFwqDvQiESnu7b9ufD1faRa+40uwrohgBopeY84El2TnBDoMNXx6iuQdreoFGjwW8vH6k68G21EpNERw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -442,9 +442,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.4.tgz",
|
||||
"integrity": "sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.5.tgz",
|
||||
"integrity": "sha512-fq9meKm1AEXeAWan3uCg6XSP5ObA6F/Ovm89TwaMiy1DNIwdgxPkNwxlXJX8iM6oRbFysYeGnT0OG8diCWb9ew==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -459,9 +459,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64-musl": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.4.tgz",
|
||||
"integrity": "sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.5.tgz",
|
||||
"integrity": "sha512-AVqLCDb/6K7aPNIcxHaTQj01sl1m989CJIQFQEaiQkGr2EQwyOpaATJ473h+nXDUuAcREhccfRpe/tu+0wu0eQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -476,9 +476,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-arm64": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.4.tgz",
|
||||
"integrity": "sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.5.tgz",
|
||||
"integrity": "sha512-xaOIad4wBambwJa6mdp1FigYSIF9i7PCqRbvBqtIi9y29QtPVQ13sDGtUnsRoe6SjL10auMzQ6YAe+B3RpZXVg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -493,9 +493,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-x64": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.4.tgz",
|
||||
"integrity": "sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.5.tgz",
|
||||
"integrity": "sha512-F/jhuXCssPFAuciMhHKk00xnCAxJRS/pUzVfXYmOMUp//XW7mO6QeCjsjvnm8L4AO/dG2VOB0O+fJPiJ2uXtIw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1088,21 +1088,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-codegen/client-preset": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-5.0.1.tgz",
|
||||
"integrity": "sha512-3dXS7Sh/AkV+Ewq/HB1DSCb0tZBOIdTL8zkGQjRKWaf14x21h2f/xKl2zhRh6KlXjcCrIpX+AxHAhQxs6cXwVw==",
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-5.0.2.tgz",
|
||||
"integrity": "sha512-lBkVMz7QA7FHWb71BcNB/tFFOh0LDNCPIBaJ70Lj1SIPjOfCEYmbkK6D5piPZu87m60hyWN3XDwNHEH8eGoXNA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.20.2",
|
||||
"@babel/template": "^7.20.7",
|
||||
"@graphql-codegen/add": "^6.0.0",
|
||||
"@graphql-codegen/gql-tag-operations": "5.0.0",
|
||||
"@graphql-codegen/gql-tag-operations": "5.0.1",
|
||||
"@graphql-codegen/plugin-helpers": "^6.0.0",
|
||||
"@graphql-codegen/typed-document-node": "^6.0.0",
|
||||
"@graphql-codegen/typescript": "^5.0.0",
|
||||
"@graphql-codegen/typescript-operations": "^5.0.0",
|
||||
"@graphql-codegen/visitor-plugin-common": "^6.0.0",
|
||||
"@graphql-codegen/typed-document-node": "^6.0.1",
|
||||
"@graphql-codegen/typescript": "^5.0.1",
|
||||
"@graphql-codegen/typescript-operations": "^5.0.1",
|
||||
"@graphql-codegen/visitor-plugin-common": "^6.0.1",
|
||||
"@graphql-tools/documents": "^1.0.0",
|
||||
"@graphql-tools/utils": "^10.0.0",
|
||||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
@@ -1155,14 +1155,14 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@graphql-codegen/gql-tag-operations": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-5.0.0.tgz",
|
||||
"integrity": "sha512-kC2pc/tyzVc1laZtlfuQHqYxF4UqB4YXzAboFfeY1cxrxCh/+H70jHnfA1O4vhPndiRd+XZA8wxPv0hIqDXYaA==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-5.0.1.tgz",
|
||||
"integrity": "sha512-GVd/B6mtRAXg6UxgeO805P7VDrCmVIb6qIMrE7O69j8e4EqIt/URdmJ7On+Bn8IIKp7TcpcLSo/VI28ptcssNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-codegen/plugin-helpers": "^6.0.0",
|
||||
"@graphql-codegen/visitor-plugin-common": "6.0.0",
|
||||
"@graphql-codegen/visitor-plugin-common": "6.0.1",
|
||||
"@graphql-tools/utils": "^10.0.0",
|
||||
"auto-bind": "~4.0.0",
|
||||
"tslib": "~2.6.0"
|
||||
@@ -1260,14 +1260,14 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@graphql-codegen/typed-document-node": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-6.0.0.tgz",
|
||||
"integrity": "sha512-OYmbadwvjq19yCZjioy901pLI9YV6i7A0fP3MpcJlo2uQVY27RJPcN2NeLfFzXdHr6f5bm9exqB6X1iKimfA2Q==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-6.0.1.tgz",
|
||||
"integrity": "sha512-z0vvvmwfdozkY1AFqbNLeb/jAWyVwWJOIllZEEwPDKcVtCMPQZ1DRApPMRDRndRL6fOG4aXXnt7C5kgniC+qGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-codegen/plugin-helpers": "^6.0.0",
|
||||
"@graphql-codegen/visitor-plugin-common": "6.0.0",
|
||||
"@graphql-codegen/visitor-plugin-common": "6.0.1",
|
||||
"auto-bind": "~4.0.0",
|
||||
"change-case-all": "1.0.15",
|
||||
"tslib": "~2.6.0"
|
||||
@@ -1287,15 +1287,15 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@graphql-codegen/typescript": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-5.0.0.tgz",
|
||||
"integrity": "sha512-u90SGM6+Rdc3Je1EmVQOrGk5fl7hK1cLR4y5Q1MeUenj0aZFxKno65DCW7RcQpcfebvkPsVGA6y3oS02wPFj6Q==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-5.0.1.tgz",
|
||||
"integrity": "sha512-GqAl4pxFdWTvW1h+Ume7djrucYwt03wiaS88m4ErG+tHsJaR2ZCtoHOo+B4bh7KIuBKap14/xOZG0qY/ThWAhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-codegen/plugin-helpers": "^6.0.0",
|
||||
"@graphql-codegen/schema-ast": "^5.0.0",
|
||||
"@graphql-codegen/visitor-plugin-common": "6.0.0",
|
||||
"@graphql-codegen/visitor-plugin-common": "6.0.1",
|
||||
"auto-bind": "~4.0.0",
|
||||
"tslib": "~2.6.0"
|
||||
},
|
||||
@@ -1307,15 +1307,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-codegen/typescript-operations": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-5.0.0.tgz",
|
||||
"integrity": "sha512-mqgp/lp5v7w+RYj5AJ/BVquP+sgje3EAgg++62ciolOB5zzWT8en09cRdNq4UZfszCYTOtlhCG7NQAAcSae37A==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-5.0.1.tgz",
|
||||
"integrity": "sha512-uJwsOIqvXyxlOI1Mnoy8Mn3TiOHTzVTGDwqL9gHnpKqQZdFfvMgfDf/HyT7Mw3XCOfhSS99fe9ATW0bkMExBZg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-codegen/plugin-helpers": "^6.0.0",
|
||||
"@graphql-codegen/typescript": "^5.0.0",
|
||||
"@graphql-codegen/visitor-plugin-common": "6.0.0",
|
||||
"@graphql-codegen/typescript": "^5.0.1",
|
||||
"@graphql-codegen/visitor-plugin-common": "6.0.1",
|
||||
"auto-bind": "~4.0.0",
|
||||
"tslib": "~2.6.0"
|
||||
},
|
||||
@@ -1340,15 +1340,15 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@graphql-codegen/typescript-resolvers": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-5.0.0.tgz",
|
||||
"integrity": "sha512-etUYZYwpBM+EmmcH/TtK9+dCzFMM36gI9aIc4/ckDnT34SLWnWVAkbfeNetwzhq98FD84SL5d+YqLGRFeEylJw==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-5.0.1.tgz",
|
||||
"integrity": "sha512-E3Dyc2gaI4I79Wgwvwo5HP2MMQkUrcGA+3Lfx/ckDlE8zi3wwjWMhAjIhW54VQbi8q2/9h7ooRph3eat9VTscA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-codegen/plugin-helpers": "^6.0.0",
|
||||
"@graphql-codegen/typescript": "^5.0.0",
|
||||
"@graphql-codegen/visitor-plugin-common": "6.0.0",
|
||||
"@graphql-codegen/typescript": "^5.0.1",
|
||||
"@graphql-codegen/visitor-plugin-common": "6.0.1",
|
||||
"@graphql-tools/utils": "^10.0.0",
|
||||
"auto-bind": "~4.0.0",
|
||||
"tslib": "~2.6.0"
|
||||
@@ -1381,9 +1381,9 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@graphql-codegen/visitor-plugin-common": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-6.0.0.tgz",
|
||||
"integrity": "sha512-K05Jv2elOeFstH3i+Ah0Pi9do6NYUvrbdhEkP+UvP9fmIro1hCKwcIEP7j4VFz8mt3gAC3dB5KVJDoyaPUgi4Q==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-6.0.1.tgz",
|
||||
"integrity": "sha512-3gopoUYXn26PSj2UdCWmYj0QiRVD5qR3eDiXx72OQcN1Vb8qj6VfOWB+NDuD1Q1sgVYbCQVKgj92ERsSW1xH9Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2387,9 +2387,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz",
|
||||
"integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz",
|
||||
"integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2401,9 +2401,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz",
|
||||
"integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz",
|
||||
"integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2415,9 +2415,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz",
|
||||
"integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz",
|
||||
"integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2429,9 +2429,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz",
|
||||
"integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz",
|
||||
"integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2443,9 +2443,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz",
|
||||
"integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz",
|
||||
"integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2457,9 +2457,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz",
|
||||
"integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz",
|
||||
"integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2471,9 +2471,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz",
|
||||
"integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz",
|
||||
"integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2485,9 +2485,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz",
|
||||
"integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz",
|
||||
"integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2499,9 +2499,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz",
|
||||
"integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz",
|
||||
"integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2513,9 +2513,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz",
|
||||
"integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz",
|
||||
"integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2527,9 +2527,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz",
|
||||
"integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz",
|
||||
"integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -2541,9 +2541,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz",
|
||||
"integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz",
|
||||
"integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -2555,9 +2555,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz",
|
||||
"integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz",
|
||||
"integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -2569,9 +2569,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz",
|
||||
"integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz",
|
||||
"integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -2583,9 +2583,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz",
|
||||
"integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz",
|
||||
"integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -2597,9 +2597,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz",
|
||||
"integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz",
|
||||
"integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2611,9 +2611,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz",
|
||||
"integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz",
|
||||
"integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2625,9 +2625,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz",
|
||||
"integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz",
|
||||
"integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2639,9 +2639,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz",
|
||||
"integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz",
|
||||
"integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2653,9 +2653,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz",
|
||||
"integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz",
|
||||
"integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -2667,9 +2667,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz",
|
||||
"integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz",
|
||||
"integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2681,9 +2681,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz",
|
||||
"integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz",
|
||||
"integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2794,13 +2794,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz",
|
||||
"integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==",
|
||||
"version": "24.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.2.tgz",
|
||||
"integrity": "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.12.0"
|
||||
"undici-types": "~7.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prismjs": {
|
||||
@@ -3030,9 +3030,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.8.tgz",
|
||||
"integrity": "sha512-be0PUaPsQX/gPWWgFsdD+GFzaoig5PXaUC1xLkQiYdDnANU8sMnHoQd8JhbJQuvTWrWLyeFN9Imb5Qtfvr4RrQ==",
|
||||
"version": "2.8.10",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.10.tgz",
|
||||
"integrity": "sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -3063,9 +3063,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.26.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz",
|
||||
"integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==",
|
||||
"version": "4.26.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz",
|
||||
"integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3083,9 +3083,9 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.3",
|
||||
"caniuse-lite": "^1.0.30001741",
|
||||
"electron-to-chromium": "^1.5.218",
|
||||
"baseline-browser-mapping": "^2.8.9",
|
||||
"caniuse-lite": "^1.0.30001746",
|
||||
"electron-to-chromium": "^1.5.227",
|
||||
"node-releases": "^2.0.21",
|
||||
"update-browserslist-db": "^1.1.3"
|
||||
},
|
||||
@@ -3135,9 +3135,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001745",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001745.tgz",
|
||||
"integrity": "sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==",
|
||||
"version": "1.0.30001747",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001747.tgz",
|
||||
"integrity": "sha512-mzFa2DGIhuc5490Nd/G31xN1pnBnYMadtkyTjefPI7wzypqgCEpeWu9bJr0OnDsyKrW75zA9ZAt7pbQFmwLsQg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3588,9 +3588,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.227",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.227.tgz",
|
||||
"integrity": "sha512-ITxuoPfJu3lsNWUi2lBM2PaBPYgH3uqmxut5vmBxgYvyI4AlJ6P3Cai1O76mOrkJCBzq0IxWg/NtqOrpu/0gKA==",
|
||||
"version": "1.5.230",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.230.tgz",
|
||||
"integrity": "sha512-A6A6Fd3+gMdaed9wX83CvHYJb4UuapPD5X5SLq72VZJzxHSY0/LUweGXRWmQlh2ln7KV7iw7jnwXK7dlPoOnHQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -4292,9 +4292,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.0.tgz",
|
||||
"integrity": "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==",
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -4369,9 +4369,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
||||
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
|
||||
"integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
@@ -4385,22 +4385,44 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"lightningcss-darwin-arm64": "1.30.1",
|
||||
"lightningcss-darwin-x64": "1.30.1",
|
||||
"lightningcss-freebsd-x64": "1.30.1",
|
||||
"lightningcss-linux-arm-gnueabihf": "1.30.1",
|
||||
"lightningcss-linux-arm64-gnu": "1.30.1",
|
||||
"lightningcss-linux-arm64-musl": "1.30.1",
|
||||
"lightningcss-linux-x64-gnu": "1.30.1",
|
||||
"lightningcss-linux-x64-musl": "1.30.1",
|
||||
"lightningcss-win32-arm64-msvc": "1.30.1",
|
||||
"lightningcss-win32-x64-msvc": "1.30.1"
|
||||
"lightningcss-android-arm64": "1.30.2",
|
||||
"lightningcss-darwin-arm64": "1.30.2",
|
||||
"lightningcss-darwin-x64": "1.30.2",
|
||||
"lightningcss-freebsd-x64": "1.30.2",
|
||||
"lightningcss-linux-arm-gnueabihf": "1.30.2",
|
||||
"lightningcss-linux-arm64-gnu": "1.30.2",
|
||||
"lightningcss-linux-arm64-musl": "1.30.2",
|
||||
"lightningcss-linux-x64-gnu": "1.30.2",
|
||||
"lightningcss-linux-x64-musl": "1.30.2",
|
||||
"lightningcss-win32-arm64-msvc": "1.30.2",
|
||||
"lightningcss-win32-x64-msvc": "1.30.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-android-arm64": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
|
||||
"integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-arm64": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz",
|
||||
"integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
|
||||
"integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4419,9 +4441,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-x64": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz",
|
||||
"integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
|
||||
"integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4440,9 +4462,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-freebsd-x64": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz",
|
||||
"integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
|
||||
"integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4461,9 +4483,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz",
|
||||
"integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
|
||||
"integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -4482,9 +4504,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-gnu": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz",
|
||||
"integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
|
||||
"integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4503,9 +4525,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-musl": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz",
|
||||
"integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
|
||||
"integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4524,9 +4546,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-gnu": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz",
|
||||
"integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
|
||||
"integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4545,9 +4567,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-musl": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz",
|
||||
"integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
|
||||
"integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4566,9 +4588,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-arm64-msvc": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz",
|
||||
"integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
|
||||
"integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4587,9 +4609,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-x64-msvc": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
|
||||
"integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
|
||||
"integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -5408,9 +5430,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz",
|
||||
"integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
|
||||
"integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5424,28 +5446,28 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.52.3",
|
||||
"@rollup/rollup-android-arm64": "4.52.3",
|
||||
"@rollup/rollup-darwin-arm64": "4.52.3",
|
||||
"@rollup/rollup-darwin-x64": "4.52.3",
|
||||
"@rollup/rollup-freebsd-arm64": "4.52.3",
|
||||
"@rollup/rollup-freebsd-x64": "4.52.3",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.52.3",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.52.3",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.52.3",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.52.3",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.52.3",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.52.3",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.52.3",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.52.3",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.52.3",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.52.3",
|
||||
"@rollup/rollup-linux-x64-musl": "4.52.3",
|
||||
"@rollup/rollup-openharmony-arm64": "4.52.3",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.52.3",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.52.3",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.52.3",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.52.3",
|
||||
"@rollup/rollup-android-arm-eabi": "4.52.4",
|
||||
"@rollup/rollup-android-arm64": "4.52.4",
|
||||
"@rollup/rollup-darwin-arm64": "4.52.4",
|
||||
"@rollup/rollup-darwin-x64": "4.52.4",
|
||||
"@rollup/rollup-freebsd-arm64": "4.52.4",
|
||||
"@rollup/rollup-freebsd-x64": "4.52.4",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.52.4",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.52.4",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.52.4",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.52.4",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.52.4",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.52.4",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.52.4",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.52.4",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.52.4",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.52.4",
|
||||
"@rollup/rollup-linux-x64-musl": "4.52.4",
|
||||
"@rollup/rollup-openharmony-arm64": "4.52.4",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.52.4",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.52.4",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.52.4",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.52.4",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@@ -5903,9 +5925,9 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -5954,9 +5976,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.12.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
|
||||
"integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==",
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz",
|
||||
"integrity": "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -6039,9 +6061,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.1.7",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz",
|
||||
"integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==",
|
||||
"version": "7.1.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz",
|
||||
"integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -6114,9 +6136,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-solid": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-solid/-/vite-plugin-solid-2.11.8.tgz",
|
||||
"integrity": "sha512-hFrCxBfv3B1BmFqnJF4JOCYpjrmi/zwyeKjcomQ0khh8HFyQ8SbuBWQ7zGojfrz6HUOBFrJBNySDi/JgAHytWg==",
|
||||
"version": "2.11.9",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-solid/-/vite-plugin-solid-2.11.9.tgz",
|
||||
"integrity": "sha512-bTA6p+bspXZsuulSd2y6aTzegF8xGaJYcq1Uyh/mv+W4DQtzCgL9nN6n2fsTaxp/dMk+ZHHKgGndlNeooqHLKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
24
package.json
24
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "publy-panel",
|
||||
"version": "0.9.28",
|
||||
"version": "0.9.30",
|
||||
"type": "module",
|
||||
"description": "Publy, a modern platform for collaborative text creation, offers a user-friendly interface for authors, editors, and readers, supporting real-time collaboration and structured feedback.",
|
||||
"scripts": {
|
||||
@@ -13,27 +13,27 @@
|
||||
"codegen": "graphql-codegen --config codegen.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.2.4",
|
||||
"@biomejs/biome": "^2.2.5",
|
||||
"@graphql-codegen/cli": "^6.0.0",
|
||||
"@graphql-codegen/client-preset": "^5.0.1",
|
||||
"@graphql-codegen/client-preset": "^5.0.2",
|
||||
"@graphql-codegen/introspection": "^5.0.0",
|
||||
"@graphql-codegen/typescript": "^5.0.0",
|
||||
"@graphql-codegen/typescript-operations": "^5.0.0",
|
||||
"@graphql-codegen/typescript-resolvers": "^5.0.0",
|
||||
"@graphql-codegen/typescript": "^5.0.1",
|
||||
"@graphql-codegen/typescript-operations": "^5.0.1",
|
||||
"@graphql-codegen/typescript-resolvers": "^5.0.1",
|
||||
"@solidjs/router": "^0.15.3",
|
||||
"@types/node": "^24.5.2",
|
||||
"@types/node": "^24.6.2",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"graphql": "^16.11.0",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"lightningcss": "^1.30.1",
|
||||
"lightningcss": "^1.30.2",
|
||||
"prismjs": "^1.30.0",
|
||||
"solid-js": "^1.9.9",
|
||||
"terser": "^5.44.0",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^7.1.7",
|
||||
"vite-plugin-solid": "^2.11.7"
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.1.9",
|
||||
"vite-plugin-solid": "^2.11.9"
|
||||
},
|
||||
"overrides": {
|
||||
"vite": "^7.1.7"
|
||||
"vite": "^7.1.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export const AuthProvider: Component<AuthProviderProps> = (props) => {
|
||||
// Начинаем с false чтобы избежать мерцания, реальная проверка будет в onMount
|
||||
const [isAuthenticated, setIsAuthenticated] = createSignal(false)
|
||||
const [isReady, setIsReady] = createSignal(false)
|
||||
|
||||
|
||||
// Флаг для предотвращения повторных инициализаций
|
||||
let isInitializing = false
|
||||
|
||||
@@ -82,7 +82,7 @@ export const AuthProvider: Component<AuthProviderProps> = (props) => {
|
||||
console.log('[AuthProvider] Already initializing, skipping...')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
isInitializing = true
|
||||
console.log('[AuthProvider] Performing auth initialization...')
|
||||
|
||||
@@ -91,7 +91,7 @@ export const AuthProvider: Component<AuthProviderProps> = (props) => {
|
||||
console.log('[AuthProvider] Checking authentication via GraphQL...')
|
||||
|
||||
// Добавляем таймаут для запроса (5 секунд для лучшего UX)
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Auth check timeout')), 5000)
|
||||
)
|
||||
|
||||
@@ -159,10 +159,10 @@ export const AuthProvider: Component<AuthProviderProps> = (props) => {
|
||||
|
||||
const logout = async () => {
|
||||
console.log('[AuthProvider] Attempting logout...')
|
||||
|
||||
|
||||
// Предотвращаем повторные инициализации во время logout
|
||||
isInitializing = true
|
||||
|
||||
|
||||
try {
|
||||
// Сначала очищаем токены на клиенте
|
||||
clearAuthTokens()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, createSignal, For, Show } from 'solid-js'
|
||||
import { query } from '../graphql'
|
||||
import type { EnvSection, EnvVariable, Query } from '../graphql/generated/schema'
|
||||
import type { EnvSection, EnvVariable, Query } from '../graphql/generated/graphql'
|
||||
import { ADMIN_UPDATE_ENV_VARIABLE_MUTATION } from '../graphql/mutations'
|
||||
import { ADMIN_GET_ENV_VARIABLES_QUERY } from '../graphql/queries'
|
||||
import EnvVariableModal from '../modals/EnvVariableModal'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "discours-core"
|
||||
version = "0.9.28"
|
||||
version = "0.9.32"
|
||||
description = "Core backend for Discours.io platform"
|
||||
authors = [
|
||||
{name = "Tony Rewin", email = "tonyrewin@yandex.ru"}
|
||||
|
||||
@@ -27,11 +27,15 @@ from utils.logger import root_logger as logger
|
||||
def resolve_roles(obj: dict | Any, info: GraphQLResolveInfo) -> list[str]:
|
||||
"""Резолвер для поля roles автора"""
|
||||
try:
|
||||
# Если это ORM объект с методом get_roles
|
||||
if hasattr(obj, "get_roles"):
|
||||
return obj.get_roles()
|
||||
|
||||
# Если это словарь
|
||||
if isinstance(obj, dict):
|
||||
roles_data = obj.get("roles_data", {})
|
||||
roles_data = obj.get("roles_data")
|
||||
if roles_data is None:
|
||||
return []
|
||||
if isinstance(roles_data, list):
|
||||
return roles_data
|
||||
if isinstance(roles_data, dict):
|
||||
@@ -122,9 +126,12 @@ async def login(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, A
|
||||
domain=SESSION_COOKIE_DOMAIN,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"✅ Admin login: httpOnly cookie установлен для пользователя {result.get('author', {}).get('id')}"
|
||||
author_id = (
|
||||
result.get("author", {}).get("id")
|
||||
if isinstance(result.get("author"), dict)
|
||||
else getattr(result.get("author"), "id", "unknown")
|
||||
)
|
||||
logger.info(f"✅ Admin login: httpOnly cookie установлен для пользователя {author_id}")
|
||||
|
||||
# Для админки НЕ возвращаем токен клиенту - он в httpOnly cookie
|
||||
result_without_token = result.copy()
|
||||
@@ -136,9 +143,12 @@ async def login(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, A
|
||||
|
||||
# Для основного сайта возвращаем токен как обычно (Bearer в localStorage)
|
||||
if not is_admin_request:
|
||||
logger.info(
|
||||
f"✅ Main site login: токен возвращен для localStorage пользователя {result.get('author', {}).get('id')}"
|
||||
author_id = (
|
||||
result.get("author", {}).get("id")
|
||||
if isinstance(result.get("author"), dict)
|
||||
else getattr(result.get("author"), "id", "unknown")
|
||||
)
|
||||
logger.info(f"✅ Main site login: токен возвращен для localStorage пользователя {author_id}")
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
|
||||
@@ -18,7 +18,7 @@ from cache.cache import (
|
||||
from orm.author import Author, AuthorFollower
|
||||
from orm.community import Community, CommunityAuthor, CommunityFollower
|
||||
from orm.reaction import Reaction
|
||||
from orm.shout import Shout, ShoutAuthor, ShoutTopic
|
||||
from orm.shout import Shout, ShoutAuthor, ShoutReactionsFollower, ShoutTopic
|
||||
from orm.topic import Topic
|
||||
from resolvers.stat import get_with_stat
|
||||
from services.auth import login_required
|
||||
@@ -974,12 +974,23 @@ async def get_author_follows(
|
||||
has_access = is_admin or (viewer_id is not None and str(viewer_id) == str(temp_author.id))
|
||||
followed_authors.append(temp_author.dict(has_access))
|
||||
|
||||
# Получаем подписанные шауты
|
||||
followed_shouts = []
|
||||
with local_session() as session:
|
||||
shout_followers = (
|
||||
session.query(ShoutReactionsFollower).filter(ShoutReactionsFollower.follower == author_id).all()
|
||||
)
|
||||
for sf in shout_followers:
|
||||
shout = session.query(Shout).filter(Shout.id == sf.shout).first()
|
||||
if shout:
|
||||
followed_shouts.append(shout.dict())
|
||||
|
||||
followed_communities = DEFAULT_COMMUNITIES # TODO: get followed communities
|
||||
return {
|
||||
"authors": followed_authors,
|
||||
"topics": followed_topics,
|
||||
"communities": followed_communities,
|
||||
"shouts": [],
|
||||
"shouts": followed_shouts,
|
||||
"error": None,
|
||||
}
|
||||
|
||||
|
||||
@@ -274,6 +274,108 @@ async def create_draft(_: None, info: GraphQLResolveInfo, draft_input: dict[str,
|
||||
return {"error": f"Failed to create draft: {e!s}"}
|
||||
|
||||
|
||||
@mutation.field("create_draft_from_shout")
|
||||
@login_required
|
||||
async def create_draft_from_shout(_: None, info: GraphQLResolveInfo, shout_id: int) -> dict[str, Any]:
|
||||
"""
|
||||
Создаёт черновик из существующего опубликованного шаута для редактирования.
|
||||
|
||||
Args:
|
||||
info: GraphQL context
|
||||
shout_id (int): ID публикации (shout)
|
||||
|
||||
Returns:
|
||||
dict: Contains either:
|
||||
- draft: The created draft object with shout reference
|
||||
- error: Error message if creation failed
|
||||
|
||||
Example:
|
||||
>>> async def test_create_from_shout():
|
||||
... context = {'user_id': '123', 'author': {'id': 1}}
|
||||
... info = type('Info', (), {'context': context})()
|
||||
... result = await create_draft_from_shout(None, info, 42)
|
||||
... assert result.get('error') is None
|
||||
... assert result['draft'].shout == 42
|
||||
... return result
|
||||
"""
|
||||
author_dict = info.context.get("author") or {}
|
||||
author_id = author_dict.get("id")
|
||||
|
||||
if not author_id or not isinstance(author_id, int):
|
||||
return {"error": "Author ID is required"}
|
||||
|
||||
try:
|
||||
with local_session() as session:
|
||||
# Загружаем шаут с авторами и темами
|
||||
shout = (
|
||||
session.query(Shout)
|
||||
.options(joinedload(Shout.authors), joinedload(Shout.topics))
|
||||
.where(Shout.id == shout_id)
|
||||
.first()
|
||||
)
|
||||
|
||||
if not shout:
|
||||
return {"error": f"Shout with id={shout_id} not found"}
|
||||
|
||||
# Проверяем, что пользователь является автором шаута
|
||||
author_ids = [a.id for a in shout.authors]
|
||||
if author_id not in author_ids:
|
||||
return {"error": "You are not authorized to edit this shout"}
|
||||
|
||||
# Проверяем, нет ли уже черновика для этого шаута
|
||||
existing_draft = session.query(Draft).where(Draft.shout == shout_id).first()
|
||||
if existing_draft:
|
||||
logger.info(f"Draft already exists for shout {shout_id}: draft_id={existing_draft.id}")
|
||||
return {"draft": create_draft_dict(existing_draft)}
|
||||
|
||||
# Создаём новый черновик из шаута
|
||||
now = int(time.time())
|
||||
draft = Draft(
|
||||
created_at=now,
|
||||
created_by=author_id,
|
||||
community=shout.community,
|
||||
layout=shout.layout or "article",
|
||||
title=shout.title or "",
|
||||
subtitle=shout.subtitle,
|
||||
body=shout.body or "",
|
||||
lead=shout.lead,
|
||||
slug=shout.slug,
|
||||
cover=shout.cover,
|
||||
cover_caption=shout.cover_caption,
|
||||
seo=shout.seo,
|
||||
media=shout.media,
|
||||
lang=shout.lang or "ru",
|
||||
shout=shout_id, # Связываем с существующим шаутом
|
||||
)
|
||||
|
||||
session.add(draft)
|
||||
session.flush()
|
||||
|
||||
# Копируем авторов из шаута
|
||||
for author in shout.authors:
|
||||
da = DraftAuthor(draft=draft.id, author=author.id)
|
||||
session.add(da)
|
||||
|
||||
# Копируем темы из шаута
|
||||
shout_topics = session.query(ShoutTopic).where(ShoutTopic.shout == shout_id).all()
|
||||
for st in shout_topics:
|
||||
dt = DraftTopic(draft=draft.id, topic=st.topic, main=st.main)
|
||||
session.add(dt)
|
||||
|
||||
session.commit()
|
||||
|
||||
logger.info(f"Created draft {draft.id} from shout {shout_id}")
|
||||
|
||||
# Формируем результат
|
||||
draft_dict = create_draft_dict(draft)
|
||||
|
||||
return {"draft": draft_dict}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create draft from shout {shout_id}: {e}", exc_info=True)
|
||||
return {"error": f"Failed to create draft from shout: {e!s}"}
|
||||
|
||||
|
||||
def generate_teaser(body: str, limit: int = 300) -> str:
|
||||
body_text = extract_text(body)
|
||||
return ". ".join(body_text[:limit].split(". ")[:-1])
|
||||
|
||||
@@ -25,8 +25,36 @@ from utils.logger import root_logger as logger
|
||||
|
||||
|
||||
def get_entity_field_name(entity_type: str) -> str:
|
||||
"""Возвращает имя поля для связи с сущностью в модели подписчика"""
|
||||
entity_field_mapping = {"author": "following", "topic": "topic", "community": "community", "shout": "shout"}
|
||||
"""
|
||||
Возвращает имя поля для связи с сущностью в модели подписчика.
|
||||
|
||||
Эта функция используется для определения правильного поля в моделях подписчиков
|
||||
(AuthorFollower, TopicFollower, CommunityFollower, ShoutReactionsFollower) при создании
|
||||
или проверке подписки.
|
||||
|
||||
Args:
|
||||
entity_type: Тип сущности в нижнем регистре ('author', 'topic', 'community', 'shout')
|
||||
|
||||
Returns:
|
||||
str: Имя поля в модели подписчика ('following', 'topic', 'community', 'shout')
|
||||
|
||||
Raises:
|
||||
ValueError: Если передан неизвестный тип сущности
|
||||
|
||||
Examples:
|
||||
>>> get_entity_field_name('author')
|
||||
'following'
|
||||
>>> get_entity_field_name('topic')
|
||||
'topic'
|
||||
>>> get_entity_field_name('invalid')
|
||||
ValueError: Unknown entity_type: invalid
|
||||
"""
|
||||
entity_field_mapping = {
|
||||
"author": "following", # AuthorFollower.following -> Author
|
||||
"topic": "topic", # TopicFollower.topic -> Topic
|
||||
"community": "community", # CommunityFollower.community -> Community
|
||||
"shout": "shout", # ShoutReactionsFollower.shout -> Shout
|
||||
}
|
||||
if entity_type not in entity_field_mapping:
|
||||
msg = f"Unknown entity_type: {entity_type}"
|
||||
raise ValueError(msg)
|
||||
@@ -38,11 +66,54 @@ def get_entity_field_name(entity_type: str) -> str:
|
||||
async def follow(
|
||||
_: None, info: GraphQLResolveInfo, what: str, slug: str = "", entity_id: int | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
GraphQL мутация для создания подписки на автора, тему, сообщество или публикацию.
|
||||
|
||||
Эта функция обрабатывает все типы подписок в системе, включая:
|
||||
- Подписку на автора (AUTHOR)
|
||||
- Подписку на тему (TOPIC)
|
||||
- Подписку на сообщество (COMMUNITY)
|
||||
- Подписку на публикацию (SHOUT)
|
||||
|
||||
Args:
|
||||
_: None - Стандартный параметр GraphQL (не используется)
|
||||
info: GraphQLResolveInfo - Контекст GraphQL запроса, содержит информацию об авторизованном пользователе
|
||||
what: str - Тип сущности для подписки ('AUTHOR', 'TOPIC', 'COMMUNITY', 'SHOUT')
|
||||
slug: str - Slug сущности (например, 'author-slug' или 'topic-slug')
|
||||
entity_id: int | None - ID сущности (альтернатива slug)
|
||||
|
||||
Returns:
|
||||
dict[str, Any] - Результат операции:
|
||||
{
|
||||
"success": bool, # Успешность операции
|
||||
"error": str | None, # Текст ошибки если есть
|
||||
"authors": Author[], # Обновленные авторы (для кеширования)
|
||||
"topics": Topic[], # Обновленные темы (для кеширования)
|
||||
"entity_id": int | None # ID созданной подписки
|
||||
}
|
||||
|
||||
Raises:
|
||||
ValueError: При передаче некорректных параметров
|
||||
DatabaseError: При проблемах с базой данных
|
||||
"""
|
||||
logger.debug("Начало выполнения функции 'follow'")
|
||||
viewer_id = info.context.get("author", {}).get("id")
|
||||
if not viewer_id:
|
||||
return {"error": "Access denied"}
|
||||
follower_dict = info.context.get("author") or {}
|
||||
|
||||
# ✅ КРИТИЧНО: Инвалидируем кеш В САМОМ НАЧАЛЕ, если пользователь авторизован
|
||||
# чтобы предотвратить чтение старых данных при последующей перезагрузке
|
||||
if viewer_id:
|
||||
entity_type = what.lower()
|
||||
cache_key_pattern = f"author:follows-{entity_type}s:{viewer_id}"
|
||||
await redis.execute("DEL", cache_key_pattern)
|
||||
await redis.execute("DEL", f"author:id:{viewer_id}")
|
||||
logger.debug(f"Инвалидирован кеш подписок follower'а: {cache_key_pattern}")
|
||||
|
||||
# Проверка авторизации пользователя
|
||||
if not viewer_id:
|
||||
logger.warning("Попытка подписаться без авторизации")
|
||||
return {"error": "Access denied"}
|
||||
|
||||
logger.debug(f"follower: {follower_dict}")
|
||||
|
||||
if not viewer_id or not follower_dict:
|
||||
@@ -52,6 +123,7 @@ async def follow(
|
||||
follower_id = follower_dict.get("id")
|
||||
logger.debug(f"follower_id: {follower_id}")
|
||||
|
||||
# Маппинг типов сущностей на их классы и методы кеширования
|
||||
entity_classes = {
|
||||
"AUTHOR": (Author, AuthorFollower, get_cached_follower_authors, cache_author),
|
||||
"TOPIC": (Topic, TopicFollower, get_cached_follower_topics, cache_topic),
|
||||
@@ -68,6 +140,10 @@ async def follow(
|
||||
follows: list[dict[str, Any]] = []
|
||||
error: str | None = None
|
||||
|
||||
# ✅ Сохраняем entity_id и error вне сессии для использования после её закрытия
|
||||
entity_id_result: int | None = None
|
||||
error_result: str | None = None
|
||||
|
||||
try:
|
||||
logger.debug("Попытка получить сущность из базы данных")
|
||||
with local_session() as session:
|
||||
@@ -109,9 +185,11 @@ async def follow(
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if existing_sub:
|
||||
logger.info(f"Пользователь {follower_id} уже подписан на {what.lower()} с ID {entity_id}")
|
||||
error = "already following"
|
||||
error_result = "already following"
|
||||
# ✅ КРИТИЧНО: Не делаем return - продолжаем для получения списка подписок
|
||||
else:
|
||||
logger.debug("Добавление новой записи в базу данных")
|
||||
sub = follower_class(follower=follower_id, **{entity_field: entity_id})
|
||||
@@ -120,42 +198,41 @@ async def follow(
|
||||
session.commit()
|
||||
logger.info(f"Пользователь {follower_id} подписался на {what.lower()} с ID {entity_id}")
|
||||
|
||||
# Инвалидируем кэш подписок пользователя после любой операции
|
||||
cache_key_pattern = f"author:follows-{entity_type}s:{follower_id}"
|
||||
await redis.execute("DEL", cache_key_pattern)
|
||||
logger.debug(f"Инвалидирован кэш подписок: {cache_key_pattern}")
|
||||
if cache_method:
|
||||
logger.debug("Обновление кэша сущности")
|
||||
await cache_method(entity_dict)
|
||||
|
||||
if cache_method:
|
||||
logger.debug("Обновление кэша сущности")
|
||||
await cache_method(entity_dict)
|
||||
if what == "AUTHOR":
|
||||
logger.debug("Отправка уведомления автору о подписке")
|
||||
if isinstance(follower_dict, dict) and isinstance(entity_id, int):
|
||||
# Получаем ID созданной записи подписки
|
||||
subscription_id = getattr(sub, "id", None) if "sub" in locals() else None
|
||||
await notify_follower(
|
||||
follower=follower_dict,
|
||||
author_id=entity_id,
|
||||
action="follow",
|
||||
subscription_id=subscription_id,
|
||||
)
|
||||
|
||||
# Инвалидируем кэш подписок пользователя для обновления списка подписок
|
||||
cache_key_pattern = f"author:follows-{entity_type}s:{follower_id}"
|
||||
await redis.execute("DEL", cache_key_pattern)
|
||||
logger.debug(f"Инвалидирован кэш подписок: {cache_key_pattern}")
|
||||
# ✅ КРИТИЧНО: Инвалидируем кеш списка подписчиков автора
|
||||
# чтобы новый подписчик сразу появился в списке
|
||||
await redis.execute("DEL", f"author:followers:{entity_id}")
|
||||
logger.debug(f"Инвалидирован кеш подписчиков автора: author:followers:{entity_id}")
|
||||
|
||||
if what == "AUTHOR" and not existing_sub:
|
||||
logger.debug("Отправка уведомления автору о подписке")
|
||||
if isinstance(follower_dict, dict) and isinstance(entity_id, int):
|
||||
# Получаем ID созданной записи подписки
|
||||
subscription_id = getattr(sub, "id", None) if "sub" in locals() else None
|
||||
await notify_follower(
|
||||
follower=follower_dict,
|
||||
author_id=entity_id,
|
||||
action="follow",
|
||||
subscription_id=subscription_id,
|
||||
)
|
||||
# Инвалидируем кеш статистики авторов для обновления счетчиков подписчиков
|
||||
logger.debug("Инвалидируем кеш статистики авторов")
|
||||
await invalidate_authors_cache(entity_id)
|
||||
|
||||
# Инвалидируем кеш статистики авторов для обновления счетчиков подписчиков
|
||||
logger.debug("Инвалидируем кеш статистики авторов")
|
||||
await invalidate_authors_cache(entity_id)
|
||||
entity_id_result = entity_id
|
||||
|
||||
# Всегда получаем актуальный список подписок для возврата клиенту
|
||||
# ✅ Получаем актуальный список подписок для возврата клиенту
|
||||
# Кеш уже инвалидирован в начале функции, поэтому get_cached_follows_method
|
||||
# вернет свежие данные из БД
|
||||
if get_cached_follows_method and isinstance(follower_id, int):
|
||||
logger.debug("Получение актуального списка подписок из кэша")
|
||||
logger.debug("Получение актуального списка подписок после закрытия сессии")
|
||||
existing_follows = await get_cached_follows_method(follower_id)
|
||||
logger.debug(
|
||||
f"Получено подписок: {len(existing_follows)}, содержит target={entity_id in [f.get('id') for f in existing_follows] if existing_follows else False}"
|
||||
f"Получено подписок: {len(existing_follows)}, содержит target={entity_id_result in [f.get('id') for f in existing_follows] if existing_follows else False}"
|
||||
)
|
||||
|
||||
# Если это авторы, получаем безопасную версию
|
||||
@@ -179,7 +256,7 @@ async def follow(
|
||||
|
||||
logger.debug(f"Актуальный список подписок получен: {len(follows)} элементов")
|
||||
|
||||
return {f"{entity_type}s": follows, "error": error}
|
||||
return {f"{entity_type}s": follows, "error": error_result}
|
||||
|
||||
except Exception as exc:
|
||||
logger.exception("Произошла ошибка в функции 'follow'")
|
||||
@@ -191,11 +268,93 @@ async def follow(
|
||||
async def unfollow(
|
||||
_: None, info: GraphQLResolveInfo, what: str, slug: str = "", entity_id: int | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
GraphQL мутация для отмены подписки на автора, тему, сообщество или публикацию.
|
||||
|
||||
Эта функция обрабатывает отмену всех типов подписок в системе, включая:
|
||||
- Отписку от автора (AUTHOR)
|
||||
- Отписку от темы (TOPIC)
|
||||
- Отписку от сообщества (COMMUNITY)
|
||||
- Отписку от публикации (SHOUT)
|
||||
|
||||
Процесс отмены подписки:
|
||||
1. Проверка авторизации пользователя
|
||||
2. Поиск существующей подписки в базе данных
|
||||
3. Удаление подписки если она найдена
|
||||
4. Инвалидация кеша для обновления данных
|
||||
5. Отправка уведомлений об отписке
|
||||
|
||||
Args:
|
||||
_: None - Стандартный параметр GraphQL (не используется)
|
||||
info: GraphQLResolveInfo - Контекст GraphQL запроса, содержит информацию об авторизованном пользователе
|
||||
what: str - Тип сущности для отписки ('AUTHOR', 'TOPIC', 'COMMUNITY', 'SHOUT')
|
||||
slug: str - Slug сущности (например, 'author-slug' или 'topic-slug')
|
||||
entity_id: int | None - ID сущности (альтернатива slug)
|
||||
|
||||
Returns:
|
||||
dict[str, Any] - Результат операции:
|
||||
{
|
||||
"success": bool, # Успешность операции
|
||||
"error": str | None, # Текст ошибки если есть
|
||||
"authors": Author[], # Обновленные авторы (для кеширования)
|
||||
"topics": Topic[], # Обновленные темы (для кеширования)
|
||||
}
|
||||
|
||||
Raises:
|
||||
ValueError: При передаче некорректных параметров
|
||||
DatabaseError: При проблемах с базой данных
|
||||
|
||||
Examples:
|
||||
# Отписка от автора
|
||||
mutation {
|
||||
unfollow(what: "AUTHOR", slug: "author-slug") {
|
||||
success
|
||||
error
|
||||
}
|
||||
}
|
||||
|
||||
# Отписка от темы
|
||||
mutation {
|
||||
unfollow(what: "TOPIC", slug: "topic-slug") {
|
||||
success
|
||||
error
|
||||
}
|
||||
}
|
||||
|
||||
# Отписка от сообщества
|
||||
mutation {
|
||||
unfollow(what: "COMMUNITY", slug: "community-slug") {
|
||||
success
|
||||
error
|
||||
}
|
||||
}
|
||||
|
||||
# Отписка от публикации
|
||||
mutation {
|
||||
unfollow(what: "SHOUT", entity_id: 123) {
|
||||
success
|
||||
error
|
||||
}
|
||||
}
|
||||
"""
|
||||
logger.debug("Начало выполнения функции 'unfollow'")
|
||||
viewer_id = info.context.get("author", {}).get("id")
|
||||
if not viewer_id:
|
||||
return {"error": "Access denied"}
|
||||
follower_dict = info.context.get("author") or {}
|
||||
|
||||
# ✅ КРИТИЧНО: Инвалидируем кеш В САМОМ НАЧАЛЕ, если пользователь авторизован
|
||||
# чтобы предотвратить чтение старых данных при последующей перезагрузке
|
||||
if viewer_id:
|
||||
entity_type = what.lower()
|
||||
cache_key_pattern = f"author:follows-{entity_type}s:{viewer_id}"
|
||||
await redis.execute("DEL", cache_key_pattern)
|
||||
await redis.execute("DEL", f"author:id:{viewer_id}")
|
||||
logger.debug(f"Инвалидирован кеш подписок В НАЧАЛЕ операции unfollow: {cache_key_pattern}")
|
||||
|
||||
# Проверка авторизации пользователя
|
||||
if not viewer_id:
|
||||
logger.warning("Попытка отписаться без авторизации")
|
||||
return {"error": "Access denied"}
|
||||
|
||||
logger.debug(f"follower: {follower_dict}")
|
||||
|
||||
if not viewer_id or not follower_dict:
|
||||
@@ -205,6 +364,7 @@ async def unfollow(
|
||||
follower_id = follower_dict.get("id")
|
||||
logger.debug(f"follower_id: {follower_id}")
|
||||
|
||||
# Маппинг типов сущностей на их классы и методы кеширования
|
||||
entity_classes = {
|
||||
"AUTHOR": (Author, AuthorFollower, get_cached_follower_authors, cache_author),
|
||||
"TOPIC": (Topic, TopicFollower, get_cached_follower_topics, cache_topic),
|
||||
@@ -262,11 +422,7 @@ async def unfollow(
|
||||
session.commit()
|
||||
logger.info(f"Пользователь {follower_id} отписался от {what.lower()} с ID {entity_id}")
|
||||
|
||||
# Инвалидируем кэш подписок пользователя
|
||||
cache_key_pattern = f"author:follows-{entity_type}s:{follower_id}"
|
||||
await redis.execute("DEL", cache_key_pattern)
|
||||
logger.debug(f"Инвалидирован кэш подписок: {cache_key_pattern}")
|
||||
|
||||
# Кеш подписок follower'а уже инвалидирован в начале функции
|
||||
if get_cached_follows_method and isinstance(follower_id, int):
|
||||
logger.debug("Получение актуального списка подписок из кэша")
|
||||
follows = await get_cached_follows_method(follower_id)
|
||||
@@ -277,6 +433,11 @@ async def unfollow(
|
||||
if what == "AUTHOR" and isinstance(follower_dict, dict):
|
||||
await notify_follower(follower=follower_dict, author_id=entity_id, action="unfollow")
|
||||
|
||||
# ✅ КРИТИЧНО: Инвалидируем кеш списка подписчиков автора
|
||||
# чтобы отписавшийся сразу исчез из списка
|
||||
await redis.execute("DEL", f"author:followers:{entity_id}")
|
||||
logger.debug(f"Инвалидирован кеш подписчиков автора после unfollow: author:followers:{entity_id}")
|
||||
|
||||
# Инвалидируем кеш статистики авторов для обновления счетчиков подписчиков
|
||||
logger.debug("Инвалидируем кеш статистики авторов после отписки")
|
||||
await invalidate_authors_cache(entity_id)
|
||||
|
||||
@@ -16,7 +16,7 @@ from orm.notification import (
|
||||
NotificationEntity,
|
||||
NotificationSeen,
|
||||
)
|
||||
from orm.shout import Shout
|
||||
from orm.shout import Shout, ShoutReactionsFollower
|
||||
from services.auth import login_required
|
||||
from storage.db import local_session
|
||||
from storage.schema import mutation, query
|
||||
@@ -57,6 +57,37 @@ def query_notifications(author_id: int, after: int = 0) -> tuple[int, int, list[
|
||||
return total, unread, notifications
|
||||
|
||||
|
||||
def check_subscription(shout_id: int, current_author_id: int) -> bool:
|
||||
"""
|
||||
Проверяет подписку пользователя на уведомления о шауте.
|
||||
|
||||
Проверяет наличие записи в ShoutReactionsFollower:
|
||||
- Запись есть → подписан
|
||||
- Записи нет → не подписан (отписался или никогда не подписывался)
|
||||
|
||||
Автоматическая подписка (auto=True) создается при:
|
||||
- Создании поста
|
||||
- Первом комментарии/реакции
|
||||
|
||||
Отписка = удаление записи из таблицы
|
||||
|
||||
Returns:
|
||||
bool: True если подписан на уведомления
|
||||
"""
|
||||
with local_session() as session:
|
||||
# Проверяем наличие записи в ShoutReactionsFollower
|
||||
follow = (
|
||||
session.query(ShoutReactionsFollower)
|
||||
.filter(
|
||||
ShoutReactionsFollower.follower == current_author_id,
|
||||
ShoutReactionsFollower.shout == shout_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
return follow is not None
|
||||
|
||||
|
||||
def group_notification(
|
||||
thread: str,
|
||||
authors: list[Any] | None = None,
|
||||
@@ -105,7 +136,6 @@ def get_notifications_grouped(author_id: int, after: int = 0, limit: int = 10, o
|
||||
authors: List[NotificationAuthor], # List of authors involved in the thread.
|
||||
}
|
||||
"""
|
||||
# TODO: use all stats
|
||||
_total, _unread, notifications = query_notifications(author_id, after)
|
||||
groups_by_thread = {}
|
||||
groups_amount = 0
|
||||
@@ -119,14 +149,20 @@ def get_notifications_grouped(author_id: int, after: int = 0, limit: int = 10, o
|
||||
if str(notification.entity) == NotificationEntity.SHOUT.value:
|
||||
shout = payload
|
||||
shout_id = shout.get("id")
|
||||
author_id = shout.get("created_by")
|
||||
shout_author_id = shout.get("created_by")
|
||||
thread_id = f"shout-{shout_id}"
|
||||
|
||||
with local_session() as session:
|
||||
author = session.query(Author).where(Author.id == author_id).first()
|
||||
author = session.query(Author).where(Author.id == shout_author_id).first()
|
||||
shout = session.query(Shout).where(Shout.id == shout_id).first()
|
||||
if author and shout:
|
||||
# Проверяем подписку - если не подписан, пропускаем это уведомление
|
||||
if not check_subscription(shout_id, author_id):
|
||||
continue
|
||||
|
||||
author_dict = author.dict()
|
||||
shout_dict = shout.dict()
|
||||
|
||||
group = group_notification(
|
||||
thread_id,
|
||||
shout=shout_dict,
|
||||
@@ -154,7 +190,8 @@ def get_notifications_grouped(author_id: int, after: int = 0, limit: int = 10, o
|
||||
reply_id = reaction.get("reply_to")
|
||||
thread_id = f"shout-{shout_id}"
|
||||
if reply_id and reaction.get("kind", "").lower() == "comment":
|
||||
thread_id += f"{reply_id}"
|
||||
thread_id = f"shout-{shout_id}::{reply_id}"
|
||||
|
||||
existing_group = groups_by_thread.get(thread_id)
|
||||
if existing_group:
|
||||
existing_group["seen"] = False
|
||||
@@ -163,6 +200,10 @@ def get_notifications_grouped(author_id: int, after: int = 0, limit: int = 10, o
|
||||
existing_group["reactions"].append(reaction)
|
||||
groups_by_thread[thread_id] = existing_group
|
||||
else:
|
||||
# Проверяем подписку - если не подписан, пропускаем это уведомление
|
||||
if not check_subscription(shout_id, author_id):
|
||||
continue
|
||||
|
||||
group = group_notification(
|
||||
thread_id,
|
||||
authors=[author_dict],
|
||||
@@ -214,6 +255,10 @@ async def load_notifications(_: None, info: GraphQLResolveInfo, after: int, limi
|
||||
if author_id:
|
||||
groups_list = get_notifications_grouped(author_id, after, limit)
|
||||
notifications = sorted(groups_list, key=lambda group: group.get("updated_at", 0), reverse=True)
|
||||
|
||||
# Считаем реальное количество сгруппированных уведомлений
|
||||
total = len(notifications)
|
||||
unread = sum(1 for n in notifications if not n.get("seen", False))
|
||||
except Exception as e:
|
||||
error = str(e)
|
||||
logger.error(e)
|
||||
@@ -245,7 +290,7 @@ async def notification_mark_seen(_: None, info: GraphQLResolveInfo, notification
|
||||
@mutation.field("notifications_seen_after")
|
||||
@login_required
|
||||
async def notifications_seen_after(_: None, info: GraphQLResolveInfo, after: int) -> dict:
|
||||
# TODO: use latest loaded notification_id as input offset parameter
|
||||
"""Mark all notifications after given timestamp as seen."""
|
||||
error = None
|
||||
try:
|
||||
author_id = info.context.get("author", {}).get("id")
|
||||
@@ -273,18 +318,64 @@ async def notifications_seen_thread(_: None, info: GraphQLResolveInfo, thread: s
|
||||
error = None
|
||||
author_id = info.context.get("author", {}).get("id")
|
||||
if author_id:
|
||||
[shout_id, reply_to_id] = thread.split(":")
|
||||
with local_session() as session:
|
||||
# Convert Unix timestamp to datetime for PostgreSQL compatibility
|
||||
after_datetime = datetime.fromtimestamp(after, tz=UTC) if after else None
|
||||
|
||||
# TODO: handle new follower and new shout notifications
|
||||
# Handle different thread types: shout reactions, followers, or new shouts
|
||||
if thread == "followers":
|
||||
# Mark follower notifications as seen
|
||||
query_conditions = [
|
||||
Notification.entity == NotificationEntity.AUTHOR.value,
|
||||
]
|
||||
if after_datetime:
|
||||
query_conditions.append(Notification.created_at > after_datetime)
|
||||
|
||||
follower_notifications = session.query(Notification).where(and_(*query_conditions)).all()
|
||||
for n in follower_notifications:
|
||||
try:
|
||||
ns = NotificationSeen(notification=n.id, viewer=author_id)
|
||||
session.add(ns)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to mark follower notification as seen: {e}")
|
||||
session.commit()
|
||||
return {"error": None}
|
||||
|
||||
# Handle shout and reaction notifications
|
||||
thread_parts = thread.split(":")
|
||||
if len(thread_parts) < 2:
|
||||
return {"error": "Invalid thread format"}
|
||||
|
||||
shout_id = thread_parts[0]
|
||||
reply_to_id = thread_parts[1] if len(thread_parts) > 1 else None
|
||||
|
||||
# Query for new shout notifications in this thread
|
||||
shout_query_conditions = [
|
||||
Notification.entity == NotificationEntity.SHOUT.value,
|
||||
Notification.action == NotificationAction.CREATE.value,
|
||||
]
|
||||
if after_datetime:
|
||||
shout_query_conditions.append(Notification.created_at > after_datetime)
|
||||
|
||||
shout_notifications = session.query(Notification).where(and_(*shout_query_conditions)).all()
|
||||
|
||||
# Mark relevant shout notifications as seen
|
||||
for n in shout_notifications:
|
||||
payload = orjson.loads(str(n.payload))
|
||||
if str(payload.get("id")) == shout_id:
|
||||
try:
|
||||
ns = NotificationSeen(notification=n.id, viewer=author_id)
|
||||
session.add(ns)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to mark shout notification as seen: {e}")
|
||||
|
||||
# Query for reaction notifications
|
||||
if after_datetime:
|
||||
new_reaction_notifications = (
|
||||
session.query(Notification)
|
||||
.where(
|
||||
Notification.action == "create",
|
||||
Notification.entity == "reaction",
|
||||
Notification.action == NotificationAction.CREATE.value,
|
||||
Notification.entity == NotificationEntity.REACTION.value,
|
||||
Notification.created_at > after_datetime,
|
||||
)
|
||||
.all()
|
||||
@@ -292,8 +383,8 @@ async def notifications_seen_thread(_: None, info: GraphQLResolveInfo, thread: s
|
||||
removed_reaction_notifications = (
|
||||
session.query(Notification)
|
||||
.where(
|
||||
Notification.action == "delete",
|
||||
Notification.entity == "reaction",
|
||||
Notification.action == NotificationAction.DELETE.value,
|
||||
Notification.entity == NotificationEntity.REACTION.value,
|
||||
Notification.created_at > after_datetime,
|
||||
)
|
||||
.all()
|
||||
@@ -302,16 +393,16 @@ async def notifications_seen_thread(_: None, info: GraphQLResolveInfo, thread: s
|
||||
new_reaction_notifications = (
|
||||
session.query(Notification)
|
||||
.where(
|
||||
Notification.action == "create",
|
||||
Notification.entity == "reaction",
|
||||
Notification.action == NotificationAction.CREATE.value,
|
||||
Notification.entity == NotificationEntity.REACTION.value,
|
||||
)
|
||||
.all()
|
||||
)
|
||||
removed_reaction_notifications = (
|
||||
session.query(Notification)
|
||||
.where(
|
||||
Notification.action == "delete",
|
||||
Notification.entity == "reaction",
|
||||
Notification.action == NotificationAction.DELETE.value,
|
||||
Notification.entity == NotificationEntity.REACTION.value,
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import asyncio
|
||||
import time
|
||||
import traceback
|
||||
from typing import Any
|
||||
@@ -143,27 +144,29 @@ def is_featured_author(session: Session, author_id: int) -> bool:
|
||||
|
||||
def check_to_feature(session: Session, approver_id: int, reaction: dict) -> bool:
|
||||
"""
|
||||
Make a shout featured if it receives more than 4 votes from authors.
|
||||
Make a shout featured if it receives more than 4 votes from featured authors.
|
||||
|
||||
:param session: Database session.
|
||||
:param approver_id: Approver author ID.
|
||||
:param reaction: Reaction object.
|
||||
:return: True if shout should be featured, else False.
|
||||
"""
|
||||
is_positive_kind = reaction.get("kind") == ReactionKind.LIKE.value
|
||||
# 🔧 Проверяем любую положительную реакцию (LIKE, ACCEPT, PROOF), не только LIKE
|
||||
is_positive_kind = reaction.get("kind") in POSITIVE_REACTIONS
|
||||
if not reaction.get("reply_to") and is_positive_kind:
|
||||
# Проверяем, не содержит ли пост более 20% дизлайков
|
||||
# Если да, то не должен быть featured независимо от количества лайков
|
||||
if check_to_unfeature(session, reaction):
|
||||
return False
|
||||
|
||||
# Собираем всех авторов, поставивших лайк
|
||||
# Собираем всех авторов, поставивших положительную реакцию
|
||||
author_approvers = set()
|
||||
reacted_readers = (
|
||||
session.query(Reaction.created_by)
|
||||
.where(
|
||||
Reaction.shout == reaction.get("shout"),
|
||||
Reaction.kind.in_(POSITIVE_REACTIONS),
|
||||
Reaction.reply_to.is_(None), # не реакция на комментарий
|
||||
# Рейтинги (LIKE, DISLIKE) физически удаляются, поэтому фильтр deleted_at не нужен
|
||||
)
|
||||
.distinct()
|
||||
@@ -189,7 +192,7 @@ def check_to_feature(session: Session, approver_id: int, reaction: dict) -> bool
|
||||
def check_to_unfeature(session: Session, reaction: dict) -> bool:
|
||||
"""
|
||||
Unfeature a shout if:
|
||||
1. Less than 5 positive votes, OR
|
||||
1. Less than 5 positive votes from featured authors, OR
|
||||
2. 20% or more of reactions are negative.
|
||||
|
||||
:param session: Database session.
|
||||
@@ -199,18 +202,8 @@ def check_to_unfeature(session: Session, reaction: dict) -> bool:
|
||||
if not reaction.get("reply_to"):
|
||||
shout_id = reaction.get("shout")
|
||||
|
||||
# Проверяем соотношение дизлайков, даже если текущая реакция не дизлайк
|
||||
total_reactions = (
|
||||
session.query(Reaction)
|
||||
.where(
|
||||
Reaction.shout == shout_id,
|
||||
Reaction.reply_to.is_(None),
|
||||
Reaction.kind.in_(RATING_REACTIONS),
|
||||
# Рейтинги физически удаляются при удалении, поэтому фильтр deleted_at не нужен
|
||||
)
|
||||
.count()
|
||||
)
|
||||
|
||||
# 🔧 Считаем все рейтинговые реакции (положительные + отрицательные)
|
||||
# Используем POSITIVE_REACTIONS + NEGATIVE_REACTIONS вместо только RATING_REACTIONS
|
||||
positive_reactions = (
|
||||
session.query(Reaction)
|
||||
.where(
|
||||
@@ -233,9 +226,13 @@ def check_to_unfeature(session: Session, reaction: dict) -> bool:
|
||||
.count()
|
||||
)
|
||||
|
||||
total_reactions = positive_reactions + negative_reactions
|
||||
|
||||
# Условие 1: Меньше 5 голосов "за"
|
||||
if positive_reactions < 5:
|
||||
logger.debug(f"Публикация {shout_id}: {positive_reactions} лайков (меньше 5) - должна быть unfeatured")
|
||||
logger.debug(
|
||||
f"Публикация {shout_id}: {positive_reactions} положительных реакций (меньше 5) - должна быть unfeatured"
|
||||
)
|
||||
return True
|
||||
|
||||
# Условие 2: Проверяем, составляют ли отрицательные реакции 20% или более от всех реакций
|
||||
@@ -256,6 +253,8 @@ async def set_featured(session: Session, shout_id: int) -> None:
|
||||
:param session: Database session.
|
||||
:param shout_id: Shout ID.
|
||||
"""
|
||||
from cache.revalidator import revalidation_manager
|
||||
|
||||
s = session.query(Shout).where(Shout.id == shout_id).first()
|
||||
if s:
|
||||
current_time = int(time.time())
|
||||
@@ -267,6 +266,22 @@ async def set_featured(session: Session, shout_id: int) -> None:
|
||||
session.add(s)
|
||||
session.commit()
|
||||
|
||||
# 🔧 Ревалидация кеша публикации и связанных сущностей
|
||||
revalidation_manager.mark_for_revalidation(shout_id, "shouts")
|
||||
# Ревалидируем авторов публикации
|
||||
for author in s.authors:
|
||||
revalidation_manager.mark_for_revalidation(author.id, "authors")
|
||||
# Ревалидируем темы публикации
|
||||
for topic in s.topics:
|
||||
revalidation_manager.mark_for_revalidation(topic.id, "topics")
|
||||
|
||||
# 🔧 Инвалидируем ключи кеша лент для обновления featured статусов
|
||||
from cache.cache import invalidate_shout_related_cache
|
||||
|
||||
await invalidate_shout_related_cache(s, s.created_by)
|
||||
|
||||
logger.info(f"Публикация {shout_id} получила статус featured, кеш помечен для ревалидации")
|
||||
|
||||
|
||||
def set_unfeatured(session: Session, shout_id: int) -> None:
|
||||
"""
|
||||
@@ -275,9 +290,33 @@ def set_unfeatured(session: Session, shout_id: int) -> None:
|
||||
:param session: Database session.
|
||||
:param shout_id: Shout ID.
|
||||
"""
|
||||
from cache.revalidator import revalidation_manager
|
||||
|
||||
# Получаем публикацию для доступа к авторам и темам
|
||||
shout = session.query(Shout).where(Shout.id == shout_id).first()
|
||||
if not shout:
|
||||
return
|
||||
|
||||
session.query(Shout).where(Shout.id == shout_id).update({"featured_at": None})
|
||||
session.commit()
|
||||
|
||||
# 🔧 Ревалидация кеша публикации и связанных сущностей
|
||||
revalidation_manager.mark_for_revalidation(shout_id, "shouts")
|
||||
# Ревалидируем авторов публикации
|
||||
for author in shout.authors:
|
||||
revalidation_manager.mark_for_revalidation(author.id, "authors")
|
||||
# Ревалидируем темы публикации
|
||||
for topic in shout.topics:
|
||||
revalidation_manager.mark_for_revalidation(topic.id, "topics")
|
||||
|
||||
# 🔧 Инвалидируем ключи кеша лент для обновления featured статусов
|
||||
from cache.cache import invalidate_shout_related_cache
|
||||
|
||||
# Используем asyncio.create_task для асинхронного вызова
|
||||
asyncio.create_task(invalidate_shout_related_cache(shout, shout.created_by))
|
||||
|
||||
logger.info(f"Публикация {shout_id} потеряла статус featured, кеш помечен для ревалидации")
|
||||
|
||||
|
||||
async def _create_reaction(session: Session, shout_id: int, is_author: bool, author_id: int, reaction: dict) -> dict:
|
||||
"""
|
||||
|
||||
@@ -24,6 +24,7 @@ type Mutation {
|
||||
|
||||
# draft
|
||||
create_draft(draft_input: DraftInput!): CommonResult!
|
||||
create_draft_from_shout(shout_id: Int!): CommonResult!
|
||||
update_draft(draft_id: Int!, draft_input: DraftInput!): CommonResult!
|
||||
delete_draft(draft_id: Int!): CommonResult!
|
||||
# publication
|
||||
|
||||
@@ -245,6 +245,7 @@ type AuthorFollowsResult {
|
||||
topics: [Topic]
|
||||
authors: [Author]
|
||||
communities: [Community]
|
||||
shouts: [Shout]
|
||||
error: String
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ OAUTH_CLIENTS = {
|
||||
}
|
||||
|
||||
# Настройки JWT
|
||||
JWT_SECRET = os.getenv("JWT_SECRET", "your-secret-key")
|
||||
JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", "your-secret-key")
|
||||
JWT_ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
||||
JWT_REFRESH_TOKEN_EXPIRE_DAYS = int(environ.get("JWT_REFRESH_TOKEN_EXPIRE_DAYS", "30"))
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ class EnvService:
|
||||
"POSTGRES_HOST": "database",
|
||||
"POSTGRES_PORT": "database",
|
||||
# Auth
|
||||
"JWT_SECRET": "auth",
|
||||
"JWT_SECRET_KEY": "auth",
|
||||
"JWT_ALGORITHM": "auth",
|
||||
"JWT_EXPIRATION": "auth",
|
||||
"SECRET_KEY": "auth",
|
||||
@@ -103,7 +103,7 @@ class EnvService:
|
||||
|
||||
# Секретные переменные (не показываем их значения в UI)
|
||||
SECRET_VARIABLES: ClassVar[set[str]] = {
|
||||
"JWT_SECRET",
|
||||
"JWT_SECRET_KEY",
|
||||
"SECRET_KEY",
|
||||
"AUTH_SECRET",
|
||||
"OAUTH_GOOGLE_CLIENT_SECRET",
|
||||
@@ -127,7 +127,7 @@ class EnvService:
|
||||
"POSTGRES_DB": "Имя базы данных PostgreSQL",
|
||||
"POSTGRES_HOST": "Хост PostgreSQL",
|
||||
"POSTGRES_PORT": "Порт PostgreSQL",
|
||||
"JWT_SECRET": "Секретный ключ для JWT токенов",
|
||||
"JWT_SECRET_KEY": "Секретный ключ для JWT токенов",
|
||||
"JWT_ALGORITHM": "Алгоритм подписи JWT",
|
||||
"JWT_EXPIRATION": "Время жизни JWT токенов",
|
||||
"SECRET_KEY": "Секретный ключ приложения",
|
||||
|
||||
@@ -64,12 +64,5 @@ def start_sentry() -> None:
|
||||
)
|
||||
logger.info("[utils.sentry] Sentry initialized successfully.")
|
||||
|
||||
# 🧪 Отправляем тестовое событие для проверки работы GlitchTip
|
||||
try:
|
||||
sentry_sdk.capture_message("🧪 GlitchTip test message - система инициализирована", level="info")
|
||||
logger.info("[utils.sentry] Тестовое сообщение отправлено в GlitchTip")
|
||||
except Exception as test_e:
|
||||
logger.warning(f"[utils.sentry] Не удалось отправить тестовое сообщение: {test_e}")
|
||||
|
||||
except (sentry_sdk.utils.BadDsn, ImportError, ValueError, TypeError) as _e:
|
||||
logger.warning("[utils.sentry] Failed to initialize Sentry", exc_info=True)
|
||||
|
||||
Reference in New Issue
Block a user